@panphora/clayjs 0.2.1 → 0.4.1

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 (45) hide show
  1. package/README.md +103 -7
  2. package/clay-data.js +1 -1
  3. package/clay-internals.js +19 -0
  4. package/clay.js +19 -3
  5. package/package.json +5 -2
  6. package/sap.js +5 -5
  7. package/src/attrs/onaftersave.js +12 -1
  8. package/src/attrs/refetch-on-save.js +4 -4
  9. package/src/attrs/save-freeze.js +47 -19
  10. package/src/core/admin-contenteditable.js +2 -2
  11. package/src/core/admin-inputs.js +2 -2
  12. package/src/core/admin-onclick.js +2 -2
  13. package/src/core/admin-resources.js +23 -14
  14. package/src/core/autosave.js +11 -1
  15. package/src/core/edit-mode.js +2 -2
  16. package/src/core/host-attrs.js +46 -0
  17. package/src/core/is-edit-mode.js +14 -6
  18. package/src/core/persist.js +12 -2
  19. package/src/core/save-core.js +199 -290
  20. package/src/core/save.js +121 -82
  21. package/src/core/snapshot.js +89 -36
  22. package/src/core/unsaved-warning.js +8 -2
  23. package/src/dom/all.js +4 -29
  24. package/src/dom/form-data.js +1 -1
  25. package/src/dom/nearest.js +11 -9
  26. package/src/internals/index.js +69 -0
  27. package/src/lib/cache-bust.js +10 -0
  28. package/src/lib/cookie.js +50 -15
  29. package/src/lib/extension-noise.js +5 -1
  30. package/src/lib/mutation.js +65 -18
  31. package/src/lib/region-policy.js +17 -2
  32. package/src/lib/root-attrs.js +45 -0
  33. package/src/lib/throttle.js +25 -13
  34. package/src/loader.js +18 -24
  35. package/src/options/options.js +1 -3
  36. package/src/plugins/demo.js +7 -1
  37. package/src/plugins/undo.js +3 -3
  38. package/src/sync/live-sync.js +77 -76
  39. package/src/ui/dialogs.js +10 -1
  40. package/src/ui/index.js +0 -10
  41. package/src/utils/debounce.js +24 -7
  42. package/src/vendor/control-serialize.vendor.js +2 -1
  43. package/src/vendor/hyper-undo.vendor.js +1 -1
  44. package/src/vendor/hypercms.vendor.js +29 -16
  45. package/src/vendor/richclay.vendor.js +29 -22
@@ -240,9 +240,7 @@ const optionVisibility = {
240
240
  }
241
241
  };
242
242
 
243
- // vendor-compat mirror: sap reads window.hyperclay.optionVisibility at runtime.
244
- window.hyperclay = window.hyperclay || {};
245
- window.hyperclay.optionVisibility = optionVisibility;
243
+ window.clay.optionVisibility = optionVisibility;
246
244
 
247
245
  export default optionVisibility;
248
246
 
@@ -20,12 +20,18 @@
20
20
  // returning visitors' old saved bodies morph over the new page.
21
21
 
22
22
  import { morph } from "../vendor/hyper-morph.vendor.js";
23
+ import { STRIP_FROM_SAVE } from "../lib/region-policy.js";
23
24
 
24
25
  const KEY = "clay:demo:" +
25
26
  (document.documentElement.getAttribute("demo-key") || window.location.pathname);
26
27
  const LATENCY_MS = 800;
28
+ // What the demo's fake server must not persist. The region half comes from
29
+ // region-policy's STRIP_FROM_SAVE rather than being re-listed here: this used to
30
+ // name "[save-remove]" alone and so missed the canonical clay="no-save" spelling,
31
+ // which meant the demo saved the very regions no-save promises it never will. The
32
+ // rest is editor chrome the real save lane strips by other means.
27
33
  const CHROME_SELECTOR = [
28
- "[save-remove]",
34
+ STRIP_FROM_SAVE,
29
35
  "[data-richclay-toolbar]",
30
36
  "[data-richclay-menu]",
31
37
  "[data-richclay-dialog]",
@@ -1,7 +1,7 @@
1
1
  // Re-exports the vendored hyper-undo singleton and auto-starts it in edit mode
2
- // so apps "just work". The loader wires it onto clay.undo (and mirrors it onto
3
- // the vendor-compat shim); the vendor's own window auto-export is suppressed by
4
- // the bootstrap's __hyperclayNoAutoExport flag.
2
+ // so apps "just work". The loader wires it onto clay.undo; the vendor's own
3
+ // window auto-export is suppressed by the bootstrap's __hyperclayNoAutoExport
4
+ // flag.
5
5
 
6
6
  import { undo } from '../vendor/hyper-undo.vendor.js'
7
7
  import { isEditMode } from '../core/is-edit-mode.js'
@@ -40,6 +40,8 @@ import Mutation from "../lib/mutation.js";
40
40
  import { isSnapshotRemoved } from "../lib/region-policy.js";
41
41
  import { isEditMode } from "../core/is-edit-mode.js";
42
42
  import { mergeTagRecognizers } from "./merge-tags.js";
43
+ import { serializeForSync } from '../core/snapshot.js';
44
+ import { isTabLocalRootAttr } from '../lib/root-attrs.js';
43
45
 
44
46
  class LiveSync {
45
47
  constructor() {
@@ -58,6 +60,8 @@ class LiveSync {
58
60
 
59
61
  this.debounceMs = 150;
60
62
  this.debounceTimer = null;
63
+ this._sendInFlight = false;
64
+ this._queuedSend = null;
61
65
  this.isPaused = false;
62
66
  this.isDestroyed = false;
63
67
  this.debug = false;
@@ -96,7 +100,7 @@ class LiveSync {
96
100
  // against the live elements so that the next save can produce the same
97
101
  // identityMap, and afterNodeMorphed transfers IDs from incoming parsed
98
102
  // elements onto the live elements they morphed into.
99
- this.idCounter = this._loadIdCounter();
103
+ this.idCounter = 0;
100
104
  this.liveWeakMap = new WeakMap();
101
105
 
102
106
  // Callbacks
@@ -118,28 +122,15 @@ class LiveSync {
118
122
  }
119
123
 
120
124
  /**
121
- * Generate or retrieve a tab-specific client ID
122
- * Uses sessionStorage (unique per tab) not localStorage (shared across tabs)
125
+ * Mint the per-tab sender identity used for echo suppression. Lives on the
126
+ * instance, never in storage: sessionStorage is COPIED into a duplicated tab,
127
+ * so a persisted id made the duplicate treat the original's edits as its own
128
+ * echoes and the two tabs silently stopped syncing. Instance lifetime is the
129
+ * durability that matters, since EventSource auto-reconnect reuses this same
130
+ * instance.
123
131
  */
124
132
  generateClientId() {
125
- let id = null;
126
-
127
- try {
128
- id = sessionStorage.getItem('livesync-client-id');
129
- } catch (e) {
130
- // sessionStorage might not be available
131
- }
132
-
133
- if (!id) {
134
- id = Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
135
- try {
136
- sessionStorage.setItem('livesync-client-id', id);
137
- } catch (e) {
138
- // That's okay
139
- }
140
- }
141
-
142
- return id;
133
+ return Math.random().toString(36).slice(2, 11) + Date.now().toString(36);
143
134
  }
144
135
 
145
136
  /**
@@ -208,6 +199,7 @@ class LiveSync {
208
199
  }
209
200
 
210
201
  clearTimeout(this.debounceTimer);
202
+ this._queuedSend = null;
211
203
 
212
204
  // Cancel any pending frame and clear the queue. A morph already in
213
205
  // flight cannot be aborted; the isDestroyed check in _runPending guards
@@ -221,27 +213,8 @@ class LiveSync {
221
213
  this._pendingIdentityMap = null;
222
214
  }
223
215
 
224
- _loadIdCounter() {
225
- try {
226
- const raw = sessionStorage.getItem('livesync-id-counter');
227
- const n = parseInt(raw || '0', 10);
228
- return Number.isFinite(n) && n > 0 ? n : 0;
229
- } catch (e) {
230
- return 0;
231
- }
232
- }
233
-
234
- _persistIdCounter() {
235
- try {
236
- sessionStorage.setItem('livesync-id-counter', String(this.idCounter));
237
- } catch (e) {
238
- // Storage full / sandboxed — fall back to in-memory only.
239
- }
240
- }
241
-
242
216
  _mintId() {
243
217
  this.idCounter++;
244
- this._persistIdCounter();
245
218
  return `${this.clientId}:${this.idCounter}`;
246
219
  }
247
220
 
@@ -387,8 +360,10 @@ class LiveSync {
387
360
  if (this.isDestroyed) return;
388
361
 
389
362
  const pageUrl = encodeURIComponent(window.location.href);
390
- const url = `/_/live-sync/stream?page-url=${pageUrl}&lane=${this.lane}&resume-id=${this.resumeId}`;
391
- this.sse = new EventSource(url);
363
+ const path = `/_/live-sync/stream?page-url=${pageUrl}&lane=${this.lane}&resume-id=${this.resumeId}`;
364
+ // Resolved against the real origin: a <base href> in the authored document
365
+ // would otherwise point the sync stream at an origin the document chose.
366
+ this.sse = new EventSource(new URL(path, window.location.origin).href);
392
367
 
393
368
  this.sse.onopen = () => {
394
369
  console.log('[LiveSync] Connected');
@@ -482,7 +457,7 @@ class LiveSync {
482
457
  // after dispatchEvent returns, so reading outerHTML and walking children
483
458
  // must happen now.
484
459
  this._log('snapshot-ready received, preparing to send');
485
- const html = clone.outerHTML;
460
+ const html = serializeForSync(clone);
486
461
  const identityMap = this._buildIdentityMap(document.documentElement, clone);
487
462
  this.sendUpdate(html, identityMap);
488
463
  };
@@ -491,42 +466,67 @@ class LiveSync {
491
466
  }
492
467
 
493
468
  /**
494
- * Send full HTML to the server (debounced)
495
- * Only updates lastHtml after successful save
469
+ * Send full HTML to the server (debounced, single-flight).
470
+ *
471
+ * One POST on the wire at a time, with at most one newer payload queued; a
472
+ * fresher snapshot replaces the queued one, because peers only ever need the
473
+ * latest state. Two concurrent fetches could reach the server in either order,
474
+ * and last-write-wins then stored the OLDER snapshot for every peer.
496
475
  */
497
476
  sendUpdate(html, identityMap) {
498
477
  clearTimeout(this.debounceTimer);
499
478
 
500
479
  this.debounceTimer = setTimeout(() => {
501
- // Skip if unchanged
502
- if (html === this.lastHtml) {
503
- this._log('Skipping send - HTML unchanged');
504
- return;
505
- }
506
-
507
- this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
508
-
509
- fetch('/_/live-sync/save', {
510
- method: 'POST',
511
- headers: { 'Content-Type': 'application/json', 'Page-URL': window.location.href },
512
- body: JSON.stringify({
513
- html: html,
514
- sender: this.clientId,
515
- identityMap: identityMap
516
- })
517
- }).then(response => {
518
- if (response.ok) {
519
- this.lastHtml = html;
520
- } else {
521
- console.warn('[LiveSync] Save returned status:', response.status);
522
- }
523
- }).catch(err => {
524
- console.error('[LiveSync] Save failed:', err);
525
- if (this.onError) this.onError(err);
526
- });
480
+ this._enqueueSend(html, identityMap);
527
481
  }, this.debounceMs);
528
482
  }
529
483
 
484
+ _enqueueSend(html, identityMap) {
485
+ if (this._sendInFlight) {
486
+ this._queuedSend = { html, identityMap };
487
+ return;
488
+ }
489
+ this._postUpdate(html, identityMap);
490
+ }
491
+
492
+ _postUpdate(html, identityMap) {
493
+ // Skip if unchanged
494
+ if (html === this.lastHtml) {
495
+ this._log('Skipping send - HTML unchanged');
496
+ return;
497
+ }
498
+
499
+ this._log(`Sending update (HTML length: ${html.length}, lastHtml length: ${this.lastHtml?.length || 0})`);
500
+
501
+ this._sendInFlight = true;
502
+
503
+ // Absolute against the real origin, so a <base href> in the page cannot
504
+ // redirect the whole document to an origin the author picked.
505
+ fetch(new URL('/_/live-sync/save', window.location.origin).href, {
506
+ method: 'POST',
507
+ headers: { 'Content-Type': 'application/json', 'Page-URL': window.location.href },
508
+ body: JSON.stringify({
509
+ html: html,
510
+ sender: this.clientId,
511
+ identityMap: identityMap
512
+ })
513
+ }).then(response => {
514
+ if (response.ok) {
515
+ this.lastHtml = html;
516
+ } else {
517
+ console.warn('[LiveSync] Save returned status:', response.status);
518
+ }
519
+ }).catch(err => {
520
+ console.error('[LiveSync] Save failed:', err);
521
+ if (this.onError) this.onError(err);
522
+ }).finally(() => {
523
+ this._sendInFlight = false;
524
+ const queued = this._queuedSend;
525
+ this._queuedSend = null;
526
+ if (queued) this._postUpdate(queued.html, queued.identityMap);
527
+ });
528
+ }
529
+
530
530
  /**
531
531
  * Apply an update received from the server. Morphs the entire document.
532
532
  *
@@ -660,6 +660,11 @@ class LiveSync {
660
660
  const id = parsedWeakMap.get(newEl);
661
661
  if (id) liveWeakMap.set(oldEl, id);
662
662
  };
663
+ // A peer may neither write these onto our root nor, by not carrying them,
664
+ // take ours away. Returning false is hyper-morph's veto for both directions
665
+ // (it calls this for updateType 'update' and 'remove' alike).
666
+ const beforeAttributeUpdated = (name, element) =>
667
+ isTabLocalRootAttr(name, element) ? false : undefined;
663
668
 
664
669
  try {
665
670
  // Morph entire document. We MUST await — HyperMorph.morph returns a
@@ -683,7 +688,7 @@ class LiveSync {
683
688
  mergeTags: mergeTagRecognizers
684
689
  },
685
690
  key,
686
- callbacks: { afterNodeMorphed }
691
+ callbacks: { afterNodeMorphed, beforeAttributeUpdated }
687
692
  });
688
693
 
689
694
  // Restore viewport. Done after morph so layout has settled.
@@ -716,10 +721,6 @@ class LiveSync {
716
721
  document.dispatchEvent(new CustomEvent('clay:sync-applied', {
717
722
  detail: { seq }
718
723
  }));
719
- // vendor-compat: hypercms's form panel listens for the legacy name.
720
- document.dispatchEvent(new CustomEvent('hyperclay:livesync-applied', {
721
- detail: { seq }
722
- }));
723
724
  } finally {
724
725
  this._log('applyUpdate - morph complete, resuming mutations');
725
726
  Mutation.resume();
package/src/ui/dialogs.js CHANGED
@@ -6,9 +6,18 @@ import copyToClipboard from "../utils/copy-to-clipboard.js";
6
6
  const CLOSE_BUTTON_SVG = `<svg viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="micromodal__close-bg" d="M132 132.5 1 1.5h131v131Z" /><path class="micromodal__close-x" fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" /></svg>`;
7
7
  const CONFIRM_BUTTON_SVG = `<div style="width: 28px;"><svg viewBox="0 0 60 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M34.5 0.75H43.5V5.25H49V9.75H54.5V14.25H60V18.75H54.5V23.25H49V27.75H43.5V32.25H34.5V27.75H40V23.25H45.5V18.75H0V14.25H45.5V9.75H40V5.25H34.5V0.75Z" fill="white"/></svg></div>`;
8
8
 
9
+ // Only for the prompt's default value. promptText and extraContent are markup by
10
+ // design, and callers pass elements through them.
11
+ function escapeAttribute(value) {
12
+ return String(value)
13
+ .replaceAll("&", "&amp;")
14
+ .replaceAll('"', "&quot;")
15
+ .replaceAll("<", "&lt;");
16
+ }
17
+
9
18
  function createModal(promptText, yesCallback, extraContent = "", includeInput = false, defaultValue = "") {
10
19
  const inputHtml = includeInput
11
- ? `<div><input class="micromodal__input" type="text" value="${defaultValue}" required></div>`
20
+ ? `<div><input class="micromodal__input" type="text" value="${escapeAttribute(defaultValue)}" required></div>`
12
21
  : "";
13
22
 
14
23
  themodal.html = `<div>
package/src/ui/index.js CHANGED
@@ -18,16 +18,6 @@ clay.modal = themodal;
18
18
  if (typeof window.toast === "undefined") window.toast = toast;
19
19
  if (typeof window.toastPersistent === "undefined") window.toastPersistent = toastPersistent;
20
20
 
21
- // vendor-compat mirror: sap reads window.hyperclay.consent (the legacy name for
22
- // clay.confirm). Never CREATE window.hyperclay here: sap's mutation bridge treats
23
- // any truthy window.hyperclay as "a hub is coming" and waits forever for it. Core
24
- // creates the object (with Mutation) and fires clay:mutation-ready; attach then.
25
- function mirrorConsent() {
26
- if (window.hyperclay && !window.hyperclay.consent) window.hyperclay.consent = consent;
27
- }
28
- if (window.hyperclay) mirrorConsent();
29
- else document.addEventListener("clay:mutation-ready", mirrorConsent, { once: true });
30
-
31
21
  // Automatic save feedback. clay:save-saving stays deliberately silent (a toast for
32
22
  // a sub-second transient is noise). Keep your own toast lib? The events are public.
33
23
  document.addEventListener("clay:save-saved", (e) =>
@@ -1,23 +1,40 @@
1
1
  // debounce.js
2
2
  function debounce(callback, delay) {
3
3
  let timeoutId;
4
- let pendingResolvers = [];
4
+ let pending = [];
5
5
 
6
6
  return function (...args) {
7
7
  const ctx = this;
8
8
  clearTimeout(timeoutId);
9
9
 
10
- return new Promise((resolve) => {
11
- pendingResolvers.push(resolve);
10
+ return new Promise((resolve, reject) => {
11
+ pending.push({ resolve, reject });
12
12
 
13
13
  timeoutId = setTimeout(() => {
14
- const resolvers = pendingResolvers;
15
- pendingResolvers = [];
16
- Promise.resolve(callback.apply(ctx, args))
17
- .then(value => { for (const r of resolvers) r(value); });
14
+ const waiting = pending;
15
+ pending = [];
16
+ settle(callback, ctx, args, waiting);
18
17
  }, delay);
19
18
  });
20
19
  };
21
20
  }
22
21
 
22
+ // Settle every caller piggybacking on this window, from both branches. The
23
+ // callback is invoked synchronously so timing is unchanged; only the failure
24
+ // path is new. Without it a throw escapes into the timer, where no caller is
25
+ // listening, and everyone waiting on that window waits forever.
26
+ function settle(callback, ctx, args, waiting) {
27
+ let result;
28
+ try {
29
+ result = callback.apply(ctx, args);
30
+ } catch (error) {
31
+ for (const p of waiting) p.reject(error);
32
+ return;
33
+ }
34
+ Promise.resolve(result).then(
35
+ value => { for (const p of waiting) p.resolve(value); },
36
+ error => { for (const p of waiting) p.reject(error); },
37
+ );
38
+ }
39
+
23
40
  export default debounce;
@@ -1,5 +1,6 @@
1
1
  // GENERATED — do not edit. Vendored from sapjs/src/control-serialize.js
2
- // via sapjs `npm run copy-to-hyperclayjs`. Edit the sapjs source and re-run.
2
+ // via sapjs `npm run propagate`, which writes every client's copy from one
3
+ // table. Edit the sapjs source and re-run.
3
4
 
4
5
  // The one place that knows how a form control's live state maps to the DOM
5
6
  // attributes that survive serialization (cloneNode / outerHTML).
@@ -1,4 +1,4 @@
1
- var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){return typeof window<"u"&&window.hyperclay&&window.hyperclay.region&&typeof window.hyperclay.region.resolveRegionPolicy=="function"?window.hyperclay.region.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&window.hyperclay&&window.hyperclay.Mutation||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from window.hyperclay.Mutation (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
1
+ var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){if(typeof window>"u")return null;let e=window.clay?.region||window.hyperclay?.region;return e&&typeof e.resolveRegionPolicy=="function"?e.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&(window.clay?.Mutation||window.hyperclay?.Mutation)||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from the platform Mutation hub (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
2
2
 
3
3
  // Auto-export to window unless suppressed by loader.
4
4
  if (!window.__hyperclayNoAutoExport) {