@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.
- package/dist/{chunk-MIOMT2CB.js → chunk-AJP4OVCN.js} +29 -9
- package/dist/chunk-AJP4OVCN.js.map +1 -0
- package/dist/{chunk-XLVBYXOU.js → chunk-ETPJTPYD.js} +6 -3
- package/dist/chunk-ETPJTPYD.js.map +1 -0
- package/dist/{chunk-JRQNDXX7.cjs → chunk-FBM7V4NE.cjs} +68 -47
- package/dist/chunk-FBM7V4NE.cjs.map +1 -0
- package/dist/{chunk-W7OUWVRA.cjs → chunk-H7MDUHS5.cjs} +6 -2
- package/dist/chunk-H7MDUHS5.cjs.map +1 -0
- package/dist/{chunk-2Y5US35K.cjs → chunk-KUXNZ5MG.cjs} +12 -12
- package/dist/{chunk-2Y5US35K.cjs.map → chunk-KUXNZ5MG.cjs.map} +1 -1
- package/dist/{chunk-INNOI6TG.js → chunk-Y3A2EVVS.js} +3 -3
- package/dist/{chunk-INNOI6TG.js.map → chunk-Y3A2EVVS.js.map} +1 -1
- package/dist/forma-runtime-csp.js +178 -79
- package/dist/forma-runtime.js +178 -79
- package/dist/formajs-runtime-hardened.global.js +178 -79
- package/dist/formajs-runtime-hardened.global.js.map +1 -1
- package/dist/formajs-runtime.global.js +178 -79
- package/dist/formajs-runtime.global.js.map +1 -1
- package/dist/formajs.global.js +245 -61
- package/dist/formajs.global.js.map +1 -1
- package/dist/http.cjs +11 -11
- package/dist/http.js +2 -2
- package/dist/index.cjs +307 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +248 -88
- package/dist/index.js.map +1 -1
- package/dist/runtime-hardened.cjs +178 -79
- package/dist/runtime-hardened.cjs.map +1 -1
- package/dist/runtime-hardened.js +178 -79
- package/dist/runtime-hardened.js.map +1 -1
- package/dist/runtime.cjs +198 -103
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +174 -79
- package/dist/runtime.js.map +1 -1
- package/dist/server.cjs +7 -7
- package/dist/server.js +2 -2
- package/dist/tc39-compat.cjs +3 -3
- package/dist/tc39-compat.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-JRQNDXX7.cjs.map +0 -1
- package/dist/chunk-MIOMT2CB.js.map +0 -1
- package/dist/chunk-W7OUWVRA.cjs.map +0 -1
- package/dist/chunk-XLVBYXOU.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { hydrateIsland } from './chunk-
|
|
2
|
-
export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-
|
|
3
|
-
import { internalEffect, createUnownedRoot, createRoot, untrack, registerDisposer, createEffect, pushSuspenseContext, popSuspenseContext, batch } from './chunk-
|
|
4
|
-
export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, createUnownedRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, trigger, untrack } from './chunk-
|
|
5
|
-
import { createSignal, __DEV__, reportError } from './chunk-
|
|
6
|
-
export { createComputed, createSignal, onError } from './chunk-
|
|
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(
|
|
10
|
-
if (typeof
|
|
9
|
+
function createText(value2) {
|
|
10
|
+
if (typeof value2 === "function") {
|
|
11
11
|
const node = new Text("");
|
|
12
12
|
internalEffect(() => {
|
|
13
|
-
node.data =
|
|
13
|
+
node.data = value2();
|
|
14
14
|
});
|
|
15
15
|
return node;
|
|
16
16
|
}
|
|
17
|
-
return new Text(
|
|
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(
|
|
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 =
|
|
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
|
|
220
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
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
|
-
|
|
536
|
-
if (
|
|
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
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
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
|
|
577
|
-
|
|
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 (
|
|
587
|
-
|
|
652
|
+
if (Array.isArray(value2)) {
|
|
653
|
+
getArrayVersion(childPath)[0]();
|
|
588
654
|
}
|
|
589
|
-
|
|
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,
|
|
663
|
+
set(target, prop, value2) {
|
|
595
664
|
if (typeof prop === "symbol") {
|
|
596
|
-
return Reflect.set(target, prop,
|
|
665
|
+
return Reflect.set(target, prop, value2);
|
|
597
666
|
}
|
|
598
667
|
const key = String(prop);
|
|
599
668
|
const childPath = basePrefix + key;
|
|
600
|
-
const rawValue =
|
|
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
|
-
|
|
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
|
-
|
|
796
|
+
const NONE = /* @__PURE__ */ Symbol("none");
|
|
797
|
+
let _expected = NONE;
|
|
700
798
|
let isFirstRun = true;
|
|
701
|
-
internalEffect(() => {
|
|
702
|
-
const
|
|
799
|
+
const disposeEffect = internalEffect(() => {
|
|
800
|
+
const value2 = sourceGet();
|
|
703
801
|
if (isFirstRun) {
|
|
704
802
|
isFirstRun = false;
|
|
705
803
|
return;
|
|
706
804
|
}
|
|
707
|
-
if (
|
|
708
|
-
|
|
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(
|
|
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
|
-
|
|
725
|
-
|
|
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
|
-
|
|
732
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
770
|
-
|
|
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,
|
|
924
|
-
if (
|
|
925
|
-
el.style[key] =
|
|
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,
|
|
931
|
-
if (
|
|
1090
|
+
for (const [name, value2] of Object.entries(attrs)) {
|
|
1091
|
+
if (value2 === false || value2 === null) {
|
|
932
1092
|
el.removeAttribute(name);
|
|
933
|
-
} else if (
|
|
1093
|
+
} else if (value2 === true) {
|
|
934
1094
|
el.setAttribute(name, "");
|
|
935
1095
|
} else {
|
|
936
|
-
el.setAttribute(name,
|
|
1096
|
+
el.setAttribute(name, value2);
|
|
937
1097
|
}
|
|
938
1098
|
}
|
|
939
1099
|
}
|