@opentf/web 0.4.0 → 0.6.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 +30 -28
- package/components/Link.jsx +43 -0
- package/core/errors.js +48 -0
- package/core/reactive.js +190 -0
- package/core/signals.js +207 -0
- package/index.js +13 -0
- package/package.json +27 -18
- package/runtime/clipboard.js +47 -0
- package/runtime/code-block.js +47 -0
- package/runtime/context.js +99 -0
- package/runtime/dom.js +368 -0
- package/runtime/error-boundary.js +109 -0
- package/runtime/events.js +17 -0
- package/runtime/hydrate.js +121 -0
- package/runtime/index.js +22 -0
- package/runtime/lifecycle.js +9 -0
- package/runtime/mount.js +49 -0
- package/runtime/portal.js +59 -0
- package/runtime/raw-html.js +33 -0
- package/runtime/router.js +392 -0
- package/server/builtins.js +16 -0
- package/server/head.js +191 -0
- package/server/index.js +8 -0
- package/server/render.js +98 -0
- package/server/ssg-runtime.js +112 -0
- package/dist/CHANGELOG.md +0 -49
- package/dist/README.md +0 -41
- package/dist/babel.config.cjs +0 -6
- package/dist/compiler/babel-plugin.cjs +0 -798
- package/dist/core/mount.js +0 -32
- package/dist/index.js +0 -7
- package/dist/node_modules/@babel/core/LICENSE +0 -22
- package/dist/node_modules/@babel/core/README.md +0 -19
- package/dist/node_modules/@babel/core/lib/config/cache-contexts.js +0 -5
- package/dist/node_modules/@babel/core/lib/config/cache-contexts.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/caching.js +0 -261
- package/dist/node_modules/@babel/core/lib/config/caching.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/config-chain.js +0 -469
- package/dist/node_modules/@babel/core/lib/config/config-chain.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/config-descriptors.js +0 -190
- package/dist/node_modules/@babel/core/lib/config/config-descriptors.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/configuration.js +0 -290
- package/dist/node_modules/@babel/core/lib/config/files/configuration.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/import.cjs +0 -6
- package/dist/node_modules/@babel/core/lib/config/files/import.cjs.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/index-browser.js +0 -58
- package/dist/node_modules/@babel/core/lib/config/files/index-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/index.js +0 -78
- package/dist/node_modules/@babel/core/lib/config/files/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/module-types.js +0 -203
- package/dist/node_modules/@babel/core/lib/config/files/module-types.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/package.js +0 -61
- package/dist/node_modules/@babel/core/lib/config/files/package.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/plugins.js +0 -220
- package/dist/node_modules/@babel/core/lib/config/files/plugins.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/types.js +0 -5
- package/dist/node_modules/@babel/core/lib/config/files/types.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/files/utils.js +0 -36
- package/dist/node_modules/@babel/core/lib/config/files/utils.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/full.js +0 -312
- package/dist/node_modules/@babel/core/lib/config/full.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js +0 -85
- package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js +0 -23
- package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/helpers/environment.js +0 -12
- package/dist/node_modules/@babel/core/lib/config/helpers/environment.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/index.js +0 -87
- package/dist/node_modules/@babel/core/lib/config/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/item.js +0 -67
- package/dist/node_modules/@babel/core/lib/config/item.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/partial.js +0 -158
- package/dist/node_modules/@babel/core/lib/config/partial.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js +0 -38
- package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/plugin.js +0 -33
- package/dist/node_modules/@babel/core/lib/config/plugin.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/printer.js +0 -113
- package/dist/node_modules/@babel/core/lib/config/printer.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js +0 -41
- package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/resolve-targets.js +0 -61
- package/dist/node_modules/@babel/core/lib/config/resolve-targets.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/util.js +0 -31
- package/dist/node_modules/@babel/core/lib/config/util.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js +0 -277
- package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/options.js +0 -187
- package/dist/node_modules/@babel/core/lib/config/validation/options.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/plugins.js +0 -67
- package/dist/node_modules/@babel/core/lib/config/validation/plugins.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/config/validation/removed.js +0 -68
- package/dist/node_modules/@babel/core/lib/config/validation/removed.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/errors/config-error.js +0 -18
- package/dist/node_modules/@babel/core/lib/errors/config-error.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js +0 -98
- package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/async.js +0 -90
- package/dist/node_modules/@babel/core/lib/gensync-utils/async.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js +0 -31
- package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js +0 -58
- package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/index.js +0 -230
- package/dist/node_modules/@babel/core/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parse.js +0 -45
- package/dist/node_modules/@babel/core/lib/parse.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parser/index.js +0 -85
- package/dist/node_modules/@babel/core/lib/parser/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js +0 -337
- package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js +0 -144
- package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-ast.js +0 -48
- package/dist/node_modules/@babel/core/lib/transform-ast.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-file-browser.js +0 -23
- package/dist/node_modules/@babel/core/lib/transform-file-browser.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform-file.js +0 -40
- package/dist/node_modules/@babel/core/lib/transform-file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transform.js +0 -47
- package/dist/node_modules/@babel/core/lib/transform.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js +0 -84
- package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs +0 -4
- package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/file.js +0 -204
- package/dist/node_modules/@babel/core/lib/transformation/file/file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/generate.js +0 -84
- package/dist/node_modules/@babel/core/lib/transformation/file/generate.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js +0 -37
- package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/index.js +0 -90
- package/dist/node_modules/@babel/core/lib/transformation/index.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js +0 -127
- package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js +0 -59
- package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js +0 -48
- package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js +0 -54
- package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js.map +0 -1
- package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js +0 -1042
- package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js.map +0 -1
- package/dist/node_modules/@babel/core/package.json +0 -82
- package/dist/node_modules/@babel/core/src/config/files/index-browser.ts +0 -115
- package/dist/node_modules/@babel/core/src/config/files/index.ts +0 -30
- package/dist/node_modules/@babel/core/src/config/resolve-targets-browser.ts +0 -42
- package/dist/node_modules/@babel/core/src/config/resolve-targets.ts +0 -54
- package/dist/node_modules/@babel/core/src/transform-file-browser.ts +0 -33
- package/dist/node_modules/@babel/core/src/transform-file.ts +0 -56
- package/dist/node_modules/@babel/helper-module-imports/LICENSE +0 -22
- package/dist/node_modules/@babel/helper-module-imports/README.md +0 -19
- package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js +0 -122
- package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js +0 -304
- package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/index.js +0 -37
- package/dist/node_modules/@babel/helper-module-imports/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js +0 -11
- package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js.map +0 -1
- package/dist/node_modules/@babel/helper-module-imports/package.json +0 -28
- package/dist/node_modules/@babel/plugin-syntax-jsx/LICENSE +0 -22
- package/dist/node_modules/@babel/plugin-syntax-jsx/README.md +0 -19
- package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js +0 -21
- package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js.map +0 -1
- package/dist/node_modules/@babel/plugin-syntax-jsx/package.json +0 -33
- package/dist/node_modules/@preact/signals-core/CHANGELOG.md +0 -331
- package/dist/node_modules/@preact/signals-core/LICENSE +0 -21
- package/dist/node_modules/@preact/signals-core/README.md +0 -258
- package/dist/node_modules/@preact/signals-core/dist/signals-core.d.ts +0 -149
- package/dist/node_modules/@preact/signals-core/dist/signals-core.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs.map +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js +0 -1
- package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js.map +0 -1
- package/dist/node_modules/@preact/signals-core/package.json +0 -48
- package/dist/node_modules/@preact/signals-core/src/index.ts +0 -1121
- package/dist/package.json +0 -30
- package/dist/router/Link.js +0 -73
- package/dist/router/index.js +0 -216
- package/dist/runtime/For.js +0 -16
- package/dist/runtime/dom.js +0 -73
- package/dist/runtime/lifecycle.js +0 -25
- package/dist/runtime/props.js +0 -103
|
@@ -1,1121 +0,0 @@
|
|
|
1
|
-
// An named symbol/brand for detecting Signal instances even when they weren't
|
|
2
|
-
// created using the same signals library version.
|
|
3
|
-
const BRAND_SYMBOL = Symbol.for("preact-signals");
|
|
4
|
-
|
|
5
|
-
// Flags for Computed and Effect.
|
|
6
|
-
const RUNNING = 1 << 0;
|
|
7
|
-
const NOTIFIED = 1 << 1;
|
|
8
|
-
const OUTDATED = 1 << 2;
|
|
9
|
-
const DISPOSED = 1 << 3;
|
|
10
|
-
const HAS_ERROR = 1 << 4;
|
|
11
|
-
const TRACKING = 1 << 5;
|
|
12
|
-
|
|
13
|
-
// A linked list node used to track dependencies (sources) and dependents (targets).
|
|
14
|
-
// Also used to remember the source's last version number that the target saw.
|
|
15
|
-
type Node = {
|
|
16
|
-
// A source whose value the target depends on.
|
|
17
|
-
_source: Signal;
|
|
18
|
-
_prevSource?: Node;
|
|
19
|
-
_nextSource?: Node;
|
|
20
|
-
|
|
21
|
-
// A target that depends on the source and should be notified when the source changes.
|
|
22
|
-
_target: Computed | Effect;
|
|
23
|
-
_prevTarget?: Node;
|
|
24
|
-
_nextTarget?: Node;
|
|
25
|
-
|
|
26
|
-
// The version number of the source that target has last seen. We use version numbers
|
|
27
|
-
// instead of storing the source value, because source values can take arbitrary amount
|
|
28
|
-
// of memory, and computeds could hang on to them forever because they're lazily evaluated.
|
|
29
|
-
// Use the special value -1 to mark potentially unused but recyclable nodes.
|
|
30
|
-
_version: number;
|
|
31
|
-
|
|
32
|
-
// Used to remember & roll back the source's previous `._node` value when entering &
|
|
33
|
-
// exiting a new evaluation context.
|
|
34
|
-
_rollbackNode?: Node;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function startBatch() {
|
|
38
|
-
batchDepth++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function endBatch() {
|
|
42
|
-
if (batchDepth > 1) {
|
|
43
|
-
batchDepth--;
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let error: unknown;
|
|
48
|
-
let hasError = false;
|
|
49
|
-
reconcileBatchSnapshots();
|
|
50
|
-
|
|
51
|
-
while (batchedEffect !== undefined) {
|
|
52
|
-
let effect: Effect | undefined = batchedEffect;
|
|
53
|
-
batchedEffect = undefined;
|
|
54
|
-
|
|
55
|
-
batchIteration++;
|
|
56
|
-
|
|
57
|
-
while (effect !== undefined) {
|
|
58
|
-
const next: Effect | undefined = effect._nextBatchedEffect;
|
|
59
|
-
effect._nextBatchedEffect = undefined;
|
|
60
|
-
effect._flags &= ~NOTIFIED;
|
|
61
|
-
|
|
62
|
-
if (!(effect._flags & DISPOSED) && needsToRecompute(effect)) {
|
|
63
|
-
try {
|
|
64
|
-
effect._callback();
|
|
65
|
-
} catch (err) {
|
|
66
|
-
if (!hasError) {
|
|
67
|
-
error = err;
|
|
68
|
-
hasError = true;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
effect = next;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
batchIteration = 0;
|
|
76
|
-
batchDepth--;
|
|
77
|
-
|
|
78
|
-
if (hasError) {
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Combine multiple value updates into one "commit" at the end of the provided callback.
|
|
85
|
-
*
|
|
86
|
-
* Batches can be nested and changes are only flushed once the outermost batch callback
|
|
87
|
-
* completes.
|
|
88
|
-
*
|
|
89
|
-
* Accessing a signal that has been modified within a batch will reflect its updated
|
|
90
|
-
* value.
|
|
91
|
-
*
|
|
92
|
-
* @param fn The callback function.
|
|
93
|
-
* @returns The value returned by the callback.
|
|
94
|
-
*/
|
|
95
|
-
function batch<T>(fn: () => T): T {
|
|
96
|
-
if (batchDepth > 0) {
|
|
97
|
-
return fn();
|
|
98
|
-
}
|
|
99
|
-
currentBatchSnapshotVersion = ++batchSnapshotVersion;
|
|
100
|
-
/*@__INLINE__**/ startBatch();
|
|
101
|
-
try {
|
|
102
|
-
return fn();
|
|
103
|
-
} finally {
|
|
104
|
-
endBatch();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Currently evaluated computed or effect.
|
|
109
|
-
let evalContext: Computed | Effect | undefined = undefined;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Run a callback function that can access signal values without
|
|
113
|
-
* subscribing to the signal updates.
|
|
114
|
-
*
|
|
115
|
-
* @param fn The callback function.
|
|
116
|
-
* @returns The value returned by the callback.
|
|
117
|
-
*/
|
|
118
|
-
function untracked<T>(fn: () => T): T {
|
|
119
|
-
const prevContext = evalContext;
|
|
120
|
-
evalContext = undefined;
|
|
121
|
-
try {
|
|
122
|
-
return fn();
|
|
123
|
-
} finally {
|
|
124
|
-
evalContext = prevContext;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Effects collected into a batch.
|
|
129
|
-
let batchedEffect: Effect | undefined = undefined;
|
|
130
|
-
let batchDepth = 0;
|
|
131
|
-
let batchIteration = 0;
|
|
132
|
-
|
|
133
|
-
type BatchSnapshot = {
|
|
134
|
-
_source: Signal;
|
|
135
|
-
_value: unknown;
|
|
136
|
-
_version: number;
|
|
137
|
-
_next?: BatchSnapshot;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
let batchSnapshotVersion = 0;
|
|
141
|
-
let currentBatchSnapshotVersion = 0;
|
|
142
|
-
let batchSnapshots: BatchSnapshot | undefined = undefined;
|
|
143
|
-
|
|
144
|
-
// A global version number for signals, used for fast-pathing repeated
|
|
145
|
-
// computed.peek()/computed.value calls when nothing has changed globally.
|
|
146
|
-
let globalVersion = 0;
|
|
147
|
-
|
|
148
|
-
function recordBatchSnapshot(source: Signal) {
|
|
149
|
-
// Only capture writes during the user-visible batch callback, not during effect flush.
|
|
150
|
-
if (batchDepth === 0 || batchIteration !== 0) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (source._batchSnapshotVersion !== currentBatchSnapshotVersion) {
|
|
155
|
-
source._batchSnapshotVersion = currentBatchSnapshotVersion;
|
|
156
|
-
batchSnapshots = {
|
|
157
|
-
_source: source,
|
|
158
|
-
_value: source._value,
|
|
159
|
-
_version: source._version,
|
|
160
|
-
_next: batchSnapshots,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function reconcileBatchSnapshots() {
|
|
166
|
-
let snapshots = batchSnapshots;
|
|
167
|
-
batchSnapshots = undefined;
|
|
168
|
-
|
|
169
|
-
while (snapshots !== undefined) {
|
|
170
|
-
if (snapshots._source._value === snapshots._value) {
|
|
171
|
-
snapshots._source._version = snapshots._version;
|
|
172
|
-
}
|
|
173
|
-
snapshots = snapshots._next;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function addDependency(signal: Signal): Node | undefined {
|
|
178
|
-
if (evalContext === undefined) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
let node = signal._node;
|
|
183
|
-
if (node === undefined || node._target !== evalContext) {
|
|
184
|
-
/**
|
|
185
|
-
* `signal` is a new dependency. Create a new dependency node, and set it
|
|
186
|
-
* as the tail of the current context's dependency list. e.g:
|
|
187
|
-
*
|
|
188
|
-
* { A <-> B }
|
|
189
|
-
* ↑ ↑
|
|
190
|
-
* tail node (new)
|
|
191
|
-
* ↓
|
|
192
|
-
* { A <-> B <-> C }
|
|
193
|
-
* ↑
|
|
194
|
-
* tail (evalContext._sources)
|
|
195
|
-
*/
|
|
196
|
-
node = {
|
|
197
|
-
_version: 0,
|
|
198
|
-
_source: signal,
|
|
199
|
-
_prevSource: evalContext._sources,
|
|
200
|
-
_nextSource: undefined,
|
|
201
|
-
_target: evalContext,
|
|
202
|
-
_prevTarget: undefined,
|
|
203
|
-
_nextTarget: undefined,
|
|
204
|
-
_rollbackNode: node,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
if (evalContext._sources !== undefined) {
|
|
208
|
-
evalContext._sources._nextSource = node;
|
|
209
|
-
}
|
|
210
|
-
evalContext._sources = node;
|
|
211
|
-
signal._node = node;
|
|
212
|
-
|
|
213
|
-
// Subscribe to change notifications from this dependency if we're in an effect
|
|
214
|
-
// OR evaluating a computed signal that in turn has subscribers.
|
|
215
|
-
if (evalContext._flags & TRACKING) {
|
|
216
|
-
signal._subscribe(node);
|
|
217
|
-
}
|
|
218
|
-
return node;
|
|
219
|
-
} else if (node._version === -1) {
|
|
220
|
-
// `signal` is an existing dependency from a previous evaluation. Reuse it.
|
|
221
|
-
node._version = 0;
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* If `node` is not already the current tail of the dependency list (i.e.
|
|
225
|
-
* there is a next node in the list), then make the `node` the new tail. e.g:
|
|
226
|
-
*
|
|
227
|
-
* { A <-> B <-> C <-> D }
|
|
228
|
-
* ↑ ↑
|
|
229
|
-
* node ┌─── tail (evalContext._sources)
|
|
230
|
-
* └─────│─────┐
|
|
231
|
-
* ↓ ↓
|
|
232
|
-
* { A <-> C <-> D <-> B }
|
|
233
|
-
* ↑
|
|
234
|
-
* tail (evalContext._sources)
|
|
235
|
-
*/
|
|
236
|
-
if (node._nextSource !== undefined) {
|
|
237
|
-
node._nextSource._prevSource = node._prevSource;
|
|
238
|
-
|
|
239
|
-
if (node._prevSource !== undefined) {
|
|
240
|
-
node._prevSource._nextSource = node._nextSource;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
node._prevSource = evalContext._sources;
|
|
244
|
-
node._nextSource = undefined;
|
|
245
|
-
|
|
246
|
-
evalContext._sources!._nextSource = node;
|
|
247
|
-
evalContext._sources = node;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// We can assume that the currently evaluated effect / computed signal is already
|
|
251
|
-
// subscribed to change notifications from `signal` if needed.
|
|
252
|
-
return node;
|
|
253
|
-
}
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
//#region Signal
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* The base class for plain and computed signals.
|
|
261
|
-
*/
|
|
262
|
-
//
|
|
263
|
-
// A function with the same name is defined later, so we need to ignore TypeScript's
|
|
264
|
-
// warning about a redeclared variable.
|
|
265
|
-
//
|
|
266
|
-
// The class is declared here, but later implemented with ES5-style prototypes.
|
|
267
|
-
// This enables better control of the transpiled output size.
|
|
268
|
-
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
|
|
269
|
-
declare class Signal<T = any> {
|
|
270
|
-
/** @internal */
|
|
271
|
-
_value: unknown;
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* @internal
|
|
275
|
-
* Version numbers should always be >= 0, because the special value -1 is used
|
|
276
|
-
* by Nodes to signify potentially unused but recyclable nodes.
|
|
277
|
-
*/
|
|
278
|
-
_version: number;
|
|
279
|
-
|
|
280
|
-
/** @internal */
|
|
281
|
-
_node?: Node;
|
|
282
|
-
|
|
283
|
-
/** @internal */
|
|
284
|
-
_targets?: Node;
|
|
285
|
-
|
|
286
|
-
/** @internal */
|
|
287
|
-
_batchSnapshotVersion: number;
|
|
288
|
-
|
|
289
|
-
constructor(value?: T, options?: SignalOptions<T>);
|
|
290
|
-
|
|
291
|
-
/** @internal */
|
|
292
|
-
_refresh(): boolean;
|
|
293
|
-
|
|
294
|
-
/** @internal */
|
|
295
|
-
_subscribe(node: Node): void;
|
|
296
|
-
|
|
297
|
-
/** @internal */
|
|
298
|
-
_unsubscribe(node: Node): void;
|
|
299
|
-
|
|
300
|
-
/** @internal */
|
|
301
|
-
_watched?(this: Signal<T>): void;
|
|
302
|
-
|
|
303
|
-
/** @internal */
|
|
304
|
-
_unwatched?(this: Signal<T>): void;
|
|
305
|
-
|
|
306
|
-
subscribe(fn: (value: T) => void): () => void;
|
|
307
|
-
|
|
308
|
-
name?: string;
|
|
309
|
-
|
|
310
|
-
valueOf(): T;
|
|
311
|
-
|
|
312
|
-
toString(): string;
|
|
313
|
-
|
|
314
|
-
toJSON(): T;
|
|
315
|
-
|
|
316
|
-
peek(): T;
|
|
317
|
-
|
|
318
|
-
brand: typeof BRAND_SYMBOL;
|
|
319
|
-
|
|
320
|
-
get value(): T;
|
|
321
|
-
set value(value: T);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export interface SignalOptions<T = any> {
|
|
325
|
-
watched?: (this: Signal<T>) => void;
|
|
326
|
-
unwatched?: (this: Signal<T>) => void;
|
|
327
|
-
name?: string;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** @internal */
|
|
331
|
-
// A class with the same name has already been declared, so we need to ignore
|
|
332
|
-
// TypeScript's warning about a redeclared variable.
|
|
333
|
-
//
|
|
334
|
-
// The previously declared class is implemented here with ES5-style prototypes.
|
|
335
|
-
// This enables better control of the transpiled output size.
|
|
336
|
-
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
|
|
337
|
-
function Signal(this: Signal, value?: unknown, options?: SignalOptions) {
|
|
338
|
-
this._value = value;
|
|
339
|
-
this._version = 0;
|
|
340
|
-
this._node = undefined;
|
|
341
|
-
this._targets = undefined;
|
|
342
|
-
this._batchSnapshotVersion = 0;
|
|
343
|
-
this._watched = options?.watched;
|
|
344
|
-
this._unwatched = options?.unwatched;
|
|
345
|
-
this.name = options?.name;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
Signal.prototype.brand = BRAND_SYMBOL;
|
|
349
|
-
|
|
350
|
-
Signal.prototype._refresh = function () {
|
|
351
|
-
return true;
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
Signal.prototype._subscribe = function (node) {
|
|
355
|
-
const targets = this._targets;
|
|
356
|
-
if (targets !== node && node._prevTarget === undefined) {
|
|
357
|
-
node._nextTarget = targets;
|
|
358
|
-
this._targets = node;
|
|
359
|
-
|
|
360
|
-
if (targets !== undefined) {
|
|
361
|
-
targets._prevTarget = node;
|
|
362
|
-
} else {
|
|
363
|
-
untracked(() => {
|
|
364
|
-
this._watched?.call(this);
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
Signal.prototype._unsubscribe = function (node) {
|
|
371
|
-
// Only run the unsubscribe step if the signal has any subscribers to begin with.
|
|
372
|
-
if (this._targets !== undefined) {
|
|
373
|
-
const prev = node._prevTarget;
|
|
374
|
-
const next = node._nextTarget;
|
|
375
|
-
if (prev !== undefined) {
|
|
376
|
-
prev._nextTarget = next;
|
|
377
|
-
node._prevTarget = undefined;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (next !== undefined) {
|
|
381
|
-
next._prevTarget = prev;
|
|
382
|
-
node._nextTarget = undefined;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (node === this._targets) {
|
|
386
|
-
this._targets = next;
|
|
387
|
-
if (next === undefined) {
|
|
388
|
-
untracked(() => {
|
|
389
|
-
this._unwatched?.call(this);
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
Signal.prototype.subscribe = function (fn) {
|
|
397
|
-
return effect(
|
|
398
|
-
() => {
|
|
399
|
-
const value = this.value;
|
|
400
|
-
const prevContext = evalContext;
|
|
401
|
-
evalContext = undefined;
|
|
402
|
-
try {
|
|
403
|
-
fn(value);
|
|
404
|
-
} finally {
|
|
405
|
-
evalContext = prevContext;
|
|
406
|
-
}
|
|
407
|
-
},
|
|
408
|
-
{ name: "sub" }
|
|
409
|
-
);
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
Signal.prototype.valueOf = function () {
|
|
413
|
-
return this.value;
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
Signal.prototype.toString = function () {
|
|
417
|
-
return this.value + "";
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
Signal.prototype.toJSON = function () {
|
|
421
|
-
return this.value;
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
Signal.prototype.peek = function () {
|
|
425
|
-
const prevContext = evalContext;
|
|
426
|
-
evalContext = undefined;
|
|
427
|
-
try {
|
|
428
|
-
return this.value;
|
|
429
|
-
} finally {
|
|
430
|
-
evalContext = prevContext;
|
|
431
|
-
}
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
Object.defineProperty(Signal.prototype, "value", {
|
|
435
|
-
get(this: Signal) {
|
|
436
|
-
const node = addDependency(this);
|
|
437
|
-
if (node !== undefined) {
|
|
438
|
-
node._version = this._version;
|
|
439
|
-
}
|
|
440
|
-
return this._value;
|
|
441
|
-
},
|
|
442
|
-
set(this: Signal, value) {
|
|
443
|
-
if (value !== this._value) {
|
|
444
|
-
if (batchIteration > 100) {
|
|
445
|
-
throw new Error("Cycle detected");
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
recordBatchSnapshot(this);
|
|
449
|
-
this._value = value;
|
|
450
|
-
this._version++;
|
|
451
|
-
globalVersion++;
|
|
452
|
-
|
|
453
|
-
/**@__INLINE__*/ startBatch();
|
|
454
|
-
try {
|
|
455
|
-
for (
|
|
456
|
-
let node = this._targets;
|
|
457
|
-
node !== undefined;
|
|
458
|
-
node = node._nextTarget
|
|
459
|
-
) {
|
|
460
|
-
node._target._notify();
|
|
461
|
-
}
|
|
462
|
-
} finally {
|
|
463
|
-
endBatch();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Create a new plain signal.
|
|
471
|
-
*
|
|
472
|
-
* @param value The initial value for the signal.
|
|
473
|
-
* @returns A new signal.
|
|
474
|
-
*/
|
|
475
|
-
export function signal<T>(value: T, options?: SignalOptions<T>): Signal<T>;
|
|
476
|
-
export function signal<T = undefined>(): Signal<T | undefined>;
|
|
477
|
-
export function signal<T>(value?: T, options?: SignalOptions<T>): Signal<T> {
|
|
478
|
-
return new Signal(value, options);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
//#endregion Signal
|
|
482
|
-
|
|
483
|
-
//#region Computed
|
|
484
|
-
|
|
485
|
-
function needsToRecompute(target: Computed | Effect): boolean {
|
|
486
|
-
// Check the dependencies for changed values. The dependency list is already
|
|
487
|
-
// in order of use. Therefore if multiple dependencies have changed values, only
|
|
488
|
-
// the first used dependency is re-evaluated at this point.
|
|
489
|
-
for (
|
|
490
|
-
let node = target._sources;
|
|
491
|
-
node !== undefined;
|
|
492
|
-
node = node._nextSource
|
|
493
|
-
) {
|
|
494
|
-
if (
|
|
495
|
-
// If the dependency has definitely been updated since its version number
|
|
496
|
-
// was observed, then we need to recompute. This first check is not strictly
|
|
497
|
-
// necessary for correctness, but allows us to skip the refresh call if the
|
|
498
|
-
// dependency has already been updated.
|
|
499
|
-
node._source._version !== node._version ||
|
|
500
|
-
// Refresh the dependency. If there's something blocking the refresh (e.g. a
|
|
501
|
-
// dependency cycle), then we need to recompute.
|
|
502
|
-
!node._source._refresh() ||
|
|
503
|
-
// If the dependency got a new version after the refresh, then we need to recompute.
|
|
504
|
-
node._source._version !== node._version
|
|
505
|
-
) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
// If none of the dependencies have changed values since last recompute then
|
|
510
|
-
// there's no need to recompute.
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function prepareSources(target: Computed | Effect) {
|
|
515
|
-
/**
|
|
516
|
-
* 1. Mark all current sources as re-usable nodes (version: -1)
|
|
517
|
-
* 2. Set a rollback node if the current node is being used in a different context
|
|
518
|
-
* 3. Point 'target._sources' to the tail of the doubly-linked list, e.g:
|
|
519
|
-
*
|
|
520
|
-
* { undefined <- A <-> B <-> C -> undefined }
|
|
521
|
-
* ↑ ↑
|
|
522
|
-
* │ └──────┐
|
|
523
|
-
* target._sources = A; (node is head) │
|
|
524
|
-
* ↓ │
|
|
525
|
-
* target._sources = C; (node is tail) ─┘
|
|
526
|
-
*/
|
|
527
|
-
for (
|
|
528
|
-
let node = target._sources;
|
|
529
|
-
node !== undefined;
|
|
530
|
-
node = node._nextSource
|
|
531
|
-
) {
|
|
532
|
-
const rollbackNode = node._source._node;
|
|
533
|
-
if (rollbackNode !== undefined) {
|
|
534
|
-
node._rollbackNode = rollbackNode;
|
|
535
|
-
}
|
|
536
|
-
node._source._node = node;
|
|
537
|
-
node._version = -1;
|
|
538
|
-
|
|
539
|
-
if (node._nextSource === undefined) {
|
|
540
|
-
target._sources = node;
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function cleanupSources(target: Computed | Effect) {
|
|
547
|
-
let node = target._sources;
|
|
548
|
-
let head: Node | undefined = undefined;
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* At this point 'target._sources' points to the tail of the doubly-linked list.
|
|
552
|
-
* It contains all existing sources + new sources in order of use.
|
|
553
|
-
* Iterate backwards until we find the head node while dropping old dependencies.
|
|
554
|
-
*/
|
|
555
|
-
while (node !== undefined) {
|
|
556
|
-
const prev = node._prevSource;
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* The node was not re-used, unsubscribe from its change notifications and remove itself
|
|
560
|
-
* from the doubly-linked list. e.g:
|
|
561
|
-
*
|
|
562
|
-
* { A <-> B <-> C }
|
|
563
|
-
* ↓
|
|
564
|
-
* { A <-> C }
|
|
565
|
-
*/
|
|
566
|
-
if (node._version === -1) {
|
|
567
|
-
node._source._unsubscribe(node);
|
|
568
|
-
|
|
569
|
-
if (prev !== undefined) {
|
|
570
|
-
prev._nextSource = node._nextSource;
|
|
571
|
-
}
|
|
572
|
-
if (node._nextSource !== undefined) {
|
|
573
|
-
node._nextSource._prevSource = prev;
|
|
574
|
-
}
|
|
575
|
-
} else {
|
|
576
|
-
/**
|
|
577
|
-
* The new head is the last node seen which wasn't removed/unsubscribed
|
|
578
|
-
* from the doubly-linked list. e.g:
|
|
579
|
-
*
|
|
580
|
-
* { A <-> B <-> C }
|
|
581
|
-
* ↑ ↑ ↑
|
|
582
|
-
* │ │ └ head = node
|
|
583
|
-
* │ └ head = node
|
|
584
|
-
* └ head = node
|
|
585
|
-
*/
|
|
586
|
-
head = node;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
node._source._node = node._rollbackNode;
|
|
590
|
-
if (node._rollbackNode !== undefined) {
|
|
591
|
-
node._rollbackNode = undefined;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
node = prev;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
target._sources = head;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* The base class for computed signals.
|
|
602
|
-
*/
|
|
603
|
-
declare class Computed<T = any> extends Signal<T> {
|
|
604
|
-
_fn: () => T;
|
|
605
|
-
_sources?: Node;
|
|
606
|
-
_globalVersion: number;
|
|
607
|
-
_flags: number;
|
|
608
|
-
|
|
609
|
-
constructor(fn: () => T, options?: SignalOptions<T>);
|
|
610
|
-
|
|
611
|
-
_notify(): void;
|
|
612
|
-
get value(): T;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/** @internal */
|
|
616
|
-
function Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {
|
|
617
|
-
Signal.call(this, undefined);
|
|
618
|
-
|
|
619
|
-
this._fn = fn;
|
|
620
|
-
this._sources = undefined;
|
|
621
|
-
this._globalVersion = globalVersion - 1;
|
|
622
|
-
this._flags = OUTDATED;
|
|
623
|
-
this._watched = options?.watched;
|
|
624
|
-
this._unwatched = options?.unwatched;
|
|
625
|
-
this.name = options?.name;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
Computed.prototype = new Signal() as Computed;
|
|
629
|
-
|
|
630
|
-
Computed.prototype._refresh = function () {
|
|
631
|
-
this._flags &= ~NOTIFIED;
|
|
632
|
-
|
|
633
|
-
if (this._flags & RUNNING) {
|
|
634
|
-
return false;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// If this computed signal has subscribed to updates from its dependencies
|
|
638
|
-
// (TRACKING flag set) and none of them have notified about changes (OUTDATED
|
|
639
|
-
// flag not set), then the computed value can't have changed.
|
|
640
|
-
if ((this._flags & (OUTDATED | TRACKING)) === TRACKING) {
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
this._flags &= ~OUTDATED;
|
|
644
|
-
|
|
645
|
-
if (this._globalVersion === globalVersion) {
|
|
646
|
-
return true;
|
|
647
|
-
}
|
|
648
|
-
this._globalVersion = globalVersion;
|
|
649
|
-
|
|
650
|
-
// Mark this computed signal running before checking the dependencies for value
|
|
651
|
-
// changes, so that the RUNNING flag can be used to notice cyclical dependencies.
|
|
652
|
-
this._flags |= RUNNING;
|
|
653
|
-
if (this._version > 0 && !needsToRecompute(this)) {
|
|
654
|
-
this._flags &= ~RUNNING;
|
|
655
|
-
return true;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const prevContext = evalContext;
|
|
659
|
-
try {
|
|
660
|
-
prepareSources(this);
|
|
661
|
-
evalContext = this;
|
|
662
|
-
const value = this._fn();
|
|
663
|
-
if (
|
|
664
|
-
this._flags & HAS_ERROR ||
|
|
665
|
-
this._value !== value ||
|
|
666
|
-
this._version === 0
|
|
667
|
-
) {
|
|
668
|
-
this._value = value;
|
|
669
|
-
this._flags &= ~HAS_ERROR;
|
|
670
|
-
this._version++;
|
|
671
|
-
}
|
|
672
|
-
} catch (err) {
|
|
673
|
-
this._value = err;
|
|
674
|
-
this._flags |= HAS_ERROR;
|
|
675
|
-
this._version++;
|
|
676
|
-
}
|
|
677
|
-
evalContext = prevContext;
|
|
678
|
-
cleanupSources(this);
|
|
679
|
-
this._flags &= ~RUNNING;
|
|
680
|
-
return true;
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
Computed.prototype._subscribe = function (node) {
|
|
684
|
-
if (this._targets === undefined) {
|
|
685
|
-
this._flags |= OUTDATED | TRACKING;
|
|
686
|
-
|
|
687
|
-
// A computed signal subscribes lazily to its dependencies when it
|
|
688
|
-
// gets its first subscriber.
|
|
689
|
-
for (
|
|
690
|
-
let node = this._sources;
|
|
691
|
-
node !== undefined;
|
|
692
|
-
node = node._nextSource
|
|
693
|
-
) {
|
|
694
|
-
node._source._subscribe(node);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
Signal.prototype._subscribe.call(this, node);
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
Computed.prototype._unsubscribe = function (node) {
|
|
701
|
-
// Only run the unsubscribe step if the computed signal has any subscribers.
|
|
702
|
-
if (this._targets !== undefined) {
|
|
703
|
-
Signal.prototype._unsubscribe.call(this, node);
|
|
704
|
-
|
|
705
|
-
// Computed signal unsubscribes from its dependencies when it loses its last subscriber.
|
|
706
|
-
// This makes it possible for unreferences subgraphs of computed signals to get garbage collected.
|
|
707
|
-
if (this._targets === undefined) {
|
|
708
|
-
this._flags &= ~TRACKING;
|
|
709
|
-
|
|
710
|
-
for (
|
|
711
|
-
let node = this._sources;
|
|
712
|
-
node !== undefined;
|
|
713
|
-
node = node._nextSource
|
|
714
|
-
) {
|
|
715
|
-
node._source._unsubscribe(node);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
};
|
|
720
|
-
|
|
721
|
-
Computed.prototype._notify = function () {
|
|
722
|
-
if (!(this._flags & NOTIFIED)) {
|
|
723
|
-
this._flags |= OUTDATED | NOTIFIED;
|
|
724
|
-
|
|
725
|
-
for (
|
|
726
|
-
let node = this._targets;
|
|
727
|
-
node !== undefined;
|
|
728
|
-
node = node._nextTarget
|
|
729
|
-
) {
|
|
730
|
-
node._target._notify();
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
Object.defineProperty(Computed.prototype, "value", {
|
|
736
|
-
get(this: Computed) {
|
|
737
|
-
if (this._flags & RUNNING) {
|
|
738
|
-
throw new Error("Cycle detected");
|
|
739
|
-
}
|
|
740
|
-
const node = addDependency(this);
|
|
741
|
-
this._refresh();
|
|
742
|
-
if (node !== undefined) {
|
|
743
|
-
node._version = this._version;
|
|
744
|
-
}
|
|
745
|
-
if (this._flags & HAS_ERROR) {
|
|
746
|
-
throw this._value;
|
|
747
|
-
}
|
|
748
|
-
return this._value;
|
|
749
|
-
},
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* An interface for read-only signals.
|
|
754
|
-
*/
|
|
755
|
-
interface ReadonlySignal<T = any> {
|
|
756
|
-
readonly value: T;
|
|
757
|
-
peek(): T;
|
|
758
|
-
|
|
759
|
-
subscribe(fn: (value: T) => void): () => void;
|
|
760
|
-
valueOf(): T;
|
|
761
|
-
toString(): string;
|
|
762
|
-
toJSON(): T;
|
|
763
|
-
brand: typeof BRAND_SYMBOL;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Create a new signal that is computed based on the values of other signals.
|
|
768
|
-
*
|
|
769
|
-
* The returned computed signal is read-only, and its value is automatically
|
|
770
|
-
* updated when any signals accessed from within the callback function change.
|
|
771
|
-
*
|
|
772
|
-
* @param fn The effect callback.
|
|
773
|
-
* @returns A new read-only signal.
|
|
774
|
-
*/
|
|
775
|
-
function computed<T>(
|
|
776
|
-
fn: () => T,
|
|
777
|
-
options?: SignalOptions<T>
|
|
778
|
-
): ReadonlySignal<T> {
|
|
779
|
-
return new Computed(fn, options);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
//#endregion Computed
|
|
783
|
-
|
|
784
|
-
//#region Effect
|
|
785
|
-
|
|
786
|
-
function cleanupEffect(effect: Effect) {
|
|
787
|
-
const cleanup = effect._cleanup;
|
|
788
|
-
effect._cleanup = undefined;
|
|
789
|
-
|
|
790
|
-
if (typeof cleanup === "function") {
|
|
791
|
-
/*@__INLINE__**/ startBatch();
|
|
792
|
-
|
|
793
|
-
// Run cleanup functions always outside of any context.
|
|
794
|
-
const prevContext = evalContext;
|
|
795
|
-
evalContext = undefined;
|
|
796
|
-
try {
|
|
797
|
-
cleanup();
|
|
798
|
-
} catch (err) {
|
|
799
|
-
effect._flags &= ~RUNNING;
|
|
800
|
-
effect._flags |= DISPOSED;
|
|
801
|
-
disposeEffect(effect);
|
|
802
|
-
throw err;
|
|
803
|
-
} finally {
|
|
804
|
-
evalContext = prevContext;
|
|
805
|
-
endBatch();
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function disposeEffect(effect: Effect) {
|
|
811
|
-
for (
|
|
812
|
-
let node = effect._sources;
|
|
813
|
-
node !== undefined;
|
|
814
|
-
node = node._nextSource
|
|
815
|
-
) {
|
|
816
|
-
node._source._unsubscribe(node);
|
|
817
|
-
}
|
|
818
|
-
effect._fn = undefined;
|
|
819
|
-
effect._sources = undefined;
|
|
820
|
-
|
|
821
|
-
cleanupEffect(effect);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
function endEffect(this: Effect, prevContext?: Computed | Effect) {
|
|
825
|
-
if (evalContext !== this) {
|
|
826
|
-
throw new Error("Out-of-order effect");
|
|
827
|
-
}
|
|
828
|
-
cleanupSources(this);
|
|
829
|
-
evalContext = prevContext;
|
|
830
|
-
|
|
831
|
-
this._flags &= ~RUNNING;
|
|
832
|
-
if (this._flags & DISPOSED) {
|
|
833
|
-
disposeEffect(this);
|
|
834
|
-
}
|
|
835
|
-
endBatch();
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
type EffectFn =
|
|
839
|
-
| ((this: { dispose: () => void }) => void | (() => void))
|
|
840
|
-
| (() => void | (() => void));
|
|
841
|
-
|
|
842
|
-
// Avoid hard-requiring the ESNext.Disposable lib in consuming tsconfigs.
|
|
843
|
-
// When `Symbol.dispose` is available, this becomes a symbol-keyed disposer type.
|
|
844
|
-
type DisposeSymbol = typeof Symbol extends { readonly dispose: infer TDispose }
|
|
845
|
-
? TDispose
|
|
846
|
-
: never;
|
|
847
|
-
type DisposableLike = {
|
|
848
|
-
[K in DisposeSymbol & PropertyKey]: () => void;
|
|
849
|
-
};
|
|
850
|
-
type DisposeFn = (() => void) & DisposableLike;
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* The base class for reactive effects.
|
|
854
|
-
*/
|
|
855
|
-
declare class Effect {
|
|
856
|
-
_fn?: EffectFn;
|
|
857
|
-
_cleanup?: () => void;
|
|
858
|
-
_sources?: Node;
|
|
859
|
-
_nextBatchedEffect?: Effect;
|
|
860
|
-
_flags: number;
|
|
861
|
-
_debugCallback?: () => void;
|
|
862
|
-
name?: string;
|
|
863
|
-
|
|
864
|
-
constructor(fn: EffectFn, options?: EffectOptions);
|
|
865
|
-
|
|
866
|
-
_callback(): void;
|
|
867
|
-
_start(): () => void;
|
|
868
|
-
_notify(): void;
|
|
869
|
-
_dispose(): void;
|
|
870
|
-
dispose(): void;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
export interface EffectOptions {
|
|
874
|
-
name?: string;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
let capturedEffects: Effect[] | undefined;
|
|
878
|
-
|
|
879
|
-
/** @internal */
|
|
880
|
-
function Effect(this: Effect, fn: EffectFn, options?: EffectOptions) {
|
|
881
|
-
this._fn = fn;
|
|
882
|
-
this._cleanup = undefined;
|
|
883
|
-
this._sources = undefined;
|
|
884
|
-
this._nextBatchedEffect = undefined;
|
|
885
|
-
this._flags = TRACKING;
|
|
886
|
-
this.name = options?.name;
|
|
887
|
-
|
|
888
|
-
if (capturedEffects) {
|
|
889
|
-
capturedEffects.push(this);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
Effect.prototype._callback = function () {
|
|
894
|
-
const finish = this._start();
|
|
895
|
-
try {
|
|
896
|
-
if (this._flags & DISPOSED) return;
|
|
897
|
-
if (this._fn === undefined) return;
|
|
898
|
-
|
|
899
|
-
const cleanup = this._fn();
|
|
900
|
-
if (typeof cleanup === "function") {
|
|
901
|
-
this._cleanup = cleanup;
|
|
902
|
-
}
|
|
903
|
-
} finally {
|
|
904
|
-
finish();
|
|
905
|
-
}
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
Effect.prototype._start = function () {
|
|
909
|
-
if (this._flags & RUNNING) {
|
|
910
|
-
throw new Error("Cycle detected");
|
|
911
|
-
}
|
|
912
|
-
this._flags |= RUNNING;
|
|
913
|
-
this._flags &= ~DISPOSED;
|
|
914
|
-
cleanupEffect(this);
|
|
915
|
-
prepareSources(this);
|
|
916
|
-
|
|
917
|
-
/*@__INLINE__**/ startBatch();
|
|
918
|
-
const prevContext = evalContext;
|
|
919
|
-
evalContext = this;
|
|
920
|
-
return endEffect.bind(this, prevContext);
|
|
921
|
-
};
|
|
922
|
-
|
|
923
|
-
Effect.prototype._notify = function () {
|
|
924
|
-
if (!(this._flags & NOTIFIED)) {
|
|
925
|
-
this._flags |= NOTIFIED;
|
|
926
|
-
this._nextBatchedEffect = batchedEffect;
|
|
927
|
-
batchedEffect = this;
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
Effect.prototype._dispose = function () {
|
|
932
|
-
this._flags |= DISPOSED;
|
|
933
|
-
|
|
934
|
-
if (!(this._flags & RUNNING)) {
|
|
935
|
-
disposeEffect(this);
|
|
936
|
-
}
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
Effect.prototype.dispose = function () {
|
|
940
|
-
this._dispose();
|
|
941
|
-
};
|
|
942
|
-
/**
|
|
943
|
-
* Create an effect to run arbitrary code in response to signal changes.
|
|
944
|
-
*
|
|
945
|
-
* An effect tracks which signals are accessed within the given callback
|
|
946
|
-
* function `fn`, and re-runs the callback when those signals change.
|
|
947
|
-
*
|
|
948
|
-
* The callback may return a cleanup function. The cleanup function gets
|
|
949
|
-
* run once, either when the callback is next called or when the effect
|
|
950
|
-
* gets disposed, whichever happens first.
|
|
951
|
-
*
|
|
952
|
-
* @param fn The effect callback.
|
|
953
|
-
* @returns A function for disposing the effect.
|
|
954
|
-
*/
|
|
955
|
-
function effect(fn: EffectFn, options?: EffectOptions): DisposeFn {
|
|
956
|
-
const effect = new Effect(fn, options);
|
|
957
|
-
try {
|
|
958
|
-
effect._callback();
|
|
959
|
-
} catch (err) {
|
|
960
|
-
effect._dispose();
|
|
961
|
-
throw err;
|
|
962
|
-
}
|
|
963
|
-
// Return a bound function instead of a wrapper like `() => effect._dispose()`,
|
|
964
|
-
// because bound functions seem to be just as fast and take up a lot less memory.
|
|
965
|
-
const dispose = effect._dispose.bind(effect);
|
|
966
|
-
(dispose as any)[Symbol.dispose] = dispose;
|
|
967
|
-
return dispose as DisposeFn;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
//#endregion Effect
|
|
971
|
-
|
|
972
|
-
//#region Action
|
|
973
|
-
|
|
974
|
-
function action<TArgs extends unknown[], TReturn>(
|
|
975
|
-
fn: (...args: TArgs) => TReturn
|
|
976
|
-
): (...args: TArgs) => TReturn {
|
|
977
|
-
return function actionWrapper(this: unknown, ...args: TArgs) {
|
|
978
|
-
return batch(() => untracked(() => fn.apply(this, args)));
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
//#endregion Action
|
|
983
|
-
|
|
984
|
-
//#region createModel
|
|
985
|
-
|
|
986
|
-
/** Models should only contain signals, actions, and nested objects containing only signals and actions. */
|
|
987
|
-
type ValidateModel<TModel> = {
|
|
988
|
-
[Key in keyof TModel]: TModel[Key] extends ReadonlySignal<unknown>
|
|
989
|
-
? TModel[Key]
|
|
990
|
-
: TModel[Key] extends (...args: any[]) => any
|
|
991
|
-
? TModel[Key]
|
|
992
|
-
: TModel[Key] extends object
|
|
993
|
-
? ValidateModel<TModel[Key]>
|
|
994
|
-
: `Property ${Key extends string ? `'${Key}' ` : ""}is not a Signal, Action, or an object that contains only Signals and Actions.`;
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
export type Model<TModel> = ValidateModel<TModel> & DisposableLike;
|
|
998
|
-
|
|
999
|
-
export type ModelFactory<TModel, TFactoryArgs extends any[] = []> = (
|
|
1000
|
-
...args: TFactoryArgs
|
|
1001
|
-
) => ValidateModel<TModel>;
|
|
1002
|
-
export type ModelConstructor<TModel, TFactoryArgs extends any[] = []> = new (
|
|
1003
|
-
...args: TFactoryArgs
|
|
1004
|
-
) => Model<TModel>;
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* The public types for ModelConstructor require using `new` to help
|
|
1008
|
-
* disambiguate the function passed into `createModel` and the returned
|
|
1009
|
-
* constructor function. It is easier to say that `createModel` accepts
|
|
1010
|
-
* a factory and returns a class, then to say it accepts a factory and
|
|
1011
|
-
* returns a factory. In other words, this example:
|
|
1012
|
-
*
|
|
1013
|
-
* ```ts
|
|
1014
|
-
* const PersonModel = createModel((name: string) => ({ ... }));
|
|
1015
|
-
* const person = new PersonModel("John");
|
|
1016
|
-
* ```
|
|
1017
|
-
*
|
|
1018
|
-
* is easier to understand than this example:
|
|
1019
|
-
*
|
|
1020
|
-
* ```ts
|
|
1021
|
-
* const createPerson = createModel((name: string) => ({ ... }));
|
|
1022
|
-
* const person = createPerson("John");
|
|
1023
|
-
* ```
|
|
1024
|
-
*
|
|
1025
|
-
* However, internally we implement `createModel` to return a function
|
|
1026
|
-
* that can be called without `new` for simplicity. To bridge the gap
|
|
1027
|
-
* between the public types and the internal implementation, we define
|
|
1028
|
-
* this internal interface that extends the public interface but also
|
|
1029
|
-
* allows calling without `new`.
|
|
1030
|
-
*
|
|
1031
|
-
* This pattern is used by the Preact & React adapters to make instantiating
|
|
1032
|
-
* a model or a function that returns a model easier.
|
|
1033
|
-
*
|
|
1034
|
-
* @internal
|
|
1035
|
-
*/
|
|
1036
|
-
interface InternalModelConstructor<
|
|
1037
|
-
TModel,
|
|
1038
|
-
TFactoryArgs extends any[],
|
|
1039
|
-
> extends ModelConstructor<TModel, TFactoryArgs> {
|
|
1040
|
-
(...args: TFactoryArgs): Model<TModel>;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
function startCapturingEffects(): () => Effect[] | undefined {
|
|
1044
|
-
let prevCapturedEffects = capturedEffects;
|
|
1045
|
-
capturedEffects = [];
|
|
1046
|
-
|
|
1047
|
-
return function stopCapturingEffects() {
|
|
1048
|
-
let modelEffects = capturedEffects;
|
|
1049
|
-
if (capturedEffects && prevCapturedEffects) {
|
|
1050
|
-
prevCapturedEffects = prevCapturedEffects.concat(capturedEffects);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
capturedEffects = prevCapturedEffects;
|
|
1054
|
-
|
|
1055
|
-
return modelEffects;
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
const wrapInAction = (value: Record<string, unknown>) => {
|
|
1060
|
-
for (const key in value) {
|
|
1061
|
-
const val = value[key];
|
|
1062
|
-
if (typeof val === "function") {
|
|
1063
|
-
value[key] = action(val as (...args: unknown[]) => unknown);
|
|
1064
|
-
} else if (typeof val === "object" && val !== null && !("brand" in val)) {
|
|
1065
|
-
// Recursively wrap nested object properties in actions. This allows users to write
|
|
1066
|
-
// nested models without worrying about wrapping their functions in `action`.
|
|
1067
|
-
wrapInAction(val as Record<string, unknown>);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
|
|
1072
|
-
function createModel<TModel, TFactoryArgs extends any[] = []>(
|
|
1073
|
-
modelFactory: ModelFactory<TModel, TFactoryArgs>
|
|
1074
|
-
): ModelConstructor<TModel, TFactoryArgs> {
|
|
1075
|
-
return function SignalModel(...args: TFactoryArgs): Model<TModel> {
|
|
1076
|
-
let modelEffects: Effect[] | undefined;
|
|
1077
|
-
let model: Model<TModel>;
|
|
1078
|
-
|
|
1079
|
-
const stopCapturingEffects = startCapturingEffects();
|
|
1080
|
-
try {
|
|
1081
|
-
model = modelFactory(...args) as Model<TModel>;
|
|
1082
|
-
} catch (err) {
|
|
1083
|
-
// Drop any captured effects on error. Errors from nested models will bubble
|
|
1084
|
-
// up here and recursively reset `capturedEffects` to `undefined` preventing
|
|
1085
|
-
// any captured effects from leaking
|
|
1086
|
-
capturedEffects = undefined;
|
|
1087
|
-
throw err;
|
|
1088
|
-
} finally {
|
|
1089
|
-
modelEffects = stopCapturingEffects();
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
wrapInAction(model);
|
|
1093
|
-
|
|
1094
|
-
model[Symbol.dispose] = action(function disposeModel() {
|
|
1095
|
-
if (modelEffects) {
|
|
1096
|
-
for (let i = 0; i < modelEffects.length; i++) {
|
|
1097
|
-
modelEffects[i].dispose();
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
modelEffects = undefined;
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
return model;
|
|
1105
|
-
} as InternalModelConstructor<TModel, TFactoryArgs>;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
//#endregion createModel
|
|
1109
|
-
|
|
1110
|
-
export {
|
|
1111
|
-
computed,
|
|
1112
|
-
effect,
|
|
1113
|
-
batch,
|
|
1114
|
-
untracked,
|
|
1115
|
-
action,
|
|
1116
|
-
createModel,
|
|
1117
|
-
Signal,
|
|
1118
|
-
ReadonlySignal,
|
|
1119
|
-
Effect,
|
|
1120
|
-
Computed,
|
|
1121
|
-
};
|