@formwright/core 0.1.0 → 0.2.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/README.md +96 -0
- package/dist/chunk-O4DUMDBU.js +3 -0
- package/dist/chunk-O4DUMDBU.js.map +1 -0
- package/dist/index.cjs +214 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -9
- package/dist/index.d.ts +51 -9
- package/dist/index.js +170 -34
- package/dist/index.js.map +1 -1
- package/dist/reactive.cjs +27 -164
- package/dist/reactive.cjs.map +1 -1
- package/dist/reactive.d.cts +1 -45
- package/dist/reactive.d.ts +1 -45
- package/dist/reactive.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-EZUHEI5F.js +0 -162
- package/dist/chunk-EZUHEI5F.js.map +0 -1
package/dist/chunk-EZUHEI5F.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// src/reactive.ts
|
|
2
|
-
var activeObserver = null;
|
|
3
|
-
var batchDepth = 0;
|
|
4
|
-
var pendingEffects = /* @__PURE__ */ new Set();
|
|
5
|
-
var flushing = false;
|
|
6
|
-
function link(source) {
|
|
7
|
-
const obs = activeObserver;
|
|
8
|
-
if (obs === null) return;
|
|
9
|
-
if (!source.observers.has(obs)) {
|
|
10
|
-
source.observers.add(obs);
|
|
11
|
-
obs.sources.add(source);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function clearSources(obs) {
|
|
15
|
-
for (const src of obs.sources) src.observers.delete(obs);
|
|
16
|
-
obs.sources.clear();
|
|
17
|
-
}
|
|
18
|
-
function flush() {
|
|
19
|
-
if (flushing) return;
|
|
20
|
-
flushing = true;
|
|
21
|
-
try {
|
|
22
|
-
while (pendingEffects.size > 0) {
|
|
23
|
-
const next = pendingEffects.values().next().value;
|
|
24
|
-
pendingEffects.delete(next);
|
|
25
|
-
next.run();
|
|
26
|
-
}
|
|
27
|
-
} finally {
|
|
28
|
-
flushing = false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
var SignalNode = class {
|
|
32
|
-
constructor(value) {
|
|
33
|
-
this.value = value;
|
|
34
|
-
}
|
|
35
|
-
value;
|
|
36
|
-
observers = /* @__PURE__ */ new Set();
|
|
37
|
-
get() {
|
|
38
|
-
link(this);
|
|
39
|
-
return this.value;
|
|
40
|
-
}
|
|
41
|
-
peek() {
|
|
42
|
-
return this.value;
|
|
43
|
-
}
|
|
44
|
-
set(next) {
|
|
45
|
-
if (Object.is(next, this.value)) return;
|
|
46
|
-
this.value = next;
|
|
47
|
-
for (const obs of [...this.observers]) obs.notify();
|
|
48
|
-
if (batchDepth === 0) flush();
|
|
49
|
-
}
|
|
50
|
-
update(fn) {
|
|
51
|
-
this.set(fn(this.value));
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var ComputedNode = class {
|
|
55
|
-
constructor(fn) {
|
|
56
|
-
this.fn = fn;
|
|
57
|
-
}
|
|
58
|
-
fn;
|
|
59
|
-
observers = /* @__PURE__ */ new Set();
|
|
60
|
-
sources = /* @__PURE__ */ new Set();
|
|
61
|
-
value;
|
|
62
|
-
dirty = true;
|
|
63
|
-
notify() {
|
|
64
|
-
if (this.dirty) return;
|
|
65
|
-
this.dirty = true;
|
|
66
|
-
for (const obs of [...this.observers]) obs.notify();
|
|
67
|
-
}
|
|
68
|
-
get() {
|
|
69
|
-
link(this);
|
|
70
|
-
if (this.dirty) this.recompute();
|
|
71
|
-
return this.value;
|
|
72
|
-
}
|
|
73
|
-
peek() {
|
|
74
|
-
if (this.dirty) this.recompute();
|
|
75
|
-
return this.value;
|
|
76
|
-
}
|
|
77
|
-
recompute() {
|
|
78
|
-
clearSources(this);
|
|
79
|
-
const prev = activeObserver;
|
|
80
|
-
activeObserver = this;
|
|
81
|
-
try {
|
|
82
|
-
this.value = this.fn();
|
|
83
|
-
this.dirty = false;
|
|
84
|
-
} finally {
|
|
85
|
-
activeObserver = prev;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
var EffectNode = class {
|
|
90
|
-
constructor(fn) {
|
|
91
|
-
this.fn = fn;
|
|
92
|
-
this.run();
|
|
93
|
-
}
|
|
94
|
-
fn;
|
|
95
|
-
sources = /* @__PURE__ */ new Set();
|
|
96
|
-
cleanup = void 0;
|
|
97
|
-
disposed = false;
|
|
98
|
-
notify() {
|
|
99
|
-
if (this.disposed) return;
|
|
100
|
-
pendingEffects.add(this);
|
|
101
|
-
}
|
|
102
|
-
run() {
|
|
103
|
-
if (this.disposed) return;
|
|
104
|
-
this.runCleanup();
|
|
105
|
-
clearSources(this);
|
|
106
|
-
const prev = activeObserver;
|
|
107
|
-
activeObserver = this;
|
|
108
|
-
try {
|
|
109
|
-
this.cleanup = this.fn();
|
|
110
|
-
} finally {
|
|
111
|
-
activeObserver = prev;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
runCleanup() {
|
|
115
|
-
if (typeof this.cleanup === "function") {
|
|
116
|
-
this.cleanup();
|
|
117
|
-
this.cleanup = void 0;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
dispose() {
|
|
121
|
-
if (this.disposed) return;
|
|
122
|
-
this.disposed = true;
|
|
123
|
-
this.runCleanup();
|
|
124
|
-
clearSources(this);
|
|
125
|
-
pendingEffects.delete(this);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
function signal(initial) {
|
|
129
|
-
return new SignalNode(initial);
|
|
130
|
-
}
|
|
131
|
-
function computed(fn) {
|
|
132
|
-
return new ComputedNode(fn);
|
|
133
|
-
}
|
|
134
|
-
function effect(fn) {
|
|
135
|
-
const node = new EffectNode(fn);
|
|
136
|
-
return () => node.dispose();
|
|
137
|
-
}
|
|
138
|
-
function untrack(fn) {
|
|
139
|
-
const prev = activeObserver;
|
|
140
|
-
activeObserver = null;
|
|
141
|
-
try {
|
|
142
|
-
return fn();
|
|
143
|
-
} finally {
|
|
144
|
-
activeObserver = prev;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function batch(fn) {
|
|
148
|
-
batchDepth++;
|
|
149
|
-
try {
|
|
150
|
-
return fn();
|
|
151
|
-
} finally {
|
|
152
|
-
batchDepth--;
|
|
153
|
-
if (batchDepth === 0) flush();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function isTracking() {
|
|
157
|
-
return activeObserver !== null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export { batch, computed, effect, isTracking, signal, untrack };
|
|
161
|
-
//# sourceMappingURL=chunk-EZUHEI5F.js.map
|
|
162
|
-
//# sourceMappingURL=chunk-EZUHEI5F.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reactive.ts"],"names":[],"mappings":"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","file":"chunk-EZUHEI5F.js","sourcesContent":["/**\n * Fine-grained reactivity — the engine behind Formwright's surgical DOM updates.\n *\n * This is a small, correct, **zero-dependency** push-pull implementation:\n * - {@link signal} holds a value and the set of observers that read it.\n * - {@link computed} is lazy and cached: it recomputes only when read *after* a\n * dependency changed.\n * - {@link effect} runs immediately and re-runs when any signal/computed it read\n * changes — this is what binds a single value to a single DOM node.\n *\n * Writing a signal marks the dependency graph dirty and synchronously flushes the\n * affected effects (so DOM updates are immediate and deterministic). Only effects\n * that actually read the changed value re-run — there is no diffing and no virtual\n * DOM. The public surface here is intentionally framework-agnostic and swappable\n * (e.g. for `alien-signals`) without touching the rest of the library.\n */\n\nexport interface ReadSignal<T> {\n /** Read the value and subscribe the current effect/computed to changes. */\n get(): T;\n /** Read the value without subscribing. */\n peek(): T;\n}\n\nexport interface WriteSignal<T> extends ReadSignal<T> {\n set(value: T): void;\n update(fn: (prev: T) => T): void;\n}\n\nexport type Dispose = () => void;\n\ninterface Observer {\n /** Sources this observer currently depends on. */\n readonly sources: Set<Source>;\n /** Called when a dependency changed; the observer reacts (recompute or queue). */\n notify(): void;\n}\n\ninterface Source {\n /** Observers currently subscribed to this source. */\n readonly observers: Set<Observer>;\n}\n\nlet activeObserver: Observer | null = null;\nlet batchDepth = 0;\nconst pendingEffects = new Set<EffectNode>();\nlet flushing = false;\n\nfunction link(source: Source): void {\n const obs = activeObserver;\n if (obs === null) return;\n if (!source.observers.has(obs)) {\n source.observers.add(obs);\n obs.sources.add(source);\n }\n}\n\nfunction clearSources(obs: Observer): void {\n for (const src of obs.sources) src.observers.delete(obs);\n obs.sources.clear();\n}\n\nfunction flush(): void {\n if (flushing) return;\n flushing = true;\n try {\n // Re-check each iteration: running an effect may queue more effects.\n while (pendingEffects.size > 0) {\n const next = pendingEffects.values().next().value as EffectNode;\n pendingEffects.delete(next);\n next.run();\n }\n } finally {\n flushing = false;\n }\n}\n\nclass SignalNode<T> implements Source {\n readonly observers = new Set<Observer>();\n constructor(private value: T) {}\n\n get(): T {\n link(this);\n return this.value;\n }\n\n peek(): T {\n return this.value;\n }\n\n set(next: T): void {\n if (Object.is(next, this.value)) return;\n this.value = next;\n // Snapshot observers: notify() may mutate downstream sets, not ours.\n for (const obs of [...this.observers]) obs.notify();\n if (batchDepth === 0) flush();\n }\n\n update(fn: (prev: T) => T): void {\n this.set(fn(this.value));\n }\n}\n\nclass ComputedNode<T> implements Source, Observer {\n readonly observers = new Set<Observer>();\n readonly sources = new Set<Source>();\n private value!: T;\n private dirty = true;\n\n constructor(private readonly fn: () => T) {}\n\n notify(): void {\n if (this.dirty) return; // already invalidated; observers already notified\n this.dirty = true;\n for (const obs of [...this.observers]) obs.notify();\n }\n\n get(): T {\n link(this);\n if (this.dirty) this.recompute();\n return this.value;\n }\n\n peek(): T {\n if (this.dirty) this.recompute();\n return this.value;\n }\n\n private recompute(): void {\n clearSources(this);\n const prev = activeObserver;\n activeObserver = this;\n try {\n this.value = this.fn();\n this.dirty = false;\n } finally {\n activeObserver = prev;\n }\n }\n}\n\nclass EffectNode implements Observer {\n readonly sources = new Set<Source>();\n private cleanup: (() => void) | void = undefined;\n private disposed = false;\n\n constructor(private readonly fn: () => void | (() => void)) {\n this.run();\n }\n\n notify(): void {\n if (this.disposed) return;\n pendingEffects.add(this);\n }\n\n run(): void {\n if (this.disposed) return;\n this.runCleanup();\n clearSources(this);\n const prev = activeObserver;\n activeObserver = this;\n try {\n this.cleanup = this.fn();\n } finally {\n activeObserver = prev;\n }\n }\n\n private runCleanup(): void {\n if (typeof this.cleanup === \"function\") {\n this.cleanup();\n this.cleanup = undefined;\n }\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.runCleanup();\n clearSources(this);\n pendingEffects.delete(this);\n }\n}\n\n/** Create a writable reactive value. */\nexport function signal<T>(initial: T): WriteSignal<T> {\n return new SignalNode(initial);\n}\n\n/** Create a lazily-evaluated, cached derived value. */\nexport function computed<T>(fn: () => T): ReadSignal<T> {\n return new ComputedNode(fn);\n}\n\n/**\n * Run `fn` immediately and again whenever a signal/computed it read changes.\n * `fn` may return a cleanup function, run before each re-run and on dispose.\n * Returns a {@link Dispose} to stop the effect.\n */\nexport function effect(fn: () => void | (() => void)): Dispose {\n const node = new EffectNode(fn);\n return () => node.dispose();\n}\n\n/** Read reactive values inside `fn` without subscribing the current observer. */\nexport function untrack<T>(fn: () => T): T {\n const prev = activeObserver;\n activeObserver = null;\n try {\n return fn();\n } finally {\n activeObserver = prev;\n }\n}\n\n/** Batch multiple writes so effects flush once, after `fn` returns. */\nexport function batch<T>(fn: () => T): T {\n batchDepth++;\n try {\n return fn();\n } finally {\n batchDepth--;\n if (batchDepth === 0) flush();\n }\n}\n\n/** True when called inside an effect/computed tracking context. */\nexport function isTracking(): boolean {\n return activeObserver !== null;\n}\n"]}
|