@esportsplus/reactivity 0.1.19 → 0.1.21
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/.editorconfig +9 -9
- package/.gitattributes +2 -2
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/bump.yml +7 -0
- package/.github/workflows/publish.yml +14 -0
- package/build/constants.js +1 -11
- package/build/index.js +6 -34
- package/build/macro.d.ts +1 -1
- package/build/macro.js +5 -10
- package/build/reactive/array.js +8 -12
- package/build/reactive/index.js +3 -5
- package/build/reactive/object.js +12 -15
- package/build/resource.js +7 -12
- package/build/signal.d.ts +3 -3
- package/build/signal.js +27 -34
- package/build/types.js +1 -2
- package/build/utilities.js +1 -5
- package/package.json +20 -20
- package/readme.md +1 -1
- package/src/constants.ts +18 -18
- package/src/index.ts +5 -5
- package/src/macro.ts +28 -28
- package/src/reactive/array.ts +212 -214
- package/src/reactive/index.ts +37 -37
- package/src/reactive/object.ts +76 -76
- package/src/resource.ts +70 -70
- package/src/signal.ts +375 -375
- package/src/types.ts +54 -54
- package/src/utilities.ts +5 -5
- package/tsconfig.json +10 -10
package/src/signal.ts
CHANGED
|
@@ -1,376 +1,376 @@
|
|
|
1
|
-
import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
|
|
2
|
-
import { Computed, Changed, Effect, Event, Function, Listener, NeverAsync, Options, Root, Scheduler, Signal, State, Type } from './types';
|
|
3
|
-
import { isArray } from './utilities';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let index = 0,
|
|
7
|
-
observer: Reactive<any> | null = null,
|
|
8
|
-
observers: Reactive<any>[] | null = null,
|
|
9
|
-
scope: Root | null = null;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Reactive<T> {
|
|
13
|
-
changed: Changed | null = null;
|
|
14
|
-
fn: Computed<T>['fn'] | Effect['fn'] | null = null;
|
|
15
|
-
listeners: Record<Event, (Listener<any> | null)[]> | null = null;
|
|
16
|
-
observers: Reactive<any>[] | null = null;
|
|
17
|
-
root: Root | null;
|
|
18
|
-
scheduler: Scheduler | null = null;
|
|
19
|
-
sources: Reactive<any>[] | null = null;
|
|
20
|
-
state: State;
|
|
21
|
-
task: Function | null = null;
|
|
22
|
-
tracking: boolean | null = null;
|
|
23
|
-
type: Type;
|
|
24
|
-
value: T;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
constructor(state: State, type: Type, value: T) {
|
|
28
|
-
let root = null;
|
|
29
|
-
|
|
30
|
-
if (type !== ROOT) {
|
|
31
|
-
if (scope !== null) {
|
|
32
|
-
root = scope;
|
|
33
|
-
}
|
|
34
|
-
else if (observer !== null) {
|
|
35
|
-
root = observer.root;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (root == null) {
|
|
39
|
-
if (type === EFFECT) {
|
|
40
|
-
throw new Error(`Reactivity: 'effect' cannot be created without a reactive root`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else if (root.tracking) {
|
|
44
|
-
root.on('dispose', () => this.dispose());
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this.root = root;
|
|
49
|
-
this.state = state;
|
|
50
|
-
this.type = type;
|
|
51
|
-
this.value = value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
dispatch<D>(event: Event, data?: D) {
|
|
56
|
-
if (this.listeners === null || this.listeners[event] === undefined) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let listeners = this.listeners[event],
|
|
61
|
-
values = {
|
|
62
|
-
data,
|
|
63
|
-
value: this.value
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
for (let i = 0, n = listeners.length; i < n; i++) {
|
|
67
|
-
let listener = listeners[i];
|
|
68
|
-
|
|
69
|
-
if (listener === null) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
listener(values);
|
|
75
|
-
|
|
76
|
-
if (listener.once !== undefined) {
|
|
77
|
-
listeners[i] = null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
listeners[i] = null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
dispose() {
|
|
87
|
-
if (this.state === DISPOSED) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
this.dispatch('cleanup', this);
|
|
92
|
-
this.dispatch('dispose', this);
|
|
93
|
-
|
|
94
|
-
removeSourceObservers(this, 0);
|
|
95
|
-
|
|
96
|
-
this.listeners = null;
|
|
97
|
-
this.observers = null;
|
|
98
|
-
this.sources = null;
|
|
99
|
-
this.state = DISPOSED;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get() {
|
|
103
|
-
if (this.state === DISPOSED) {
|
|
104
|
-
return this.value;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (observer !== null) {
|
|
108
|
-
if (observers === null) {
|
|
109
|
-
if (observer.sources !== null && observer.sources[index] == this) {
|
|
110
|
-
index++;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
observers = [this];
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
observers.push(this);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (this.type === COMPUTED || this.type === EFFECT) {
|
|
122
|
-
sync(this);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return this.value;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
on<T>(event: Event, listener: Listener<T>) {
|
|
129
|
-
if (this.state === DIRTY) {
|
|
130
|
-
if (event !== 'cleanup') {
|
|
131
|
-
throw new Error(`Reactivity: events set within computed or effects must use the 'cleanup' event name`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
listener.once = true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (this.listeners === null) {
|
|
138
|
-
this.listeners = { [event]: [listener] };
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
let listeners = this.listeners[event];
|
|
142
|
-
|
|
143
|
-
if (listeners === undefined) {
|
|
144
|
-
this.listeners[event] = [listener];
|
|
145
|
-
}
|
|
146
|
-
else if (listeners.indexOf(listener) === -1) {
|
|
147
|
-
let i = listeners.indexOf(null);
|
|
148
|
-
|
|
149
|
-
if (i === -1) {
|
|
150
|
-
listeners.push(listener);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
listeners[i] = listener;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
once<T>(event: Event, listener: Listener<T>) {
|
|
160
|
-
listener.once = true;
|
|
161
|
-
this.on(event, listener);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
set(value: T): T {
|
|
165
|
-
if (this.type !== SIGNAL && observer !== this) {
|
|
166
|
-
throw new Error(`Reactivity: 'set' method is only available on signals`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (this.changed!(this.value, value)) {
|
|
170
|
-
this.value = value;
|
|
171
|
-
notify(this.observers, DIRTY);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return this.value;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
function changed(a: unknown, b: unknown) {
|
|
180
|
-
return a !== b;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function notify<T>(nodes: Reactive<T>[] | null, state: typeof CHECK | typeof DIRTY) {
|
|
184
|
-
if (nodes === null) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
for (let i = 0, n = nodes.length; i < n; i++) {
|
|
189
|
-
let node = nodes[i];
|
|
190
|
-
|
|
191
|
-
if (node.state < state) {
|
|
192
|
-
if (node.type === EFFECT && node.state === CLEAN) {
|
|
193
|
-
(node as Effect).root.scheduler((node as Effect).task);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
node.state = state;
|
|
197
|
-
notify(node.observers, CHECK);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function removeSourceObservers<T>(node: Reactive<T>, start: number) {
|
|
203
|
-
if (node.sources === null) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
for (let i = start, n = node.sources.length; i < n; i++) {
|
|
208
|
-
let observers = node.sources[i].observers;
|
|
209
|
-
|
|
210
|
-
if (observers === null) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
observers[observers.indexOf(node)] = observers[observers.length - 1];
|
|
215
|
-
observers.pop();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function sync<T>(node: Reactive<T>) {
|
|
220
|
-
if (node.state === CHECK && node.sources !== null) {
|
|
221
|
-
for (let i = 0, n = node.sources.length; i < n; i++) {
|
|
222
|
-
sync(node.sources[i]);
|
|
223
|
-
|
|
224
|
-
// Stop the loop here so we won't trigger updates on other parents unnecessarily
|
|
225
|
-
// If our computation changes to no longer use some sources, we don't
|
|
226
|
-
// want to update() a source we used last time, but now don't use.
|
|
227
|
-
if ((node.state as State) === DIRTY) {
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (node.state === DIRTY) {
|
|
234
|
-
update(node);
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
node.state = CLEAN;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function update<T>(node: Reactive<T>) {
|
|
242
|
-
let i = index,
|
|
243
|
-
o = observer,
|
|
244
|
-
os = observers;
|
|
245
|
-
|
|
246
|
-
index = 0;
|
|
247
|
-
observer = node;
|
|
248
|
-
observers = null as typeof observers;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
node.dispatch('cleanup');
|
|
252
|
-
node.dispatch('update');
|
|
253
|
-
|
|
254
|
-
// @ts-ignore
|
|
255
|
-
let value = node.fn.call(null, node);
|
|
256
|
-
|
|
257
|
-
if (observers) {
|
|
258
|
-
removeSourceObservers(node, index);
|
|
259
|
-
|
|
260
|
-
if (node.sources !== null && index > 0) {
|
|
261
|
-
node.sources.length = index + observers.length;
|
|
262
|
-
|
|
263
|
-
for (let i = 0, n = observers.length; i < n; i++) {
|
|
264
|
-
node.sources[index + i] = observers[i];
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
node.sources = observers;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
for (let i = index, n = node.sources.length; i < n; i++) {
|
|
272
|
-
let source = node.sources[i];
|
|
273
|
-
|
|
274
|
-
if (source.observers === null) {
|
|
275
|
-
source.observers = [node];
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
source.observers.push(node);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
else if (node.sources !== null && index < node.sources.length) {
|
|
283
|
-
removeSourceObservers(node, index);
|
|
284
|
-
node.sources.length = index;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (node.type === COMPUTED) {
|
|
288
|
-
node.set(value as T);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
finally {
|
|
292
|
-
index = i;
|
|
293
|
-
observer = o;
|
|
294
|
-
observers = os;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
node.state = CLEAN;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const computed = <T>(fn: Computed<T>['fn'], options?: Options) => {
|
|
302
|
-
let instance = new Reactive(DIRTY, COMPUTED, undefined as T);
|
|
303
|
-
|
|
304
|
-
instance.changed = options?.changed || changed;
|
|
305
|
-
instance.fn = fn;
|
|
306
|
-
|
|
307
|
-
return instance as Computed<T>;
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const dispose = <T extends { dispose: VoidFunction }>(dispose?: T[] | T | null) => {
|
|
311
|
-
if (dispose == null) {
|
|
312
|
-
}
|
|
313
|
-
else if (isArray(dispose)) {
|
|
314
|
-
for (let i = 0, n = dispose.length; i < n; i++) {
|
|
315
|
-
dispose[i].dispose();
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
dispose.dispose();
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return dispose;
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const effect = (fn: Effect['fn']) => {
|
|
326
|
-
let instance = new Reactive(DIRTY, EFFECT, null);
|
|
327
|
-
|
|
328
|
-
instance.fn = fn;
|
|
329
|
-
instance.task = () => instance.get();
|
|
330
|
-
|
|
331
|
-
update(instance);
|
|
332
|
-
|
|
333
|
-
return instance as Effect;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const root = <T>(fn: NeverAsync<(instance: Root) => T>, scheduler?: Scheduler) => {
|
|
337
|
-
let o = observer,
|
|
338
|
-
s = scope;
|
|
339
|
-
|
|
340
|
-
if (scheduler === undefined) {
|
|
341
|
-
if (o?.type === EFFECT) {
|
|
342
|
-
scope = o.root;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (scope === null) {
|
|
346
|
-
throw new Error('Reactivity: `root` cannot be created without a task scheduler');
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
scheduler = scope.scheduler;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
observer = null;
|
|
353
|
-
|
|
354
|
-
scope = new Reactive(CLEAN, ROOT, null) as any as Root;
|
|
355
|
-
scope.scheduler = scheduler;
|
|
356
|
-
scope.tracking = fn.length > 0;
|
|
357
|
-
|
|
358
|
-
let result = fn.call(null, scope);
|
|
359
|
-
|
|
360
|
-
observer = o;
|
|
361
|
-
scope = s;
|
|
362
|
-
|
|
363
|
-
return result;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const signal = <T>(value: T, options?: Options) => {
|
|
367
|
-
let instance = new Reactive(CLEAN, SIGNAL, value);
|
|
368
|
-
|
|
369
|
-
instance.changed = options?.changed || changed;
|
|
370
|
-
|
|
371
|
-
return instance as Signal<T>;
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
export { computed, dispose, effect, root, signal };
|
|
1
|
+
import { CHECK, CLEAN, COMPUTED, DIRTY, DISPOSED, EFFECT, ROOT, SIGNAL } from './constants';
|
|
2
|
+
import { Computed, Changed, Effect, Event, Function, Listener, NeverAsync, Options, Root, Scheduler, Signal, State, Type } from './types';
|
|
3
|
+
import { isArray } from './utilities';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
let index = 0,
|
|
7
|
+
observer: Reactive<any> | null = null,
|
|
8
|
+
observers: Reactive<any>[] | null = null,
|
|
9
|
+
scope: Root | null = null;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Reactive<T> {
|
|
13
|
+
changed: Changed | null = null;
|
|
14
|
+
fn: Computed<T>['fn'] | Effect['fn'] | null = null;
|
|
15
|
+
listeners: Record<Event, (Listener<any> | null)[]> | null = null;
|
|
16
|
+
observers: Reactive<any>[] | null = null;
|
|
17
|
+
root: Root | null;
|
|
18
|
+
scheduler: Scheduler | null = null;
|
|
19
|
+
sources: Reactive<any>[] | null = null;
|
|
20
|
+
state: State;
|
|
21
|
+
task: Function | null = null;
|
|
22
|
+
tracking: boolean | null = null;
|
|
23
|
+
type: Type;
|
|
24
|
+
value: T;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
constructor(state: State, type: Type, value: T) {
|
|
28
|
+
let root = null;
|
|
29
|
+
|
|
30
|
+
if (type !== ROOT) {
|
|
31
|
+
if (scope !== null) {
|
|
32
|
+
root = scope;
|
|
33
|
+
}
|
|
34
|
+
else if (observer !== null) {
|
|
35
|
+
root = observer.root;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (root == null) {
|
|
39
|
+
if (type === EFFECT) {
|
|
40
|
+
throw new Error(`Reactivity: 'effect' cannot be created without a reactive root`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (root.tracking) {
|
|
44
|
+
root.on('dispose', () => this.dispose());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.root = root;
|
|
49
|
+
this.state = state;
|
|
50
|
+
this.type = type;
|
|
51
|
+
this.value = value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
dispatch<D>(event: Event, data?: D) {
|
|
56
|
+
if (this.listeners === null || this.listeners[event] === undefined) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let listeners = this.listeners[event],
|
|
61
|
+
values = {
|
|
62
|
+
data,
|
|
63
|
+
value: this.value
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
for (let i = 0, n = listeners.length; i < n; i++) {
|
|
67
|
+
let listener = listeners[i];
|
|
68
|
+
|
|
69
|
+
if (listener === null) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
listener(values);
|
|
75
|
+
|
|
76
|
+
if (listener.once !== undefined) {
|
|
77
|
+
listeners[i] = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
listeners[i] = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
dispose() {
|
|
87
|
+
if (this.state === DISPOSED) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.dispatch('cleanup', this);
|
|
92
|
+
this.dispatch('dispose', this);
|
|
93
|
+
|
|
94
|
+
removeSourceObservers(this, 0);
|
|
95
|
+
|
|
96
|
+
this.listeners = null;
|
|
97
|
+
this.observers = null;
|
|
98
|
+
this.sources = null;
|
|
99
|
+
this.state = DISPOSED;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get() {
|
|
103
|
+
if (this.state === DISPOSED) {
|
|
104
|
+
return this.value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (observer !== null) {
|
|
108
|
+
if (observers === null) {
|
|
109
|
+
if (observer.sources !== null && observer.sources[index] == this) {
|
|
110
|
+
index++;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
observers = [this];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
observers.push(this);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.type === COMPUTED || this.type === EFFECT) {
|
|
122
|
+
sync(this);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
on<T>(event: Event, listener: Listener<T>) {
|
|
129
|
+
if (this.state === DIRTY) {
|
|
130
|
+
if (event !== 'cleanup') {
|
|
131
|
+
throw new Error(`Reactivity: events set within computed or effects must use the 'cleanup' event name`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
listener.once = true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this.listeners === null) {
|
|
138
|
+
this.listeners = { [event]: [listener] };
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
let listeners = this.listeners[event];
|
|
142
|
+
|
|
143
|
+
if (listeners === undefined) {
|
|
144
|
+
this.listeners[event] = [listener];
|
|
145
|
+
}
|
|
146
|
+
else if (listeners.indexOf(listener) === -1) {
|
|
147
|
+
let i = listeners.indexOf(null);
|
|
148
|
+
|
|
149
|
+
if (i === -1) {
|
|
150
|
+
listeners.push(listener);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
listeners[i] = listener;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
once<T>(event: Event, listener: Listener<T>) {
|
|
160
|
+
listener.once = true;
|
|
161
|
+
this.on(event, listener);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
set(value: T): T {
|
|
165
|
+
if (this.type !== SIGNAL && observer !== this) {
|
|
166
|
+
throw new Error(`Reactivity: 'set' method is only available on signals`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (this.changed!(this.value, value)) {
|
|
170
|
+
this.value = value;
|
|
171
|
+
notify(this.observers, DIRTY);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return this.value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
function changed(a: unknown, b: unknown) {
|
|
180
|
+
return a !== b;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function notify<T>(nodes: Reactive<T>[] | null, state: typeof CHECK | typeof DIRTY) {
|
|
184
|
+
if (nodes === null) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (let i = 0, n = nodes.length; i < n; i++) {
|
|
189
|
+
let node = nodes[i];
|
|
190
|
+
|
|
191
|
+
if (node.state < state) {
|
|
192
|
+
if (node.type === EFFECT && node.state === CLEAN) {
|
|
193
|
+
(node as Effect).root.scheduler((node as Effect).task);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
node.state = state;
|
|
197
|
+
notify(node.observers, CHECK);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function removeSourceObservers<T>(node: Reactive<T>, start: number) {
|
|
203
|
+
if (node.sources === null) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (let i = start, n = node.sources.length; i < n; i++) {
|
|
208
|
+
let observers = node.sources[i].observers;
|
|
209
|
+
|
|
210
|
+
if (observers === null) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
observers[observers.indexOf(node)] = observers[observers.length - 1];
|
|
215
|
+
observers.pop();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sync<T>(node: Reactive<T>) {
|
|
220
|
+
if (node.state === CHECK && node.sources !== null) {
|
|
221
|
+
for (let i = 0, n = node.sources.length; i < n; i++) {
|
|
222
|
+
sync(node.sources[i]);
|
|
223
|
+
|
|
224
|
+
// Stop the loop here so we won't trigger updates on other parents unnecessarily
|
|
225
|
+
// If our computation changes to no longer use some sources, we don't
|
|
226
|
+
// want to update() a source we used last time, but now don't use.
|
|
227
|
+
if ((node.state as State) === DIRTY) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (node.state === DIRTY) {
|
|
234
|
+
update(node);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
node.state = CLEAN;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function update<T>(node: Reactive<T>) {
|
|
242
|
+
let i = index,
|
|
243
|
+
o = observer,
|
|
244
|
+
os = observers;
|
|
245
|
+
|
|
246
|
+
index = 0;
|
|
247
|
+
observer = node;
|
|
248
|
+
observers = null as typeof observers;
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
node.dispatch('cleanup');
|
|
252
|
+
node.dispatch('update');
|
|
253
|
+
|
|
254
|
+
// @ts-ignore
|
|
255
|
+
let value = node.fn.call(null, node);
|
|
256
|
+
|
|
257
|
+
if (observers) {
|
|
258
|
+
removeSourceObservers(node, index);
|
|
259
|
+
|
|
260
|
+
if (node.sources !== null && index > 0) {
|
|
261
|
+
node.sources.length = index + observers.length;
|
|
262
|
+
|
|
263
|
+
for (let i = 0, n = observers.length; i < n; i++) {
|
|
264
|
+
node.sources[index + i] = observers[i];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
node.sources = observers;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (let i = index, n = node.sources.length; i < n; i++) {
|
|
272
|
+
let source = node.sources[i];
|
|
273
|
+
|
|
274
|
+
if (source.observers === null) {
|
|
275
|
+
source.observers = [node];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
source.observers.push(node);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (node.sources !== null && index < node.sources.length) {
|
|
283
|
+
removeSourceObservers(node, index);
|
|
284
|
+
node.sources.length = index;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (node.type === COMPUTED) {
|
|
288
|
+
node.set(value as T);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
index = i;
|
|
293
|
+
observer = o;
|
|
294
|
+
observers = os;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
node.state = CLEAN;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
const computed = <T>(fn: Computed<T>['fn'], options?: Options) => {
|
|
302
|
+
let instance = new Reactive(DIRTY, COMPUTED, undefined as T);
|
|
303
|
+
|
|
304
|
+
instance.changed = options?.changed || changed;
|
|
305
|
+
instance.fn = fn;
|
|
306
|
+
|
|
307
|
+
return instance as Computed<T>;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const dispose = <T extends { dispose: VoidFunction }>(dispose?: T[] | T | null) => {
|
|
311
|
+
if (dispose == null) {
|
|
312
|
+
}
|
|
313
|
+
else if (isArray(dispose)) {
|
|
314
|
+
for (let i = 0, n = dispose.length; i < n; i++) {
|
|
315
|
+
dispose[i].dispose();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
dispose.dispose();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return dispose;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const effect = (fn: Effect['fn']) => {
|
|
326
|
+
let instance = new Reactive(DIRTY, EFFECT, null);
|
|
327
|
+
|
|
328
|
+
instance.fn = fn;
|
|
329
|
+
instance.task = () => instance.get();
|
|
330
|
+
|
|
331
|
+
update(instance);
|
|
332
|
+
|
|
333
|
+
return instance as Effect;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const root = <T>(fn: NeverAsync<(instance: Root) => T>, scheduler?: Scheduler) => {
|
|
337
|
+
let o = observer,
|
|
338
|
+
s = scope;
|
|
339
|
+
|
|
340
|
+
if (scheduler === undefined) {
|
|
341
|
+
if (o?.type === EFFECT) {
|
|
342
|
+
scope = o.root;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (scope === null) {
|
|
346
|
+
throw new Error('Reactivity: `root` cannot be created without a task scheduler');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
scheduler = scope.scheduler;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
observer = null;
|
|
353
|
+
|
|
354
|
+
scope = new Reactive(CLEAN, ROOT, null) as any as Root;
|
|
355
|
+
scope.scheduler = scheduler;
|
|
356
|
+
scope.tracking = fn.length > 0;
|
|
357
|
+
|
|
358
|
+
let result = fn.call(null, scope);
|
|
359
|
+
|
|
360
|
+
observer = o;
|
|
361
|
+
scope = s;
|
|
362
|
+
|
|
363
|
+
return result;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const signal = <T>(value: T, options?: Options) => {
|
|
367
|
+
let instance = new Reactive(CLEAN, SIGNAL, value);
|
|
368
|
+
|
|
369
|
+
instance.changed = options?.changed || changed;
|
|
370
|
+
|
|
371
|
+
return instance as Signal<T>;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
export { computed, dispose, effect, root, signal };
|
|
376
376
|
export { Reactive };
|