UNPKG

tldraw

Version:

A tiny little drawing editor.

189 lines (188 loc) • 7.13 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var ArrowBindingUtil_exports = {}; __export(ArrowBindingUtil_exports, { ArrowBindingUtil: () => ArrowBindingUtil, updateArrowTerminal: () => updateArrowTerminal }); module.exports = __toCommonJS(ArrowBindingUtil_exports); var import_editor = require("@tldraw/editor"); var import_shared = require("../../shapes/arrow/shared"); class ArrowBindingUtil extends import_editor.BindingUtil { static type = "arrow"; static props = import_editor.arrowBindingProps; static migrations = import_editor.arrowBindingMigrations; getDefaultProps() { return { isPrecise: false, isExact: false, normalizedAnchor: { x: 0.5, y: 0.5 } }; } // when the binding itself changes onAfterCreate({ binding }) { const arrow = this.editor.getShape(binding.fromId); if (!arrow) return; arrowDidUpdate(this.editor, arrow); } // when the binding itself changes onAfterChange({ bindingAfter }) { const arrow = this.editor.getShape(bindingAfter.fromId); if (!arrow) return; arrowDidUpdate(this.editor, arrow); } // when the arrow itself changes onAfterChangeFromShape({ shapeAfter }) { arrowDidUpdate(this.editor, shapeAfter); } // when the shape an arrow is bound to changes onAfterChangeToShape({ binding }) { reparentArrow(this.editor, binding.fromId); } // when the arrow is isolated we need to update it's x,y positions onBeforeIsolateFromShape({ binding }) { const arrow = this.editor.getShape(binding.fromId); if (!arrow) return; updateArrowTerminal({ editor: this.editor, arrow, terminal: binding.props.terminal }); } } function reparentArrow(editor, arrowId) { const arrow = editor.getShape(arrowId); if (!arrow) return; const bindings = (0, import_shared.getArrowBindings)(editor, arrow); const { start, end } = bindings; const startShape = start ? editor.getShape(start.toId) : void 0; const endShape = end ? editor.getShape(end.toId) : void 0; const parentPageId = editor.getAncestorPageId(arrow); if (!parentPageId) return; let nextParentId; if (startShape && endShape) { nextParentId = editor.findCommonAncestor([startShape, endShape]) ?? parentPageId; } else if (startShape || endShape) { const bindingParentId = (startShape || endShape)?.parentId; if (bindingParentId && bindingParentId === arrow.parentId) { nextParentId = arrow.parentId; } else { nextParentId = parentPageId; } } else { return; } if (nextParentId && nextParentId !== arrow.parentId) { editor.reparentShapes([arrowId], nextParentId); } const reparentedArrow = editor.getShape(arrowId); if (!reparentedArrow) throw Error("no reparented arrow"); const startSibling = editor.getShapeNearestSibling(reparentedArrow, startShape); const endSibling = editor.getShapeNearestSibling(reparentedArrow, endShape); let highestSibling; if (startSibling && endSibling) { highestSibling = startSibling.index > endSibling.index ? startSibling : endSibling; } else if (startSibling && !endSibling) { highestSibling = startSibling; } else if (endSibling && !startSibling) { highestSibling = endSibling; } else { return; } let finalIndex; const higherSiblings = editor.getSortedChildIdsForParent(highestSibling.parentId).map((id) => editor.getShape(id)).filter((sibling) => sibling.index > highestSibling.index); if (higherSiblings.length) { const nextHighestNonArrowSibling = higherSiblings.find((sibling) => sibling.type !== "arrow"); if ( // ...then, if we're above the last shape we want to be above... reparentedArrow.index > highestSibling.index && // ...but below the next non-arrow sibling... (!nextHighestNonArrowSibling || reparentedArrow.index < nextHighestNonArrowSibling.index) ) { return; } finalIndex = (0, import_editor.getIndexBetween)(highestSibling.index, higherSiblings[0].index); } else { finalIndex = (0, import_editor.getIndexAbove)(highestSibling.index); } if (finalIndex !== reparentedArrow.index) { editor.updateShapes([{ id: arrowId, type: "arrow", index: finalIndex }]); } } function arrowDidUpdate(editor, arrow) { const bindings = (0, import_shared.getArrowBindings)(editor, arrow); for (const handle of ["start", "end"]) { const binding = bindings[handle]; if (!binding) continue; const boundShape = editor.getShape(binding.toId); const isShapeInSamePageAsArrow = editor.getAncestorPageId(arrow) === editor.getAncestorPageId(boundShape); if (!boundShape || !isShapeInSamePageAsArrow) { updateArrowTerminal({ editor, arrow, terminal: handle, unbind: true }); } } reparentArrow(editor, arrow.id); } function updateArrowTerminal({ editor, arrow, terminal, unbind = false, useHandle = false }) { const info = (0, import_shared.getArrowInfo)(editor, arrow); if (!info) { throw new Error("expected arrow info"); } const startPoint = useHandle ? info.start.handle : info.start.point; const endPoint = useHandle ? info.end.handle : info.end.point; const point = terminal === "start" ? startPoint : endPoint; const update = { id: arrow.id, type: "arrow", props: { [terminal]: { x: point.x, y: point.y }, bend: arrow.props.bend } }; if (!info.isStraight) { const newStart = terminal === "start" ? startPoint : info.start.handle; const newEnd = terminal === "end" ? endPoint : info.end.handle; const newMidPoint = import_editor.Vec.Med(newStart, newEnd); const lineSegment = import_editor.Vec.Sub(newStart, newEnd).per().uni().mul(info.handleArc.radius * 2 * Math.sign(arrow.props.bend)); const intersections = (0, import_editor.intersectLineSegmentCircle)( info.handleArc.center, import_editor.Vec.Add(newMidPoint, lineSegment), info.handleArc.center, info.handleArc.radius ); (0, import_editor.assert)(intersections?.length === 1); const bend = import_editor.Vec.Dist(newMidPoint, intersections[0]) * Math.sign(arrow.props.bend); if (!(0, import_editor.approximately)(bend, update.props.bend)) { update.props.bend = bend; } } editor.updateShape(update); if (unbind) { (0, import_shared.removeArrowBinding)(editor, arrow, terminal); } } //# sourceMappingURL=ArrowBindingUtil.js.map