@oh-just-another/state 0.57.0

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 (269) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/actions/actionClipboard.d.ts +6 -0
  6. package/dist/actions/actionClipboard.d.ts.map +1 -0
  7. package/dist/actions/actionClipboard.js +26 -0
  8. package/dist/actions/actionClipboard.js.map +1 -0
  9. package/dist/actions/actionGrouping.d.ts +5 -0
  10. package/dist/actions/actionGrouping.d.ts.map +1 -0
  11. package/dist/actions/actionGrouping.js +26 -0
  12. package/dist/actions/actionGrouping.js.map +1 -0
  13. package/dist/actions/actionHistory.d.ts +5 -0
  14. package/dist/actions/actionHistory.d.ts.map +1 -0
  15. package/dist/actions/actionHistory.js +25 -0
  16. package/dist/actions/actionHistory.js.map +1 -0
  17. package/dist/actions/actionKeyboard.d.ts +3 -0
  18. package/dist/actions/actionKeyboard.d.ts.map +1 -0
  19. package/dist/actions/actionKeyboard.js +130 -0
  20. package/dist/actions/actionKeyboard.js.map +1 -0
  21. package/dist/actions/actionLayout.d.ts +3 -0
  22. package/dist/actions/actionLayout.d.ts.map +1 -0
  23. package/dist/actions/actionLayout.js +65 -0
  24. package/dist/actions/actionLayout.js.map +1 -0
  25. package/dist/actions/actionMode.d.ts +13 -0
  26. package/dist/actions/actionMode.d.ts.map +1 -0
  27. package/dist/actions/actionMode.js +116 -0
  28. package/dist/actions/actionMode.js.map +1 -0
  29. package/dist/actions/actionSelection.d.ts +9 -0
  30. package/dist/actions/actionSelection.d.ts.map +1 -0
  31. package/dist/actions/actionSelection.js +57 -0
  32. package/dist/actions/actionSelection.js.map +1 -0
  33. package/dist/actions/actionView.d.ts +7 -0
  34. package/dist/actions/actionView.d.ts.map +1 -0
  35. package/dist/actions/actionView.js +14 -0
  36. package/dist/actions/actionView.js.map +1 -0
  37. package/dist/actions/actionZOrder.d.ts +7 -0
  38. package/dist/actions/actionZOrder.d.ts.map +1 -0
  39. package/dist/actions/actionZOrder.js +42 -0
  40. package/dist/actions/actionZOrder.js.map +1 -0
  41. package/dist/actions/actionZoom.d.ts +8 -0
  42. package/dist/actions/actionZoom.d.ts.map +1 -0
  43. package/dist/actions/actionZoom.js +61 -0
  44. package/dist/actions/actionZoom.js.map +1 -0
  45. package/dist/actions/index.d.ts +12 -0
  46. package/dist/actions/index.d.ts.map +1 -0
  47. package/dist/actions/index.js +11 -0
  48. package/dist/actions/index.js.map +1 -0
  49. package/dist/actions/registry.d.ts +68 -0
  50. package/dist/actions/registry.d.ts.map +1 -0
  51. package/dist/actions/registry.js +300 -0
  52. package/dist/actions/registry.js.map +1 -0
  53. package/dist/actions/types.d.ts +103 -0
  54. package/dist/actions/types.d.ts.map +1 -0
  55. package/dist/actions/types.js +10 -0
  56. package/dist/actions/types.js.map +1 -0
  57. package/dist/animation-tick.d.ts +28 -0
  58. package/dist/animation-tick.d.ts.map +1 -0
  59. package/dist/animation-tick.js +50 -0
  60. package/dist/animation-tick.js.map +1 -0
  61. package/dist/auto-compact.d.ts +27 -0
  62. package/dist/auto-compact.d.ts.map +1 -0
  63. package/dist/auto-compact.js +46 -0
  64. package/dist/auto-compact.js.map +1 -0
  65. package/dist/auto-layout-scheduler.d.ts +50 -0
  66. package/dist/auto-layout-scheduler.d.ts.map +1 -0
  67. package/dist/auto-layout-scheduler.js +94 -0
  68. package/dist/auto-layout-scheduler.js.map +1 -0
  69. package/dist/built-in-handlers.d.ts +24 -0
  70. package/dist/built-in-handlers.d.ts.map +1 -0
  71. package/dist/built-in-handlers.js +210 -0
  72. package/dist/built-in-handlers.js.map +1 -0
  73. package/dist/clipboard.d.ts +51 -0
  74. package/dist/clipboard.d.ts.map +1 -0
  75. package/dist/clipboard.js +105 -0
  76. package/dist/clipboard.js.map +1 -0
  77. package/dist/constants.d.ts +544 -0
  78. package/dist/constants.d.ts.map +1 -0
  79. package/dist/constants.js +537 -0
  80. package/dist/constants.js.map +1 -0
  81. package/dist/dom-events.d.ts +13 -0
  82. package/dist/dom-events.d.ts.map +1 -0
  83. package/dist/dom-events.js +70 -0
  84. package/dist/dom-events.js.map +1 -0
  85. package/dist/editor/anchor-points.d.ts +22 -0
  86. package/dist/editor/anchor-points.d.ts.map +1 -0
  87. package/dist/editor/anchor-points.js +28 -0
  88. package/dist/editor/anchor-points.js.map +1 -0
  89. package/dist/editor/applies/create.d.ts +60 -0
  90. package/dist/editor/applies/create.d.ts.map +1 -0
  91. package/dist/editor/applies/create.js +107 -0
  92. package/dist/editor/applies/create.js.map +1 -0
  93. package/dist/editor/applies/edge.d.ts +32 -0
  94. package/dist/editor/applies/edge.d.ts.map +1 -0
  95. package/dist/editor/applies/edge.js +44 -0
  96. package/dist/editor/applies/edge.js.map +1 -0
  97. package/dist/editor/applies/link-move.d.ts +53 -0
  98. package/dist/editor/applies/link-move.d.ts.map +1 -0
  99. package/dist/editor/applies/link-move.js +160 -0
  100. package/dist/editor/applies/link-move.js.map +1 -0
  101. package/dist/editor/applies/move.d.ts +38 -0
  102. package/dist/editor/applies/move.d.ts.map +1 -0
  103. package/dist/editor/applies/move.js +83 -0
  104. package/dist/editor/applies/move.js.map +1 -0
  105. package/dist/editor/applies/resize.d.ts +68 -0
  106. package/dist/editor/applies/resize.d.ts.map +1 -0
  107. package/dist/editor/applies/resize.js +199 -0
  108. package/dist/editor/applies/resize.js.map +1 -0
  109. package/dist/editor/applies/selection.d.ts +34 -0
  110. package/dist/editor/applies/selection.d.ts.map +1 -0
  111. package/dist/editor/applies/selection.js +65 -0
  112. package/dist/editor/applies/selection.js.map +1 -0
  113. package/dist/editor/applies/snap-grid.d.ts +45 -0
  114. package/dist/editor/applies/snap-grid.d.ts.map +1 -0
  115. package/dist/editor/applies/snap-grid.js +90 -0
  116. package/dist/editor/applies/snap-grid.js.map +1 -0
  117. package/dist/editor/container-ops.d.ts +74 -0
  118. package/dist/editor/container-ops.d.ts.map +1 -0
  119. package/dist/editor/container-ops.js +262 -0
  120. package/dist/editor/container-ops.js.map +1 -0
  121. package/dist/editor/event-fanout.d.ts +48 -0
  122. package/dist/editor/event-fanout.d.ts.map +1 -0
  123. package/dist/editor/event-fanout.js +53 -0
  124. package/dist/editor/event-fanout.js.map +1 -0
  125. package/dist/editor/gesture-tx.d.ts +77 -0
  126. package/dist/editor/gesture-tx.d.ts.map +1 -0
  127. package/dist/editor/gesture-tx.js +93 -0
  128. package/dist/editor/gesture-tx.js.map +1 -0
  129. package/dist/editor/hit-test.d.ts +178 -0
  130. package/dist/editor/hit-test.d.ts.map +1 -0
  131. package/dist/editor/hit-test.js +460 -0
  132. package/dist/editor/hit-test.js.map +1 -0
  133. package/dist/editor/long-press.d.ts +34 -0
  134. package/dist/editor/long-press.d.ts.map +1 -0
  135. package/dist/editor/long-press.js +58 -0
  136. package/dist/editor/long-press.js.map +1 -0
  137. package/dist/editor/pinch.d.ts +37 -0
  138. package/dist/editor/pinch.d.ts.map +1 -0
  139. package/dist/editor/pinch.js +90 -0
  140. package/dist/editor/pinch.js.map +1 -0
  141. package/dist/editor/pointer-binding.d.ts +18 -0
  142. package/dist/editor/pointer-binding.d.ts.map +1 -0
  143. package/dist/editor/pointer-binding.js +1087 -0
  144. package/dist/editor/pointer-binding.js.map +1 -0
  145. package/dist/editor/public/annotations.d.ts +54 -0
  146. package/dist/editor/public/annotations.d.ts.map +1 -0
  147. package/dist/editor/public/annotations.js +106 -0
  148. package/dist/editor/public/annotations.js.map +1 -0
  149. package/dist/editor/public/arrange-group.d.ts +81 -0
  150. package/dist/editor/public/arrange-group.d.ts.map +1 -0
  151. package/dist/editor/public/arrange-group.js +193 -0
  152. package/dist/editor/public/arrange-group.js.map +1 -0
  153. package/dist/editor/public/brush.d.ts +33 -0
  154. package/dist/editor/public/brush.d.ts.map +1 -0
  155. package/dist/editor/public/brush.js +54 -0
  156. package/dist/editor/public/brush.js.map +1 -0
  157. package/dist/editor/public/clipboard.d.ts +31 -0
  158. package/dist/editor/public/clipboard.d.ts.map +1 -0
  159. package/dist/editor/public/clipboard.js +35 -0
  160. package/dist/editor/public/clipboard.js.map +1 -0
  161. package/dist/editor/public/image-insert.d.ts +30 -0
  162. package/dist/editor/public/image-insert.d.ts.map +1 -0
  163. package/dist/editor/public/image-insert.js +57 -0
  164. package/dist/editor/public/image-insert.js.map +1 -0
  165. package/dist/editor/public/layers.d.ts +56 -0
  166. package/dist/editor/public/layers.d.ts.map +1 -0
  167. package/dist/editor/public/layers.js +104 -0
  168. package/dist/editor/public/layers.js.map +1 -0
  169. package/dist/editor/public/link.d.ts +25 -0
  170. package/dist/editor/public/link.d.ts.map +1 -0
  171. package/dist/editor/public/link.js +74 -0
  172. package/dist/editor/public/link.js.map +1 -0
  173. package/dist/editor/public/placement.d.ts +60 -0
  174. package/dist/editor/public/placement.d.ts.map +1 -0
  175. package/dist/editor/public/placement.js +111 -0
  176. package/dist/editor/public/placement.js.map +1 -0
  177. package/dist/editor/public/selection-ops.d.ts +78 -0
  178. package/dist/editor/public/selection-ops.d.ts.map +1 -0
  179. package/dist/editor/public/selection-ops.js +226 -0
  180. package/dist/editor/public/selection-ops.js.map +1 -0
  181. package/dist/editor/public/text-edit.d.ts +17 -0
  182. package/dist/editor/public/text-edit.d.ts.map +1 -0
  183. package/dist/editor/public/text-edit.js +28 -0
  184. package/dist/editor/public/text-edit.js.map +1 -0
  185. package/dist/editor/public/z-order.d.ts +47 -0
  186. package/dist/editor/public/z-order.d.ts.map +1 -0
  187. package/dist/editor/public/z-order.js +151 -0
  188. package/dist/editor/public/z-order.js.map +1 -0
  189. package/dist/editor/public/zoom-pan.d.ts +44 -0
  190. package/dist/editor/public/zoom-pan.d.ts.map +1 -0
  191. package/dist/editor/public/zoom-pan.js +116 -0
  192. package/dist/editor/public/zoom-pan.js.map +1 -0
  193. package/dist/editor/render-orchestrator.d.ts +11 -0
  194. package/dist/editor/render-orchestrator.d.ts.map +1 -0
  195. package/dist/editor/render-orchestrator.js +473 -0
  196. package/dist/editor/render-orchestrator.js.map +1 -0
  197. package/dist/editor/resize-helpers.d.ts +30 -0
  198. package/dist/editor/resize-helpers.d.ts.map +1 -0
  199. package/dist/editor/resize-helpers.js +121 -0
  200. package/dist/editor/resize-helpers.js.map +1 -0
  201. package/dist/editor/shape-filters.d.ts +11 -0
  202. package/dist/editor/shape-filters.d.ts.map +1 -0
  203. package/dist/editor/shape-filters.js +19 -0
  204. package/dist/editor/shape-filters.js.map +1 -0
  205. package/dist/editor/shape-traits.d.ts +16 -0
  206. package/dist/editor/shape-traits.d.ts.map +1 -0
  207. package/dist/editor/shape-traits.js +29 -0
  208. package/dist/editor/shape-traits.js.map +1 -0
  209. package/dist/editor/viewport-helpers.d.ts +24 -0
  210. package/dist/editor/viewport-helpers.d.ts.map +1 -0
  211. package/dist/editor/viewport-helpers.js +63 -0
  212. package/dist/editor/viewport-helpers.js.map +1 -0
  213. package/dist/editor-events.d.ts +33 -0
  214. package/dist/editor-events.d.ts.map +1 -0
  215. package/dist/editor-events.js +2 -0
  216. package/dist/editor-events.js.map +1 -0
  217. package/dist/editor.d.ts +1841 -0
  218. package/dist/editor.d.ts.map +1 -0
  219. package/dist/editor.js +4803 -0
  220. package/dist/editor.js.map +1 -0
  221. package/dist/file-drop.d.ts +99 -0
  222. package/dist/file-drop.d.ts.map +1 -0
  223. package/dist/file-drop.js +198 -0
  224. package/dist/file-drop.js.map +1 -0
  225. package/dist/frame-helpers.d.ts +30 -0
  226. package/dist/frame-helpers.d.ts.map +1 -0
  227. package/dist/frame-helpers.js +105 -0
  228. package/dist/frame-helpers.js.map +1 -0
  229. package/dist/group-helpers.d.ts +39 -0
  230. package/dist/group-helpers.d.ts.map +1 -0
  231. package/dist/group-helpers.js +116 -0
  232. package/dist/group-helpers.js.map +1 -0
  233. package/dist/handle.d.ts +73 -0
  234. package/dist/handle.d.ts.map +1 -0
  235. package/dist/handle.js +184 -0
  236. package/dist/handle.js.map +1 -0
  237. package/dist/index.d.ts +26 -0
  238. package/dist/index.d.ts.map +1 -0
  239. package/dist/index.js +16 -0
  240. package/dist/index.js.map +1 -0
  241. package/dist/interactive.d.ts +21 -0
  242. package/dist/interactive.d.ts.map +1 -0
  243. package/dist/interactive.js +11 -0
  244. package/dist/interactive.js.map +1 -0
  245. package/dist/link-selection.d.ts +21 -0
  246. package/dist/link-selection.d.ts.map +1 -0
  247. package/dist/link-selection.js +37 -0
  248. package/dist/link-selection.js.map +1 -0
  249. package/dist/machine.d.ts +322 -0
  250. package/dist/machine.d.ts.map +1 -0
  251. package/dist/machine.js +568 -0
  252. package/dist/machine.js.map +1 -0
  253. package/dist/modes.d.ts +24 -0
  254. package/dist/modes.d.ts.map +1 -0
  255. package/dist/modes.js +2 -0
  256. package/dist/modes.js.map +1 -0
  257. package/dist/overlay.d.ts +262 -0
  258. package/dist/overlay.d.ts.map +1 -0
  259. package/dist/overlay.js +673 -0
  260. package/dist/overlay.js.map +1 -0
  261. package/dist/platform.d.ts +70 -0
  262. package/dist/platform.d.ts.map +1 -0
  263. package/dist/platform.js +115 -0
  264. package/dist/platform.js.map +1 -0
  265. package/dist/selection.d.ts +18 -0
  266. package/dist/selection.d.ts.map +1 -0
  267. package/dist/selection.js +31 -0
  268. package/dist/selection.js.map +1 -0
  269. package/package.json +59 -0
@@ -0,0 +1,300 @@
1
+ import { isMac } from "../platform.js";
2
+ import { SEQUENCE_HOTKEY_WINDOW_MS } from "../constants.js";
3
+ import { historyActions } from "./actionHistory.js";
4
+ import { selectionActions } from "./actionSelection.js";
5
+ import { clipboardActions } from "./actionClipboard.js";
6
+ import { zOrderActions } from "./actionZOrder.js";
7
+ import { groupingActions } from "./actionGrouping.js";
8
+ import { zoomActions } from "./actionZoom.js";
9
+ import { modeActions } from "./actionMode.js";
10
+ import { keyboardActions } from "./actionKeyboard.js";
11
+ import { layoutActions } from "./actionLayout.js";
12
+ import { viewActions } from "./actionView.js";
13
+ /**
14
+ * Registry of actions. Hosts construct one (or use the shared
15
+ * `defaultActionRegistry` populated by `registerBuiltinActions`),
16
+ * register custom actions on top, and pass it to the hotkey listener
17
+ * + context menu builder. Order of registration is preserved so
18
+ * `dispatchHotkey` returns the first match.
19
+ */
20
+ export class ActionRegistry {
21
+ entries = new Map();
22
+ /** Registered keys for ordered iteration on dispatch. */
23
+ order = [];
24
+ /**
25
+ * Rolling buffer of recent plain keypresses (key + event timestamp) for
26
+ * matching multi-key `sequence` actions like `g d`. Pruned to the
27
+ * `SEQUENCE_HOTKEY_WINDOW_MS` window on each feed; cleared on a match or
28
+ * when a modifier combo interrupts the chain.
29
+ */
30
+ seqBuffer = [];
31
+ register(action) {
32
+ if (this.entries.has(action.id)) {
33
+ throw new Error(`Action already registered: ${action.id}`);
34
+ }
35
+ this.entries.set(action.id, action);
36
+ this.order.push(action.id);
37
+ }
38
+ /** Re-registering the same id overwrites it in place. */
39
+ replace(action) {
40
+ if (this.entries.has(action.id)) {
41
+ this.entries.set(action.id, action);
42
+ return;
43
+ }
44
+ this.register(action);
45
+ }
46
+ unregister(id) {
47
+ if (!this.entries.delete(id))
48
+ return;
49
+ const idx = this.order.indexOf(id);
50
+ if (idx !== -1)
51
+ this.order.splice(idx, 1);
52
+ }
53
+ get(id) {
54
+ return this.entries.get(id);
55
+ }
56
+ getAll() {
57
+ return this.order
58
+ .map((id) => this.entries.get(id))
59
+ .filter((a) => a !== undefined);
60
+ }
61
+ /**
62
+ * Dispatch by id. Returns `true` if an action ran (predicate passed
63
+ * AND perform was called), `false` otherwise.
64
+ */
65
+ dispatch(id, ctx) {
66
+ const action = this.entries.get(id);
67
+ if (!action)
68
+ return false;
69
+ if (action.predicate && !action.predicate(ctx))
70
+ return false;
71
+ action.perform(ctx);
72
+ return true;
73
+ }
74
+ /**
75
+ * Match the keyboard event against every registered action's
76
+ * `hotkey` matcher(s) and dispatch the first whose predicate
77
+ * passes. Returns `true` on hit (caller should `preventDefault`),
78
+ * `false` when no action matched.
79
+ *
80
+ * Honours platform convention for `meta`: on macOS the
81
+ * platform's Cmd key (event.metaKey) qualifies; elsewhere it's
82
+ * Ctrl (event.ctrlKey).
83
+ */
84
+ dispatchHotkey(event, ctx) {
85
+ const fullCtx = { ...ctx, event };
86
+ // 1. Multi-key sequences (g d, …) — checked first so the final key
87
+ // can't also fire a single-key action bound to the same letter.
88
+ if (this.trySequence(event, fullCtx))
89
+ return true;
90
+ for (const id of this.order) {
91
+ const action = this.entries.get(id);
92
+ if (!action)
93
+ continue;
94
+ // 2. Custom keyTest escape hatch (before declarative matchers).
95
+ if (action.keyTest?.(event, fullCtx)) {
96
+ if (action.predicate && !action.predicate(fullCtx))
97
+ continue;
98
+ action.perform(fullCtx);
99
+ return true;
100
+ }
101
+ // 3. Declarative single-key matchers.
102
+ if (!action.hotkey)
103
+ continue;
104
+ const matchers = Array.isArray(action.hotkey)
105
+ ? action.hotkey
106
+ : [action.hotkey];
107
+ for (const m of matchers) {
108
+ if (!matchesHotkey(event, m))
109
+ continue;
110
+ if (action.predicate && !action.predicate(fullCtx))
111
+ continue;
112
+ action.perform(fullCtx);
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+ /**
119
+ * Feed the event into the sequence buffer and dispatch the first action
120
+ * whose `sequence` the buffer's tail now completes (within the time
121
+ * window). Only plain keypresses (single char, no Meta/Ctrl/Alt) chain;
122
+ * any modifier combo clears the buffer so `g`-then-`Cmd+d` can't match.
123
+ */
124
+ trySequence(event, ctx) {
125
+ const plain = event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
126
+ if (!plain) {
127
+ this.seqBuffer = [];
128
+ return false;
129
+ }
130
+ const t = event.timeStamp;
131
+ this.seqBuffer = this.seqBuffer.filter((e) => t - e.t <= SEQUENCE_HOTKEY_WINDOW_MS);
132
+ // Keep key AND code so the match is layout-independent (a Cyrillic
133
+ // layout's physical G/D still complete a "g d" sequence via `code`).
134
+ this.seqBuffer.push({ key: event.key, code: event.code, t });
135
+ for (const id of this.order) {
136
+ const action = this.entries.get(id);
137
+ if (!action?.sequence || action.sequence.length === 0)
138
+ continue;
139
+ const seq = action.sequence;
140
+ const tail = this.seqBuffer.slice(-seq.length);
141
+ if (tail.length !== seq.length)
142
+ continue;
143
+ if (!tail.every((e, i) => matchKeyOrCodeRaw(e.key, e.code, seq[i] ?? "")))
144
+ continue;
145
+ if (action.predicate && !action.predicate(ctx))
146
+ continue;
147
+ this.seqBuffer = [];
148
+ action.perform(ctx);
149
+ return true;
150
+ }
151
+ return false;
152
+ }
153
+ }
154
+ const matchesHotkey = (event, m) => {
155
+ if (m.key !== undefined && !matchKeyOrCode(event, m.key))
156
+ return false;
157
+ if (m.code !== undefined && event.code !== m.code)
158
+ return false;
159
+ const want = (flag) => flag === true;
160
+ // Cross-platform meta: macOS uses metaKey, others use ctrlKey.
161
+ const metaPressed = isMac ? event.metaKey : event.ctrlKey;
162
+ if (want(m.meta) !== metaPressed)
163
+ return false;
164
+ if (want(m.shift) !== event.shiftKey)
165
+ return false;
166
+ if (want(m.alt) !== event.altKey)
167
+ return false;
168
+ if (m.ctrl !== undefined && want(m.ctrl) !== event.ctrlKey)
169
+ return false;
170
+ return true;
171
+ };
172
+ /**
173
+ * Hotkey-key match that survives non-Latin keyboard layouts.
174
+ *
175
+ * 1. First try direct `event.key.toLowerCase()` comparison.
176
+ * Latin-letter layouts (US, UK, German, French, Czech, Turkish,
177
+ * Pinyin IME, …) provide the matcher's expected letter through
178
+ * `event.key` even when the underlying physical key is somewhere
179
+ * else, so this is the right comparison for them.
180
+ *
181
+ * 2. If `event.key` IS itself a Latin letter (a-z) and didn't
182
+ * match — bail. The user pressed a *different* Latin letter
183
+ * from the one the matcher wants — e.g. Czech layout swaps
184
+ * physical Y / Z, so pressing Y yields `key:'y'` even though
185
+ * `code:'KeyZ'`; we must NOT silently rewrite that to the Z
186
+ * hotkey.
187
+ *
188
+ * 3. Otherwise (`event.key` is Cyrillic, Greek, Hebrew, Arabic,
189
+ * a CJK glyph, etc) fall back to `event.code` — the
190
+ * physical-key identifier that is layout-invariant. `KeyZ` is
191
+ * always the key in the Latin-Z position no matter what label
192
+ * the OS prints on it.
193
+ *
194
+ * Special keys (Escape, ArrowLeft, Enter, F1, …) match through
195
+ * step 1 because their `event.key` is stable across layouts.
196
+ *
197
+ * Pinyin / Cangjie / Japanese IMEs: Cmd-modified key presses
198
+ * usually deliver the underlying Latin letter through `event.key`
199
+ * (because the IME is bypassed), so they hit step 1. The few that
200
+ * deliver an IME glyph instead are caught by step 3.
201
+ */
202
+ const matchKeyOrCode = (event, want) => matchKeyOrCodeRaw(event.key, event.code, want);
203
+ /**
204
+ * Layout-independent key match on raw (`key`, `code`) — the core of
205
+ * {@link matchKeyOrCode}, also used by sequence matching where we only kept
206
+ * the key/code, not the whole event. Same rules: prefer `key`, bail on a
207
+ * mismatching Latin letter, else fall back to the physical `code` (so a
208
+ * Cyrillic/Greek/etc layout still triggers a Latin-letter shortcut).
209
+ */
210
+ const matchKeyOrCodeRaw = (key, code, want) => {
211
+ if (key.toLowerCase() === want.toLowerCase())
212
+ return true;
213
+ // Bail when the user pressed a *different* Latin letter — we'd
214
+ // otherwise hijack layouts that swap physical key positions
215
+ // (Czech Y/Z, French QWERTY-AZERTY, etc).
216
+ if (isLatinChar(key))
217
+ return false;
218
+ const expectedCode = codeForKey(want);
219
+ return expectedCode !== null && code === expectedCode;
220
+ };
221
+ const isLatinChar = (s) => /^[a-z]$/i.test(s);
222
+ /**
223
+ * Map a host-declared `key` literal to the `event.code` we'd see
224
+ * when the same physical key is pressed on any layout. Returns
225
+ * `null` for keys whose `code` we don't (or shouldn't) try to
226
+ * derive — those are matched through `event.key` only.
227
+ */
228
+ const codeForKey = (key) => {
229
+ if (key.length !== 1)
230
+ return null;
231
+ const c = key.toLowerCase();
232
+ if (c >= "a" && c <= "z")
233
+ return `Key${c.toUpperCase()}`;
234
+ if (c >= "0" && c <= "9")
235
+ return `Digit${c}`;
236
+ // Punctuation that commonly carries a host-declared hotkey
237
+ // (Cmd+[, Cmd+], Cmd+/, Cmd+., Cmd+, Cmd+- Cmd+= Cmd+;).
238
+ switch (c) {
239
+ case "[":
240
+ return "BracketLeft";
241
+ case "]":
242
+ return "BracketRight";
243
+ case ",":
244
+ return "Comma";
245
+ case ".":
246
+ return "Period";
247
+ case "/":
248
+ return "Slash";
249
+ case ";":
250
+ return "Semicolon";
251
+ case "'":
252
+ return "Quote";
253
+ case "`":
254
+ return "Backquote";
255
+ case "-":
256
+ return "Minus";
257
+ case "=":
258
+ return "Equal";
259
+ case "\\":
260
+ return "Backslash";
261
+ default:
262
+ return null;
263
+ }
264
+ };
265
+ /**
266
+ * Register the kernel's built-in actions on the given registry.
267
+ * Pure side-effect — call once at host bootstrap (or use
268
+ * `defaultActionRegistry` which has them pre-registered).
269
+ *
270
+ * Covers: undo / redo, clipboard (copy / cut / paste), selection
271
+ * (all / clear / duplicate / delete), z-order (front / back),
272
+ * grouping (group / ungroup), zoom (in / out / reset / fit),
273
+ * mode switching (select / hand / draw-rect / draw-ellipse /
274
+ * draw-edge / brush), tool-lock toggle, cancel. Anything host-
275
+ * specific (Save / Load / Export) is registered by the host on top.
276
+ */
277
+ export const registerBuiltinActions = (registry) => {
278
+ const allBuiltins = [
279
+ ...historyActions,
280
+ ...selectionActions,
281
+ ...clipboardActions,
282
+ ...zOrderActions,
283
+ ...groupingActions,
284
+ ...zoomActions,
285
+ ...modeActions,
286
+ ...keyboardActions,
287
+ ...layoutActions,
288
+ ...viewActions,
289
+ ];
290
+ for (const action of allBuiltins) {
291
+ registry.register(action);
292
+ }
293
+ };
294
+ /**
295
+ * Shared registry with built-in actions pre-registered. Hosts can add
296
+ * more on top via `register` or replace existing ones via `replace`.
297
+ */
298
+ export const defaultActionRegistry = new ActionRegistry();
299
+ registerBuiltinActions(defaultActionRegistry);
300
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/actions/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import type { Editor } from "../editor.js";
2
+ /**
3
+ * Action architecture. Every host-facing imperative command (undo, copy,
4
+ * switch-mode, group, …) is wrapped as an `Action` and registered in an
5
+ * `ActionRegistry`. Hotkeys, context menu, and toolbar all dispatch through
6
+ * the same registry, so a host extension that registers a new action gets the
7
+ * keyboard + menu wiring for free if it provides the right metadata.
8
+ */
9
+ export interface ActionContext {
10
+ /** The live editor — every action operates against it. */
11
+ readonly editor: Editor;
12
+ /**
13
+ * The originating event when the action was dispatched via
14
+ * `dispatchHotkey`. Hosts can read modifier state from it; absent
15
+ * when the action was triggered from UI (button / menu).
16
+ */
17
+ readonly event?: KeyboardEvent | undefined;
18
+ }
19
+ /**
20
+ * Keyboard descriptor — matched against a `KeyboardEvent` in
21
+ * `dispatchHotkey`. `key` is case-insensitive against `event.key`;
22
+ * `code` uses `event.code` (layout-independent — handy for non-latin
23
+ * keyboards). At least one of `key` / `code` must be set.
24
+ *
25
+ * `meta` defaults to `false` when omitted. Use the same modifier
26
+ * idiom on macOS and Linux/Windows — `meta: true` matches Cmd on
27
+ * macOS and Ctrl on the others. To target Ctrl specifically (e.g.
28
+ * for the Linux-only convention) set `ctrl: true` and `meta: false`.
29
+ */
30
+ export interface HotkeyMatcher {
31
+ readonly key?: string;
32
+ readonly code?: string;
33
+ /** Cmd on macOS, Ctrl on Linux/Windows. */
34
+ readonly meta?: boolean;
35
+ readonly shift?: boolean;
36
+ readonly alt?: boolean;
37
+ readonly ctrl?: boolean;
38
+ }
39
+ /**
40
+ * Category groups Actions in the help dialog / menus.
41
+ */
42
+ export type ActionCategory = "history" | "selection" | "clipboard" | "z-order" | "grouping" | "zoom" | "mode" | "layout" | "edit" | "other";
43
+ export interface Action {
44
+ /** Stable identifier — used by context menu / hotkey routing. */
45
+ readonly id: string;
46
+ /** Display label for menus / tooltips. */
47
+ readonly label?: string;
48
+ /** Category for grouping in help dialog / menus. */
49
+ readonly category?: ActionCategory;
50
+ /** Hotkey trigger; multiple matchers allowed (e.g. ⌘Z and Ctrl+Z). */
51
+ readonly hotkey?: HotkeyMatcher | readonly HotkeyMatcher[];
52
+ /**
53
+ * Multi-key SEQUENCE trigger — the keys (plain, no modifiers) must be
54
+ * pressed in order within `SEQUENCE_HOTKEY_WINDOW_MS` (e.g.
55
+ * `["g", "d"]` to toggle the debug panel, vim/Gmail style). Matched
56
+ * case-insensitively against `event.key`. Sequences are checked before
57
+ * single-key `hotkey` / `keyTest` so the final key can't double-fire.
58
+ */
59
+ readonly sequence?: readonly string[];
60
+ /**
61
+ * Escape hatch for hotkeys the declarative `hotkey` / `sequence` can't
62
+ * express (modifier combos with custom logic, contextual triggers).
63
+ * Returns `true` to claim the event. Checked after `sequence` and
64
+ * before the declarative `hotkey` matchers.
65
+ */
66
+ readonly keyTest?: (event: KeyboardEvent, ctx: ActionContext) => boolean;
67
+ /**
68
+ * `true` when the action makes sense in the current state — used
69
+ * by menus to gate visibility AND by `dispatch` to short-circuit
70
+ * a no-op trigger. Defaults to "always true" when omitted.
71
+ */
72
+ readonly predicate?: (ctx: ActionContext) => boolean;
73
+ /**
74
+ * Toggle state for `uiKind: "toggle"` actions (e.g. is the debug panel
75
+ * open, is snap on). Read by the UI to render the pressed/checked state.
76
+ * Pure read of editor / host state.
77
+ */
78
+ readonly checked?: (ctx: ActionContext) => boolean;
79
+ /**
80
+ * Serializable UI metadata so `react-ui` can render the action without
81
+ * the kernel importing React. `iconId` keys an icon registry on the UI
82
+ * side; `uiKind` picks the control shape.
83
+ */
84
+ readonly iconId?: string;
85
+ readonly uiKind?: "button" | "toggle" | "menu-item";
86
+ /**
87
+ * When `true`, the action stays available in read-only / view mode.
88
+ * Default (absent) → hidden in view mode.
89
+ */
90
+ readonly viewMode?: boolean;
91
+ /** The actual work — receives the editor and (optionally) the event. */
92
+ readonly perform: (ctx: ActionContext) => void;
93
+ }
94
+ /** Shared predicate — "element selection is non-empty". */
95
+ export declare const hasSelection: (ctx: ActionContext) => boolean;
96
+ /**
97
+ * Predicate — "anything deletable is selected": elements OR one-or-more
98
+ * links. Links live in a parallel selection set (`editor.selectedLinks`), so
99
+ * a link-only (incl. multi-link) selection must still enable Delete/Backspace.
100
+ * `deleteSelected()` removes every selected link; this just opens the gate.
101
+ */
102
+ export declare const hasSelectionOrLink: (ctx: ActionContext) => boolean;
103
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/actions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC5C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,WAAW,GACX,WAAW,GACX,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,CAAC;AAEZ,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACnC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,aAAa,EAAE,CAAC;IAC3D;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACzE;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD;AAED,2DAA2D;AAC3D,eAAO,MAAM,YAAY,GAAI,KAAK,aAAa,KAAG,OAAwC,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,aAAa,KAAG,OACY,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Shared predicate — "element selection is non-empty". */
2
+ export const hasSelection = (ctx) => ctx.editor.selection.size > 0;
3
+ /**
4
+ * Predicate — "anything deletable is selected": elements OR one-or-more
5
+ * links. Links live in a parallel selection set (`editor.selectedLinks`), so
6
+ * a link-only (incl. multi-link) selection must still enable Delete/Backspace.
7
+ * `deleteSelected()` removes every selected link; this just opens the gate.
8
+ */
9
+ export const hasSelectionOrLink = (ctx) => ctx.editor.selection.size > 0 || ctx.editor.selectedLinks.size > 0;
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/actions/types.ts"],"names":[],"mappings":"AA4GA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAkB,EAAW,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC;AAE3F;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAkB,EAAW,EAAE,CAChE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Tiny requestAnimationFrame loop manager. Used by the editor for
3
+ * animated content (GIFs) that needs a re-render every frame even when
4
+ * the scene reference hasn't changed.
5
+ *
6
+ * Self-terminating: each tick consults `isAnimated()`; when it
7
+ * returns false the loop stops and `start()` is a no-op until
8
+ * called again. SSR-safe — no-op when `requestAnimationFrame` is
9
+ * not on `globalThis`.
10
+ */
11
+ export declare class AnimationTick {
12
+ private frameId;
13
+ private readonly isAnimated;
14
+ private readonly onTick;
15
+ constructor(opts: {
16
+ readonly isAnimated: () => boolean;
17
+ readonly onTick: () => void;
18
+ });
19
+ /** Begin the rAF loop. No-op when already running or when
20
+ * `requestAnimationFrame` is unavailable (Node).
21
+ */
22
+ start(): void;
23
+ /** Cancel any pending frame. Safe to call from dispose paths. */
24
+ stop(): void;
25
+ /** True while the loop is actively scheduled. */
26
+ get running(): boolean;
27
+ }
28
+ //# sourceMappingURL=animation-tick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animation-tick.d.ts","sourceRoot":"","sources":["../src/animation-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,IAAI,EAAE;QAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE;IAKrF;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb,iEAAiE;IACjE,IAAI,IAAI,IAAI;IAMZ,iDAAiD;IACjD,IAAI,OAAO,IAAI,OAAO,CAErB;CACF"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tiny requestAnimationFrame loop manager. Used by the editor for
3
+ * animated content (GIFs) that needs a re-render every frame even when
4
+ * the scene reference hasn't changed.
5
+ *
6
+ * Self-terminating: each tick consults `isAnimated()`; when it
7
+ * returns false the loop stops and `start()` is a no-op until
8
+ * called again. SSR-safe — no-op when `requestAnimationFrame` is
9
+ * not on `globalThis`.
10
+ */
11
+ export class AnimationTick {
12
+ frameId = null;
13
+ isAnimated;
14
+ onTick;
15
+ constructor(opts) {
16
+ this.isAnimated = opts.isAnimated;
17
+ this.onTick = opts.onTick;
18
+ }
19
+ /** Begin the rAF loop. No-op when already running or when
20
+ * `requestAnimationFrame` is unavailable (Node).
21
+ */
22
+ start() {
23
+ if (this.frameId !== null)
24
+ return;
25
+ if (typeof requestAnimationFrame === "undefined")
26
+ return;
27
+ const loop = () => {
28
+ if (!this.isAnimated()) {
29
+ this.frameId = null;
30
+ return;
31
+ }
32
+ this.onTick();
33
+ this.frameId = requestAnimationFrame(loop);
34
+ };
35
+ this.frameId = requestAnimationFrame(loop);
36
+ }
37
+ /** Cancel any pending frame. Safe to call from dispose paths. */
38
+ stop() {
39
+ if (this.frameId === null)
40
+ return;
41
+ if (typeof cancelAnimationFrame !== "undefined")
42
+ cancelAnimationFrame(this.frameId);
43
+ this.frameId = null;
44
+ }
45
+ /** True while the loop is actively scheduled. */
46
+ get running() {
47
+ return this.frameId !== null;
48
+ }
49
+ }
50
+ //# sourceMappingURL=animation-tick.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animation-tick.js","sourceRoot":"","sources":["../src/animation-tick.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,aAAa;IAChB,OAAO,GAAkB,IAAI,CAAC;IACrB,UAAU,CAAgB;IAC1B,MAAM,CAAa;IAEpC,YAAY,IAAyE;QACnF,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,OAAO,qBAAqB,KAAK,WAAW;YAAE,OAAO;QACzD,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,iEAAiE;IACjE,IAAI;QACF,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QAClC,IAAI,OAAO,oBAAoB,KAAK,WAAW;YAAE,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,iDAAiD;IACjD,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import type { LayerId } from "@oh-just-another/types";
2
+ import type { Scene } from "@oh-just-another/scene";
3
+ /**
4
+ * Microtask-scheduled rebalancer for fractional `order` keys. When any
5
+ * shape or edge's order string grows past AUTO_COMPACT_THRESHOLD, a
6
+ * single compaction pass is scheduled per mutation burst so users never
7
+ * see the long-key state.
8
+ *
9
+ * The scheduler keeps a `pending` flag to coalesce bursts into one
10
+ * microtask; `runCheck()` is what the microtask invokes (and what tests
11
+ * call directly when they want determinism).
12
+ */
13
+ export declare class AutoCompactScheduler {
14
+ private pending;
15
+ private readonly getScene;
16
+ private readonly compact;
17
+ constructor(opts: {
18
+ readonly getScene: () => Scene;
19
+ /** Caller-supplied compaction action — usually `editor.compactLayerZOrder(layerId, {recordHistory:false})`. */
20
+ readonly compact: (layerId: LayerId) => void;
21
+ });
22
+ /** Queue a check for the next microtask. Coalesces bursts. */
23
+ schedule(): void;
24
+ /** Run the actual check + compact immediately. Used by tests. */
25
+ runCheck(): void;
26
+ }
27
+ //# sourceMappingURL=auto-compact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-compact.d.ts","sourceRoot":"","sources":["../src/auto-compact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAGpD;;;;;;;;;GASG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;gBAEzC,IAAI,EAAE;QAChB,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC;QAC/B,+GAA+G;QAC/G,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KAC9C;IAKD,8DAA8D;IAC9D,QAAQ,IAAI,IAAI;IAShB,iEAAiE;IACjE,QAAQ,IAAI,IAAI;CAWjB"}
@@ -0,0 +1,46 @@
1
+ import { AUTO_COMPACT_THRESHOLD } from "./constants.js";
2
+ /**
3
+ * Microtask-scheduled rebalancer for fractional `order` keys. When any
4
+ * shape or edge's order string grows past AUTO_COMPACT_THRESHOLD, a
5
+ * single compaction pass is scheduled per mutation burst so users never
6
+ * see the long-key state.
7
+ *
8
+ * The scheduler keeps a `pending` flag to coalesce bursts into one
9
+ * microtask; `runCheck()` is what the microtask invokes (and what tests
10
+ * call directly when they want determinism).
11
+ */
12
+ export class AutoCompactScheduler {
13
+ pending = false;
14
+ getScene;
15
+ compact;
16
+ constructor(opts) {
17
+ this.getScene = opts.getScene;
18
+ this.compact = opts.compact;
19
+ }
20
+ /** Queue a check for the next microtask. Coalesces bursts. */
21
+ schedule() {
22
+ if (this.pending)
23
+ return;
24
+ this.pending = true;
25
+ queueMicrotask(() => {
26
+ this.pending = false;
27
+ this.runCheck();
28
+ });
29
+ }
30
+ /** Run the actual check + compact immediately. Used by tests. */
31
+ runCheck() {
32
+ const scene = this.getScene();
33
+ const layersToCompact = new Set();
34
+ for (const s of scene.elements.values()) {
35
+ if (s.order.length > AUTO_COMPACT_THRESHOLD)
36
+ layersToCompact.add(s.layerId);
37
+ }
38
+ for (const e of scene.links.values()) {
39
+ if (e.order.length > AUTO_COMPACT_THRESHOLD)
40
+ layersToCompact.add(e.layerId);
41
+ }
42
+ for (const lid of layersToCompact)
43
+ this.compact(lid);
44
+ }
45
+ }
46
+ //# sourceMappingURL=auto-compact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-compact.js","sourceRoot":"","sources":["../src/auto-compact.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAoB;IACvB,OAAO,GAAG,KAAK,CAAC;IACP,QAAQ,CAAc;IACtB,OAAO,CAA6B;IAErD,YAAY,IAIX;QACC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,8DAA8D;IAC9D,QAAQ;QACN,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iEAAiE;IACjE,QAAQ;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,eAAe,GAAG,IAAI,GAAG,EAAW,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,sBAAsB;gBAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,sBAAsB;gBAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9E,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,eAAe;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,50 @@
1
+ import type { ElementId } from "@oh-just-another/types";
2
+ import { apply, type Patch, type Scene } from "@oh-just-another/scene";
3
+ /**
4
+ * Microtask-coalesced re-runner for shapes carrying
5
+ * `metadata.autoLayout`. The editor calls `schedule()` from `notify()`;
6
+ * the check walks every auto-layout parent, compares its children-set
7
+ * fingerprint against the previous run, and re-runs the layout when the
8
+ * set changed.
9
+ *
10
+ * Pure position edits inside an existing children set are ignored
11
+ * (fingerprint is sorted-ids only), so a manual nudge of a child
12
+ * isn't snapped back. Add / remove / reparent does trigger.
13
+ */
14
+ export declare class AutoLayoutScheduler {
15
+ private readonly opts;
16
+ private pending;
17
+ private readonly signatures;
18
+ constructor(opts: AutoLayoutSchedulerOptions);
19
+ /** Queue a check for the next microtask. Coalesces bursts. */
20
+ schedule(): void;
21
+ /**
22
+ * Recompute and store the signature for a parent, used after the
23
+ * editor's public `runLayout(parentId)` to prevent the next
24
+ * scheduled check from firing a redundant second run.
25
+ */
26
+ resetSignature(parentId: ElementId): void;
27
+ /** Run the actual check + relayout immediately. Used by tests. */
28
+ runCheck(): void;
29
+ private signatureFor;
30
+ }
31
+ export interface AutoLayoutSchedulerOptions {
32
+ readonly getScene: () => Scene;
33
+ /**
34
+ * Apply the layout patch — implementation knows whether to join
35
+ * a running gesture transaction or push straight to history.
36
+ */
37
+ readonly applyPatch: (patch: Patch) => void;
38
+ /** Editor-side hook to grow the container after the relayout. */
39
+ readonly growContainer: (parentId: ElementId, childId: ElementId) => void;
40
+ /**
41
+ * Called once at the end of a check that produced at least one
42
+ * mutation — editor renders + fires listeners (intentionally NOT
43
+ * `notify()` because that would re-schedule the check and risk
44
+ * a microtask loop).
45
+ */
46
+ readonly onMutated: () => void;
47
+ }
48
+ export type { Patch, Scene };
49
+ export { apply };
50
+ //# sourceMappingURL=auto-layout-scheduler.d.ts.map