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