diff --git a/packages/core/src/components/container/container.tsx b/packages/core/src/components/container/container.tsx index c88bdb820..9354cbbe6 100644 --- a/packages/core/src/components/container/container.tsx +++ b/packages/core/src/components/container/container.tsx @@ -15,7 +15,7 @@ export const Container = React.memo( const rBounds = usePosition(bounds, rotation) return ( -
+
{children}
) diff --git a/packages/core/src/components/handles/handle.tsx b/packages/core/src/components/handles/handle.tsx index 8fa422bc2..3a698e1ed 100644 --- a/packages/core/src/components/handles/handle.tsx +++ b/packages/core/src/components/handles/handle.tsx @@ -12,26 +12,22 @@ interface HandleProps { export const Handle = React.memo(({ id, point }: HandleProps) => { const events = useHandleEvents(id) - const bounds = React.useMemo( - () => - Utils.translateBounds( + return ( + + )} + > - + diff --git a/packages/core/src/components/shape/shape.tsx b/packages/core/src/components/shape/shape.tsx index 47ba6047c..32b359dee 100644 --- a/packages/core/src/components/shape/shape.tsx +++ b/packages/core/src/components/shape/shape.tsx @@ -25,12 +25,7 @@ export const Shape = ({ const events = useShapeEvents(shape.id, isCurrentParent) return ( - + .tl-handle-bg { + .tl-handle:hover .tl-handle-bg { fill: var(--tl-selectFill); } - .tl-handles:hover > .tl-handle-bg > * { + .tl-handle:hover .tl-handle-bg > * { stroke: var(--tl-selectFill); } - .tl-handles:active > .tl-handle-bg { + .tl-handle:active .tl-handle-bg { fill: var(--tl-selectFill); } - .tl-handles:active > .tl-handle-bg > * { + .tl-handle:active .tl-handle-bg > * { stroke: var(--tl-selectFill); } @@ -309,7 +309,7 @@ const tlcss = css` fill: transparent; stroke: none; pointer-events: all; - r: calc(20 / max(1, var(--tl-zoom))); + r: calc(20px / max(1, var(--tl-zoom))); } .tl-binding-indicator { diff --git a/packages/core/src/shapes/createShape.tsx b/packages/core/src/shapes/createShape.tsx index f9d274f2d..a516db99e 100644 --- a/packages/core/src/shapes/createShape.tsx +++ b/packages/core/src/shapes/createShape.tsx @@ -206,8 +206,13 @@ export const ShapeUtil = function (() => const sw = strokeWidth * 1.618 + const dots = getArcPoints(shape) + return ( @@ -249,7 +251,7 @@ export const Arrow = new ShapeUtil(() => }, Indicator({ shape }) { - const path = Utils.getFromCache(simplePathCache, shape.handles, () => getArrowPath(shape)) + const path = getArrowPath(shape) return }, @@ -260,20 +262,25 @@ export const Arrow = new ShapeUtil(() => getBounds(shape) { const bounds = Utils.getFromCache(this.boundsCache, shape, () => { - const { start, bend, end } = shape.handles - return Utils.getBoundsFromPoints([start.point, bend.point, end.point]) + const points = getArcPoints(shape) + return Utils.getBoundsFromPoints(points) }) return Utils.translateBounds(bounds, shape.point) }, getRotatedBounds(shape) { - const { start, bend, end } = shape.handles + let points = getArcPoints(shape) - return Utils.translateBounds( - Utils.getBoundsFromPoints([start.point, bend.point, end.point], shape.rotation), - shape.point - ) + const { minX, minY, maxX, maxY } = Utils.getBoundsFromPoints(points) + + if (shape.rotation !== 0) { + points = points.map((pt) => + Vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], shape.rotation || 0) + ) + } + + return Utils.translateBounds(Utils.getBoundsFromPoints(points), shape.point) }, getCenter(shape) { @@ -544,11 +551,10 @@ export const Arrow = new ShapeUtil(() => // Zero out the handles to prevent handles with negative points. If a handle's x or y // is below zero, we need to move the shape left or up to make it zero. - const bounds = Utils.getBoundsFromPoints( - Object.values(nextShape.handles).map((handle) => handle.point) - ) + const topLeft = shape.point + const nextBounds = this.getBounds({ ...nextShape } as ArrowShape) - const offset = [bounds.minX, bounds.minY] + const offset = Vec.sub([nextBounds.minX, nextBounds.minY], topLeft) if (!Vec.isEqual(offset, [0, 0])) { Object.values(nextShape.handles).forEach((handle) => { @@ -766,3 +772,22 @@ function getArrowPath(shape: ArrowShape) { return path.join(' ') } + +function getArcPoints(shape: ArrowShape) { + const { start, bend, end } = shape.handles + const points: number[][] = [start.point, end.point] + + if (Vec.dist2(bend.point, Vec.med(start.point, end.point)) > 4) { + // We're an arc, calculate points along the arc + const { center, radius } = getArrowArc(shape) + const startAngle = Vec.angle(center, start.point) + const endAngle = Vec.angle(center, end.point) + + for (let i = 1 / 20; i < 1; i += 1 / 20) { + const angle = Utils.lerpAngles(startAngle, endAngle, i) + points.push(Vec.nudgeAtAngle(center, angle, radius)) + } + } + + return points +} diff --git a/packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts b/packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts index 1f8415f79..d778276d4 100644 --- a/packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts +++ b/packages/tldraw/src/state/session/sessions/arrow/arrow.session.ts @@ -10,6 +10,7 @@ import { import { Vec } from '@tldraw/vec' import { Utils } from '@tldraw/core' import { TLDR } from '~state/tldr' +import { ThickArrowDownIcon } from '@radix-ui/react-icons' export class ArrowSession implements Session { id = 'transform_single' @@ -18,6 +19,7 @@ export class ArrowSession implements Session { delta = [0, 0] offset = [0, 0] origin: number[] + topLeft: number[] initialShape: ArrowShape handleId: 'start' | 'end' bindableShapeIds: string[] @@ -33,6 +35,7 @@ export class ArrowSession implements Session { this.origin = point this.handleId = handleId this.initialShape = TLDR.getShape(data, shapeId, data.appState.currentPageId) + this.topLeft = this.initialShape.point this.bindableShapeIds = TLDR.getBindableShapeIds(data) const initialBindingId = this.initialShape.handles[this.handleId].bindingId @@ -66,7 +69,9 @@ export class ArrowSession implements Session { } // First update the handle's next point - const change = TLDR.getShapeUtils(shape.type).onHandleChange( + const utils = TLDR.getShapeUtils(shape.type) + + const change = utils.onHandleChange( shape, { [handleId]: handle, diff --git a/packages/tldraw/src/state/session/sessions/handle/handle.session.ts b/packages/tldraw/src/state/session/sessions/handle/handle.session.ts index d40f313fc..5b93ed8e0 100644 --- a/packages/tldraw/src/state/session/sessions/handle/handle.session.ts +++ b/packages/tldraw/src/state/session/sessions/handle/handle.session.ts @@ -9,6 +9,7 @@ export class HandleSession implements Session { status = TLDrawStatus.TranslatingHandle commandId: string delta = [0, 0] + topLeft: number[] origin: number[] shiftKey = false initialShape: ShapesWithProp<'handles'> @@ -17,6 +18,7 @@ export class HandleSession implements Session { constructor(data: Data, handleId: string, point: number[], commandId = 'move_handle') { const { currentPageId } = data.appState const shapeId = TLDR.getSelectedIds(data, currentPageId)[0] + this.topLeft = point this.origin = point this.handleId = handleId this.initialShape = TLDR.getShape(data, shapeId, currentPageId) @@ -43,6 +45,7 @@ export class HandleSession implements Session { } // First update the handle's next point + const change = TLDR.getShapeUtils(shape).onHandleChange( shape, { diff --git a/packages/tldraw/tsconfig.json b/packages/tldraw/tsconfig.json index 734e77e6f..d94705f05 100644 --- a/packages/tldraw/tsconfig.json +++ b/packages/tldraw/tsconfig.json @@ -6,7 +6,6 @@ "outDir": "./dist/types", "rootDir": "src", "baseUrl": "src", - "emitDeclarationOnly": false, "paths": { "~*": ["./*"], "@tldraw/core": ["../core"],