@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,130 @@
1
+ import { Child, Signal } from "./types";
2
+
3
+ /** Global epoch counter — increments on every atom change */
4
+ let globalEpoch = 0;
5
+
6
+ export function getGlobalEpoch(): number {
7
+ return globalEpoch;
8
+ }
9
+
10
+ export function advanceGlobalEpoch(): number {
11
+ return ++globalEpoch;
12
+ }
13
+
14
+ // --- Transaction ---
15
+
16
+ interface Transaction {
17
+ initialAtomValues: Map<_Atom<any>, any>;
18
+ }
19
+
20
+ let currentTransaction: Transaction | null = null;
21
+
22
+ export function getCurrentTransaction(): Transaction | null {
23
+ return currentTransaction;
24
+ }
25
+
26
+ export function transact(fn: () => void): void {
27
+ if (currentTransaction) {
28
+ // Nested transaction — just run inside the existing one
29
+ fn();
30
+ return;
31
+ }
32
+
33
+ const txn: Transaction = { initialAtomValues: new Map() };
34
+ currentTransaction = txn;
35
+ try {
36
+ fn();
37
+ // Commit: flush all effects
38
+ flushChanges(txn.initialAtomValues.keys());
39
+ } catch (e) {
40
+ // Abort: rollback
41
+ for (const [a, value] of txn.initialAtomValues) {
42
+ a.__unsafe__setWithoutNotify(value);
43
+ }
44
+ throw e;
45
+ } finally {
46
+ currentTransaction = null;
47
+ }
48
+ }
49
+
50
+ export function noteInitialValue(
51
+ atom: _Atom<any>,
52
+ previousValue: any,
53
+ ): void {
54
+ if (
55
+ currentTransaction &&
56
+ !currentTransaction.initialAtomValues.has(atom)
57
+ ) {
58
+ currentTransaction.initialAtomValues.set(atom, previousValue);
59
+ }
60
+ }
61
+
62
+ // --- Dependency capture ---
63
+
64
+ interface CaptureFrame {
65
+ child: Child;
66
+ parentSet: Set<Signal<any>>;
67
+ below: CaptureFrame | null;
68
+ }
69
+
70
+ let captureStack: CaptureFrame | null = null;
71
+
72
+ export function startCapture(child: Child): void {
73
+ captureStack = { child, parentSet: new Set(), below: captureStack };
74
+ }
75
+
76
+ export function stopCapture(): Set<Signal<any>> {
77
+ if (!captureStack) {
78
+ throw new Error(
79
+ "Cannot stop dependency capture because no capture is active",
80
+ );
81
+ }
82
+ const frame = captureStack;
83
+ captureStack = frame.below;
84
+ return frame.parentSet;
85
+ }
86
+
87
+ export function maybeCaptureParent(parent: Signal<any>): void {
88
+ if (!captureStack) return;
89
+ captureStack.parentSet.add(parent);
90
+ parent.children.add(captureStack.child);
91
+ }
92
+
93
+ // --- Change propagation ---
94
+
95
+ function flushChanges(changedAtoms: Iterable<_Atom<any>>): void {
96
+ const effects = new Set<Child>();
97
+ const visited = new Set<Child>();
98
+
99
+ function traverse(children: Set<Child>) {
100
+ for (const child of children) {
101
+ if (visited.has(child)) continue;
102
+ visited.add(child);
103
+ if (child.type === "effect") {
104
+ effects.add(child);
105
+ } else if (child.type === "computed") {
106
+ const source = child.source;
107
+ if (source) traverse(source.children);
108
+ }
109
+ }
110
+ }
111
+
112
+ for (const atom of changedAtoms) {
113
+ traverse(atom.children);
114
+ }
115
+
116
+ for (const effect of effects) {
117
+ effect.notify();
118
+ }
119
+ }
120
+
121
+ export function atomDidChange(atom: _Atom<any>): void {
122
+ if (!currentTransaction) {
123
+ flushChanges([atom]);
124
+ }
125
+ }
126
+
127
+ // Forward reference types — implemented in their own files
128
+ interface _Atom<T> extends Signal<T> {
129
+ __unsafe__setWithoutNotify(value: T): void;
130
+ }
@@ -0,0 +1,117 @@
1
+ import { Child, Signal } from "./types";
2
+ import { getGlobalEpoch, startCapture, stopCapture } from "./core";
3
+
4
+ export interface EffectSchedulerOptions {
5
+ scheduleEffect?: (execute: () => void) => void;
6
+ }
7
+
8
+ export class EffectScheduler {
9
+ type = "effect" as const;
10
+ private parents: Signal<any>[] = [];
11
+ private parentEpochs: number[] = [];
12
+ private lastReactedEpoch = -1;
13
+ private isActive = false;
14
+ private scheduleEffect?: (execute: () => void) => void;
15
+
16
+ constructor(
17
+ public readonly name: string,
18
+ private fn: () => void,
19
+ opts?: EffectSchedulerOptions,
20
+ ) {
21
+ this.scheduleEffect = opts?.scheduleEffect;
22
+ }
23
+
24
+ private selfAsChild: Child = {
25
+ type: "effect",
26
+ notify: () => this.maybeSchedule(),
27
+ };
28
+
29
+ attach(): void {
30
+ this.isActive = true;
31
+ this.execute();
32
+ }
33
+
34
+ detach(): void {
35
+ this.isActive = false;
36
+ // Remove from all parents
37
+ for (const parent of this.parents) {
38
+ parent.children.delete(this.selfAsChild);
39
+ }
40
+ this.parents = [];
41
+ this.parentEpochs = [];
42
+ }
43
+
44
+ maybeSchedule(): void {
45
+ if (!this.isActive) return;
46
+ if (this.lastReactedEpoch === getGlobalEpoch()) return;
47
+
48
+ if (!this.haveParentsChanged()) {
49
+ this.lastReactedEpoch = getGlobalEpoch();
50
+ return;
51
+ }
52
+
53
+ if (this.scheduleEffect) {
54
+ this.scheduleEffect(() => this.execute());
55
+ } else {
56
+ this.execute();
57
+ }
58
+ }
59
+
60
+ execute(): void {
61
+ if (!this.isActive) return;
62
+
63
+ // Remove from old parents
64
+ for (const parent of this.parents) {
65
+ parent.children.delete(this.selfAsChild);
66
+ }
67
+
68
+ startCapture(this.selfAsChild);
69
+ try {
70
+ this.fn();
71
+ } finally {
72
+ const newParentSet = stopCapture();
73
+ this.parents = [...newParentSet];
74
+ this.parentEpochs = this.parents.map((p) => p.lastChangedEpoch);
75
+ for (const parent of this.parents) {
76
+ parent.children.add(this.selfAsChild);
77
+ }
78
+ this.lastReactedEpoch = getGlobalEpoch();
79
+ }
80
+ }
81
+
82
+ private haveParentsChanged(): boolean {
83
+ for (let i = 0; i < this.parents.length; i++) {
84
+ const parent = this.parents[i];
85
+ parent.__unsafe__getWithoutCapture();
86
+ if (parent.lastChangedEpoch !== this.parentEpochs[i]) {
87
+ return true;
88
+ }
89
+ }
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Create a reactive effect that re-runs whenever its dependencies change.
96
+ * Returns a cleanup function to stop the effect.
97
+ */
98
+ export function effect(
99
+ name: string,
100
+ fn: () => void,
101
+ opts?: EffectSchedulerOptions,
102
+ ): () => void {
103
+ const scheduler = new EffectScheduler(name, fn, opts);
104
+ scheduler.attach();
105
+ return () => scheduler.detach();
106
+ }
107
+
108
+ /**
109
+ * Alias: react() — same as effect()
110
+ */
111
+ export function react(
112
+ name: string,
113
+ fn: () => void,
114
+ opts?: EffectSchedulerOptions,
115
+ ): () => void {
116
+ return effect(name, fn, opts);
117
+ }
@@ -0,0 +1,9 @@
1
+ export { atom, isAtom } from "./atom";
2
+ export type { Atom } from "./atom";
3
+ export { computed, isComputed, UNINITIALIZED } from "./computed";
4
+ export type { Computed } from "./computed";
5
+ export { effect, react, EffectScheduler } from "./effect";
6
+ export type { EffectSchedulerOptions } from "./effect";
7
+ export { transact, getGlobalEpoch } from "./core";
8
+ export { RESET_VALUE } from "./types";
9
+ export type { Signal, Child } from "./types";
@@ -0,0 +1,17 @@
1
+ /** A reactive signal that holds subscribers */
2
+ export interface Signal<T> {
3
+ readonly name: string;
4
+ get(): T;
5
+ __unsafe__getWithoutCapture(): T;
6
+ lastChangedEpoch: number;
7
+ children: Set<Child>;
8
+ }
9
+
10
+ export interface Child {
11
+ type: "computed" | "effect";
12
+ notify(): void;
13
+ source?: Signal<any>;
14
+ }
15
+
16
+ export const RESET_VALUE: unique symbol = Symbol("RESET_VALUE");
17
+ export type RESET_VALUE = typeof RESET_VALUE;
@@ -0,0 +1,171 @@
1
+ import { atom, type Atom } from "@land/signals";
2
+ import {
3
+ createEmptyRecordsDiff,
4
+ getRecordsDiff,
5
+ hasRecordsDiff,
6
+ } from "./diff";
7
+ import type {
8
+ ChangeSource,
9
+ RecordsDiff,
10
+ ScopedStoreRecord,
11
+ StoreChange,
12
+ StoreRecord,
13
+ StoreScopeOf,
14
+ } from "./types";
15
+
16
+ type StoreListener<TRecord extends StoreRecord> = (
17
+ change: StoreChange<TRecord>,
18
+ ) => void;
19
+ type ScopedRecord<TRecord extends StoreRecord> = Extract<
20
+ TRecord,
21
+ ScopedStoreRecord
22
+ >;
23
+
24
+ export class Store<TRecord extends StoreRecord = StoreRecord> {
25
+ readonly recordsSignal: Atom<Map<string, TRecord>>;
26
+
27
+ private readonly listeners = new Set<StoreListener<TRecord>>();
28
+ private transactionDepth = 0;
29
+ private draftRecords: Map<string, TRecord> | null = null;
30
+ private pendingSource: ChangeSource = "system";
31
+
32
+ constructor(initialRecords: TRecord[]) {
33
+ this.recordsSignal = atom(
34
+ "store.records",
35
+ new Map(initialRecords.map((record) => [record.id, record])),
36
+ );
37
+ }
38
+
39
+ get(id: string): TRecord | undefined {
40
+ return this.getReadableRecords().get(id);
41
+ }
42
+
43
+ getAll(): TRecord[] {
44
+ return Array.from(this.getReadableRecords().values());
45
+ }
46
+
47
+ getByScope<TScope extends StoreScopeOf<ScopedRecord<TRecord>>>(
48
+ scope: TScope,
49
+ ): Array<Extract<ScopedRecord<TRecord>, { scope: TScope }>> {
50
+ return this.getAll().filter(
51
+ (record): record is Extract<ScopedRecord<TRecord>, { scope: TScope }> =>
52
+ "scope" in record && record.scope === scope,
53
+ );
54
+ }
55
+
56
+ listen(listener: StoreListener<TRecord>): () => void {
57
+ this.listeners.add(listener);
58
+ return () => this.listeners.delete(listener);
59
+ }
60
+
61
+ atomic(fn: () => void, source: ChangeSource = "system"): void {
62
+ const isOuter = this.transactionDepth === 0;
63
+ if (isOuter) {
64
+ this.draftRecords = new Map(this.recordsSignal.get());
65
+ this.pendingSource = source;
66
+ }
67
+
68
+ this.transactionDepth += 1;
69
+ let thrown: unknown;
70
+
71
+ try {
72
+ fn();
73
+ } catch (error) {
74
+ thrown = error;
75
+ }
76
+
77
+ this.transactionDepth -= 1;
78
+
79
+ if (isOuter) {
80
+ const previous = this.recordsSignal.get();
81
+ const next = this.draftRecords;
82
+ const pendingSource = this.pendingSource;
83
+ this.draftRecords = null;
84
+ this.pendingSource = "system";
85
+ const diff = next ? getRecordsDiff(previous, next) : createEmptyRecordsDiff<TRecord>();
86
+
87
+ if (!thrown && next && hasRecordsDiff(diff)) {
88
+ this.recordsSignal.set(next);
89
+ const change = {
90
+ source: pendingSource,
91
+ diff,
92
+ } satisfies StoreChange<TRecord>;
93
+ for (const notify of this.listeners) {
94
+ notify(change);
95
+ }
96
+ }
97
+ }
98
+
99
+ if (thrown) {
100
+ throw thrown;
101
+ }
102
+ }
103
+
104
+ put(records: TRecord[], source: ChangeSource = "system"): void {
105
+ this.atomic(() => {
106
+ const target = this.getMutableRecords();
107
+ for (const record of records) {
108
+ const previous = target.get(record.id);
109
+ if (previous === record) continue;
110
+ target.set(record.id, record);
111
+ }
112
+ }, source);
113
+ }
114
+
115
+ remove(ids: string[], source: ChangeSource = "system"): void {
116
+ this.atomic(() => {
117
+ const target = this.getMutableRecords();
118
+ for (const id of ids) {
119
+ if (!target.delete(id)) continue;
120
+ }
121
+ }, source);
122
+ }
123
+
124
+ replaceAll(records: TRecord[], source: ChangeSource = "system"): void {
125
+ const next = new Map(records.map((record) => [record.id, record]));
126
+ const diff = getRecordsDiff(this.recordsSignal.get(), next);
127
+ if (!hasRecordsDiff(diff)) {
128
+ return;
129
+ }
130
+ this.recordsSignal.set(next);
131
+ const change = {
132
+ source,
133
+ diff,
134
+ } satisfies StoreChange<TRecord>;
135
+ for (const notify of this.listeners) {
136
+ notify(change);
137
+ }
138
+ }
139
+
140
+ applyDiff<TChangeRecord extends TRecord>(
141
+ diff: RecordsDiff<TChangeRecord>,
142
+ source: ChangeSource = "system",
143
+ ): void {
144
+ this.atomic(() => {
145
+ const target = this.getMutableRecords();
146
+
147
+ for (const record of diff.added) {
148
+ target.set(record.id, record);
149
+ }
150
+
151
+ for (const update of diff.updated) {
152
+ target.set(update.after.id, update.after);
153
+ }
154
+
155
+ for (const record of diff.removed) {
156
+ target.delete(record.id);
157
+ }
158
+ }, source);
159
+ }
160
+
161
+ private getMutableRecords(): Map<string, TRecord> {
162
+ if (!this.draftRecords) {
163
+ throw new Error("Store mutations must run inside store.atomic");
164
+ }
165
+ return this.draftRecords;
166
+ }
167
+
168
+ private getReadableRecords(): Map<string, TRecord> {
169
+ return this.draftRecords ?? this.recordsSignal.get();
170
+ }
171
+ }
@@ -0,0 +1,229 @@
1
+ import type {
2
+ RecordUpdate,
3
+ RecordsDiff,
4
+ StoreRecord,
5
+ } from "./types";
6
+
7
+ function isMap<TRecord extends StoreRecord>(
8
+ value: Map<string, TRecord> | Iterable<TRecord>,
9
+ ): value is Map<string, TRecord> {
10
+ return value instanceof Map;
11
+ }
12
+
13
+ export function createEmptyRecordsDiff<
14
+ TRecord extends StoreRecord,
15
+ >(): RecordsDiff<TRecord> {
16
+ return {
17
+ added: [],
18
+ updated: [],
19
+ removed: [],
20
+ };
21
+ }
22
+
23
+ export function hasRecordsDiff<TRecord extends StoreRecord>(
24
+ diff: RecordsDiff<TRecord>,
25
+ ): boolean {
26
+ return diff.added.length > 0 || diff.updated.length > 0 || diff.removed.length > 0;
27
+ }
28
+
29
+ export function getRecordMap<TRecord extends StoreRecord>(
30
+ records: Map<string, TRecord> | Iterable<TRecord>,
31
+ ): Map<string, TRecord> {
32
+ return isMap(records)
33
+ ? new Map(records)
34
+ : new Map(Array.from(records, (record) => [record.id, record]));
35
+ }
36
+
37
+ export function getRecordsDiff<TRecord extends StoreRecord>(
38
+ before: Map<string, TRecord> | Iterable<TRecord>,
39
+ after: Map<string, TRecord> | Iterable<TRecord>,
40
+ ): RecordsDiff<TRecord> {
41
+ const beforeMap = getRecordMap(before);
42
+ const afterMap = getRecordMap(after);
43
+ const diff = createEmptyRecordsDiff<TRecord>();
44
+
45
+ for (const [id, previous] of beforeMap) {
46
+ const next = afterMap.get(id);
47
+ if (!next) {
48
+ diff.removed.push(previous);
49
+ continue;
50
+ }
51
+
52
+ if (next !== previous) {
53
+ diff.updated.push({
54
+ before: previous,
55
+ after: next,
56
+ });
57
+ }
58
+ }
59
+
60
+ for (const [id, next] of afterMap) {
61
+ if (!beforeMap.has(id)) {
62
+ diff.added.push(next);
63
+ }
64
+ }
65
+
66
+ return diff;
67
+ }
68
+
69
+ export function invertRecordsDiff<TRecord extends StoreRecord>(
70
+ diff: RecordsDiff<TRecord>,
71
+ ): RecordsDiff<TRecord> {
72
+ return {
73
+ added: [...diff.removed],
74
+ updated: diff.updated.map(
75
+ (update): RecordUpdate<TRecord> => ({
76
+ before: update.after,
77
+ after: update.before,
78
+ }),
79
+ ),
80
+ removed: [...diff.added],
81
+ };
82
+ }
83
+
84
+ export function composeRecordsDiff<
85
+ TRecord extends StoreRecord,
86
+ TNextRecord extends TRecord,
87
+ >(
88
+ left: RecordsDiff<TRecord>,
89
+ right: RecordsDiff<TNextRecord>,
90
+ ): RecordsDiff<TRecord | TNextRecord> {
91
+ const added = new Map<string, TRecord | TNextRecord>();
92
+ const updated = new Map<string, RecordUpdate<TRecord | TNextRecord>>();
93
+ const removed = new Map<string, TRecord | TNextRecord>();
94
+
95
+ for (const record of left.added) {
96
+ added.set(record.id, record);
97
+ }
98
+
99
+ for (const update of left.updated) {
100
+ updated.set(update.after.id, update);
101
+ }
102
+
103
+ for (const record of left.removed) {
104
+ removed.set(record.id, record);
105
+ }
106
+
107
+ for (const record of right.added) {
108
+ const existingRemoved = removed.get(record.id);
109
+ if (existingRemoved) {
110
+ removed.delete(record.id);
111
+ updated.set(record.id, {
112
+ before: existingRemoved,
113
+ after: record,
114
+ });
115
+ continue;
116
+ }
117
+
118
+ const existingUpdated = updated.get(record.id);
119
+ if (existingUpdated) {
120
+ updated.set(record.id, {
121
+ before: existingUpdated.before,
122
+ after: record,
123
+ });
124
+ continue;
125
+ }
126
+
127
+ added.set(record.id, record);
128
+ }
129
+
130
+ for (const change of right.updated) {
131
+ const existingAdded = added.get(change.after.id);
132
+ if (existingAdded) {
133
+ added.set(change.after.id, change.after);
134
+ continue;
135
+ }
136
+
137
+ const existingRemoved = removed.get(change.after.id);
138
+ if (existingRemoved) {
139
+ removed.delete(change.after.id);
140
+ updated.set(change.after.id, {
141
+ before: existingRemoved,
142
+ after: change.after,
143
+ });
144
+ continue;
145
+ }
146
+
147
+ const existingUpdated = updated.get(change.after.id);
148
+ if (existingUpdated) {
149
+ updated.set(change.after.id, {
150
+ before: existingUpdated.before,
151
+ after: change.after,
152
+ });
153
+ continue;
154
+ }
155
+
156
+ updated.set(change.after.id, {
157
+ before: change.before,
158
+ after: change.after,
159
+ });
160
+ }
161
+
162
+ for (const record of right.removed) {
163
+ const existingAdded = added.get(record.id);
164
+ if (existingAdded) {
165
+ added.delete(record.id);
166
+ continue;
167
+ }
168
+
169
+ const existingUpdated = updated.get(record.id);
170
+ if (existingUpdated) {
171
+ updated.delete(record.id);
172
+ removed.set(record.id, existingUpdated.before);
173
+ continue;
174
+ }
175
+
176
+ removed.set(record.id, record);
177
+ }
178
+
179
+ const next = createEmptyRecordsDiff<TRecord | TNextRecord>();
180
+
181
+ for (const record of added.values()) {
182
+ next.added.push(record);
183
+ }
184
+
185
+ for (const change of updated.values()) {
186
+ if (change.before === change.after) {
187
+ continue;
188
+ }
189
+ next.updated.push(change);
190
+ }
191
+
192
+ for (const record of removed.values()) {
193
+ next.removed.push(record);
194
+ }
195
+
196
+ return next;
197
+ }
198
+
199
+ export function squashRecordsDiffs<
200
+ TRecord extends StoreRecord,
201
+ TNextRecord extends TRecord,
202
+ >(
203
+ diffs: Iterable<RecordsDiff<TRecord> | RecordsDiff<TNextRecord>>,
204
+ ): RecordsDiff<TRecord | TNextRecord> {
205
+ let next = createEmptyRecordsDiff<TRecord | TNextRecord>();
206
+ for (const diff of diffs) {
207
+ next = composeRecordsDiff(next, diff);
208
+ }
209
+ return next;
210
+ }
211
+
212
+ export function filterRecordsDiff<
213
+ TRecord extends StoreRecord,
214
+ TFilteredRecord extends TRecord,
215
+ >(
216
+ diff: RecordsDiff<TRecord>,
217
+ predicate: (record: TRecord) => record is TFilteredRecord,
218
+ ): RecordsDiff<TFilteredRecord> {
219
+ return {
220
+ added: diff.added.filter(predicate),
221
+ updated: diff.updated.filter(
222
+ (
223
+ update,
224
+ ): update is RecordUpdate<TFilteredRecord> =>
225
+ predicate(update.before) && predicate(update.after),
226
+ ),
227
+ removed: diff.removed.filter(predicate),
228
+ };
229
+ }