@pixerate/editor 0.22.0 → 0.24.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 (188) hide show
  1. package/README.md +175 -2
  2. package/THIRD_PARTY_NOTICES.md +139 -0
  3. package/dist/agent/index.cjs +87 -111
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +70 -211
  6. package/dist/agent/index.d.ts +70 -211
  7. package/dist/agent/index.js +78 -102
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
  10. package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
  11. package/dist/canvas/index.cjs +2075 -86
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.d.cts +608 -186
  14. package/dist/canvas/index.d.ts +608 -186
  15. package/dist/canvas/index.js +2060 -71
  16. package/dist/canvas/index.js.map +1 -1
  17. package/dist/chunk-26BVFKHU.cjs +453 -0
  18. package/dist/chunk-26BVFKHU.cjs.map +1 -0
  19. package/dist/chunk-2H7ZHPEJ.js +762 -0
  20. package/dist/chunk-2H7ZHPEJ.js.map +1 -0
  21. package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
  22. package/dist/chunk-3CN2WD3X.js.map +1 -0
  23. package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
  24. package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
  25. package/dist/chunk-5S3Z4PPF.js.map +1 -0
  26. package/dist/chunk-664LQYFJ.js +115 -0
  27. package/dist/chunk-664LQYFJ.js.map +1 -0
  28. package/dist/chunk-7DWMJCAT.js +453 -0
  29. package/dist/chunk-7DWMJCAT.js.map +1 -0
  30. package/dist/chunk-ARRTP33I.js +101 -0
  31. package/dist/chunk-ARRTP33I.js.map +1 -0
  32. package/dist/chunk-CPOKFNBG.cjs +101 -0
  33. package/dist/chunk-CPOKFNBG.cjs.map +1 -0
  34. package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
  35. package/dist/chunk-D27YFC46.cjs.map +1 -0
  36. package/dist/chunk-D6FH2YAX.cjs +131 -0
  37. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  38. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  39. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  40. package/dist/chunk-FODNLX5N.js +749 -0
  41. package/dist/chunk-FODNLX5N.js.map +1 -0
  42. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  43. package/dist/chunk-FPENXAEJ.js.map +1 -0
  44. package/dist/chunk-G2I4QJOT.cjs +762 -0
  45. package/dist/chunk-G2I4QJOT.cjs.map +1 -0
  46. package/dist/chunk-HLIMTC6N.js +1 -0
  47. package/dist/chunk-HLIMTC6N.js.map +1 -0
  48. package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
  49. package/dist/chunk-ITBSKI26.js.map +1 -0
  50. package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
  51. package/dist/chunk-MA2F5KHF.cjs.map +1 -0
  52. package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
  53. package/dist/chunk-MCSJFWAO.cjs.map +1 -0
  54. package/dist/chunk-MKA3AHLV.cjs +256 -0
  55. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  56. package/dist/chunk-OLZJXL4R.cjs +749 -0
  57. package/dist/chunk-OLZJXL4R.cjs.map +1 -0
  58. package/dist/chunk-OXSV64JX.js +689 -0
  59. package/dist/chunk-OXSV64JX.js.map +1 -0
  60. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  61. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  62. package/dist/chunk-P6ODPZ4W.js +256 -0
  63. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  64. package/dist/chunk-PGNZKFQS.js +685 -0
  65. package/dist/chunk-PGNZKFQS.js.map +1 -0
  66. package/dist/chunk-PL4JB3BF.cjs +1 -0
  67. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  68. package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
  69. package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
  70. package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
  71. package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
  72. package/dist/chunk-TNNJXF46.cjs +115 -0
  73. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  74. package/dist/chunk-U5HZSFCS.js +131 -0
  75. package/dist/chunk-U5HZSFCS.js.map +1 -0
  76. package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
  77. package/dist/chunk-U5KWZU4X.js.map +1 -0
  78. package/dist/chunk-XNORY7M6.cjs +685 -0
  79. package/dist/chunk-XNORY7M6.cjs.map +1 -0
  80. package/dist/commands/index.cjs +860 -0
  81. package/dist/commands/index.cjs.map +1 -0
  82. package/dist/commands/index.d.cts +244 -0
  83. package/dist/commands/index.d.ts +244 -0
  84. package/dist/commands/index.js +860 -0
  85. package/dist/commands/index.js.map +1 -0
  86. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  87. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  88. package/dist/compare/index.cjs +15 -0
  89. package/dist/compare/index.cjs.map +1 -0
  90. package/dist/compare/index.d.cts +58 -0
  91. package/dist/compare/index.d.ts +58 -0
  92. package/dist/compare/index.js +15 -0
  93. package/dist/compare/index.js.map +1 -0
  94. package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
  95. package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
  96. package/dist/documents-BQjzJNe2.d.ts +76 -0
  97. package/dist/documents-KRTPhvst.d.cts +76 -0
  98. package/dist/extensions/index.cjs +53 -5
  99. package/dist/extensions/index.cjs.map +1 -1
  100. package/dist/extensions/index.d.cts +7 -3
  101. package/dist/extensions/index.d.ts +7 -3
  102. package/dist/extensions/index.js +52 -4
  103. package/dist/generation/index.cjs +1373 -0
  104. package/dist/generation/index.cjs.map +1 -0
  105. package/dist/generation/index.d.cts +554 -0
  106. package/dist/generation/index.d.ts +554 -0
  107. package/dist/generation/index.js +1373 -0
  108. package/dist/generation/index.js.map +1 -0
  109. package/dist/grammar/index.cjs +48 -3
  110. package/dist/grammar/index.cjs.map +1 -1
  111. package/dist/grammar/index.d.cts +322 -100
  112. package/dist/grammar/index.d.ts +322 -100
  113. package/dist/grammar/index.js +54 -9
  114. package/dist/history/index.cjs +12 -2
  115. package/dist/history/index.cjs.map +1 -1
  116. package/dist/history/index.d.cts +211 -7
  117. package/dist/history/index.d.ts +211 -7
  118. package/dist/history/index.js +13 -3
  119. package/dist/image-editor/index.cjs +2244 -38
  120. package/dist/image-editor/index.cjs.map +1 -1
  121. package/dist/image-editor/index.d.cts +742 -4
  122. package/dist/image-editor/index.d.ts +742 -4
  123. package/dist/image-editor/index.js +2222 -16
  124. package/dist/image-editor/index.js.map +1 -1
  125. package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
  126. package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
  127. package/dist/index.cjs +193 -98
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +17 -48
  130. package/dist/index.d.ts +17 -48
  131. package/dist/index.js +198 -103
  132. package/dist/index.js.map +1 -1
  133. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  134. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  135. package/dist/persistence/index.cjs +676 -0
  136. package/dist/persistence/index.cjs.map +1 -0
  137. package/dist/persistence/index.d.cts +347 -0
  138. package/dist/persistence/index.d.ts +347 -0
  139. package/dist/persistence/index.js +676 -0
  140. package/dist/persistence/index.js.map +1 -0
  141. package/dist/registry-BDe-vlv3.d.ts +171 -0
  142. package/dist/registry-D32LpIc4.d.cts +354 -0
  143. package/dist/registry-DnzpgY27.d.cts +171 -0
  144. package/dist/registry-evKTP4ls.d.ts +354 -0
  145. package/dist/schema-DYDGdYL9.d.cts +41 -0
  146. package/dist/schema-DYDGdYL9.d.ts +41 -0
  147. package/dist/serializers/index.cjs +4 -3
  148. package/dist/serializers/index.cjs.map +1 -1
  149. package/dist/serializers/index.d.cts +2 -1
  150. package/dist/serializers/index.d.ts +2 -1
  151. package/dist/serializers/index.js +3 -2
  152. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  153. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  154. package/dist/spreadsheet/index.cjs +53 -3
  155. package/dist/spreadsheet/index.cjs.map +1 -1
  156. package/dist/spreadsheet/index.d.cts +247 -18
  157. package/dist/spreadsheet/index.d.ts +247 -18
  158. package/dist/spreadsheet/index.js +58 -8
  159. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  160. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  161. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
  162. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
  163. package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
  164. package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
  165. package/dist/types-GT5a5YAX.d.cts +86 -0
  166. package/dist/types-GT5a5YAX.d.ts +86 -0
  167. package/package.json +23 -2
  168. package/dist/chunk-2TWGLC5K.cjs +0 -262
  169. package/dist/chunk-2TWGLC5K.cjs.map +0 -1
  170. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  171. package/dist/chunk-6M3BCTBD.cjs.map +0 -1
  172. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  173. package/dist/chunk-AXV2ZW34.cjs +0 -118
  174. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  175. package/dist/chunk-B2ZRLN4S.js +0 -118
  176. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  177. package/dist/chunk-DENSCKFC.js.map +0 -1
  178. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  179. package/dist/chunk-KAADNAPF.js +0 -262
  180. package/dist/chunk-KAADNAPF.js.map +0 -1
  181. package/dist/chunk-MJZBNWG7.js.map +0 -1
  182. package/dist/chunk-OYXSKQFR.js.map +0 -1
  183. package/dist/chunk-QGHGJ56O.js.map +0 -1
  184. package/dist/chunk-RU52I7QO.js.map +0 -1
  185. package/dist/chunk-SAZRENEZ.cjs.map +0 -1
  186. package/dist/types-BZgcDsC5.d.cts +0 -42
  187. package/dist/types-BZgcDsC5.d.ts +0 -42
  188. /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
@@ -0,0 +1,762 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;// src/history/HistoryManager.ts
2
+ var sumBytes = (parts) => parts.reduce((n, p) => n + p.bytes, 0);
3
+ function undoParts(parts) {
4
+ for (let i = parts.length - 1; i >= 0; i--) parts[i].undo();
5
+ }
6
+ function redoParts(parts) {
7
+ for (let i = 0; i < parts.length; i++) parts[i].redo();
8
+ }
9
+ var HistoryManager = (_class = class {
10
+ __init() {this.undoStack = []}
11
+ __init2() {this.redoStack = []}
12
+
13
+
14
+
15
+ __init3() {this.isApplying = false}
16
+ __init4() {this.batchDepth = 0}
17
+ __init5() {this.currentBatch = null}
18
+ __init6() {this.listeners = /* @__PURE__ */ new Set()}
19
+
20
+
21
+ __init7() {this.cachedEntries = null}
22
+ __init8() {this.nextId = 1}
23
+ __init9() {this.revision = 0}
24
+ /** Bumped by `clear()`; marks from an earlier epoch are stale. */
25
+ __init10() {this.epoch = 0}
26
+ /** Number of steps dropped from the bottom (depth / byte budget) so far. */
27
+ __init11() {this.dropped = 0}
28
+ /** Key of the gesture that the top step belongs to (see `executeMerged`). */
29
+ __init12() {this.gestureKey = null}
30
+ __init13() {this.gestureEntryId = null}
31
+ /** Key every merged execute takes while a gesture is held (`beginGesture`). */
32
+ __init14() {this.heldKey = null}
33
+ constructor(options = {}) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);
34
+ this.maxDepth = _nullishCoalesce(options.maxDepth, () => ( 50));
35
+ this.maxBytes = Math.max(0, _nullishCoalesce(options.maxBytes, () => ( 0)));
36
+ this.sizeOf = options.sizeOf;
37
+ this.onStateChange = options.onStateChange;
38
+ this.cachedState = this.computeState();
39
+ }
40
+ computeState() {
41
+ const canUndo = this.undoStack.length > 0;
42
+ const canRedo = this.redoStack.length > 0;
43
+ return {
44
+ canUndo,
45
+ canRedo,
46
+ undoName: canUndo ? this.undoStack[this.undoStack.length - 1].name : "",
47
+ redoName: canRedo ? this.redoStack[this.redoStack.length - 1].name : "",
48
+ undoCount: this.undoStack.length,
49
+ redoCount: this.redoStack.length
50
+ };
51
+ }
52
+ /**
53
+ * Returns a snapshot of the current history state.
54
+ * Cached to maintain referential equality across renders for useSyncExternalStore.
55
+ */
56
+ getState() {
57
+ return this.cachedState;
58
+ }
59
+ /**
60
+ * Monotonic counter bumped on every history change (execute, merge, undo,
61
+ * redo, revert, clear). Useful as a cache key, e.g. for `DirtyTracker`.
62
+ */
63
+ getRevision() {
64
+ return this.revision;
65
+ }
66
+ notify() {
67
+ this.revision++;
68
+ this.cachedState = this.computeState();
69
+ this.cachedEntries = null;
70
+ const state = this.cachedState;
71
+ _optionalChain([this, 'access', _ => _.onStateChange, 'optionalCall', _2 => _2(state)]);
72
+ this.listeners.forEach((listener) => {
73
+ try {
74
+ listener(state);
75
+ } catch (err) {
76
+ console.error("[HistoryManager] Error in state listener:", err);
77
+ }
78
+ });
79
+ }
80
+ measure(command) {
81
+ const n = this.sizeOf ? this.sizeOf(command) : _nullishCoalesce(command.byteSize, () => ( 0));
82
+ return Number.isFinite(n) && n > 0 ? n : 0;
83
+ }
84
+ makeEntry(command, timestamp) {
85
+ const part = {
86
+ undo: command.undo,
87
+ redo: _nullishCoalesce(command.redo, () => ( command.execute)),
88
+ tag: command.tag,
89
+ bytes: this.measure(command)
90
+ };
91
+ return {
92
+ id: this.nextId++,
93
+ name: command.name,
94
+ tag: command.tag,
95
+ timestamp,
96
+ parts: [part],
97
+ bytes: part.bytes
98
+ };
99
+ }
100
+ /** Pushes a step, clears redo and applies the depth and byte limits. */
101
+ record(entry) {
102
+ this.undoStack.push(entry);
103
+ while (this.undoStack.length > this.maxDepth) {
104
+ this.undoStack.shift();
105
+ this.dropped++;
106
+ }
107
+ this.redoStack = [];
108
+ this.trim();
109
+ }
110
+ /**
111
+ * Keeps the stacks within `maxBytes`: the oldest undo steps that do not fit
112
+ * are dropped, but the most recent undo step is always kept.
113
+ */
114
+ trim() {
115
+ if (this.maxBytes <= 0 || this.undoStack.length <= 1) return;
116
+ let total = this.getByteSize();
117
+ while (total > this.maxBytes && this.undoStack.length > 1) {
118
+ total -= this.undoStack.shift().bytes;
119
+ this.dropped++;
120
+ }
121
+ }
122
+ /** Approximate bytes held by the undo and redo stacks (see `maxBytes`). */
123
+ getByteSize() {
124
+ let total = 0;
125
+ for (const e of this.undoStack) total += e.bytes;
126
+ for (const e of this.redoStack) total += e.bytes;
127
+ return total;
128
+ }
129
+ resetGesture() {
130
+ this.gestureKey = null;
131
+ this.gestureEntryId = null;
132
+ }
133
+ /**
134
+ * Executes a command and pushes it onto the undo stack.
135
+ * Clears the redo stack and ends the current gesture.
136
+ */
137
+ execute(command) {
138
+ if (this.isApplying) {
139
+ command.execute();
140
+ return;
141
+ }
142
+ if (this.batchDepth > 0 && this.currentBatch) {
143
+ command.execute();
144
+ this.currentBatch.push({
145
+ ...command,
146
+ timestamp: _nullishCoalesce(command.timestamp, () => ( Date.now()))
147
+ });
148
+ return;
149
+ }
150
+ command.execute();
151
+ this.record(this.makeEntry(command, _nullishCoalesce(command.timestamp, () => ( Date.now()))));
152
+ this.resetGesture();
153
+ this.notify();
154
+ }
155
+ /**
156
+ * Executes a command as part of a continuous interaction (slider scrub, node drag).
157
+ *
158
+ * - With `options.key` (or while a gesture is held with `beginGesture`):
159
+ * consecutive merged executes with the same key fold into one undo step,
160
+ * however long the gesture lasts. Recording any other command, undo, redo,
161
+ * `revertTo`, `goTo`, `clear` or `endGesture()` ends the gesture.
162
+ * - Without a key: coalesces with the previous command if they share the same
163
+ * `tag` and occurred within `windowMs` (default: 300ms).
164
+ */
165
+ executeMerged(command, options = {}) {
166
+ const now = Date.now();
167
+ const key = _nullishCoalesce(this.heldKey, () => ( options.key));
168
+ if (key !== void 0) {
169
+ if (this.isApplying || this.batchDepth > 0) {
170
+ this.execute({ ...command, timestamp: now });
171
+ return;
172
+ }
173
+ const top = this.undoStack[this.undoStack.length - 1];
174
+ if (top && this.gestureKey === key && this.gestureEntryId === top.id) {
175
+ command.execute();
176
+ const redo = _nullishCoalesce(command.redo, () => ( command.execute));
177
+ const bytes = this.measure(command);
178
+ const last = top.parts[top.parts.length - 1];
179
+ if (last.tag !== void 0 && last.tag === command.tag) {
180
+ top.parts[top.parts.length - 1] = { undo: last.undo, redo, tag: last.tag, bytes };
181
+ } else {
182
+ top.parts.push({ undo: command.undo, redo, tag: command.tag, bytes });
183
+ }
184
+ top.bytes = sumBytes(top.parts);
185
+ top.timestamp = now;
186
+ this.redoStack = [];
187
+ this.trim();
188
+ this.notify();
189
+ return;
190
+ }
191
+ this.execute({ ...command, timestamp: now });
192
+ const pushed = this.undoStack[this.undoStack.length - 1];
193
+ if (pushed) {
194
+ this.gestureKey = key;
195
+ this.gestureEntryId = pushed.id;
196
+ }
197
+ return;
198
+ }
199
+ const windowMs = _nullishCoalesce(options.windowMs, () => ( 300));
200
+ if (command.tag && this.undoStack.length > 0 && this.batchDepth === 0 && !this.isApplying) {
201
+ const top = this.undoStack[this.undoStack.length - 1];
202
+ const elapsed = top.timestamp ? now - top.timestamp : Infinity;
203
+ if (top.tag === command.tag && elapsed <= windowMs) {
204
+ command.execute();
205
+ const initialParts = top.parts;
206
+ const bytes = this.measure(command);
207
+ this.undoStack[this.undoStack.length - 1] = {
208
+ id: top.id,
209
+ name: command.name,
210
+ tag: command.tag,
211
+ timestamp: now,
212
+ parts: [
213
+ {
214
+ undo: () => undoParts(initialParts),
215
+ redo: _nullishCoalesce(command.redo, () => ( command.execute)),
216
+ tag: command.tag,
217
+ bytes
218
+ }
219
+ ],
220
+ bytes
221
+ };
222
+ this.redoStack = [];
223
+ this.resetGesture();
224
+ this.trim();
225
+ this.notify();
226
+ return;
227
+ }
228
+ }
229
+ this.execute({ ...command, timestamp: now });
230
+ }
231
+ /**
232
+ * Holds a gesture: until `endGesture()`, every `executeMerged` call (whatever
233
+ * its own key or tag) joins one undo step, e.g. an automation pass and
234
+ * whatever is dragged during it. Commands recorded with `execute` still end
235
+ * the current step; the next merged execute then starts a new one.
236
+ */
237
+ beginGesture(key) {
238
+ if (this.heldKey === key) return;
239
+ this.heldKey = key;
240
+ this.resetGesture();
241
+ }
242
+ /** Ends the current (keyed or held) gesture; the next merged execute starts a new step. */
243
+ endGesture() {
244
+ this.heldKey = null;
245
+ this.resetGesture();
246
+ }
247
+ /** The key of the gesture currently merging into the top step, if any. */
248
+ getGestureKey() {
249
+ return _nullishCoalesce(this.heldKey, () => ( this.gestureKey));
250
+ }
251
+ /**
252
+ * Batches multiple commands executed within `fn` into a single atomic undo entry.
253
+ */
254
+ batch(name, fn) {
255
+ if (this.isApplying) {
256
+ fn();
257
+ return;
258
+ }
259
+ if (this.batchDepth > 0) {
260
+ fn();
261
+ return;
262
+ }
263
+ this.batchDepth++;
264
+ this.currentBatch = [];
265
+ try {
266
+ fn();
267
+ } catch (error) {
268
+ const executed = _nullishCoalesce(this.currentBatch, () => ( []));
269
+ this.batchDepth--;
270
+ this.currentBatch = null;
271
+ this.rollback(executed);
272
+ throw error;
273
+ }
274
+ this.batchDepth--;
275
+ const batchCommands = _nullishCoalesce(this.currentBatch, () => ( []));
276
+ this.currentBatch = null;
277
+ if (batchCommands.length > 0) {
278
+ const run = () => {
279
+ for (let i = 0; i < batchCommands.length; i++) {
280
+ (_nullishCoalesce(batchCommands[i].redo, () => ( batchCommands[i].execute)))();
281
+ }
282
+ };
283
+ const bytes = batchCommands.reduce((n, c) => n + this.measure(c), 0);
284
+ this.record({
285
+ id: this.nextId++,
286
+ name,
287
+ timestamp: Date.now(),
288
+ parts: [
289
+ {
290
+ undo: () => {
291
+ for (let i = batchCommands.length - 1; i >= 0; i--) {
292
+ batchCommands[i].undo();
293
+ }
294
+ },
295
+ redo: run,
296
+ bytes
297
+ }
298
+ ],
299
+ bytes
300
+ });
301
+ this.resetGesture();
302
+ this.notify();
303
+ }
304
+ }
305
+ /**
306
+ * Reverts already-executed commands of an aborted batch, newest first.
307
+ * Commands executed by the undo closures themselves are not recorded.
308
+ */
309
+ rollback(commands) {
310
+ const wasApplying = this.isApplying;
311
+ this.isApplying = true;
312
+ try {
313
+ for (let i = commands.length - 1; i >= 0; i--) {
314
+ try {
315
+ commands[i].undo();
316
+ } catch (err) {
317
+ console.error(
318
+ `[HistoryManager] Error rolling back "${commands[i].name}" of a failed batch:`,
319
+ err
320
+ );
321
+ }
322
+ }
323
+ } finally {
324
+ this.isApplying = wasApplying;
325
+ }
326
+ }
327
+ /** Undoes the top step (caller holds `isApplying`). Throws leave it on the stack. */
328
+ undoOne() {
329
+ const entry = this.undoStack[this.undoStack.length - 1];
330
+ undoParts(entry.parts);
331
+ this.undoStack.pop();
332
+ this.redoStack.push(entry);
333
+ }
334
+ redoOne() {
335
+ const entry = this.redoStack[this.redoStack.length - 1];
336
+ redoParts(entry.parts);
337
+ this.redoStack.pop();
338
+ this.undoStack.push(entry);
339
+ }
340
+ /**
341
+ * Reverts the most recent command on the undo stack.
342
+ * Returns true if a command was undone, false otherwise.
343
+ */
344
+ undo() {
345
+ if (this.undoStack.length === 0 || this.isApplying || this.batchDepth > 0) {
346
+ return false;
347
+ }
348
+ this.isApplying = true;
349
+ this.resetGesture();
350
+ try {
351
+ this.undoOne();
352
+ return true;
353
+ } finally {
354
+ this.isApplying = false;
355
+ this.notify();
356
+ }
357
+ }
358
+ /**
359
+ * Re-applies the most recent undone command on the redo stack.
360
+ * Returns true if a command was redone, false otherwise.
361
+ */
362
+ redo() {
363
+ if (this.redoStack.length === 0 || this.isApplying || this.batchDepth > 0) {
364
+ return false;
365
+ }
366
+ this.isApplying = true;
367
+ this.resetGesture();
368
+ try {
369
+ this.redoOne();
370
+ return true;
371
+ } finally {
372
+ this.isApplying = false;
373
+ this.notify();
374
+ }
375
+ }
376
+ /**
377
+ * Captures the current point in the history (its top step), for `revertTo`.
378
+ */
379
+ mark() {
380
+ const top = this.undoStack[this.undoStack.length - 1];
381
+ return {
382
+ topId: top ? top.id : null,
383
+ nextId: this.nextId,
384
+ epoch: this.epoch,
385
+ dropped: this.dropped
386
+ };
387
+ }
388
+ /** Whether `mark` still points into the undo history. */
389
+ isMarkValid(mark) {
390
+ if (mark.epoch !== this.epoch) return false;
391
+ if (mark.topId === null) return mark.dropped === this.dropped;
392
+ return this.undoStack.some((e) => e.id === mark.topId);
393
+ }
394
+ /**
395
+ * Takes back every step made since `mark` (merged or not), e.g. to cancel a
396
+ * drag with Escape: the state returns to how it was at `mark`, and neither
397
+ * undo nor redo keeps the reverted steps. Does nothing when `mark` has left
398
+ * the history (undone past, dropped by the limits, or cleared).
399
+ * Returns how many steps were taken back.
400
+ */
401
+ revertTo(mark) {
402
+ if (this.isApplying || this.batchDepth > 0 || !this.isMarkValid(mark)) {
403
+ return 0;
404
+ }
405
+ const redoBefore = this.redoStack.length;
406
+ let n = 0;
407
+ this.isApplying = true;
408
+ this.resetGesture();
409
+ try {
410
+ while (this.undoStack.length > 0) {
411
+ const top = this.undoStack[this.undoStack.length - 1];
412
+ if (top.id === mark.topId) break;
413
+ undoParts(top.parts);
414
+ this.undoStack.pop();
415
+ n++;
416
+ }
417
+ } finally {
418
+ this.isApplying = false;
419
+ this.redoStack = n > 0 ? [] : this.redoStack.filter((e) => e.id < mark.nextId);
420
+ if (n > 0 || this.redoStack.length !== redoBefore) this.notify();
421
+ }
422
+ return n;
423
+ }
424
+ /**
425
+ * Lists the steps for a History panel: undo steps oldest first, then redo
426
+ * steps (the next redo first). Cached until the history changes.
427
+ */
428
+ getEntries() {
429
+ if (this.cachedEntries) return this.cachedEntries;
430
+ const u = this.undoStack.length;
431
+ const entries = this.undoStack.map((e, index) => ({
432
+ id: e.id,
433
+ label: e.name,
434
+ index,
435
+ current: index === u - 1,
436
+ undone: false
437
+ }));
438
+ for (let i = this.redoStack.length - 1; i >= 0; i--) {
439
+ const e = this.redoStack[i];
440
+ entries.push({
441
+ id: e.id,
442
+ label: e.name,
443
+ index: entries.length,
444
+ current: false,
445
+ undone: true
446
+ });
447
+ }
448
+ this.cachedEntries = entries;
449
+ return entries;
450
+ }
451
+ /**
452
+ * Undoes or redoes until the step at `index` (from `getEntries`) is the
453
+ * current one; `-1` undoes everything. Returns how many steps were applied.
454
+ */
455
+ goTo(index) {
456
+ if (this.isApplying || this.batchDepth > 0) return 0;
457
+ const total = this.undoStack.length + this.redoStack.length;
458
+ const target = Math.max(0, Math.min(total, Math.floor(index) + 1));
459
+ if (target === this.undoStack.length) return 0;
460
+ let n = 0;
461
+ this.isApplying = true;
462
+ this.resetGesture();
463
+ try {
464
+ while (this.undoStack.length > target) {
465
+ this.undoOne();
466
+ n++;
467
+ }
468
+ while (this.undoStack.length < target) {
469
+ this.redoOne();
470
+ n++;
471
+ }
472
+ } finally {
473
+ this.isApplying = false;
474
+ this.notify();
475
+ }
476
+ return n;
477
+ }
478
+ /**
479
+ * Clears both undo and redo stacks.
480
+ */
481
+ clear() {
482
+ this.undoStack = [];
483
+ this.redoStack = [];
484
+ this.epoch++;
485
+ this.resetGesture();
486
+ this.notify();
487
+ }
488
+ /**
489
+ * Subscribes to history state changes.
490
+ * The listener is immediately invoked with the current state.
491
+ * Returns an unsubscribe function.
492
+ */
493
+ subscribe(listener) {
494
+ this.listeners.add(listener);
495
+ listener(this.getState());
496
+ return () => {
497
+ this.listeners.delete(listener);
498
+ };
499
+ }
500
+ /**
501
+ * Utility helper to create a Command from before/after state snapshots.
502
+ */
503
+ static createSnapshotCommand(options) {
504
+ return {
505
+ name: options.name,
506
+ tag: options.tag,
507
+ execute: () => options.apply(options.after),
508
+ undo: () => options.apply(options.before),
509
+ redo: () => options.apply(options.after)
510
+ };
511
+ }
512
+ }, _class);
513
+ function createHistoryManager(options = {}) {
514
+ return new HistoryManager(options);
515
+ }
516
+
517
+ // src/dirty.ts
518
+ function defaultIsEqual(a, b) {
519
+ if (Object.is(a, b)) return true;
520
+ if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
521
+ return false;
522
+ }
523
+ if (Array.isArray(a) && Array.isArray(b)) {
524
+ if (a.length !== b.length) return false;
525
+ for (let i = 0; i < a.length; i++) {
526
+ if (!defaultIsEqual(a[i], b[i])) return false;
527
+ }
528
+ return true;
529
+ }
530
+ if (Array.isArray(a) !== Array.isArray(b)) {
531
+ return false;
532
+ }
533
+ const keysA = Object.keys(a);
534
+ const keysB = Object.keys(b);
535
+ if (keysA.length !== keysB.length) return false;
536
+ for (const key of keysA) {
537
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
538
+ if (!defaultIsEqual(a[key], b[key])) return false;
539
+ }
540
+ return true;
541
+ }
542
+ var isThenable = (value) => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
543
+ var DirtyTracker = (_class2 = class {
544
+
545
+
546
+
547
+ __init15() {this.normalizedBaseline = null}
548
+ /** `isDirty` result as of a revision (and baseline version). */
549
+ __init16() {this.cache = null}
550
+ __init17() {this.baselineVersion = 0}
551
+ __init18() {this.suppressDepth = 0}
552
+ __init19() {this.hasValue = false}
553
+
554
+ __init20() {this.dirtyState = false}
555
+ __init21() {this.listeners = /* @__PURE__ */ new Set()}
556
+ constructor(initialBaseline, options) {;_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this);_class2.prototype.__init17.call(this);_class2.prototype.__init18.call(this);_class2.prototype.__init19.call(this);_class2.prototype.__init20.call(this);_class2.prototype.__init21.call(this);
557
+ this.baseline = initialBaseline;
558
+ if (typeof options === "function") {
559
+ this.isEqual = options;
560
+ } else {
561
+ this.isEqual = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _3 => _3.isEqual]), () => ( defaultIsEqual));
562
+ this.normalize = _optionalChain([options, 'optionalAccess', _4 => _4.normalize]);
563
+ }
564
+ }
565
+ /**
566
+ * Returns current baseline value.
567
+ */
568
+ getBaseline() {
569
+ return this.baseline;
570
+ }
571
+ changeBaseline(value) {
572
+ this.baseline = value;
573
+ this.baselineVersion++;
574
+ this.normalizedBaseline = null;
575
+ this.cache = null;
576
+ }
577
+ /**
578
+ * Sets a new baseline value without triggering any dirty state.
579
+ * If a value is being tracked (`update`), `dirty` is recomputed against it.
580
+ */
581
+ setBaseline(value) {
582
+ this.changeBaseline(value);
583
+ if (this.hasValue) {
584
+ this.setDirtyState(this.compare(this.lastValue));
585
+ }
586
+ }
587
+ compare(current) {
588
+ if (!this.normalize) return !this.isEqual(this.baseline, current);
589
+ this.normalizedBaseline ??= { value: this.normalize(this.baseline) };
590
+ return !this.isEqual(
591
+ this.normalizedBaseline.value,
592
+ this.normalize(current)
593
+ );
594
+ }
595
+ /**
596
+ * Checks whether the given value differs from the baseline.
597
+ * With a `revision` (e.g. `HistoryManager.getRevision()`), the result is
598
+ * cached and reused for the same revision until the baseline changes.
599
+ */
600
+ isDirty(current, revision) {
601
+ if (revision === void 0) return this.compare(current);
602
+ const cache = this.cache;
603
+ if (cache && cache.revision === revision && cache.baseline === this.baselineVersion) {
604
+ return cache.dirty;
605
+ }
606
+ const dirty = this.compare(current);
607
+ this.cache = { revision, baseline: this.baselineVersion, dirty };
608
+ return dirty;
609
+ }
610
+ /**
611
+ * Resets the baseline to the provided current value (or clears dirty status).
612
+ */
613
+ reset(current) {
614
+ this.changeBaseline(current);
615
+ this.hasValue = true;
616
+ this.lastValue = current;
617
+ this.setDirtyState(false);
618
+ }
619
+ /**
620
+ * Records the current value and returns whether it is dirty. While
621
+ * suppressed (`suppress`), the value becomes the new baseline instead, so
622
+ * programmatic loads and restores never mark the document dirty.
623
+ */
624
+ update(current, revision) {
625
+ this.hasValue = true;
626
+ this.lastValue = current;
627
+ if (this.suppressDepth > 0) {
628
+ this.changeBaseline(current);
629
+ this.setDirtyState(false);
630
+ return false;
631
+ }
632
+ const dirty = this.isDirty(current, revision);
633
+ this.setDirtyState(dirty);
634
+ return dirty;
635
+ }
636
+ /** Whether the last value passed to `update` differs from the baseline. */
637
+ get dirty() {
638
+ return this.dirtyState;
639
+ }
640
+ /** Whether `suppress` is currently running. */
641
+ get suppressed() {
642
+ return this.suppressDepth > 0;
643
+ }
644
+ /**
645
+ * Marks the document saved: `current` (default: the last value passed to
646
+ * `update`) becomes the baseline.
647
+ */
648
+ markClean(...args) {
649
+ if (args.length > 0) {
650
+ this.reset(args[0]);
651
+ } else if (this.hasValue) {
652
+ this.reset(this.lastValue);
653
+ } else {
654
+ this.setDirtyState(false);
655
+ }
656
+ }
657
+ /**
658
+ * Runs `fn` without marking anything dirty: every `update` during it
659
+ * rebaselines. Re-entrant (depth counter). If `fn` returns a promise, the
660
+ * suppression lasts until it settles.
661
+ */
662
+ suppress(fn) {
663
+ this.suppressDepth++;
664
+ let result;
665
+ try {
666
+ result = fn();
667
+ } catch (error) {
668
+ this.suppressDepth--;
669
+ throw error;
670
+ }
671
+ if (isThenable(result)) {
672
+ const release = () => {
673
+ this.suppressDepth--;
674
+ };
675
+ return result.then(
676
+ (value) => {
677
+ release();
678
+ return value;
679
+ },
680
+ (error) => {
681
+ release();
682
+ throw error;
683
+ }
684
+ );
685
+ }
686
+ this.suppressDepth--;
687
+ return result;
688
+ }
689
+ /**
690
+ * Subscribes to changes of `dirty`. The listener is called immediately with
691
+ * the current value. Returns an unsubscribe function.
692
+ */
693
+ subscribe(listener) {
694
+ this.listeners.add(listener);
695
+ listener(this.dirtyState);
696
+ return () => {
697
+ this.listeners.delete(listener);
698
+ };
699
+ }
700
+ setDirtyState(dirty) {
701
+ if (dirty === this.dirtyState) return;
702
+ this.dirtyState = dirty;
703
+ this.listeners.forEach((listener) => {
704
+ try {
705
+ listener(dirty);
706
+ } catch (err) {
707
+ console.error("[DirtyTracker] Error in listener:", err);
708
+ }
709
+ });
710
+ }
711
+ }, _class2);
712
+ function createDirtyTracker(initialBaseline, options) {
713
+ return new DirtyTracker(initialBaseline, options);
714
+ }
715
+ function withoutDirty(tracker, fn) {
716
+ return tracker.suppress(fn);
717
+ }
718
+ function bindDirtyToHistory(options) {
719
+ const { history, getValue } = options;
720
+ const tracker = _nullishCoalesce(options.tracker, () => ( new DirtyTracker(getValue(), {
721
+ isEqual: options.isEqual,
722
+ normalize: options.normalize
723
+ })));
724
+ const unsubscribe = history.subscribe(() => {
725
+ tracker.update(getValue(), _optionalChain([history, 'access', _5 => _5.getRevision, 'optionalCall', _6 => _6()]));
726
+ });
727
+ return {
728
+ tracker,
729
+ get dirty() {
730
+ return tracker.dirty;
731
+ },
732
+ markClean: () => tracker.markClean(getValue()),
733
+ suppress: (fn) => {
734
+ const rebaseline = () => {
735
+ if (!tracker.suppressed) tracker.reset(getValue());
736
+ };
737
+ const result = tracker.suppress(fn);
738
+ if (isThenable(result)) {
739
+ return result.then((value) => {
740
+ rebaseline();
741
+ return value;
742
+ });
743
+ }
744
+ rebaseline();
745
+ return result;
746
+ },
747
+ refresh: () => tracker.update(getValue()),
748
+ subscribe: (listener) => tracker.subscribe(listener),
749
+ destroy: unsubscribe
750
+ };
751
+ }
752
+
753
+
754
+
755
+
756
+
757
+
758
+
759
+
760
+
761
+ exports.defaultIsEqual = defaultIsEqual; exports.DirtyTracker = DirtyTracker; exports.createDirtyTracker = createDirtyTracker; exports.withoutDirty = withoutDirty; exports.bindDirtyToHistory = bindDirtyToHistory; exports.HistoryManager = HistoryManager; exports.createHistoryManager = createHistoryManager;
762
+ //# sourceMappingURL=chunk-G2I4QJOT.cjs.map