Move SVG container to shape implementations

This commit is contained in:
Steve Ruiz 2021-09-11 17:21:10 +01:00
parent 4c41d98c8e
commit 5359e92771
10 changed files with 138 additions and 106 deletions

View file

@ -1,2 +1,3 @@
export * from './renderer' export * from './renderer'
export { brushUpdater } from './brush' export { brushUpdater } from './brush'
export * from './svg-container'

View file

@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as React from 'react' import * as React from 'react'
import { usePosition, useShapeEvents } from '+hooks' import { useShapeEvents } from '+hooks'
import type { IShapeTreeNode, TLBounds, TLShape, TLShapeUtil } from '+types' import type { IShapeTreeNode, TLShape, TLShapeUtil } from '+types'
import { RenderedShape } from './rendered-shape' import { RenderedShape } from './rendered-shape'
import { EditingTextShape } from './editing-text-shape' import { EditingTextShape } from './editing-text-shape'
import { Container } from '+components/container' import { Container } from '+components/container'
import { SVGContainer } from '+components/svg-container'
// function setTransform(elm: HTMLDivElement, bounds: TLBounds, rotation = 0) { // function setTransform(elm: HTMLDivElement, bounds: TLBounds, rotation = 0) {
// const transform = ` // const transform = `
@ -43,33 +42,31 @@ export const Shape = <
bounds={bounds} bounds={bounds}
rotation={shape.rotation} rotation={shape.rotation}
> >
<SVGContainer> {isEditing && utils.isEditableText ? (
{isEditing && utils.isEditableText ? ( <EditingTextShape
<EditingTextShape shape={shape}
shape={shape} isBinding={false}
isBinding={false} isCurrentParent={false}
isCurrentParent={false} isEditing={true}
isEditing={true} isHovered={isHovered}
isHovered={isHovered} isSelected={isSelected}
isSelected={isSelected} utils={utils as any}
utils={utils as any} meta={meta as any}
meta={meta as any} events={events}
events={events} />
/> ) : (
) : ( <RenderedShape
<RenderedShape shape={shape}
shape={shape} isBinding={isBinding}
isBinding={isBinding} isCurrentParent={isCurrentParent}
isCurrentParent={isCurrentParent} isEditing={isEditing}
isEditing={isEditing} isHovered={isHovered}
isHovered={isHovered} isSelected={isSelected}
isSelected={isSelected} utils={utils as any}
utils={utils as any} meta={meta as any}
meta={meta as any} events={events}
events={events} />
/> )}
)}
</SVGContainer>
</Container> </Container>
) )
} }

View file

@ -1,13 +1,15 @@
import * as React from 'react' import * as React from 'react'
interface SvgContainerProps { interface SvgContainerProps extends React.SVGProps<SVGSVGElement> {
children: React.ReactNode children: React.ReactNode
} }
export const SVGContainer = React.memo(({ children }: SvgContainerProps) => { export const SVGContainer = React.memo(
return ( React.forwardRef<SVGSVGElement, SvgContainerProps>(({ children, ...rest }, ref) => {
<svg className="tl-positioned-svg"> return (
<g className="tl-centered-g">{children}</g> <svg ref={ref} className="tl-positioned-svg" {...rest}>
</svg> <g className="tl-centered-g">{children}</g>
) </svg>
}) )
})
)

View file

@ -151,7 +151,6 @@ const tlcss = css`
.tl-positioned-svg { .tl-positioned-svg {
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
.tl-layer { .tl-layer {

View file

@ -1,5 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { import {
SVGContainer,
TLBounds, TLBounds,
Utils, Utils,
Vec, Vec,
@ -20,10 +21,9 @@ import {
DashStyle, DashStyle,
TLDrawShape, TLDrawShape,
ArrowBinding, ArrowBinding,
TLDrawRenderInfo,
} from '~types' } from '~types'
export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGGElement> { export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGSVGElement> {
type = TLDrawShapeType.Arrow as const type = TLDrawShapeType.Arrow as const
toolType = TLDrawToolType.Handle toolType = TLDrawToolType.Handle
canStyleFill = false canStyleFill = false
@ -71,7 +71,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGGElement> {
return next.handles !== prev.handles || next.style !== prev.style return next.handles !== prev.handles || next.style !== prev.style
} }
render = React.forwardRef<SVGGElement, TLShapeProps<ArrowShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<ArrowShape, SVGSVGElement>>(
({ shape, meta, events }, ref) => { ({ shape, meta, events }, ref) => {
const { const {
handles: { start, bend, end }, handles: { start, bend, end },
@ -220,7 +220,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGGElement> {
const sw = strokeWidth * 1.618 const sw = strokeWidth * 1.618
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
<g pointerEvents="none"> <g pointerEvents="none">
{shaftPath} {shaftPath}
{startArrowHead && ( {startArrowHead && (
@ -250,7 +250,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape, SVGGElement> {
/> />
)} )}
</g> </g>
</g> </SVGContainer>
) )
} }
) )

View file

@ -1,10 +1,18 @@
import * as React from 'react' import * as React from 'react'
import { TLBounds, Utils, Vec, TLTransformInfo, Intersect, TLShapeProps } from '@tldraw/core' import {
SVGContainer,
TLBounds,
Utils,
Vec,
TLTransformInfo,
Intersect,
TLShapeProps,
} from '@tldraw/core'
import getStroke, { getStrokePoints } from 'perfect-freehand' import getStroke, { getStrokePoints } from 'perfect-freehand'
import { defaultStyle, getShapeStyle } from '~shape/shape-styles' import { defaultStyle, getShapeStyle } from '~shape/shape-styles'
import { DrawShape, DashStyle, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType } from '~types' import { DrawShape, DashStyle, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType } from '~types'
export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> { export class Draw extends TLDrawShapeUtil<DrawShape, SVGSVGElement> {
type = TLDrawShapeType.Draw as const type = TLDrawShapeType.Draw as const
toolType = TLDrawToolType.Draw toolType = TLDrawToolType.Draw
@ -30,7 +38,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
return next.points !== prev.points || next.style !== prev.style return next.points !== prev.points || next.style !== prev.style
} }
render = React.forwardRef<SVGGElement, TLShapeProps<DrawShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<DrawShape, SVGSVGElement>>(
({ shape, meta, events, isEditing }, ref) => { ({ shape, meta, events, isEditing }, ref) => {
const { points, style } = shape const { points, style } = shape
@ -47,7 +55,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
const sw = strokeWidth * 0.618 const sw = strokeWidth * 0.618
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
<circle <circle
r={strokeWidth * 0.618} r={strokeWidth * 0.618}
fill={styles.stroke} fill={styles.stroke}
@ -55,7 +63,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
strokeWidth={sw} strokeWidth={sw}
pointerEvents="all" pointerEvents="all"
/> />
</g> </SVGContainer>
) )
} }
@ -76,7 +84,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
: Utils.getFromCache(this.drawPathCache, points, () => getDrawStrokePath(shape, false)) : Utils.getFromCache(this.drawPathCache, points, () => getDrawStrokePath(shape, false))
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{shouldFill && ( {shouldFill && (
<path <path
d={polygonPathData} d={polygonPathData}
@ -96,7 +104,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
strokeLinecap="round" strokeLinecap="round"
pointerEvents="all" pointerEvents="all"
/> />
</g> </SVGContainer>
) )
} }
@ -121,7 +129,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
const sw = strokeWidth * 1.618 const sw = strokeWidth * 1.618
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
<path <path
d={path} d={path}
fill={shouldFill ? styles.fill : 'none'} fill={shouldFill ? styles.fill : 'none'}
@ -142,7 +150,7 @@ export class Draw extends TLDrawShapeUtil<DrawShape, SVGGElement> {
strokeLinecap="round" strokeLinecap="round"
pointerEvents="stroke" pointerEvents="stroke"
/> />
</g> </SVGContainer>
) )
} }
) )

View file

@ -1,10 +1,17 @@
import * as React from 'react' import * as React from 'react'
import { Utils, TLTransformInfo, TLBounds, Intersect, TLShapeProps, Vec } from '@tldraw/core' import {
SVGContainer,
Utils,
TLTransformInfo,
TLBounds,
Intersect,
TLShapeProps,
Vec,
} from '@tldraw/core'
import { import {
ArrowShape, ArrowShape,
DashStyle, DashStyle,
EllipseShape, EllipseShape,
TLDrawRenderInfo,
TLDrawShapeType, TLDrawShapeType,
TLDrawShapeUtil, TLDrawShapeUtil,
TLDrawToolType, TLDrawToolType,
@ -15,7 +22,7 @@ import getStroke from 'perfect-freehand'
// TODO // TODO
// [ ] Improve indicator shape for drawn shapes // [ ] Improve indicator shape for drawn shapes
export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> { export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGSVGElement> {
type = TLDrawShapeType.Ellipse as const type = TLDrawShapeType.Ellipse as const
toolType = TLDrawToolType.Bounds toolType = TLDrawToolType.Bounds
pathCache = new WeakMap<EllipseShape, string>([]) pathCache = new WeakMap<EllipseShape, string>([])
@ -37,7 +44,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> {
return next.radius !== prev.radius || next.style !== prev.style return next.radius !== prev.radius || next.style !== prev.style
} }
render = React.forwardRef<SVGGElement, TLShapeProps<EllipseShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<EllipseShape, SVGSVGElement>>(
({ shape, meta, isBinding, events }, ref) => { ({ shape, meta, isBinding, events }, ref) => {
const { const {
radius: [radiusX, radiusY], radius: [radiusX, radiusY],
@ -56,7 +63,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> {
) )
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{isBinding && ( {isBinding && (
<ellipse <ellipse
className="tl-binding-indicator" className="tl-binding-indicator"
@ -84,7 +91,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> {
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
</g> </SVGContainer>
) )
} }
@ -102,7 +109,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> {
const sw = strokeWidth * 1.618 const sw = strokeWidth * 1.618
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{isBinding && ( {isBinding && (
<ellipse <ellipse
className="tl-binding-indicator" className="tl-binding-indicator"
@ -126,7 +133,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape, SVGGElement> {
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
</g> </SVGContainer>
) )
} }
) )

View file

@ -1,5 +1,5 @@
import * as React from 'react' import * as React from 'react'
import { TLBounds, Utils, Vec, Intersect, TLShapeProps } from '@tldraw/core' import { SVGContainer, TLBounds, Utils, Vec, Intersect, TLShapeProps } from '@tldraw/core'
import { defaultStyle, getPerfectDashProps } from '~shape/shape-styles' import { defaultStyle, getPerfectDashProps } from '~shape/shape-styles'
import { import {
GroupShape, GroupShape,
@ -14,7 +14,7 @@ import {
// TODO // TODO
// [ ] - Find bounds based on common bounds of descendants // [ ] - Find bounds based on common bounds of descendants
export class Group extends TLDrawShapeUtil<GroupShape, SVGGElement> { export class Group extends TLDrawShapeUtil<GroupShape, SVGSVGElement> {
type = TLDrawShapeType.Group as const type = TLDrawShapeType.Group as const
toolType = TLDrawToolType.Bounds toolType = TLDrawToolType.Bounds
canBind = true canBind = true
@ -38,7 +38,7 @@ export class Group extends TLDrawShapeUtil<GroupShape, SVGGElement> {
return next.size !== prev.size || next.style !== prev.style return next.size !== prev.size || next.style !== prev.style
} }
render = React.forwardRef<SVGGElement, TLShapeProps<GroupShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<GroupShape, SVGSVGElement>>(
({ shape, isBinding, isHovered, isSelected, events }, ref) => { ({ shape, isBinding, isHovered, isSelected, events }, ref) => {
const { id, size } = shape const { id, size } = shape
@ -77,7 +77,7 @@ export class Group extends TLDrawShapeUtil<GroupShape, SVGGElement> {
}) })
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{isBinding && ( {isBinding && (
<rect <rect
className="tl-binding-indicator" className="tl-binding-indicator"
@ -96,7 +96,7 @@ export class Group extends TLDrawShapeUtil<GroupShape, SVGGElement> {
pointerEvents="all" pointerEvents="all"
/> />
<g pointerEvents="stroke">{paths}</g> <g pointerEvents="stroke">{paths}</g>
</g> </SVGContainer>
) )
} }
) )

View file

@ -1,5 +1,13 @@
import * as React from 'react' import * as React from 'react'
import { TLBounds, Utils, Vec, TLTransformInfo, Intersect, TLShapeProps } from '@tldraw/core' import {
TLBounds,
Utils,
Vec,
TLTransformInfo,
Intersect,
TLShapeProps,
SVGContainer,
} from '@tldraw/core'
import getStroke from 'perfect-freehand' import getStroke from 'perfect-freehand'
import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles' import { getPerfectDashProps, defaultStyle, getShapeStyle } from '~shape/shape-styles'
import { import {
@ -14,7 +22,7 @@ import {
// TODO // TODO
// [ ] - Make sure that fill does not extend drawn shape at corners // [ ] - Make sure that fill does not extend drawn shape at corners
export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGGElement> { export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGSVGElement> {
type = TLDrawShapeType.Rectangle as const type = TLDrawShapeType.Rectangle as const
toolType = TLDrawToolType.Bounds toolType = TLDrawToolType.Bounds
canBind = true canBind = true
@ -36,7 +44,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGGElement> {
return next.size !== prev.size || next.style !== prev.style return next.size !== prev.size || next.style !== prev.style
} }
render = React.forwardRef<SVGGElement, TLShapeProps<RectangleShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<RectangleShape, SVGSVGElement>>(
({ shape, isBinding, meta, events }, ref) => { ({ shape, isBinding, meta, events }, ref) => {
const { id, size, style } = shape const { id, size, style } = shape
const styles = getShapeStyle(style, meta.isDarkMode) const styles = getShapeStyle(style, meta.isDarkMode)
@ -46,7 +54,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGGElement> {
const pathData = Utils.getFromCache(this.pathCache, shape.size, () => renderPath(shape)) const pathData = Utils.getFromCache(this.pathCache, shape.size, () => renderPath(shape))
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{isBinding && ( {isBinding && (
<rect <rect
className="tl-binding-indicator" className="tl-binding-indicator"
@ -72,7 +80,7 @@ export class Rectangle extends TLDrawShapeUtil<RectangleShape, SVGGElement> {
strokeWidth={styles.strokeWidth} strokeWidth={styles.strokeWidth}
pointerEvents="all" pointerEvents="all"
/> />
</g> </SVGContainer>
) )
} }

View file

@ -1,5 +1,13 @@
import * as React from 'react' import * as React from 'react'
import { TLBounds, Utils, Vec, TLTransformInfo, Intersect, TLShapeProps } from '@tldraw/core' import {
SVGContainer,
TLBounds,
Utils,
Vec,
TLTransformInfo,
Intersect,
TLShapeProps,
} from '@tldraw/core'
import { getShapeStyle, getFontSize, getFontStyle, defaultStyle } from '~shape/shape-styles' import { getShapeStyle, getFontSize, getFontStyle, defaultStyle } from '~shape/shape-styles'
import { TextShape, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType, ArrowShape } from '~types' import { TextShape, TLDrawShapeUtil, TLDrawShapeType, TLDrawToolType, ArrowShape } from '~types'
import styled from '~styles' import styled from '~styles'
@ -49,7 +57,7 @@ if (typeof window !== 'undefined') {
melm = getMeasurementDiv() melm = getMeasurementDiv()
} }
export class Text extends TLDrawShapeUtil<TextShape, SVGGElement> { export class Text extends TLDrawShapeUtil<TextShape, SVGSVGElement> {
type = TLDrawShapeType.Text as const type = TLDrawShapeType.Text as const
toolType = TLDrawToolType.Text toolType = TLDrawToolType.Text
isAspectRatioLocked = true isAspectRatioLocked = true
@ -83,7 +91,7 @@ export class Text extends TLDrawShapeUtil<TextShape, SVGGElement> {
) )
} }
render = React.forwardRef<SVGGElement, TLShapeProps<TextShape, SVGGElement>>( render = React.forwardRef<SVGSVGElement, TLShapeProps<TextShape, SVGSVGElement>>(
({ shape, meta, isEditing, isBinding, events }, ref) => { ({ shape, meta, isEditing, isBinding, events }, ref) => {
const rInput = React.useRef<HTMLTextAreaElement>(null) const rInput = React.useRef<HTMLTextAreaElement>(null)
const { id, text, style } = shape const { id, text, style } = shape
@ -151,7 +159,7 @@ export class Text extends TLDrawShapeUtil<TextShape, SVGGElement> {
if (!isEditing) { if (!isEditing) {
return ( return (
<g ref={ref} {...events}> <SVGContainer ref={ref} {...events}>
{isBinding && ( {isBinding && (
<rect <rect
className="tl-binding-indicator" className="tl-binding-indicator"
@ -183,41 +191,43 @@ export class Text extends TLDrawShapeUtil<TextShape, SVGGElement> {
{str} {str}
</text> </text>
))} ))}
</g> </SVGContainer>
) )
} }
return ( return (
<foreignObject <SVGContainer ref={ref} {...events}>
width={bounds.width} <foreignObject
height={bounds.height} width={bounds.width}
pointerEvents="none" height={bounds.height}
onPointerDown={(e) => e.stopPropagation()} pointerEvents="none"
> onPointerDown={(e) => e.stopPropagation()}
<StyledTextArea >
ref={rInput} <StyledTextArea
style={{ ref={rInput}
font, style={{
color: styles.stroke, font,
}} color: styles.stroke,
name="text" }}
defaultValue={text} name="text"
tabIndex={-1} defaultValue={text}
autoComplete="false" tabIndex={-1}
autoCapitalize="false" autoComplete="false"
autoCorrect="false" autoCapitalize="false"
autoSave="false" autoCorrect="false"
placeholder="" autoSave="false"
color={styles.stroke} placeholder=""
autoFocus={true} color={styles.stroke}
onFocus={handleFocus} autoFocus={true}
onBlur={handleBlur} onFocus={handleFocus}
onKeyDown={handleKeyDown} onBlur={handleBlur}
onKeyUp={handleKeyUp} onKeyDown={handleKeyDown}
onChange={handleChange} onKeyUp={handleKeyUp}
onPointerDown={handlePointerDown} onChange={handleChange}
/> onPointerDown={handlePointerDown}
</foreignObject> />
</foreignObject>
</SVGContainer>
) )
} }
) )