@tldraw/editor 4.6.0-canary.4d72d3cf0933 → 4.6.0-canary.4e3f067c74e9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/components/MenuClickCapture.js +16 -1
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +2 -1
- package/dist-cjs/lib/utils/reparenting.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/components/MenuClickCapture.mjs +16 -1
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +2 -1
- package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/components/MenuClickCapture.tsx +20 -0
- package/src/lib/utils/reparenting.ts +6 -2
- package/src/version.ts +3 -3
package/dist-cjs/index.js
CHANGED
|
@@ -385,7 +385,7 @@ var import_uniq = require("./lib/utils/uniq");
|
|
|
385
385
|
var import_defaultThemes2 = require("./lib/editor/managers/ThemeManager/defaultThemes");
|
|
386
386
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
387
387
|
"@tldraw/editor",
|
|
388
|
-
"4.6.0-canary.
|
|
388
|
+
"4.6.0-canary.4e3f067c74e9",
|
|
389
389
|
"cjs"
|
|
390
390
|
);
|
|
391
391
|
//# sourceMappingURL=index.js.map
|
|
@@ -50,6 +50,17 @@ function MenuClickCapture() {
|
|
|
50
50
|
};
|
|
51
51
|
rDidAPointerDownAndDragWhileMenuWasOpen.current = false;
|
|
52
52
|
}
|
|
53
|
+
if (e.button === 2) {
|
|
54
|
+
const ownerDocument = editor.getContainerDocument();
|
|
55
|
+
ownerDocument.addEventListener(
|
|
56
|
+
"contextmenu",
|
|
57
|
+
(event) => {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
event.stopImmediatePropagation();
|
|
60
|
+
},
|
|
61
|
+
{ capture: true, once: true }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
53
64
|
editor.menus.clearOpenMenus();
|
|
54
65
|
},
|
|
55
66
|
[editor]
|
|
@@ -110,7 +121,11 @@ function MenuClickCapture() {
|
|
|
110
121
|
...canvasEvents,
|
|
111
122
|
onPointerDown: handlePointerDown,
|
|
112
123
|
onPointerMove: handlePointerMove,
|
|
113
|
-
onPointerUp: handlePointerUp
|
|
124
|
+
onPointerUp: handlePointerUp,
|
|
125
|
+
onContextMenu: (e) => {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
e.stopPropagation();
|
|
128
|
+
}
|
|
114
129
|
}
|
|
115
130
|
);
|
|
116
131
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/components/MenuClickCapture.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { PointerEvent, useCallback, useRef, useState } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { getPointerInfo } from '../utils/getPointerInfo'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\t// Whether any menus are open\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Whether we're pointing or not\u2014keep this component visible if we're pointing\n\tconst [isPointing, setIsPointing] = useState(false)\n\n\tconst showElement = isMenuOpen || isPointing\n\n\t// Get the same events that we use on the canvas\n\tconst canvasEvents = useCanvasEvents()\n\n\t// Keep track of the pointer state\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tstart: new Vec(),\n\t})\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tif (e.button === 0) {\n\t\t\t\tsetIsPointing(true)\n\t\t\t\trPointerState.current = {\n\t\t\t\t\tisDown: true,\n\t\t\t\t\tisDragging: false,\n\t\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t\t}\n\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t\t}\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst rDidAPointerDownAndDragWhileMenuWasOpen = useRef(false)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Do nothing unless we're pointing\n\t\t\tif (!rPointerState.current.isDown) return\n\n\t\t\t// call the onPointerDown with the original pointer position\n\t\t\tconst { x, y } = rPointerState.current.start\n\n\t\t\tif (!rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\tif (\n\t\t\t\t\t// We're pointing, but are we dragging?\n\t\t\t\t\tVec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = true\n\t\t\t\t\t// Wehaddaeventitsadrag\n\t\t\t\t\trPointerState.current = {\n\t\t\t\t\t\t...rPointerState.current,\n\t\t\t\t\t\tisDown: true,\n\t\t\t\t\t\tisDragging: true,\n\t\t\t\t\t}\n\t\t\t\t\tcanvasEvents.onPointerDown?.({\n\t\t\t\t\t\t...e,\n\t\t\t\t\t\tclientX: x,\n\t\t\t\t\t\tclientY: y,\n\t\t\t\t\t\tbutton: 0,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[canvasEvents, editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Run the pointer up\n\t\t\tcanvasEvents.onPointerUp?.(e)\n\t\t\t// Then turn off pointing\n\t\t\tsetIsPointing(false)\n\t\t\t// Reset the pointer state\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t},\n\t\t[canvasEvents]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { PointerEvent, useCallback, useRef, useState } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { getPointerInfo } from '../utils/getPointerInfo'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\t// Whether any menus are open\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Whether we're pointing or not\u2014keep this component visible if we're pointing\n\tconst [isPointing, setIsPointing] = useState(false)\n\n\tconst showElement = isMenuOpen || isPointing\n\n\t// Get the same events that we use on the canvas\n\tconst canvasEvents = useCanvasEvents()\n\n\t// Keep track of the pointer state\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tstart: new Vec(),\n\t})\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tif (e.button === 0) {\n\t\t\t\tsetIsPointing(true)\n\t\t\t\trPointerState.current = {\n\t\t\t\t\tisDown: true,\n\t\t\t\t\tisDragging: false,\n\t\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t\t}\n\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t\t}\n\t\t\tif (e.button === 2) {\n\t\t\t\t// Swallow the contextmenu event that follows this right-click pointerdown.\n\t\t\t\t// clearOpenMenus() below triggers a synchronous render that unmounts this\n\t\t\t\t// component, so our React onContextMenu handler won't be around to catch it.\n\t\t\t\t// Without this, the contextmenu event reaches the Radix Trigger and briefly\n\t\t\t\t// opens a new context menu (which then immediately dismisses \u2014 causing a flash).\n\t\t\t\tconst ownerDocument = editor.getContainerDocument()\n\t\t\t\townerDocument.addEventListener(\n\t\t\t\t\t'contextmenu',\n\t\t\t\t\t(event) => {\n\t\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\t\tevent.stopImmediatePropagation()\n\t\t\t\t\t},\n\t\t\t\t\t{ capture: true, once: true }\n\t\t\t\t)\n\t\t\t}\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst rDidAPointerDownAndDragWhileMenuWasOpen = useRef(false)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Do nothing unless we're pointing\n\t\t\tif (!rPointerState.current.isDown) return\n\n\t\t\t// call the onPointerDown with the original pointer position\n\t\t\tconst { x, y } = rPointerState.current.start\n\n\t\t\tif (!rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\tif (\n\t\t\t\t\t// We're pointing, but are we dragging?\n\t\t\t\t\tVec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = true\n\t\t\t\t\t// Wehaddaeventitsadrag\n\t\t\t\t\trPointerState.current = {\n\t\t\t\t\t\t...rPointerState.current,\n\t\t\t\t\t\tisDown: true,\n\t\t\t\t\t\tisDragging: true,\n\t\t\t\t\t}\n\t\t\t\t\tcanvasEvents.onPointerDown?.({\n\t\t\t\t\t\t...e,\n\t\t\t\t\t\tclientX: x,\n\t\t\t\t\t\tclientY: y,\n\t\t\t\t\t\tbutton: 0,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[canvasEvents, editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Run the pointer up\n\t\t\tcanvasEvents.onPointerUp?.(e)\n\t\t\t// Then turn off pointing\n\t\t\tsetIsPointing(false)\n\t\t\t// Reset the pointer state\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t},\n\t\t[canvasEvents]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t\tonContextMenu={(e) => {\n\t\t\t\t\te.preventDefault()\n\t\t\t\t\te.stopPropagation()\n\t\t\t\t}}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -81,7 +81,8 @@ function kickoutOccludedShapes(editor, shapeIds, opts) {
|
|
|
81
81
|
const oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id);
|
|
82
82
|
if (oldParentIndex > -1) {
|
|
83
83
|
const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1];
|
|
84
|
-
const
|
|
84
|
+
const siblingAboveIndex = siblingsIndexAbove ? editor.getShape(siblingsIndexAbove).index : void 0;
|
|
85
|
+
const indexKeyAbove = siblingAboveIndex && siblingAboveIndex > prevParent.index ? siblingAboveIndex : (0, import_utils.getIndexAbove)(prevParent.index);
|
|
85
86
|
insertIndexKey = (0, import_utils.getIndexBetween)(prevParent.index, indexKeyAbove);
|
|
86
87
|
} else {
|
|
87
88
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/utils/reparenting.ts"],
|
|
4
|
-
"sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, 'group'))?.id ??\n\t\t\t\teditor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst indexKeyAbove = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst _shape = editor.getShape(shape)\n\tif (!_shape) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape)\n\n\tconst parentPageMaskVertices = clipPath ? parentPageTransform.applyToPoints(clipPath) : undefined\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA4B;AAE5B,mBAAkE;AAElE,uBAAwC;AAWjC,SAAS,sBACf,QACA,UACA,MACC;AACD,QAAM,iBAAiB,oBAAI,IAAa;AAExC,aAAW,MAAM,UAAU;AAC1B,UAAM,QAAQ,OAAO,SAAS,EAAE;AAEhC,QAAI,CAAC,MAAO;AACZ,mBAAe,IAAI,KAAK;AAExB,UAAM,SAAS,OAAO,SAAS,MAAM,QAAQ;AAC7C,QAAI,CAAC,OAAQ;AACb,mBAAe,IAAI,MAAM;AAAA,EAC1B;AAGA,QAAM,wBAAwB,oBAAI,IAA0B;AAE5D,aAAW,UAAU,gBAAgB;AACpC,UAAM,WAAW,OAAO,2BAA2B,MAAM;AACzD,QAAI,MAAM,UAAU,CAAC,KAAK,OAAO,MAAM,GAAG;AAEzC,4BAAsB,IAAI,QAAQ,QAAQ;AAAA,IAC3C,OAAO;AACN,YAAM,sBAAsB,qBAAqB,QAAQ,OAAO,IAAI,QAAQ;AAC5E,UAAI,oBAAoB,SAAS,SAAS,QAAQ;AACjD,8BAAsB;AAAA,UACrB;AAAA,UACA,SAAS,OAAO,CAAC,OAAO,CAAC,oBAAoB,SAAS,EAAE,CAAC;AAAA,QAC1D;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAGA,QAAM,iBAAiB,OAAO,2BAA2B,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AAE1E,QAAM,uBAGF,CAAC;AAEL,aAAW,CAAC,YAAY,eAAe,KAAK,uBAAuB;AAClE,UAAM,mBAAe,sBAAQ,gBAAgB,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAI7E,UAAM,EAAE,aAAa,0BAA0B,IAAI;AAAA,MAClD;AAAA,MACA;AAAA,MACA,CAAC,OAAO,mBAAmB;AAE1B,YAAI,MAAM,UAAU,CAAC,KAAK,OAAO,cAAc,EAAG,QAAO;AACzD,eACC,eAAe,OAAO,WAAW,MACjC,eAAe,QAAQ,eAAe,EAAE,IAAI,eAAe,QAAQ,MAAM,EAAE;AAAA,MAE7E;AAAA,IACD;AAEA,gBAAY,QAAQ,CAAC,oBAAoB,gBAAgB;AACxD,UAAI,mBAAmB,WAAW,EAAG;AACrC,UAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,6BAAqB,WAAW,IAAI;AAAA,UACnC,UAAU;AAAA,UACV,UAAU,CAAC;AAAA,QACZ;AAAA,MACD;AACA,2BAAqB,WAAW,EAAE,SAAS,KAAK,GAAG,mBAAmB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAAA,IACvF,CAAC;AAGD,QAAI,0BAA0B,OAAO,GAAG;AAEvC,YAAM,cACL,OAAO,kBAAkB,YAAY,CAAC,MAAM,OAAO,cAAc,GAAG,OAAO,CAAC,GAAG,MAC/E,OAAO,iBAAiB;AAEzB,gCAA0B,QAAQ,CAAC,UAAU;AAC5C,YAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,cAAI;AAEJ,gBAAM,sBAAsB,OAAO,2BAA2B,WAAW;AACzE,gBAAM,iBAAiB,oBAAoB,QAAQ,WAAW,EAAE;AAChE,cAAI,iBAAiB,IAAI;AAExB,kBAAM,qBAAqB,oBAAoB,iBAAiB,CAAC;AACjE,kBAAM,
|
|
4
|
+
"sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, 'group'))?.id ??\n\t\t\t\teditor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst siblingAboveIndex = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\tconst indexKeyAbove =\n\t\t\t\t\t\t\tsiblingAboveIndex && siblingAboveIndex > prevParent.index\n\t\t\t\t\t\t\t\t? siblingAboveIndex\n\t\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst _shape = editor.getShape(shape)\n\tif (!_shape) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape)\n\n\tconst parentPageMaskVertices = clipPath ? parentPageTransform.applyToPoints(clipPath) : undefined\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["shapeIds"]
|
|
7
7
|
}
|
package/dist-cjs/version.js
CHANGED
|
@@ -22,10 +22,10 @@ __export(version_exports, {
|
|
|
22
22
|
version: () => version
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(version_exports);
|
|
25
|
-
const version = "4.6.0-canary.
|
|
25
|
+
const version = "4.6.0-canary.4e3f067c74e9";
|
|
26
26
|
const publishDates = {
|
|
27
27
|
major: "2025-09-18T14:39:22.803Z",
|
|
28
|
-
minor: "2026-04-
|
|
29
|
-
patch: "2026-04-
|
|
28
|
+
minor: "2026-04-10T10:42:07.953Z",
|
|
29
|
+
patch: "2026-04-10T10:42:07.953Z"
|
|
30
30
|
};
|
|
31
31
|
//# sourceMappingURL=version.js.map
|
package/dist-cjs/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.4e3f067c74e9'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-04-10T10:42:07.953Z',\n\tpatch: '2026-04-10T10:42:07.953Z',\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -314,7 +314,7 @@ import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
|
|
|
314
314
|
import { uniq } from "./lib/utils/uniq.mjs";
|
|
315
315
|
registerTldrawLibraryVersion(
|
|
316
316
|
"@tldraw/editor",
|
|
317
|
-
"4.6.0-canary.
|
|
317
|
+
"4.6.0-canary.4e3f067c74e9",
|
|
318
318
|
"esm"
|
|
319
319
|
);
|
|
320
320
|
import { getColorValue } from "./lib/editor/managers/ThemeManager/defaultThemes.mjs";
|
|
@@ -27,6 +27,17 @@ function MenuClickCapture() {
|
|
|
27
27
|
};
|
|
28
28
|
rDidAPointerDownAndDragWhileMenuWasOpen.current = false;
|
|
29
29
|
}
|
|
30
|
+
if (e.button === 2) {
|
|
31
|
+
const ownerDocument = editor.getContainerDocument();
|
|
32
|
+
ownerDocument.addEventListener(
|
|
33
|
+
"contextmenu",
|
|
34
|
+
(event) => {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
event.stopImmediatePropagation();
|
|
37
|
+
},
|
|
38
|
+
{ capture: true, once: true }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
30
41
|
editor.menus.clearOpenMenus();
|
|
31
42
|
},
|
|
32
43
|
[editor]
|
|
@@ -87,7 +98,11 @@ function MenuClickCapture() {
|
|
|
87
98
|
...canvasEvents,
|
|
88
99
|
onPointerDown: handlePointerDown,
|
|
89
100
|
onPointerMove: handlePointerMove,
|
|
90
|
-
onPointerUp: handlePointerUp
|
|
101
|
+
onPointerUp: handlePointerUp,
|
|
102
|
+
onContextMenu: (e) => {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
}
|
|
91
106
|
}
|
|
92
107
|
);
|
|
93
108
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/components/MenuClickCapture.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { PointerEvent, useCallback, useRef, useState } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { getPointerInfo } from '../utils/getPointerInfo'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\t// Whether any menus are open\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Whether we're pointing or not\u2014keep this component visible if we're pointing\n\tconst [isPointing, setIsPointing] = useState(false)\n\n\tconst showElement = isMenuOpen || isPointing\n\n\t// Get the same events that we use on the canvas\n\tconst canvasEvents = useCanvasEvents()\n\n\t// Keep track of the pointer state\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tstart: new Vec(),\n\t})\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tif (e.button === 0) {\n\t\t\t\tsetIsPointing(true)\n\t\t\t\trPointerState.current = {\n\t\t\t\t\tisDown: true,\n\t\t\t\t\tisDragging: false,\n\t\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t\t}\n\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t\t}\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst rDidAPointerDownAndDragWhileMenuWasOpen = useRef(false)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Do nothing unless we're pointing\n\t\t\tif (!rPointerState.current.isDown) return\n\n\t\t\t// call the onPointerDown with the original pointer position\n\t\t\tconst { x, y } = rPointerState.current.start\n\n\t\t\tif (!rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\tif (\n\t\t\t\t\t// We're pointing, but are we dragging?\n\t\t\t\t\tVec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = true\n\t\t\t\t\t// Wehaddaeventitsadrag\n\t\t\t\t\trPointerState.current = {\n\t\t\t\t\t\t...rPointerState.current,\n\t\t\t\t\t\tisDown: true,\n\t\t\t\t\t\tisDragging: true,\n\t\t\t\t\t}\n\t\t\t\t\tcanvasEvents.onPointerDown?.({\n\t\t\t\t\t\t...e,\n\t\t\t\t\t\tclientX: x,\n\t\t\t\t\t\tclientY: y,\n\t\t\t\t\t\tbutton: 0,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[canvasEvents, editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Run the pointer up\n\t\t\tcanvasEvents.onPointerUp?.(e)\n\t\t\t// Then turn off pointing\n\t\t\tsetIsPointing(false)\n\t\t\t// Reset the pointer state\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t},\n\t\t[canvasEvents]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { useValue } from '@tldraw/state-react'\nimport { PointerEvent, useCallback, useRef, useState } from 'react'\nimport { useCanvasEvents } from '../hooks/useCanvasEvents'\nimport { useEditor } from '../hooks/useEditor'\nimport { Vec } from '../primitives/Vec'\nimport { getPointerInfo } from '../utils/getPointerInfo'\n\n/**\n * When a menu is open, this component prevents the user from interacting with the canvas.\n *\n * @public @react\n */\nexport function MenuClickCapture() {\n\tconst editor = useEditor()\n\n\t// Whether any menus are open\n\tconst isMenuOpen = useValue('is menu open', () => editor.menus.hasAnyOpenMenus(), [editor])\n\n\t// Whether we're pointing or not\u2014keep this component visible if we're pointing\n\tconst [isPointing, setIsPointing] = useState(false)\n\n\tconst showElement = isMenuOpen || isPointing\n\n\t// Get the same events that we use on the canvas\n\tconst canvasEvents = useCanvasEvents()\n\n\t// Keep track of the pointer state\n\tconst rPointerState = useRef({\n\t\tisDown: false,\n\t\tisDragging: false,\n\t\tstart: new Vec(),\n\t})\n\n\tconst handlePointerDown = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\tif (e.button === 0) {\n\t\t\t\tsetIsPointing(true)\n\t\t\t\trPointerState.current = {\n\t\t\t\t\tisDown: true,\n\t\t\t\t\tisDragging: false,\n\t\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t\t}\n\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t\t}\n\t\t\tif (e.button === 2) {\n\t\t\t\t// Swallow the contextmenu event that follows this right-click pointerdown.\n\t\t\t\t// clearOpenMenus() below triggers a synchronous render that unmounts this\n\t\t\t\t// component, so our React onContextMenu handler won't be around to catch it.\n\t\t\t\t// Without this, the contextmenu event reaches the Radix Trigger and briefly\n\t\t\t\t// opens a new context menu (which then immediately dismisses \u2014 causing a flash).\n\t\t\t\tconst ownerDocument = editor.getContainerDocument()\n\t\t\t\townerDocument.addEventListener(\n\t\t\t\t\t'contextmenu',\n\t\t\t\t\t(event) => {\n\t\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\t\tevent.stopImmediatePropagation()\n\t\t\t\t\t},\n\t\t\t\t\t{ capture: true, once: true }\n\t\t\t\t)\n\t\t\t}\n\t\t\teditor.menus.clearOpenMenus()\n\t\t},\n\t\t[editor]\n\t)\n\n\tconst rDidAPointerDownAndDragWhileMenuWasOpen = useRef(false)\n\n\tconst handlePointerMove = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Do nothing unless we're pointing\n\t\t\tif (!rPointerState.current.isDown) return\n\n\t\t\t// call the onPointerDown with the original pointer position\n\t\t\tconst { x, y } = rPointerState.current.start\n\n\t\t\tif (!rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\tif (\n\t\t\t\t\t// We're pointing, but are we dragging?\n\t\t\t\t\tVec.Dist2(rPointerState.current.start, new Vec(e.clientX, e.clientY)) >\n\t\t\t\t\teditor.options.dragDistanceSquared\n\t\t\t\t) {\n\t\t\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = true\n\t\t\t\t\t// Wehaddaeventitsadrag\n\t\t\t\t\trPointerState.current = {\n\t\t\t\t\t\t...rPointerState.current,\n\t\t\t\t\t\tisDown: true,\n\t\t\t\t\t\tisDragging: true,\n\t\t\t\t\t}\n\t\t\t\t\tcanvasEvents.onPointerDown?.({\n\t\t\t\t\t\t...e,\n\t\t\t\t\t\tclientX: x,\n\t\t\t\t\t\tclientY: y,\n\t\t\t\t\t\tbutton: 0,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rDidAPointerDownAndDragWhileMenuWasOpen.current) {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pointer',\n\t\t\t\t\ttarget: 'canvas',\n\t\t\t\t\tname: 'pointer_move',\n\t\t\t\t\t...getPointerInfo(editor, e),\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t[canvasEvents, editor]\n\t)\n\n\tconst handlePointerUp = useCallback(\n\t\t(e: PointerEvent) => {\n\t\t\t// Run the pointer up\n\t\t\tcanvasEvents.onPointerUp?.(e)\n\t\t\t// Then turn off pointing\n\t\t\tsetIsPointing(false)\n\t\t\t// Reset the pointer state\n\t\t\trPointerState.current = {\n\t\t\t\tisDown: false,\n\t\t\t\tisDragging: false,\n\t\t\t\tstart: new Vec(e.clientX, e.clientY),\n\t\t\t}\n\t\t\trDidAPointerDownAndDragWhileMenuWasOpen.current = false\n\t\t},\n\t\t[canvasEvents]\n\t)\n\n\treturn (\n\t\tshowElement && (\n\t\t\t<div\n\t\t\t\tclassName=\"tlui-menu-click-capture\"\n\t\t\t\tdata-testid=\"menu-click-capture.content\"\n\t\t\t\t{...canvasEvents}\n\t\t\t\tonPointerDown={handlePointerDown}\n\t\t\t\tonPointerMove={handlePointerMove}\n\t\t\t\tonPointerUp={handlePointerUp}\n\t\t\t\tonContextMenu={(e) => {\n\t\t\t\t\te.preventDefault()\n\t\t\t\t\te.stopPropagation()\n\t\t\t\t}}\n\t\t\t/>\n\t\t)\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -57,7 +57,8 @@ function kickoutOccludedShapes(editor, shapeIds, opts) {
|
|
|
57
57
|
const oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id);
|
|
58
58
|
if (oldParentIndex > -1) {
|
|
59
59
|
const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1];
|
|
60
|
-
const
|
|
60
|
+
const siblingAboveIndex = siblingsIndexAbove ? editor.getShape(siblingsIndexAbove).index : void 0;
|
|
61
|
+
const indexKeyAbove = siblingAboveIndex && siblingAboveIndex > prevParent.index ? siblingAboveIndex : getIndexAbove(prevParent.index);
|
|
61
62
|
insertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove);
|
|
62
63
|
} else {
|
|
63
64
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/utils/reparenting.ts"],
|
|
4
|
-
"sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, 'group'))?.id ??\n\t\t\t\teditor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst indexKeyAbove = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst _shape = editor.getShape(shape)\n\tif (!_shape) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape)\n\n\tconst parentPageMaskVertices = clipPath ? parentPageTransform.applyToPoints(clipPath) : undefined\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,mBAAmB;AAE5B,SAAmB,SAAS,eAAe,uBAAuB;AAElE,SAAS,+BAA+B;AAWjC,SAAS,sBACf,QACA,UACA,MACC;AACD,QAAM,iBAAiB,oBAAI,IAAa;AAExC,aAAW,MAAM,UAAU;AAC1B,UAAM,QAAQ,OAAO,SAAS,EAAE;AAEhC,QAAI,CAAC,MAAO;AACZ,mBAAe,IAAI,KAAK;AAExB,UAAM,SAAS,OAAO,SAAS,MAAM,QAAQ;AAC7C,QAAI,CAAC,OAAQ;AACb,mBAAe,IAAI,MAAM;AAAA,EAC1B;AAGA,QAAM,wBAAwB,oBAAI,IAA0B;AAE5D,aAAW,UAAU,gBAAgB;AACpC,UAAM,WAAW,OAAO,2BAA2B,MAAM;AACzD,QAAI,MAAM,UAAU,CAAC,KAAK,OAAO,MAAM,GAAG;AAEzC,4BAAsB,IAAI,QAAQ,QAAQ;AAAA,IAC3C,OAAO;AACN,YAAM,sBAAsB,qBAAqB,QAAQ,OAAO,IAAI,QAAQ;AAC5E,UAAI,oBAAoB,SAAS,SAAS,QAAQ;AACjD,8BAAsB;AAAA,UACrB;AAAA,UACA,SAAS,OAAO,CAAC,OAAO,CAAC,oBAAoB,SAAS,EAAE,CAAC;AAAA,QAC1D;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAGA,QAAM,iBAAiB,OAAO,2BAA2B,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AAE1E,QAAM,uBAGF,CAAC;AAEL,aAAW,CAAC,YAAY,eAAe,KAAK,uBAAuB;AAClE,UAAM,eAAe,QAAQ,gBAAgB,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAI7E,UAAM,EAAE,aAAa,0BAA0B,IAAI;AAAA,MAClD;AAAA,MACA;AAAA,MACA,CAAC,OAAO,mBAAmB;AAE1B,YAAI,MAAM,UAAU,CAAC,KAAK,OAAO,cAAc,EAAG,QAAO;AACzD,eACC,eAAe,OAAO,WAAW,MACjC,eAAe,QAAQ,eAAe,EAAE,IAAI,eAAe,QAAQ,MAAM,EAAE;AAAA,MAE7E;AAAA,IACD;AAEA,gBAAY,QAAQ,CAAC,oBAAoB,gBAAgB;AACxD,UAAI,mBAAmB,WAAW,EAAG;AACrC,UAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,6BAAqB,WAAW,IAAI;AAAA,UACnC,UAAU;AAAA,UACV,UAAU,CAAC;AAAA,QACZ;AAAA,MACD;AACA,2BAAqB,WAAW,EAAE,SAAS,KAAK,GAAG,mBAAmB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAAA,IACvF,CAAC;AAGD,QAAI,0BAA0B,OAAO,GAAG;AAEvC,YAAM,cACL,OAAO,kBAAkB,YAAY,CAAC,MAAM,OAAO,cAAc,GAAG,OAAO,CAAC,GAAG,MAC/E,OAAO,iBAAiB;AAEzB,gCAA0B,QAAQ,CAAC,UAAU;AAC5C,YAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,cAAI;AAEJ,gBAAM,sBAAsB,OAAO,2BAA2B,WAAW;AACzE,gBAAM,iBAAiB,oBAAoB,QAAQ,WAAW,EAAE;AAChE,cAAI,iBAAiB,IAAI;AAExB,kBAAM,qBAAqB,oBAAoB,iBAAiB,CAAC;AACjE,kBAAM,
|
|
4
|
+
"sourcesContent": ["import { EMPTY_ARRAY } from '@tldraw/state'\nimport { TLGroupShape, TLParentId, TLShape, TLShapeId } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndexAbove, getIndexBetween } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n *\n * @param editor - The editor instance.\n * @param shapeIds - The IDs of the shapes to reparent.\n * @param opts - Optional options, including a callback to filter out certain parents, such as when removing a frame.\n *\n * @public\n */\nexport function kickoutOccludedShapes(\n\teditor: Editor,\n\tshapeIds: TLShapeId[],\n\topts?: { filter?(parent: TLShape): boolean }\n) {\n\tconst parentsToCheck = new Set<TLShape>()\n\n\tfor (const id of shapeIds) {\n\t\tconst shape = editor.getShape(id)\n\n\t\tif (!shape) continue\n\t\tparentsToCheck.add(shape)\n\n\t\tconst parent = editor.getShape(shape.parentId)\n\t\tif (!parent) continue\n\t\tparentsToCheck.add(parent)\n\t}\n\n\t// Check all of the parents and gather up parents who have lost children\n\tconst parentsToLostChildren = new Map<TLShape, TLShapeId[]>()\n\n\tfor (const parent of parentsToCheck) {\n\t\tconst childIds = editor.getSortedChildIdsForParent(parent)\n\t\tif (opts?.filter && !opts.filter(parent)) {\n\t\t\t// If the shape is filtered out, we kick out all of its children\n\t\t\tparentsToLostChildren.set(parent, childIds)\n\t\t} else {\n\t\t\tconst overlappingChildren = getOverlappingShapes(editor, parent.id, childIds)\n\t\t\tif (overlappingChildren.length < childIds.length) {\n\t\t\t\tparentsToLostChildren.set(\n\t\t\t\t\tparent,\n\t\t\t\t\tchildIds.filter((id) => !overlappingChildren.includes(id))\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get all of the shapes on the current page, sorted by their index\n\tconst sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id)\n\n\tconst parentsToNewChildren: Record<\n\t\tTLParentId,\n\t\t{ parentId: TLParentId; shapeIds: TLShapeId[]; index?: IndexKey }\n\t> = {}\n\n\tfor (const [prevParent, lostChildrenIds] of parentsToLostChildren) {\n\t\tconst lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)))\n\n\t\t// Otherwise, we have no next dropping shape under the cursor, so go find\n\t\t// all the frames on the page where the moving shapes will fall into\n\t\tconst { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(\n\t\t\teditor,\n\t\t\tlostChildren,\n\t\t\t(shape, maybeNewParent) => {\n\t\t\t\t// If we're filtering out a potential parent, don't reparent shapes to the filtered out shape\n\t\t\t\tif (opts?.filter && !opts.filter(maybeNewParent)) return false\n\t\t\t\treturn (\n\t\t\t\t\tmaybeNewParent.id !== prevParent.id &&\n\t\t\t\t\tsortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id)\n\t\t\t\t)\n\t\t\t}\n\t\t)\n\n\t\treparenting.forEach((childrenToReparent, newParentId) => {\n\t\t\tif (childrenToReparent.length === 0) return\n\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\tshapeIds: [],\n\t\t\t\t}\n\t\t\t}\n\t\t\tparentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id))\n\t\t})\n\n\t\t// Reparent the rest to the page (or containing group)\n\t\tif (remainingShapesToReparent.size > 0) {\n\t\t\t// The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page\n\t\t\tconst newParentId =\n\t\t\t\teditor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, 'group'))?.id ??\n\t\t\t\teditor.getCurrentPageId()\n\n\t\t\tremainingShapesToReparent.forEach((shape) => {\n\t\t\t\tif (!parentsToNewChildren[newParentId]) {\n\t\t\t\t\tlet insertIndexKey: IndexKey | undefined\n\n\t\t\t\t\tconst oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId)\n\t\t\t\t\tconst oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id)\n\t\t\t\t\tif (oldParentIndex > -1) {\n\t\t\t\t\t\t// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.\n\t\t\t\t\t\tconst siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]\n\t\t\t\t\t\tconst siblingAboveIndex = siblingsIndexAbove\n\t\t\t\t\t\t\t? editor.getShape(siblingsIndexAbove)!.index\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\tconst indexKeyAbove =\n\t\t\t\t\t\t\tsiblingAboveIndex && siblingAboveIndex > prevParent.index\n\t\t\t\t\t\t\t\t? siblingAboveIndex\n\t\t\t\t\t\t\t\t: getIndexAbove(prevParent.index)\n\t\t\t\t\t\tinsertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// If the old parent is not a direct child of the new parent, then we'll add them to the \"top\" of the new parent's children.\n\t\t\t\t\t\t// This is done automatically if we leave the index undefined, so let's do that.\n\t\t\t\t\t}\n\n\t\t\t\t\tparentsToNewChildren[newParentId] = {\n\t\t\t\t\t\tparentId: newParentId,\n\t\t\t\t\t\tshapeIds: [],\n\t\t\t\t\t\tindex: insertIndexKey,\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tparentsToNewChildren[newParentId].shapeIds.push(shape.id)\n\t\t\t})\n\t\t}\n\t}\n\n\teditor.run(() => {\n\t\tObject.values(parentsToNewChildren).forEach(({ parentId, shapeIds, index }) => {\n\t\t\tif (shapeIds.length === 0) return\n\t\t\t// Before we reparent, sort the new shape ids by their place in the original absolute order on the page\n\t\t\tshapeIds.sort((a, b) => (sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1))\n\t\t\teditor.reparentShapes(shapeIds, parentId, index)\n\t\t})\n\t})\n}\n\n/**\n * Get the shapes that overlap with a given shape.\n *\n * @param editor - The editor instance.\n * @param shape - The shapes or shape IDs to check against.\n * @param otherShapes - The shapes or shape IDs to check for overlap.\n * @returns An array of shapes or shape IDs that overlap with the given shape.\n */\nfunction getOverlappingShapes<T extends TLShape[] | TLShapeId[]>(\n\teditor: Editor,\n\tshape: T[number],\n\totherShapes: T\n) {\n\tif (otherShapes.length === 0) {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\tconst parentPageBounds = editor.getShapePageBounds(shape)\n\tif (!parentPageBounds) return EMPTY_ARRAY\n\n\tconst _shape = editor.getShape(shape)\n\tif (!_shape) return EMPTY_ARRAY\n\n\tconst parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst clipPath = editor.getShapeUtil(_shape.type).getClipPath?.(_shape)\n\n\tconst parentPageMaskVertices = clipPath ? parentPageTransform.applyToPoints(clipPath) : undefined\n\tconst parentPagePolygon = parentPageMaskVertices\n\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t: parentPageCorners\n\n\tif (!parentPagePolygon) return EMPTY_ARRAY\n\n\treturn otherShapes.filter((childId) => {\n\t\tconst shapePageBounds = editor.getShapePageBounds(childId)\n\t\tif (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false\n\n\t\tconst parentPolygonInShapeShape = editor\n\t\t\t.getShapePageTransform(childId)\n\t\t\t.clone()\n\t\t\t.invert()\n\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\tconst geometry = editor.getShapeGeometry(childId)\n\n\t\treturn geometry.overlapsPolygon(parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * Get the shapes that will be reparented to new parents when the shapes are dropped.\n *\n * @param editor - The editor instance.\n * @param shapes - The shapes to check.\n * @param cb - A callback to filter out certain shapes.\n * @returns An object with the shapes that will be reparented to new parents and the shapes that will be reparented to the page or their ancestral group.\n *\n * @public\n */\nexport function getDroppedShapesToNewParents(\n\teditor: Editor,\n\tshapes: Set<TLShape> | TLShape[],\n\tcb?: (shape: TLShape, parent: TLShape) => boolean\n) {\n\tconst shapesToActuallyCheck = new Set<TLShape>(shapes)\n\tconst movingGroups = new Set<TLGroupShape>()\n\n\tfor (const shape of shapes) {\n\t\tconst parent = editor.getShapeParent(shape)\n\t\tif (parent && editor.isShapeOfType(parent, 'group')) {\n\t\t\tif (!movingGroups.has(parent)) {\n\t\t\t\tmovingGroups.add(parent)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If all of a group's children are moving, then move the group instead\n\tfor (const movingGroup of movingGroups) {\n\t\tconst children = compact(\n\t\t\teditor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))\n\t\t)\n\t\tfor (const child of children) {\n\t\t\tshapesToActuallyCheck.delete(child)\n\t\t}\n\t\tshapesToActuallyCheck.add(movingGroup)\n\t}\n\n\t// this could be cached and passed in\n\tconst shapeGroupIds = new Map<TLShapeId, TLShapeId | undefined>()\n\n\tconst reparenting = new Map<TLShapeId, TLShape[]>()\n\n\tconst remainingShapesToReparent = new Set(shapesToActuallyCheck)\n\n\tconst potentialParentShapes = editor\n\t\t.getCurrentPageShapesSorted()\n\t\t// filter out any shapes that aren't frames or that are included among the provided shapes\n\t\t.filter(\n\t\t\t(s) =>\n\t\t\t\teditor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) &&\n\t\t\t\t!remainingShapesToReparent.has(s)\n\t\t)\n\n\tparentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {\n\t\tconst parentShape = potentialParentShapes[i]\n\t\tconst parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>\n\t\t\teditor.isShapeOfType(s, 'group')\n\t\t)?.id\n\n\t\tconst parentGeometry = editor.getShapeGeometry(parentShape)\n\t\tconst parentPageTransform = editor.getShapePageTransform(parentShape)\n\t\tconst parentPageMaskVertices = editor.getShapeMask(parentShape)\n\t\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\t\tconst parentPagePolygon = parentPageMaskVertices\n\t\t\t? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners)\n\t\t\t: parentPageCorners\n\n\t\tif (!parentPagePolygon) continue parentCheck\n\n\t\tconst childrenToReparent = []\n\n\t\t// For each of the dropping shapes...\n\t\tshapeCheck: for (const shape of remainingShapesToReparent) {\n\t\t\t// Don't reparent a frame to itself\n\t\t\tif (parentShape.id === shape.id) continue shapeCheck\n\n\t\t\t// Use the callback to filter out certain shapes\n\t\t\tif (cb && !cb(shape, parentShape)) continue shapeCheck\n\n\t\t\tif (!shapeGroupIds.has(shape.id)) {\n\t\t\t\tshapeGroupIds.set(\n\t\t\t\t\tshape.id,\n\t\t\t\t\teditor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, 'group'))?.id\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst shapeGroupId = shapeGroupIds.get(shape.id)\n\n\t\t\t// Are the shape and the parent part of different groups?\n\t\t\tif (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck\n\n\t\t\t// Is the shape actually the ancestor of the parent?\n\t\t\tif (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck\n\n\t\t\t// Convert the parent polygon to the shape's space\n\t\t\tconst parentPolygonInShapeSpace = editor\n\t\t\t\t.getShapePageTransform(shape)\n\t\t\t\t.clone()\n\t\t\t\t.invert()\n\t\t\t\t.applyToPoints(parentPagePolygon)\n\n\t\t\t// If the shape overlaps the parent polygon, reparent it to that parent\n\t\t\tif (editor.getShapeGeometry(shape).overlapsPolygon(parentPolygonInShapeSpace)) {\n\t\t\t\t// Use the util to check if the shape can be reparented to the parent\n\t\t\t\tif (\n\t\t\t\t\t!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type)\n\t\t\t\t)\n\t\t\t\t\tcontinue shapeCheck\n\n\t\t\t\tif (shape.parentId !== parentShape.id) {\n\t\t\t\t\tchildrenToReparent.push(shape)\n\t\t\t\t}\n\t\t\t\tremainingShapesToReparent.delete(shape)\n\t\t\t\tcontinue shapeCheck\n\t\t\t}\n\t\t}\n\n\t\tif (childrenToReparent.length) {\n\t\t\treparenting.set(parentShape.id, childrenToReparent)\n\t\t}\n\t}\n\n\treturn {\n\t\t// these are the shapes that will be reparented to new parents\n\t\treparenting,\n\t\t// these are the shapes that will be reparented to the page or their ancestral group\n\t\tremainingShapesToReparent,\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB;AAE5B,SAAmB,SAAS,eAAe,uBAAuB;AAElE,SAAS,+BAA+B;AAWjC,SAAS,sBACf,QACA,UACA,MACC;AACD,QAAM,iBAAiB,oBAAI,IAAa;AAExC,aAAW,MAAM,UAAU;AAC1B,UAAM,QAAQ,OAAO,SAAS,EAAE;AAEhC,QAAI,CAAC,MAAO;AACZ,mBAAe,IAAI,KAAK;AAExB,UAAM,SAAS,OAAO,SAAS,MAAM,QAAQ;AAC7C,QAAI,CAAC,OAAQ;AACb,mBAAe,IAAI,MAAM;AAAA,EAC1B;AAGA,QAAM,wBAAwB,oBAAI,IAA0B;AAE5D,aAAW,UAAU,gBAAgB;AACpC,UAAM,WAAW,OAAO,2BAA2B,MAAM;AACzD,QAAI,MAAM,UAAU,CAAC,KAAK,OAAO,MAAM,GAAG;AAEzC,4BAAsB,IAAI,QAAQ,QAAQ;AAAA,IAC3C,OAAO;AACN,YAAM,sBAAsB,qBAAqB,QAAQ,OAAO,IAAI,QAAQ;AAC5E,UAAI,oBAAoB,SAAS,SAAS,QAAQ;AACjD,8BAAsB;AAAA,UACrB;AAAA,UACA,SAAS,OAAO,CAAC,OAAO,CAAC,oBAAoB,SAAS,EAAE,CAAC;AAAA,QAC1D;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAGA,QAAM,iBAAiB,OAAO,2BAA2B,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AAE1E,QAAM,uBAGF,CAAC;AAEL,aAAW,CAAC,YAAY,eAAe,KAAK,uBAAuB;AAClE,UAAM,eAAe,QAAQ,gBAAgB,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC;AAI7E,UAAM,EAAE,aAAa,0BAA0B,IAAI;AAAA,MAClD;AAAA,MACA;AAAA,MACA,CAAC,OAAO,mBAAmB;AAE1B,YAAI,MAAM,UAAU,CAAC,KAAK,OAAO,cAAc,EAAG,QAAO;AACzD,eACC,eAAe,OAAO,WAAW,MACjC,eAAe,QAAQ,eAAe,EAAE,IAAI,eAAe,QAAQ,MAAM,EAAE;AAAA,MAE7E;AAAA,IACD;AAEA,gBAAY,QAAQ,CAAC,oBAAoB,gBAAgB;AACxD,UAAI,mBAAmB,WAAW,EAAG;AACrC,UAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,6BAAqB,WAAW,IAAI;AAAA,UACnC,UAAU;AAAA,UACV,UAAU,CAAC;AAAA,QACZ;AAAA,MACD;AACA,2BAAqB,WAAW,EAAE,SAAS,KAAK,GAAG,mBAAmB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAAA,IACvF,CAAC;AAGD,QAAI,0BAA0B,OAAO,GAAG;AAEvC,YAAM,cACL,OAAO,kBAAkB,YAAY,CAAC,MAAM,OAAO,cAAc,GAAG,OAAO,CAAC,GAAG,MAC/E,OAAO,iBAAiB;AAEzB,gCAA0B,QAAQ,CAAC,UAAU;AAC5C,YAAI,CAAC,qBAAqB,WAAW,GAAG;AACvC,cAAI;AAEJ,gBAAM,sBAAsB,OAAO,2BAA2B,WAAW;AACzE,gBAAM,iBAAiB,oBAAoB,QAAQ,WAAW,EAAE;AAChE,cAAI,iBAAiB,IAAI;AAExB,kBAAM,qBAAqB,oBAAoB,iBAAiB,CAAC;AACjE,kBAAM,oBAAoB,qBACvB,OAAO,SAAS,kBAAkB,EAAG,QACrC;AACH,kBAAM,gBACL,qBAAqB,oBAAoB,WAAW,QACjD,oBACA,cAAc,WAAW,KAAK;AAClC,6BAAiB,gBAAgB,WAAW,OAAO,aAAa;AAAA,UACjE,OAAO;AAAA,UAGP;AAEA,+BAAqB,WAAW,IAAI;AAAA,YACnC,UAAU;AAAA,YACV,UAAU,CAAC;AAAA,YACX,OAAO;AAAA,UACR;AAAA,QACD;AAEA,6BAAqB,WAAW,EAAE,SAAS,KAAK,MAAM,EAAE;AAAA,MACzD,CAAC;AAAA,IACF;AAAA,EACD;AAEA,SAAO,IAAI,MAAM;AAChB,WAAO,OAAO,oBAAoB,EAAE,QAAQ,CAAC,EAAE,UAAU,UAAAA,WAAU,MAAM,MAAM;AAC9E,UAAIA,UAAS,WAAW,EAAG;AAE3B,MAAAA,UAAS,KAAK,CAAC,GAAG,MAAO,eAAe,QAAQ,CAAC,IAAI,eAAe,QAAQ,CAAC,IAAI,KAAK,CAAE;AACxF,aAAO,eAAeA,WAAU,UAAU,KAAK;AAAA,IAChD,CAAC;AAAA,EACF,CAAC;AACF;AAUA,SAAS,qBACR,QACA,OACA,aACC;AACD,MAAI,YAAY,WAAW,GAAG;AAC7B,WAAO;AAAA,EACR;AAEA,QAAM,mBAAmB,OAAO,mBAAmB,KAAK;AACxD,MAAI,CAAC,iBAAkB,QAAO;AAE9B,QAAM,SAAS,OAAO,SAAS,KAAK;AACpC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,iBAAiB,OAAO,iBAAiB,KAAK;AACpD,QAAM,sBAAsB,OAAO,sBAAsB,KAAK;AAC9D,QAAM,oBAAoB,oBAAoB,cAAc,eAAe,QAAQ;AAEnF,QAAM,WAAW,OAAO,aAAa,OAAO,IAAI,EAAE,cAAc,MAAM;AAEtE,QAAM,yBAAyB,WAAW,oBAAoB,cAAc,QAAQ,IAAI;AACxF,QAAM,oBAAoB,yBACvB,wBAAwB,wBAAwB,iBAAiB,IACjE;AAEH,MAAI,CAAC,kBAAmB,QAAO;AAE/B,SAAO,YAAY,OAAO,CAAC,YAAY;AACtC,UAAM,kBAAkB,OAAO,mBAAmB,OAAO;AACzD,QAAI,CAAC,mBAAmB,CAAC,iBAAiB,SAAS,eAAe,EAAG,QAAO;AAE5E,UAAM,4BAA4B,OAChC,sBAAsB,OAAO,EAC7B,MAAM,EACN,OAAO,EACP,cAAc,iBAAiB;AAEjC,UAAM,WAAW,OAAO,iBAAiB,OAAO;AAEhD,WAAO,SAAS,gBAAgB,yBAAyB;AAAA,EAC1D,CAAC;AACF;AAYO,SAAS,6BACf,QACA,QACA,IACC;AACD,QAAM,wBAAwB,IAAI,IAAa,MAAM;AACrD,QAAM,eAAe,oBAAI,IAAkB;AAE3C,aAAW,SAAS,QAAQ;AAC3B,UAAM,SAAS,OAAO,eAAe,KAAK;AAC1C,QAAI,UAAU,OAAO,cAAc,QAAQ,OAAO,GAAG;AACpD,UAAI,CAAC,aAAa,IAAI,MAAM,GAAG;AAC9B,qBAAa,IAAI,MAAM;AAAA,MACxB;AAAA,IACD;AAAA,EACD;AAGA,aAAW,eAAe,cAAc;AACvC,UAAM,WAAW;AAAA,MAChB,OAAO,2BAA2B,WAAW,EAAE,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC;AAAA,IAC/E;AACA,eAAW,SAAS,UAAU;AAC7B,4BAAsB,OAAO,KAAK;AAAA,IACnC;AACA,0BAAsB,IAAI,WAAW;AAAA,EACtC;AAGA,QAAM,gBAAgB,oBAAI,IAAsC;AAEhE,QAAM,cAAc,oBAAI,IAA0B;AAElD,QAAM,4BAA4B,IAAI,IAAI,qBAAqB;AAE/D,QAAM,wBAAwB,OAC5B,2BAA2B,EAE3B;AAAA,IACA,CAAC,MACA,OAAO,aAAa,CAAC,EAAE,8BAA8B,GAAG,EAAE,IAAI,KAC9D,CAAC,0BAA0B,IAAI,CAAC;AAAA,EAClC;AAED,cAAa,UAAS,IAAI,sBAAsB,SAAS,GAAG,KAAK,GAAG,KAAK;AACxE,UAAM,cAAc,sBAAsB,CAAC;AAC3C,UAAM,+BAA+B,OAAO;AAAA,MAAkB;AAAA,MAAa,CAAC,MAC3E,OAAO,cAAc,GAAG,OAAO;AAAA,IAChC,GAAG;AAEH,UAAM,iBAAiB,OAAO,iBAAiB,WAAW;AAC1D,UAAM,sBAAsB,OAAO,sBAAsB,WAAW;AACpE,UAAM,yBAAyB,OAAO,aAAa,WAAW;AAC9D,UAAM,oBAAoB,oBAAoB,cAAc,eAAe,QAAQ;AACnF,UAAM,oBAAoB,yBACvB,wBAAwB,wBAAwB,iBAAiB,IACjE;AAEH,QAAI,CAAC,kBAAmB,UAAS;AAEjC,UAAM,qBAAqB,CAAC;AAG5B,eAAY,YAAW,SAAS,2BAA2B;AAE1D,UAAI,YAAY,OAAO,MAAM,GAAI,UAAS;AAG1C,UAAI,MAAM,CAAC,GAAG,OAAO,WAAW,EAAG,UAAS;AAE5C,UAAI,CAAC,cAAc,IAAI,MAAM,EAAE,GAAG;AACjC,sBAAc;AAAA,UACb,MAAM;AAAA,UACN,OAAO,kBAAkB,OAAO,CAAC,MAAM,OAAO,cAAc,GAAG,OAAO,CAAC,GAAG;AAAA,QAC3E;AAAA,MACD;AAEA,YAAM,eAAe,cAAc,IAAI,MAAM,EAAE;AAG/C,UAAI,iBAAiB,6BAA8B,UAAS;AAG5D,UAAI,OAAO,kBAAkB,aAAa,CAAC,MAAM,MAAM,OAAO,EAAE,EAAE,EAAG,UAAS;AAG9E,YAAM,4BAA4B,OAChC,sBAAsB,KAAK,EAC3B,MAAM,EACN,OAAO,EACP,cAAc,iBAAiB;AAGjC,UAAI,OAAO,iBAAiB,KAAK,EAAE,gBAAgB,yBAAyB,GAAG;AAE9E,YACC,CAAC,OAAO,aAAa,WAAW,EAAE,8BAA8B,aAAa,MAAM,IAAI;AAEvF,mBAAS;AAEV,YAAI,MAAM,aAAa,YAAY,IAAI;AACtC,6BAAmB,KAAK,KAAK;AAAA,QAC9B;AACA,kCAA0B,OAAO,KAAK;AACtC,iBAAS;AAAA,MACV;AAAA,IACD;AAEA,QAAI,mBAAmB,QAAQ;AAC9B,kBAAY,IAAI,YAAY,IAAI,kBAAkB;AAAA,IACnD;AAAA,EACD;AAEA,SAAO;AAAA;AAAA,IAEN;AAAA;AAAA,IAEA;AAAA,EACD;AACD;",
|
|
6
6
|
"names": ["shapeIds"]
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "4.6.0-canary.
|
|
1
|
+
const version = "4.6.0-canary.4e3f067c74e9";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2025-09-18T14:39:22.803Z",
|
|
4
|
-
minor: "2026-04-
|
|
5
|
-
patch: "2026-04-
|
|
4
|
+
minor: "2026-04-10T10:42:07.953Z",
|
|
5
|
+
patch: "2026-04-10T10:42:07.953Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.4e3f067c74e9'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-04-10T10:42:07.953Z',\n\tpatch: '2026-04-10T10:42:07.953Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "tldraw infinite canvas SDK (editor).",
|
|
4
|
-
"version": "4.6.0-canary.
|
|
4
|
+
"version": "4.6.0-canary.4e3f067c74e9",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"@tiptap/core": "^3.12.1",
|
|
50
50
|
"@tiptap/pm": "^3.12.1",
|
|
51
51
|
"@tiptap/react": "^3.12.1",
|
|
52
|
-
"@tldraw/state": "4.6.0-canary.
|
|
53
|
-
"@tldraw/state-react": "4.6.0-canary.
|
|
54
|
-
"@tldraw/store": "4.6.0-canary.
|
|
55
|
-
"@tldraw/tlschema": "4.6.0-canary.
|
|
56
|
-
"@tldraw/utils": "4.6.0-canary.
|
|
57
|
-
"@tldraw/validate": "4.6.0-canary.
|
|
52
|
+
"@tldraw/state": "4.6.0-canary.4e3f067c74e9",
|
|
53
|
+
"@tldraw/state-react": "4.6.0-canary.4e3f067c74e9",
|
|
54
|
+
"@tldraw/store": "4.6.0-canary.4e3f067c74e9",
|
|
55
|
+
"@tldraw/tlschema": "4.6.0-canary.4e3f067c74e9",
|
|
56
|
+
"@tldraw/utils": "4.6.0-canary.4e3f067c74e9",
|
|
57
|
+
"@tldraw/validate": "4.6.0-canary.4e3f067c74e9",
|
|
58
58
|
"classnames": "^2.5.1",
|
|
59
59
|
"eventemitter3": "^4.0.7",
|
|
60
60
|
"idb": "^7.1.1",
|
|
@@ -42,6 +42,22 @@ export function MenuClickCapture() {
|
|
|
42
42
|
}
|
|
43
43
|
rDidAPointerDownAndDragWhileMenuWasOpen.current = false
|
|
44
44
|
}
|
|
45
|
+
if (e.button === 2) {
|
|
46
|
+
// Swallow the contextmenu event that follows this right-click pointerdown.
|
|
47
|
+
// clearOpenMenus() below triggers a synchronous render that unmounts this
|
|
48
|
+
// component, so our React onContextMenu handler won't be around to catch it.
|
|
49
|
+
// Without this, the contextmenu event reaches the Radix Trigger and briefly
|
|
50
|
+
// opens a new context menu (which then immediately dismisses — causing a flash).
|
|
51
|
+
const ownerDocument = editor.getContainerDocument()
|
|
52
|
+
ownerDocument.addEventListener(
|
|
53
|
+
'contextmenu',
|
|
54
|
+
(event) => {
|
|
55
|
+
event.preventDefault()
|
|
56
|
+
event.stopImmediatePropagation()
|
|
57
|
+
},
|
|
58
|
+
{ capture: true, once: true }
|
|
59
|
+
)
|
|
60
|
+
}
|
|
45
61
|
editor.menus.clearOpenMenus()
|
|
46
62
|
},
|
|
47
63
|
[editor]
|
|
@@ -117,6 +133,10 @@ export function MenuClickCapture() {
|
|
|
117
133
|
onPointerDown={handlePointerDown}
|
|
118
134
|
onPointerMove={handlePointerMove}
|
|
119
135
|
onPointerUp={handlePointerUp}
|
|
136
|
+
onContextMenu={(e) => {
|
|
137
|
+
e.preventDefault()
|
|
138
|
+
e.stopPropagation()
|
|
139
|
+
}}
|
|
120
140
|
/>
|
|
121
141
|
)
|
|
122
142
|
)
|
|
@@ -103,9 +103,13 @@ export function kickoutOccludedShapes(
|
|
|
103
103
|
if (oldParentIndex > -1) {
|
|
104
104
|
// If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.
|
|
105
105
|
const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]
|
|
106
|
-
const
|
|
106
|
+
const siblingAboveIndex = siblingsIndexAbove
|
|
107
107
|
? editor.getShape(siblingsIndexAbove)!.index
|
|
108
|
-
:
|
|
108
|
+
: undefined
|
|
109
|
+
const indexKeyAbove =
|
|
110
|
+
siblingAboveIndex && siblingAboveIndex > prevParent.index
|
|
111
|
+
? siblingAboveIndex
|
|
112
|
+
: getIndexAbove(prevParent.index)
|
|
109
113
|
insertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)
|
|
110
114
|
} else {
|
|
111
115
|
// If the old parent is not a direct child of the new parent, then we'll add them to the "top" of the new parent's children.
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '4.6.0-canary.
|
|
4
|
+
export const version = '4.6.0-canary.4e3f067c74e9'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2025-09-18T14:39:22.803Z',
|
|
7
|
-
minor: '2026-04-
|
|
8
|
-
patch: '2026-04-
|
|
7
|
+
minor: '2026-04-10T10:42:07.953Z',
|
|
8
|
+
patch: '2026-04-10T10:42:07.953Z',
|
|
9
9
|
}
|