@yuzhouu/canvas-kit 0.1.2

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.
Files changed (263) hide show
  1. package/README.md +145 -0
  2. package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
  3. package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
  4. package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
  5. package/_vendor/canvas-core/contextMenu.ts +85 -0
  6. package/_vendor/canvas-core/editor/Editor.ts +2614 -0
  7. package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
  8. package/_vendor/canvas-core/editor/StateNode.ts +162 -0
  9. package/_vendor/canvas-core/editor/camera.ts +6 -0
  10. package/_vendor/canvas-core/editor/clipboard.ts +478 -0
  11. package/_vendor/canvas-core/editor/commands.ts +833 -0
  12. package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
  13. package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
  14. package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
  15. package/_vendor/canvas-core/editor/events.ts +93 -0
  16. package/_vendor/canvas-core/editor/index.ts +12 -0
  17. package/_vendor/canvas-core/editor/interactions.ts +101 -0
  18. package/_vendor/canvas-core/editor/internal.ts +22 -0
  19. package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
  20. package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
  21. package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
  22. package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
  23. package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
  24. package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
  25. package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
  26. package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
  27. package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
  28. package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
  29. package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
  30. package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
  31. package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
  32. package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
  33. package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
  34. package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
  35. package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
  36. package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
  37. package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
  38. package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
  39. package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
  40. package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
  41. package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
  42. package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
  43. package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
  44. package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
  45. package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
  46. package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
  47. package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
  48. package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
  49. package/_vendor/canvas-core/editor/selection.ts +489 -0
  50. package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
  51. package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
  52. package/_vendor/canvas-core/exports/integrity.ts +174 -0
  53. package/_vendor/canvas-core/exports/svg.ts +494 -0
  54. package/_vendor/canvas-core/geometry/Box.ts +80 -0
  55. package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
  56. package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
  57. package/_vendor/canvas-core/geometry/Mat.ts +92 -0
  58. package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
  59. package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
  60. package/_vendor/canvas-core/geometry/Vec.ts +123 -0
  61. package/_vendor/canvas-core/geometry/index.ts +13 -0
  62. package/_vendor/canvas-core/geometry/segments.ts +65 -0
  63. package/_vendor/canvas-core/index.ts +391 -0
  64. package/_vendor/canvas-core/plugins/index.ts +5 -0
  65. package/_vendor/canvas-core/plugins/types.ts +53 -0
  66. package/_vendor/canvas-core/reactivity.ts +16 -0
  67. package/_vendor/canvas-core/schema/records.ts +221 -0
  68. package/_vendor/canvas-core/schema/snapshots.ts +527 -0
  69. package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
  70. package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
  71. package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
  72. package/_vendor/canvas-core/shapes/types.ts +20 -0
  73. package/_vendor/canvas-core/toolbar.ts +148 -0
  74. package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
  75. package/_vendor/canvas-core/tools/index.ts +2 -0
  76. package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
  77. package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
  78. package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
  79. package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
  80. package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
  81. package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
  82. package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
  83. package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
  84. package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
  85. package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
  86. package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
  87. package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
  88. package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
  89. package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
  90. package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
  91. package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
  92. package/_vendor/canvas-plugin-api/feature.ts +204 -0
  93. package/_vendor/canvas-plugin-api/index.ts +142 -0
  94. package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
  95. package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
  96. package/_vendor/canvas-product-react/api/index.ts +31 -0
  97. package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
  98. package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
  99. package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
  100. package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
  101. package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
  102. package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
  103. package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
  104. package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
  105. package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
  106. package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
  107. package/_vendor/canvas-product-react/index.ts +84 -0
  108. package/_vendor/canvas-product-react/persistence/index.ts +18 -0
  109. package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
  110. package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
  111. package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
  112. package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
  113. package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
  114. package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
  115. package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
  116. package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
  117. package/_vendor/canvas-react/components/index.ts +4 -0
  118. package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
  119. package/_vendor/canvas-react/hooks/useValue.ts +14 -0
  120. package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
  121. package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
  122. package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
  123. package/_vendor/canvas-react/host/index.ts +38 -0
  124. package/_vendor/canvas-react/host/pluginHost.ts +635 -0
  125. package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
  126. package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
  127. package/_vendor/canvas-react/index.ts +3 -0
  128. package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
  129. package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
  130. package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
  131. package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
  132. package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
  133. package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
  134. package/_vendor/plugin-arrow/canvas.tsx +92 -0
  135. package/_vendor/plugin-arrow/core.ts +409 -0
  136. package/_vendor/plugin-arrow/feature.ts +20 -0
  137. package/_vendor/plugin-arrow/index.ts +28 -0
  138. package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
  139. package/_vendor/plugin-arrow/types.ts +63 -0
  140. package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
  141. package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
  142. package/_vendor/plugin-draw/DrawTool.ts +444 -0
  143. package/_vendor/plugin-draw/canvas.tsx +24 -0
  144. package/_vendor/plugin-draw/core.ts +515 -0
  145. package/_vendor/plugin-draw/drawGeometry.ts +491 -0
  146. package/_vendor/plugin-draw/drawHitTest.ts +271 -0
  147. package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
  148. package/_vendor/plugin-draw/drawPath.ts +550 -0
  149. package/_vendor/plugin-draw/drawSegments.ts +106 -0
  150. package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
  151. package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
  152. package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
  153. package/_vendor/plugin-draw/feature.ts +20 -0
  154. package/_vendor/plugin-draw/index.ts +31 -0
  155. package/_vendor/plugin-draw/types.ts +36 -0
  156. package/_vendor/plugin-eraser/EraserTool.ts +297 -0
  157. package/_vendor/plugin-eraser/canvas.tsx +616 -0
  158. package/_vendor/plugin-eraser/core.ts +49 -0
  159. package/_vendor/plugin-eraser/feature.ts +20 -0
  160. package/_vendor/plugin-eraser/index.ts +4 -0
  161. package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
  162. package/_vendor/plugin-frame/FrameTool.ts +225 -0
  163. package/_vendor/plugin-frame/canvas.tsx +75 -0
  164. package/_vendor/plugin-frame/core.ts +534 -0
  165. package/_vendor/plugin-frame/feature.ts +20 -0
  166. package/_vendor/plugin-frame/index.ts +6 -0
  167. package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
  168. package/_vendor/plugin-geo/GeoTools.ts +247 -0
  169. package/_vendor/plugin-geo/canvas.tsx +90 -0
  170. package/_vendor/plugin-geo/core.ts +495 -0
  171. package/_vendor/plugin-geo/feature.ts +20 -0
  172. package/_vendor/plugin-geo/index.ts +7 -0
  173. package/_vendor/plugin-geo/types.ts +27 -0
  174. package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
  175. package/_vendor/plugin-group/canvas.tsx +59 -0
  176. package/_vendor/plugin-group/core.ts +40 -0
  177. package/_vendor/plugin-group/feature.ts +20 -0
  178. package/_vendor/plugin-group/index.ts +5 -0
  179. package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
  180. package/_vendor/plugin-image/canvas.tsx +212 -0
  181. package/_vendor/plugin-image/core.ts +542 -0
  182. package/_vendor/plugin-image/crop.ts +770 -0
  183. package/_vendor/plugin-image/cropInteractions.ts +350 -0
  184. package/_vendor/plugin-image/cropSession.ts +38 -0
  185. package/_vendor/plugin-image/feature.ts +20 -0
  186. package/_vendor/plugin-image/index.ts +29 -0
  187. package/_vendor/plugin-image/types.ts +55 -0
  188. package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
  189. package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
  190. package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
  191. package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
  192. package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
  193. package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
  194. package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
  195. package/_vendor/plugin-image-product-react/index.ts +60 -0
  196. package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
  197. package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
  198. package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
  199. package/_vendor/plugin-laser/LaserTool.ts +154 -0
  200. package/_vendor/plugin-laser/canvas.tsx +645 -0
  201. package/_vendor/plugin-laser/core.ts +49 -0
  202. package/_vendor/plugin-laser/feature.ts +20 -0
  203. package/_vendor/plugin-laser/index.ts +4 -0
  204. package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
  205. package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
  206. package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
  207. package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
  208. package/_vendor/plugin-text/TextTool.ts +228 -0
  209. package/_vendor/plugin-text/canvas.tsx +115 -0
  210. package/_vendor/plugin-text/core.ts +422 -0
  211. package/_vendor/plugin-text/feature.ts +20 -0
  212. package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
  213. package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
  214. package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
  215. package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
  216. package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
  217. package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
  218. package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
  219. package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
  220. package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
  221. package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
  222. package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
  223. package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
  224. package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
  225. package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
  226. package/_vendor/plugin-text/index.ts +18 -0
  227. package/_vendor/plugin-text/richTextUtils.ts +259 -0
  228. package/_vendor/plugin-text/textInteractions.ts +366 -0
  229. package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
  230. package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
  231. package/_vendor/plugin-text/textStyle.ts +96 -0
  232. package/_vendor/plugin-text/textTypes.ts +48 -0
  233. package/_vendor/signals/atom.ts +74 -0
  234. package/_vendor/signals/computed.ts +132 -0
  235. package/_vendor/signals/core.ts +130 -0
  236. package/_vendor/signals/effect.ts +117 -0
  237. package/_vendor/signals/index.ts +9 -0
  238. package/_vendor/signals/types.ts +17 -0
  239. package/_vendor/store/Store.ts +171 -0
  240. package/_vendor/store/diff.ts +229 -0
  241. package/_vendor/store/history.ts +177 -0
  242. package/_vendor/store/index.ts +32 -0
  243. package/_vendor/store/types.ts +37 -0
  244. package/api/canvasKitApi.d.ts +29 -0
  245. package/index.css +3 -0
  246. package/index.d.ts +16 -0
  247. package/index.mjs +1 -0
  248. package/internal-modules.d.ts +187 -0
  249. package/package.json +48 -0
  250. package/product/Shell.d.ts +19 -0
  251. package/product/chromeContext.d.ts +70 -0
  252. package/product/createCanvasKitEditor.d.ts +6 -0
  253. package/product/index.d.ts +7 -0
  254. package/product/persistence.d.ts +3 -0
  255. package/product/product.d.ts +22 -0
  256. package/product/useCanvasClipboardShortcuts.d.ts +6 -0
  257. package/product-iyBbEPIs.mjs +4 -0
  258. package/product.mjs +1 -0
  259. package/standard/extensions.d.ts +1 -0
  260. package/standard/index.d.ts +3 -0
  261. package/standard/standardApi.d.ts +91 -0
  262. package/standard-BmrxwqpJ.mjs +44 -0
  263. package/standard.mjs +1 -0
@@ -0,0 +1,797 @@
1
+ import type { ChangeSource } from "@land/store";
2
+ import { Box } from "../../geometry/Box";
3
+ import { Vec } from "../../geometry/Vec";
4
+ import type {
5
+ PageId,
6
+ ShapeId,
7
+ ShapeParentId,
8
+ ShapeRecord,
9
+ } from "../../schema/records";
10
+ import type { Editor } from "../Editor";
11
+ import type {
12
+ ContainerBoundsQueryOptions,
13
+ LayerOrderDirection,
14
+ LayerOrderEdge,
15
+ LayerTreeNode,
16
+ ReparentShapePlacementInput,
17
+ ReparentShapePlacementResult,
18
+ ShapeGroupState,
19
+ ShapeSelectionSiblingOrderState,
20
+ ShapeSiblingOrderDirection,
21
+ ShapeSiblingOrderEdge,
22
+ ShapeSiblingOrderState,
23
+ } from "../Editor";
24
+ import type { ShapeUpdate } from "../commands";
25
+ import { getEditorStore } from "../internal";
26
+ import { isValidBox } from "../editorComputedHelpers";
27
+ import * as shapeOrder from "./shapeOrder";
28
+ import * as shapeReparenting from "./shapeReparenting";
29
+
30
+ /**
31
+ * Shape-tree structure operations for the editor: grouping/ungrouping, the layer
32
+ * tree, sibling/parent z-ordering, and reparent placement. These are cohesive
33
+ * mutations over the shape hierarchy carved out of the Editor facade so the
34
+ * editor can stay a thinner delegating surface. The Editor exposes thin wrappers
35
+ * that forward here.
36
+ */
37
+ export class ShapeHierarchyManager {
38
+ constructor(private readonly editor: Editor) {}
39
+
40
+ private get store() {
41
+ return getEditorStore(this.editor);
42
+ }
43
+
44
+ getSelectionParentOrderState(): ShapeSelectionSiblingOrderState | null {
45
+ return shapeOrder.getSelectionParentOrderState(this.editor);
46
+ }
47
+
48
+ getSelectionGroupState(): ShapeGroupState | null {
49
+ const editor = this.editor;
50
+ const selectedShapes = editor.getSelectedShapes();
51
+ if (selectedShapes.length < 2) {
52
+ return null;
53
+ }
54
+ if (
55
+ selectedShapes.some(
56
+ (shape) => shape.isLocked || editor.hasLockedDescendant(shape.id),
57
+ )
58
+ ) {
59
+ return null;
60
+ }
61
+
62
+ const parentId = selectedShapes[0]?.parentId;
63
+ if (!parentId) {
64
+ return null;
65
+ }
66
+ if (selectedShapes.some((shape) => shape.parentId !== parentId)) {
67
+ return null;
68
+ }
69
+
70
+ const siblings = editor.getChildShapes(parentId);
71
+ const selectedIds = new Set(selectedShapes.map((shape) => shape.id));
72
+ const sortedShapeIds = siblings
73
+ .filter((shape) => selectedIds.has(shape.id))
74
+ .map((shape) => shape.id);
75
+ if (sortedShapeIds.length !== selectedShapes.length) {
76
+ return null;
77
+ }
78
+
79
+ const bounds = editor.getSelectionPageBounds();
80
+ if (!bounds || !isValidBox(bounds)) {
81
+ return null;
82
+ }
83
+
84
+ return {
85
+ parentId,
86
+ shapeIds: sortedShapeIds,
87
+ bounds,
88
+ };
89
+ }
90
+
91
+ canGroupSelection(): boolean {
92
+ return this.getSelectionGroupState() !== null;
93
+ }
94
+
95
+ private getGroupStateForShapeIds(
96
+ ids: readonly ShapeId[],
97
+ ): ShapeGroupState | null {
98
+ const editor = this.editor;
99
+ const uniqueIds = Array.from(new Set(ids));
100
+ const shapes = uniqueIds
101
+ .map((id) => editor.getShape(id))
102
+ .filter((shape): shape is ShapeRecord => !!shape);
103
+ if (shapes.length < 2 || shapes.length !== uniqueIds.length) {
104
+ return null;
105
+ }
106
+ if (
107
+ shapes.some(
108
+ (shape) =>
109
+ shape.isLocked ||
110
+ editor.hasLockedDescendant(shape.id) ||
111
+ editor.isShapeHidden(shape.id),
112
+ )
113
+ ) {
114
+ return null;
115
+ }
116
+
117
+ const parentId = shapes[0]?.parentId;
118
+ if (!parentId) {
119
+ return null;
120
+ }
121
+ if (shapes.some((shape) => shape.parentId !== parentId)) {
122
+ return null;
123
+ }
124
+
125
+ const siblings = editor.getChildShapes(parentId);
126
+ const groupedIds = new Set(uniqueIds);
127
+ const sortedShapeIds = siblings
128
+ .filter((shape) => groupedIds.has(shape.id))
129
+ .map((shape) => shape.id);
130
+ if (sortedShapeIds.length !== uniqueIds.length) {
131
+ return null;
132
+ }
133
+
134
+ const boundsPoints = sortedShapeIds
135
+ .map((id) => editor.getShapePageBounds(id))
136
+ .filter((box): box is Box => !!box)
137
+ .flatMap((box) => [
138
+ new Vec(box.minX, box.minY),
139
+ new Vec(box.maxX, box.minY),
140
+ new Vec(box.maxX, box.maxY),
141
+ new Vec(box.minX, box.maxY),
142
+ ]);
143
+ const bounds =
144
+ boundsPoints.length > 0 ? Box.FromPoints(boundsPoints) : null;
145
+ if (!bounds || !isValidBox(bounds)) {
146
+ return null;
147
+ }
148
+
149
+ return {
150
+ parentId,
151
+ shapeIds: sortedShapeIds,
152
+ bounds,
153
+ };
154
+ }
155
+
156
+ private groupShapesFromState(
157
+ state: ShapeGroupState,
158
+ source: ChangeSource,
159
+ ): ShapeRecord | null {
160
+ const editor = this.editor;
161
+ const siblings = editor.getChildShapes(state.parentId);
162
+ const groupedIds = new Set(state.shapeIds);
163
+ const firstGroupedIndex = siblings.findIndex((shape) =>
164
+ groupedIds.has(shape.id),
165
+ );
166
+ if (firstGroupedIndex < 0) {
167
+ return null;
168
+ }
169
+
170
+ editor.cancelInteractionForCommand();
171
+ const createdGroups: ShapeRecord[] = [];
172
+ this.store.atomic(() => {
173
+ const [createdGroup] = editor.commands.createShapes(
174
+ [
175
+ {
176
+ type: "group",
177
+ x: state.bounds.x,
178
+ y: state.bounds.y,
179
+ parentId: state.parentId,
180
+ index: editor.createShapeIndex(firstGroupedIndex + 1),
181
+ props: {
182
+ w: state.bounds.w,
183
+ h: state.bounds.h,
184
+ },
185
+ },
186
+ ],
187
+ { source },
188
+ );
189
+ if (!createdGroup) {
190
+ return;
191
+ }
192
+ createdGroups.push(createdGroup);
193
+
194
+ const nextParentSiblings: ShapeRecord[] = [];
195
+ let didInsertGroup = false;
196
+ for (const sibling of siblings) {
197
+ if (groupedIds.has(sibling.id)) {
198
+ if (!didInsertGroup) {
199
+ nextParentSiblings.push(createdGroup);
200
+ didInsertGroup = true;
201
+ }
202
+ continue;
203
+ }
204
+ nextParentSiblings.push(sibling);
205
+ }
206
+
207
+ const selectedChildren = state.shapeIds
208
+ .map((id) => editor.getShape(id))
209
+ .filter((shape): shape is ShapeRecord => !!shape);
210
+ editor.commands.updateShapes(
211
+ [
212
+ ...nextParentSiblings.map((shape, index) => ({
213
+ id: shape.id,
214
+ index: editor.createShapeIndex(index + 1),
215
+ })),
216
+ ...selectedChildren.map((shape, index) => ({
217
+ id: shape.id,
218
+ parentId: createdGroup.id,
219
+ index: editor.createShapeIndex(index + 1),
220
+ })),
221
+ ],
222
+ { source },
223
+ );
224
+ }, source);
225
+
226
+ return createdGroups[0] ?? null;
227
+ }
228
+
229
+ getLayerTree(
230
+ pageId: PageId = this.editor.getCurrentPageId(),
231
+ ): LayerTreeNode[] {
232
+ const editor = this.editor;
233
+ const nodes: LayerTreeNode[] = [];
234
+
235
+ const visit = (parentId: ShapeParentId, depth: number) => {
236
+ const children = editor.getChildShapes(parentId);
237
+ children.forEach((shape, index) => {
238
+ const childIds = editor.getChildShapeIds(shape.id);
239
+ nodes.push({
240
+ id: shape.id,
241
+ parentId: shape.parentId,
242
+ pageId,
243
+ type: shape.type,
244
+ childIds,
245
+ index,
246
+ depth,
247
+ isHidden: shape.isHidden,
248
+ isLocked: shape.isLocked,
249
+ isEffectivelyHidden: editor.isShapeHidden(shape.id),
250
+ isEffectivelyLocked: editor.isShapeOrAncestorLocked(shape.id),
251
+ });
252
+ visit(shape.id, depth + 1);
253
+ });
254
+ };
255
+
256
+ visit(pageId, 0);
257
+ return nodes;
258
+ }
259
+
260
+ getLayerNode(id: ShapeId): LayerTreeNode | null {
261
+ const editor = this.editor;
262
+ const shape = editor.getShape(id);
263
+ const pageId = shape ? editor.getShapePageId(shape.id) : null;
264
+ if (!shape || !pageId) {
265
+ return null;
266
+ }
267
+
268
+ const siblings = editor.getChildShapeIds(shape.parentId);
269
+ return {
270
+ id: shape.id,
271
+ parentId: shape.parentId,
272
+ pageId,
273
+ type: shape.type,
274
+ childIds: editor.getChildShapeIds(shape.id),
275
+ index: siblings.indexOf(shape.id),
276
+ depth: this.getLayerDepth(shape.id),
277
+ isHidden: shape.isHidden,
278
+ isLocked: shape.isLocked,
279
+ isEffectivelyHidden: editor.isShapeHidden(shape.id),
280
+ isEffectivelyLocked: editor.isShapeOrAncestorLocked(shape.id),
281
+ };
282
+ }
283
+
284
+ private getLayerDepth(id: ShapeId): number {
285
+ const editor = this.editor;
286
+ let depth = 0;
287
+ let shape = editor.getShape(id);
288
+ const seen = new Set<ShapeId>([id]);
289
+ while (shape?.parentId.startsWith("shape:")) {
290
+ const parentId = shape.parentId as ShapeId;
291
+ if (seen.has(parentId)) {
292
+ break;
293
+ }
294
+ seen.add(parentId);
295
+ shape = editor.getShape(parentId);
296
+ depth += 1;
297
+ }
298
+ return depth;
299
+ }
300
+
301
+ canGroupShapes(ids: readonly ShapeId[]): boolean {
302
+ return this.getGroupStateForShapeIds(ids) !== null;
303
+ }
304
+
305
+ groupShapes(
306
+ ids: readonly ShapeId[],
307
+ source: ChangeSource = "user",
308
+ ): ShapeRecord | null {
309
+ const state = this.getGroupStateForShapeIds(ids);
310
+ if (!state) {
311
+ return null;
312
+ }
313
+ return this.groupShapesFromState(state, source);
314
+ }
315
+
316
+ groupSelection(source: ChangeSource = "user"): ShapeRecord | null {
317
+ const editor = this.editor;
318
+ const group = this.groupShapes(editor.getSelectedShapeIds(), source);
319
+ if (group) {
320
+ editor.commands.setSelectedShapeIds([group.id], { source });
321
+ }
322
+ return group;
323
+ }
324
+
325
+ getUngroupableSelectedShapeIds(): ShapeId[] {
326
+ return this.getUngroupableShapeIds(this.editor.getSelectedShapeIds());
327
+ }
328
+
329
+ getUngroupableShapeIds(ids: readonly ShapeId[]): ShapeId[] {
330
+ const editor = this.editor;
331
+ return ids
332
+ .map((id) => editor.getShape(id))
333
+ .filter((shape): shape is ShapeRecord => !!shape)
334
+ .filter((shape) =>
335
+ editor.getShapeUtil(shape).shouldTransformDescendants(shape),
336
+ )
337
+ .filter(
338
+ (shape) =>
339
+ !shape.isLocked &&
340
+ !editor.hasLockedDescendant(shape.id) &&
341
+ editor.getChildShapeIds(shape.id).length > 0,
342
+ )
343
+ .map((shape) => shape.id);
344
+ }
345
+
346
+ canUngroupSelection(): boolean {
347
+ return this.getUngroupableSelectedShapeIds().length > 0;
348
+ }
349
+
350
+ canUngroupShapes(ids: readonly ShapeId[]): boolean {
351
+ return this.getUngroupableShapeIds(ids).length > 0;
352
+ }
353
+
354
+ ungroupShapes(
355
+ ids: readonly ShapeId[],
356
+ source: ChangeSource = "user",
357
+ ): ShapeId[] {
358
+ const editor = this.editor;
359
+ const selectedShapes = ids
360
+ .map((id) => editor.getShape(id))
361
+ .filter((shape): shape is ShapeRecord => !!shape);
362
+ const selectedGroupIds = new Set(this.getUngroupableShapeIds(ids));
363
+ if (selectedGroupIds.size === 0) {
364
+ return [];
365
+ }
366
+
367
+ editor.cancelInteractionForCommand();
368
+ const nextSelectedIds: ShapeId[] = [];
369
+ const nextSelectedIdSet = new Set<ShapeId>();
370
+
371
+ this.store.atomic(() => {
372
+ const updates: ShapeUpdate[] = [];
373
+ const groupsToDelete: ShapeId[] = [];
374
+
375
+ for (const selectedShape of selectedShapes) {
376
+ if (!selectedGroupIds.has(selectedShape.id)) {
377
+ if (!nextSelectedIdSet.has(selectedShape.id)) {
378
+ nextSelectedIdSet.add(selectedShape.id);
379
+ nextSelectedIds.push(selectedShape.id);
380
+ }
381
+ continue;
382
+ }
383
+
384
+ const group = editor.getShape(selectedShape.id);
385
+ if (!group) {
386
+ continue;
387
+ }
388
+
389
+ const children = editor.getChildShapes(group.id);
390
+ const parentSiblings = editor.getChildShapes(group.parentId);
391
+ const childIds = new Set(children.map((child) => child.id));
392
+ const nextParentSiblings: ShapeRecord[] = [];
393
+ let didInsertChildren = false;
394
+ for (const sibling of parentSiblings) {
395
+ if (sibling.id === group.id) {
396
+ nextParentSiblings.push(...children);
397
+ didInsertChildren = true;
398
+ continue;
399
+ }
400
+ if (childIds.has(sibling.id)) {
401
+ continue;
402
+ }
403
+ nextParentSiblings.push(sibling);
404
+ }
405
+ if (!didInsertChildren) {
406
+ nextParentSiblings.push(...children);
407
+ }
408
+
409
+ updates.push(
410
+ ...nextParentSiblings.map((shape, index) => ({
411
+ id: shape.id,
412
+ parentId: childIds.has(shape.id) ? group.parentId : shape.parentId,
413
+ index: editor.createShapeIndex(index + 1),
414
+ })),
415
+ );
416
+ for (const child of children) {
417
+ if (!nextSelectedIdSet.has(child.id)) {
418
+ nextSelectedIdSet.add(child.id);
419
+ nextSelectedIds.push(child.id);
420
+ }
421
+ }
422
+ groupsToDelete.push(group.id);
423
+ }
424
+
425
+ if (updates.length > 0) {
426
+ editor.commands.updateShapes(updates, { source });
427
+ }
428
+ if (groupsToDelete.length > 0) {
429
+ editor.bindings.cleanupBindingsForDeletedShapes(groupsToDelete, source);
430
+ this.store.remove(groupsToDelete, source);
431
+ editor.removeShapeIdsFromSelection(groupsToDelete, source);
432
+ editor.removeShapeIdsFromEditingSession(groupsToDelete, source);
433
+ }
434
+ }, source);
435
+
436
+ if (nextSelectedIds.length > 0) {
437
+ editor.commands.setSelectedShapeIds(nextSelectedIds, { source });
438
+ }
439
+ return nextSelectedIds;
440
+ }
441
+
442
+ ungroupSelection(source: ChangeSource = "user"): ShapeId[] {
443
+ return this.ungroupShapes(this.editor.getSelectedShapeIds(), source);
444
+ }
445
+
446
+ getSelectionSiblingOrderState(): ShapeSiblingOrderState | null {
447
+ return shapeOrder.getSelectionSiblingOrderState(this.editor);
448
+ }
449
+
450
+ canMoveSelectionWithinParent(
451
+ direction: ShapeSiblingOrderDirection,
452
+ ): boolean {
453
+ return shapeOrder.canMoveSelectionWithinParent(this.editor, direction);
454
+ }
455
+
456
+ canMoveSelectionInParentOrder(
457
+ direction: ShapeSiblingOrderDirection,
458
+ ): boolean {
459
+ return shapeOrder.canMoveSelectionInParentOrder(this.editor, direction);
460
+ }
461
+
462
+ canMoveShapesInParentOrder(
463
+ ids: readonly ShapeId[],
464
+ direction: LayerOrderDirection,
465
+ ): boolean {
466
+ return shapeOrder.canMoveShapesInParentOrder(this.editor, ids, direction);
467
+ }
468
+
469
+ moveShapesInParentOrder(
470
+ ids: readonly ShapeId[],
471
+ direction: LayerOrderDirection,
472
+ source: ChangeSource = "user",
473
+ ): boolean {
474
+ return shapeOrder.moveShapesInParentOrder(
475
+ this.editor,
476
+ ids,
477
+ direction,
478
+ source,
479
+ );
480
+ }
481
+
482
+ moveShapesToParentOrderEdge(
483
+ ids: readonly ShapeId[],
484
+ edge: LayerOrderEdge,
485
+ source: ChangeSource = "user",
486
+ ): boolean {
487
+ return shapeOrder.moveShapesToParentOrderEdge(
488
+ this.editor,
489
+ ids,
490
+ edge,
491
+ source,
492
+ );
493
+ }
494
+
495
+ moveSelectionWithinParent(
496
+ direction: ShapeSiblingOrderDirection,
497
+ source: ChangeSource = "user",
498
+ ): boolean {
499
+ return shapeOrder.moveSelectionWithinParent(
500
+ this.editor,
501
+ direction,
502
+ source,
503
+ );
504
+ }
505
+
506
+ moveShapesToParentPlacement(
507
+ input: ReparentShapePlacementInput,
508
+ source: ChangeSource = "user",
509
+ ): boolean {
510
+ return shapeReparenting.moveShapesToParentPlacement(
511
+ this.editor,
512
+ input,
513
+ source,
514
+ );
515
+ }
516
+
517
+ moveShapesToParentPlacementResult(
518
+ input: ReparentShapePlacementInput,
519
+ source: ChangeSource = "user",
520
+ ): ReparentShapePlacementResult {
521
+ return shapeReparenting.moveShapesToParentPlacementResult(
522
+ this.editor,
523
+ input,
524
+ source,
525
+ );
526
+ }
527
+
528
+ moveSelectionInParentOrder(
529
+ direction: ShapeSiblingOrderDirection,
530
+ source: ChangeSource = "user",
531
+ ): boolean {
532
+ return shapeOrder.moveSelectionInParentOrder(
533
+ this.editor,
534
+ direction,
535
+ source,
536
+ );
537
+ }
538
+
539
+ moveSelectionToParentOrderEdge(
540
+ edge: ShapeSiblingOrderEdge,
541
+ source: ChangeSource = "user",
542
+ ): boolean {
543
+ return shapeOrder.moveSelectionToParentOrderEdge(
544
+ this.editor,
545
+ edge,
546
+ source,
547
+ );
548
+ }
549
+
550
+ isShapeOrAncestorLocked(shapeId: ShapeId): boolean {
551
+ const editor = this.editor;
552
+ const seen = new Set<ShapeId>();
553
+ let shape = editor.getShape(shapeId);
554
+
555
+ while (shape) {
556
+ if (shape.isLocked) {
557
+ return true;
558
+ }
559
+ if (!shape.parentId.startsWith("shape:")) {
560
+ return false;
561
+ }
562
+
563
+ const parentId = shape.parentId as ShapeId;
564
+ if (seen.has(parentId)) {
565
+ return false;
566
+ }
567
+ seen.add(parentId);
568
+ shape = editor.getShape(parentId);
569
+ }
570
+
571
+ return false;
572
+ }
573
+
574
+ hasLockedDescendant(shapeId: ShapeId): boolean {
575
+ const editor = this.editor;
576
+ return editor.getDescendantShapeIds(shapeId).some(
577
+ (descendantId) => editor.getShape(descendantId)?.isLocked ?? false,
578
+ );
579
+ }
580
+
581
+ getOutermostSelectableShapeId(shapeId: ShapeId): ShapeId {
582
+ const editor = this.editor;
583
+ let selectableShapeId = shapeId;
584
+ let shape = editor.getShape(shapeId);
585
+ const seen = new Set<ShapeId>([shapeId]);
586
+
587
+ while (shape?.parentId.startsWith("shape:")) {
588
+ const parentId = shape.parentId as ShapeId;
589
+ if (seen.has(parentId)) {
590
+ break;
591
+ }
592
+ seen.add(parentId);
593
+
594
+ const parent = editor.getShape(parentId);
595
+ if (!parent) {
596
+ break;
597
+ }
598
+ if (editor.getShapeUtil(parent).shouldTransformDescendants(parent)) {
599
+ selectableShapeId = parent.id;
600
+ }
601
+ shape = parent;
602
+ }
603
+
604
+ return selectableShapeId;
605
+ }
606
+
607
+ getShapeIdsIncludingDescendants(shapeIds: Iterable<ShapeId>): ShapeId[] {
608
+ const ids: ShapeId[] = [];
609
+ const seen = new Set<ShapeId>();
610
+
611
+ for (const shapeId of shapeIds) {
612
+ if (seen.has(shapeId)) {
613
+ continue;
614
+ }
615
+ seen.add(shapeId);
616
+ ids.push(shapeId);
617
+
618
+ for (const descendantId of this.editor.getDescendantShapeIds(shapeId)) {
619
+ if (seen.has(descendantId)) {
620
+ continue;
621
+ }
622
+ seen.add(descendantId);
623
+ ids.push(descendantId);
624
+ }
625
+ }
626
+
627
+ return ids;
628
+ }
629
+
630
+ getShapeIdsForSelectionTransform(shapeIds: Iterable<ShapeId>): ShapeId[] {
631
+ return this.getShapeIdsForSelectionDescendantTransform(
632
+ shapeIds,
633
+ (shape) => this.editor.getShapeUtil(shape).shouldTransformDescendants(shape),
634
+ );
635
+ }
636
+
637
+ getShapeIdsForSelectionRotation(shapeIds: Iterable<ShapeId>): ShapeId[] {
638
+ return this.getShapeIdsForSelectionDescendantTransform(
639
+ shapeIds,
640
+ (shape) => this.editor.getShapeUtil(shape).shouldRotateDescendants(shape),
641
+ );
642
+ }
643
+
644
+ getSelectionTransformTargetShapes(): ShapeRecord[] {
645
+ return this.getShapeIdsForSelectionTransform(this.editor.getSelectedShapeIds())
646
+ .map((shapeId) => this.editor.getShape(shapeId))
647
+ .filter((shape): shape is ShapeRecord => !!shape);
648
+ }
649
+
650
+ getSelectionRotationTargetShapes(): ShapeRecord[] {
651
+ return this.getShapeIdsForSelectionRotation(this.editor.getSelectedShapeIds())
652
+ .map((shapeId) => this.editor.getShape(shapeId))
653
+ .filter((shape): shape is ShapeRecord => !!shape);
654
+ }
655
+
656
+ getShapeExportTraversalIds(shapeIds: Iterable<ShapeId>): ShapeId[] {
657
+ const editor = this.editor;
658
+ const ids = this.getShapeIdsIncludingDescendants(shapeIds).filter(
659
+ (shapeId) => !editor.isShapeHidden(shapeId),
660
+ );
661
+ const seen = new Set(ids);
662
+
663
+ for (const shapeId of ids) {
664
+ for (const binding of editor.getBindingsForShape(shapeId)) {
665
+ const exportShapeIds = editor.getBindingUtil(binding).getExportShapeIds?.(
666
+ editor,
667
+ binding as never,
668
+ );
669
+ for (const exportShapeId of exportShapeIds ?? []) {
670
+ for (const descendantId of this.getShapeIdsIncludingDescendants([
671
+ exportShapeId,
672
+ ])) {
673
+ if (seen.has(descendantId)) {
674
+ continue;
675
+ }
676
+ seen.add(descendantId);
677
+ ids.push(descendantId);
678
+ }
679
+ }
680
+ }
681
+ }
682
+
683
+ return ids;
684
+ }
685
+
686
+ getShapeIdsInContainerBounds(
687
+ containerId: ShapeId,
688
+ options: ContainerBoundsQueryOptions = {},
689
+ ): ShapeId[] {
690
+ const editor = this.editor;
691
+ const pageId = editor.getShapePageId(containerId);
692
+ const containerBounds = editor.getShapePageBounds(containerId);
693
+ if (!pageId || !containerBounds) {
694
+ return [];
695
+ }
696
+
697
+ return editor.getShapeIdsInBounds(containerBounds, { pageId }).filter(
698
+ (shapeId) => {
699
+ if (shapeId === containerId) {
700
+ return false;
701
+ }
702
+
703
+ const containment = editor.getShapeContainment(containerId, shapeId);
704
+ return (
705
+ containment === "inside" ||
706
+ (options.includeIntersecting && containment === "intersecting")
707
+ );
708
+ },
709
+ );
710
+ }
711
+
712
+ canUseShapeParent(parentId: ShapeParentId, childId?: ShapeId): boolean {
713
+ return shapeReparenting.canUseShapeParent(
714
+ this.editor,
715
+ parentId,
716
+ childId,
717
+ );
718
+ }
719
+
720
+ isShapeOrAncestorInSet(shapeId: ShapeId, ids: ReadonlySet<ShapeId>): boolean {
721
+ let shape = this.editor.getShape(shapeId);
722
+ const seen = new Set<ShapeId>();
723
+ while (shape) {
724
+ if (ids.has(shape.id)) {
725
+ return true;
726
+ }
727
+ if (!shape.parentId.startsWith("shape:")) {
728
+ return false;
729
+ }
730
+ const parentId = shape.parentId as ShapeId;
731
+ if (seen.has(parentId)) {
732
+ return false;
733
+ }
734
+ seen.add(parentId);
735
+ shape = this.editor.getShape(parentId);
736
+ }
737
+ return false;
738
+ }
739
+
740
+ isShapeDescendantOf(shapeId: ShapeId, ancestorId: ShapeId): boolean {
741
+ let shape = this.editor.getShape(shapeId);
742
+ const seen = new Set<ShapeId>();
743
+ while (shape?.parentId.startsWith("shape:")) {
744
+ const parentId = shape.parentId as ShapeId;
745
+ if (parentId === ancestorId) {
746
+ return true;
747
+ }
748
+ if (seen.has(parentId)) {
749
+ return false;
750
+ }
751
+ seen.add(parentId);
752
+ shape = this.editor.getShape(parentId);
753
+ }
754
+ return false;
755
+ }
756
+
757
+ private getShapeIdsForSelectionDescendantTransform(
758
+ shapeIds: Iterable<ShapeId>,
759
+ shouldIncludeDescendants: (shape: ShapeRecord) => boolean,
760
+ ): ShapeId[] {
761
+ const editor = this.editor;
762
+ const ids: ShapeId[] = [];
763
+ const seen = new Set<ShapeId>();
764
+
765
+ for (const shapeId of shapeIds) {
766
+ const shape = editor.getShape(shapeId);
767
+ if (!shape || seen.has(shape.id) || shape.isLocked) {
768
+ continue;
769
+ }
770
+ const includeDescendants = shouldIncludeDescendants(shape);
771
+ if (includeDescendants && this.hasLockedDescendant(shape.id)) {
772
+ continue;
773
+ }
774
+
775
+ seen.add(shape.id);
776
+ ids.push(shape.id);
777
+
778
+ if (!includeDescendants) {
779
+ continue;
780
+ }
781
+
782
+ for (const descendantId of editor.getDescendantShapeIds(shape.id)) {
783
+ if (seen.has(descendantId)) {
784
+ continue;
785
+ }
786
+ const descendant = editor.getShape(descendantId);
787
+ if (!descendant || descendant.isLocked) {
788
+ continue;
789
+ }
790
+ seen.add(descendant.id);
791
+ ids.push(descendant.id);
792
+ }
793
+ }
794
+
795
+ return ids;
796
+ }
797
+ }