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.
@@ -1,5 +1,2 @@
1
- import "inferno";
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":"AAKA,OAAO,SAAS,CAAC;AAGjB,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAInC,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;AACnE,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC"}
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"}
@@ -1,7 +1,5 @@
1
- // JSX runtime for TypeScript type checking
2
- // The actual JSX transformation is done by SWC plugin at build time
3
- // This file only provides type declarations for TypeScript's "jsx": "react-jsx" mode
4
- // Import inferno to get the global JSX namespace
5
- import "inferno";
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
  }
@@ -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,EAGP,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,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rask-ui",
3
- "version": "0.29.4",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/dist/batch.d.ts DELETED
@@ -1,6 +0,0 @@
1
- export type QueuedCallback = (() => void) & {
2
- __queued: boolean;
3
- };
4
- export declare function queue(cb: QueuedCallback): void;
5
- export declare function syncBatch(cb: () => void): void;
6
- //# sourceMappingURL=batch.d.ts.map
@@ -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
- }
@@ -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"}
@@ -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
- }