UNPKG

tldraw

Version:

A tiny little drawing editor.

147 lines (146 loc) 5.1 kB
import { Group2d, Mat, Vec } from "@tldraw/editor"; import { createComputedCache } from "@tldraw/store"; import { getCurvedArrowInfo } from "./curved-arrow.mjs"; import { getStraightArrowInfo } from "./straight-arrow.mjs"; const MIN_ARROW_BEND = 8; function getIsArrowStraight(shape) { return Math.abs(shape.props.bend) < MIN_ARROW_BEND * shape.props.scale; } function getBoundShapeInfoForTerminal(editor, arrow, terminalName) { const binding = editor.getBindingsFromShape(arrow, "arrow").find((b) => b.props.terminal === terminalName); if (!binding) return; const boundShape = editor.getShape(binding.toId); if (!boundShape) return; const transform = editor.getShapePageTransform(boundShape); const geometry = editor.getShapeGeometry(boundShape); const outline = geometry instanceof Group2d ? geometry.children[0].vertices : geometry.vertices; return { shape: boundShape, transform, isClosed: geometry.isClosed, isExact: binding.props.isExact, didIntersect: false, outline }; } function getArrowTerminalInArrowSpace(editor, arrowPageTransform, binding, forceImprecise) { const boundShape = editor.getShape(binding.toId); if (!boundShape) { return new Vec(0, 0); } else { const { point, size } = editor.getShapeGeometry(boundShape).bounds; const shapePoint = Vec.Add( point, Vec.MulV( // if the parent is the bound shape, then it's ALWAYS precise binding.props.isPrecise || forceImprecise ? binding.props.normalizedAnchor : { x: 0.5, y: 0.5 }, size ) ); const pagePoint = Mat.applyToPoint(editor.getShapePageTransform(boundShape), shapePoint); const arrowPoint = Mat.applyToPoint(Mat.Inverse(arrowPageTransform), pagePoint); return arrowPoint; } } function getArrowBindings(editor, shape) { const bindings = editor.getBindingsFromShape(shape, "arrow"); return { start: bindings.find((b) => b.props.terminal === "start"), end: bindings.find((b) => b.props.terminal === "end") }; } const arrowInfoCache = createComputedCache("arrow info", (editor, shape) => { const bindings = getArrowBindings(editor, shape); return getIsArrowStraight(shape) ? getStraightArrowInfo(editor, shape, bindings) : getCurvedArrowInfo(editor, shape, bindings); }); function getArrowInfo(editor, shape) { const id = typeof shape === "string" ? shape : shape.id; return arrowInfoCache.get(editor, id); } function getArrowTerminalsInArrowSpace(editor, shape, bindings) { const arrowPageTransform = editor.getShapePageTransform(shape); const boundShapeRelationships = getBoundShapeRelationships( editor, bindings.start?.toId, bindings.end?.toId ); const start = bindings.start ? getArrowTerminalInArrowSpace( editor, arrowPageTransform, bindings.start, boundShapeRelationships === "double-bound" || boundShapeRelationships === "start-contains-end" ) : Vec.From(shape.props.start); const end = bindings.end ? getArrowTerminalInArrowSpace( editor, arrowPageTransform, bindings.end, boundShapeRelationships === "double-bound" || boundShapeRelationships === "end-contains-start" ) : Vec.From(shape.props.end); return { start, end }; } function createOrUpdateArrowBinding(editor, arrow, target, props) { const arrowId = typeof arrow === "string" ? arrow : arrow.id; const targetId = typeof target === "string" ? target : target.id; const existingMany = editor.getBindingsFromShape(arrowId, "arrow").filter((b) => b.props.terminal === props.terminal); if (existingMany.length > 1) { editor.deleteBindings(existingMany.slice(1)); } const existing = existingMany[0]; if (existing) { editor.updateBinding({ ...existing, toId: targetId, props }); } else { editor.createBinding({ type: "arrow", fromId: arrowId, toId: targetId, props }); } } function removeArrowBinding(editor, arrow, terminal) { const existing = editor.getBindingsFromShape(arrow, "arrow").filter((b) => b.props.terminal === terminal); editor.deleteBindings(existing); } const MIN_ARROW_LENGTH = 10; const BOUND_ARROW_OFFSET = 10; const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10; const STROKE_SIZES = { s: 2, m: 3.5, l: 5, xl: 10 }; function getBoundShapeRelationships(editor, startShapeId, endShapeId) { if (!startShapeId || !endShapeId) return "safe"; if (startShapeId === endShapeId) return "double-bound"; const startBounds = editor.getShapePageBounds(startShapeId); const endBounds = editor.getShapePageBounds(endShapeId); if (startBounds && endBounds) { if (startBounds.contains(endBounds)) return "start-contains-end"; if (endBounds.contains(startBounds)) return "end-contains-start"; } return "safe"; } export { BOUND_ARROW_OFFSET, MIN_ARROW_LENGTH, STROKE_SIZES, WAY_TOO_BIG_ARROW_BEND_FACTOR, createOrUpdateArrowBinding, getArrowBindings, getArrowInfo, getArrowTerminalsInArrowSpace, getBoundShapeInfoForTerminal, getBoundShapeRelationships, getIsArrowStraight, removeArrowBinding }; //# sourceMappingURL=shared.mjs.map