@getforma/core 1.1.0 → 1.3.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 (45) hide show
  1. package/dist/{chunk-MIOMT2CB.js → chunk-AJP4OVCN.js} +29 -9
  2. package/dist/chunk-AJP4OVCN.js.map +1 -0
  3. package/dist/{chunk-XLVBYXOU.js → chunk-ETPJTPYD.js} +6 -3
  4. package/dist/chunk-ETPJTPYD.js.map +1 -0
  5. package/dist/{chunk-JRQNDXX7.cjs → chunk-FBM7V4NE.cjs} +68 -47
  6. package/dist/chunk-FBM7V4NE.cjs.map +1 -0
  7. package/dist/{chunk-W7OUWVRA.cjs → chunk-H7MDUHS5.cjs} +6 -2
  8. package/dist/chunk-H7MDUHS5.cjs.map +1 -0
  9. package/dist/{chunk-2Y5US35K.cjs → chunk-KUXNZ5MG.cjs} +12 -12
  10. package/dist/{chunk-2Y5US35K.cjs.map → chunk-KUXNZ5MG.cjs.map} +1 -1
  11. package/dist/{chunk-INNOI6TG.js → chunk-Y3A2EVVS.js} +3 -3
  12. package/dist/{chunk-INNOI6TG.js.map → chunk-Y3A2EVVS.js.map} +1 -1
  13. package/dist/forma-runtime-csp.js +178 -79
  14. package/dist/forma-runtime.js +178 -79
  15. package/dist/formajs-runtime-hardened.global.js +178 -79
  16. package/dist/formajs-runtime-hardened.global.js.map +1 -1
  17. package/dist/formajs-runtime.global.js +178 -79
  18. package/dist/formajs-runtime.global.js.map +1 -1
  19. package/dist/formajs.global.js +245 -61
  20. package/dist/formajs.global.js.map +1 -1
  21. package/dist/http.cjs +11 -11
  22. package/dist/http.js +2 -2
  23. package/dist/index.cjs +307 -143
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +34 -2
  26. package/dist/index.d.ts +34 -2
  27. package/dist/index.js +248 -88
  28. package/dist/index.js.map +1 -1
  29. package/dist/runtime-hardened.cjs +178 -79
  30. package/dist/runtime-hardened.cjs.map +1 -1
  31. package/dist/runtime-hardened.js +178 -79
  32. package/dist/runtime-hardened.js.map +1 -1
  33. package/dist/runtime.cjs +198 -103
  34. package/dist/runtime.cjs.map +1 -1
  35. package/dist/runtime.js +174 -79
  36. package/dist/runtime.js.map +1 -1
  37. package/dist/server.cjs +7 -7
  38. package/dist/server.js +2 -2
  39. package/dist/tc39-compat.cjs +3 -3
  40. package/dist/tc39-compat.js +1 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-JRQNDXX7.cjs.map +0 -1
  43. package/dist/chunk-MIOMT2CB.js.map +0 -1
  44. package/dist/chunk-W7OUWVRA.cjs.map +0 -1
  45. package/dist/chunk-XLVBYXOU.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
- import { hydrateIsland } from './chunk-MIOMT2CB.js';
2
- export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-MIOMT2CB.js';
3
- import { internalEffect, createUnownedRoot, createRoot, untrack, registerDisposer, createEffect, pushSuspenseContext, popSuspenseContext, batch } from './chunk-INNOI6TG.js';
4
- export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, createUnownedRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, trigger, untrack } from './chunk-INNOI6TG.js';
5
- import { createSignal, __DEV__, reportError } from './chunk-XLVBYXOU.js';
6
- export { createComputed, createSignal, onError } from './chunk-XLVBYXOU.js';
1
+ import { hydrateIsland } from './chunk-AJP4OVCN.js';
2
+ export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList, svg } from './chunk-AJP4OVCN.js';
3
+ import { internalEffect, createUnownedRoot, createRoot, untrack, registerDisposer, createEffect, pushSuspenseContext, popSuspenseContext, batch } from './chunk-Y3A2EVVS.js';
4
+ export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, createUnownedRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, trigger, untrack } from './chunk-Y3A2EVVS.js';
5
+ import { createSignal, __DEV__, reportError, value } from './chunk-ETPJTPYD.js';
6
+ export { createComputed, createSignal, onError } from './chunk-ETPJTPYD.js';
7
7
 
8
8
  // src/dom/text.ts
9
- function createText(value) {
10
- if (typeof value === "function") {
9
+ function createText(value2) {
10
+ if (typeof value2 === "function") {
11
11
  const node = new Text("");
12
12
  internalEffect(() => {
13
- node.data = value();
13
+ node.data = value2();
14
14
  });
15
15
  return node;
16
16
  }
17
- return new Text(value);
17
+ return new Text(value2);
18
18
  }
19
19
 
20
20
  // src/dom/mount.ts
@@ -47,7 +47,7 @@ function mount(component, container) {
47
47
  }
48
48
 
49
49
  // src/dom/switch.ts
50
- function createSwitch(value, cases, fallback) {
50
+ function createSwitch(value2, cases, fallback) {
51
51
  const startMarker = document.createComment("forma-switch");
52
52
  const endMarker = document.createComment("/forma-switch");
53
53
  const fragment2 = document.createDocumentFragment();
@@ -57,7 +57,7 @@ function createSwitch(value, cases, fallback) {
57
57
  let currentNode = null;
58
58
  let currentMatch = UNSET;
59
59
  const switchDispose = internalEffect(() => {
60
- const val = value();
60
+ const val = value2();
61
61
  if (val === currentMatch) return;
62
62
  const DEBUG = typeof globalThis.__FORMA_DEBUG__ !== "undefined";
63
63
  if (DEBUG) console.log("[forma:switch] transition", String(currentMatch), "\u2192", String(val));
@@ -216,8 +216,14 @@ function createSuspense(fallback, children2) {
216
216
  const isPending = pending() > 0;
217
217
  const newNode = isPending ? fallbackNode ??= fallback() : resolvedNode;
218
218
  if (newNode === currentNode) return;
219
- if (currentNode && currentNode.parentNode === parent2) {
220
- parent2.removeChild(currentNode);
219
+ if (currentNode) {
220
+ if (currentNode.parentNode === parent2) {
221
+ parent2.removeChild(currentNode);
222
+ } else if (currentNode.nodeType === 11) {
223
+ while (startMarker.nextSibling && startMarker.nextSibling !== endMarker) {
224
+ currentNode.appendChild(startMarker.nextSibling);
225
+ }
226
+ }
221
227
  }
222
228
  if (newNode) {
223
229
  parent2.insertBefore(newNode, endMarker);
@@ -273,6 +279,10 @@ function activateIslands(registry) {
273
279
  const sharedProps = scriptBlock ? JSON.parse(scriptBlock.textContent) : null;
274
280
  const islands = document.querySelectorAll("[data-forma-island]");
275
281
  for (const root of islands) {
282
+ const status = root.getAttribute("data-forma-status");
283
+ if (status === "active" || status === "hydrating" || status === "disposed" || status === "error") continue;
284
+ if (root.__formaScheduled) continue;
285
+ delete root.__formaDisposed;
276
286
  const id = parseInt(root.getAttribute("data-forma-island"), 10);
277
287
  const componentName = root.getAttribute("data-forma-component");
278
288
  const hydrateFn = registry[componentName];
@@ -283,30 +293,39 @@ function activateIslands(registry) {
283
293
  }
284
294
  const trigger2 = root.getAttribute("data-forma-hydrate") || "load";
285
295
  if (trigger2 === "visible") {
296
+ root.__formaScheduled = true;
286
297
  const observer = new IntersectionObserver(
287
298
  (entries) => {
288
299
  for (const entry of entries) {
289
300
  if (!entry.isIntersecting) continue;
290
301
  observer.disconnect();
302
+ delete root.__formaObserver;
291
303
  hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
292
304
  }
293
305
  },
294
306
  { rootMargin: "200px" }
295
307
  );
308
+ root.__formaObserver = observer;
296
309
  observer.observe(root);
297
310
  } else if (trigger2 === "idle") {
311
+ root.__formaScheduled = true;
298
312
  const hydrate = () => hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
299
313
  if (typeof requestIdleCallback === "function") {
300
- requestIdleCallback(hydrate);
314
+ const handle = requestIdleCallback(hydrate);
315
+ root.__formaIdleCancel = () => cancelIdleCallback(handle);
301
316
  } else {
302
- setTimeout(hydrate, 200);
317
+ const handle = setTimeout(hydrate, 200);
318
+ root.__formaIdleCancel = () => clearTimeout(handle);
303
319
  }
304
320
  } else if (trigger2 === "interaction") {
321
+ root.__formaScheduled = true;
305
322
  const hydrate = () => {
306
323
  root.removeEventListener("pointerdown", hydrate, true);
307
324
  root.removeEventListener("focusin", hydrate, true);
325
+ delete root.__formaInteractionHandler;
308
326
  hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps);
309
327
  };
328
+ root.__formaInteractionHandler = hydrate;
310
329
  root.addEventListener("pointerdown", hydrate, { capture: true, once: true });
311
330
  root.addEventListener("focusin", hydrate, { capture: true, once: true });
312
331
  } else {
@@ -315,6 +334,24 @@ function activateIslands(registry) {
315
334
  }
316
335
  }
317
336
  function deactivateIsland(el) {
337
+ const observer = el.__formaObserver;
338
+ if (observer) {
339
+ observer.disconnect();
340
+ delete el.__formaObserver;
341
+ }
342
+ const interactionHandler = el.__formaInteractionHandler;
343
+ if (interactionHandler) {
344
+ el.removeEventListener("pointerdown", interactionHandler, true);
345
+ el.removeEventListener("focusin", interactionHandler, true);
346
+ delete el.__formaInteractionHandler;
347
+ }
348
+ const idleCancel = el.__formaIdleCancel;
349
+ if (idleCancel) {
350
+ idleCancel();
351
+ delete el.__formaIdleCancel;
352
+ }
353
+ delete el.__formaScheduled;
354
+ el.__formaDisposed = true;
318
355
  const dispose = el.__formaDispose;
319
356
  if (typeof dispose === "function") {
320
357
  dispose();
@@ -323,13 +360,15 @@ function deactivateIsland(el) {
323
360
  }
324
361
  }
325
362
  function deactivateAllIslands(root = document) {
326
- const islands = root.querySelectorAll('[data-forma-status="active"]');
363
+ const islands = root.querySelectorAll("[data-forma-island]");
327
364
  for (const island of islands) {
328
365
  deactivateIsland(island);
329
366
  }
330
367
  }
331
368
  function hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps) {
369
+ if (root.__formaDisposed) return;
332
370
  try {
371
+ delete root.__formaScheduled;
333
372
  const props = loadIslandProps(root, id, sharedProps);
334
373
  root.setAttribute("data-forma-status", "hydrating");
335
374
  let activeRoot = root;
@@ -437,13 +476,13 @@ function createContext(defaultValue) {
437
476
  defaultValue
438
477
  };
439
478
  }
440
- function provide(ctx, value) {
479
+ function provide(ctx, value2) {
441
480
  let stack = contextStacks.get(ctx.id);
442
481
  if (stack === void 0) {
443
482
  stack = [];
444
483
  contextStacks.set(ctx.id, stack);
445
484
  }
446
- stack.push(value);
485
+ stack.push(value2);
447
486
  }
448
487
  function inject(ctx) {
449
488
  const stack = contextStacks.get(ctx.id);
@@ -499,6 +538,19 @@ function deepClone(obj, seen) {
499
538
  function createStore(initial) {
500
539
  const signals = /* @__PURE__ */ new Map();
501
540
  const children2 = /* @__PURE__ */ new Map();
541
+ const arrayVersions = /* @__PURE__ */ new Map();
542
+ function getArrayVersion(path) {
543
+ let p = arrayVersions.get(path);
544
+ if (!p) {
545
+ p = createSignal(0);
546
+ arrayVersions.set(path, p);
547
+ }
548
+ return p;
549
+ }
550
+ function bumpArrayVersion(path) {
551
+ const p = arrayVersions.get(path);
552
+ if (p) p[1]((n) => n + 1);
553
+ }
502
554
  function registerChild(path) {
503
555
  const lastDot = path.lastIndexOf(".");
504
556
  if (lastDot === -1) return;
@@ -530,14 +582,37 @@ function createStore(initial) {
530
582
  }
531
583
  childSet.clear();
532
584
  }
585
+ function lastSegment(path) {
586
+ const d = path.lastIndexOf(".");
587
+ return d === -1 ? path : path.substring(d + 1);
588
+ }
589
+ function setLiteral(pair, v) {
590
+ pair[1](typeof v === "function" ? value(v) : v);
591
+ }
592
+ function reconcileChildren(parentPath, rawParent) {
593
+ const set = children2.get(parentPath);
594
+ if (!set) return;
595
+ for (const childPath of set) {
596
+ const key = lastSegment(childPath);
597
+ const nv = rawParent[key];
598
+ const pair = signals.get(childPath);
599
+ if (pair) setLiteral(pair, nv);
600
+ if (nv != null && typeof nv === "object") {
601
+ reconcileChildren(childPath, nv);
602
+ } else {
603
+ invalidateChildren(childPath);
604
+ }
605
+ }
606
+ }
533
607
  function wrap(raw, basePath) {
534
608
  if (!shouldWrap(raw)) return raw;
535
- const cached = proxyCache.get(raw);
536
- if (cached) return cached;
609
+ let byPath = proxyCache.get(raw);
610
+ if (byPath) {
611
+ const hit = byPath.get(basePath);
612
+ if (hit) return hit;
613
+ }
537
614
  const isArr = Array.isArray(raw);
538
615
  const basePrefix = basePath ? basePath + "." : "";
539
- let lastKey = "";
540
- let lastSignal;
541
616
  const proxy = new Proxy(raw, {
542
617
  // -------------------------------------------------------------------
543
618
  // GET
@@ -558,12 +633,10 @@ function createStore(initial) {
558
633
  (a) => a != null && typeof a === "object" && a[RAW] ? a[RAW] : a
559
634
  );
560
635
  result = target[key].apply(target, rawArgs);
561
- invalidateChildren(basePath);
562
- const [, setLen] = getSignal(
563
- basePrefix + "length",
564
- target.length
565
- );
566
- setLen(target.length);
636
+ reconcileChildren(basePath, target);
637
+ const lenPair = signals.get(basePrefix + "length");
638
+ if (lenPair) lenPair[1](target.length);
639
+ if (basePath) bumpArrayVersion(basePath);
567
640
  });
568
641
  return result;
569
642
  };
@@ -573,34 +646,32 @@ function createStore(initial) {
573
646
  getter();
574
647
  return target.length;
575
648
  }
576
- const value = Reflect.get(target, prop);
577
- let pair;
578
- if (key === lastKey && lastSignal) {
579
- pair = lastSignal;
580
- } else {
581
- pair = getSignal(childPath, value);
582
- lastKey = key;
583
- lastSignal = pair;
584
- }
649
+ const value2 = Reflect.get(target, prop);
650
+ const pair = getSignal(childPath, value2);
585
651
  pair[0]();
586
- if (shouldWrap(value)) {
587
- return wrap(value, childPath);
652
+ if (Array.isArray(value2)) {
653
+ getArrayVersion(childPath)[0]();
588
654
  }
589
- return value;
655
+ if (shouldWrap(value2)) {
656
+ return wrap(value2, childPath);
657
+ }
658
+ return value2;
590
659
  },
591
660
  // -------------------------------------------------------------------
592
661
  // SET
593
662
  // -------------------------------------------------------------------
594
- set(target, prop, value) {
663
+ set(target, prop, value2) {
595
664
  if (typeof prop === "symbol") {
596
- return Reflect.set(target, prop, value);
665
+ return Reflect.set(target, prop, value2);
597
666
  }
598
667
  const key = String(prop);
599
668
  const childPath = basePrefix + key;
600
- const rawValue = value != null && typeof value === "object" && value[RAW] ? value[RAW] : value;
669
+ const rawValue = value2 != null && typeof value2 === "object" && value2[RAW] ? value2[RAW] : value2;
670
+ const oldRaw = Reflect.get(target, prop);
601
671
  Reflect.set(target, prop, rawValue);
602
- if (rawValue != null && typeof rawValue === "object") {
672
+ if (rawValue != null && typeof rawValue === "object" && oldRaw !== rawValue) {
603
673
  invalidateChildren(childPath);
674
+ evictProxy(oldRaw, childPath);
604
675
  }
605
676
  if (isArr && key !== "length") {
606
677
  const lengthPath = basePrefix + "length";
@@ -609,6 +680,12 @@ function createStore(initial) {
609
680
  lenPair[1](target.length);
610
681
  }
611
682
  }
683
+ if (isArr && key === "length") {
684
+ batch(() => {
685
+ reconcileChildren(basePath, target);
686
+ if (basePath) bumpArrayVersion(basePath);
687
+ });
688
+ }
612
689
  const [, setter2] = getSignal(childPath, rawValue);
613
690
  setter2(rawValue);
614
691
  return true;
@@ -647,24 +724,30 @@ function createStore(initial) {
647
724
  }
648
725
  const key = String(prop);
649
726
  const childPath = basePrefix + key;
727
+ const oldRaw = Reflect.get(target, prop);
650
728
  const result = Reflect.deleteProperty(target, prop);
729
+ const delPair = signals.get(childPath);
730
+ if (delPair) delPair[1](void 0);
731
+ evictProxy(oldRaw, childPath);
651
732
  invalidateChildren(childPath);
652
- signals.delete(childPath);
653
- const parentPath = basePath;
654
- if (parentPath !== void 0) {
655
- const parentSet = children2.get(parentPath);
656
- if (parentSet) {
657
- parentSet.delete(childPath);
658
- if (parentSet.size === 0) children2.delete(parentPath);
659
- }
660
- }
661
- children2.delete(childPath);
662
733
  return result;
663
734
  }
664
735
  });
665
- proxyCache.set(raw, proxy);
736
+ if (!byPath) {
737
+ byPath = /* @__PURE__ */ new Map();
738
+ proxyCache.set(raw, byPath);
739
+ }
740
+ byPath.set(basePath, proxy);
666
741
  return proxy;
667
742
  }
743
+ function evictProxy(oldRaw, path) {
744
+ if (oldRaw == null || typeof oldRaw !== "object") return;
745
+ const om = proxyCache.get(oldRaw);
746
+ if (om) {
747
+ om.delete(path);
748
+ if (om.size === 0) proxyCache.delete(oldRaw);
749
+ }
750
+ }
668
751
  const rootProxy = wrap(initial, "");
669
752
  function getCurrentSnapshot() {
670
753
  return untrack(() => deepClone(initial));
@@ -681,10 +764,24 @@ function createStore(initial) {
681
764
  }
682
765
 
683
766
  // src/state/history.ts
767
+ function cloneEntry(v, seen) {
768
+ if (v === null || typeof v !== "object") return v;
769
+ const proto = Object.getPrototypeOf(v);
770
+ if (!Array.isArray(v) && proto !== Object.prototype && proto !== null) return v;
771
+ if (!seen) seen = /* @__PURE__ */ new WeakSet();
772
+ if (seen.has(v)) return v;
773
+ seen.add(v);
774
+ if (Array.isArray(v)) return v.map((i) => cloneEntry(i, seen));
775
+ const out = {};
776
+ for (const k of Object.keys(v)) {
777
+ out[k] = cloneEntry(v[k], seen);
778
+ }
779
+ return out;
780
+ }
684
781
  function createHistory(source, options) {
685
782
  const [sourceGet, sourceSet] = source;
686
- const maxLength = options?.maxLength ?? 100;
687
- let _stack = [sourceGet()];
783
+ const maxLength = Math.max(1, options?.maxLength ?? 100);
784
+ let _stack = [cloneEntry(sourceGet())];
688
785
  let _cursor = 0;
689
786
  const [stackSignal, setStackSignal] = createSignal([..._stack]);
690
787
  const [cursorSignal, setCursorSignal] = createSignal(_cursor);
@@ -696,45 +793,52 @@ function createHistory(source, options) {
696
793
  setStackLenSignal(_stack.length);
697
794
  });
698
795
  }
699
- let ignoreNext = false;
796
+ const NONE = /* @__PURE__ */ Symbol("none");
797
+ let _expected = NONE;
700
798
  let isFirstRun = true;
701
- internalEffect(() => {
702
- const value = sourceGet();
799
+ const disposeEffect = internalEffect(() => {
800
+ const value2 = sourceGet();
703
801
  if (isFirstRun) {
704
802
  isFirstRun = false;
705
803
  return;
706
804
  }
707
- if (ignoreNext) {
708
- ignoreNext = false;
805
+ if (_expected !== NONE && Object.is(value2, _expected)) {
806
+ _expected = NONE;
709
807
  return;
710
808
  }
809
+ _expected = NONE;
711
810
  _stack = _stack.slice(0, _cursor + 1);
712
- _stack.push(value);
811
+ _stack.push(cloneEntry(value2));
713
812
  if (_stack.length > maxLength) {
714
813
  _stack.splice(0, _stack.length - maxLength);
715
814
  }
716
815
  _cursor = _stack.length - 1;
717
816
  syncSignals();
718
817
  });
818
+ const destroy = () => {
819
+ disposeEffect();
820
+ };
719
821
  const canUndo = () => cursorSignal() > 0;
720
822
  const canRedo = () => cursorSignal() < stackLenSignal() - 1;
721
823
  const undo = () => {
722
824
  if (_cursor <= 0) return;
723
825
  _cursor--;
724
- ignoreNext = true;
725
- sourceSet(_stack[_cursor]);
826
+ const restored = cloneEntry(_stack[_cursor]);
827
+ _expected = restored;
828
+ sourceSet(restored);
726
829
  syncSignals();
727
830
  };
728
831
  const redo = () => {
729
832
  if (_cursor >= _stack.length - 1) return;
730
833
  _cursor++;
731
- ignoreNext = true;
732
- sourceSet(_stack[_cursor]);
834
+ const restored = cloneEntry(_stack[_cursor]);
835
+ _expected = restored;
836
+ sourceSet(restored);
733
837
  syncSignals();
734
838
  };
735
839
  const clear = () => {
736
840
  const currentValue = sourceGet();
737
- _stack = [currentValue];
841
+ _stack = [cloneEntry(currentValue)];
738
842
  _cursor = 0;
739
843
  syncSignals();
740
844
  };
@@ -745,35 +849,91 @@ function createHistory(source, options) {
745
849
  canRedo,
746
850
  history: () => stackSignal(),
747
851
  cursor: () => cursorSignal(),
748
- clear
852
+ clear,
853
+ destroy
749
854
  };
750
855
  }
751
856
 
752
857
  // src/state/persist.ts
858
+ var ENVELOPE_TAG = "$forma:v";
753
859
  function persist(source, key, options) {
754
860
  const [sourceGet, sourceSet] = source;
755
861
  const storage = options?.storage ?? globalThis.localStorage;
756
862
  const serialize = options?.serialize ?? JSON.stringify;
757
863
  const deserialize = options?.deserialize ?? JSON.parse;
758
864
  const validate = options?.validate;
759
- try {
760
- const stored = storage.getItem(key);
761
- if (stored !== null) {
762
- const value = deserialize(stored);
763
- if (!validate || validate(value)) {
764
- sourceSet(value);
865
+ const version = options?.version;
866
+ const migrate = options?.migrate;
867
+ const onError2 = options?.onError;
868
+ let writing = false;
869
+ function unwrap(stored) {
870
+ const parsed = deserialize(stored);
871
+ if (parsed != null && typeof parsed === "object" && Object.prototype.hasOwnProperty.call(parsed, ENVELOPE_TAG)) {
872
+ const env = parsed;
873
+ return { value: env.value, version: Number(env[ENVELOPE_TAG]) };
874
+ }
875
+ return { value: parsed, version: 0 };
876
+ }
877
+ function hydrate() {
878
+ let stored;
879
+ try {
880
+ stored = storage.getItem(key);
881
+ } catch (err) {
882
+ onError2?.(err, "hydrate");
883
+ return;
884
+ }
885
+ if (stored === null) return;
886
+ try {
887
+ const { value: raw, version: storedVersion } = unwrap(stored);
888
+ let value2 = raw;
889
+ if (version !== void 0 && storedVersion < version) {
890
+ if (!migrate) return;
891
+ try {
892
+ value2 = migrate(raw, storedVersion);
893
+ } catch (err) {
894
+ onError2?.(err, "migrate");
895
+ return;
896
+ }
765
897
  }
898
+ if (!validate || validate(value2)) {
899
+ writing = true;
900
+ try {
901
+ sourceSet(value2);
902
+ } finally {
903
+ writing = false;
904
+ }
905
+ }
906
+ } catch (err) {
907
+ onError2?.(err, "hydrate");
766
908
  }
767
- } catch {
768
909
  }
769
- internalEffect(() => {
770
- const value = sourceGet();
910
+ hydrate();
911
+ const stopEffect = internalEffect(() => {
912
+ const value2 = sourceGet();
913
+ if (writing) return;
771
914
  try {
772
- const serialized = serialize(value);
915
+ const serialized = version !== void 0 ? serialize({ [ENVELOPE_TAG]: version, value: value2 }) : serialize(value2);
773
916
  storage.setItem(key, serialized);
774
- } catch {
917
+ } catch (err) {
918
+ onError2?.(err, err?.name === "QuotaExceededError" ? "write" : "serialize");
775
919
  }
776
920
  });
921
+ const enableSync = options?.syncTabs ?? (typeof window !== "undefined" && storage === globalThis.localStorage);
922
+ let onStorage;
923
+ if (enableSync && typeof window !== "undefined") {
924
+ onStorage = (e) => {
925
+ if (e.storageArea !== storage) return;
926
+ if (e.key !== null && e.key !== key) return;
927
+ hydrate();
928
+ };
929
+ window.addEventListener("storage", onStorage);
930
+ }
931
+ return () => {
932
+ stopEffect();
933
+ if (onStorage && typeof window !== "undefined") {
934
+ window.removeEventListener("storage", onStorage);
935
+ }
936
+ };
777
937
  }
778
938
 
779
939
  // src/events/bus.ts
@@ -920,20 +1080,20 @@ function toggleClass(el, className, force) {
920
1080
  return el.classList.toggle(className, force);
921
1081
  }
922
1082
  function setStyle(el, styles) {
923
- for (const [key, value] of Object.entries(styles)) {
924
- if (value !== void 0) {
925
- el.style[key] = value;
1083
+ for (const [key, value2] of Object.entries(styles)) {
1084
+ if (value2 !== void 0) {
1085
+ el.style[key] = value2;
926
1086
  }
927
1087
  }
928
1088
  }
929
1089
  function setAttr(el, attrs) {
930
- for (const [name, value] of Object.entries(attrs)) {
931
- if (value === false || value === null) {
1090
+ for (const [name, value2] of Object.entries(attrs)) {
1091
+ if (value2 === false || value2 === null) {
932
1092
  el.removeAttribute(name);
933
- } else if (value === true) {
1093
+ } else if (value2 === true) {
934
1094
  el.setAttribute(name, "");
935
1095
  } else {
936
- el.setAttribute(name, value);
1096
+ el.setAttribute(name, value2);
937
1097
  }
938
1098
  }
939
1099
  }