@oscarpalmer/mora 0.22.0 → 0.23.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/dist/batch.js +8 -9
- package/dist/constants.js +34 -0
- package/dist/effect.js +7 -8
- package/dist/helpers/is.js +8 -18
- package/dist/helpers/proxy.js +3 -2
- package/dist/helpers/value.js +7 -8
- package/dist/index.js +1 -1
- package/dist/models.js +0 -0
- package/dist/mora.full.js +218 -130
- package/dist/subscription.js +7 -5
- package/dist/value/array.js +24 -24
- package/dist/value/computed.js +18 -21
- package/dist/value/signal.js +2 -2
- package/dist/value/store.js +8 -2
- package/package.json +9 -10
- package/src/batch.ts +11 -13
- package/src/constants.ts +49 -0
- package/src/effect.ts +11 -16
- package/src/helpers/is.ts +41 -19
- package/src/helpers/proxy.ts +34 -38
- package/src/helpers/value.ts +19 -14
- package/src/index.ts +1 -0
- package/src/models.ts +66 -0
- package/src/subscription.ts +14 -16
- package/src/value/array.ts +86 -44
- package/src/value/computed.ts +29 -39
- package/src/value/reactive.ts +9 -24
- package/src/value/signal.ts +9 -3
- package/src/value/store.ts +40 -9
- package/types/batch.d.ts +3 -5
- package/types/constants.d.ts +14 -0
- package/types/effect.d.ts +2 -1
- package/types/helpers/is.d.ts +23 -10
- package/types/helpers/proxy.d.ts +2 -3
- package/types/helpers/value.d.ts +1 -1
- package/types/index.d.ts +1 -0
- package/types/models.d.ts +56 -0
- package/types/subscription.d.ts +3 -6
- package/types/value/array.d.ts +48 -7
- package/types/value/computed.d.ts +2 -11
- package/types/value/reactive.d.ts +8 -17
- package/types/value/signal.d.ts +7 -1
- package/types/value/store.d.ts +28 -4
package/src/models.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import type {Effect} from './effect';
|
|
3
|
+
import type {Subscription} from './subscription';
|
|
4
|
+
import type {Computed} from './value/computed';
|
|
5
|
+
import type {Signal} from './value/signal';
|
|
6
|
+
|
|
7
|
+
export type Active = {
|
|
8
|
+
computed?: Computed<unknown>;
|
|
9
|
+
effect?: Effect;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type Batch = {
|
|
13
|
+
depth: number;
|
|
14
|
+
handlers: Set<Effect | Subscription>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ComputedEffect = {
|
|
18
|
+
dirty: boolean;
|
|
19
|
+
instance: Effect;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type EffectState = {
|
|
23
|
+
callback: GenericCallback;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type InternalComputed = {
|
|
27
|
+
readonly effect: ComputedEffect;
|
|
28
|
+
readonly state: ReactiveState<unknown, unknown>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type InternalEffect = {
|
|
32
|
+
state: EffectState;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ReactiveOptions<Value> = {
|
|
36
|
+
/**
|
|
37
|
+
* Method for comparing values for equality
|
|
38
|
+
* @param first First value
|
|
39
|
+
* @param second Second value
|
|
40
|
+
* @returns Are the values equal?
|
|
41
|
+
* @default Object.is
|
|
42
|
+
*/
|
|
43
|
+
equal?: (first: Value, second: Value) => boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ReactiveState<Value, Equal> = {
|
|
47
|
+
computeds: Set<Computed<unknown>>;
|
|
48
|
+
effects: Set<Effect>;
|
|
49
|
+
equal: (first: Equal, second: Equal) => boolean;
|
|
50
|
+
subscriptions: Map<GenericCallback, Subscription>;
|
|
51
|
+
value: Value;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type SetValueInProxyParameters<Value, Equal> = {
|
|
55
|
+
target: Value;
|
|
56
|
+
property: PropertyKey;
|
|
57
|
+
value: unknown;
|
|
58
|
+
state: ReactiveState<Value, Equal>;
|
|
59
|
+
isArray: boolean;
|
|
60
|
+
length?: Signal<number>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Unsubscribe from changes
|
|
65
|
+
*/
|
|
66
|
+
export type Unsubscribe = () => void;
|
package/src/subscription.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import type {ReactiveState} from './
|
|
2
|
+
import type {ReactiveState, Unsubscribe} from './models';
|
|
3
3
|
|
|
4
4
|
export class Subscription {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
) {
|
|
5
|
+
callback: GenericCallback;
|
|
6
|
+
state: ReactiveState<unknown, never>;
|
|
7
|
+
|
|
8
|
+
constructor(state: ReactiveState<unknown, never>, callback: GenericCallback) {
|
|
9
|
+
this.state = state;
|
|
10
|
+
this.callback = callback;
|
|
11
|
+
|
|
9
12
|
callback(state.value);
|
|
10
13
|
}
|
|
11
|
-
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
destroy(): void {
|
|
16
|
+
this.callback = noop;
|
|
17
|
+
this.state = undefined as never;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
17
20
|
|
|
18
21
|
export function noop(): void {}
|
|
19
22
|
|
|
@@ -36,12 +39,7 @@ export function unsubscribe<Value>(
|
|
|
36
39
|
state: ReactiveState<Value, never>,
|
|
37
40
|
callback: (value: Value) => void,
|
|
38
41
|
): void {
|
|
39
|
-
|
|
42
|
+
state.subscriptions.get(callback)?.destroy();
|
|
40
43
|
|
|
41
44
|
state.subscriptions.delete(callback);
|
|
42
|
-
|
|
43
|
-
if (subscription != null) {
|
|
44
|
-
subscription.callback = noop;
|
|
45
|
-
subscription.state = undefined as never;
|
|
46
|
-
}
|
|
47
45
|
}
|
package/src/value/array.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
METHODS_AFFECTING_LENGTH,
|
|
4
|
+
METHODS_UPDATE,
|
|
5
|
+
NAME_ARRAY,
|
|
6
|
+
} from '../constants';
|
|
3
7
|
import {getReactiveValueInProxy, setValueInProxy} from '../helpers/proxy';
|
|
4
8
|
import {emitValue, equalArrays, getValue} from '../helpers/value';
|
|
5
|
-
import
|
|
9
|
+
import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
|
|
10
|
+
import {noop, subscribe} from '../subscription';
|
|
6
11
|
import {type Computed, computed} from './computed';
|
|
7
|
-
import {Reactive
|
|
12
|
+
import {Reactive} from './reactive';
|
|
8
13
|
import {type Signal, signal} from './signal';
|
|
9
14
|
|
|
10
15
|
export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
11
|
-
#indiced = new Map<number, Computed<unknown>>();
|
|
12
|
-
|
|
16
|
+
readonly #indiced = new Map<number, Computed<unknown>>();
|
|
17
|
+
|
|
18
|
+
readonly #size = signal(0);
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* The length of the array
|
|
@@ -33,21 +39,21 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
33
39
|
|
|
34
40
|
constructor(value: Item[], options?: ReactiveOptions<Item>) {
|
|
35
41
|
super(
|
|
36
|
-
|
|
42
|
+
NAME_ARRAY,
|
|
37
43
|
new Proxy(value, {
|
|
38
|
-
get: (target, property) =>
|
|
39
|
-
|
|
44
|
+
get: (target: Item[], property: PropertyKey) =>
|
|
45
|
+
METHODS_UPDATE.has(property as string)
|
|
40
46
|
? updateArray(property as string, target, this.state, this.#size)
|
|
41
47
|
: Reflect.get(target, property),
|
|
42
|
-
set: (target, property, value) =>
|
|
43
|
-
setValueInProxy(
|
|
44
|
-
target,
|
|
48
|
+
set: (target: Item[], property: PropertyKey, value: Item) =>
|
|
49
|
+
setValueInProxy({
|
|
45
50
|
property,
|
|
51
|
+
target,
|
|
46
52
|
value,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.#size,
|
|
50
|
-
),
|
|
53
|
+
isArray: true,
|
|
54
|
+
state: this.state,
|
|
55
|
+
length: this.#size,
|
|
56
|
+
}),
|
|
51
57
|
}),
|
|
52
58
|
options,
|
|
53
59
|
);
|
|
@@ -62,6 +68,17 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
62
68
|
this.length = 0;
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Create a computed, filtered array
|
|
73
|
+
* @param callback Callback to evaluate each item
|
|
74
|
+
* @return Computed array of filtered items
|
|
75
|
+
*/
|
|
76
|
+
filter(
|
|
77
|
+
callback: (item: Item, index: number, array: Item[]) => boolean,
|
|
78
|
+
): Computed<Item[]> {
|
|
79
|
+
return computed(() => this.get().filter(callback));
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
/**
|
|
66
83
|
* @inheritdoc
|
|
67
84
|
*/
|
|
@@ -69,11 +86,14 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
69
86
|
|
|
70
87
|
/**
|
|
71
88
|
* Get the value at an index
|
|
89
|
+
* @param index Index of item to get _(if negative, starts from the end)_
|
|
90
|
+
* @returns Item at index, or `undefined` if it doesn't exist
|
|
72
91
|
*/
|
|
73
92
|
get(index: number): Item | undefined;
|
|
74
93
|
|
|
75
94
|
/**
|
|
76
95
|
* Get the length of the array
|
|
96
|
+
* @returns Length of the array
|
|
77
97
|
*/
|
|
78
98
|
get(property: 'length'): number;
|
|
79
99
|
|
|
@@ -89,17 +109,10 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
89
109
|
return getValue(this.state);
|
|
90
110
|
}
|
|
91
111
|
|
|
92
|
-
/**
|
|
93
|
-
* Create a computed, filtered array
|
|
94
|
-
*/
|
|
95
|
-
filter(
|
|
96
|
-
callback: (item: Item, index: number, array: Item[]) => boolean,
|
|
97
|
-
): Computed<Item[]> {
|
|
98
|
-
return computed(() => this.get().filter(callback));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
112
|
/**
|
|
102
113
|
* Create a computed, mapped array
|
|
114
|
+
* @param callback Callback to transform each item
|
|
115
|
+
* @return Computed array of mapped items
|
|
103
116
|
*/
|
|
104
117
|
map<Mapped>(
|
|
105
118
|
callback: (item: Item, index: number, array: Item[]) => Mapped,
|
|
@@ -107,20 +120,36 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
107
120
|
return computed(() => this.get().map(callback));
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Notify dependents of changes
|
|
125
|
+
*
|
|
126
|
+
* _This bypasses equality checks and will immediately notify dependents.
|
|
127
|
+
* Use this only if you're modifying nested data that would be ignored by equality checks._
|
|
128
|
+
*/
|
|
129
|
+
notify(): void {
|
|
130
|
+
emitValue(this.state);
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
/**
|
|
111
134
|
* @inheritdoc
|
|
112
135
|
*/
|
|
113
136
|
peek(): Item[];
|
|
114
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Get the value at an index _(without reactivity)_
|
|
140
|
+
* @param index Index of item to get _(if negative, starts from the end)_
|
|
141
|
+
* @returns Item at index, or `undefined` if it doesn't exist
|
|
142
|
+
*/
|
|
115
143
|
peek(index: number): Item | undefined;
|
|
116
144
|
|
|
117
145
|
/**
|
|
118
146
|
* Get the length of the array _(without reactivity)_
|
|
147
|
+
* @returns Length of the array
|
|
119
148
|
*/
|
|
120
|
-
peek(
|
|
149
|
+
peek(property: 'length'): number;
|
|
121
150
|
|
|
122
151
|
peek(value?: unknown): unknown {
|
|
123
|
-
if (value ===
|
|
152
|
+
if (value === 'length') {
|
|
124
153
|
return this.#size.peek();
|
|
125
154
|
}
|
|
126
155
|
|
|
@@ -133,6 +162,7 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
133
162
|
|
|
134
163
|
/**
|
|
135
164
|
* Remove and return the last item of the array
|
|
165
|
+
* @returns Removed item, or `undefined` if the array is empty
|
|
136
166
|
*/
|
|
137
167
|
pop(): Item | undefined {
|
|
138
168
|
return this.state.value.pop();
|
|
@@ -140,6 +170,8 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
140
170
|
|
|
141
171
|
/**
|
|
142
172
|
* Add items to the end of the array
|
|
173
|
+
* @param items Items to add
|
|
174
|
+
* @returns New array length
|
|
143
175
|
*/
|
|
144
176
|
push(...items: Item[]): number {
|
|
145
177
|
return this.state.value.push(...items);
|
|
@@ -147,16 +179,20 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
147
179
|
|
|
148
180
|
/**
|
|
149
181
|
* Set the value
|
|
182
|
+
* @param value New array of items _(defaults to an empty array)_
|
|
150
183
|
*/
|
|
151
184
|
set(value?: Item[]): void;
|
|
152
185
|
|
|
153
186
|
/**
|
|
154
187
|
* Set the value at an index
|
|
188
|
+
* @param index Index of item to set __(if negative, starts from the end)_
|
|
189
|
+
* @param value New item
|
|
155
190
|
*/
|
|
156
191
|
set(index: number, value: Item): void;
|
|
157
192
|
|
|
158
193
|
/**
|
|
159
194
|
* Set the length of the array
|
|
195
|
+
* @param value New array length
|
|
160
196
|
*/
|
|
161
197
|
set(property: 'length', value: number): void;
|
|
162
198
|
|
|
@@ -165,13 +201,14 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
165
201
|
this.state.value.splice(0, this.state.value.length, ...(first ?? []));
|
|
166
202
|
} else if (first === 'length') {
|
|
167
203
|
this.length = second as number;
|
|
168
|
-
} else if (typeof first === 'number') {
|
|
169
|
-
this.state.value
|
|
204
|
+
} else if (typeof first === 'number' && !Number.isNaN(first)) {
|
|
205
|
+
setAtIndex(this.state.value, first, second as Item);
|
|
170
206
|
}
|
|
171
207
|
}
|
|
172
208
|
|
|
173
209
|
/**
|
|
174
210
|
* Remove and return the first item of the array
|
|
211
|
+
* @returns Removed item, or `undefined` if the array is empty
|
|
175
212
|
*/
|
|
176
213
|
shift(): Item | undefined {
|
|
177
214
|
return this.state.value.shift();
|
|
@@ -179,6 +216,10 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
179
216
|
|
|
180
217
|
/**
|
|
181
218
|
* Remove and return items from the array _(and optionally add new items)_
|
|
219
|
+
* @param from Index to start removing items from
|
|
220
|
+
* @param to Index to stop removing items at _(defaults to the end of the array)_
|
|
221
|
+
* @param items Optional items to add
|
|
222
|
+
* @returns Removed items
|
|
182
223
|
*/
|
|
183
224
|
splice(from: number, to?: number, ...items: Item[]): Item[] {
|
|
184
225
|
return this.state.value.splice(
|
|
@@ -195,6 +236,9 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
195
236
|
|
|
196
237
|
/**
|
|
197
238
|
* Subscribe to changes at a specific index
|
|
239
|
+
* @param index Index of item to subscribe to
|
|
240
|
+
* @param callback Callback for changes
|
|
241
|
+
* @returns Unsubscribe callback
|
|
198
242
|
*/
|
|
199
243
|
subscribe(
|
|
200
244
|
index: number,
|
|
@@ -219,6 +263,8 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
219
263
|
|
|
220
264
|
/**
|
|
221
265
|
* Add items to the beginning of the array
|
|
266
|
+
* @param items Items to add
|
|
267
|
+
* @returns New array length
|
|
222
268
|
*/
|
|
223
269
|
unshift(...items: Item[]): number {
|
|
224
270
|
return this.state.value.unshift(...items);
|
|
@@ -226,6 +272,7 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
226
272
|
|
|
227
273
|
/**
|
|
228
274
|
* Update the value _(based on the current value)_
|
|
275
|
+
* @param callback Callback to update the value
|
|
229
276
|
*/
|
|
230
277
|
update(callback: (value: Item[]) => Item[]): void {
|
|
231
278
|
const updated = callback(this.state.value);
|
|
@@ -238,6 +285,9 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
|
|
|
238
285
|
|
|
239
286
|
/**
|
|
240
287
|
* Create a reactive array
|
|
288
|
+
* @param value Initial array of items
|
|
289
|
+
* @param options Optional reactivity options
|
|
290
|
+
* @returns Reactive array
|
|
241
291
|
*/
|
|
242
292
|
export function array<Item>(
|
|
243
293
|
value: Item[],
|
|
@@ -252,7 +302,7 @@ function updateArray<Item>(
|
|
|
252
302
|
state: ReactiveState<Item[], Item>,
|
|
253
303
|
length: Signal<number>,
|
|
254
304
|
): unknown {
|
|
255
|
-
const affectsLength =
|
|
305
|
+
const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
|
|
256
306
|
const previousArray = affectsLength ? [] : [...array];
|
|
257
307
|
const previousLength = array.length;
|
|
258
308
|
|
|
@@ -275,18 +325,10 @@ function updateArray<Item>(
|
|
|
275
325
|
};
|
|
276
326
|
}
|
|
277
327
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const updateMethods = new Set<string>([
|
|
286
|
-
...lengthAffectingMethods,
|
|
287
|
-
'copyWithin',
|
|
288
|
-
'fill',
|
|
289
|
-
'reverse',
|
|
290
|
-
'sort',
|
|
291
|
-
'splice',
|
|
292
|
-
]);
|
|
328
|
+
function setAtIndex<Item>(array: Item[], index: number, value: Item): void {
|
|
329
|
+
const actual = index < 0 ? array.length + index : index;
|
|
330
|
+
|
|
331
|
+
if (actual > -1) {
|
|
332
|
+
array[actual] = value;
|
|
333
|
+
}
|
|
334
|
+
}
|
package/src/value/computed.ts
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {Reactive
|
|
5
|
-
|
|
6
|
-
export type ComputedEffect = {
|
|
7
|
-
dirty: boolean;
|
|
8
|
-
instance: Effect;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type InternalComputed = {
|
|
12
|
-
readonly effect: ComputedEffect;
|
|
13
|
-
readonly state: ReactiveState<unknown, unknown>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export let activeComputed: Computed<unknown> | undefined;
|
|
1
|
+
import {ACTIVE, BATCH, NAME_COMPUTED} from '../constants';
|
|
2
|
+
import {effect, runEffect} from '../effect';
|
|
3
|
+
import type {ComputedEffect, ReactiveOptions} from '../models';
|
|
4
|
+
import {Reactive} from './reactive';
|
|
17
5
|
|
|
18
6
|
export class Computed<Value> extends Reactive<Value> {
|
|
19
7
|
private readonly effect: ComputedEffect = {
|
|
@@ -22,36 +10,38 @@ export class Computed<Value> extends Reactive<Value> {
|
|
|
22
10
|
};
|
|
23
11
|
|
|
24
12
|
constructor(callback: () => Value, options?: ReactiveOptions<Value>) {
|
|
25
|
-
super(
|
|
13
|
+
super(NAME_COMPUTED, undefined as never, options);
|
|
26
14
|
|
|
27
15
|
this.effect.instance = effect(() => {
|
|
28
|
-
if (this.effect.dirty) {
|
|
29
|
-
|
|
16
|
+
if (!this.effect.dirty) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
const previousComputed = ACTIVE.computed;
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
ACTIVE.computed = this as never;
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
const value = callback();
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
this.state.value = value;
|
|
26
|
+
ACTIVE.computed = previousComputed;
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
28
|
+
if (!this.state.equal(this.state.value, value)) {
|
|
29
|
+
this.state.value = value;
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
for (const computed of this.state.computeds) {
|
|
32
|
+
computed.effect.dirty = true;
|
|
33
|
+
}
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
35
|
+
for (const effect of this.state.effects) {
|
|
36
|
+
BATCH.handlers.add(effect);
|
|
51
37
|
}
|
|
52
38
|
|
|
53
|
-
this.
|
|
39
|
+
for (const [, subscription] of this.state.subscriptions) {
|
|
40
|
+
subscription.callback(value);
|
|
41
|
+
}
|
|
54
42
|
}
|
|
43
|
+
|
|
44
|
+
this.effect.dirty = false;
|
|
55
45
|
});
|
|
56
46
|
}
|
|
57
47
|
|
|
@@ -59,15 +49,15 @@ export class Computed<Value> extends Reactive<Value> {
|
|
|
59
49
|
* @inheritdoc
|
|
60
50
|
*/
|
|
61
51
|
get(): Value {
|
|
62
|
-
if (
|
|
63
|
-
this.state.computeds.add(
|
|
52
|
+
if (ACTIVE.computed != null && this !== ACTIVE.computed) {
|
|
53
|
+
this.state.computeds.add(ACTIVE.computed);
|
|
64
54
|
}
|
|
65
55
|
|
|
66
|
-
if (
|
|
67
|
-
this.state.effects.add(
|
|
56
|
+
if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) {
|
|
57
|
+
this.state.effects.add(ACTIVE.effect);
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
if (this.effect.dirty &&
|
|
60
|
+
if (this.effect.dirty && BATCH.depth === 0) {
|
|
71
61
|
runEffect(this.effect.instance);
|
|
72
62
|
}
|
|
73
63
|
|
package/src/value/reactive.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
type Subscription,
|
|
5
|
-
type Unsubscribe,
|
|
6
|
-
subscribe,
|
|
7
|
-
unsubscribe,
|
|
8
|
-
} from '../subscription';
|
|
9
|
-
import type {Computed} from './computed';
|
|
1
|
+
import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
|
|
2
|
+
import {subscribe, unsubscribe} from '../subscription';
|
|
10
3
|
|
|
11
4
|
export abstract class Reactive<Value, Equal = Value> {
|
|
12
5
|
protected readonly state: ReactiveState<Value, Equal> = {
|
|
@@ -31,11 +24,13 @@ export abstract class Reactive<Value, Equal = Value> {
|
|
|
31
24
|
|
|
32
25
|
/**
|
|
33
26
|
* Get the value
|
|
27
|
+
* @return Current value
|
|
34
28
|
*/
|
|
35
29
|
abstract get(): Value;
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* Get the value _(without reactivity)_
|
|
33
|
+
* @return Current value
|
|
39
34
|
*/
|
|
40
35
|
peek(): Value {
|
|
41
36
|
return this.state.value;
|
|
@@ -43,6 +38,8 @@ export abstract class Reactive<Value, Equal = Value> {
|
|
|
43
38
|
|
|
44
39
|
/**
|
|
45
40
|
* Subscribe to changes
|
|
41
|
+
* @param callback Callback for changes
|
|
42
|
+
* @return Unsubscribe callback
|
|
46
43
|
*/
|
|
47
44
|
subscribe(callback: (value: Value) => void): Unsubscribe {
|
|
48
45
|
return subscribe(this.state, callback);
|
|
@@ -50,6 +47,7 @@ export abstract class Reactive<Value, Equal = Value> {
|
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
49
|
* JSON representation of the value
|
|
50
|
+
* @return JSON value
|
|
53
51
|
*/
|
|
54
52
|
toJSON(): Value {
|
|
55
53
|
return this.get();
|
|
@@ -57,6 +55,7 @@ export abstract class Reactive<Value, Equal = Value> {
|
|
|
57
55
|
|
|
58
56
|
/**
|
|
59
57
|
* String representation of the value
|
|
58
|
+
* @return Value as string
|
|
60
59
|
*/
|
|
61
60
|
toString(): string {
|
|
62
61
|
return String(this.get());
|
|
@@ -64,23 +63,9 @@ export abstract class Reactive<Value, Equal = Value> {
|
|
|
64
63
|
|
|
65
64
|
/**
|
|
66
65
|
* Unsubscribe from changes
|
|
66
|
+
* @param callback Callback to unsubscribe
|
|
67
67
|
*/
|
|
68
68
|
unsubscribe(callback: (value: Value) => void): void {
|
|
69
69
|
unsubscribe(this.state, callback);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
export type ReactiveOptions<Value> = {
|
|
74
|
-
/**
|
|
75
|
-
* Method to compare values for equality
|
|
76
|
-
*/
|
|
77
|
-
equal?: (first: Value, second: Value) => boolean;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export type ReactiveState<Value, Equal> = {
|
|
81
|
-
computeds: Set<Computed<unknown>>;
|
|
82
|
-
effects: Set<Effect>;
|
|
83
|
-
equal: (first: Equal, second: Equal) => boolean;
|
|
84
|
-
subscriptions: Map<GenericCallback, Subscription>;
|
|
85
|
-
value: Value;
|
|
86
|
-
};
|
package/src/value/signal.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {NAME_SIGNAL} from '../constants';
|
|
2
2
|
import {emitValue, getValue} from '../helpers/value';
|
|
3
|
-
import
|
|
3
|
+
import type {ReactiveOptions} from '../models';
|
|
4
|
+
import {Reactive} from './reactive';
|
|
4
5
|
|
|
5
6
|
export class Signal<Value> extends Reactive<Value> {
|
|
6
7
|
constructor(value: Value, options?: ReactiveOptions<Value>) {
|
|
7
|
-
super(
|
|
8
|
+
super(NAME_SIGNAL, value, options);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -16,6 +17,7 @@ export class Signal<Value> extends Reactive<Value> {
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Set the value
|
|
20
|
+
* @param value New value
|
|
19
21
|
*/
|
|
20
22
|
set(value: Value): void {
|
|
21
23
|
if (!this.state.equal(this.state.value, value)) {
|
|
@@ -27,6 +29,7 @@ export class Signal<Value> extends Reactive<Value> {
|
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Update the value _(based on the current value)_
|
|
32
|
+
* @param callback Callback to update the value
|
|
30
33
|
*/
|
|
31
34
|
update(callback: (value: Value) => Value): void {
|
|
32
35
|
this.set(callback(this.state.value));
|
|
@@ -35,6 +38,9 @@ export class Signal<Value> extends Reactive<Value> {
|
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Create a reactive value
|
|
41
|
+
* @param value Initial value
|
|
42
|
+
* @param options Optional reactivity options
|
|
43
|
+
* @returns Reactive value
|
|
38
44
|
*/
|
|
39
45
|
export function signal<Value>(
|
|
40
46
|
value: Value,
|