@oscarpalmer/mora 0.29.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 -18
- 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 +22 -5
- 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 +448 -421
- 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 -49
- package/dist/value/reactive.mjs +10 -54
- 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 +7 -8
- package/src/batch.ts +22 -9
- package/src/constants.ts +3 -4
- package/src/effect.ts +35 -40
- package/src/helpers/is.ts +11 -10
- package/src/helpers/proxy.ts +23 -18
- package/src/helpers/value.ts +39 -5
- 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 -77
- package/src/value/signal.ts +34 -71
- package/src/value/store.ts +84 -177
- package/types/constants.d.ts +1 -1
- package/types/index.d.ts +1 -1
- package/types/value/array.d.ts +1 -1
- package/types/value/computed.d.ts +3 -0
- package/types/value/signal.d.ts +1 -1
- package/types/value/store.d.ts +1 -1
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
|
-
}
|
package/src/value/reactive.ts
CHANGED
|
@@ -1,85 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
|
|
3
|
-
import {subscribe, unsubscribe} from '../subscription';
|
|
1
|
+
import type {Reactive, ReactiveOptions, ReactiveState} from '../models';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
declare protected readonly $mora: string;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Internal state of the reactive value
|
|
15
|
-
*
|
|
16
|
-
* @internal
|
|
17
|
-
*/
|
|
18
|
-
protected readonly state: ReactiveState<Value, Equal> = {
|
|
3
|
+
export function reactive<Value, Equal = Value>(
|
|
4
|
+
value: Value,
|
|
5
|
+
options?: ReactiveOptions<Equal>,
|
|
6
|
+
): [Reactive<Value>, ReactiveState<Value, Equal>] {
|
|
7
|
+
const state: ReactiveState<Value, Equal> = {
|
|
19
8
|
computeds: new Set(),
|
|
20
9
|
effects: new Set(),
|
|
21
|
-
equal:
|
|
10
|
+
equal:
|
|
11
|
+
typeof options === 'object' && typeof options?.equal === 'function'
|
|
12
|
+
? options.equal
|
|
13
|
+
: Object.is,
|
|
22
14
|
subscriptions: new Map(),
|
|
23
|
-
value:
|
|
15
|
+
value: value as Value,
|
|
24
16
|
};
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.state.equal = options.equal;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
Object.defineProperty(this, NAME_MORA, {
|
|
34
|
-
value: name,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get the value
|
|
40
|
-
* @return Current value
|
|
41
|
-
*/
|
|
42
|
-
abstract get(): Value;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get the value _(without reactivity)_
|
|
46
|
-
* @return Current value
|
|
47
|
-
*/
|
|
48
|
-
peek(): Value {
|
|
49
|
-
return this.state.value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Subscribe to changes
|
|
54
|
-
* @param callback Callback for changes
|
|
55
|
-
* @return Unsubscribe callback
|
|
56
|
-
*/
|
|
57
|
-
subscribe(callback: (value: Value) => void): Unsubscribe {
|
|
58
|
-
return subscribe(this.state, callback);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* JSON representation of the value
|
|
63
|
-
* @return JSON value
|
|
64
|
-
*/
|
|
65
|
-
toJSON(): Value {
|
|
66
|
-
return this.get();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* String representation of the value
|
|
71
|
-
* @return Value as string
|
|
72
|
-
*/
|
|
73
|
-
toString(): string {
|
|
74
|
-
// oxlint-disable-next-line @oscarpalmer/atoms/string.getString: base toString is expected; allow custom toString in options?
|
|
75
|
-
return String(this.get());
|
|
76
|
-
}
|
|
18
|
+
const instance = {
|
|
19
|
+
toJSON: () => state.value,
|
|
20
|
+
toString: () => String(state.value),
|
|
21
|
+
};
|
|
77
22
|
|
|
78
|
-
|
|
79
|
-
* Unsubscribe from changes
|
|
80
|
-
* @param callback Callback to unsubscribe
|
|
81
|
-
*/
|
|
82
|
-
unsubscribe(callback: (value: Value) => void): void {
|
|
83
|
-
unsubscribe(this.state, callback);
|
|
84
|
-
}
|
|
23
|
+
return [instance, state];
|
|
85
24
|
}
|
package/src/value/signal.ts
CHANGED
|
@@ -1,36 +1,8 @@
|
|
|
1
|
-
import {NAME_SIGNAL} from '../constants';
|
|
2
|
-
import {emitValue,
|
|
3
|
-
import type {ReactiveOptions, ReactiveState} from '../models';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export class Signal<Value> extends Reactive<Value> {
|
|
7
|
-
constructor(value: Value, options?: ReactiveOptions<Value>) {
|
|
8
|
-
super(NAME_SIGNAL, value, options);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @inheritdoc
|
|
13
|
-
*/
|
|
14
|
-
get(): Value {
|
|
15
|
-
return getValue(this.state);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Set the value
|
|
20
|
-
* @param value New value
|
|
21
|
-
*/
|
|
22
|
-
set(value: Value | (() => Value | Promise<Value>) | Promise<Value>): void {
|
|
23
|
-
setValue<Value>(this.state, value);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Update the value _(based on the current value)_
|
|
28
|
-
* @param callback Callback to update the value
|
|
29
|
-
*/
|
|
30
|
-
update(callback: (value: Value) => Value): void {
|
|
31
|
-
this.set(callback(this.state.value));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
import {NAME_MORA, NAME_SIGNAL} from '../constants';
|
|
2
|
+
import {emitValue, getSimpleValue, handleSimpleValue} from '../helpers/value';
|
|
3
|
+
import type {ReactiveOptions, ReactiveState, Signal} from '../models';
|
|
4
|
+
import {subscribe} from '../subscription';
|
|
5
|
+
import {reactive} from './reactive';
|
|
34
6
|
|
|
35
7
|
function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
|
|
36
8
|
if (!state.equal(state.value, value)) {
|
|
@@ -40,41 +12,9 @@ function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): vo
|
|
|
40
12
|
}
|
|
41
13
|
}
|
|
42
14
|
|
|
43
|
-
function setValue<Value>(
|
|
44
|
-
state: ReactiveState<Value, Value>,
|
|
45
|
-
value: Value | (() => Value | Promise<Value>) | Promise<Value>,
|
|
46
|
-
): void {
|
|
47
|
-
try {
|
|
48
|
-
let actual = value;
|
|
49
|
-
|
|
50
|
-
if (typeof value === 'function') {
|
|
51
|
-
actual = (value as () => Value | Promise<Value>)() as Value | Promise<Value>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (actual instanceof Promise) {
|
|
55
|
-
state.promise = actual;
|
|
56
|
-
|
|
57
|
-
void actual
|
|
58
|
-
.then(value => {
|
|
59
|
-
if (actual === state.promise) {
|
|
60
|
-
state.promise = undefined;
|
|
61
|
-
|
|
62
|
-
setAndEmit(state, value);
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
.catch(() => {
|
|
66
|
-
if (actual === state.promise) {
|
|
67
|
-
state.promise = undefined;
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
} else {
|
|
71
|
-
setAndEmit(state, actual as Value);
|
|
72
|
-
}
|
|
73
|
-
} catch {}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
15
|
/**
|
|
77
16
|
* Create a reactive value from a function result
|
|
17
|
+
*
|
|
78
18
|
* @param value Initial value
|
|
79
19
|
* @param options Reactivity options
|
|
80
20
|
* @returns Reactive value
|
|
@@ -86,6 +26,7 @@ export function signal<Value>(
|
|
|
86
26
|
|
|
87
27
|
/**
|
|
88
28
|
* Create a reactive value from a promise
|
|
29
|
+
*
|
|
89
30
|
* @param value Initial value
|
|
90
31
|
* @param options Reactivity options
|
|
91
32
|
* @returns Reactive value
|
|
@@ -97,6 +38,7 @@ export function signal<Value>(
|
|
|
97
38
|
|
|
98
39
|
/**
|
|
99
40
|
* Create a reactive value
|
|
41
|
+
*
|
|
100
42
|
* @param value Initial value
|
|
101
43
|
* @param options Reactivity options
|
|
102
44
|
* @returns Reactive value
|
|
@@ -107,9 +49,30 @@ export function signal<Value>(
|
|
|
107
49
|
value: Value | (() => Value | Promise<Value>) | Promise<Value>,
|
|
108
50
|
options?: ReactiveOptions<Value>,
|
|
109
51
|
): Signal<Value> {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
instance
|
|
113
|
-
|
|
114
|
-
|
|
52
|
+
const [rx, state] = reactive<Value>(undefined as unknown as Value, options);
|
|
53
|
+
|
|
54
|
+
const instance = {
|
|
55
|
+
...rx,
|
|
56
|
+
get: () => getSimpleValue(state),
|
|
57
|
+
peek: () => state.value,
|
|
58
|
+
set: (value: Value | (() => Value | Promise<Value>) | Promise<Value>) => {
|
|
59
|
+
handleSimpleValue(state, value, setAndEmit);
|
|
60
|
+
},
|
|
61
|
+
update: (callback: (value: Value) => Value) => {
|
|
62
|
+
handleSimpleValue(state, callback(state.value), setAndEmit);
|
|
63
|
+
},
|
|
64
|
+
subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
|
|
65
|
+
unsubscribe: (callback: (value: Value) => void) => {
|
|
66
|
+
state.subscriptions.delete(callback);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
Object.defineProperty(instance, NAME_MORA, {
|
|
71
|
+
enumerable: false,
|
|
72
|
+
value: NAME_SIGNAL,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
handleSimpleValue(state, value, setAndEmit);
|
|
76
|
+
|
|
77
|
+
return Object.freeze(instance) as Signal<Value>;
|
|
115
78
|
}
|