rask-ui 0.29.4 → 0.30.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/jsx-runtime.d.ts +1 -4
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +5 -7
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
- package/dist/batch.d.ts +0 -6
- package/dist/batch.d.ts.map +0 -1
- package/dist/batch.js +0 -84
- package/dist/observation.d.ts +0 -93
- package/dist/observation.d.ts.map +0 -1
- package/dist/observation.js +0 -200
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export { Fragment } from "inferno";
|
|
3
|
-
export declare function jsx(type: any, props: any, key?: any): any;
|
|
4
|
-
export declare function jsxs(type: any, props: any, key?: any): any;
|
|
1
|
+
export { Fragment, createVNode as jsx, createVNode as jsxs } from "inferno";
|
|
5
2
|
//# sourceMappingURL=jsx-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime.d.ts","sourceRoot":"","sources":["../src/jsx-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"jsx-runtime.d.ts","sourceRoot":"","sources":["../src/jsx-runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,WAAW,IAAI,GAAG,EAAE,WAAW,IAAI,IAAI,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// JSX runtime for TypeScript
|
|
2
|
-
// The actual JSX transformation is done by SWC plugin at build time
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
// Re-export Fragment for JSX fragment syntax
|
|
7
|
-
export { Fragment } from "inferno";
|
|
1
|
+
// JSX runtime stub for TypeScript's "jsx": "react-jsx" mode
|
|
2
|
+
// The actual JSX transformation is done by the SWC plugin at build time,
|
|
3
|
+
// but Vite's dependency optimizer resolves and bundles this module at runtime,
|
|
4
|
+
// so we need real exports (not just type declarations) to avoid missing export errors
|
|
5
|
+
export { Fragment, createVNode as jsx, createVNode as jsxs } from "inferno";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClipboardEvent as InfernoClipboardEvent, ChangeEvent as InfernoChangeEvent, DragEvent as InfernoDragEvent, FocusEvent as InfernoFocusEvent, FormEvent as InfernoFormEvent, InfernoMouseEvent, InfernoAnimationEvent, InfernoKeyboardEvent, InfernoPointerEvent, InfernoTouchEvent, InfernoWheelEvent, InfernoTransitionEvent, CompositionEvent as InfernoCompositionEvent, Inferno, Refs } from "inferno";
|
|
1
|
+
import type { ClipboardEvent as InfernoClipboardEvent, ChangeEvent as InfernoChangeEvent, DragEvent as InfernoDragEvent, FocusEvent as InfernoFocusEvent, FormEvent as InfernoFormEvent, InfernoMouseEvent, InfernoAnimationEvent, InfernoKeyboardEvent, InfernoPointerEvent, InfernoTouchEvent, InfernoWheelEvent, InfernoTransitionEvent, CompositionEvent as InfernoCompositionEvent, Inferno, InfernoNode, Refs } from "inferno";
|
|
2
2
|
declare global {
|
|
3
3
|
namespace Rask {
|
|
4
4
|
interface Component<P = {}> {
|
|
@@ -19,6 +19,8 @@ declare global {
|
|
|
19
19
|
type FocusEvent<T = Element> = InfernoFocusEvent<T>;
|
|
20
20
|
type FormEvent<T = Element> = InfernoFormEvent<T>;
|
|
21
21
|
type CompositionEvent<T = Element> = InfernoCompositionEvent<T>;
|
|
22
|
+
type Children = InfernoNode;
|
|
23
|
+
type RaskNode = InfernoNode;
|
|
22
24
|
type ElementProps<T extends keyof JSX.IntrinsicElements> = Omit<JSX.IntrinsicElements[T], keyof Inferno.Attributes>;
|
|
23
25
|
}
|
|
24
26
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,IAAI,qBAAqB,EACvC,WAAW,IAAI,kBAAkB,EACjC,SAAS,IAAI,gBAAgB,EAC7B,UAAU,IAAI,iBAAiB,EAC/B,SAAS,IAAI,gBAAgB,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,IAAI,uBAAuB,EAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,IAAI,qBAAqB,EACvC,WAAW,IAAI,kBAAkB,EACjC,SAAS,IAAI,gBAAgB,EAC7B,UAAU,IAAI,iBAAiB,EAC/B,SAAS,IAAI,gBAAgB,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,IAAI,uBAAuB,EAC3C,OAAO,EAEP,WAAW,EACX,IAAI,EACL,MAAM,SAAS,CAAC;AAEjB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI,CAAC;QACb,UAAiB,SAAS,CAAC,CAAC,GAAG,EAAE;YAC/B,CAAC,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC;YAChB,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;YAC7C,YAAY,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;SAC3C;QAED,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACjE,KAAY,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC/D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACrE,KAAY,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnE,KAAY,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC7D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAC3D,KAAY,SAAS,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAAC;QACzD,KAAY,gBAAgB,CAAC,CAAC,GAAG,OAAO,IAAI,uBAAuB,CAAC,CAAC,CAAC,CAAC;QACvE,KAAY,QAAQ,GAAG,WAAW,CAAC;QACnC,KAAY,QAAQ,GAAG,WAAW,CAAC;QACnC,KAAY,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,iBAAiB,IAAI,IAAI,CACpE,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EACxB,MAAM,OAAO,CAAC,UAAU,CACzB,CAAC;KACH;CACF;AAGD,OAAO,EAAE,CAAC"}
|
package/package.json
CHANGED
|
Binary file
|
package/dist/batch.d.ts
DELETED
package/dist/batch.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AA+BlE,wBAAgB,KAAK,CAAC,EAAE,EAAE,cAAc,QAgBvC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QA6CvC"}
|
package/dist/batch.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
const asyncQueue = [];
|
|
2
|
-
const syncQueueStack = [];
|
|
3
|
-
let inInteractive = 0;
|
|
4
|
-
let asyncScheduled = false;
|
|
5
|
-
function scheduleAsyncFlush() {
|
|
6
|
-
if (asyncScheduled)
|
|
7
|
-
return;
|
|
8
|
-
asyncScheduled = true;
|
|
9
|
-
queueMicrotask(flushAsyncQueue);
|
|
10
|
-
}
|
|
11
|
-
function flushAsyncQueue() {
|
|
12
|
-
asyncScheduled = false;
|
|
13
|
-
if (!asyncQueue.length)
|
|
14
|
-
return;
|
|
15
|
-
// Note: we intentionally DO NOT snapshot.
|
|
16
|
-
// If callbacks queue more async work, it gets picked up
|
|
17
|
-
// in this same loop because length grows.
|
|
18
|
-
for (let i = 0; i < asyncQueue.length; i++) {
|
|
19
|
-
const cb = asyncQueue[i];
|
|
20
|
-
asyncQueue[i] = undefined;
|
|
21
|
-
cb();
|
|
22
|
-
cb.__queued = false;
|
|
23
|
-
}
|
|
24
|
-
asyncQueue.length = 0;
|
|
25
|
-
}
|
|
26
|
-
export function queue(cb) {
|
|
27
|
-
if (cb.__queued)
|
|
28
|
-
return;
|
|
29
|
-
cb.__queued = true;
|
|
30
|
-
// If we're in a sync batch, push to the current sync queue
|
|
31
|
-
if (syncQueueStack.length) {
|
|
32
|
-
syncQueueStack[syncQueueStack.length - 1].push(cb);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
// Otherwise, push to async queue
|
|
36
|
-
asyncQueue.push(cb);
|
|
37
|
-
if (!inInteractive) {
|
|
38
|
-
scheduleAsyncFlush();
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export function syncBatch(cb) {
|
|
42
|
-
// Create a new queue for this sync batch
|
|
43
|
-
const queue = [];
|
|
44
|
-
syncQueueStack.push(queue);
|
|
45
|
-
try {
|
|
46
|
-
cb();
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
// Pop the queue even on error, but don't flush
|
|
50
|
-
syncQueueStack.pop();
|
|
51
|
-
throw e;
|
|
52
|
-
}
|
|
53
|
-
// CASCADING SYNCHRONOUS UPDATES
|
|
54
|
-
// ------------------------------
|
|
55
|
-
// Keep flushing the queue in a loop while it has work. This is critical for handling
|
|
56
|
-
// cascading reactive updates where one observer's notification triggers another.
|
|
57
|
-
//
|
|
58
|
-
// Example cascade: state → derived → component
|
|
59
|
-
// 1. User updates state in a syncBatch
|
|
60
|
-
// 2. Derived observer is notified and queued
|
|
61
|
-
// 3. Derived observer runs, marks derived as dirty
|
|
62
|
-
// 4. Derived's signal notifies component observers
|
|
63
|
-
// 5. Component observers are added to the SAME queue (because it's still on the stack)
|
|
64
|
-
// 6. Loop continues, flushing component observers
|
|
65
|
-
//
|
|
66
|
-
// By keeping the queue on the stack during the flush loop, all synchronous cascades
|
|
67
|
-
// are captured in the same batch. This ensures:
|
|
68
|
-
// - No updates escape to the async queue
|
|
69
|
-
// - Components render with fully updated derived values
|
|
70
|
-
// - Deduplication works across the entire cascade (same observer can't be queued twice)
|
|
71
|
-
//
|
|
72
|
-
// The loop terminates when all cascades have settled (queue is empty).
|
|
73
|
-
while (queue.length > 0) {
|
|
74
|
-
for (let i = 0; i < queue.length; i++) {
|
|
75
|
-
const cb = queue[i];
|
|
76
|
-
queue[i] = undefined;
|
|
77
|
-
cb();
|
|
78
|
-
cb.__queued = false;
|
|
79
|
-
}
|
|
80
|
-
queue.length = 0;
|
|
81
|
-
}
|
|
82
|
-
// Pop the queue after everything is flushed
|
|
83
|
-
syncQueueStack.pop();
|
|
84
|
-
}
|
package/dist/observation.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ---------------------------------------------------------------------------
|
|
3
|
-
* OBSERVER–SIGNAL SYSTEM (OPTIMIZED / LOW MEMORY / ZERO PENDING ARRAYS)
|
|
4
|
-
* ---------------------------------------------------------------------------
|
|
5
|
-
*
|
|
6
|
-
* STRATEGY OVERVIEW
|
|
7
|
-
* -----------------
|
|
8
|
-
* Instead of storing subscriber callbacks and disposer closures,
|
|
9
|
-
* we model the connection between an Observer and a Signal using a
|
|
10
|
-
* lightweight Subscription node.
|
|
11
|
-
*
|
|
12
|
-
* Each Subscription is owned *by both sides*:
|
|
13
|
-
* - Signal keeps a doubly-linked list of all Subscriptions.
|
|
14
|
-
* - Observer keeps a doubly-linked list of all Subscriptions it has made.
|
|
15
|
-
*
|
|
16
|
-
* This gives several important advantages:
|
|
17
|
-
*
|
|
18
|
-
* ✔ NO per-subscription closures (disposers)
|
|
19
|
-
* ✔ NO Set allocations in Signal or Observer
|
|
20
|
-
* ✔ NO Array copies during notify()
|
|
21
|
-
* ✔ Unsubscribing while iterating is safe (linked-list + cached "next")
|
|
22
|
-
* ✔ New subscriptions inside notify() DO NOT fire in the same notify pass
|
|
23
|
-
* (using an epoch barrier)
|
|
24
|
-
* ✔ Observer.clearSignals() is O(n) with real O(1) unlink
|
|
25
|
-
* ✔ Memory overhead is extremely small (one node with 4 pointers)
|
|
26
|
-
*
|
|
27
|
-
* NOTIFY BARRIER
|
|
28
|
-
* --------------
|
|
29
|
-
* A global integer `epoch` increments for each notify().
|
|
30
|
-
* New subscriptions created during notify() store `createdAtEpoch = epoch+1`.
|
|
31
|
-
* During traversal, we only fire subscriptions where:
|
|
32
|
-
*
|
|
33
|
-
* sub.createdAtEpoch <= epoch
|
|
34
|
-
*
|
|
35
|
-
* This guarantees consistency and prevents "late" subscribers from firing too early.
|
|
36
|
-
*
|
|
37
|
-
* ---------------------------------------------------------------------------
|
|
38
|
-
*/
|
|
39
|
-
export declare function getCurrentObserver(): Observer | undefined;
|
|
40
|
-
/**
|
|
41
|
-
* A lightweight link connecting ONE observer ↔ ONE signal.
|
|
42
|
-
* Stored in a linked list on both sides.
|
|
43
|
-
*/
|
|
44
|
-
declare class Subscription {
|
|
45
|
-
signal: Signal;
|
|
46
|
-
observer: Observer;
|
|
47
|
-
prevInSignal: Subscription | null;
|
|
48
|
-
nextInSignal: Subscription | null;
|
|
49
|
-
prevInObserver: Subscription | null;
|
|
50
|
-
nextInObserver: Subscription | null;
|
|
51
|
-
active: boolean;
|
|
52
|
-
createdAtEpoch: number;
|
|
53
|
-
constructor(signal: Signal, observer: Observer, epoch: number);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* SIGNAL — Notifies subscribed observers.
|
|
57
|
-
*/
|
|
58
|
-
export declare class Signal {
|
|
59
|
-
private head;
|
|
60
|
-
private tail;
|
|
61
|
-
private epoch;
|
|
62
|
-
/** INTERNAL: Create a subscription from observer → signal. */
|
|
63
|
-
_subscribe(observer: Observer): Subscription;
|
|
64
|
-
/** INTERNAL: Unlink a subscription from this signal. */
|
|
65
|
-
_unsubscribe(sub: Subscription): void;
|
|
66
|
-
/** Notify all observers.
|
|
67
|
-
* Safe even if observers unsubscribe themselves or subscribe new ones mid-run.
|
|
68
|
-
*/
|
|
69
|
-
notify(): void;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* OBSERVER — Reacts to signal changes.
|
|
73
|
-
* Typically wraps a computation or component.
|
|
74
|
-
*/
|
|
75
|
-
export declare class Observer {
|
|
76
|
-
isDisposed: boolean;
|
|
77
|
-
private subsHead;
|
|
78
|
-
private subsTail;
|
|
79
|
-
private readonly onNotify;
|
|
80
|
-
constructor(onNotify: () => void, shouldQueue?: boolean);
|
|
81
|
-
/** Called from Signal.notify() */
|
|
82
|
-
_notify(): void;
|
|
83
|
-
/** Subscribe this observer to a signal */
|
|
84
|
-
subscribeSignal(signal: Signal): void;
|
|
85
|
-
/** Remove all signal subscriptions (fast + safe) */
|
|
86
|
-
clearSignals(): void;
|
|
87
|
-
/** Begin dependency collection */
|
|
88
|
-
observe(): () => void;
|
|
89
|
-
/** Dispose the observer completely */
|
|
90
|
-
dispose(): void;
|
|
91
|
-
}
|
|
92
|
-
export {};
|
|
93
|
-
//# sourceMappingURL=observation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"observation.d.ts","sourceRoot":"","sources":["../src/observation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AASH,wBAAgB,kBAAkB,yBAEjC;AAED;;;GAGG;AACH,cAAM,YAAY;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IAGnB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IACzC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IAGzC,cAAc,EAAE,YAAY,GAAG,IAAI,CAAQ;IAC3C,cAAc,EAAE,YAAY,GAAG,IAAI,CAAQ;IAG3C,MAAM,UAAQ;IAGd,cAAc,EAAE,MAAM,CAAC;gBAEX,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM;CAK9D;AAED;;GAEG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,IAAI,CAA6B;IAGzC,OAAO,CAAC,KAAK,CAAK;IAElB,8DAA8D;IAC9D,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,YAAY;IAe5C,wDAAwD;IACxD,YAAY,CAAC,GAAG,EAAE,YAAY;IAe9B;;OAEG;IACH,MAAM;CAgBP;AAED;;;GAGG;AACH,qBAAa,QAAQ;IACnB,UAAU,UAAS;IAGnB,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAA6B;IAG7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;gBAE1B,QAAQ,EAAE,MAAM,IAAI,EAAE,WAAW,UAAO;IAcpD,kCAAkC;IAClC,OAAO;IAMP,0CAA0C;IAC1C,eAAe,CAAC,MAAM,EAAE,MAAM;IAe9B,oDAAoD;IACpD,YAAY;IAiBZ,kCAAkC;IAClC,OAAO;IAUP,sCAAsC;IACtC,OAAO;CAKR"}
|
package/dist/observation.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ---------------------------------------------------------------------------
|
|
3
|
-
* OBSERVER–SIGNAL SYSTEM (OPTIMIZED / LOW MEMORY / ZERO PENDING ARRAYS)
|
|
4
|
-
* ---------------------------------------------------------------------------
|
|
5
|
-
*
|
|
6
|
-
* STRATEGY OVERVIEW
|
|
7
|
-
* -----------------
|
|
8
|
-
* Instead of storing subscriber callbacks and disposer closures,
|
|
9
|
-
* we model the connection between an Observer and a Signal using a
|
|
10
|
-
* lightweight Subscription node.
|
|
11
|
-
*
|
|
12
|
-
* Each Subscription is owned *by both sides*:
|
|
13
|
-
* - Signal keeps a doubly-linked list of all Subscriptions.
|
|
14
|
-
* - Observer keeps a doubly-linked list of all Subscriptions it has made.
|
|
15
|
-
*
|
|
16
|
-
* This gives several important advantages:
|
|
17
|
-
*
|
|
18
|
-
* ✔ NO per-subscription closures (disposers)
|
|
19
|
-
* ✔ NO Set allocations in Signal or Observer
|
|
20
|
-
* ✔ NO Array copies during notify()
|
|
21
|
-
* ✔ Unsubscribing while iterating is safe (linked-list + cached "next")
|
|
22
|
-
* ✔ New subscriptions inside notify() DO NOT fire in the same notify pass
|
|
23
|
-
* (using an epoch barrier)
|
|
24
|
-
* ✔ Observer.clearSignals() is O(n) with real O(1) unlink
|
|
25
|
-
* ✔ Memory overhead is extremely small (one node with 4 pointers)
|
|
26
|
-
*
|
|
27
|
-
* NOTIFY BARRIER
|
|
28
|
-
* --------------
|
|
29
|
-
* A global integer `epoch` increments for each notify().
|
|
30
|
-
* New subscriptions created during notify() store `createdAtEpoch = epoch+1`.
|
|
31
|
-
* During traversal, we only fire subscriptions where:
|
|
32
|
-
*
|
|
33
|
-
* sub.createdAtEpoch <= epoch
|
|
34
|
-
*
|
|
35
|
-
* This guarantees consistency and prevents "late" subscribers from firing too early.
|
|
36
|
-
*
|
|
37
|
-
* ---------------------------------------------------------------------------
|
|
38
|
-
*/
|
|
39
|
-
import { queue } from "./batch";
|
|
40
|
-
// GLOBAL OBSERVER STACK (for dependency tracking)
|
|
41
|
-
const observerStack = [];
|
|
42
|
-
let stackTop = -1;
|
|
43
|
-
// Get the active observer during a render/compute
|
|
44
|
-
export function getCurrentObserver() {
|
|
45
|
-
return stackTop >= 0 ? observerStack[stackTop] : undefined;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* A lightweight link connecting ONE observer ↔ ONE signal.
|
|
49
|
-
* Stored in a linked list on both sides.
|
|
50
|
-
*/
|
|
51
|
-
class Subscription {
|
|
52
|
-
signal;
|
|
53
|
-
observer;
|
|
54
|
-
// Linked list pointers within the signal
|
|
55
|
-
prevInSignal = null;
|
|
56
|
-
nextInSignal = null;
|
|
57
|
-
// Linked list pointers within the observer
|
|
58
|
-
prevInObserver = null;
|
|
59
|
-
nextInObserver = null;
|
|
60
|
-
// Whether this subscription is active
|
|
61
|
-
active = true;
|
|
62
|
-
// Used for the notify barrier
|
|
63
|
-
createdAtEpoch;
|
|
64
|
-
constructor(signal, observer, epoch) {
|
|
65
|
-
this.signal = signal;
|
|
66
|
-
this.observer = observer;
|
|
67
|
-
this.createdAtEpoch = epoch;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* SIGNAL — Notifies subscribed observers.
|
|
72
|
-
*/
|
|
73
|
-
export class Signal {
|
|
74
|
-
head = null;
|
|
75
|
-
tail = null;
|
|
76
|
-
// Incremented for every notify() call
|
|
77
|
-
epoch = 0;
|
|
78
|
-
/** INTERNAL: Create a subscription from observer → signal. */
|
|
79
|
-
_subscribe(observer) {
|
|
80
|
-
const sub = new Subscription(this, observer, this.epoch + 1);
|
|
81
|
-
// Attach to signal's linked list
|
|
82
|
-
if (this.tail) {
|
|
83
|
-
this.tail.nextInSignal = sub;
|
|
84
|
-
sub.prevInSignal = this.tail;
|
|
85
|
-
this.tail = sub;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
this.head = this.tail = sub;
|
|
89
|
-
}
|
|
90
|
-
return sub;
|
|
91
|
-
}
|
|
92
|
-
/** INTERNAL: Unlink a subscription from this signal. */
|
|
93
|
-
_unsubscribe(sub) {
|
|
94
|
-
if (!sub.active)
|
|
95
|
-
return;
|
|
96
|
-
sub.active = false;
|
|
97
|
-
const { prevInSignal, nextInSignal } = sub;
|
|
98
|
-
if (prevInSignal)
|
|
99
|
-
prevInSignal.nextInSignal = nextInSignal;
|
|
100
|
-
else
|
|
101
|
-
this.head = nextInSignal;
|
|
102
|
-
if (nextInSignal)
|
|
103
|
-
nextInSignal.prevInSignal = prevInSignal;
|
|
104
|
-
else
|
|
105
|
-
this.tail = prevInSignal;
|
|
106
|
-
sub.prevInSignal = sub.nextInSignal = null;
|
|
107
|
-
}
|
|
108
|
-
/** Notify all observers.
|
|
109
|
-
* Safe even if observers unsubscribe themselves or subscribe new ones mid-run.
|
|
110
|
-
*/
|
|
111
|
-
notify() {
|
|
112
|
-
if (!this.head)
|
|
113
|
-
return;
|
|
114
|
-
const barrier = ++this.epoch; // new subs won't fire now
|
|
115
|
-
let sub = this.head;
|
|
116
|
-
while (sub) {
|
|
117
|
-
const next = sub.nextInSignal; // cache next → safe if sub unlinks itself
|
|
118
|
-
if (sub.active && sub.createdAtEpoch <= barrier) {
|
|
119
|
-
sub.observer._notify();
|
|
120
|
-
}
|
|
121
|
-
sub = next;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* OBSERVER — Reacts to signal changes.
|
|
127
|
-
* Typically wraps a computation or component.
|
|
128
|
-
*/
|
|
129
|
-
export class Observer {
|
|
130
|
-
isDisposed = false;
|
|
131
|
-
// Doubly-linked list of all subscriptions from this observer
|
|
132
|
-
subsHead = null;
|
|
133
|
-
subsTail = null;
|
|
134
|
-
// Only ONE notify callback closure per observer
|
|
135
|
-
onNotify;
|
|
136
|
-
constructor(onNotify, shouldQueue = true) {
|
|
137
|
-
const onNotifyQueued = onNotify;
|
|
138
|
-
onNotifyQueued.__queued = false;
|
|
139
|
-
if (shouldQueue) {
|
|
140
|
-
this.onNotify = () => {
|
|
141
|
-
if (onNotifyQueued.__queued)
|
|
142
|
-
return;
|
|
143
|
-
queue(onNotify);
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this.onNotify = onNotify;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
/** Called from Signal.notify() */
|
|
151
|
-
_notify() {
|
|
152
|
-
if (!this.isDisposed) {
|
|
153
|
-
this.onNotify();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
/** Subscribe this observer to a signal */
|
|
157
|
-
subscribeSignal(signal) {
|
|
158
|
-
if (this.isDisposed)
|
|
159
|
-
return;
|
|
160
|
-
const sub = signal._subscribe(this);
|
|
161
|
-
// Add to observer's linked list
|
|
162
|
-
if (this.subsTail) {
|
|
163
|
-
this.subsTail.nextInObserver = sub;
|
|
164
|
-
sub.prevInObserver = this.subsTail;
|
|
165
|
-
this.subsTail = sub;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
this.subsHead = this.subsTail = sub;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/** Remove all signal subscriptions (fast + safe) */
|
|
172
|
-
clearSignals() {
|
|
173
|
-
let sub = this.subsHead;
|
|
174
|
-
this.subsHead = this.subsTail = null;
|
|
175
|
-
while (sub) {
|
|
176
|
-
const next = sub.nextInObserver;
|
|
177
|
-
// Unlink from the signal
|
|
178
|
-
sub.signal._unsubscribe(sub);
|
|
179
|
-
// Clean up observer-side pointers
|
|
180
|
-
sub.prevInObserver = sub.nextInObserver = null;
|
|
181
|
-
sub = next;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/** Begin dependency collection */
|
|
185
|
-
observe() {
|
|
186
|
-
this.clearSignals();
|
|
187
|
-
observerStack[++stackTop] = this;
|
|
188
|
-
// Return a disposer for this observation frame
|
|
189
|
-
return () => {
|
|
190
|
-
stackTop--;
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/** Dispose the observer completely */
|
|
194
|
-
dispose() {
|
|
195
|
-
if (this.isDisposed)
|
|
196
|
-
return;
|
|
197
|
-
this.isDisposed = true;
|
|
198
|
-
this.clearSignals();
|
|
199
|
-
}
|
|
200
|
-
}
|