@oscarpalmer/mora 0.28.0 → 0.30.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.mjs +15 -6
- package/dist/constants.d.mts +0 -1
- package/dist/constants.mjs +4 -3
- package/dist/effect.d.mts +5 -8
- package/dist/effect.mjs +23 -19
- package/dist/helpers/is.d.mts +9 -9
- package/dist/helpers/is.mjs +6 -0
- package/dist/helpers/proxy.d.mts +5 -9
- package/dist/helpers/proxy.mjs +4 -4
- package/dist/helpers/value.d.mts +3 -3
- package/dist/helpers/value.mjs +21 -3
- package/dist/index.d.mts +7 -8
- package/dist/index.mjs +1 -1
- package/dist/models.d.mts +356 -18
- package/dist/mora.full.mjs +458 -409
- package/dist/subscription.d.mts +1 -9
- package/dist/subscription.mjs +14 -15
- package/dist/value/array.d.mts +5 -133
- package/dist/value/array.mjs +91 -137
- package/dist/value/computed.d.mts +4 -12
- package/dist/value/computed.mjs +54 -50
- package/dist/value/reactive.d.mts +3 -38
- package/dist/value/reactive.mjs +10 -49
- package/dist/value/signal.d.mts +5 -21
- package/dist/value/signal.mjs +27 -49
- package/dist/value/store.d.mts +9 -92
- package/dist/value/store.mjs +42 -49
- package/package.json +8 -8
- package/src/batch.ts +22 -9
- package/src/constants.ts +3 -4
- package/src/effect.ts +31 -26
- package/src/helpers/is.ts +11 -10
- package/src/helpers/proxy.ts +23 -18
- package/src/helpers/value.ts +38 -3
- package/src/index.ts +6 -7
- package/src/models.ts +431 -16
- package/src/subscription.ts +20 -20
- package/src/value/array.ts +183 -265
- package/src/value/computed.ts +80 -74
- package/src/value/reactive.ts +16 -64
- package/src/value/signal.ts +34 -71
- package/src/value/store.ts +84 -177
package/src/value/array.ts
CHANGED
|
@@ -1,268 +1,37 @@
|
|
|
1
|
+
import {select} from '@oscarpalmer/atoms/array';
|
|
2
|
+
import {filter} from '@oscarpalmer/atoms/array/filter';
|
|
3
|
+
import {noop} from '@oscarpalmer/atoms/function';
|
|
1
4
|
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
METHODS_AFFECTING_LENGTH,
|
|
7
|
+
METHODS_UPDATE,
|
|
8
|
+
NAME_ARRAY,
|
|
9
|
+
NAME_MORA,
|
|
10
|
+
PROPERTY_LENGTH,
|
|
11
|
+
} from '../constants';
|
|
3
12
|
import {
|
|
4
13
|
emityProxyValues,
|
|
5
14
|
getReactiveValueInProxy,
|
|
6
15
|
setProxyValue,
|
|
7
16
|
setValueInProxy,
|
|
8
17
|
} from '../helpers/proxy';
|
|
9
|
-
import {emitValue, equalArrays,
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* The length of the array
|
|
23
|
-
*/
|
|
24
|
-
get length(): number {
|
|
25
|
-
return this.#size.get();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Set the length of the array
|
|
30
|
-
*/
|
|
31
|
-
set length(value: number) {
|
|
32
|
-
if (typeof value === 'number' && value >= 0 && value !== this.state.value.length) {
|
|
33
|
-
this.get().length = value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
constructor(value: Item[], options?: ReactiveOptions<Item>) {
|
|
38
|
-
super(
|
|
39
|
-
NAME_ARRAY,
|
|
40
|
-
new Proxy(value, {
|
|
41
|
-
get: (target: Item[], property: PropertyKey) =>
|
|
42
|
-
METHODS_UPDATE.has(property as string)
|
|
43
|
-
? updateArray(property as string, target, this.state, this.#size)
|
|
44
|
-
: Reflect.get(target, property),
|
|
45
|
-
set: (target: Item[], property: PropertyKey, value: Item) =>
|
|
46
|
-
setValueInProxy({
|
|
47
|
-
property,
|
|
48
|
-
target,
|
|
49
|
-
value,
|
|
50
|
-
isArray: true,
|
|
51
|
-
state: this.state,
|
|
52
|
-
length: this.#size,
|
|
53
|
-
}),
|
|
54
|
-
}),
|
|
55
|
-
options,
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
this.#size.set(value.length);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Clear the array
|
|
63
|
-
*/
|
|
64
|
-
clear(): void {
|
|
65
|
-
this.length = 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Create a computed, filtered array
|
|
70
|
-
* @param callback Callback to evaluate each item
|
|
71
|
-
* @return Computed array of filtered items
|
|
72
|
-
*/
|
|
73
|
-
filter(callback: (item: Item, index: number, array: Item[]) => boolean): Computed<Item[]> {
|
|
74
|
-
return computed(() => this.get().filter(callback));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @inheritdoc
|
|
79
|
-
*/
|
|
80
|
-
get(): Item[];
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get the value at an index
|
|
84
|
-
* @param index Index of item to get _(if negative, starts from the end)_
|
|
85
|
-
* @returns Item at index, or `undefined` if it doesn't exist
|
|
86
|
-
*/
|
|
87
|
-
get(index: number): Item | undefined;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get the length of the array
|
|
91
|
-
* @returns Length of the array
|
|
92
|
-
*/
|
|
93
|
-
get(property: typeof PROPERTY_LENGTH): number;
|
|
94
|
-
|
|
95
|
-
get(first?: unknown): unknown {
|
|
96
|
-
if (typeof first === 'number') {
|
|
97
|
-
return getReactiveValueInProxy(this, this.#indiced, first, true).get();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return first === PROPERTY_LENGTH ? this.length : getValue(this.state);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Create a computed, mapped array
|
|
105
|
-
* @param callback Callback to transform each item
|
|
106
|
-
* @return Computed array of mapped items
|
|
107
|
-
*/
|
|
108
|
-
map<Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped): Computed<Mapped[]> {
|
|
109
|
-
return computed(() => this.get().map(callback));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Notify dependents of changes
|
|
114
|
-
*
|
|
115
|
-
* _This bypasses equality checks and will immediately notify dependents.
|
|
116
|
-
* Use this only if you're modifying nested data that would be ignored by equality checks._
|
|
117
|
-
*/
|
|
118
|
-
notify(): void {
|
|
119
|
-
emityProxyValues(this.state, this.#indiced);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @inheritdoc
|
|
124
|
-
*/
|
|
125
|
-
override peek(): Item[];
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get the value at an index _(without reactivity)_
|
|
129
|
-
* @param index Index of item to get _(if negative, starts from the end)_
|
|
130
|
-
* @returns Item at index, or `undefined` if it doesn't exist
|
|
131
|
-
*/
|
|
132
|
-
override peek(index: number): Item | undefined;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get the length of the array _(without reactivity)_
|
|
136
|
-
* @returns Length of the array
|
|
137
|
-
*/
|
|
138
|
-
override peek(property: typeof PROPERTY_LENGTH): number;
|
|
139
|
-
|
|
140
|
-
override peek(value?: unknown): unknown {
|
|
141
|
-
if (value === PROPERTY_LENGTH) {
|
|
142
|
-
return this.#size.peek();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return typeof value === 'number' ? this.state.value.at(value) : this.state.value.slice();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Remove and return the last item of the array
|
|
150
|
-
* @returns Removed item, or `undefined` if the array is empty
|
|
151
|
-
*/
|
|
152
|
-
pop(): Item | undefined {
|
|
153
|
-
return this.state.value.pop();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Add items to the end of the array
|
|
158
|
-
* @param items Items to add
|
|
159
|
-
* @returns New array length
|
|
160
|
-
*/
|
|
161
|
-
push(...items: Item[]): number {
|
|
162
|
-
return this.state.value.push(...items);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Set the value
|
|
167
|
-
* @param value New array of items _(defaults to an empty array)_
|
|
168
|
-
*/
|
|
169
|
-
set(
|
|
170
|
-
value?:
|
|
171
|
-
| Item[]
|
|
172
|
-
| (() => null | undefined | Item[] | Promise<null | undefined | Item[]>)
|
|
173
|
-
| Promise<null | undefined | Item[]>,
|
|
174
|
-
): void;
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Set the value at an index
|
|
178
|
-
* @param index Index of item to set __(if negative, starts from the end)_
|
|
179
|
-
* @param value New item
|
|
180
|
-
*/
|
|
181
|
-
set(index: number, value: Item | (() => Item | Promise<Item>) | Promise<Item>): void;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Set the length of the array
|
|
185
|
-
* @param value New array length
|
|
186
|
-
*/
|
|
187
|
-
set(property: typeof PROPERTY_LENGTH, value: number): void;
|
|
188
|
-
|
|
189
|
-
set(first?: unknown, second?: unknown): void {
|
|
190
|
-
setProxyValue<Item[], Item>(
|
|
191
|
-
true,
|
|
192
|
-
this.state,
|
|
193
|
-
isArrayValue,
|
|
194
|
-
isArrayIndex,
|
|
195
|
-
setArray,
|
|
196
|
-
setAtIndex,
|
|
197
|
-
first,
|
|
198
|
-
second,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Remove and return the first item of the array
|
|
204
|
-
* @returns Removed item, or `undefined` if the array is empty
|
|
205
|
-
*/
|
|
206
|
-
shift(): Item | undefined {
|
|
207
|
-
return this.state.value.shift();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Remove and return items from the array _(and optionally add new items)_
|
|
212
|
-
* @param from Index to start removing items from
|
|
213
|
-
* @param to Index to stop removing items at _(defaults to the end of the array)_
|
|
214
|
-
* @param items Optional items to add
|
|
215
|
-
* @returns Removed items
|
|
216
|
-
*/
|
|
217
|
-
splice(from: number, to?: number, ...items: Item[]): Item[] {
|
|
218
|
-
return this.state.value.splice(from, to ?? this.state.value.length, ...items);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* @inheritdoc
|
|
223
|
-
*/
|
|
224
|
-
override subscribe(callback: (value: Item[]) => void): Unsubscribe;
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Subscribe to changes at a specific index
|
|
228
|
-
* @param index Index of item to subscribe to
|
|
229
|
-
* @param callback Callback for changes
|
|
230
|
-
* @returns Unsubscribe callback
|
|
231
|
-
*/
|
|
232
|
-
override subscribe(index: number, callback: (value: Item | undefined) => void): Unsubscribe;
|
|
233
|
-
|
|
234
|
-
override subscribe(first: number | GenericCallback, second?: GenericCallback): Unsubscribe {
|
|
235
|
-
if (typeof first === 'number' && typeof second === 'function') {
|
|
236
|
-
return getReactiveValueInProxy(this, this.#indiced, first, true).subscribe(second);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return typeof first === 'function' ? subscribe(this.state, first) : noop;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Add items to the beginning of the array
|
|
244
|
-
* @param items Items to add
|
|
245
|
-
* @returns New array length
|
|
246
|
-
*/
|
|
247
|
-
unshift(...items: Item[]): number {
|
|
248
|
-
return this.state.value.unshift(...items);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Update the value _(based on the current value)_
|
|
253
|
-
* @param callback Callback to update the value
|
|
254
|
-
*/
|
|
255
|
-
update(callback: (value: Item[]) => Item[]): void {
|
|
256
|
-
const updated = callback(this.state.value);
|
|
257
|
-
|
|
258
|
-
if (updated == null || Array.isArray(updated)) {
|
|
259
|
-
this.set(updated);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
18
|
+
import {emitValue, equalArrays, getSimpleValue} from '../helpers/value';
|
|
19
|
+
import type {
|
|
20
|
+
Computed,
|
|
21
|
+
ComputedEffect,
|
|
22
|
+
ReactiveArray,
|
|
23
|
+
ReactiveOptions,
|
|
24
|
+
ReactiveState,
|
|
25
|
+
Signal,
|
|
26
|
+
} from '../models';
|
|
27
|
+
import {subscribe, unsubscribe} from '../subscription';
|
|
28
|
+
import {computed} from './computed';
|
|
29
|
+
import {reactive} from './reactive';
|
|
30
|
+
import {signal} from './signal';
|
|
263
31
|
|
|
264
32
|
/**
|
|
265
33
|
* Create a reactive array from a function result
|
|
34
|
+
*
|
|
266
35
|
* @param value Initial array of items
|
|
267
36
|
* @param options Reactivity options
|
|
268
37
|
* @returns Reactive array
|
|
@@ -274,6 +43,7 @@ export function array<Item>(
|
|
|
274
43
|
|
|
275
44
|
/**
|
|
276
45
|
* Create a reactive array from a promise
|
|
46
|
+
*
|
|
277
47
|
* @param value Initial array of items
|
|
278
48
|
* @param options Reactivity options
|
|
279
49
|
* @returns Reactive array
|
|
@@ -285,6 +55,7 @@ export function array<Item>(
|
|
|
285
55
|
|
|
286
56
|
/**
|
|
287
57
|
* Create a reactive array
|
|
58
|
+
*
|
|
288
59
|
* @param value Initial array of items
|
|
289
60
|
* @param options Reactivity options
|
|
290
61
|
* @returns Reactive array
|
|
@@ -295,11 +66,128 @@ export function array<Item>(
|
|
|
295
66
|
value: Item[] | (() => Item[] | Promise<Item[]>) | Promise<Item[]>,
|
|
296
67
|
options?: ReactiveOptions<Item>,
|
|
297
68
|
): ReactiveArray<Item> {
|
|
298
|
-
const
|
|
69
|
+
const [rx, state] = reactive<Item[], Item>([], options);
|
|
70
|
+
|
|
71
|
+
const indiced = new Map<number, [Computed<unknown>, ComputedEffect]>();
|
|
72
|
+
const length = signal(0);
|
|
73
|
+
|
|
74
|
+
state.value = new Proxy([], {
|
|
75
|
+
get: (target: Item[], property: PropertyKey) =>
|
|
76
|
+
METHODS_UPDATE.has(property as string)
|
|
77
|
+
? updateArray(property as string, target, state, length)
|
|
78
|
+
: Reflect.get(target, property),
|
|
79
|
+
set: (target: Item[], property: PropertyKey, value: Item) =>
|
|
80
|
+
setValueInProxy({
|
|
81
|
+
length,
|
|
82
|
+
property,
|
|
83
|
+
state,
|
|
84
|
+
target,
|
|
85
|
+
value,
|
|
86
|
+
isArray: true,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function get(): Item[];
|
|
91
|
+
function get(index: number): Item | undefined;
|
|
92
|
+
function get(property: typeof PROPERTY_LENGTH): number;
|
|
93
|
+
function get(value?: unknown): number | Item | Item[] | undefined;
|
|
94
|
+
|
|
95
|
+
function get(value?: unknown): number | Item | Item[] | undefined {
|
|
96
|
+
return getArrayValue(instance as ReactiveArray<Item>, indiced, state, length, value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const instance = {
|
|
100
|
+
...rx,
|
|
101
|
+
length: state.value.length,
|
|
102
|
+
at: (index: number): Item | undefined => get(index),
|
|
103
|
+
clear: () => {
|
|
104
|
+
state.value.length = 0;
|
|
105
|
+
},
|
|
106
|
+
filter: (callback: (item: Item, index: number, array: Item[]) => boolean) =>
|
|
107
|
+
computed(() => filter(get(), callback)),
|
|
108
|
+
get: (value?: number | typeof PROPERTY_LENGTH) => get(value),
|
|
109
|
+
map: <Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped) =>
|
|
110
|
+
computed(() => (get() as Item[]).map(callback)),
|
|
111
|
+
notify: () => {
|
|
112
|
+
emityProxyValues(state, indiced);
|
|
113
|
+
},
|
|
114
|
+
peek: (value?: unknown) => peekArrayValue(state, length, value),
|
|
115
|
+
pop: () => state.value.pop(),
|
|
116
|
+
push: (...items: Item[]) => state.value.push(...items),
|
|
117
|
+
select: <Mapped>(
|
|
118
|
+
filter: (item: Item, index: number, array: Item[]) => boolean,
|
|
119
|
+
map: (item: Item, index: number, array: Item[]) => Mapped,
|
|
120
|
+
) => computed(() => select(get() as Item[], filter, map)),
|
|
121
|
+
set: (first?: unknown, second?: unknown) => {
|
|
122
|
+
setProxyValue<Item[], Item>(
|
|
123
|
+
true,
|
|
124
|
+
state,
|
|
125
|
+
isArrayValue,
|
|
126
|
+
isArrayIndex,
|
|
127
|
+
setArray,
|
|
128
|
+
setAtIndex,
|
|
129
|
+
first,
|
|
130
|
+
second,
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
shift: () => state.value.shift(),
|
|
134
|
+
splice: (from: number, to?: number, ...items: Item[]) =>
|
|
135
|
+
state.value.splice(from, to ?? state.value.length, ...items),
|
|
136
|
+
subscribe: (first: number | GenericCallback, second?: GenericCallback) => {
|
|
137
|
+
if (typeof first === 'number' && typeof second === 'function') {
|
|
138
|
+
return getReactiveValueInProxy(
|
|
139
|
+
instance as ReactiveArray<Item>,
|
|
140
|
+
indiced,
|
|
141
|
+
first,
|
|
142
|
+
true,
|
|
143
|
+
).subscribe(second);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return typeof first === 'function' ? subscribe(state, first) : noop;
|
|
147
|
+
},
|
|
148
|
+
unshift: (...items: Item[]) => state.value.unshift(...items),
|
|
149
|
+
unsubscribe: (first: number | GenericCallback, second?: GenericCallback) => {
|
|
150
|
+
if (typeof first === 'number' && typeof second === 'function') {
|
|
151
|
+
getReactiveValueInProxy(instance as ReactiveArray<Item>, indiced, first, true)?.unsubscribe(
|
|
152
|
+
second,
|
|
153
|
+
);
|
|
154
|
+
} else if (typeof first === 'function') {
|
|
155
|
+
unsubscribe(state, first);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
update: (callback: (value: Item[]) => Item[]) =>
|
|
159
|
+
updateArrayValue(instance as ReactiveArray<Item>, state, callback),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
Object.defineProperties(instance, {
|
|
163
|
+
[NAME_MORA]: {
|
|
164
|
+
enumerable: false,
|
|
165
|
+
value: NAME_ARRAY,
|
|
166
|
+
},
|
|
167
|
+
length: {
|
|
168
|
+
enumerable: true,
|
|
169
|
+
get: () => length.get(),
|
|
170
|
+
set: (value: number) => setArrayLength(state, value),
|
|
171
|
+
},
|
|
172
|
+
});
|
|
299
173
|
|
|
300
174
|
instance.set(value);
|
|
301
175
|
|
|
302
|
-
return instance
|
|
176
|
+
return Object.freeze(instance) as ReactiveArray<Item>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getArrayValue<Item>(
|
|
180
|
+
instance: ReactiveArray<Item>,
|
|
181
|
+
indiced: Map<number, [Computed<unknown>, ComputedEffect]>,
|
|
182
|
+
state: ReactiveState<Item[], Item>,
|
|
183
|
+
length: Signal<number>,
|
|
184
|
+
first?: unknown,
|
|
185
|
+
): number | Item | Item[] | undefined {
|
|
186
|
+
if (typeof first === 'number') {
|
|
187
|
+
return getReactiveValueInProxy(instance, indiced, first, true).get();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return first === PROPERTY_LENGTH ? length.get() : getSimpleValue(state);
|
|
303
191
|
}
|
|
304
192
|
|
|
305
193
|
function isArrayIndex(value: unknown): boolean {
|
|
@@ -310,6 +198,36 @@ function isArrayValue<Item>(value: unknown): value is Item[] | undefined {
|
|
|
310
198
|
return value == null || Array.isArray(value);
|
|
311
199
|
}
|
|
312
200
|
|
|
201
|
+
function peekArrayValue<Item>(
|
|
202
|
+
state: ReactiveState<Item[], Item>,
|
|
203
|
+
length: Signal<number>,
|
|
204
|
+
value?: unknown,
|
|
205
|
+
): number | Item | Item[] | undefined {
|
|
206
|
+
if (value === PROPERTY_LENGTH) {
|
|
207
|
+
return length.peek();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return typeof value === 'number' ? state.value.at(value) : state.value.slice();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function setArray<Item>(state: ReactiveState<Item[], Item>, value: Item[] | undefined): void {
|
|
214
|
+
state.value.splice(0, state.value.length, ...(value ?? []));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function setArrayLength<Item>(state: ReactiveState<Item[], Item>, value: number): void {
|
|
218
|
+
if (typeof value === 'number' && value >= 0 && value !== state.value.length) {
|
|
219
|
+
state.value.length = value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function setAtIndex<Item>(state: ReactiveState<Item[], Item>, index: unknown, value: Item): void {
|
|
224
|
+
const actual = (index as number) < 0 ? state.value.length + (index as number) : (index as number);
|
|
225
|
+
|
|
226
|
+
if (actual > -1) {
|
|
227
|
+
state.value[actual] = value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
313
231
|
function updateArray<Item>(
|
|
314
232
|
type: string,
|
|
315
233
|
array: Item[],
|
|
@@ -335,14 +253,14 @@ function updateArray<Item>(
|
|
|
335
253
|
};
|
|
336
254
|
}
|
|
337
255
|
|
|
338
|
-
function
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
256
|
+
function updateArrayValue<Item>(
|
|
257
|
+
instance: ReactiveArray<Item>,
|
|
258
|
+
state: ReactiveState<Item[], Item>,
|
|
259
|
+
callback: (value: Item[]) => Item[],
|
|
260
|
+
): void {
|
|
261
|
+
const updated = callback(state.value);
|
|
344
262
|
|
|
345
|
-
if (
|
|
346
|
-
|
|
263
|
+
if (updated == null || Array.isArray(updated)) {
|
|
264
|
+
instance.set(updated);
|
|
347
265
|
}
|
|
348
266
|
}
|
package/src/value/computed.ts
CHANGED
|
@@ -1,58 +1,100 @@
|
|
|
1
1
|
import {flushHandlers} from '../batch';
|
|
2
|
-
import {ACTIVE, BATCH, NAME_COMPUTED} from '../constants';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
2
|
+
import {ACTIVE, BATCH, NAME_COMPUTED, NAME_MORA} from '../constants';
|
|
3
|
+
import {internalEffect, runEffect} from '../effect';
|
|
4
|
+
import {handleSimpleValue} from '../helpers/value';
|
|
5
|
+
import type {Computed, ComputedEffect, ReactiveOptions, ReactiveState} from '../models';
|
|
6
|
+
import {subscribe} from '../subscription';
|
|
7
|
+
import {reactive} from './reactive';
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Create a computed value
|
|
11
|
+
*
|
|
12
|
+
* @param callback Callback to compute the value
|
|
13
|
+
* @param options Reactivity options
|
|
14
|
+
* @returns Computed value
|
|
15
|
+
*/
|
|
16
|
+
export function computed<Value>(
|
|
17
|
+
callback: () => Value | Promise<Value>,
|
|
18
|
+
options?: ReactiveOptions<Value>,
|
|
19
|
+
): Computed<Value> {
|
|
20
|
+
return getComputed(callback, options)[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getComputed<Value>(
|
|
24
|
+
callback: () => Value | Promise<Value>,
|
|
25
|
+
options?: ReactiveOptions<Value>,
|
|
26
|
+
): [Computed<Value>, ComputedEffect] {
|
|
27
|
+
const [rx, state] = reactive<Value>(undefined as never, options);
|
|
28
|
+
|
|
29
|
+
let fx: ComputedEffect;
|
|
30
|
+
|
|
31
|
+
const instance = {
|
|
32
|
+
...rx,
|
|
33
|
+
get: () => getValue(state, fx),
|
|
34
|
+
peek: () => state.value,
|
|
35
|
+
subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
|
|
36
|
+
unsubscribe: (callback: (value: Value) => void) => {
|
|
37
|
+
state.subscriptions.delete(callback);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(instance, NAME_MORA, {
|
|
42
|
+
enumerable: false,
|
|
43
|
+
value: NAME_COMPUTED,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
fx = getComputedEffect(state, callback);
|
|
47
|
+
|
|
48
|
+
return [Object.freeze(instance), fx];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getComputedEffect<Value>(
|
|
52
|
+
state: ReactiveState<Value, Value>,
|
|
53
|
+
callback: () => Value | Promise<Value>,
|
|
54
|
+
): ComputedEffect {
|
|
55
|
+
const fx: ComputedEffect = {
|
|
9
56
|
dirty: true,
|
|
10
57
|
instance: undefined as never,
|
|
11
58
|
};
|
|
12
59
|
|
|
13
|
-
|
|
14
|
-
|
|
60
|
+
fx.instance = internalEffect(() => {
|
|
61
|
+
if (fx.dirty) {
|
|
62
|
+
const previousComputed = ACTIVE.computed;
|
|
15
63
|
|
|
16
|
-
|
|
17
|
-
if (this.effect.dirty) {
|
|
18
|
-
setValue(this, this.state, callback);
|
|
64
|
+
ACTIVE.computed = fx;
|
|
19
65
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
66
|
+
handleSimpleValue(state, callback, setAndEmit, () => {
|
|
67
|
+
ACTIVE.computed = previousComputed;
|
|
68
|
+
});
|
|
24
69
|
|
|
25
|
-
|
|
26
|
-
* @inheritdoc
|
|
27
|
-
*/
|
|
28
|
-
get(): Value {
|
|
29
|
-
if (ACTIVE.computed != null && this !== ACTIVE.computed) {
|
|
30
|
-
this.state.computeds.add(ACTIVE.computed);
|
|
70
|
+
fx.dirty = false;
|
|
31
71
|
}
|
|
72
|
+
});
|
|
32
73
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
74
|
+
return fx;
|
|
75
|
+
}
|
|
36
76
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
function getValue<Value>(state: ReactiveState<Value, Value>, fx: ComputedEffect): Value {
|
|
78
|
+
if (ACTIVE.computed != null && fx !== ACTIVE.computed) {
|
|
79
|
+
state.computeds.add(ACTIVE.computed);
|
|
80
|
+
}
|
|
40
81
|
|
|
41
|
-
|
|
82
|
+
if (ACTIVE.effect != null && ACTIVE.effect !== fx.instance) {
|
|
83
|
+
state.effects.add(ACTIVE.effect);
|
|
42
84
|
}
|
|
85
|
+
|
|
86
|
+
if (fx.dirty && BATCH.depth === 0) {
|
|
87
|
+
runEffect(fx.instance);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return state.value;
|
|
43
91
|
}
|
|
44
92
|
|
|
45
|
-
|
|
46
|
-
* Create a computed value
|
|
47
|
-
* @param callback Callback to compute the value
|
|
48
|
-
* @param options Reactivity options
|
|
49
|
-
* @returns Computed value
|
|
50
|
-
*/
|
|
51
|
-
export function computed<Value>(
|
|
93
|
+
export function internalComputed<Value>(
|
|
52
94
|
callback: () => Value | Promise<Value>,
|
|
53
95
|
options?: ReactiveOptions<Value>,
|
|
54
|
-
): Computed<Value
|
|
55
|
-
return
|
|
96
|
+
): [Computed<Value>, ComputedEffect] {
|
|
97
|
+
return getComputed(callback, options);
|
|
56
98
|
}
|
|
57
99
|
|
|
58
100
|
function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
|
|
@@ -63,7 +105,7 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
|
|
|
63
105
|
state.value = value;
|
|
64
106
|
|
|
65
107
|
for (const computed of state.computeds) {
|
|
66
|
-
|
|
108
|
+
computed.dirty = true;
|
|
67
109
|
}
|
|
68
110
|
|
|
69
111
|
for (const effect of state.effects) {
|
|
@@ -76,39 +118,3 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
|
|
|
76
118
|
|
|
77
119
|
flushHandlers();
|
|
78
120
|
}
|
|
79
|
-
|
|
80
|
-
function setValue<Value>(
|
|
81
|
-
instance: Computed<Value>,
|
|
82
|
-
state: ReactiveState<Value, Value>,
|
|
83
|
-
callback: () => Value | Promise<Value>,
|
|
84
|
-
): void {
|
|
85
|
-
const previousComputed = ACTIVE.computed;
|
|
86
|
-
|
|
87
|
-
ACTIVE.computed = instance as never;
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const value = callback();
|
|
91
|
-
|
|
92
|
-
if (value instanceof Promise) {
|
|
93
|
-
state.promise = value;
|
|
94
|
-
|
|
95
|
-
value
|
|
96
|
-
.then(resolvedValue => {
|
|
97
|
-
if (state.promise === value) {
|
|
98
|
-
state.promise = undefined;
|
|
99
|
-
|
|
100
|
-
setAndEmit(state, resolvedValue);
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
.catch(() => {
|
|
104
|
-
if (state.promise === value) {
|
|
105
|
-
state.promise = undefined;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
} else {
|
|
109
|
-
setAndEmit(state, value);
|
|
110
|
-
}
|
|
111
|
-
} finally {
|
|
112
|
-
ACTIVE.computed = previousComputed;
|
|
113
|
-
}
|
|
114
|
-
}
|