@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,262 @@
1
+ import type { ChangeSource } from "@land/store";
2
+ import {
3
+ type BindingId,
4
+ type BindingRecord,
5
+ type ShapeId,
6
+ type ShapeRecord,
7
+ } from "../../schema/records";
8
+ import type { Editor } from "../Editor";
9
+ import { getEditorStore } from "../internal";
10
+ import type { BindingUtilEffect } from "../../bindings/BindingUtil";
11
+
12
+ export interface ShapeChangeEntry {
13
+ before: ShapeRecord;
14
+ after: ShapeRecord;
15
+ }
16
+
17
+ export class BindingManager {
18
+ constructor(private readonly editor: Editor) {}
19
+
20
+ getBindingsFromShape(shapeId: ShapeId): BindingRecord[] {
21
+ return this.editor.queries
22
+ .getBindingIdsFromShape(shapeId)
23
+ .map((id) => this.editor.queries.getBinding(id))
24
+ .filter((binding): binding is BindingRecord => !!binding);
25
+ }
26
+
27
+ getBindingsToShape(shapeId: ShapeId): BindingRecord[] {
28
+ return this.editor.queries
29
+ .getBindingIdsToShape(shapeId)
30
+ .map((id) => this.editor.queries.getBinding(id))
31
+ .filter((binding): binding is BindingRecord => !!binding);
32
+ }
33
+
34
+ getBindingsForShape(shapeId: ShapeId): BindingRecord[] {
35
+ return this.editor.queries
36
+ .getBindingIdsForShape(shapeId)
37
+ .map((id) => this.editor.queries.getBinding(id))
38
+ .filter((binding): binding is BindingRecord => !!binding);
39
+ }
40
+
41
+ getConflictingBindings(binding: BindingRecord): BindingRecord[] {
42
+ const exclusiveKey = this.getExclusiveBindingKey(binding);
43
+ if (!exclusiveKey) {
44
+ return [];
45
+ }
46
+
47
+ return this.getAllBindings().filter(
48
+ (candidate) =>
49
+ candidate.id !== binding.id &&
50
+ candidate.type === binding.type &&
51
+ this.getExclusiveBindingKey(candidate) === exclusiveKey,
52
+ );
53
+ }
54
+
55
+ canBind(binding: BindingRecord): boolean {
56
+ const fromShape = this.editor.getShape(binding.fromId);
57
+ const toShape = this.editor.getShape(binding.toId);
58
+ if (!fromShape || !toShape) {
59
+ return false;
60
+ }
61
+
62
+ return (
63
+ this.editor.getBindingUtil(binding).canBind?.(
64
+ this.editor,
65
+ binding as never,
66
+ {
67
+ fromShape,
68
+ toShape,
69
+ },
70
+ ) ?? true
71
+ );
72
+ }
73
+
74
+ syncCreatedBinding(binding: BindingRecord, source: ChangeSource): void {
75
+ const effect = this.editor
76
+ .getBindingUtil(binding)
77
+ .onBindingCreate?.(this.editor, binding as never);
78
+ this.applyEffect(effect, source, binding.id);
79
+ }
80
+
81
+ syncUpdatedBinding(
82
+ before: BindingRecord,
83
+ after: BindingRecord,
84
+ source: ChangeSource,
85
+ ): void {
86
+ const effect = this.editor
87
+ .getBindingUtil(after)
88
+ .onBindingUpdate?.(this.editor, before as never, after as never);
89
+ this.applyEffect(effect, source, after.id);
90
+ }
91
+
92
+ repairLoadedBindings(source: ChangeSource): void {
93
+ getEditorStore(this.editor).atomic(() => {
94
+ const seenExclusiveBindings = new Set<string>();
95
+
96
+ for (const binding of this.getAllBindings()) {
97
+ const fromShape = this.editor.getShape(binding.fromId);
98
+ const toShape = this.editor.getShape(binding.toId);
99
+ if (!fromShape || !toShape) {
100
+ getEditorStore(this.editor).remove([binding.id], source);
101
+ continue;
102
+ }
103
+
104
+ if (!this.canBind(binding)) {
105
+ getEditorStore(this.editor).remove([binding.id], source);
106
+ continue;
107
+ }
108
+
109
+ const exclusiveKey = this.getExclusiveBindingKey(binding);
110
+ if (exclusiveKey) {
111
+ if (seenExclusiveBindings.has(exclusiveKey)) {
112
+ getEditorStore(this.editor).remove([binding.id], source);
113
+ continue;
114
+ }
115
+ seenExclusiveBindings.add(exclusiveKey);
116
+ }
117
+
118
+ const effect = this.editor
119
+ .getBindingUtil(binding)
120
+ .onBindingLoad?.(this.editor, binding as never);
121
+ this.applyEffect(effect, source, binding.id);
122
+ }
123
+ }, source);
124
+ }
125
+
126
+ handleShapeChanges(
127
+ changes: ShapeChangeEntry[],
128
+ source: ChangeSource,
129
+ options?: { skipBindingIds?: ReadonlySet<BindingId> },
130
+ ): void {
131
+ if (changes.length === 0) {
132
+ return;
133
+ }
134
+
135
+ for (const change of changes) {
136
+ for (const binding of this.getBindingsForShape(change.after.id)) {
137
+ if (options?.skipBindingIds?.has(binding.id)) {
138
+ continue;
139
+ }
140
+ const role = binding.fromId === change.after.id ? "from" : "to";
141
+ const effect = this.editor
142
+ .getBindingUtil(binding)
143
+ .onAfterShapeChange?.(this.editor, binding as never, {
144
+ before: change.before,
145
+ after: change.after,
146
+ role,
147
+ });
148
+ this.applyEffect(effect, source, binding.id);
149
+ }
150
+ }
151
+ }
152
+
153
+ cleanupBindingsForDeletedShapes(ids: ShapeId[], source: ChangeSource): void {
154
+ if (ids.length === 0) {
155
+ return;
156
+ }
157
+
158
+ const deletingIds = new Set(ids);
159
+ const seenBindings = new Set<BindingId>();
160
+
161
+ for (const shapeId of ids) {
162
+ const shape = this.editor.getShape(shapeId);
163
+ if (!shape) {
164
+ continue;
165
+ }
166
+
167
+ for (const binding of this.getBindingsForShape(shapeId)) {
168
+ if (seenBindings.has(binding.id)) {
169
+ continue;
170
+ }
171
+ seenBindings.add(binding.id);
172
+
173
+ const role = binding.fromId === shapeId ? "from" : "to";
174
+ if (role === "to" && deletingIds.has(binding.fromId)) {
175
+ getEditorStore(this.editor).remove([binding.id], source);
176
+ continue;
177
+ }
178
+
179
+ const effect = this.editor
180
+ .getBindingUtil(binding)
181
+ .onBeforeDeleteShape?.(this.editor, binding as never, {
182
+ shape,
183
+ role,
184
+ });
185
+ this.applyEffect(effect, source, binding.id);
186
+ }
187
+ }
188
+ }
189
+
190
+ cleanupBindings(ids: BindingId[], source: ChangeSource): void {
191
+ for (const id of ids) {
192
+ const binding = this.editor.getBinding(id);
193
+ if (!binding) {
194
+ continue;
195
+ }
196
+ const effect = this.editor
197
+ .getBindingUtil(binding)
198
+ .onBeforeDeleteBinding?.(this.editor, binding as never);
199
+ this.applyEffect(effect, source, binding.id);
200
+ }
201
+ }
202
+
203
+ private applyEffect(
204
+ effect: BindingUtilEffect | void,
205
+ source: ChangeSource,
206
+ originBindingId?: BindingId,
207
+ ): void {
208
+ if (!effect) {
209
+ return;
210
+ }
211
+
212
+ if (effect.bindings && effect.bindings.length > 0) {
213
+ getEditorStore(this.editor).put(effect.bindings, source);
214
+ }
215
+
216
+ if (effect.shapes && effect.shapes.length > 0) {
217
+ const changes = effect.shapes.flatMap((after) => {
218
+ const before = this.editor.getShape(after.id);
219
+ return before ? [{ before, after }] : [];
220
+ });
221
+ getEditorStore(this.editor).put(effect.shapes, source);
222
+ this.handleShapeChanges(
223
+ changes,
224
+ source,
225
+ originBindingId
226
+ ? { skipBindingIds: new Set([originBindingId]) }
227
+ : undefined,
228
+ );
229
+ }
230
+
231
+ if (effect.deleteShapeIds && effect.deleteShapeIds.length > 0) {
232
+ const idsWithDescendants = this.editor.getShapeIdsIncludingDescendants(
233
+ effect.deleteShapeIds,
234
+ );
235
+ this.cleanupBindingsForDeletedShapes(idsWithDescendants, source);
236
+ getEditorStore(this.editor).remove(
237
+ Array.from(new Set(idsWithDescendants)),
238
+ source,
239
+ );
240
+ this.editor.removeShapeIdsFromSelection(idsWithDescendants, source);
241
+ this.editor.removeShapeIdsFromEditingSession(idsWithDescendants, source);
242
+ }
243
+
244
+ if (effect.deleteBindingIds && effect.deleteBindingIds.length > 0) {
245
+ getEditorStore(this.editor).remove(
246
+ Array.from(new Set(effect.deleteBindingIds)),
247
+ source,
248
+ );
249
+ }
250
+ }
251
+
252
+ private getAllBindings(): BindingRecord[] {
253
+ return this.editor.queries.getBindings();
254
+ }
255
+
256
+ private getExclusiveBindingKey(binding: BindingRecord): string | null {
257
+ const key = this.editor
258
+ .getBindingUtil(binding)
259
+ .getExclusiveBindingKey?.(binding as never);
260
+ return key ? `${binding.type}:${key}` : null;
261
+ }
262
+ }
@@ -0,0 +1,147 @@
1
+ import { type Atom, type Signal, atom } from "@land/signals";
2
+ import type { ChangeSource } from "@land/store";
3
+ import { Vec } from "../../geometry/Vec";
4
+ import { type Camera, DEFAULT_CAMERA } from "../../schema/records";
5
+ import type { PageId } from "../../schema/records";
6
+ import type { Editor } from "../Editor";
7
+ import { clampCameraZoom } from "../camera";
8
+
9
+ /**
10
+ * Camera and viewport-screen-size state for the editor. Owns the viewport size
11
+ * atom (viewport + camera together define the page<->screen transform) and the
12
+ * camera read/write/transform operations carved out of the Editor facade so the
13
+ * editor can stay a thinner delegating surface. The Editor exposes thin wrappers
14
+ * that forward here and re-publishes {@link viewportScreenSizeSignal}.
15
+ */
16
+ export class CameraManager {
17
+ private readonly _viewportScreenSizeSignal: Atom<{ w: number; h: number }> =
18
+ atom<{ w: number; h: number }>("editor.viewportScreenSize", { w: 0, h: 0 });
19
+ readonly viewportScreenSizeSignal: Signal<{ w: number; h: number }> =
20
+ this._viewportScreenSizeSignal;
21
+
22
+ constructor(private readonly editor: Editor) {}
23
+
24
+ getCurrentCamera(): Camera {
25
+ return this.editor.currentCameraSignal.get();
26
+ }
27
+
28
+ getCamera(pageId: PageId = this.editor.getCurrentPageId()): Camera {
29
+ return this.editor.sessionSignal.get().pageCameraById[pageId] ?? DEFAULT_CAMERA;
30
+ }
31
+
32
+ pageToScreen(point: Vec, pageId: PageId = this.editor.getCurrentPageId()): Vec {
33
+ const camera = this.getCamera(pageId);
34
+ return new (point.constructor as typeof Vec)(
35
+ (point.x - camera.x) * camera.z,
36
+ (point.y - camera.y) * camera.z,
37
+ );
38
+ }
39
+
40
+ screenToPage(point: Vec, pageId: PageId = this.editor.getCurrentPageId()): Vec {
41
+ const camera = this.getCamera(pageId);
42
+ return new (point.constructor as typeof Vec)(
43
+ point.x / camera.z + camera.x,
44
+ point.y / camera.z + camera.y,
45
+ );
46
+ }
47
+
48
+ setCamera(
49
+ partial: Partial<Camera>,
50
+ options?: { pageId?: PageId; source?: ChangeSource },
51
+ ): void {
52
+ const source = options?.source ?? "system";
53
+ const pageId = options?.pageId ?? this.editor.getCurrentPageId();
54
+ const current = this.getCamera(pageId);
55
+ const nextX = typeof partial.x === "number" ? partial.x : current.x;
56
+ const nextY = typeof partial.y === "number" ? partial.y : current.y;
57
+ const nextZInput = typeof partial.z === "number" ? partial.z : current.z;
58
+ const nextCamera: Camera = {
59
+ x: nextX,
60
+ y: nextY,
61
+ z: clampCameraZoom(nextZInput),
62
+ };
63
+
64
+ this.editor.updateSession(
65
+ {
66
+ pageCameraById: {
67
+ ...this.editor.sessionSignal.get().pageCameraById,
68
+ [pageId]: nextCamera,
69
+ },
70
+ },
71
+ source,
72
+ );
73
+ }
74
+
75
+ panCamera(delta: Vec, source: ChangeSource = "system"): void {
76
+ const camera = this.getCurrentCamera();
77
+ this.setCamera(
78
+ {
79
+ x: camera.x + delta.x / camera.z,
80
+ y: camera.y + delta.y / camera.z,
81
+ },
82
+ { source },
83
+ );
84
+ }
85
+
86
+ zoomCameraAtScreenPoint(
87
+ screenPoint: Vec,
88
+ deltaY: number,
89
+ source: ChangeSource = "system",
90
+ ): void {
91
+ const current = this.getCurrentCamera();
92
+ const nextZoom = clampCameraZoom(current.z * Math.exp(-deltaY * 0.0015));
93
+ this.zoomCameraToScreenPoint(screenPoint, nextZoom, source);
94
+ }
95
+
96
+ zoomCameraToScreenPoint(
97
+ screenPoint: Vec,
98
+ zoom: number,
99
+ source: ChangeSource = "system",
100
+ ): void {
101
+ const nextZoom = clampCameraZoom(zoom);
102
+ const pagePoint = this.screenToPage(screenPoint);
103
+
104
+ this.setCamera(
105
+ {
106
+ x: pagePoint.x - screenPoint.x / nextZoom,
107
+ y: pagePoint.y - screenPoint.y / nextZoom,
108
+ z: nextZoom,
109
+ },
110
+ { source },
111
+ );
112
+ }
113
+
114
+ zoomAtViewportCenter(deltaY: number, source: ChangeSource = "system"): void {
115
+ const viewport = this.viewportScreenSizeSignal.get();
116
+ const center = new Vec(viewport.w / 2, viewport.h / 2);
117
+ this.zoomCameraAtScreenPoint(center, deltaY, source);
118
+ }
119
+
120
+ resetZoom(source: ChangeSource = "user"): void {
121
+ const viewport = this.viewportScreenSizeSignal.get();
122
+ const centerScreen = new Vec(viewport.w / 2, viewport.h / 2);
123
+ const centerPage = this.screenToPage(centerScreen);
124
+ this.editor.commands.setCamera(
125
+ {
126
+ camera: {
127
+ x: centerPage.x - centerScreen.x,
128
+ y: centerPage.y - centerScreen.y,
129
+ z: 1,
130
+ },
131
+ },
132
+ { source },
133
+ );
134
+ }
135
+
136
+ setViewportScreenSize(size: { w: number; h: number }): void {
137
+ const nextSize = {
138
+ w: Math.max(0, size.w),
139
+ h: Math.max(0, size.h),
140
+ };
141
+ const current = this._viewportScreenSizeSignal.get();
142
+ if (current.w === nextSize.w && current.h === nextSize.h) {
143
+ return;
144
+ }
145
+ this._viewportScreenSizeSignal.set(nextSize);
146
+ }
147
+ }
@@ -0,0 +1,32 @@
1
+ import type { ShapeId } from "../../schema/records";
2
+ import {
3
+ createEditorClipboardPayloadFromSelection,
4
+ duplicateEditorSelection,
5
+ pasteEditorClipboardPayload,
6
+ type DuplicateSelectionInput,
7
+ type EditorClipboardPayload,
8
+ type PasteClipboardPayloadInput,
9
+ } from "../clipboard";
10
+ import type { Editor } from "../Editor";
11
+
12
+ /**
13
+ * Clipboard operations over editor selections. The Editor exposes thin wrappers
14
+ * that forward here.
15
+ */
16
+ export class ClipboardManager {
17
+ constructor(private readonly editor: Editor) {}
18
+
19
+ createPayloadFromSelection(): EditorClipboardPayload | null {
20
+ return createEditorClipboardPayloadFromSelection(this.editor);
21
+ }
22
+
23
+ pastePayload(input: PasteClipboardPayloadInput): ShapeId[] {
24
+ this.editor.cancelInteractionForCommand();
25
+ return pasteEditorClipboardPayload(this.editor, input);
26
+ }
27
+
28
+ duplicateSelection(input: DuplicateSelectionInput = {}): ShapeId[] {
29
+ this.editor.cancelInteractionForCommand();
30
+ return duplicateEditorSelection(this.editor, input);
31
+ }
32
+ }
@@ -0,0 +1,256 @@
1
+ import type { ChangeSource } from "@land/store";
2
+ import {
3
+ DEFAULT_CAMERA,
4
+ DEFAULT_PAGE_ID,
5
+ DEFAULT_SESSION_ID,
6
+ createDefaultPage,
7
+ createDefaultSession,
8
+ isAssetRecord,
9
+ isBindingRecord,
10
+ isDocumentRecord,
11
+ isPageRecord,
12
+ isSessionRecord,
13
+ isShapeRecord,
14
+ type EditorRecord,
15
+ type PageId,
16
+ type SessionRecord,
17
+ type ShapeId,
18
+ } from "../../schema/records";
19
+ import {
20
+ SnapshotParseError,
21
+ parseDocumentSnapshot,
22
+ parseSessionSnapshot,
23
+ type DocumentSnapshot,
24
+ type SessionSnapshot,
25
+ } from "../../schema/snapshots";
26
+ import type { Editor } from "../Editor";
27
+ import { getShapePageIdFromParentChain } from "../editorComputedHelpers";
28
+ import { getEditorStore } from "../internal";
29
+
30
+ /**
31
+ * Document/session snapshot serialization, restore, and post-restore repair.
32
+ * The Editor exposes thin wrappers that forward here.
33
+ */
34
+ export class DocumentSnapshotManager {
35
+ constructor(private readonly editor: Editor) {}
36
+
37
+ buildInitialRecords(
38
+ documentSnapshot: unknown,
39
+ sessionSnapshot: unknown,
40
+ ): EditorRecord[] {
41
+ const parsedDocumentSnapshot =
42
+ this.parseInitialDocumentSnapshot(documentSnapshot);
43
+ const parsedSessionSnapshot =
44
+ this.parseInitialSessionSnapshot(sessionSnapshot);
45
+
46
+ const documentRecords = parsedDocumentSnapshot.records.length
47
+ ? parsedDocumentSnapshot.records
48
+ : [createDefaultPage()];
49
+ const session = parsedSessionSnapshot.records[0] ?? createDefaultSession();
50
+
51
+ return [...documentRecords, session];
52
+ }
53
+
54
+ getDocumentSnapshot(): DocumentSnapshot {
55
+ const records = this.editor.store.getAll();
56
+ const pages = this.editor.getPages();
57
+ const assets = records
58
+ .filter(isAssetRecord)
59
+ .sort((left, right) => left.id.localeCompare(right.id));
60
+ const shapes = records.filter(isShapeRecord).sort((left, right) => {
61
+ if (left.parentId === right.parentId) {
62
+ return left.index.localeCompare(right.index);
63
+ }
64
+ return left.parentId.localeCompare(right.parentId);
65
+ });
66
+ const bindings = records
67
+ .filter(isBindingRecord)
68
+ .sort((left, right) => left.id.localeCompare(right.id));
69
+
70
+ return {
71
+ schemaVersion: 1,
72
+ records: [...pages, ...assets, ...shapes, ...bindings],
73
+ };
74
+ }
75
+
76
+ getSessionSnapshot(): SessionSnapshot {
77
+ return {
78
+ schemaVersion: 1,
79
+ records: [this.editor.sessionSignal.get()],
80
+ };
81
+ }
82
+
83
+ loadDocumentSnapshot(
84
+ snapshot: unknown,
85
+ options?: { source?: ChangeSource },
86
+ ): void {
87
+ const source = options?.source ?? "system";
88
+ const parsed = parseDocumentSnapshot(
89
+ snapshot,
90
+ this.editor.shapeUtils,
91
+ this.editor.bindingUtils,
92
+ );
93
+ this.editor.cancelInteractionAndClearTransients("restore");
94
+ this.editor.editing.end({ runEditEnd: false, source });
95
+ const session = this.editor.sessionSignal.get();
96
+ getEditorStore(this.editor).replaceAll([...parsed.records, session], source);
97
+ this.syncAfterDocumentSnapshotLoad(source);
98
+ }
99
+
100
+ loadSessionSnapshot(
101
+ snapshot: unknown,
102
+ options?: { source?: ChangeSource },
103
+ ): void {
104
+ const source = options?.source ?? "system";
105
+ const session =
106
+ parseSessionSnapshot(snapshot).records[0] ?? createDefaultSession();
107
+ const documentRecords = getEditorStore(this.editor)
108
+ .getAll()
109
+ .filter(isDocumentRecord);
110
+ this.editor.cancelInteractionAndClearTransients("restore");
111
+ this.editor.editing.end({ runEditEnd: false, source });
112
+ getEditorStore(this.editor).replaceAll(
113
+ [...documentRecords, session],
114
+ source,
115
+ );
116
+ this.ensureSessionIntegrity();
117
+ this.editor.syncToolStateFromSession(source);
118
+ }
119
+
120
+ syncAfterDocumentRestore(): void {
121
+ this.ensureSessionIntegrity();
122
+ this.editor.syncShapeCounter();
123
+ this.editor.syncPageCounter();
124
+ this.editor.syncBindingCounter();
125
+ this.editor.syncAssetCounter();
126
+ }
127
+
128
+ ensureSessionIntegrity(): void {
129
+ const store = getEditorStore(this.editor);
130
+ const records = store.getAll();
131
+ const pages = records
132
+ .filter(isPageRecord)
133
+ .sort((left, right) => left.index.localeCompare(right.index));
134
+ const fallbackPageId = pages[0]?.id ?? DEFAULT_PAGE_ID;
135
+ const pageIds = new Set(pages.map((record) => record.id));
136
+ const shapes = records.filter(isShapeRecord);
137
+ const shapeIds = new Set(shapes.map((record) => record.id));
138
+ const shapeById = new Map(shapes.map((record) => [record.id, record]));
139
+ const shapePageById = new Map<ShapeId, PageId>();
140
+ for (const shape of shapes) {
141
+ shapePageById.set(
142
+ shape.id,
143
+ getShapePageIdFromParentChain(shape, shapeById, pageIds) ??
144
+ fallbackPageId,
145
+ );
146
+ }
147
+ const currentSession = records.find(
148
+ (record) => record.id === DEFAULT_SESSION_ID,
149
+ );
150
+ const session = isSessionRecord(currentSession)
151
+ ? currentSession
152
+ : createDefaultSession(fallbackPageId);
153
+
154
+ const nextSession: SessionRecord = {
155
+ ...session,
156
+ currentPageId: pageIds.has(session.currentPageId)
157
+ ? session.currentPageId
158
+ : fallbackPageId,
159
+ pageCameraById: this.repairCameraByPage(session.pageCameraById, pageIds),
160
+ pageSelectedShapeIdsById: this.repairSelectionByPage(
161
+ session.pageSelectedShapeIdsById,
162
+ pageIds,
163
+ shapeIds,
164
+ shapePageById,
165
+ ),
166
+ };
167
+
168
+ store.put([nextSession], "system");
169
+ }
170
+
171
+ private syncAfterDocumentSnapshotLoad(source: ChangeSource): void {
172
+ this.syncAfterDocumentRestore();
173
+ this.editor.history.clear();
174
+ this.editor.bindings.repairLoadedBindings(source);
175
+ }
176
+
177
+ private repairCameraByPage(
178
+ cameraByPage: SessionRecord["pageCameraById"],
179
+ pageIds: Set<PageId>,
180
+ ): SessionRecord["pageCameraById"] {
181
+ const nextCameraByPage: SessionRecord["pageCameraById"] = {};
182
+
183
+ for (const pageId of pageIds) {
184
+ const camera = cameraByPage[pageId] ?? DEFAULT_CAMERA;
185
+ nextCameraByPage[pageId] = {
186
+ x: Number.isFinite(camera.x) ? camera.x : DEFAULT_CAMERA.x,
187
+ y: Number.isFinite(camera.y) ? camera.y : DEFAULT_CAMERA.y,
188
+ z: Math.min(
189
+ 8,
190
+ Math.max(
191
+ 0.1,
192
+ Number.isFinite(camera.z) ? camera.z : DEFAULT_CAMERA.z,
193
+ ),
194
+ ),
195
+ };
196
+ }
197
+
198
+ return nextCameraByPage;
199
+ }
200
+
201
+ private repairSelectionByPage(
202
+ selectionByPage: SessionRecord["pageSelectedShapeIdsById"],
203
+ pageIds: Set<PageId>,
204
+ shapeIds: Set<ShapeId>,
205
+ shapePageById: Map<ShapeId, PageId>,
206
+ ): SessionRecord["pageSelectedShapeIdsById"] {
207
+ const nextSelectionByPage: SessionRecord["pageSelectedShapeIdsById"] = {};
208
+
209
+ for (const pageId of pageIds) {
210
+ nextSelectionByPage[pageId] = Array.from(
211
+ new Set(
212
+ (selectionByPage[pageId] ?? []).filter(
213
+ (shapeId) =>
214
+ shapeIds.has(shapeId) && shapePageById.get(shapeId) === pageId,
215
+ ),
216
+ ),
217
+ );
218
+ }
219
+
220
+ return nextSelectionByPage;
221
+ }
222
+
223
+ private parseInitialDocumentSnapshot(snapshot: unknown): DocumentSnapshot {
224
+ if (!snapshot) {
225
+ return { schemaVersion: 1, records: [createDefaultPage()] };
226
+ }
227
+
228
+ try {
229
+ return parseDocumentSnapshot(
230
+ snapshot,
231
+ this.editor.shapeUtils,
232
+ this.editor.bindingUtils,
233
+ );
234
+ } catch (error) {
235
+ if (!(error instanceof SnapshotParseError)) {
236
+ throw error;
237
+ }
238
+ return { schemaVersion: 1, records: [createDefaultPage()] };
239
+ }
240
+ }
241
+
242
+ private parseInitialSessionSnapshot(snapshot: unknown): SessionSnapshot {
243
+ if (!snapshot) {
244
+ return { schemaVersion: 1, records: [createDefaultSession()] };
245
+ }
246
+
247
+ try {
248
+ return parseSessionSnapshot(snapshot);
249
+ } catch (error) {
250
+ if (!(error instanceof SnapshotParseError)) {
251
+ throw error;
252
+ }
253
+ return { schemaVersion: 1, records: [createDefaultSession()] };
254
+ }
255
+ }
256
+ }