@esportsplus/reactivity 0.11.8 → 0.12.1
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/build/constants.d.ts +5 -2
- package/build/constants.js +5 -2
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/reactive/array.d.ts +8 -3
- package/build/reactive/array.js +28 -36
- package/build/reactive/index.d.ts +2 -5
- package/build/reactive/index.js +17 -7
- package/build/reactive/object.d.ts +6 -4
- package/build/reactive/object.js +17 -21
- package/build/system.d.ts +7 -5
- package/build/system.js +49 -48
- package/build/types.d.ts +8 -6
- package/build/types.js +1 -1
- package/package.json +1 -1
- package/src/constants.ts +11 -2
- package/src/index.ts +1 -0
- package/src/reactive/array.ts +34 -46
- package/src/reactive/index.ts +30 -12
- package/src/reactive/object.ts +22 -33
- package/src/system.ts +56 -54
- package/src/types.ts +8 -6
- package/build/reactive/disposable.d.ts +0 -4
- package/build/reactive/disposable.js +0 -6
- package/src/reactive/disposable.ts +0 -8
package/build/constants.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
declare const
|
|
1
|
+
declare const COMPUTED: unique symbol;
|
|
2
|
+
declare const REACTIVE_ARRAY: unique symbol;
|
|
3
|
+
declare const REACTIVE_OBJECT: unique symbol;
|
|
4
|
+
declare const SIGNAL: unique symbol;
|
|
2
5
|
declare const STABILIZER_IDLE = 0;
|
|
3
6
|
declare const STABILIZER_RESCHEDULE = 1;
|
|
4
7
|
declare const STABILIZER_RUNNING = 2;
|
|
@@ -8,4 +11,4 @@ declare const STATE_CHECK: number;
|
|
|
8
11
|
declare const STATE_DIRTY: number;
|
|
9
12
|
declare const STATE_RECOMPUTING: number;
|
|
10
13
|
declare const STATE_IN_HEAP: number;
|
|
11
|
-
export {
|
|
14
|
+
export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
|
package/build/constants.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
const
|
|
1
|
+
const COMPUTED = Symbol('computed');
|
|
2
|
+
const REACTIVE_ARRAY = Symbol('reactive.array');
|
|
3
|
+
const REACTIVE_OBJECT = Symbol('reactive.object');
|
|
4
|
+
const SIGNAL = Symbol('signal');
|
|
2
5
|
const STABILIZER_IDLE = 0;
|
|
3
6
|
const STABILIZER_RESCHEDULE = 1;
|
|
4
7
|
const STABILIZER_RUNNING = 2;
|
|
@@ -8,4 +11,4 @@ const STATE_CHECK = 1 << 0;
|
|
|
8
11
|
const STATE_DIRTY = 1 << 1;
|
|
9
12
|
const STATE_RECOMPUTING = 1 << 2;
|
|
10
13
|
const STATE_IN_HEAP = 1 << 3;
|
|
11
|
-
export {
|
|
14
|
+
export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { REACTIVE_ARRAY } from '../constants.js';
|
|
1
2
|
import { Computed, Infer } from '../types.js';
|
|
2
3
|
import { ReactiveObject } from './object.js';
|
|
3
|
-
import { Disposable } from './disposable.js';
|
|
4
4
|
type API<T> = Infer<T>[] & ReactiveArray<T>;
|
|
5
5
|
type Events<T> = {
|
|
6
6
|
pop: {
|
|
@@ -27,19 +27,22 @@ type Events<T> = {
|
|
|
27
27
|
items: Item<T>[];
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
|
-
type Item<T> = Computed<T> | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never
|
|
30
|
+
type Item<T> = T | Computed<T> | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never>;
|
|
31
31
|
type Listener<V> = {
|
|
32
32
|
once?: boolean;
|
|
33
33
|
(value: V): void;
|
|
34
34
|
};
|
|
35
35
|
type Value<T> = T extends Record<PropertyKey, unknown> ? ReactiveObject<T> : T extends Array<infer U> ? API<U> : T;
|
|
36
|
-
declare class ReactiveArray<T>
|
|
36
|
+
declare class ReactiveArray<T> {
|
|
37
|
+
[REACTIVE_ARRAY]: boolean;
|
|
38
|
+
disposables: number;
|
|
37
39
|
private data;
|
|
38
40
|
private listeners;
|
|
39
41
|
private proxy;
|
|
40
42
|
constructor(data: Item<T>[], proxy: API<T>);
|
|
41
43
|
get length(): number;
|
|
42
44
|
set length(n: number);
|
|
45
|
+
private cleanup;
|
|
43
46
|
at(i: number): T | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never>;
|
|
44
47
|
dispatch<K extends keyof Events<T>, V>(event: K, value?: V): void;
|
|
45
48
|
dispose(): void;
|
|
@@ -54,5 +57,7 @@ declare class ReactiveArray<T> extends Disposable {
|
|
|
54
57
|
splice(start: number, deleteCount?: number, ...input: T[]): Item<T>[];
|
|
55
58
|
unshift(...input: T[]): number;
|
|
56
59
|
}
|
|
60
|
+
declare const isReactiveArray: (value: any) => value is ReactiveArray<any>;
|
|
57
61
|
export default function array<T>(input: T[]): API<T>;
|
|
62
|
+
export { isReactiveArray };
|
|
58
63
|
export type { API as ReactiveArray };
|
package/build/reactive/array.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { isFunction,
|
|
1
|
+
import { isFunction, isNumber, isObject } from '@esportsplus/utilities';
|
|
2
|
+
import { REACTIVE_ARRAY } from '../constants.js';
|
|
2
3
|
import { computed, dispose, isComputed, read } from '../system.js';
|
|
3
|
-
import object from './object.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import object, { isReactiveObject } from './object.js';
|
|
5
|
+
class ReactiveArray {
|
|
6
|
+
[REACTIVE_ARRAY] = true;
|
|
7
|
+
disposables = 0;
|
|
6
8
|
data;
|
|
7
9
|
listeners = null;
|
|
8
10
|
proxy;
|
|
9
11
|
constructor(data, proxy) {
|
|
10
|
-
super();
|
|
11
12
|
this.data = data;
|
|
12
13
|
this.proxy = proxy;
|
|
13
14
|
}
|
|
@@ -20,6 +21,14 @@ class ReactiveArray extends Disposable {
|
|
|
20
21
|
}
|
|
21
22
|
this.splice(n);
|
|
22
23
|
}
|
|
24
|
+
cleanup(item) {
|
|
25
|
+
if (isReactiveObject(item)) {
|
|
26
|
+
item.dispose();
|
|
27
|
+
}
|
|
28
|
+
else if (isComputed(item)) {
|
|
29
|
+
dispose(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
23
32
|
at(i) {
|
|
24
33
|
let value = this.data[i];
|
|
25
34
|
if (isComputed(value)) {
|
|
@@ -49,17 +58,9 @@ class ReactiveArray extends Disposable {
|
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
dispose() {
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
let value = data[i];
|
|
55
|
-
if (isInstanceOf(value, Disposable)) {
|
|
56
|
-
value.dispose();
|
|
57
|
-
}
|
|
58
|
-
else if (isComputed(value)) {
|
|
59
|
-
dispose(value);
|
|
60
|
-
}
|
|
61
|
+
for (let i = 0, n = this.data.length; i < n; i++) {
|
|
62
|
+
this.cleanup(this.data[i]);
|
|
61
63
|
}
|
|
62
|
-
this.listeners = null;
|
|
63
64
|
}
|
|
64
65
|
map(fn, i, n) {
|
|
65
66
|
let { data, proxy } = this, values = [];
|
|
@@ -103,9 +104,7 @@ class ReactiveArray extends Disposable {
|
|
|
103
104
|
pop() {
|
|
104
105
|
let item = this.data.pop();
|
|
105
106
|
if (item !== undefined) {
|
|
106
|
-
|
|
107
|
-
dispose(item);
|
|
108
|
-
}
|
|
107
|
+
this.cleanup(item);
|
|
109
108
|
this.dispatch('pop', { item });
|
|
110
109
|
}
|
|
111
110
|
return item;
|
|
@@ -123,9 +122,7 @@ class ReactiveArray extends Disposable {
|
|
|
123
122
|
shift() {
|
|
124
123
|
let item = this.data.shift();
|
|
125
124
|
if (item !== undefined) {
|
|
126
|
-
|
|
127
|
-
dispose(item);
|
|
128
|
-
}
|
|
125
|
+
this.cleanup(item);
|
|
129
126
|
this.dispatch('shift', { item });
|
|
130
127
|
}
|
|
131
128
|
return item;
|
|
@@ -139,10 +136,7 @@ class ReactiveArray extends Disposable {
|
|
|
139
136
|
let items = factory(input), removed = this.data.splice(start, deleteCount, ...items);
|
|
140
137
|
if (items.length > 0 || removed.length > 0) {
|
|
141
138
|
for (let i = 0, n = removed.length; i < n; i++) {
|
|
142
|
-
|
|
143
|
-
if (isComputed(item)) {
|
|
144
|
-
dispose(item);
|
|
145
|
-
}
|
|
139
|
+
this.cleanup(removed[i]);
|
|
146
140
|
}
|
|
147
141
|
this.dispatch('splice', {
|
|
148
142
|
deleteCount,
|
|
@@ -174,6 +168,9 @@ function factory(input) {
|
|
|
174
168
|
}
|
|
175
169
|
return items;
|
|
176
170
|
}
|
|
171
|
+
const isReactiveArray = (value) => {
|
|
172
|
+
return isObject(value) && REACTIVE_ARRAY in value;
|
|
173
|
+
};
|
|
177
174
|
export default function array(input) {
|
|
178
175
|
let proxy = new Proxy({}, {
|
|
179
176
|
get(_, key) {
|
|
@@ -184,29 +181,24 @@ export default function array(input) {
|
|
|
184
181
|
}
|
|
185
182
|
return value;
|
|
186
183
|
}
|
|
187
|
-
else if (key in
|
|
188
|
-
return
|
|
184
|
+
else if (key in array) {
|
|
185
|
+
return array[key];
|
|
189
186
|
}
|
|
190
187
|
return wrapped[key];
|
|
191
188
|
},
|
|
192
189
|
set(_, key, value) {
|
|
193
190
|
if (isNumber(key)) {
|
|
194
|
-
|
|
195
|
-
if (host === undefined || !isComputed(host)) {
|
|
196
|
-
a.splice(key, 1, value);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
191
|
+
array.splice(key, 1, value);
|
|
201
192
|
return true;
|
|
202
193
|
}
|
|
203
194
|
else if (key === 'length') {
|
|
204
|
-
return
|
|
195
|
+
return array.length = value;
|
|
205
196
|
}
|
|
206
197
|
return false;
|
|
207
198
|
}
|
|
208
199
|
}), wrapped = factory(input);
|
|
209
|
-
let
|
|
200
|
+
let array = new ReactiveArray(wrapped, proxy);
|
|
210
201
|
return proxy;
|
|
211
202
|
}
|
|
212
203
|
;
|
|
204
|
+
export { isReactiveArray };
|
|
@@ -3,10 +3,7 @@ import object from './object.js';
|
|
|
3
3
|
type API<T> = T extends Record<PropertyKey, unknown> ? ReturnType<typeof object<T>> : T extends unknown[] ? ReturnType<typeof array<T>> : never;
|
|
4
4
|
type Input<T> = T extends {
|
|
5
5
|
dispose: any;
|
|
6
|
-
}
|
|
7
|
-
signals: any;
|
|
8
|
-
} ? {
|
|
9
|
-
never: '[ dispose, signals ] are reserved keys';
|
|
10
|
-
} : T;
|
|
6
|
+
} ? never : T extends Record<PropertyKey, unknown> ? T : T extends unknown[] ? Input<T[number]>[] : never;
|
|
11
7
|
declare const _default: <T extends Record<PropertyKey, unknown> | unknown[]>(input: Input<T>) => API<T>;
|
|
12
8
|
export default _default;
|
|
9
|
+
export type { Input };
|
package/build/reactive/index.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
2
|
+
import { onCleanup, root } from '../system.js';
|
|
2
3
|
import array from './array.js';
|
|
3
4
|
import object from './object.js';
|
|
4
5
|
export default (input) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
let value;
|
|
7
|
+
return root(() => {
|
|
8
|
+
if (isArray(input)) {
|
|
9
|
+
value = array(input);
|
|
10
|
+
}
|
|
11
|
+
else if (isObject(input)) {
|
|
12
|
+
value = object(input);
|
|
13
|
+
}
|
|
14
|
+
if (value) {
|
|
15
|
+
if (root.disposables) {
|
|
16
|
+
onCleanup(() => value.dispose());
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
21
|
+
});
|
|
12
22
|
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Prettify } from '@esportsplus/utilities';
|
|
2
2
|
import { Infer } from '../types.js';
|
|
3
|
-
import {
|
|
3
|
+
import { REACTIVE_OBJECT } from '../constants.js';
|
|
4
4
|
type API<T extends Record<PropertyKey, unknown>> = Prettify<{
|
|
5
5
|
[K in keyof T]: Infer<T[K]>;
|
|
6
6
|
}> & ReactiveObject<T>;
|
|
7
|
-
declare class ReactiveObject<T extends Record<PropertyKey, unknown>>
|
|
8
|
-
|
|
7
|
+
declare class ReactiveObject<T extends Record<PropertyKey, unknown>> {
|
|
8
|
+
[REACTIVE_OBJECT]: boolean;
|
|
9
9
|
constructor(data: T);
|
|
10
10
|
dispose(): void;
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
declare const isReactiveObject: (value: any) => value is ReactiveObject<any>;
|
|
13
|
+
export default function object<T extends Record<PropertyKey, unknown>>(input: T): ReactiveObject<T>;
|
|
14
|
+
export { isReactiveObject };
|
|
13
15
|
export type { API as ReactiveObject };
|
package/build/reactive/object.js
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
import { defineProperty, isArray, isFunction,
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
disposable = {};
|
|
1
|
+
import { defineProperty, isArray, isFunction, isObject, isPromise } from '@esportsplus/utilities';
|
|
2
|
+
import { computed, dispose, effect, isComputed, read, root, set, signal } from '../system.js';
|
|
3
|
+
import { REACTIVE_OBJECT } from '../constants.js';
|
|
4
|
+
import array, { isReactiveArray } from './array.js';
|
|
5
|
+
class ReactiveObject {
|
|
6
|
+
[REACTIVE_OBJECT] = true;
|
|
8
7
|
constructor(data) {
|
|
9
|
-
super();
|
|
10
|
-
let disposable = this.disposable, triggers = {};
|
|
11
8
|
for (let key in data) {
|
|
12
9
|
let value = data[key];
|
|
13
10
|
if (isArray(value)) {
|
|
14
|
-
let a =
|
|
11
|
+
let a = array(value);
|
|
15
12
|
defineProperty(this, key, {
|
|
16
13
|
enumerable: true,
|
|
17
14
|
get() {
|
|
18
|
-
read(t);
|
|
19
15
|
return a;
|
|
20
|
-
},
|
|
21
|
-
set(v) {
|
|
22
|
-
a = disposable[key] = array(v);
|
|
23
|
-
set(t, !!t.value);
|
|
24
16
|
}
|
|
25
17
|
});
|
|
26
18
|
}
|
|
@@ -31,7 +23,7 @@ class ReactiveObject extends Disposable {
|
|
|
31
23
|
get() {
|
|
32
24
|
if (c === undefined) {
|
|
33
25
|
root(() => {
|
|
34
|
-
c =
|
|
26
|
+
c = computed(value);
|
|
35
27
|
if (isPromise(c.value)) {
|
|
36
28
|
let factory = c, version = 0;
|
|
37
29
|
c = signal(undefined);
|
|
@@ -66,19 +58,23 @@ class ReactiveObject extends Disposable {
|
|
|
66
58
|
}
|
|
67
59
|
}
|
|
68
60
|
dispose() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
61
|
+
let value;
|
|
62
|
+
for (let key in this) {
|
|
63
|
+
value = this[key];
|
|
64
|
+
if (isReactiveArray(value) || isReactiveObject(value)) {
|
|
72
65
|
value.dispose();
|
|
73
66
|
}
|
|
74
|
-
else {
|
|
67
|
+
else if (isComputed(value)) {
|
|
75
68
|
dispose(value);
|
|
76
69
|
}
|
|
77
70
|
}
|
|
78
|
-
this.disposable = {};
|
|
79
71
|
}
|
|
80
72
|
}
|
|
73
|
+
const isReactiveObject = (value) => {
|
|
74
|
+
return isObject(value) && REACTIVE_OBJECT in value;
|
|
75
|
+
};
|
|
81
76
|
export default function object(input) {
|
|
82
77
|
return new ReactiveObject(input);
|
|
83
78
|
}
|
|
84
79
|
;
|
|
80
|
+
export { isReactiveObject };
|
package/build/system.d.ts
CHANGED
|
@@ -6,9 +6,11 @@ declare const isComputed: (value: unknown) => value is Computed<unknown>;
|
|
|
6
6
|
declare const isSignal: (value: unknown) => value is Signal<unknown>;
|
|
7
7
|
declare const onCleanup: (fn: VoidFunction) => typeof fn;
|
|
8
8
|
declare const read: <T>(node: Signal<T> | Computed<T>) => T;
|
|
9
|
-
declare const root:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
set<T>(signal: Signal<T>, value: T): void;
|
|
9
|
+
declare const root: {
|
|
10
|
+
<T>(fn: () => T): T;
|
|
11
|
+
disposables: number;
|
|
13
12
|
};
|
|
14
|
-
|
|
13
|
+
declare const set: <T>(signal: Signal<T>, value: T) => void;
|
|
14
|
+
declare const signal: <T>(value: T) => Signal<T>;
|
|
15
|
+
export { computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, set, signal };
|
|
16
|
+
export type { Computed, Signal };
|
package/build/system.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
2
|
-
import {
|
|
3
|
-
let depth = 0, heap = new Array(
|
|
4
|
-
function cleanup(
|
|
5
|
-
if (!
|
|
2
|
+
import { COMPUTED, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
|
|
3
|
+
let depth = 0, heap = new Array(2048), index = 0, length = 0, microtask = queueMicrotask, notified = false, observer = null, stabilizer = STABILIZER_IDLE, version = 0;
|
|
4
|
+
function cleanup(computed) {
|
|
5
|
+
if (!computed.cleanup) {
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
|
-
let
|
|
9
|
-
if (isArray(
|
|
10
|
-
for (let i = 0; i <
|
|
11
|
-
|
|
8
|
+
let value = computed.cleanup;
|
|
9
|
+
if (isArray(value)) {
|
|
10
|
+
for (let i = 0, n = value.length; i < n; i++) {
|
|
11
|
+
value[i]();
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
-
|
|
15
|
+
value();
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
computed.cleanup = null;
|
|
18
18
|
}
|
|
19
19
|
function deleteFromHeap(computed) {
|
|
20
20
|
let state = computed.state;
|
|
@@ -58,7 +58,7 @@ function insertIntoHeap(computed) {
|
|
|
58
58
|
if (height > length) {
|
|
59
59
|
length = height;
|
|
60
60
|
if (height >= heap.length) {
|
|
61
|
-
heap.length
|
|
61
|
+
heap.length = Math.round(heap.length * 1.5);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -138,11 +138,11 @@ function recompute(computed, del) {
|
|
|
138
138
|
depth--;
|
|
139
139
|
observer = o;
|
|
140
140
|
computed.state = STATE_NONE;
|
|
141
|
-
let depsTail = computed.depsTail,
|
|
142
|
-
if (
|
|
141
|
+
let depsTail = computed.depsTail, remove = depsTail !== null ? depsTail.nextDep : computed.deps;
|
|
142
|
+
if (remove !== null) {
|
|
143
143
|
do {
|
|
144
|
-
|
|
145
|
-
} while (
|
|
144
|
+
remove = unlink(remove);
|
|
145
|
+
} while (remove !== null);
|
|
146
146
|
if (depsTail !== null) {
|
|
147
147
|
depsTail.nextDep = null;
|
|
148
148
|
}
|
|
@@ -153,11 +153,11 @@ function recompute(computed, del) {
|
|
|
153
153
|
if (ok && value !== computed.value) {
|
|
154
154
|
computed.value = value;
|
|
155
155
|
for (let c = computed.subs; c !== null; c = c.nextSub) {
|
|
156
|
-
let
|
|
156
|
+
let s = c.sub, state = s.state;
|
|
157
157
|
if (state & STATE_CHECK) {
|
|
158
|
-
|
|
158
|
+
s.state = state | STATE_DIRTY;
|
|
159
159
|
}
|
|
160
|
-
insertIntoHeap(
|
|
160
|
+
insertIntoHeap(s);
|
|
161
161
|
}
|
|
162
162
|
schedule();
|
|
163
163
|
}
|
|
@@ -165,7 +165,7 @@ function recompute(computed, del) {
|
|
|
165
165
|
function schedule() {
|
|
166
166
|
if (stabilizer === STABILIZER_IDLE && !depth) {
|
|
167
167
|
stabilizer = STABILIZER_SCHEDULED;
|
|
168
|
-
|
|
168
|
+
microtask(stabilize);
|
|
169
169
|
}
|
|
170
170
|
else if (stabilizer === STABILIZER_RUNNING) {
|
|
171
171
|
stabilizer = STABILIZER_RESCHEDULE;
|
|
@@ -184,7 +184,7 @@ function stabilize() {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
if (stabilizer === STABILIZER_RESCHEDULE) {
|
|
187
|
-
|
|
187
|
+
microtask(stabilize);
|
|
188
188
|
}
|
|
189
189
|
else {
|
|
190
190
|
stabilizer = STABILIZER_IDLE;
|
|
@@ -202,11 +202,8 @@ function unlink(link) {
|
|
|
202
202
|
if (prevSub !== null) {
|
|
203
203
|
prevSub.nextSub = nextSub;
|
|
204
204
|
}
|
|
205
|
-
else {
|
|
206
|
-
dep
|
|
207
|
-
if (nextSub === null && 'fn' in dep) {
|
|
208
|
-
dispose(dep);
|
|
209
|
-
}
|
|
205
|
+
else if ((dep.subs = nextSub) === null && 'fn' in dep) {
|
|
206
|
+
dispose(dep);
|
|
210
207
|
}
|
|
211
208
|
return nextDep;
|
|
212
209
|
}
|
|
@@ -216,9 +213,9 @@ function update(computed) {
|
|
|
216
213
|
let dep = link.dep;
|
|
217
214
|
if ('fn' in dep) {
|
|
218
215
|
update(dep);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
if (computed.state & STATE_DIRTY) {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
222
219
|
}
|
|
223
220
|
}
|
|
224
221
|
}
|
|
@@ -229,7 +226,7 @@ function update(computed) {
|
|
|
229
226
|
}
|
|
230
227
|
const computed = (fn) => {
|
|
231
228
|
let self = {
|
|
232
|
-
[
|
|
229
|
+
[COMPUTED]: true,
|
|
233
230
|
cleanup: null,
|
|
234
231
|
deps: null,
|
|
235
232
|
depsTail: null,
|
|
@@ -254,9 +251,11 @@ const computed = (fn) => {
|
|
|
254
251
|
schedule();
|
|
255
252
|
}
|
|
256
253
|
link(self, observer);
|
|
254
|
+
onCleanup(() => dispose(self));
|
|
257
255
|
}
|
|
258
256
|
else {
|
|
259
257
|
recompute(self, false);
|
|
258
|
+
root.disposables++;
|
|
260
259
|
}
|
|
261
260
|
return self;
|
|
262
261
|
};
|
|
@@ -276,24 +275,23 @@ const effect = (fn) => {
|
|
|
276
275
|
};
|
|
277
276
|
};
|
|
278
277
|
const isComputed = (value) => {
|
|
279
|
-
return isObject(value) &&
|
|
278
|
+
return isObject(value) && COMPUTED in value;
|
|
280
279
|
};
|
|
281
280
|
const isSignal = (value) => {
|
|
282
|
-
return isObject(value) &&
|
|
281
|
+
return isObject(value) && SIGNAL in value;
|
|
283
282
|
};
|
|
284
283
|
const onCleanup = (fn) => {
|
|
285
284
|
if (!observer) {
|
|
286
285
|
return fn;
|
|
287
286
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
node.cleanup = fn;
|
|
287
|
+
if (!observer.cleanup) {
|
|
288
|
+
observer.cleanup = fn;
|
|
291
289
|
}
|
|
292
|
-
else if (isArray(
|
|
293
|
-
|
|
290
|
+
else if (isArray(observer.cleanup)) {
|
|
291
|
+
observer.cleanup.push(fn);
|
|
294
292
|
}
|
|
295
293
|
else {
|
|
296
|
-
|
|
294
|
+
observer.cleanup = [observer.cleanup, fn];
|
|
297
295
|
}
|
|
298
296
|
return fn;
|
|
299
297
|
};
|
|
@@ -322,21 +320,16 @@ const read = (node) => {
|
|
|
322
320
|
return node.value;
|
|
323
321
|
};
|
|
324
322
|
const root = (fn) => {
|
|
325
|
-
let o = observer;
|
|
323
|
+
let d = root.disposables, o = observer;
|
|
326
324
|
observer = null;
|
|
325
|
+
root.disposables = 0;
|
|
327
326
|
let value = fn();
|
|
328
327
|
observer = o;
|
|
328
|
+
root.disposables = d;
|
|
329
329
|
return value;
|
|
330
330
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
[REACTIVE]: true,
|
|
334
|
-
subs: null,
|
|
335
|
-
subsTail: null,
|
|
336
|
-
value,
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
signal.set = (signal, value) => {
|
|
331
|
+
root.disposables = 0;
|
|
332
|
+
const set = (signal, value) => {
|
|
340
333
|
if (signal.value === value) {
|
|
341
334
|
return;
|
|
342
335
|
}
|
|
@@ -350,4 +343,12 @@ signal.set = (signal, value) => {
|
|
|
350
343
|
}
|
|
351
344
|
schedule();
|
|
352
345
|
};
|
|
353
|
-
|
|
346
|
+
const signal = (value) => {
|
|
347
|
+
return {
|
|
348
|
+
[SIGNAL]: true,
|
|
349
|
+
subs: null,
|
|
350
|
+
subsTail: null,
|
|
351
|
+
value,
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
export { computed, dispose, effect, isComputed, isSignal, onCleanup, read, root, set, signal };
|
package/build/types.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { onCleanup } from './system.js';
|
|
1
|
+
import { COMPUTED, SIGNAL, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants.js';
|
|
3
2
|
import { ReactiveArray } from './reactive/array.js';
|
|
4
3
|
import { ReactiveObject } from './reactive/object.js';
|
|
5
|
-
interface Computed<T>
|
|
6
|
-
[
|
|
4
|
+
interface Computed<T> {
|
|
5
|
+
[COMPUTED]: true;
|
|
7
6
|
cleanup: VoidFunction | VoidFunction[] | null;
|
|
8
7
|
deps: Link | null;
|
|
9
8
|
depsTail: Link | null;
|
|
10
|
-
fn: (
|
|
9
|
+
fn: (onCleanup?: (fn: VoidFunction) => typeof fn) => T;
|
|
11
10
|
height: number;
|
|
12
11
|
nextHeap: Computed<unknown> | undefined;
|
|
13
12
|
prevHeap: Computed<unknown>;
|
|
14
13
|
state: typeof STATE_CHECK | typeof STATE_DIRTY | typeof STATE_IN_HEAP | typeof STATE_NONE | typeof STATE_RECOMPUTING;
|
|
14
|
+
subs: Link | null;
|
|
15
|
+
subsTail: Link | null;
|
|
16
|
+
value: T;
|
|
15
17
|
}
|
|
16
18
|
type Infer<T> = T extends (...args: unknown[]) => Promise<infer R> ? R | undefined : T extends (...args: any[]) => infer R ? R : T extends (infer U)[] ? ReactiveArray<U> : T extends ReactiveObject<any> ? T : T extends Record<PropertyKey, unknown> ? {
|
|
17
19
|
[K in keyof T]: T[K];
|
|
@@ -26,7 +28,7 @@ interface Link {
|
|
|
26
28
|
}
|
|
27
29
|
type Reactive<T> = T extends Record<PropertyKey, unknown> ? ReactiveObject<T> : ReactiveArray<T>;
|
|
28
30
|
type Signal<T> = {
|
|
29
|
-
[
|
|
31
|
+
[SIGNAL]: true;
|
|
30
32
|
subs: Link | null;
|
|
31
33
|
subsTail: Link | null;
|
|
32
34
|
value: T;
|
package/build/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { COMPUTED, SIGNAL } from './constants.js';
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
const
|
|
1
|
+
const COMPUTED = Symbol('computed');
|
|
2
|
+
|
|
3
|
+
const REACTIVE_ARRAY = Symbol('reactive.array');
|
|
4
|
+
|
|
5
|
+
const REACTIVE_OBJECT = Symbol('reactive.object');
|
|
6
|
+
|
|
7
|
+
const SIGNAL = Symbol('signal');
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
const STABILIZER_IDLE = 0;
|
|
@@ -22,7 +28,10 @@ const STATE_IN_HEAP = 1 << 3;
|
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
export {
|
|
25
|
-
|
|
31
|
+
COMPUTED,
|
|
32
|
+
REACTIVE_ARRAY,
|
|
33
|
+
REACTIVE_OBJECT,
|
|
34
|
+
SIGNAL,
|
|
26
35
|
STABILIZER_IDLE,
|
|
27
36
|
STABILIZER_RESCHEDULE,
|
|
28
37
|
STABILIZER_RUNNING,
|
package/src/index.ts
CHANGED
package/src/reactive/array.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isFunction,
|
|
1
|
+
import { isFunction, isNumber, isObject } from '@esportsplus/utilities';
|
|
2
|
+
import { REACTIVE_ARRAY } from '~/constants';
|
|
2
3
|
import { computed, dispose, isComputed, read } from '~/system';
|
|
3
4
|
import { Computed, Infer } from '~/types';
|
|
4
|
-
import object, { ReactiveObject } from './object';
|
|
5
|
-
import { Disposable } from './disposable';
|
|
5
|
+
import object, { isReactiveObject, ReactiveObject } from './object';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type API<T> = Infer<T>[] & ReactiveArray<T>;
|
|
@@ -33,7 +33,7 @@ type Events<T> = {
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
type Item<T> = Computed<T> | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never
|
|
36
|
+
type Item<T> = T | Computed<T> | API<T> | ReactiveObject< T extends Record<PropertyKey, unknown> ? T : never >;
|
|
37
37
|
|
|
38
38
|
type Listener<V> = {
|
|
39
39
|
once?: boolean;
|
|
@@ -48,15 +48,16 @@ type Value<T> =
|
|
|
48
48
|
: T;
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
class ReactiveArray<T>
|
|
51
|
+
class ReactiveArray<T> {
|
|
52
|
+
[REACTIVE_ARRAY] = true;
|
|
53
|
+
disposables: number = 0;
|
|
54
|
+
|
|
52
55
|
private data: Item<T>[];
|
|
53
56
|
private listeners: Record<string, (Listener<any> | null)[]> | null = null;
|
|
54
57
|
private proxy: API<T>;
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
constructor(data: Item<T>[], proxy: API<T>) {
|
|
58
|
-
super();
|
|
59
|
-
|
|
60
61
|
this.data = data;
|
|
61
62
|
this.proxy = proxy;
|
|
62
63
|
}
|
|
@@ -75,6 +76,16 @@ class ReactiveArray<T> extends Disposable {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
|
|
79
|
+
private cleanup(item: Item<T>) {
|
|
80
|
+
if (isReactiveObject(item)) {
|
|
81
|
+
item.dispose();
|
|
82
|
+
}
|
|
83
|
+
else if (isComputed(item)) {
|
|
84
|
+
dispose(item);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
78
89
|
at(i: number) {
|
|
79
90
|
let value = this.data[i];
|
|
80
91
|
|
|
@@ -113,20 +124,9 @@ class ReactiveArray<T> extends Disposable {
|
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
dispose() {
|
|
116
|
-
let
|
|
117
|
-
|
|
118
|
-
for (let i = 0, n = data.length; i < n; i++) {
|
|
119
|
-
let value = data[i];
|
|
120
|
-
|
|
121
|
-
if (isInstanceOf(value, Disposable)) {
|
|
122
|
-
value.dispose();
|
|
123
|
-
}
|
|
124
|
-
else if (isComputed(value)) {
|
|
125
|
-
dispose(value);
|
|
126
|
-
}
|
|
127
|
+
for (let i = 0, n = this.data.length; i < n; i++) {
|
|
128
|
+
this.cleanup(this.data[i]);
|
|
127
129
|
}
|
|
128
|
-
|
|
129
|
-
this.listeners = null;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
map<R>(
|
|
@@ -194,10 +194,7 @@ class ReactiveArray<T> extends Disposable {
|
|
|
194
194
|
let item = this.data.pop();
|
|
195
195
|
|
|
196
196
|
if (item !== undefined) {
|
|
197
|
-
|
|
198
|
-
dispose(item);
|
|
199
|
-
}
|
|
200
|
-
|
|
197
|
+
this.cleanup(item);
|
|
201
198
|
this.dispatch('pop', { item });
|
|
202
199
|
}
|
|
203
200
|
|
|
@@ -224,10 +221,7 @@ class ReactiveArray<T> extends Disposable {
|
|
|
224
221
|
let item = this.data.shift();
|
|
225
222
|
|
|
226
223
|
if (item !== undefined) {
|
|
227
|
-
|
|
228
|
-
dispose(item);
|
|
229
|
-
}
|
|
230
|
-
|
|
224
|
+
this.cleanup(item);
|
|
231
225
|
this.dispatch('shift', { item });
|
|
232
226
|
}
|
|
233
227
|
|
|
@@ -250,11 +244,7 @@ class ReactiveArray<T> extends Disposable {
|
|
|
250
244
|
|
|
251
245
|
if (items.length > 0 || removed.length > 0) {
|
|
252
246
|
for (let i = 0, n = removed.length; i < n; i++) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (isComputed(item)) {
|
|
256
|
-
dispose(item);
|
|
257
|
-
}
|
|
247
|
+
this.cleanup(removed[i]);
|
|
258
248
|
}
|
|
259
249
|
|
|
260
250
|
this.dispatch('splice', {
|
|
@@ -299,6 +289,11 @@ function factory<T>(input: T[]) {
|
|
|
299
289
|
}
|
|
300
290
|
|
|
301
291
|
|
|
292
|
+
const isReactiveArray = (value: any): value is ReactiveArray<any> => {
|
|
293
|
+
return isObject(value) && REACTIVE_ARRAY in value;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
|
|
302
297
|
export default function array<T>(input: T[]) {
|
|
303
298
|
let proxy = new Proxy({}, {
|
|
304
299
|
get(_: any, key: any) {
|
|
@@ -311,27 +306,19 @@ export default function array<T>(input: T[]) {
|
|
|
311
306
|
|
|
312
307
|
return value;
|
|
313
308
|
}
|
|
314
|
-
else if (key in
|
|
315
|
-
return
|
|
309
|
+
else if (key in array) {
|
|
310
|
+
return array[key as keyof typeof array];
|
|
316
311
|
}
|
|
317
312
|
|
|
318
313
|
return wrapped[key];
|
|
319
314
|
},
|
|
320
315
|
set(_: any, key: any, value: any) {
|
|
321
316
|
if (isNumber(key)) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (host === undefined || !isComputed(host)) {
|
|
325
|
-
a.splice(key, 1, value);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
return false;
|
|
329
|
-
}
|
|
330
|
-
|
|
317
|
+
array.splice(key, 1, value);
|
|
331
318
|
return true;
|
|
332
319
|
}
|
|
333
320
|
else if (key === 'length') {
|
|
334
|
-
return
|
|
321
|
+
return array.length = value;
|
|
335
322
|
}
|
|
336
323
|
|
|
337
324
|
return false;
|
|
@@ -339,8 +326,9 @@ export default function array<T>(input: T[]) {
|
|
|
339
326
|
}) as API<T>,
|
|
340
327
|
wrapped = factory(input);
|
|
341
328
|
|
|
342
|
-
let
|
|
329
|
+
let array = new ReactiveArray(wrapped, proxy);
|
|
343
330
|
|
|
344
331
|
return proxy;
|
|
345
332
|
};
|
|
333
|
+
export { isReactiveArray };
|
|
346
334
|
export type { API as ReactiveArray };
|
package/src/reactive/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
2
|
+
import { onCleanup, root } from '~/system';
|
|
2
3
|
import array from './array';
|
|
3
4
|
import object from './object';
|
|
4
5
|
|
|
@@ -11,18 +12,35 @@ type API<T> =
|
|
|
11
12
|
: never;
|
|
12
13
|
|
|
13
14
|
type Input<T> =
|
|
14
|
-
T extends { dispose: any }
|
|
15
|
-
?
|
|
16
|
-
: T
|
|
15
|
+
T extends { dispose: any }
|
|
16
|
+
? never
|
|
17
|
+
: T extends Record<PropertyKey, unknown>
|
|
18
|
+
? T
|
|
19
|
+
: T extends unknown[]
|
|
20
|
+
? Input<T[number]>[]
|
|
21
|
+
: never;
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
export default <T extends Record<PropertyKey, unknown> | unknown[]>(input: Input<T>): API<T> => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
25
|
+
let value: API<T> | undefined;
|
|
26
|
+
|
|
27
|
+
return root(() => {
|
|
28
|
+
if (isArray(input)) {
|
|
29
|
+
value = array(input) as API<T>;
|
|
30
|
+
}
|
|
31
|
+
else if (isObject(input)) {
|
|
32
|
+
value = object(input) as API<T>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (value) {
|
|
36
|
+
if (root.disposables) {
|
|
37
|
+
onCleanup(() => value!.dispose());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error(`@esportsplus/reactivity: 'reactive' received invalid input - ${JSON.stringify(input)}`);
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
export type { Input };
|
package/src/reactive/object.ts
CHANGED
|
@@ -1,45 +1,28 @@
|
|
|
1
|
-
import { defineProperty, isArray, isFunction,
|
|
2
|
-
import
|
|
3
|
-
import { computed, dispose, effect, read, root, signal } from '~/system';
|
|
1
|
+
import { defineProperty, isArray, isFunction, isObject, isPromise, Prettify } from '@esportsplus/utilities';
|
|
2
|
+
import { computed, dispose, effect, isComputed, read, root, set, signal } from '~/system';
|
|
4
3
|
import { Computed, Infer, Signal } from '~/types';
|
|
5
|
-
import {
|
|
4
|
+
import { REACTIVE_OBJECT } from '~/constants';
|
|
5
|
+
import array, { isReactiveArray } from './array';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
type API<T extends Record<PropertyKey, unknown>> = Prettify<{ [K in keyof T]: Infer<T[K]> }> & ReactiveObject<T>;
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable {
|
|
15
|
-
private disposable: Record<
|
|
16
|
-
PropertyKey,
|
|
17
|
-
Computed<any> | ReactiveArray<any> | ReactiveObject<any>
|
|
18
|
-
> = {};
|
|
11
|
+
class ReactiveObject<T extends Record<PropertyKey, unknown>> {
|
|
12
|
+
[REACTIVE_OBJECT] = true;
|
|
19
13
|
|
|
20
14
|
|
|
21
15
|
constructor(data: T) {
|
|
22
|
-
super();
|
|
23
|
-
|
|
24
|
-
let disposable = this.disposable,
|
|
25
|
-
triggers: Record<string, Signal<boolean>> = {};
|
|
26
|
-
|
|
27
16
|
for (let key in data) {
|
|
28
17
|
let value = data[key];
|
|
29
18
|
|
|
30
19
|
if (isArray(value)) {
|
|
31
|
-
let a =
|
|
32
|
-
t = triggers[key] = signal(false);
|
|
20
|
+
let a = array(value);
|
|
33
21
|
|
|
34
22
|
defineProperty(this, key, {
|
|
35
23
|
enumerable: true,
|
|
36
24
|
get() {
|
|
37
|
-
read(t);
|
|
38
25
|
return a;
|
|
39
|
-
},
|
|
40
|
-
set(v: typeof value) {
|
|
41
|
-
a = disposable[key] = array(v);
|
|
42
|
-
set(t, !!t.value);
|
|
43
26
|
}
|
|
44
27
|
});
|
|
45
28
|
}
|
|
@@ -51,7 +34,7 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable
|
|
|
51
34
|
get() {
|
|
52
35
|
if (c === undefined) {
|
|
53
36
|
root(() => {
|
|
54
|
-
c =
|
|
37
|
+
c = computed(value as Computed<T[typeof key]>['fn']);
|
|
55
38
|
|
|
56
39
|
if (isPromise(c.value)) {
|
|
57
40
|
let factory = c,
|
|
@@ -67,7 +50,7 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable
|
|
|
67
50
|
return;
|
|
68
51
|
}
|
|
69
52
|
|
|
70
|
-
set(c
|
|
53
|
+
set(c as Signal<typeof value>, value);
|
|
71
54
|
});
|
|
72
55
|
});
|
|
73
56
|
}
|
|
@@ -96,23 +79,29 @@ class ReactiveObject<T extends Record<PropertyKey, unknown>> extends Disposable
|
|
|
96
79
|
|
|
97
80
|
|
|
98
81
|
dispose() {
|
|
99
|
-
|
|
100
|
-
let value = this.disposable[key];
|
|
82
|
+
let value;
|
|
101
83
|
|
|
102
|
-
|
|
84
|
+
for (let key in this) {
|
|
85
|
+
value = this[key];
|
|
86
|
+
|
|
87
|
+
if (isReactiveArray(value) || isReactiveObject(value)) {
|
|
103
88
|
value.dispose();
|
|
104
89
|
}
|
|
105
|
-
else {
|
|
90
|
+
else if (isComputed(value)) {
|
|
106
91
|
dispose(value);
|
|
107
92
|
}
|
|
108
93
|
}
|
|
109
|
-
|
|
110
|
-
this.disposable = {};
|
|
111
94
|
}
|
|
112
95
|
}
|
|
113
96
|
|
|
114
97
|
|
|
98
|
+
const isReactiveObject = (value: any): value is ReactiveObject<any> => {
|
|
99
|
+
return isObject(value) && REACTIVE_OBJECT in value;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
|
|
115
103
|
export default function object<T extends Record<PropertyKey, unknown>>(input: T) {
|
|
116
|
-
return new ReactiveObject(input)
|
|
104
|
+
return new ReactiveObject<T>(input);
|
|
117
105
|
};
|
|
106
|
+
export { isReactiveObject };
|
|
118
107
|
export type { API as ReactiveObject };
|
package/src/system.ts
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
import { isArray, isObject } from '@esportsplus/utilities';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
COMPUTED, SIGNAL,
|
|
4
4
|
STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED,
|
|
5
5
|
STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING
|
|
6
6
|
} from './constants';
|
|
7
|
-
import { Computed, Link, Signal
|
|
7
|
+
import { Computed, Link, Signal } from './types';
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
let depth = 0,
|
|
11
|
-
heap: (Computed<unknown> | undefined)[] = new Array(
|
|
11
|
+
heap: (Computed<unknown> | undefined)[] = new Array(2048),
|
|
12
12
|
index = 0,
|
|
13
13
|
length = 0,
|
|
14
|
+
microtask = queueMicrotask,
|
|
14
15
|
notified = false,
|
|
15
16
|
observer: Computed<unknown> | null = null,
|
|
16
|
-
scheduler = queueMicrotask,
|
|
17
17
|
stabilizer = STABILIZER_IDLE,
|
|
18
18
|
version = 0;
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
function cleanup<T>(
|
|
22
|
-
if (!
|
|
21
|
+
function cleanup<T>(computed: Computed<T>): void {
|
|
22
|
+
if (!computed.cleanup) {
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
let
|
|
26
|
+
let value = computed.cleanup;
|
|
27
27
|
|
|
28
|
-
if (isArray(
|
|
29
|
-
for (let i = 0; i <
|
|
30
|
-
|
|
28
|
+
if (isArray(value)) {
|
|
29
|
+
for (let i = 0, n = value.length; i < n; i++) {
|
|
30
|
+
value[i]();
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
|
-
|
|
34
|
+
value();
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
computed.cleanup = null;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function deleteFromHeap<T>(computed: Computed<T>) {
|
|
@@ -98,7 +98,7 @@ function insertIntoHeap<T>(computed: Computed<T>) {
|
|
|
98
98
|
|
|
99
99
|
// Simple auto adjust to avoid manual management within apps.
|
|
100
100
|
if (height >= heap.length) {
|
|
101
|
-
heap.length
|
|
101
|
+
heap.length = Math.round(heap.length * 1.5);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
@@ -208,13 +208,13 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
|
|
|
208
208
|
computed.state = STATE_NONE;
|
|
209
209
|
|
|
210
210
|
let depsTail = computed.depsTail as Link | null,
|
|
211
|
-
|
|
211
|
+
remove = depsTail !== null ? depsTail.nextDep : computed.deps;
|
|
212
212
|
|
|
213
|
-
if (
|
|
213
|
+
if (remove !== null) {
|
|
214
214
|
do {
|
|
215
|
-
|
|
215
|
+
remove = unlink(remove);
|
|
216
216
|
}
|
|
217
|
-
while (
|
|
217
|
+
while (remove !== null);
|
|
218
218
|
|
|
219
219
|
if (depsTail !== null) {
|
|
220
220
|
depsTail.nextDep = null;
|
|
@@ -228,14 +228,14 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
|
|
|
228
228
|
computed.value = value as T;
|
|
229
229
|
|
|
230
230
|
for (let c = computed.subs; c !== null; c = c.nextSub) {
|
|
231
|
-
let
|
|
232
|
-
state =
|
|
231
|
+
let s = c.sub,
|
|
232
|
+
state = s.state;
|
|
233
233
|
|
|
234
234
|
if (state & STATE_CHECK) {
|
|
235
|
-
|
|
235
|
+
s.state = state | STATE_DIRTY;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
insertIntoHeap(
|
|
238
|
+
insertIntoHeap(s);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
schedule();
|
|
@@ -245,7 +245,7 @@ function recompute<T>(computed: Computed<T>, del: boolean) {
|
|
|
245
245
|
function schedule() {
|
|
246
246
|
if (stabilizer === STABILIZER_IDLE && !depth) {
|
|
247
247
|
stabilizer = STABILIZER_SCHEDULED;
|
|
248
|
-
|
|
248
|
+
microtask(stabilize);
|
|
249
249
|
}
|
|
250
250
|
else if (stabilizer === STABILIZER_RUNNING) {
|
|
251
251
|
stabilizer = STABILIZER_RESCHEDULE;
|
|
@@ -271,7 +271,7 @@ function stabilize() {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
if (stabilizer === STABILIZER_RESCHEDULE) {
|
|
274
|
-
|
|
274
|
+
microtask(stabilize);
|
|
275
275
|
}
|
|
276
276
|
else {
|
|
277
277
|
stabilizer = STABILIZER_IDLE;
|
|
@@ -293,12 +293,8 @@ function unlink(link: Link): Link | null {
|
|
|
293
293
|
if (prevSub !== null) {
|
|
294
294
|
prevSub.nextSub = nextSub;
|
|
295
295
|
}
|
|
296
|
-
|
|
297
|
-
dep
|
|
298
|
-
|
|
299
|
-
if (nextSub === null && 'fn' in dep) {
|
|
300
|
-
dispose(dep);
|
|
301
|
-
}
|
|
296
|
+
else if ((dep.subs = nextSub) === null && 'fn' in dep) {
|
|
297
|
+
dispose(dep);
|
|
302
298
|
}
|
|
303
299
|
|
|
304
300
|
return nextDep;
|
|
@@ -311,10 +307,10 @@ function update<T>(computed: Computed<T>): void {
|
|
|
311
307
|
|
|
312
308
|
if ('fn' in dep) {
|
|
313
309
|
update(dep);
|
|
314
|
-
}
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
|
|
311
|
+
if (computed.state & STATE_DIRTY) {
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
318
314
|
}
|
|
319
315
|
}
|
|
320
316
|
}
|
|
@@ -329,7 +325,7 @@ function update<T>(computed: Computed<T>): void {
|
|
|
329
325
|
|
|
330
326
|
const computed = <T>(fn: Computed<T>['fn']): Computed<T> => {
|
|
331
327
|
let self: Computed<T> = {
|
|
332
|
-
[
|
|
328
|
+
[COMPUTED]: true,
|
|
333
329
|
cleanup: null,
|
|
334
330
|
deps: null,
|
|
335
331
|
depsTail: null,
|
|
@@ -357,9 +353,11 @@ const computed = <T>(fn: Computed<T>['fn']): Computed<T> => {
|
|
|
357
353
|
}
|
|
358
354
|
|
|
359
355
|
link(self, observer);
|
|
356
|
+
onCleanup(() => dispose(self));
|
|
360
357
|
}
|
|
361
358
|
else {
|
|
362
359
|
recompute(self, false);
|
|
360
|
+
root.disposables++;
|
|
363
361
|
}
|
|
364
362
|
|
|
365
363
|
return self;
|
|
@@ -388,11 +386,11 @@ const effect = <T>(fn: Computed<T>['fn']) => {
|
|
|
388
386
|
};
|
|
389
387
|
|
|
390
388
|
const isComputed = (value: unknown): value is Computed<unknown> => {
|
|
391
|
-
return isObject(value) &&
|
|
389
|
+
return isObject(value) && COMPUTED in value;
|
|
392
390
|
};
|
|
393
391
|
|
|
394
392
|
const isSignal = (value: unknown): value is Signal<unknown> => {
|
|
395
|
-
return isObject(value) &&
|
|
393
|
+
return isObject(value) && SIGNAL in value;
|
|
396
394
|
};
|
|
397
395
|
|
|
398
396
|
const onCleanup = (fn: VoidFunction): typeof fn => {
|
|
@@ -400,16 +398,14 @@ const onCleanup = (fn: VoidFunction): typeof fn => {
|
|
|
400
398
|
return fn;
|
|
401
399
|
}
|
|
402
400
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (!node.cleanup) {
|
|
406
|
-
node.cleanup = fn;
|
|
401
|
+
if (!observer.cleanup) {
|
|
402
|
+
observer.cleanup = fn;
|
|
407
403
|
}
|
|
408
|
-
else if (isArray(
|
|
409
|
-
|
|
404
|
+
else if (isArray(observer.cleanup)) {
|
|
405
|
+
observer.cleanup.push(fn);
|
|
410
406
|
}
|
|
411
407
|
else {
|
|
412
|
-
|
|
408
|
+
observer.cleanup = [observer.cleanup, fn];
|
|
413
409
|
}
|
|
414
410
|
|
|
415
411
|
return fn;
|
|
@@ -449,27 +445,23 @@ const read = <T>(node: Signal<T> | Computed<T>): T => {
|
|
|
449
445
|
};
|
|
450
446
|
|
|
451
447
|
const root = <T>(fn: () => T) => {
|
|
452
|
-
let
|
|
448
|
+
let d = root.disposables,
|
|
449
|
+
o = observer;
|
|
453
450
|
|
|
454
451
|
observer = null;
|
|
452
|
+
root.disposables = 0;
|
|
455
453
|
|
|
456
454
|
let value = fn();
|
|
457
455
|
|
|
458
456
|
observer = o;
|
|
457
|
+
root.disposables = d;
|
|
459
458
|
|
|
460
459
|
return value;
|
|
461
460
|
};
|
|
462
461
|
|
|
463
|
-
|
|
464
|
-
return {
|
|
465
|
-
[REACTIVE]: true,
|
|
466
|
-
subs: null,
|
|
467
|
-
subsTail: null,
|
|
468
|
-
value,
|
|
469
|
-
};
|
|
470
|
-
};
|
|
462
|
+
root.disposables = 0;
|
|
471
463
|
|
|
472
|
-
|
|
464
|
+
const set = <T>(signal: Signal<T>, value: T) => {
|
|
473
465
|
if (signal.value === value) {
|
|
474
466
|
return;
|
|
475
467
|
}
|
|
@@ -488,6 +480,15 @@ signal.set = <T>(signal: Signal<T>, value: T) => {
|
|
|
488
480
|
schedule();
|
|
489
481
|
};
|
|
490
482
|
|
|
483
|
+
const signal = <T>(value: T): Signal<T> => {
|
|
484
|
+
return {
|
|
485
|
+
[SIGNAL]: true,
|
|
486
|
+
subs: null,
|
|
487
|
+
subsTail: null,
|
|
488
|
+
value,
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
|
|
491
492
|
|
|
492
493
|
export {
|
|
493
494
|
computed,
|
|
@@ -496,5 +497,6 @@ export {
|
|
|
496
497
|
isComputed, isSignal,
|
|
497
498
|
onCleanup,
|
|
498
499
|
read, root,
|
|
499
|
-
signal
|
|
500
|
-
};
|
|
500
|
+
set, signal
|
|
501
|
+
};
|
|
502
|
+
export type { Computed, Signal };
|
package/src/types.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { onCleanup } from './system';
|
|
1
|
+
import { COMPUTED, SIGNAL, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING } from './constants';
|
|
3
2
|
import { ReactiveArray } from './reactive/array';
|
|
4
3
|
import { ReactiveObject } from './reactive/object';
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
interface Computed<T>
|
|
8
|
-
[
|
|
6
|
+
interface Computed<T> {
|
|
7
|
+
[COMPUTED]: true;
|
|
9
8
|
cleanup: VoidFunction | VoidFunction[] | null;
|
|
10
9
|
deps: Link | null;
|
|
11
10
|
depsTail: Link | null;
|
|
12
|
-
fn: (
|
|
11
|
+
fn: (onCleanup?: (fn: VoidFunction) => typeof fn) => T;
|
|
13
12
|
height: number;
|
|
14
13
|
nextHeap: Computed<unknown> | undefined;
|
|
15
14
|
prevHeap: Computed<unknown>;
|
|
@@ -19,6 +18,9 @@ interface Computed<T> extends Signal<T> {
|
|
|
19
18
|
typeof STATE_IN_HEAP |
|
|
20
19
|
typeof STATE_NONE |
|
|
21
20
|
typeof STATE_RECOMPUTING;
|
|
21
|
+
subs: Link | null;
|
|
22
|
+
subsTail: Link | null;
|
|
23
|
+
value: T;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
type Infer<T> =
|
|
@@ -48,7 +50,7 @@ type Reactive<T> = T extends Record<PropertyKey, unknown>
|
|
|
48
50
|
: ReactiveArray<T>;
|
|
49
51
|
|
|
50
52
|
type Signal<T> = {
|
|
51
|
-
[
|
|
53
|
+
[SIGNAL]: true;
|
|
52
54
|
subs: Link | null;
|
|
53
55
|
subsTail: Link | null;
|
|
54
56
|
value: T;
|