@oscarpalmer/mora 0.22.0 → 0.24.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 +36 -0
- package/dist/effect.js +7 -8
- package/dist/helpers/is.js +9 -19
- package/dist/helpers/proxy.js +12 -7
- package/dist/helpers/value.js +7 -8
- package/dist/index.js +1 -1
- package/dist/models.js +0 -0
- package/dist/mora.full.js +248 -148
- package/dist/subscription.js +7 -5
- package/dist/value/array.js +27 -29
- package/dist/value/computed.js +18 -21
- package/dist/value/reactive.js +2 -1
- package/dist/value/signal.js +2 -2
- package/dist/value/store.js +12 -3
- package/package.json +9 -11
- package/src/batch.ts +11 -13
- package/src/constants.ts +53 -0
- package/src/effect.ts +12 -17
- package/src/helpers/is.ts +45 -22
- package/src/helpers/proxy.ts +62 -39
- 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 +92 -55
- package/src/value/computed.ts +29 -39
- package/src/value/reactive.ts +11 -25
- package/src/value/signal.ts +9 -3
- package/src/value/store.ts +55 -13
- package/types/batch.d.ts +3 -5
- package/types/constants.d.ts +16 -0
- package/types/effect.d.ts +2 -1
- package/types/helpers/is.d.ts +23 -10
- package/types/helpers/proxy.d.ts +4 -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 +38 -7
package/dist/mora.full.js
CHANGED
|
@@ -1,78 +1,124 @@
|
|
|
1
|
+
const ACTIVE = {};
|
|
2
|
+
const ARRAY_THRESHOLD = 100;
|
|
3
|
+
const ARRAY_OFFSET = 25;
|
|
4
|
+
const ARRAY_PEEK = 10;
|
|
5
|
+
const BATCH = {
|
|
6
|
+
depth: 0,
|
|
7
|
+
handlers: new Set(),
|
|
8
|
+
};
|
|
9
|
+
const METHODS_AFFECTING_LENGTH = new Set([
|
|
10
|
+
'pop',
|
|
11
|
+
'push',
|
|
12
|
+
'shift',
|
|
13
|
+
'unshift',
|
|
14
|
+
]);
|
|
15
|
+
const METHODS_UPDATE = new Set([
|
|
16
|
+
...METHODS_AFFECTING_LENGTH,
|
|
17
|
+
'copyWithin',
|
|
18
|
+
'fill',
|
|
19
|
+
'reverse',
|
|
20
|
+
'sort',
|
|
21
|
+
'splice',
|
|
22
|
+
]);
|
|
23
|
+
const NAME_ARRAY = 'array';
|
|
24
|
+
const NAME_COMPUTED = 'computed';
|
|
25
|
+
const NAME_EFFECT = 'effect';
|
|
26
|
+
const NAME_MORA = '$mora';
|
|
27
|
+
const NAME_SIGNAL = 'signal';
|
|
28
|
+
const NAME_STORE = 'store';
|
|
29
|
+
const NAMES = new Set([
|
|
30
|
+
NAME_ARRAY,
|
|
31
|
+
NAME_COMPUTED,
|
|
32
|
+
NAME_SIGNAL,
|
|
33
|
+
NAME_STORE,
|
|
34
|
+
]);
|
|
35
|
+
const PROPERTY_LENGTH = 'length';
|
|
36
|
+
|
|
37
|
+
class Effect {
|
|
38
|
+
constructor(callback) {
|
|
39
|
+
Object.defineProperty(this, NAME_MORA, {
|
|
40
|
+
value: NAME_EFFECT,
|
|
41
|
+
});
|
|
42
|
+
this.state = {
|
|
43
|
+
callback,
|
|
44
|
+
};
|
|
45
|
+
runEffect(this);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function runEffect(effect) {
|
|
49
|
+
const previousEffect = ACTIVE.effect;
|
|
50
|
+
ACTIVE.effect = effect;
|
|
51
|
+
try {
|
|
52
|
+
effect.state.callback();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
ACTIVE.effect = previousEffect;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create an effect
|
|
60
|
+
* @param callback Callback for handling signal effects
|
|
61
|
+
* @returns Effect
|
|
62
|
+
*/
|
|
63
|
+
function effect(callback) {
|
|
64
|
+
return typeof callback === 'function'
|
|
65
|
+
? new Effect(callback)
|
|
66
|
+
: undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
1
69
|
/**
|
|
2
70
|
* Is the value a reactive array?
|
|
71
|
+
* @param value Value to check
|
|
72
|
+
* @returns True if value is a {@link ReactiveArray}
|
|
3
73
|
*/
|
|
4
74
|
function isArray(value) {
|
|
5
|
-
return isMora(value,
|
|
75
|
+
return isMora(value, NAME_ARRAY);
|
|
6
76
|
}
|
|
7
77
|
/**
|
|
8
78
|
* Is the value a computed signal?
|
|
79
|
+
* @param value Value to check
|
|
80
|
+
* @returns True if value is a {@link Computed}
|
|
9
81
|
*/
|
|
10
82
|
function isComputed(value) {
|
|
11
|
-
return isMora(value,
|
|
83
|
+
return isMora(value, NAME_COMPUTED);
|
|
12
84
|
}
|
|
13
85
|
/**
|
|
14
86
|
* Is the value an effect?
|
|
87
|
+
* @param value Value to check
|
|
88
|
+
* @returns True if value is an {@link Effect}
|
|
15
89
|
*/
|
|
16
90
|
function isEffect(value) {
|
|
17
|
-
return isMora(value,
|
|
91
|
+
return isMora(value, NAME_EFFECT);
|
|
18
92
|
}
|
|
19
93
|
function isMora(value, name) {
|
|
20
94
|
return (typeof value === 'object' &&
|
|
21
95
|
value != null &&
|
|
22
|
-
|
|
96
|
+
NAME_MORA in value &&
|
|
23
97
|
(typeof name === 'string'
|
|
24
|
-
? value
|
|
25
|
-
: name.has(value
|
|
98
|
+
? value[NAME_MORA] === name
|
|
99
|
+
: name.has(value[NAME_MORA])));
|
|
26
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Is the value reactive?
|
|
103
|
+
* @param value Value to check
|
|
104
|
+
* @returns True if value is a {@link Reactive}
|
|
105
|
+
*/
|
|
27
106
|
function isReactive(value) {
|
|
28
|
-
return isMora(value,
|
|
107
|
+
return isMora(value, NAMES);
|
|
29
108
|
}
|
|
30
109
|
/**
|
|
31
110
|
* Is the value a signal?
|
|
111
|
+
* @param value Value to check
|
|
112
|
+
* @returns True if value is a {@link Signal}
|
|
32
113
|
*/
|
|
33
114
|
function isSignal(value) {
|
|
34
|
-
return isMora(value,
|
|
35
|
-
}
|
|
36
|
-
const arrayName = 'array';
|
|
37
|
-
const computedName = 'computed';
|
|
38
|
-
const effectName = 'effect';
|
|
39
|
-
const signalName = 'signal';
|
|
40
|
-
const storeName = 'store';
|
|
41
|
-
const reactiveNames = new Set([arrayName, computedName, signalName, storeName]);
|
|
42
|
-
|
|
43
|
-
class Effect {
|
|
44
|
-
constructor(callback) {
|
|
45
|
-
Object.defineProperty(this, '$mora', {
|
|
46
|
-
value: effectName,
|
|
47
|
-
});
|
|
48
|
-
this.state = {
|
|
49
|
-
callback,
|
|
50
|
-
};
|
|
51
|
-
runEffect(this);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function runEffect(effect) {
|
|
55
|
-
const previousEffect = activeEffect;
|
|
56
|
-
activeEffect = effect;
|
|
57
|
-
try {
|
|
58
|
-
effect.state.callback();
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
activeEffect = previousEffect;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Create an effect
|
|
66
|
-
*/
|
|
67
|
-
function effect(callback) {
|
|
68
|
-
return new Effect(callback);
|
|
115
|
+
return isMora(value, NAME_SIGNAL);
|
|
69
116
|
}
|
|
70
|
-
let activeEffect;
|
|
71
117
|
|
|
72
118
|
function flushHandlers() {
|
|
73
|
-
while (
|
|
74
|
-
const handlers = [...
|
|
75
|
-
|
|
119
|
+
while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
|
|
120
|
+
const handlers = [...BATCH.handlers];
|
|
121
|
+
BATCH.handlers.clear();
|
|
76
122
|
for (const handler of handlers) {
|
|
77
123
|
if (isEffect(handler)) {
|
|
78
124
|
runEffect(handler);
|
|
@@ -84,31 +130,35 @@ function flushHandlers() {
|
|
|
84
130
|
}
|
|
85
131
|
}
|
|
86
132
|
/**
|
|
87
|
-
* Start batching effects
|
|
133
|
+
* Start batching effects
|
|
134
|
+
*
|
|
135
|
+
* _(Use {@link stopBatch} to flush and run batched effects)_
|
|
88
136
|
*/
|
|
89
137
|
function startBatch() {
|
|
90
|
-
|
|
138
|
+
BATCH.depth += 1;
|
|
91
139
|
}
|
|
92
140
|
/**
|
|
93
141
|
* Stop batching effects and flush _(run)_ them
|
|
94
142
|
*/
|
|
95
143
|
function stopBatch() {
|
|
96
|
-
if (
|
|
97
|
-
|
|
144
|
+
if (BATCH.depth > 0) {
|
|
145
|
+
BATCH.depth -= 1;
|
|
98
146
|
}
|
|
99
147
|
flushHandlers();
|
|
100
148
|
}
|
|
101
|
-
const batchedHandlers = new Set();
|
|
102
|
-
let batchDepth = 0;
|
|
103
149
|
|
|
104
150
|
class Subscription {
|
|
105
|
-
state;
|
|
106
151
|
callback;
|
|
152
|
+
state;
|
|
107
153
|
constructor(state, callback) {
|
|
108
154
|
this.state = state;
|
|
109
155
|
this.callback = callback;
|
|
110
156
|
callback(state.value);
|
|
111
157
|
}
|
|
158
|
+
destroy() {
|
|
159
|
+
this.callback = noop;
|
|
160
|
+
this.state = undefined;
|
|
161
|
+
}
|
|
112
162
|
}
|
|
113
163
|
function noop() { }
|
|
114
164
|
function subscribe(state, callback) {
|
|
@@ -121,12 +171,8 @@ function subscribe(state, callback) {
|
|
|
121
171
|
};
|
|
122
172
|
}
|
|
123
173
|
function unsubscribe(state, callback) {
|
|
124
|
-
|
|
174
|
+
state.subscriptions.get(callback)?.destroy();
|
|
125
175
|
state.subscriptions.delete(callback);
|
|
126
|
-
if (subscription != null) {
|
|
127
|
-
subscription.callback = noop;
|
|
128
|
-
subscription.state = undefined;
|
|
129
|
-
}
|
|
130
176
|
}
|
|
131
177
|
|
|
132
178
|
class Reactive {
|
|
@@ -142,83 +188,89 @@ class Reactive {
|
|
|
142
188
|
if (typeof options === 'object' && typeof options.equal === 'function') {
|
|
143
189
|
this.state.equal = options.equal;
|
|
144
190
|
}
|
|
145
|
-
Object.defineProperty(this,
|
|
191
|
+
Object.defineProperty(this, NAME_MORA, {
|
|
146
192
|
value: name,
|
|
147
193
|
});
|
|
148
194
|
}
|
|
149
195
|
/**
|
|
150
196
|
* Get the value _(without reactivity)_
|
|
197
|
+
* @return Current value
|
|
151
198
|
*/
|
|
152
199
|
peek() {
|
|
153
200
|
return this.state.value;
|
|
154
201
|
}
|
|
155
202
|
/**
|
|
156
203
|
* Subscribe to changes
|
|
204
|
+
* @param callback Callback for changes
|
|
205
|
+
* @return Unsubscribe callback
|
|
157
206
|
*/
|
|
158
207
|
subscribe(callback) {
|
|
159
208
|
return subscribe(this.state, callback);
|
|
160
209
|
}
|
|
161
210
|
/**
|
|
162
211
|
* JSON representation of the value
|
|
212
|
+
* @return JSON value
|
|
163
213
|
*/
|
|
164
214
|
toJSON() {
|
|
165
215
|
return this.get();
|
|
166
216
|
}
|
|
167
217
|
/**
|
|
168
218
|
* String representation of the value
|
|
219
|
+
* @return Value as string
|
|
169
220
|
*/
|
|
170
221
|
toString() {
|
|
171
222
|
return String(this.get());
|
|
172
223
|
}
|
|
173
224
|
/**
|
|
174
225
|
* Unsubscribe from changes
|
|
226
|
+
* @param callback Callback to unsubscribe
|
|
175
227
|
*/
|
|
176
228
|
unsubscribe(callback) {
|
|
177
229
|
unsubscribe(this.state, callback);
|
|
178
230
|
}
|
|
179
231
|
}
|
|
180
232
|
|
|
181
|
-
let activeComputed;
|
|
182
233
|
class Computed extends Reactive {
|
|
183
234
|
effect = {
|
|
184
235
|
dirty: true,
|
|
185
236
|
instance: undefined,
|
|
186
237
|
};
|
|
187
238
|
constructor(callback, options) {
|
|
188
|
-
super(
|
|
239
|
+
super(NAME_COMPUTED, undefined, options);
|
|
189
240
|
this.effect.instance = effect(() => {
|
|
190
|
-
if (this.effect.dirty) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
241
|
+
if (!this.effect.dirty) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const previousComputed = ACTIVE.computed;
|
|
245
|
+
ACTIVE.computed = this;
|
|
246
|
+
const value = callback();
|
|
247
|
+
ACTIVE.computed = previousComputed;
|
|
248
|
+
if (!this.state.equal(this.state.value, value)) {
|
|
249
|
+
this.state.value = value;
|
|
250
|
+
for (const computed of this.state.computeds) {
|
|
251
|
+
computed.effect.dirty = true;
|
|
252
|
+
}
|
|
253
|
+
for (const effect of this.state.effects) {
|
|
254
|
+
BATCH.handlers.add(effect);
|
|
255
|
+
}
|
|
256
|
+
for (const [, subscription] of this.state.subscriptions) {
|
|
257
|
+
subscription.callback(value);
|
|
206
258
|
}
|
|
207
|
-
this.effect.dirty = false;
|
|
208
259
|
}
|
|
260
|
+
this.effect.dirty = false;
|
|
209
261
|
});
|
|
210
262
|
}
|
|
211
263
|
/**
|
|
212
264
|
* @inheritdoc
|
|
213
265
|
*/
|
|
214
266
|
get() {
|
|
215
|
-
if (
|
|
216
|
-
this.state.computeds.add(
|
|
267
|
+
if (ACTIVE.computed != null && this !== ACTIVE.computed) {
|
|
268
|
+
this.state.computeds.add(ACTIVE.computed);
|
|
217
269
|
}
|
|
218
|
-
if (
|
|
219
|
-
this.state.effects.add(
|
|
270
|
+
if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) {
|
|
271
|
+
this.state.effects.add(ACTIVE.effect);
|
|
220
272
|
}
|
|
221
|
-
if (this.effect.dirty &&
|
|
273
|
+
if (this.effect.dirty && BATCH.depth === 0) {
|
|
222
274
|
runEffect(this.effect.instance);
|
|
223
275
|
}
|
|
224
276
|
return this.state.value;
|
|
@@ -236,12 +288,12 @@ function emitValue(state) {
|
|
|
236
288
|
computed.effect.dirty = true;
|
|
237
289
|
}
|
|
238
290
|
for (const effect of state.effects) {
|
|
239
|
-
|
|
291
|
+
BATCH.handlers.add(effect);
|
|
240
292
|
}
|
|
241
293
|
for (const [, subscription] of state.subscriptions) {
|
|
242
|
-
|
|
294
|
+
BATCH.handlers.add(subscription);
|
|
243
295
|
}
|
|
244
|
-
if (
|
|
296
|
+
if (BATCH.depth === 0) {
|
|
245
297
|
flushHandlers();
|
|
246
298
|
}
|
|
247
299
|
}
|
|
@@ -251,9 +303,9 @@ function equalArrays(state, first, second) {
|
|
|
251
303
|
return false;
|
|
252
304
|
}
|
|
253
305
|
let offset = 0;
|
|
254
|
-
if (length >=
|
|
255
|
-
offset = Math.round(length /
|
|
256
|
-
offset = offset >
|
|
306
|
+
if (length >= ARRAY_THRESHOLD) {
|
|
307
|
+
offset = Math.round(length / ARRAY_PEEK);
|
|
308
|
+
offset = offset > ARRAY_OFFSET ? ARRAY_OFFSET : offset;
|
|
257
309
|
for (let index = 0; index < offset; index += 1) {
|
|
258
310
|
if (!state.equal(first[index], second[index])) {
|
|
259
311
|
return false;
|
|
@@ -269,24 +321,29 @@ function equalArrays(state, first, second) {
|
|
|
269
321
|
return true;
|
|
270
322
|
}
|
|
271
323
|
function getValue(state) {
|
|
272
|
-
if (
|
|
273
|
-
state.computeds.add(
|
|
324
|
+
if (ACTIVE.computed != null) {
|
|
325
|
+
state.computeds.add(ACTIVE.computed);
|
|
274
326
|
}
|
|
275
|
-
if (
|
|
276
|
-
state.effects.add(
|
|
327
|
+
if (ACTIVE.effect != null) {
|
|
328
|
+
state.effects.add(ACTIVE.effect);
|
|
277
329
|
}
|
|
278
330
|
return state.value;
|
|
279
331
|
}
|
|
280
332
|
|
|
333
|
+
function emityProxyValues(state, mapped) {
|
|
334
|
+
const values = [...mapped.values()];
|
|
335
|
+
const { length } = values;
|
|
336
|
+
for (let index = 0; index < length; index += 1) {
|
|
337
|
+
values[index].effect.dirty = true;
|
|
338
|
+
}
|
|
339
|
+
emitValue(state);
|
|
340
|
+
}
|
|
281
341
|
function getReactiveValueInProxy(reactive, mapped, key, isArray) {
|
|
282
342
|
let item = mapped.get(key);
|
|
283
343
|
if (item == null) {
|
|
284
|
-
item = computed(() =>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
? value.at(key)
|
|
288
|
-
: value[key];
|
|
289
|
-
});
|
|
344
|
+
item = computed(() => isArray
|
|
345
|
+
? reactive.get().at(key)
|
|
346
|
+
: reactive.get()[key]);
|
|
290
347
|
mapped.set(key, item);
|
|
291
348
|
}
|
|
292
349
|
return item;
|
|
@@ -312,11 +369,12 @@ function setProxyValue(proxy, value) {
|
|
|
312
369
|
}
|
|
313
370
|
stopBatch();
|
|
314
371
|
}
|
|
315
|
-
function setValueInProxy(
|
|
372
|
+
function setValueInProxy(parameters) {
|
|
373
|
+
const { isArray, length, property, state, target, value } = parameters;
|
|
316
374
|
if (isArray) {
|
|
317
375
|
const isIndex = !Number.isNaN(Number(property));
|
|
318
|
-
const isLength = property ===
|
|
319
|
-
if (!isIndex
|
|
376
|
+
const isLength = property === PROPERTY_LENGTH;
|
|
377
|
+
if (!(isIndex || isLength)) {
|
|
320
378
|
return Reflect.set(target, property, value);
|
|
321
379
|
}
|
|
322
380
|
}
|
|
@@ -333,7 +391,7 @@ function setValueInProxy(target, property, value, state, isArray, length) {
|
|
|
333
391
|
|
|
334
392
|
class Signal extends Reactive {
|
|
335
393
|
constructor(value, options) {
|
|
336
|
-
super(
|
|
394
|
+
super(NAME_SIGNAL, value, options);
|
|
337
395
|
}
|
|
338
396
|
/**
|
|
339
397
|
* @inheritdoc
|
|
@@ -343,6 +401,7 @@ class Signal extends Reactive {
|
|
|
343
401
|
}
|
|
344
402
|
/**
|
|
345
403
|
* Set the value
|
|
404
|
+
* @param value New value
|
|
346
405
|
*/
|
|
347
406
|
set(value) {
|
|
348
407
|
if (!this.state.equal(this.state.value, value)) {
|
|
@@ -352,6 +411,7 @@ class Signal extends Reactive {
|
|
|
352
411
|
}
|
|
353
412
|
/**
|
|
354
413
|
* Update the value _(based on the current value)_
|
|
414
|
+
* @param callback Callback to update the value
|
|
355
415
|
*/
|
|
356
416
|
update(callback) {
|
|
357
417
|
this.set(callback(this.state.value));
|
|
@@ -359,6 +419,9 @@ class Signal extends Reactive {
|
|
|
359
419
|
}
|
|
360
420
|
/**
|
|
361
421
|
* Create a reactive value
|
|
422
|
+
* @param value Initial value
|
|
423
|
+
* @param options Optional reactivity options
|
|
424
|
+
* @returns Reactive value
|
|
362
425
|
*/
|
|
363
426
|
function signal(value, options) {
|
|
364
427
|
return new Signal(value, options);
|
|
@@ -384,11 +447,18 @@ class ReactiveArray extends Reactive {
|
|
|
384
447
|
}
|
|
385
448
|
}
|
|
386
449
|
constructor(value, options) {
|
|
387
|
-
super(
|
|
388
|
-
get: (target, property) =>
|
|
450
|
+
super(NAME_ARRAY, new Proxy(value, {
|
|
451
|
+
get: (target, property) => METHODS_UPDATE.has(property)
|
|
389
452
|
? updateArray(property, target, this.state, this.#size)
|
|
390
453
|
: Reflect.get(target, property),
|
|
391
|
-
set: (target, property, value) => setValueInProxy(
|
|
454
|
+
set: (target, property, value) => setValueInProxy({
|
|
455
|
+
property,
|
|
456
|
+
target,
|
|
457
|
+
value,
|
|
458
|
+
isArray: true,
|
|
459
|
+
state: this.state,
|
|
460
|
+
length: this.#size,
|
|
461
|
+
}),
|
|
392
462
|
}), options);
|
|
393
463
|
this.#size.set(value.length);
|
|
394
464
|
}
|
|
@@ -398,44 +468,56 @@ class ReactiveArray extends Reactive {
|
|
|
398
468
|
clear() {
|
|
399
469
|
this.length = 0;
|
|
400
470
|
}
|
|
401
|
-
get(first) {
|
|
402
|
-
if (typeof first === 'number') {
|
|
403
|
-
return getReactiveValueInProxy(this, this.#indiced, first, true).get();
|
|
404
|
-
}
|
|
405
|
-
if (first === 'length') {
|
|
406
|
-
return this.length;
|
|
407
|
-
}
|
|
408
|
-
return getValue(this.state);
|
|
409
|
-
}
|
|
410
471
|
/**
|
|
411
472
|
* Create a computed, filtered array
|
|
473
|
+
* @param callback Callback to evaluate each item
|
|
474
|
+
* @return Computed array of filtered items
|
|
412
475
|
*/
|
|
413
476
|
filter(callback) {
|
|
414
477
|
return computed(() => this.get().filter(callback));
|
|
415
478
|
}
|
|
479
|
+
get(first) {
|
|
480
|
+
if (typeof first === 'number') {
|
|
481
|
+
return getReactiveValueInProxy(this, this.#indiced, first, true).get();
|
|
482
|
+
}
|
|
483
|
+
return first === PROPERTY_LENGTH ? this.length : getValue(this.state);
|
|
484
|
+
}
|
|
416
485
|
/**
|
|
417
486
|
* Create a computed, mapped array
|
|
487
|
+
* @param callback Callback to transform each item
|
|
488
|
+
* @return Computed array of mapped items
|
|
418
489
|
*/
|
|
419
490
|
map(callback) {
|
|
420
491
|
return computed(() => this.get().map(callback));
|
|
421
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Notify dependents of changes
|
|
495
|
+
*
|
|
496
|
+
* _This bypasses equality checks and will immediately notify dependents.
|
|
497
|
+
* Use this only if you're modifying nested data that would be ignored by equality checks._
|
|
498
|
+
*/
|
|
499
|
+
notify() {
|
|
500
|
+
emityProxyValues(this.state, this.#indiced);
|
|
501
|
+
}
|
|
422
502
|
peek(value) {
|
|
423
|
-
if (value ===
|
|
503
|
+
if (value === 'length') {
|
|
424
504
|
return this.#size.peek();
|
|
425
505
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return [...this.state.value];
|
|
506
|
+
return typeof value === 'number'
|
|
507
|
+
? this.state.value.at(value)
|
|
508
|
+
: [...this.state.value];
|
|
430
509
|
}
|
|
431
510
|
/**
|
|
432
511
|
* Remove and return the last item of the array
|
|
512
|
+
* @returns Removed item, or `undefined` if the array is empty
|
|
433
513
|
*/
|
|
434
514
|
pop() {
|
|
435
515
|
return this.state.value.pop();
|
|
436
516
|
}
|
|
437
517
|
/**
|
|
438
518
|
* Add items to the end of the array
|
|
519
|
+
* @param items Items to add
|
|
520
|
+
* @returns New array length
|
|
439
521
|
*/
|
|
440
522
|
push(...items) {
|
|
441
523
|
return this.state.value.push(...items);
|
|
@@ -447,18 +529,23 @@ class ReactiveArray extends Reactive {
|
|
|
447
529
|
else if (first === 'length') {
|
|
448
530
|
this.length = second;
|
|
449
531
|
}
|
|
450
|
-
else if (typeof first === 'number') {
|
|
451
|
-
this.state.value
|
|
532
|
+
else if (typeof first === 'number' && !Number.isNaN(first)) {
|
|
533
|
+
setAtIndex(this.state.value, first, second);
|
|
452
534
|
}
|
|
453
535
|
}
|
|
454
536
|
/**
|
|
455
537
|
* Remove and return the first item of the array
|
|
538
|
+
* @returns Removed item, or `undefined` if the array is empty
|
|
456
539
|
*/
|
|
457
540
|
shift() {
|
|
458
541
|
return this.state.value.shift();
|
|
459
542
|
}
|
|
460
543
|
/**
|
|
461
544
|
* Remove and return items from the array _(and optionally add new items)_
|
|
545
|
+
* @param from Index to start removing items from
|
|
546
|
+
* @param to Index to stop removing items at _(defaults to the end of the array)_
|
|
547
|
+
* @param items Optional items to add
|
|
548
|
+
* @returns Removed items
|
|
462
549
|
*/
|
|
463
550
|
splice(from, to, ...items) {
|
|
464
551
|
return this.state.value.splice(from, to ?? this.state.value.length, ...items);
|
|
@@ -471,12 +558,15 @@ class ReactiveArray extends Reactive {
|
|
|
471
558
|
}
|
|
472
559
|
/**
|
|
473
560
|
* Add items to the beginning of the array
|
|
561
|
+
* @param items Items to add
|
|
562
|
+
* @returns New array length
|
|
474
563
|
*/
|
|
475
564
|
unshift(...items) {
|
|
476
565
|
return this.state.value.unshift(...items);
|
|
477
566
|
}
|
|
478
567
|
/**
|
|
479
568
|
* Update the value _(based on the current value)_
|
|
569
|
+
* @param callback Callback to update the value
|
|
480
570
|
*/
|
|
481
571
|
update(callback) {
|
|
482
572
|
const updated = callback(this.state.value);
|
|
@@ -487,12 +577,15 @@ class ReactiveArray extends Reactive {
|
|
|
487
577
|
}
|
|
488
578
|
/**
|
|
489
579
|
* Create a reactive array
|
|
580
|
+
* @param value Initial array of items
|
|
581
|
+
* @param options Optional reactivity options
|
|
582
|
+
* @returns Reactive array
|
|
490
583
|
*/
|
|
491
584
|
function array(value, options) {
|
|
492
585
|
return new ReactiveArray(Array.isArray(value) ? value : [], options);
|
|
493
586
|
}
|
|
494
587
|
function updateArray(type, array, state, length) {
|
|
495
|
-
const affectsLength =
|
|
588
|
+
const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
|
|
496
589
|
const previousArray = affectsLength ? [] : [...array];
|
|
497
590
|
const previousLength = array.length;
|
|
498
591
|
return (...args) => {
|
|
@@ -506,40 +599,34 @@ function updateArray(type, array, state, length) {
|
|
|
506
599
|
return result;
|
|
507
600
|
};
|
|
508
601
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const updateMethods = new Set([
|
|
516
|
-
...lengthAffectingMethods,
|
|
517
|
-
'copyWithin',
|
|
518
|
-
'fill',
|
|
519
|
-
'reverse',
|
|
520
|
-
'sort',
|
|
521
|
-
'splice',
|
|
522
|
-
]);
|
|
602
|
+
function setAtIndex(array, index, value) {
|
|
603
|
+
const actual = index < 0 ? array.length + index : index;
|
|
604
|
+
if (actual > -1) {
|
|
605
|
+
array[actual] = value;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
523
608
|
|
|
524
609
|
function isKey(value) {
|
|
525
|
-
|
|
610
|
+
return typeof value === "number" || typeof value === "string";
|
|
526
611
|
}
|
|
527
612
|
function isPlainObject(value) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
const prototype = Object.getPrototypeOf(value);
|
|
535
|
-
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
613
|
+
if (value === null || typeof value !== "object") return false;
|
|
614
|
+
if (Symbol.toStringTag in value || Symbol.iterator in value) return false;
|
|
615
|
+
const prototype = Object.getPrototypeOf(value);
|
|
616
|
+
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
536
617
|
}
|
|
537
618
|
|
|
538
619
|
class Store extends Reactive {
|
|
539
620
|
#keyed = new Map();
|
|
540
621
|
constructor(value, options) {
|
|
541
|
-
super(
|
|
542
|
-
set: (target, property, value) => setValueInProxy(
|
|
622
|
+
super(NAME_STORE, new Proxy(value, {
|
|
623
|
+
set: (target, property, value) => setValueInProxy({
|
|
624
|
+
target,
|
|
625
|
+
property,
|
|
626
|
+
value,
|
|
627
|
+
isArray: false,
|
|
628
|
+
state: this.state,
|
|
629
|
+
}),
|
|
543
630
|
}), options);
|
|
544
631
|
}
|
|
545
632
|
get(key) {
|
|
@@ -547,6 +634,15 @@ class Store extends Reactive {
|
|
|
547
634
|
? getReactiveValueInProxy(this, this.#keyed, key, false).get()
|
|
548
635
|
: getValue(this.state);
|
|
549
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* Notify dependents of changes
|
|
639
|
+
*
|
|
640
|
+
* _This bypasses equality checks and will immediately notify dependents.
|
|
641
|
+
* Use this only if you're modifying nested data that would be ignored by equality checks._
|
|
642
|
+
*/
|
|
643
|
+
notify() {
|
|
644
|
+
emityProxyValues(this.state, this.#keyed);
|
|
645
|
+
}
|
|
550
646
|
peek(key) {
|
|
551
647
|
return isKey(key) ? this.state.value[key] : { ...this.state.value };
|
|
552
648
|
}
|
|
@@ -566,6 +662,7 @@ class Store extends Reactive {
|
|
|
566
662
|
}
|
|
567
663
|
/**
|
|
568
664
|
* Update the value _(based on the current value)_
|
|
665
|
+
* @param callback Callback to update the value
|
|
569
666
|
*/
|
|
570
667
|
update(callback) {
|
|
571
668
|
const updated = callback({ ...this.state.value });
|
|
@@ -576,6 +673,9 @@ class Store extends Reactive {
|
|
|
576
673
|
}
|
|
577
674
|
/**
|
|
578
675
|
* Create a reactive store
|
|
676
|
+
* @param value Initial object value
|
|
677
|
+
* @param options Optional reactivity options
|
|
678
|
+
* @returns Reactive store
|
|
579
679
|
*/
|
|
580
680
|
function store(value, options) {
|
|
581
681
|
return new Store((isPlainObject(value) ? value : {}), options);
|