@sigil-dev/runtime 0.9.3 → 0.9.5
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/async-boundary.ts +30 -30
- package/boundary.ts +44 -44
- package/easing.ts +66 -66
- package/index.test.ts +1843 -1618
- package/index.ts +164 -40
- package/jsr.json +8 -8
- package/package.json +1 -1
- package/reactivity/map.ts +98 -98
- package/reactivity/set.ts +74 -74
- package/test-preload.ts +9 -9
- package/transition.ts +69 -0
- package/tsconfig.json +30 -30
package/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export {
|
|
|
20
20
|
} from "./easing.ts";
|
|
21
21
|
export { ReactiveMap } from "./reactivity/map.ts";
|
|
22
22
|
export { ReactiveSet } from "./reactivity/set.ts";
|
|
23
|
+
export { mountTransition, removeAnimated } from "./transition.ts";
|
|
23
24
|
|
|
24
25
|
export const SIGIL_SAFE = Symbol.for("sigil.safe");
|
|
25
26
|
export class SafeHtml {
|
|
@@ -142,6 +143,9 @@ function createReactiveProxy<T extends object>(target: T): T {
|
|
|
142
143
|
trigger(target, k);
|
|
143
144
|
}
|
|
144
145
|
if (Array.isArray(target)) {
|
|
146
|
+
// delete leaves holes — length must shrink to the new value's,
|
|
147
|
+
// or iteration (spread, reconcile) sees phantom trailing slots
|
|
148
|
+
target.length = (newValue as unknown[]).length;
|
|
145
149
|
trigger(target, "length");
|
|
146
150
|
}
|
|
147
151
|
};
|
|
@@ -287,6 +291,23 @@ export function tracking(): boolean {
|
|
|
287
291
|
return currentEffect !== null;
|
|
288
292
|
}
|
|
289
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Run fn() outside of any reactive context: signal reads inside fn() do not
|
|
296
|
+
* register dependencies on the enclosing effect.
|
|
297
|
+
*/
|
|
298
|
+
export function untrack<T>(fn: () => T): T {
|
|
299
|
+
const prevEffect = currentEffect;
|
|
300
|
+
const prevDeps = currentDeps;
|
|
301
|
+
currentEffect = null;
|
|
302
|
+
currentDeps = null;
|
|
303
|
+
try {
|
|
304
|
+
return fn();
|
|
305
|
+
} finally {
|
|
306
|
+
currentEffect = prevEffect;
|
|
307
|
+
currentDeps = prevDeps;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
290
311
|
// A4: $inspect — dev-only effect logging
|
|
291
312
|
export function createInspectEffect(
|
|
292
313
|
getValues: () => unknown[],
|
|
@@ -360,6 +381,15 @@ export function createProfilerEffect(
|
|
|
360
381
|
});
|
|
361
382
|
}
|
|
362
383
|
|
|
384
|
+
// Deferred effect mode for hydration: effects created while the flag is set
|
|
385
|
+
// are queued instead of run immediately, then flushed after DOM insertion.
|
|
386
|
+
const deferredEffectQueue: (() => void)[] = [];
|
|
387
|
+
|
|
388
|
+
export function flushDeferredEffects(): void {
|
|
389
|
+
const pending = deferredEffectQueue.splice(0);
|
|
390
|
+
for (const run of pending) run();
|
|
391
|
+
}
|
|
392
|
+
|
|
363
393
|
// biome-ignore lint/suspicious/noConfusingVoidType: please shut up its a weird type
|
|
364
394
|
export function createEffect(fn: () => void | (() => void)): () => void {
|
|
365
395
|
let cleanup: (() => void) | undefined;
|
|
@@ -383,7 +413,13 @@ export function createEffect(fn: () => void | (() => void)): () => void {
|
|
|
383
413
|
}
|
|
384
414
|
};
|
|
385
415
|
|
|
386
|
-
|
|
416
|
+
// In deferred mode (hydration), queue the effect instead of running immediately.
|
|
417
|
+
// This ensures the DOM is inserted before effect callbacks query it.
|
|
418
|
+
if ((globalThis as any).__sigilDeferEffects) {
|
|
419
|
+
deferredEffectQueue.push(() => effect());
|
|
420
|
+
} else {
|
|
421
|
+
effect();
|
|
422
|
+
}
|
|
387
423
|
|
|
388
424
|
const dispose = () => {
|
|
389
425
|
for (const depSet of ownDeps) depSet.delete(effect);
|
|
@@ -417,12 +453,137 @@ export function withEffectScope(fn: () => void): () => void {
|
|
|
417
453
|
};
|
|
418
454
|
}
|
|
419
455
|
|
|
456
|
+
type EffectScopeGlobal = { __sigilEffectScope?: (() => void)[] };
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Run fn() with a detached effect scope. Effects created inside are invisible
|
|
460
|
+
* to enclosing scopes (the scope array is replaced, not merged) and live until
|
|
461
|
+
* dispose() is called — for app bootstrap, routers, and long-lived listeners.
|
|
462
|
+
*/
|
|
463
|
+
export function createRoot<T>(fn: (dispose: () => void) => T): T {
|
|
464
|
+
const g = globalThis as EffectScopeGlobal;
|
|
465
|
+
const prev = g.__sigilEffectScope;
|
|
466
|
+
const disposers: (() => void)[] = [];
|
|
467
|
+
g.__sigilEffectScope = disposers;
|
|
468
|
+
const dispose = () => {
|
|
469
|
+
for (const d of disposers.splice(0)) d();
|
|
470
|
+
};
|
|
471
|
+
try {
|
|
472
|
+
return fn(dispose);
|
|
473
|
+
} finally {
|
|
474
|
+
g.__sigilEffectScope = prev;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export interface Resource<T> {
|
|
479
|
+
data: () => T | undefined;
|
|
480
|
+
loading: () => boolean;
|
|
481
|
+
error: () => Error | undefined;
|
|
482
|
+
refetch: () => void;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Async data source with reactive re-fetching. Re-runs when `source()` changes
|
|
487
|
+
* or refetch() is called; stale responses are discarded by request id.
|
|
488
|
+
*/
|
|
489
|
+
export function createResource<T>(
|
|
490
|
+
fetcher: (source: unknown, info: { refetch: () => void }) => Promise<T>,
|
|
491
|
+
source?: () => unknown,
|
|
492
|
+
): Resource<T> {
|
|
493
|
+
const data = createSignal<T | undefined>(undefined);
|
|
494
|
+
const loading = createSignal(true);
|
|
495
|
+
const error = createSignal<Error | undefined>(undefined);
|
|
496
|
+
const version = createSignal(0);
|
|
497
|
+
let requestCount = 0;
|
|
498
|
+
let versionCounter = 0;
|
|
499
|
+
|
|
500
|
+
const refetch = () => {
|
|
501
|
+
versionCounter++;
|
|
502
|
+
version.set(versionCounter);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
createEffect(() => {
|
|
506
|
+
const src = source?.();
|
|
507
|
+
version(); // track refetch bumps
|
|
508
|
+
const id = ++requestCount;
|
|
509
|
+
loading.set(true);
|
|
510
|
+
error.set(undefined);
|
|
511
|
+
fetcher(src, { refetch }).then(
|
|
512
|
+
(result) => {
|
|
513
|
+
if (id !== requestCount) return; // stale response
|
|
514
|
+
data.set(result);
|
|
515
|
+
loading.set(false);
|
|
516
|
+
},
|
|
517
|
+
(err: unknown) => {
|
|
518
|
+
if (id !== requestCount) return;
|
|
519
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
520
|
+
loading.set(false);
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return { data, loading, error, refetch };
|
|
526
|
+
}
|
|
527
|
+
|
|
420
528
|
export function createMemo<T>(fn: () => T): Signal<T> {
|
|
421
529
|
const inner = createSignal<T>(undefined as T);
|
|
422
530
|
createEffect(() => inner.set(fn()));
|
|
423
531
|
return inner;
|
|
424
532
|
}
|
|
425
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Reconcile a list of keyed DOM nodes.
|
|
536
|
+
* anchor.before(node) handles both insert and move natively.
|
|
537
|
+
* @param claimNode - Optional hydration callback. Called with (element, key) for
|
|
538
|
+
* server-rendered elements removed from the DOM. Returns the element to use,
|
|
539
|
+
* or null to discard.
|
|
540
|
+
*/
|
|
541
|
+
const lastItemFor = new WeakMap<object, unknown>();
|
|
542
|
+
|
|
543
|
+
export function reconcile<T>(
|
|
544
|
+
anchor: Partial<Comment>,
|
|
545
|
+
keyMap: Map<unknown, Node>,
|
|
546
|
+
newItems: T[],
|
|
547
|
+
getKey: (item: T, index: number) => unknown,
|
|
548
|
+
createNode: (item: T) => Node,
|
|
549
|
+
// is this meant to be unused? claimNode?: (element: Element, key: unknown) => Element | null,
|
|
550
|
+
): void {
|
|
551
|
+
const newKeys = new Set(newItems.map((item, i) => getKey(item, i)));
|
|
552
|
+
|
|
553
|
+
// Remove nodes no longer in list (or claim them for hydration)
|
|
554
|
+
for (const [key, node] of keyMap) {
|
|
555
|
+
if (!newKeys.has(key)) {
|
|
556
|
+
node.parentNode?.removeChild(node);
|
|
557
|
+
keyMap.delete(key);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Insert/move in order — anchor.before() handles both cases.
|
|
562
|
+
// A surviving node whose item instance changed is rebuilt: compiled row
|
|
563
|
+
// bodies bake item reads at creation time, so immutable-style updates
|
|
564
|
+
// (new object, same key) must swap the node to show fresh content.
|
|
565
|
+
for (let i = 0; i < newItems.length; i++) {
|
|
566
|
+
const item = newItems[i]!;
|
|
567
|
+
const key = getKey(item, i);
|
|
568
|
+
let node = keyMap.get(key);
|
|
569
|
+
if (
|
|
570
|
+
node &&
|
|
571
|
+
lastItemFor.has(node) &&
|
|
572
|
+
!Object.is(lastItemFor.get(node), item)
|
|
573
|
+
) {
|
|
574
|
+
node.parentNode?.removeChild(node);
|
|
575
|
+
keyMap.delete(key);
|
|
576
|
+
node = undefined;
|
|
577
|
+
}
|
|
578
|
+
if (!node) {
|
|
579
|
+
node = createNode(item);
|
|
580
|
+
lastItemFor.set(node, item);
|
|
581
|
+
keyMap.set(key, node);
|
|
582
|
+
}
|
|
583
|
+
anchor.before?.(node);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
426
587
|
/**
|
|
427
588
|
* Claim an existing element from a hydration pool by tag name, or create a new one.
|
|
428
589
|
* Removes the claimed node from the pool array.
|
|
@@ -488,43 +649,6 @@ export function claimComment(
|
|
|
488
649
|
return comment;
|
|
489
650
|
}
|
|
490
651
|
|
|
491
|
-
/**
|
|
492
|
-
* Reconcile a list of keyed DOM nodes.
|
|
493
|
-
* anchor.before(node) handles both insert and move natively.
|
|
494
|
-
* @param claimNode - Optional hydration callback. Called with (element, key) for
|
|
495
|
-
* server-rendered elements removed from the DOM. Returns the element to use,
|
|
496
|
-
* or null to discard.
|
|
497
|
-
*/
|
|
498
|
-
export function reconcile<T>(
|
|
499
|
-
anchor: Partial<Comment>,
|
|
500
|
-
keyMap: Map<unknown, Node>,
|
|
501
|
-
newItems: T[],
|
|
502
|
-
getKey: (item: T) => unknown,
|
|
503
|
-
createNode: (item: T) => Node,
|
|
504
|
-
// is this meant to be unused? claimNode?: (element: Element, key: unknown) => Element | null,
|
|
505
|
-
): void {
|
|
506
|
-
const newKeys = new Set(newItems.map(getKey));
|
|
507
|
-
|
|
508
|
-
// Remove nodes no longer in list (or claim them for hydration)
|
|
509
|
-
for (const [key, node] of keyMap) {
|
|
510
|
-
if (!newKeys.has(key)) {
|
|
511
|
-
node.parentNode?.removeChild(node);
|
|
512
|
-
keyMap.delete(key);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Insert/move in order — anchor.before() handles both cases
|
|
517
|
-
for (const item of newItems) {
|
|
518
|
-
const key = getKey(item);
|
|
519
|
-
let node = keyMap.get(key);
|
|
520
|
-
if (!node) {
|
|
521
|
-
node = createNode(item);
|
|
522
|
-
keyMap.set(key, node);
|
|
523
|
-
}
|
|
524
|
-
anchor.before?.(node);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
652
|
/**
|
|
529
653
|
* Hydrate a keyed list: claim the comment anchor, walk its sibling elements
|
|
530
654
|
* to build a keyMap, then reconcile with signal data.
|
|
@@ -535,7 +659,7 @@ export function reconcile<T>(
|
|
|
535
659
|
export function hydrateKeyedList<T>(
|
|
536
660
|
nodesVar: ChildNode[],
|
|
537
661
|
signalItems: T[],
|
|
538
|
-
getKey: (item: T) => unknown,
|
|
662
|
+
getKey: (item: T, index: number) => unknown,
|
|
539
663
|
createNode: (item: T) => Node,
|
|
540
664
|
parent?: Element,
|
|
541
665
|
): { anchor: Comment; keyMap: Map<unknown, Node> } {
|
|
@@ -564,7 +688,7 @@ export function hydrateKeyedList<T>(
|
|
|
564
688
|
// Map claimed elements to keys by index
|
|
565
689
|
for (let i = 0; i < claimed.length && i < signalItems.length; i++) {
|
|
566
690
|
//@ts-expect-error i is always in bounds
|
|
567
|
-
const key = getKey(signalItems[i]);
|
|
691
|
+
const key = getKey(signalItems[i], i);
|
|
568
692
|
//@ts-expect-error i is always in bounds
|
|
569
693
|
keyMap.set(key, claimed[i]);
|
|
570
694
|
}
|
package/jsr.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@sigil-dev/runtime",
|
|
3
|
-
"version": "0.6.0",
|
|
4
|
-
"exports": "./index.ts",
|
|
5
|
-
"publish": {
|
|
6
|
-
"exclude": ["src/**/*.test.ts"]
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigil-dev/runtime",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"exports": "./index.ts",
|
|
5
|
+
"publish": {
|
|
6
|
+
"exclude": ["src/**/*.test.ts"]
|
|
7
|
+
}
|
|
8
|
+
}
|
package/package.json
CHANGED
package/reactivity/map.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { createSignal, type Signal } from "../index.ts";
|
|
2
|
-
|
|
3
|
-
export class ReactiveMap<K, V> extends Map<K, V> {
|
|
4
|
-
#keyVersions: Map<K, Signal<number>> = new Map();
|
|
5
|
-
#structVersion: Signal<number>;
|
|
6
|
-
#structVal: number = 0;
|
|
7
|
-
#getKeyVersion(key: K): Signal<number> {
|
|
8
|
-
let sig = this.#keyVersions.get(key);
|
|
9
|
-
if (!sig) {
|
|
10
|
-
const inner = createSignal(0);
|
|
11
|
-
this.#keyVersions.set(key, inner);
|
|
12
|
-
sig = inner;
|
|
13
|
-
}
|
|
14
|
-
return sig;
|
|
15
|
-
}
|
|
16
|
-
#bumpKey(key: K) {
|
|
17
|
-
const sig = this.#keyVersions.get(key);
|
|
18
|
-
if (sig) {
|
|
19
|
-
const v = sig.peek() + 1;
|
|
20
|
-
(sig as Signal<number>).set(v);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
#bumpStruct() {
|
|
24
|
-
this.#structVal++;
|
|
25
|
-
this.#structVersion.set(this.#structVal);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
constructor(entries?: readonly (readonly [K, V])[] | null) {
|
|
29
|
-
super(entries);
|
|
30
|
-
this.#structVersion = createSignal(0);
|
|
31
|
-
if (entries) {
|
|
32
|
-
for (const [k] of entries) this.#getKeyVersion(k);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
get(key: K): V | undefined {
|
|
37
|
-
this.#getKeyVersion(key)();
|
|
38
|
-
return super.get(key);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
set(key: K, value: V): this {
|
|
42
|
-
const existed = super.has(key);
|
|
43
|
-
super.set(key, value);
|
|
44
|
-
this.#bumpKey(key);
|
|
45
|
-
if (!existed) this.#bumpStruct();
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
delete(key: K): boolean {
|
|
50
|
-
const result = super.delete(key);
|
|
51
|
-
if (result) {
|
|
52
|
-
this.#bumpKey(key);
|
|
53
|
-
this.#bumpStruct();
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
clear(): void {
|
|
59
|
-
if (this.size > 0) {
|
|
60
|
-
for (const key of this.keys()) this.#bumpKey(key);
|
|
61
|
-
super.clear();
|
|
62
|
-
this.#bumpStruct();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
forEach(
|
|
67
|
-
callbackfn: (value: V, key: K, map: Map<K, V>) => void,
|
|
68
|
-
thisArg?: unknown,
|
|
69
|
-
): void {
|
|
70
|
-
this.#structVersion();
|
|
71
|
-
super.forEach(callbackfn, thisArg);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get size(): number {
|
|
75
|
-
this.#structVersion();
|
|
76
|
-
return super.size;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
entries(): IterableIterator<[K, V]> {
|
|
80
|
-
this.#structVersion();
|
|
81
|
-
return super.entries();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
keys(): IterableIterator<K> {
|
|
85
|
-
this.#structVersion();
|
|
86
|
-
return super.keys();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
values(): IterableIterator<V> {
|
|
90
|
-
this.#structVersion();
|
|
91
|
-
return super.values();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
[Symbol.iterator](): IterableIterator<[K, V]> {
|
|
95
|
-
this.#structVersion();
|
|
96
|
-
return super[Symbol.iterator]();
|
|
97
|
-
}
|
|
98
|
-
}
|
|
1
|
+
import { createSignal, type Signal } from "../index.ts";
|
|
2
|
+
|
|
3
|
+
export class ReactiveMap<K, V> extends Map<K, V> {
|
|
4
|
+
#keyVersions: Map<K, Signal<number>> = new Map();
|
|
5
|
+
#structVersion: Signal<number>;
|
|
6
|
+
#structVal: number = 0;
|
|
7
|
+
#getKeyVersion(key: K): Signal<number> {
|
|
8
|
+
let sig = this.#keyVersions.get(key);
|
|
9
|
+
if (!sig) {
|
|
10
|
+
const inner = createSignal(0);
|
|
11
|
+
this.#keyVersions.set(key, inner);
|
|
12
|
+
sig = inner;
|
|
13
|
+
}
|
|
14
|
+
return sig;
|
|
15
|
+
}
|
|
16
|
+
#bumpKey(key: K) {
|
|
17
|
+
const sig = this.#keyVersions.get(key);
|
|
18
|
+
if (sig) {
|
|
19
|
+
const v = sig.peek() + 1;
|
|
20
|
+
(sig as Signal<number>).set(v);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
#bumpStruct() {
|
|
24
|
+
this.#structVal++;
|
|
25
|
+
this.#structVersion.set(this.#structVal);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor(entries?: readonly (readonly [K, V])[] | null) {
|
|
29
|
+
super(entries);
|
|
30
|
+
this.#structVersion = createSignal(0);
|
|
31
|
+
if (entries) {
|
|
32
|
+
for (const [k] of entries) this.#getKeyVersion(k);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get(key: K): V | undefined {
|
|
37
|
+
this.#getKeyVersion(key)();
|
|
38
|
+
return super.get(key);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
set(key: K, value: V): this {
|
|
42
|
+
const existed = super.has(key);
|
|
43
|
+
super.set(key, value);
|
|
44
|
+
this.#bumpKey(key);
|
|
45
|
+
if (!existed) this.#bumpStruct();
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
delete(key: K): boolean {
|
|
50
|
+
const result = super.delete(key);
|
|
51
|
+
if (result) {
|
|
52
|
+
this.#bumpKey(key);
|
|
53
|
+
this.#bumpStruct();
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clear(): void {
|
|
59
|
+
if (this.size > 0) {
|
|
60
|
+
for (const key of this.keys()) this.#bumpKey(key);
|
|
61
|
+
super.clear();
|
|
62
|
+
this.#bumpStruct();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
forEach(
|
|
67
|
+
callbackfn: (value: V, key: K, map: Map<K, V>) => void,
|
|
68
|
+
thisArg?: unknown,
|
|
69
|
+
): void {
|
|
70
|
+
this.#structVersion();
|
|
71
|
+
super.forEach(callbackfn, thisArg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get size(): number {
|
|
75
|
+
this.#structVersion();
|
|
76
|
+
return super.size;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
entries(): IterableIterator<[K, V]> {
|
|
80
|
+
this.#structVersion();
|
|
81
|
+
return super.entries();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
keys(): IterableIterator<K> {
|
|
85
|
+
this.#structVersion();
|
|
86
|
+
return super.keys();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
values(): IterableIterator<V> {
|
|
90
|
+
this.#structVersion();
|
|
91
|
+
return super.values();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[Symbol.iterator](): IterableIterator<[K, V]> {
|
|
95
|
+
this.#structVersion();
|
|
96
|
+
return super[Symbol.iterator]();
|
|
97
|
+
}
|
|
98
|
+
}
|
package/reactivity/set.ts
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { createSignal, type Signal } from "../index.ts";
|
|
2
|
-
|
|
3
|
-
export class ReactiveSet<T> extends Set<T> {
|
|
4
|
-
#version: Signal<number>;
|
|
5
|
-
#versionVal: number = 0;
|
|
6
|
-
|
|
7
|
-
constructor(values?: readonly T[] | null) {
|
|
8
|
-
super(values);
|
|
9
|
-
this.#version = createSignal(0);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
#bump() {
|
|
13
|
-
this.#versionVal++;
|
|
14
|
-
this.#version.set(this.#versionVal);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
has(value: T): boolean {
|
|
18
|
-
this.#version();
|
|
19
|
-
return super.has(value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
add(value: T): this {
|
|
23
|
-
const sizeBefore = super.size;
|
|
24
|
-
super.add(value);
|
|
25
|
-
if (super.size !== sizeBefore) this.#bump();
|
|
26
|
-
return this;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
delete(value: T): boolean {
|
|
30
|
-
const result = super.delete(value);
|
|
31
|
-
if (result) this.#bump();
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
clear(): void {
|
|
36
|
-
if (this.size > 0) {
|
|
37
|
-
super.clear();
|
|
38
|
-
this.#bump();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
forEach(
|
|
43
|
-
callbackfn: (value: T, value2: T, set: Set<T>) => void,
|
|
44
|
-
thisArg?: unknown,
|
|
45
|
-
): void {
|
|
46
|
-
this.#version();
|
|
47
|
-
super.forEach(callbackfn, thisArg);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get size(): number {
|
|
51
|
-
this.#version();
|
|
52
|
-
return super.size;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
entries(): IterableIterator<[T, T]> {
|
|
56
|
-
this.#version();
|
|
57
|
-
return super.entries();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
keys(): IterableIterator<T> {
|
|
61
|
-
this.#version();
|
|
62
|
-
return super.keys();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
values(): IterableIterator<T> {
|
|
66
|
-
this.#version();
|
|
67
|
-
return super.values();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
[Symbol.iterator](): IterableIterator<T> {
|
|
71
|
-
this.#version();
|
|
72
|
-
return super[Symbol.iterator]();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
import { createSignal, type Signal } from "../index.ts";
|
|
2
|
+
|
|
3
|
+
export class ReactiveSet<T> extends Set<T> {
|
|
4
|
+
#version: Signal<number>;
|
|
5
|
+
#versionVal: number = 0;
|
|
6
|
+
|
|
7
|
+
constructor(values?: readonly T[] | null) {
|
|
8
|
+
super(values);
|
|
9
|
+
this.#version = createSignal(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#bump() {
|
|
13
|
+
this.#versionVal++;
|
|
14
|
+
this.#version.set(this.#versionVal);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
has(value: T): boolean {
|
|
18
|
+
this.#version();
|
|
19
|
+
return super.has(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
add(value: T): this {
|
|
23
|
+
const sizeBefore = super.size;
|
|
24
|
+
super.add(value);
|
|
25
|
+
if (super.size !== sizeBefore) this.#bump();
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
delete(value: T): boolean {
|
|
30
|
+
const result = super.delete(value);
|
|
31
|
+
if (result) this.#bump();
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
clear(): void {
|
|
36
|
+
if (this.size > 0) {
|
|
37
|
+
super.clear();
|
|
38
|
+
this.#bump();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
forEach(
|
|
43
|
+
callbackfn: (value: T, value2: T, set: Set<T>) => void,
|
|
44
|
+
thisArg?: unknown,
|
|
45
|
+
): void {
|
|
46
|
+
this.#version();
|
|
47
|
+
super.forEach(callbackfn, thisArg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get size(): number {
|
|
51
|
+
this.#version();
|
|
52
|
+
return super.size;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
entries(): IterableIterator<[T, T]> {
|
|
56
|
+
this.#version();
|
|
57
|
+
return super.entries();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
keys(): IterableIterator<T> {
|
|
61
|
+
this.#version();
|
|
62
|
+
return super.keys();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
values(): IterableIterator<T> {
|
|
66
|
+
this.#version();
|
|
67
|
+
return super.values();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[Symbol.iterator](): IterableIterator<T> {
|
|
71
|
+
this.#version();
|
|
72
|
+
return super[Symbol.iterator]();
|
|
73
|
+
}
|
|
74
|
+
}
|
package/test-preload.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Window } from "happy-dom";
|
|
2
|
-
|
|
3
|
-
const window = new Window();
|
|
4
|
-
(globalThis as any).document = window.document;
|
|
5
|
-
(globalThis as any).Element = window.Element;
|
|
6
|
-
(globalThis as any).Text = window.Text;
|
|
7
|
-
(globalThis as any).Node = window.Node;
|
|
8
|
-
(globalThis as any).HTMLElement = window.HTMLElement;
|
|
9
|
-
(globalThis as any).Comment = window.Comment;
|
|
1
|
+
import { Window } from "happy-dom";
|
|
2
|
+
|
|
3
|
+
const window = new Window();
|
|
4
|
+
(globalThis as any).document = window.document;
|
|
5
|
+
(globalThis as any).Element = window.Element;
|
|
6
|
+
(globalThis as any).Text = window.Text;
|
|
7
|
+
(globalThis as any).Node = window.Node;
|
|
8
|
+
(globalThis as any).HTMLElement = window.HTMLElement;
|
|
9
|
+
(globalThis as any).Comment = window.Comment;
|