@tldraw/editor 3.14.0-internal.601adf6e424b → 3.14.0-internal.eada756f6aa0
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.d.ts +92 -13
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +51 -24
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +3 -1
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +1 -1
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +232 -0
- package/dist-cjs/lib/utils/reparenting.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +92 -13
- package/dist-esm/index.mjs +4 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +51 -24
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +3 -1
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +1 -1
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +216 -0
- package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +10 -11
- package/package.json +7 -7
- package/src/index.ts +5 -0
- package/src/lib/editor/Editor.ts +68 -34
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +3 -1
- package/src/lib/editor/shapes/ShapeUtil.ts +46 -15
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
- package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
- package/src/lib/utils/dom.ts +1 -1
- package/src/lib/utils/reparenting.ts +383 -0
- package/src/version.ts +3 -3
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { EMPTY_ARRAY } from "@tldraw/state";
|
|
2
|
+
import { compact, getIndexAbove, getIndexBetween } from "@tldraw/utils";
|
|
3
|
+
import { Group2d } from "../primitives/geometry/Group2d.mjs";
|
|
4
|
+
import {
|
|
5
|
+
intersectPolygonPolygon,
|
|
6
|
+
polygonIntersectsPolyline,
|
|
7
|
+
polygonsIntersect
|
|
8
|
+
} from "../primitives/intersect.mjs";
|
|
9
|
+
import { pointInPolygon } from "../primitives/utils.mjs";
|
|
10
|
+
function kickoutOccludedShapes(editor, shapeIds, opts) {
|
|
11
|
+
const parentsToCheck = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const id of shapeIds) {
|
|
13
|
+
const shape = editor.getShape(id);
|
|
14
|
+
if (!shape) continue;
|
|
15
|
+
parentsToCheck.add(shape);
|
|
16
|
+
const parent = editor.getShape(shape.parentId);
|
|
17
|
+
if (!parent) continue;
|
|
18
|
+
parentsToCheck.add(parent);
|
|
19
|
+
}
|
|
20
|
+
const parentsToLostChildren = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const parent of parentsToCheck) {
|
|
22
|
+
const childIds = editor.getSortedChildIdsForParent(parent);
|
|
23
|
+
if (opts?.filter && !opts.filter(parent)) {
|
|
24
|
+
parentsToLostChildren.set(parent, childIds);
|
|
25
|
+
} else {
|
|
26
|
+
const overlappingChildren = getOverlappingShapes(editor, parent.id, childIds);
|
|
27
|
+
if (overlappingChildren.length < childIds.length) {
|
|
28
|
+
parentsToLostChildren.set(
|
|
29
|
+
parent,
|
|
30
|
+
childIds.filter((id) => !overlappingChildren.includes(id))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const sortedShapeIds = editor.getCurrentPageShapesSorted().map((s) => s.id);
|
|
36
|
+
const parentsToNewChildren = {};
|
|
37
|
+
for (const [prevParent, lostChildrenIds] of parentsToLostChildren) {
|
|
38
|
+
const lostChildren = compact(lostChildrenIds.map((id) => editor.getShape(id)));
|
|
39
|
+
const { reparenting, remainingShapesToReparent } = getDroppedShapesToNewParents(
|
|
40
|
+
editor,
|
|
41
|
+
lostChildren,
|
|
42
|
+
(shape, maybeNewParent) => {
|
|
43
|
+
if (opts?.filter && !opts.filter(maybeNewParent)) return false;
|
|
44
|
+
return maybeNewParent.id !== prevParent.id && sortedShapeIds.indexOf(maybeNewParent.id) < sortedShapeIds.indexOf(shape.id);
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
reparenting.forEach((childrenToReparent, newParentId) => {
|
|
48
|
+
if (childrenToReparent.length === 0) return;
|
|
49
|
+
if (!parentsToNewChildren[newParentId]) {
|
|
50
|
+
parentsToNewChildren[newParentId] = {
|
|
51
|
+
parentId: newParentId,
|
|
52
|
+
shapeIds: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
parentsToNewChildren[newParentId].shapeIds.push(...childrenToReparent.map((s) => s.id));
|
|
56
|
+
});
|
|
57
|
+
if (remainingShapesToReparent.size > 0) {
|
|
58
|
+
const newParentId = editor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, "group"))?.id ?? editor.getCurrentPageId();
|
|
59
|
+
remainingShapesToReparent.forEach((shape) => {
|
|
60
|
+
if (!parentsToNewChildren[newParentId]) {
|
|
61
|
+
let insertIndexKey;
|
|
62
|
+
const oldParentSiblingIds = editor.getSortedChildIdsForParent(newParentId);
|
|
63
|
+
const oldParentIndex = oldParentSiblingIds.indexOf(prevParent.id);
|
|
64
|
+
if (oldParentIndex > -1) {
|
|
65
|
+
const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1];
|
|
66
|
+
const indexKeyAbove = siblingsIndexAbove ? editor.getShape(siblingsIndexAbove).index : getIndexAbove(prevParent.index);
|
|
67
|
+
insertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove);
|
|
68
|
+
} else {
|
|
69
|
+
}
|
|
70
|
+
parentsToNewChildren[newParentId] = {
|
|
71
|
+
parentId: newParentId,
|
|
72
|
+
shapeIds: [],
|
|
73
|
+
index: insertIndexKey
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
parentsToNewChildren[newParentId].shapeIds.push(shape.id);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
editor.run(() => {
|
|
81
|
+
Object.values(parentsToNewChildren).forEach(({ parentId, shapeIds: shapeIds2, index }) => {
|
|
82
|
+
if (shapeIds2.length === 0) return;
|
|
83
|
+
shapeIds2.sort((a, b) => sortedShapeIds.indexOf(a) < sortedShapeIds.indexOf(b) ? -1 : 1);
|
|
84
|
+
editor.reparentShapes(shapeIds2, parentId, index);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function getOverlappingShapes(editor, shape, otherShapes) {
|
|
89
|
+
if (otherShapes.length === 0) {
|
|
90
|
+
return EMPTY_ARRAY;
|
|
91
|
+
}
|
|
92
|
+
const parentPageBounds = editor.getShapePageBounds(shape);
|
|
93
|
+
if (!parentPageBounds) return EMPTY_ARRAY;
|
|
94
|
+
const parentGeometry = editor.getShapeGeometry(shape);
|
|
95
|
+
const parentPageTransform = editor.getShapePageTransform(shape);
|
|
96
|
+
const parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices);
|
|
97
|
+
const parentPageMaskVertices = editor.getShapeMask(shape);
|
|
98
|
+
const parentPagePolygon = parentPageMaskVertices ? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners) : parentPageCorners;
|
|
99
|
+
if (!parentPagePolygon) return EMPTY_ARRAY;
|
|
100
|
+
return otherShapes.filter((childId) => {
|
|
101
|
+
const shapePageBounds = editor.getShapePageBounds(childId);
|
|
102
|
+
if (!shapePageBounds || !parentPageBounds.includes(shapePageBounds)) return false;
|
|
103
|
+
const parentPolygonInShapeShape = editor.getShapePageTransform(childId).clone().invert().applyToPoints(parentPagePolygon);
|
|
104
|
+
const geometry = editor.getShapeGeometry(childId);
|
|
105
|
+
return doesGeometryOverlapPolygon(geometry, parentPolygonInShapeShape);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function doesGeometryOverlapPolygon(geometry, parentCornersInShapeSpace) {
|
|
109
|
+
if (geometry instanceof Group2d) {
|
|
110
|
+
return geometry.children.some(
|
|
111
|
+
(childGeometry) => doesGeometryOverlapPolygon(childGeometry, parentCornersInShapeSpace)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const { vertices, center, isFilled, isEmptyLabel, isClosed } = geometry;
|
|
115
|
+
if (isEmptyLabel) return false;
|
|
116
|
+
if (vertices.some((v) => pointInPolygon(v, parentCornersInShapeSpace))) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (isClosed) {
|
|
120
|
+
if (isFilled) {
|
|
121
|
+
if (pointInPolygon(center, parentCornersInShapeSpace)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (parentCornersInShapeSpace.every((v) => pointInPolygon(v, vertices))) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (polygonsIntersect(parentCornersInShapeSpace, vertices)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
if (polygonIntersectsPolyline(parentCornersInShapeSpace, vertices)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
function getDroppedShapesToNewParents(editor, shapes, cb) {
|
|
139
|
+
const shapesToActuallyCheck = new Set(shapes);
|
|
140
|
+
const movingGroups = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const shape of shapes) {
|
|
142
|
+
const parent = editor.getShapeParent(shape);
|
|
143
|
+
if (parent && editor.isShapeOfType(parent, "group")) {
|
|
144
|
+
if (!movingGroups.has(parent)) {
|
|
145
|
+
movingGroups.add(parent);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const movingGroup of movingGroups) {
|
|
150
|
+
const children = compact(
|
|
151
|
+
editor.getSortedChildIdsForParent(movingGroup).map((id) => editor.getShape(id))
|
|
152
|
+
);
|
|
153
|
+
for (const child of children) {
|
|
154
|
+
shapesToActuallyCheck.delete(child);
|
|
155
|
+
}
|
|
156
|
+
shapesToActuallyCheck.add(movingGroup);
|
|
157
|
+
}
|
|
158
|
+
const shapeGroupIds = /* @__PURE__ */ new Map();
|
|
159
|
+
const reparenting = /* @__PURE__ */ new Map();
|
|
160
|
+
const remainingShapesToReparent = new Set(shapesToActuallyCheck);
|
|
161
|
+
const potentialParentShapes = editor.getCurrentPageShapesSorted().filter(
|
|
162
|
+
(s) => editor.getShapeUtil(s).canReceiveNewChildrenOfType?.(s, s.type) && !remainingShapesToReparent.has(s)
|
|
163
|
+
);
|
|
164
|
+
parentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {
|
|
165
|
+
const parentShape = potentialParentShapes[i];
|
|
166
|
+
const parentShapeContainingGroupId = editor.findShapeAncestor(
|
|
167
|
+
parentShape,
|
|
168
|
+
(s) => editor.isShapeOfType(s, "group")
|
|
169
|
+
)?.id;
|
|
170
|
+
const parentGeometry = editor.getShapeGeometry(parentShape);
|
|
171
|
+
const parentPageTransform = editor.getShapePageTransform(parentShape);
|
|
172
|
+
const parentPageMaskVertices = editor.getShapeMask(parentShape);
|
|
173
|
+
const parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices);
|
|
174
|
+
const parentPagePolygon = parentPageMaskVertices ? intersectPolygonPolygon(parentPageMaskVertices, parentPageCorners) : parentPageCorners;
|
|
175
|
+
if (!parentPagePolygon) continue parentCheck;
|
|
176
|
+
const childrenToReparent = [];
|
|
177
|
+
shapeCheck: for (const shape of remainingShapesToReparent) {
|
|
178
|
+
if (parentShape.id === shape.id) continue shapeCheck;
|
|
179
|
+
if (cb && !cb(shape, parentShape)) continue shapeCheck;
|
|
180
|
+
if (!shapeGroupIds.has(shape.id)) {
|
|
181
|
+
shapeGroupIds.set(
|
|
182
|
+
shape.id,
|
|
183
|
+
editor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, "group"))?.id
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const shapeGroupId = shapeGroupIds.get(shape.id);
|
|
187
|
+
if (shapeGroupId !== parentShapeContainingGroupId) continue shapeCheck;
|
|
188
|
+
if (editor.findShapeAncestor(parentShape, (s) => shape.id === s.id)) continue shapeCheck;
|
|
189
|
+
const parentPolygonInShapeSpace = editor.getShapePageTransform(shape).clone().invert().applyToPoints(parentPagePolygon);
|
|
190
|
+
if (doesGeometryOverlapPolygon(editor.getShapeGeometry(shape), parentPolygonInShapeSpace)) {
|
|
191
|
+
if (!editor.getShapeUtil(parentShape).canReceiveNewChildrenOfType?.(parentShape, shape.type))
|
|
192
|
+
continue shapeCheck;
|
|
193
|
+
if (shape.parentId !== parentShape.id) {
|
|
194
|
+
childrenToReparent.push(shape);
|
|
195
|
+
}
|
|
196
|
+
remainingShapesToReparent.delete(shape);
|
|
197
|
+
continue shapeCheck;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (childrenToReparent.length) {
|
|
201
|
+
reparenting.set(parentShape.id, childrenToReparent);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
// these are the shapes that will be reparented to new parents
|
|
206
|
+
reparenting,
|
|
207
|
+
// these are the shapes that will be reparented to the page or their ancestral group
|
|
208
|
+
remainingShapesToReparent
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
doesGeometryOverlapPolygon,
|
|
213
|
+
getDroppedShapesToNewParents,
|
|
214
|
+
kickoutOccludedShapes
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=reparenting.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 { Vec } from '../primitives/Vec'\nimport { Geometry2d } from '../primitives/geometry/Geometry2d'\nimport { Group2d } from '../primitives/geometry/Group2d'\nimport {\n\tintersectPolygonPolygon,\n\tpolygonIntersectsPolyline,\n\tpolygonsIntersect,\n} from '../primitives/intersect'\nimport { pointInPolygon } from '../primitives/utils'\n\n/**\n * Reparents shapes that are no longer contained within their parent shapes.\n * todo: rename me to something more descriptive, like `reparentOccludedShapes` or `reparentAutoDroppedShapes`\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// Don't fall \"up\" into frames in front of the shape\n\t\t// if (pageShapes.indexOf(shape) < frameSortPosition) continue shapeCheck\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<TLGroupShape>(s, 'group'))\n\t\t\t\t\t?.id ?? editor.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 parentGeometry = editor.getShapeGeometry(shape)\n\tconst parentPageTransform = editor.getShapePageTransform(shape)\n\tconst parentPageCorners = parentPageTransform.applyToPoints(parentGeometry.vertices)\n\n\tconst parentPageMaskVertices = editor.getShapeMask(shape)\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 doesGeometryOverlapPolygon(geometry, parentPolygonInShapeShape)\n\t})\n}\n\n/**\n * @public\n */\nexport function doesGeometryOverlapPolygon(\n\tgeometry: Geometry2d,\n\tparentCornersInShapeSpace: Vec[]\n): boolean {\n\t// If the child is a group, check if any of its children overlap the box\n\tif (geometry instanceof Group2d) {\n\t\treturn geometry.children.some((childGeometry) =>\n\t\t\tdoesGeometryOverlapPolygon(childGeometry, parentCornersInShapeSpace)\n\t\t)\n\t}\n\n\t// Otherwise, check if the geometry overlaps the box\n\tconst { vertices, center, isFilled, isEmptyLabel, isClosed } = geometry\n\n\t// We'll do things in order of cheapest to most expensive checks\n\n\t// Skip empty labels\n\tif (isEmptyLabel) return false\n\n\t// If any of the shape's vertices are inside the occluder, it's inside\n\tif (vertices.some((v) => pointInPolygon(v, parentCornersInShapeSpace))) {\n\t\treturn true\n\t}\n\n\t// If the shape is filled and closed and its center is inside the parent, it's inside\n\tif (isClosed) {\n\t\tif (isFilled) {\n\t\t\t// If closed and filled, check if the center is inside the parent\n\t\t\tif (pointInPolygon(center, parentCornersInShapeSpace)) {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// ..then, slightly more expensive check, see the shape covers the entire parent but not its center\n\t\t\tif (parentCornersInShapeSpace.every((v) => pointInPolygon(v, vertices))) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\t// If any the shape's vertices intersect the edge of the occluder, it's inside.\n\t\t// for example when a rotated rectangle is moved over the corner of a parent rectangle\n\t\t// If the child shape is closed, intersect as a polygon\n\t\tif (polygonsIntersect(parentCornersInShapeSpace, vertices)) {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\t// if the child shape is not closed, intersect as a polyline\n\t\tif (polygonIntersectsPolyline(parentCornersInShapeSpace, vertices)) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// If none of the above checks passed, the shape is outside the parent\n\treturn false\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<TLGroupShape>(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<TLGroupShape>(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<TLGroupShape>(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 is 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 (doesGeometryOverlapPolygon(editor.getShapeGeometry(shape), 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
|
+
"names": ["shapeIds"]
|
|
7
|
+
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.14.0-internal.
|
|
1
|
+
const version = "3.14.0-internal.eada756f6aa0";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-06-
|
|
5
|
-
patch: "2025-06-
|
|
4
|
+
minor: "2025-06-27T08:29:38.348Z",
|
|
5
|
+
patch: "2025-06-27T08:29:38.348Z"
|
|
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 = '3.14.0-internal.
|
|
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 = '3.14.0-internal.eada756f6aa0'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-27T08:29:38.348Z',\n\tpatch: '2025-06-27T08:29:38.348Z',\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/editor.css
CHANGED
|
@@ -176,8 +176,7 @@
|
|
|
176
176
|
--color-success: hsl(123, 46%, 34%);
|
|
177
177
|
--color-info: hsl(201, 98%, 41%);
|
|
178
178
|
--color-warning: hsl(27, 98%, 47%);
|
|
179
|
-
--color-
|
|
180
|
-
--color-warn: hsl(0, 90%, 43%);
|
|
179
|
+
--color-danger: hsl(0, 90%, 43%);
|
|
181
180
|
--color-laser: hsl(0, 100%, 50%);
|
|
182
181
|
/* Shadows */
|
|
183
182
|
--shadow-1: 0px 1px 2px hsl(0, 0%, 0%, 25%), 0px 1px 3px hsl(0, 0%, 0%, 9%);
|
|
@@ -232,8 +231,7 @@
|
|
|
232
231
|
--color-success: hsl(123, 38%, 57%);
|
|
233
232
|
--color-info: hsl(199, 92%, 56%);
|
|
234
233
|
--color-warning: hsl(36, 100%, 57%);
|
|
235
|
-
--color-
|
|
236
|
-
--color-warn: hsl(0, 81%, 66%);
|
|
234
|
+
--color-danger: hsl(0, 82%, 66%);
|
|
237
235
|
--color-laser: hsl(0, 100%, 50%);
|
|
238
236
|
/* Shadows */
|
|
239
237
|
--shadow-1:
|
|
@@ -1057,8 +1055,8 @@ input,
|
|
|
1057
1055
|
}
|
|
1058
1056
|
|
|
1059
1057
|
.tl-hyperlink__icon {
|
|
1060
|
-
width:
|
|
1061
|
-
height:
|
|
1058
|
+
width: 15px;
|
|
1059
|
+
height: 15px;
|
|
1062
1060
|
background-color: currentColor;
|
|
1063
1061
|
pointer-events: none;
|
|
1064
1062
|
}
|
|
@@ -1157,7 +1155,7 @@ input,
|
|
|
1157
1155
|
stroke-linejoin: round;
|
|
1158
1156
|
/* content-visibility: auto; */
|
|
1159
1157
|
transform-origin: top left;
|
|
1160
|
-
color:
|
|
1158
|
+
color: var(--color-text-1);
|
|
1161
1159
|
}
|
|
1162
1160
|
|
|
1163
1161
|
/* -------------------- Group shape ------------------ */
|
|
@@ -1272,6 +1270,7 @@ input,
|
|
|
1272
1270
|
display: flex;
|
|
1273
1271
|
justify-content: flex-end;
|
|
1274
1272
|
align-items: flex-start;
|
|
1273
|
+
box-shadow: inset 0px 0px 0px 1px var(--color-divider);
|
|
1275
1274
|
}
|
|
1276
1275
|
|
|
1277
1276
|
.tl-bookmark__image_container > .tl-hyperlink-button::after {
|
|
@@ -1294,7 +1293,7 @@ input,
|
|
|
1294
1293
|
}
|
|
1295
1294
|
|
|
1296
1295
|
.tl-bookmark__copy_container {
|
|
1297
|
-
background-color: var(--color-muted);
|
|
1296
|
+
background-color: var(--color-muted-0);
|
|
1298
1297
|
padding: var(--space-4);
|
|
1299
1298
|
pointer-events: all;
|
|
1300
1299
|
display: flex;
|
|
@@ -1313,11 +1312,11 @@ input,
|
|
|
1313
1312
|
|
|
1314
1313
|
.tl-bookmark__heading {
|
|
1315
1314
|
font-size: 16px;
|
|
1316
|
-
line-height: 1.
|
|
1315
|
+
line-height: 1.6;
|
|
1317
1316
|
font-weight: bold;
|
|
1318
1317
|
padding-bottom: var(--space-2);
|
|
1319
1318
|
overflow: hidden;
|
|
1320
|
-
max-height: calc((16px * 1.
|
|
1319
|
+
max-height: calc((16px * 1.6) * 2);
|
|
1321
1320
|
-webkit-box-orient: vertical;
|
|
1322
1321
|
-webkit-line-clamp: 2;
|
|
1323
1322
|
line-clamp: 2;
|
|
@@ -1706,7 +1705,7 @@ it from receiving any pointer events or affecting the cursor. */
|
|
|
1706
1705
|
gap: var(--space-4);
|
|
1707
1706
|
}
|
|
1708
1707
|
.tl-error-boundary__content .tl-error-boundary__reset {
|
|
1709
|
-
color: var(--color-
|
|
1708
|
+
color: var(--color-danger);
|
|
1710
1709
|
}
|
|
1711
1710
|
.tl-error-boundary__content .tl-error-boundary__refresh {
|
|
1712
1711
|
background-color: var(--color-primary);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.14.0-internal.
|
|
4
|
+
"version": "3.14.0-internal.eada756f6aa0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.14.0-internal.
|
|
52
|
-
"@tldraw/state-react": "3.14.0-internal.
|
|
53
|
-
"@tldraw/store": "3.14.0-internal.
|
|
54
|
-
"@tldraw/tlschema": "3.14.0-internal.
|
|
55
|
-
"@tldraw/utils": "3.14.0-internal.
|
|
56
|
-
"@tldraw/validate": "3.14.0-internal.
|
|
51
|
+
"@tldraw/state": "3.14.0-internal.eada756f6aa0",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-internal.eada756f6aa0",
|
|
53
|
+
"@tldraw/store": "3.14.0-internal.eada756f6aa0",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-internal.eada756f6aa0",
|
|
55
|
+
"@tldraw/utils": "3.14.0-internal.eada756f6aa0",
|
|
56
|
+
"@tldraw/validate": "3.14.0-internal.eada756f6aa0",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -182,6 +182,10 @@ export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseB
|
|
|
182
182
|
export {
|
|
183
183
|
ShapeUtil,
|
|
184
184
|
type TLCropInfo,
|
|
185
|
+
type TLDragShapesInInfo,
|
|
186
|
+
type TLDragShapesOutInfo,
|
|
187
|
+
type TLDragShapesOverInfo,
|
|
188
|
+
type TLDropShapesOverInfo,
|
|
185
189
|
type TLGeometryOpts,
|
|
186
190
|
type TLHandleDragInfo,
|
|
187
191
|
type TLResizeInfo,
|
|
@@ -446,6 +450,7 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
|
|
|
446
450
|
export { isAccelKey } from './lib/utils/keyboard'
|
|
447
451
|
export { normalizeWheel } from './lib/utils/normalizeWheel'
|
|
448
452
|
export { refreshPage } from './lib/utils/refreshPage'
|
|
453
|
+
export { getDroppedShapesToNewParents, kickoutOccludedShapes } from './lib/utils/reparenting'
|
|
449
454
|
export {
|
|
450
455
|
getFontsFromRichText,
|
|
451
456
|
type RichTextFontVisitor,
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -3662,7 +3662,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
3662
3662
|
* @public
|
|
3663
3663
|
*/
|
|
3664
3664
|
updateViewportScreenBounds(screenBounds: Box | HTMLElement, center = false): this {
|
|
3665
|
-
if (screenBounds instanceof
|
|
3665
|
+
if (!(screenBounds instanceof Box)) {
|
|
3666
3666
|
const rect = screenBounds.getBoundingClientRect()
|
|
3667
3667
|
screenBounds = new Box(
|
|
3668
3668
|
rect.left || rect.x,
|
|
@@ -5528,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5528
5528
|
if (!id) return undefined
|
|
5529
5529
|
const freshShape = this.getShape(id)
|
|
5530
5530
|
if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
|
|
5531
|
-
return this.
|
|
5531
|
+
return this.getShape(freshShape.parentId)
|
|
5532
5532
|
}
|
|
5533
5533
|
|
|
5534
5534
|
/**
|
|
@@ -5711,6 +5711,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5711
5711
|
const newPoint = invertedParentTransform.applyToPoint(pagePoint)
|
|
5712
5712
|
const newRotation = pageTransform.rotation() - parentPageRotation
|
|
5713
5713
|
|
|
5714
|
+
if (shape.id === parentId) {
|
|
5715
|
+
throw Error('Attempted to reparent a shape to itself!')
|
|
5716
|
+
}
|
|
5717
|
+
|
|
5714
5718
|
changes.push({
|
|
5715
5719
|
id: shape.id,
|
|
5716
5720
|
type: shape.type,
|
|
@@ -5814,6 +5818,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5814
5818
|
return shapeIds
|
|
5815
5819
|
}
|
|
5816
5820
|
|
|
5821
|
+
/** @deprecated Use {@link Editor.getDraggingOverShape} instead */
|
|
5822
|
+
getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
|
|
5823
|
+
return this.getDraggingOverShape(point, droppingShapes)
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5817
5826
|
/**
|
|
5818
5827
|
* Get the shape that some shapes should be dropped on at a given point.
|
|
5819
5828
|
*
|
|
@@ -5824,35 +5833,33 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5824
5833
|
*
|
|
5825
5834
|
* @public
|
|
5826
5835
|
*/
|
|
5827
|
-
|
|
5828
|
-
//
|
|
5829
|
-
const
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
if (
|
|
5834
|
-
// ignore hidden shapes
|
|
5835
|
-
this.isShapeHidden(shape) ||
|
|
5836
|
-
// don't allow dropping on selected shapes
|
|
5837
|
-
this.getSelectedShapeIds().includes(shape.id) ||
|
|
5838
|
-
// only allow shapes that can receive children
|
|
5839
|
-
!this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
|
|
5840
|
-
// don't allow dropping a shape on itself or one of it's children
|
|
5841
|
-
droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
|
|
5842
|
-
) {
|
|
5843
|
-
continue
|
|
5844
|
-
}
|
|
5836
|
+
getDraggingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
|
|
5837
|
+
// get fresh moving shapes
|
|
5838
|
+
const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
|
|
5839
|
+
(s) => !s.isLocked && !this.isShapeHidden(s)
|
|
5840
|
+
)
|
|
5845
5841
|
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5842
|
+
const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
|
|
5843
|
+
hitInside: true,
|
|
5844
|
+
margin: 0,
|
|
5845
|
+
}).filter(
|
|
5846
|
+
(s) =>
|
|
5847
|
+
!droppingShapes.includes(s) &&
|
|
5848
|
+
!s.isLocked &&
|
|
5849
|
+
!this.isShapeHidden(s) &&
|
|
5850
|
+
!draggingShapes.includes(s)
|
|
5851
|
+
)
|
|
5849
5852
|
|
|
5853
|
+
for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
|
|
5854
|
+
const shapeUtil = this.getShapeUtil(maybeDraggingOverShape)
|
|
5855
|
+
// Any shape that can handle any dragging interactions is a valid target
|
|
5850
5856
|
if (
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5857
|
+
shapeUtil.onDragShapesOver ||
|
|
5858
|
+
shapeUtil.onDragShapesIn ||
|
|
5859
|
+
shapeUtil.onDragShapesOut ||
|
|
5860
|
+
shapeUtil.onDropShapesOver
|
|
5854
5861
|
) {
|
|
5855
|
-
return
|
|
5862
|
+
return maybeDraggingOverShape
|
|
5856
5863
|
}
|
|
5857
5864
|
}
|
|
5858
5865
|
}
|
|
@@ -6296,10 +6303,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
6296
6303
|
})
|
|
6297
6304
|
const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
|
|
6298
6305
|
|
|
6299
|
-
|
|
6300
|
-
shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
|
|
6301
|
-
|
|
6302
|
-
if (maxShapesReached) {
|
|
6306
|
+
if (!this.canCreateShapes(shapesToCreate)) {
|
|
6303
6307
|
alertMaxShapes(this)
|
|
6304
6308
|
return
|
|
6305
6309
|
}
|
|
@@ -7728,6 +7732,32 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7728
7732
|
return {}
|
|
7729
7733
|
}
|
|
7730
7734
|
|
|
7735
|
+
/**
|
|
7736
|
+
* Get whether the provided shape can be created.
|
|
7737
|
+
*
|
|
7738
|
+
* @param shape - The shape or shape IDs to check.
|
|
7739
|
+
*
|
|
7740
|
+
* @public
|
|
7741
|
+
*/
|
|
7742
|
+
canCreateShape<T extends TLUnknownShape>(
|
|
7743
|
+
shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
|
|
7744
|
+
): boolean {
|
|
7745
|
+
return this.canCreateShapes([shape])
|
|
7746
|
+
}
|
|
7747
|
+
|
|
7748
|
+
/**
|
|
7749
|
+
* Get whether the provided shapes can be created.
|
|
7750
|
+
*
|
|
7751
|
+
* @param shapes - The shapes or shape IDs to create.
|
|
7752
|
+
*
|
|
7753
|
+
* @public
|
|
7754
|
+
*/
|
|
7755
|
+
canCreateShapes<T extends TLUnknownShape>(
|
|
7756
|
+
shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
|
|
7757
|
+
): boolean {
|
|
7758
|
+
return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
|
|
7759
|
+
}
|
|
7760
|
+
|
|
7731
7761
|
/**
|
|
7732
7762
|
* Create a single shape.
|
|
7733
7763
|
*
|
|
@@ -7774,6 +7804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7774
7804
|
if (maxShapesReached) {
|
|
7775
7805
|
// can't create more shapes than fit on the page
|
|
7776
7806
|
alertMaxShapes(this)
|
|
7807
|
+
// todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
|
|
7777
7808
|
return this
|
|
7778
7809
|
}
|
|
7779
7810
|
|
|
@@ -7806,9 +7837,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
7806
7837
|
|
|
7807
7838
|
for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
|
|
7808
7839
|
const parent = currentPageShapesSorted[i]
|
|
7840
|
+
const util = this.getShapeUtil(parent)
|
|
7809
7841
|
if (
|
|
7842
|
+
util.canReceiveNewChildrenOfType(parent, partial.type) &&
|
|
7810
7843
|
!this.isShapeHidden(parent) &&
|
|
7811
|
-
this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
|
|
7812
7844
|
this.isPointInShape(
|
|
7813
7845
|
parent,
|
|
7814
7846
|
// If no parent is provided, then we can treat the
|
|
@@ -9512,6 +9544,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9512
9544
|
previousPagePoint,
|
|
9513
9545
|
currentScreenPoint,
|
|
9514
9546
|
currentPagePoint,
|
|
9547
|
+
originScreenPoint,
|
|
9548
|
+
originPagePoint,
|
|
9515
9549
|
} = this.inputs
|
|
9516
9550
|
|
|
9517
9551
|
const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
|
|
@@ -9540,8 +9574,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
9540
9574
|
// Reset velocity on pointer down, or when a pinch starts or ends
|
|
9541
9575
|
if (info.name === 'pointer_down' || this.inputs.isPinching) {
|
|
9542
9576
|
pointerVelocity.set(0, 0)
|
|
9543
|
-
|
|
9544
|
-
|
|
9577
|
+
originScreenPoint.setTo(currentScreenPoint)
|
|
9578
|
+
originPagePoint.setTo(currentPagePoint)
|
|
9545
9579
|
}
|
|
9546
9580
|
|
|
9547
9581
|
// todo: We only have to do this if there are multiple users in the document
|
|
@@ -241,7 +241,9 @@ export class HistoryManager<R extends UnknownRecord> {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
bailToMark(id: string) {
|
|
244
|
-
|
|
244
|
+
if (id) {
|
|
245
|
+
this._undo({ pushToRedoStack: false, toMark: id })
|
|
246
|
+
}
|
|
245
247
|
|
|
246
248
|
return this
|
|
247
249
|
}
|