@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,527 @@
1
+ import type { BindingUtilRegistry } from "../bindings/BindingUtilRegistry";
2
+ import type { ShapeUtilRegistry } from "../shapes/ShapeUtilRegistry";
3
+ import type { StoreSnapshot } from "@land/store";
4
+ import {
5
+ DEFAULT_CAMERA,
6
+ DEFAULT_PAGE_ID,
7
+ isAssetRecord,
8
+ createDefaultPage,
9
+ createDefaultSession,
10
+ isBindingRecord,
11
+ type AssetId,
12
+ type AssetRecord,
13
+ type Camera,
14
+ type BindingId,
15
+ type BindingRecord,
16
+ type DocumentRecord,
17
+ type PageId,
18
+ type PageRecord,
19
+ type SessionRecord,
20
+ type ShapeId,
21
+ type ShapeParentId,
22
+ type ShapeRecord,
23
+ } from "./records";
24
+
25
+ export type DocumentSnapshot = StoreSnapshot<DocumentRecord, 1>;
26
+ export type SessionSnapshot = StoreSnapshot<SessionRecord, 1>;
27
+
28
+ export class SnapshotParseError extends Error {
29
+ constructor(message: string) {
30
+ super(message);
31
+ this.name = "SnapshotParseError";
32
+ }
33
+ }
34
+
35
+ const CURRENT_DOCUMENT_SNAPSHOT_VERSION = 1 as const;
36
+ const CURRENT_SESSION_SNAPSHOT_VERSION = 1 as const;
37
+
38
+ /**
39
+ * A single forward migration step. `up` transforms the raw records array from
40
+ * `fromVersion` to `fromVersion + 1`. Steps are chained in order, so adding a
41
+ * new schema version means bumping the CURRENT_* constant and appending one
42
+ * migration with `fromVersion` equal to the previous current version.
43
+ */
44
+ export interface SnapshotMigration {
45
+ fromVersion: number;
46
+ up: (records: unknown[]) => unknown[];
47
+ }
48
+
49
+ // No migrations yet: version 1 is the first schema. Append future steps here.
50
+ const DOCUMENT_SNAPSHOT_MIGRATIONS: readonly SnapshotMigration[] = [];
51
+ const SESSION_SNAPSHOT_MIGRATIONS: readonly SnapshotMigration[] = [];
52
+
53
+ /**
54
+ * Apply forward migration steps to bring a records array from `fromVersion` up
55
+ * to `toVersion`. Throws if a step is missing or the snapshot is newer than the
56
+ * supported version (no down-migration).
57
+ */
58
+ export function migrateSnapshotRecords(
59
+ records: unknown[],
60
+ fromVersion: number,
61
+ toVersion: number,
62
+ migrations: readonly SnapshotMigration[],
63
+ kind: "document" | "session" = "document",
64
+ ): unknown[] {
65
+ if (fromVersion > toVersion) {
66
+ throw new SnapshotParseError(
67
+ `Unsupported ${kind} snapshot schema version: ${fromVersion} is newer than ${toVersion}`,
68
+ );
69
+ }
70
+
71
+ let current = records;
72
+ for (let version = fromVersion; version < toVersion; version += 1) {
73
+ const step = migrations.find((migration) => migration.fromVersion === version);
74
+ if (!step) {
75
+ throw new SnapshotParseError(
76
+ `No ${kind} snapshot migration from schema version ${version}`,
77
+ );
78
+ }
79
+ current = step.up(current);
80
+ }
81
+ return current;
82
+ }
83
+
84
+ type UnknownRecord = Record<string, unknown>;
85
+
86
+ function isObject(value: unknown): value is UnknownRecord {
87
+ return typeof value === "object" && value !== null && !Array.isArray(value);
88
+ }
89
+
90
+ function asString(value: unknown, fallback: string): string {
91
+ return typeof value === "string" ? value : fallback;
92
+ }
93
+
94
+ function asFiniteNumber(value: unknown, fallback: number): number {
95
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
96
+ }
97
+
98
+ function asBoolean(value: unknown, fallback: boolean): boolean {
99
+ return typeof value === "boolean" ? value : fallback;
100
+ }
101
+
102
+ function asStringArray(value: unknown): string[] {
103
+ return Array.isArray(value)
104
+ ? value.filter((item): item is string => typeof item === "string")
105
+ : [];
106
+ }
107
+
108
+ function asMeta(value: unknown): Record<string, unknown> {
109
+ return isObject(value) ? value : {};
110
+ }
111
+
112
+ function parseCamera(value: unknown): Camera | null {
113
+ if (!isObject(value)) return null;
114
+
115
+ return {
116
+ x: asFiniteNumber(value.x, DEFAULT_CAMERA.x),
117
+ y: asFiniteNumber(value.y, DEFAULT_CAMERA.y),
118
+ z: Math.min(8, Math.max(0.1, asFiniteNumber(value.z, DEFAULT_CAMERA.z))),
119
+ };
120
+ }
121
+
122
+ function parseCameraMap(value: unknown): SessionRecord["pageCameraById"] {
123
+ if (!isObject(value)) return {};
124
+
125
+ const entries = Object.entries(value)
126
+ .filter(([pageId]) => pageId.startsWith("page:"))
127
+ .map(([pageId, camera]) => [pageId as PageId, parseCamera(camera)])
128
+ .filter((entry): entry is [PageId, Camera] => entry[1] !== null);
129
+
130
+ return Object.fromEntries(entries);
131
+ }
132
+
133
+ function parseSelectedShapeIds(
134
+ value: unknown,
135
+ ): SessionRecord["pageSelectedShapeIdsById"][PageId] {
136
+ return Array.from(
137
+ new Set(
138
+ asStringArray(value).filter((id): id is ShapeRecord["id"] =>
139
+ id.startsWith("shape:"),
140
+ ),
141
+ ),
142
+ );
143
+ }
144
+
145
+ function parseSelectionMap(
146
+ value: unknown,
147
+ ): SessionRecord["pageSelectedShapeIdsById"] {
148
+ if (!isObject(value)) return {};
149
+
150
+ const entries = Object.entries(value)
151
+ .filter(([pageId]) => pageId.startsWith("page:"))
152
+ .map(([pageId, selectedShapeIds]) => [
153
+ pageId as PageId,
154
+ parseSelectedShapeIds(selectedShapeIds),
155
+ ]);
156
+
157
+ return Object.fromEntries(entries);
158
+ }
159
+
160
+ function getSnapshotEnvelope(
161
+ snapshot: unknown,
162
+ kind: "document" | "session",
163
+ ): { schemaVersion: number; records: unknown[] } {
164
+ if (!isObject(snapshot)) {
165
+ throw new SnapshotParseError(`Invalid ${kind} snapshot: expected object`);
166
+ }
167
+
168
+ const rawVersion = snapshot.schemaVersion;
169
+ const schemaVersion = asFiniteNumber(rawVersion, Number.NaN);
170
+ if (!Number.isFinite(schemaVersion) || schemaVersion < 1) {
171
+ throw new SnapshotParseError(
172
+ `Unsupported ${kind} snapshot schema version: ${String(rawVersion)}`,
173
+ );
174
+ }
175
+
176
+ if (!Array.isArray(snapshot.records)) {
177
+ throw new SnapshotParseError(
178
+ `Invalid ${kind} snapshot: records must be an array`,
179
+ );
180
+ }
181
+
182
+ const currentVersion =
183
+ kind === "document"
184
+ ? CURRENT_DOCUMENT_SNAPSHOT_VERSION
185
+ : CURRENT_SESSION_SNAPSHOT_VERSION;
186
+ const migrations =
187
+ kind === "document"
188
+ ? DOCUMENT_SNAPSHOT_MIGRATIONS
189
+ : SESSION_SNAPSHOT_MIGRATIONS;
190
+
191
+ const records = migrateSnapshotRecords(
192
+ snapshot.records,
193
+ schemaVersion,
194
+ currentVersion,
195
+ migrations,
196
+ kind,
197
+ );
198
+
199
+ return { schemaVersion: currentVersion, records };
200
+ }
201
+
202
+ function parsePageRecord(record: UnknownRecord, order: number): PageRecord {
203
+ if (typeof record.id !== "string" || !record.id.startsWith("page:")) {
204
+ throw new SnapshotParseError("Invalid page record id");
205
+ }
206
+
207
+ return {
208
+ id: record.id as PageId,
209
+ typeName: "page",
210
+ scope: "document",
211
+ name: asString(record.name, `Page ${order + 1}`),
212
+ index: asString(record.index, String(order + 1).padStart(4, "0")),
213
+ meta: asMeta(record.meta),
214
+ };
215
+ }
216
+
217
+ function parseShapeRecord(
218
+ record: UnknownRecord,
219
+ shapeUtils: ShapeUtilRegistry,
220
+ order: number,
221
+ ): ShapeRecord {
222
+ if (typeof record.id !== "string" || !record.id.startsWith("shape:")) {
223
+ throw new SnapshotParseError("Invalid shape record id");
224
+ }
225
+
226
+ if (typeof record.type !== "string") {
227
+ throw new SnapshotParseError("Invalid shape record type");
228
+ }
229
+
230
+ if (
231
+ typeof record.parentId !== "string" ||
232
+ (!record.parentId.startsWith("page:") &&
233
+ !record.parentId.startsWith("shape:"))
234
+ ) {
235
+ throw new SnapshotParseError(`Invalid parent id for shape ${record.id}`);
236
+ }
237
+
238
+ const shared = {
239
+ id: record.id as ShapeRecord["id"],
240
+ typeName: "shape" as const,
241
+ scope: "document" as const,
242
+ parentId: record.parentId as ShapeParentId,
243
+ index: asString(record.index, String(order + 1).padStart(6, "0")),
244
+ x: asFiniteNumber(record.x, 0),
245
+ y: asFiniteNumber(record.y, 0),
246
+ rotation: asFiniteNumber(record.rotation, 0),
247
+ scaleX: asFiniteNumber(record.scaleX, 1),
248
+ scaleY: asFiniteNumber(record.scaleY, 1),
249
+ opacity: Math.min(1, Math.max(0, asFiniteNumber(record.opacity, 1))),
250
+ isHidden: asBoolean(record.isHidden, false),
251
+ isLocked: asBoolean(record.isLocked, false),
252
+ meta: asMeta(record.meta),
253
+ };
254
+ return {
255
+ ...shared,
256
+ type: record.type,
257
+ props: shapeUtils.get(record.type).validateProps(record.props),
258
+ } as ShapeRecord;
259
+ }
260
+
261
+ function parseBindingRecord(
262
+ record: UnknownRecord,
263
+ bindingUtils: BindingUtilRegistry,
264
+ ): BindingRecord {
265
+ if (typeof record.id !== "string" || !record.id.startsWith("binding:")) {
266
+ throw new SnapshotParseError("Invalid binding record id");
267
+ }
268
+
269
+ if (typeof record.type !== "string") {
270
+ throw new SnapshotParseError("Invalid binding record type");
271
+ }
272
+
273
+ if (
274
+ typeof record.fromId !== "string" ||
275
+ !record.fromId.startsWith("shape:")
276
+ ) {
277
+ throw new SnapshotParseError(`Invalid from id for binding ${record.id}`);
278
+ }
279
+
280
+ if (typeof record.toId !== "string" || !record.toId.startsWith("shape:")) {
281
+ throw new SnapshotParseError(`Invalid to id for binding ${record.id}`);
282
+ }
283
+
284
+ const bindingType = record.type;
285
+ const util = bindingUtils.get(bindingType);
286
+
287
+ return {
288
+ id: record.id as BindingId,
289
+ typeName: "binding",
290
+ scope: "document",
291
+ type: bindingType,
292
+ fromId: record.fromId as ShapeId,
293
+ toId: record.toId as ShapeId,
294
+ props: util.validateProps(record.props),
295
+ meta: asMeta(record.meta),
296
+ } as BindingRecord;
297
+ }
298
+
299
+ function parseAssetRecord(record: UnknownRecord): AssetRecord {
300
+ if (typeof record.id !== "string" || !record.id.startsWith("asset:")) {
301
+ throw new SnapshotParseError("Invalid asset record id");
302
+ }
303
+
304
+ if (typeof record.type !== "string") {
305
+ throw new SnapshotParseError("Invalid asset record type");
306
+ }
307
+
308
+ return {
309
+ id: record.id as AssetId,
310
+ typeName: "asset",
311
+ scope: "document",
312
+ type: record.type,
313
+ props: isObject(record.props) ? record.props : {},
314
+ meta: asMeta(record.meta),
315
+ } as AssetRecord;
316
+ }
317
+
318
+ function hasValidShapeParent({
319
+ pageIds,
320
+ parentId,
321
+ shapeById,
322
+ shapeId,
323
+ shapeUtils,
324
+ }: {
325
+ pageIds: ReadonlySet<PageId>;
326
+ parentId: ShapeParentId;
327
+ shapeById: ReadonlyMap<ShapeId, ShapeRecord>;
328
+ shapeId: ShapeId;
329
+ shapeUtils: ShapeUtilRegistry;
330
+ }): boolean {
331
+ if (parentId.startsWith("page:")) {
332
+ return pageIds.has(parentId as PageId);
333
+ }
334
+
335
+ if (parentId === shapeId) {
336
+ return false;
337
+ }
338
+
339
+ const parentShape = shapeById.get(parentId as ShapeId);
340
+ if (!parentShape) {
341
+ return false;
342
+ }
343
+
344
+ if (!shapeUtils.get(parentShape.type).canReceiveChildren(parentShape)) {
345
+ return false;
346
+ }
347
+
348
+ const seen = new Set<ShapeId>([shapeId]);
349
+ let currentParentId = parentShape.parentId;
350
+ while (currentParentId.startsWith("shape:")) {
351
+ const ancestorId = currentParentId as ShapeId;
352
+ if (seen.has(ancestorId)) {
353
+ return false;
354
+ }
355
+ seen.add(ancestorId);
356
+
357
+ const ancestor = shapeById.get(ancestorId);
358
+ if (!ancestor) {
359
+ return false;
360
+ }
361
+ if (!shapeUtils.get(ancestor.type).canReceiveChildren(ancestor)) {
362
+ return false;
363
+ }
364
+ currentParentId = ancestor.parentId;
365
+ }
366
+
367
+ return pageIds.has(currentParentId as PageId);
368
+ }
369
+
370
+ function repairDocumentRecords(
371
+ records: DocumentRecord[],
372
+ shapeUtils: ShapeUtilRegistry,
373
+ ): DocumentRecord[] {
374
+ const pages = records.filter(
375
+ (record): record is PageRecord => record.typeName === "page",
376
+ );
377
+ const shapes = records.filter(
378
+ (record): record is ShapeRecord => record.typeName === "shape",
379
+ );
380
+ const bindings = records.filter(isBindingRecord);
381
+ const assets = records.filter(isAssetRecord);
382
+
383
+ const orderedPages =
384
+ pages.length > 0
385
+ ? [...pages].sort((left, right) => left.index.localeCompare(right.index))
386
+ : [createDefaultPage()];
387
+ const repairedPages = orderedPages.map((page, index) => ({
388
+ ...page,
389
+ index: String(index + 1).padStart(4, "0"),
390
+ }));
391
+ const fallbackPageId = repairedPages[0]?.id ?? DEFAULT_PAGE_ID;
392
+ const pageIds = new Set(repairedPages.map((page) => page.id));
393
+ const orderedShapes = [...shapes].sort((left, right) =>
394
+ left.index.localeCompare(right.index),
395
+ );
396
+ const shapeById = new Map(orderedShapes.map((shape) => [shape.id, shape]));
397
+
398
+ const repairedShapes = orderedShapes.map((shape, index) => {
399
+ const parentId = hasValidShapeParent({
400
+ pageIds,
401
+ parentId: shape.parentId,
402
+ shapeById,
403
+ shapeId: shape.id,
404
+ shapeUtils,
405
+ })
406
+ ? shape.parentId
407
+ : fallbackPageId;
408
+ const repairedShape = {
409
+ ...shape,
410
+ parentId,
411
+ index: shape.index || String(index + 1).padStart(6, "0"),
412
+ };
413
+
414
+ return shapeUtils.get(repairedShape.type).repairLoadedShape(repairedShape) ??
415
+ repairedShape;
416
+ });
417
+
418
+ const shapeIds = new Set(repairedShapes.map((shape) => shape.id));
419
+ const repairedBindings = bindings.filter(
420
+ (binding) => shapeIds.has(binding.fromId) && shapeIds.has(binding.toId),
421
+ );
422
+
423
+ return [...repairedPages, ...assets, ...repairedShapes, ...repairedBindings];
424
+ }
425
+
426
+ export function parseDocumentSnapshot(
427
+ snapshot: unknown,
428
+ shapeUtils: ShapeUtilRegistry,
429
+ bindingUtils: BindingUtilRegistry,
430
+ ): DocumentSnapshot {
431
+ const envelope = getSnapshotEnvelope(snapshot, "document");
432
+ const seenIds = new Set<string>();
433
+ const records: DocumentRecord[] = [];
434
+
435
+ envelope.records.forEach((value, index) => {
436
+ if (!isObject(value)) {
437
+ throw new SnapshotParseError("Invalid document record: expected object");
438
+ }
439
+
440
+ if (typeof value.id !== "string") {
441
+ throw new SnapshotParseError("Invalid document record id");
442
+ }
443
+ if (seenIds.has(value.id)) {
444
+ throw new SnapshotParseError(`Duplicate document record id: ${value.id}`);
445
+ }
446
+ seenIds.add(value.id);
447
+
448
+ if (value.typeName === "page") {
449
+ records.push(parsePageRecord(value, index));
450
+ return;
451
+ }
452
+
453
+ if (value.typeName === "shape") {
454
+ records.push(parseShapeRecord(value, shapeUtils, index));
455
+ return;
456
+ }
457
+
458
+ if (value.typeName === "binding") {
459
+ records.push(parseBindingRecord(value, bindingUtils));
460
+ return;
461
+ }
462
+
463
+ if (value.typeName === "asset") {
464
+ records.push(parseAssetRecord(value));
465
+ return;
466
+ }
467
+
468
+ throw new SnapshotParseError(
469
+ `Unsupported document record type: ${String(value.typeName)}`,
470
+ );
471
+ });
472
+
473
+ return {
474
+ schemaVersion: CURRENT_DOCUMENT_SNAPSHOT_VERSION,
475
+ records: repairDocumentRecords(records, shapeUtils),
476
+ };
477
+ }
478
+
479
+ export function parseSessionSnapshot(snapshot: unknown): SessionSnapshot {
480
+ const envelope = getSnapshotEnvelope(snapshot, "session");
481
+ const record = envelope.records[0];
482
+ if (!isObject(record)) {
483
+ return {
484
+ schemaVersion: CURRENT_SESSION_SNAPSHOT_VERSION,
485
+ records: [createDefaultSession()],
486
+ };
487
+ }
488
+
489
+ const fallback = createDefaultSession();
490
+ const currentPageId =
491
+ typeof record.currentPageId === "string" &&
492
+ record.currentPageId.startsWith("page:")
493
+ ? (record.currentPageId as SessionRecord["currentPageId"])
494
+ : fallback.currentPageId;
495
+ const parsedCameraById = parseCameraMap(record.pageCameraById);
496
+ const parsedSelectionById = parseSelectionMap(
497
+ record.pageSelectedShapeIdsById,
498
+ );
499
+ const session: SessionRecord = {
500
+ ...fallback,
501
+ id: fallback.id,
502
+ typeName: "session",
503
+ scope: "session",
504
+ currentPageId,
505
+ activeToolId: asString(record.activeToolId, fallback.activeToolId),
506
+ isToolLocked:
507
+ typeof record.isToolLocked === "boolean"
508
+ ? record.isToolLocked
509
+ : fallback.isToolLocked,
510
+ editingShapeId: null,
511
+ shapeEditingSession: null,
512
+ pageCameraById:
513
+ Object.keys(parsedCameraById).length > 0
514
+ ? parsedCameraById
515
+ : fallback.pageCameraById,
516
+ pageSelectedShapeIdsById:
517
+ Object.keys(parsedSelectionById).length > 0
518
+ ? parsedSelectionById
519
+ : fallback.pageSelectedShapeIdsById,
520
+ meta: asMeta(record.meta),
521
+ };
522
+
523
+ return {
524
+ schemaVersion: CURRENT_SESSION_SNAPSHOT_VERSION,
525
+ records: [session],
526
+ };
527
+ }