@praxisjs/decorators 1.6.1 → 2.0.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/CHANGELOG.md +46 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +8 -15
- package/dist/component/component.js.map +1 -1
- package/dist/component/lazy.d.ts +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +44 -36
- package/dist/component/lazy.js.map +1 -1
- package/dist/create-class-decorator.d.ts +27 -5
- package/dist/create-class-decorator.d.ts.map +1 -1
- package/dist/create-class-decorator.js +53 -18
- package/dist/create-class-decorator.js.map +1 -1
- package/dist/create-field-decorator.d.ts +42 -3
- package/dist/create-field-decorator.d.ts.map +1 -1
- package/dist/create-field-decorator.js +63 -27
- package/dist/create-field-decorator.js.map +1 -1
- package/dist/create-getter-decorator.d.ts +81 -16
- package/dist/create-getter-decorator.d.ts.map +1 -1
- package/dist/create-getter-decorator.js +127 -15
- package/dist/create-getter-decorator.js.map +1 -1
- package/dist/create-lifecycle-method-decorator.d.ts +21 -3
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
- package/dist/create-lifecycle-method-decorator.js +25 -18
- package/dist/create-lifecycle-method-decorator.js.map +1 -1
- package/dist/create-method-decorator.d.ts +43 -3
- package/dist/create-method-decorator.d.ts.map +1 -1
- package/dist/create-method-decorator.js +49 -8
- package/dist/create-method-decorator.js.map +1 -1
- package/dist/events/command.d.ts +2 -1
- package/dist/events/command.d.ts.map +1 -1
- package/dist/events/command.js +10 -8
- package/dist/events/command.js.map +1 -1
- package/dist/events/emit.d.ts +2 -2
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +18 -24
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +2 -1
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +12 -14
- package/dist/events/on-command.js.map +1 -1
- package/dist/field.d.ts +51 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +82 -0
- package/dist/field.js.map +1 -0
- package/dist/functions/bind.d.ts +2 -1
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -4
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +2 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +25 -23
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/log.d.ts +2 -1
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +38 -40
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +9 -1
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +44 -30
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +2 -1
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +10 -14
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +2 -1
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +21 -23
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +2 -1
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +12 -14
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/until.d.ts +2 -1
- package/dist/functions/until.d.ts.map +1 -1
- package/dist/functions/until.js +13 -14
- package/dist/functions/until.js.map +1 -1
- package/dist/functions/watch.d.ts +1 -1
- package/dist/functions/watch.d.ts.map +1 -1
- package/dist/functions/watch.js +36 -34
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -1
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +19 -21
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +24 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +55 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/properties/compose.d.ts +3 -2
- package/dist/properties/compose.d.ts.map +1 -1
- package/dist/properties/compose.js +33 -19
- package/dist/properties/compose.js.map +1 -1
- package/dist/properties/computed.d.ts +12 -10
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +26 -21
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/deep-state.d.ts +2 -1
- package/dist/properties/deep-state.d.ts.map +1 -1
- package/dist/properties/deep-state.js +66 -49
- package/dist/properties/deep-state.js.map +1 -1
- package/dist/properties/function-prop.d.ts +2 -1
- package/dist/properties/function-prop.d.ts.map +1 -1
- package/dist/properties/function-prop.js +20 -22
- package/dist/properties/function-prop.js.map +1 -1
- package/dist/properties/history.d.ts +2 -2
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +34 -31
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +1 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +1 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +2 -1
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +30 -19
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +2 -1
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +26 -22
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/ref.d.ts +2 -1
- package/dist/properties/ref.d.ts.map +1 -1
- package/dist/properties/ref.js +11 -8
- package/dist/properties/ref.js.map +1 -1
- package/dist/properties/resource.d.ts.map +1 -1
- package/dist/properties/resource.js +21 -16
- package/dist/properties/resource.js.map +1 -1
- package/dist/properties/selector.d.ts +12 -0
- package/dist/properties/selector.d.ts.map +1 -0
- package/dist/properties/selector.js +25 -0
- package/dist/properties/selector.js.map +1 -0
- package/dist/properties/slot.d.ts +2 -1
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +22 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +27 -16
- package/dist/properties/state.js.map +1 -1
- package/dist/properties/synced.d.ts +2 -1
- package/dist/properties/synced.d.ts.map +1 -1
- package/dist/properties/synced.js +27 -19
- package/dist/properties/synced.js.map +1 -1
- package/dist/shared-method.d.ts +14 -0
- package/dist/shared-method.d.ts.map +1 -0
- package/dist/shared-method.js +87 -0
- package/dist/shared-method.js.map +1 -0
- package/package.json +14 -5
- package/src/component/component.ts +8 -27
- package/src/component/lazy.ts +51 -41
- package/src/create-class-decorator.ts +90 -31
- package/src/create-field-decorator.ts +120 -43
- package/src/create-getter-decorator.ts +263 -66
- package/src/create-lifecycle-method-decorator.ts +50 -28
- package/src/create-method-decorator.ts +119 -13
- package/src/events/command.ts +11 -9
- package/src/events/emit.ts +16 -22
- package/src/events/on-command.ts +17 -18
- package/src/field.ts +116 -0
- package/src/functions/bind.ts +4 -4
- package/src/functions/debounce.ts +25 -23
- package/src/functions/index.ts +1 -1
- package/src/functions/log.ts +44 -46
- package/src/functions/memo.ts +54 -28
- package/src/functions/once.ts +10 -13
- package/src/functions/retry.ts +19 -21
- package/src/functions/throttle.ts +12 -13
- package/src/functions/until.ts +14 -14
- package/src/functions/watch.ts +37 -36
- package/src/functions/when.ts +18 -20
- package/src/index.ts +13 -5
- package/src/lifecycle.ts +74 -0
- package/src/properties/compose.ts +33 -20
- package/src/properties/computed.ts +35 -45
- package/src/properties/deep-state.ts +68 -53
- package/src/properties/function-prop.ts +25 -27
- package/src/properties/history.ts +34 -33
- package/src/properties/index.ts +1 -0
- package/src/properties/persisted.ts +41 -27
- package/src/properties/prop.ts +30 -22
- package/src/properties/ref.ts +12 -10
- package/src/properties/resource.ts +22 -17
- package/src/properties/selector.ts +45 -0
- package/src/properties/slot.ts +28 -22
- package/src/properties/state.ts +32 -16
- package/src/properties/synced.ts +31 -19
- package/src/shared-method.ts +101 -0
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -147
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/compose.test.d.ts +0 -2
- package/dist/__tests__/compose.test.d.ts.map +0 -1
- package/dist/__tests__/compose.test.js +0 -164
- package/dist/__tests__/compose.test.js.map +0 -1
- package/dist/__tests__/deep-state.test.d.ts +0 -2
- package/dist/__tests__/deep-state.test.d.ts.map +0 -1
- package/dist/__tests__/deep-state.test.js +0 -150
- package/dist/__tests__/deep-state.test.js.map +0 -1
- package/dist/__tests__/events.test.d.ts +0 -2
- package/dist/__tests__/events.test.d.ts.map +0 -1
- package/dist/__tests__/events.test.js +0 -295
- package/dist/__tests__/events.test.js.map +0 -1
- package/dist/__tests__/functions.test.d.ts +0 -2
- package/dist/__tests__/functions.test.d.ts.map +0 -1
- package/dist/__tests__/functions.test.js +0 -618
- package/dist/__tests__/functions.test.js.map +0 -1
- package/dist/__tests__/lazy.test.d.ts +0 -2
- package/dist/__tests__/lazy.test.d.ts.map +0 -1
- package/dist/__tests__/lazy.test.js +0 -227
- package/dist/__tests__/lazy.test.js.map +0 -1
- package/dist/__tests__/memo.test.d.ts +0 -2
- package/dist/__tests__/memo.test.d.ts.map +0 -1
- package/dist/__tests__/memo.test.js +0 -168
- package/dist/__tests__/memo.test.js.map +0 -1
- package/dist/__tests__/properties-browser.test.d.ts +0 -2
- package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
- package/dist/__tests__/properties-browser.test.js +0 -178
- package/dist/__tests__/properties-browser.test.js.map +0 -1
- package/dist/__tests__/properties.test.d.ts +0 -2
- package/dist/__tests__/properties.test.d.ts.map +0 -1
- package/dist/__tests__/properties.test.js +0 -1226
- package/dist/__tests__/properties.test.js.map +0 -1
- package/dist/__tests__/ref.test.d.ts +0 -2
- package/dist/__tests__/ref.test.d.ts.map +0 -1
- package/dist/__tests__/ref.test.js +0 -106
- package/dist/__tests__/ref.test.js.map +0 -1
- package/dist/__tests__/resource.test.d.ts +0 -2
- package/dist/__tests__/resource.test.d.ts.map +0 -1
- package/dist/__tests__/resource.test.js +0 -217
- package/dist/__tests__/resource.test.js.map +0 -1
- package/dist/__tests__/slot.test.d.ts +0 -2
- package/dist/__tests__/slot.test.d.ts.map +0 -1
- package/dist/__tests__/slot.test.js +0 -147
- package/dist/__tests__/slot.test.js.map +0 -1
- package/dist/__tests__/synced.test.d.ts +0 -2
- package/dist/__tests__/synced.test.d.ts.map +0 -1
- package/dist/__tests__/synced.test.js +0 -161
- package/dist/__tests__/synced.test.js.map +0 -1
- package/dist/__tests__/until.test.d.ts +0 -2
- package/dist/__tests__/until.test.d.ts.map +0 -1
- package/dist/__tests__/until.test.js +0 -88
- package/dist/__tests__/until.test.js.map +0 -1
- package/dist/create-accessor-decorator.d.ts +0 -14
- package/dist/create-accessor-decorator.d.ts.map +0 -1
- package/dist/create-accessor-decorator.js +0 -27
- package/dist/create-accessor-decorator.js.map +0 -1
- package/src/__tests__/component.test.ts +0 -167
- package/src/__tests__/compose.test.ts +0 -202
- package/src/__tests__/deep-state.test.ts +0 -185
- package/src/__tests__/events.test.ts +0 -366
- package/src/__tests__/functions.test.ts +0 -803
- package/src/__tests__/lazy.test.ts +0 -296
- package/src/__tests__/memo.test.ts +0 -208
- package/src/__tests__/properties-browser.test.ts +0 -234
- package/src/__tests__/properties.test.ts +0 -1526
- package/src/__tests__/ref.test.ts +0 -131
- package/src/__tests__/resource.test.ts +0 -246
- package/src/__tests__/slot.test.ts +0 -179
- package/src/__tests__/synced.test.ts +0 -193
- package/src/__tests__/until.test.ts +0 -113
- package/src/create-accessor-decorator.ts +0 -45
- package/tsconfig.json +0 -9
package/src/functions/watch.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
|
-
import {
|
|
2
|
+
import { batch, createEffect } from "@praxisjs/core/internal";
|
|
3
3
|
import type { Computed } from "@praxisjs/shared";
|
|
4
4
|
import { isComputed } from "@praxisjs/shared/internal";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { lifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
|
|
7
7
|
|
|
8
8
|
type BaseComponentKeys = keyof StatefulComponent;
|
|
9
9
|
|
|
@@ -58,43 +58,44 @@ export function Watch<
|
|
|
58
58
|
>(...propNames: ValidateKeys<T, Keys>) {
|
|
59
59
|
const props = propNames as unknown as string[];
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (props.length === 1) {
|
|
62
|
+
const key = props[0];
|
|
63
|
+
return lifecycleMethodDecorator<T>((instance, method) => {
|
|
63
64
|
const inst = instance as T & Record<string, unknown>;
|
|
65
|
+
let oldVal = readValue(inst, key);
|
|
66
|
+
return createEffect(() => {
|
|
67
|
+
const newVal = readValue(inst, key);
|
|
68
|
+
if (!Object.is(newVal, oldVal)) {
|
|
69
|
+
method.call(inst, newVal, oldVal);
|
|
70
|
+
oldVal = newVal;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
latestVals = newVals;
|
|
85
|
-
if (!scheduled) {
|
|
86
|
-
scheduled = true;
|
|
87
|
-
const prevVals = oldVals;
|
|
88
|
-
queueMicrotask(() => {
|
|
89
|
-
scheduled = false;
|
|
90
|
-
oldVals = latestVals;
|
|
91
|
-
batch(() => {
|
|
92
|
-
callback(latestVals, prevVals);
|
|
93
|
-
});
|
|
76
|
+
return lifecycleMethodDecorator<T>((instance, method) => {
|
|
77
|
+
const inst = instance as T & Record<string, unknown>;
|
|
78
|
+
const read = () =>
|
|
79
|
+
Object.fromEntries(props.map((p) => [p, readValue(inst, p)]));
|
|
80
|
+
let oldVals = read();
|
|
81
|
+
let latestVals = oldVals;
|
|
82
|
+
let scheduled = false;
|
|
83
|
+
return createEffect(() => {
|
|
84
|
+
const newVals = read();
|
|
85
|
+
if (props.some((p) => !Object.is(newVals[p], oldVals[p]))) {
|
|
86
|
+
latestVals = newVals;
|
|
87
|
+
if (!scheduled) {
|
|
88
|
+
scheduled = true;
|
|
89
|
+
const prevVals = oldVals;
|
|
90
|
+
queueMicrotask(() => {
|
|
91
|
+
scheduled = false;
|
|
92
|
+
oldVals = latestVals;
|
|
93
|
+
batch(() => {
|
|
94
|
+
method.call(inst, latestVals, prevVals);
|
|
94
95
|
});
|
|
95
|
-
}
|
|
96
|
+
});
|
|
96
97
|
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
99
100
|
});
|
|
100
101
|
}
|
package/src/functions/when.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEffect } from "@praxisjs/core/internal";
|
|
2
2
|
import type { Computed, Signal } from "@praxisjs/shared";
|
|
3
3
|
import { isComputed, isSignal } from "@praxisjs/shared/internal";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { lifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
|
|
6
6
|
|
|
7
7
|
/** Read the current value of a property, unwrapping Signal or Computed if needed. */
|
|
8
8
|
function readProp(instance: Record<string, unknown>, key: string): unknown {
|
|
@@ -16,26 +16,24 @@ export function When<T = unknown>(
|
|
|
16
16
|
propName: string,
|
|
17
17
|
condition?: (value: NonNullable<T>) => boolean,
|
|
18
18
|
) {
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let fired = false;
|
|
19
|
+
return lifecycleMethodDecorator<object>((instance, method) => {
|
|
20
|
+
const inst = instance as Record<string, unknown>;
|
|
21
|
+
let fired = false;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
return createEffect(() => {
|
|
24
|
+
if (fired) return;
|
|
25
|
+
// Reading via readProp subscribes this effect to the underlying signal,
|
|
26
|
+
// whether the property is a raw Signal/Computed or an @State getter.
|
|
27
|
+
const val = readProp(inst, propName) as T;
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const meetsCondition = condition
|
|
30
|
+
? val != null && condition(val)
|
|
31
|
+
: val != null && Boolean(val);
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
33
|
+
if (meetsCondition) {
|
|
34
|
+
fired = true;
|
|
35
|
+
method.call(inst, val as NonNullable<T>);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
40
38
|
});
|
|
41
39
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Component, Lazy } from "./component";
|
|
2
2
|
export {
|
|
3
3
|
Memo,
|
|
4
|
+
type MemoOptions,
|
|
4
5
|
Bind,
|
|
5
6
|
Log,
|
|
6
7
|
Once,
|
|
@@ -33,12 +34,17 @@ export {
|
|
|
33
34
|
DeepState,
|
|
34
35
|
Ref,
|
|
35
36
|
createRef,
|
|
37
|
+
Selector,
|
|
38
|
+
type SelectorOptions,
|
|
36
39
|
} from "./properties";
|
|
37
40
|
export { Command, createCommand, Emit, OnCommand } from "./events";
|
|
41
|
+
export { addClaimableInitializer, assertFieldContext, type FieldDecorator } from "./field";
|
|
38
42
|
export {
|
|
39
43
|
createFieldDecorator,
|
|
40
44
|
type FieldBehavior,
|
|
41
45
|
type FieldBinding,
|
|
46
|
+
type FieldSetup,
|
|
47
|
+
type SharedFieldBehavior,
|
|
42
48
|
} from "./create-field-decorator";
|
|
43
49
|
export { reactiveHostType, type ReactiveHost } from "./reactive-host";
|
|
44
50
|
export {
|
|
@@ -49,6 +55,9 @@ export {
|
|
|
49
55
|
export {
|
|
50
56
|
createMethodDecorator,
|
|
51
57
|
type MethodBehavior,
|
|
58
|
+
type MethodDecorator,
|
|
59
|
+
type MethodSetup,
|
|
60
|
+
type SharedMethodBehavior,
|
|
52
61
|
} from "./create-method-decorator";
|
|
53
62
|
export {
|
|
54
63
|
createLifecycleMethodDecorator,
|
|
@@ -57,14 +66,13 @@ export {
|
|
|
57
66
|
export {
|
|
58
67
|
createGetterDecorator,
|
|
59
68
|
type GetterBehavior,
|
|
69
|
+
type GetterDecorator,
|
|
70
|
+
type GetterSetup,
|
|
71
|
+
type SharedGetterBehavior,
|
|
60
72
|
createWritableGetterDecorator,
|
|
61
73
|
type WritableGetterBehavior,
|
|
62
74
|
type WritableGetterBinding,
|
|
75
|
+
type SharedWritableGetterBehavior,
|
|
63
76
|
createGetterObserverDecorator,
|
|
64
77
|
type GetterObserverBehavior,
|
|
65
78
|
} from "./create-getter-decorator";
|
|
66
|
-
export {
|
|
67
|
-
createAccessorDecorator,
|
|
68
|
-
type AccessorBehavior,
|
|
69
|
-
type AccessorBinding,
|
|
70
|
-
} from "./create-accessor-decorator";
|
package/src/lifecycle.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { disposeEffect, type EffectNode } from "@praxisjs/core/internal";
|
|
2
|
+
|
|
3
|
+
export type LifecycleHook = (instance: object) => void;
|
|
4
|
+
|
|
5
|
+
export interface LifecycleHost {
|
|
6
|
+
onMount?: ((this: object) => void) | undefined;
|
|
7
|
+
onUnmount?: ((this: object) => void) | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** What a mount registration leaves to undo on unmount: a stop function or an effect to dispose. */
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
12
|
+
export type LifecycleCleanup = (() => void) | EffectNode | undefined | void;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runs `mount` / `unmount` (called with the instance) after the instance's current `onMount` /
|
|
16
|
+
* `onUnmount`, and so after the hooks added before them. Calling `instance.onMount()` by hand runs
|
|
17
|
+
* them too.
|
|
18
|
+
*
|
|
19
|
+
* The previous hooks are called with `this` rather than bound to the instance: two closures per
|
|
20
|
+
* hook pair are all this allocates.
|
|
21
|
+
*/
|
|
22
|
+
export function addLifecycleHooks(
|
|
23
|
+
object: object,
|
|
24
|
+
mount: LifecycleHook | undefined,
|
|
25
|
+
unmount: LifecycleHook | undefined,
|
|
26
|
+
): void {
|
|
27
|
+
const instance = object as LifecycleHost;
|
|
28
|
+
const prevMount = instance.onMount;
|
|
29
|
+
const prevUnmount = instance.onUnmount;
|
|
30
|
+
instance.onMount = function (this: object) {
|
|
31
|
+
prevMount?.call(this);
|
|
32
|
+
mount?.(this);
|
|
33
|
+
};
|
|
34
|
+
instance.onUnmount = function (this: object) {
|
|
35
|
+
prevUnmount?.call(this);
|
|
36
|
+
unmount?.(this);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stop(cleanup: LifecycleCleanup): void {
|
|
41
|
+
if (typeof cleanup === "function") cleanup();
|
|
42
|
+
else if (cleanup != null) disposeEffect(cleanup);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Calls `start(instance, arg)` on every mount and undoes what it returned on unmount. Like
|
|
47
|
+
* `addLifecycleHooks`, it chains onto the instance's hooks with two closures and no bound
|
|
48
|
+
* functions; the cleanup lives in their shared scope.
|
|
49
|
+
*/
|
|
50
|
+
export function addMountRegistration<T extends object, A>(
|
|
51
|
+
object: T,
|
|
52
|
+
start: (instance: T, arg: A) => LifecycleCleanup,
|
|
53
|
+
arg: A,
|
|
54
|
+
): void {
|
|
55
|
+
const instance = object as LifecycleHost;
|
|
56
|
+
const prevMount = instance.onMount;
|
|
57
|
+
const prevUnmount = instance.onUnmount;
|
|
58
|
+
let cleanup: LifecycleCleanup;
|
|
59
|
+
instance.onMount = function (this: object) {
|
|
60
|
+
prevMount?.call(this);
|
|
61
|
+
// A second onMount() without an onUnmount() in between would otherwise leak the first
|
|
62
|
+
// registration, which nothing could stop anymore.
|
|
63
|
+
const previous = cleanup;
|
|
64
|
+
cleanup = undefined;
|
|
65
|
+
stop(previous);
|
|
66
|
+
cleanup = start(this as T, arg);
|
|
67
|
+
};
|
|
68
|
+
instance.onUnmount = function (this: object) {
|
|
69
|
+
prevUnmount?.call(this);
|
|
70
|
+
const previous = cleanup;
|
|
71
|
+
cleanup = undefined;
|
|
72
|
+
stop(previous);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Composable } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { type FieldDecorator, type FieldHost, sharedField } from "../field";
|
|
4
|
+
import { addLifecycleHooks } from "../lifecycle";
|
|
4
5
|
|
|
5
6
|
interface ComposeFactory { __isComposeFactory: true; resolve: (instance: object) => unknown }
|
|
6
7
|
|
|
@@ -43,24 +44,36 @@ export function Compose(
|
|
|
43
44
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
45
|
ComposableClass: new (...args: any[]) => Composable,
|
|
45
46
|
...ctorArgs: unknown[]
|
|
46
|
-
) {
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
): FieldDecorator {
|
|
48
|
+
return sharedField((name) => {
|
|
49
|
+
const composableSlot = Symbol(`praxis.composable.${name}`);
|
|
50
|
+
const viewSlot = Symbol(`praxis.compose.${name}`);
|
|
51
|
+
const composableOf = (instance: object) => (instance as FieldHost)[composableSlot] as Composable;
|
|
52
|
+
const mount = (instance: object) => { composableOf(instance).onMount?.(); };
|
|
53
|
+
const unmount = (instance: object) => { composableOf(instance).onUnmount?.(); };
|
|
54
|
+
return {
|
|
55
|
+
descriptor: {
|
|
56
|
+
get(this: FieldHost) {
|
|
57
|
+
return this[viewSlot];
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
init(instance) {
|
|
61
|
+
const resolvedArgs = ctorArgs.map((arg) => {
|
|
62
|
+
if (isFactory(arg)) return arg.resolve(instance);
|
|
63
|
+
if (typeof arg !== "string") return arg;
|
|
64
|
+
const prop = instance[arg];
|
|
65
|
+
return prop !== undefined ? prop : arg;
|
|
66
|
+
});
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const composable = new ComposableClass(...resolvedArgs);
|
|
69
|
+
instance[composableSlot] = composable;
|
|
70
|
+
instance[viewSlot] = buildView(composable.setup());
|
|
71
|
+
const hasMount = typeof composable.onMount === "function";
|
|
72
|
+
const hasUnmount = typeof composable.onUnmount === "function";
|
|
73
|
+
if (hasMount || hasUnmount) {
|
|
74
|
+
addLifecycleHooks(instance, hasMount ? mount : undefined, hasUnmount ? unmount : undefined);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
65
78
|
});
|
|
66
79
|
}
|
|
@@ -1,55 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ComputedNode, readComputed } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { createWritableGetterDecorator } from "../create-getter-decorator";
|
|
3
|
+
import { createGetterDecorator, type GetterDecorator } from "../create-getter-decorator";
|
|
5
4
|
|
|
6
|
-
export interface ComputedOptions
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export interface ComputedOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Handles writes to the property. A `set` accessor declared next to the getter in the class
|
|
8
|
+
* does the same, and TypeScript sees it.
|
|
9
|
+
*/
|
|
10
|
+
set?(this: object, value: unknown): void;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
// TypeScript sees the accessor as writable; no cast needed when assigning.
|
|
13
|
-
// The `Owner` generic is inferred from the `this` type of the provided functions.
|
|
14
|
-
export function Computed<T, Owner extends object = object>(options: {
|
|
15
|
-
get: (this: Owner) => T;
|
|
16
|
-
set: (this: Owner, value: T) => void;
|
|
17
|
-
}): <This extends object>(
|
|
18
|
-
target: ClassAccessorDecoratorTarget<This, T>,
|
|
19
|
-
context: ClassAccessorDecoratorContext<This, T>,
|
|
20
|
-
) => ClassAccessorDecoratorResult<This, T>;
|
|
13
|
+
type Host = Record<symbol, ComputedNode | undefined>;
|
|
21
14
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
target: (this: This) => R,
|
|
27
|
-
context: ClassGetterDecoratorContext<This, R>,
|
|
28
|
-
) => (this: This) => R;
|
|
29
|
-
|
|
30
|
-
export function Computed<T = unknown>(options?: ComputedOptions<T>): unknown {
|
|
31
|
-
const optionsGet = options?.get as ((this: object) => unknown) | undefined;
|
|
32
|
-
const optionsSet = options?.set as ((this: object, value: unknown) => void) | undefined;
|
|
33
|
-
|
|
34
|
-
if (optionsGet) {
|
|
35
|
-
return createAccessorDecorator({
|
|
36
|
-
bind(instance, _name, _initialValue) {
|
|
37
|
-
const c = computed(() => optionsGet.call(instance));
|
|
38
|
-
return {
|
|
39
|
-
get: c,
|
|
40
|
-
set: optionsSet ? (value) => { optionsSet.call(instance, value); } : undefined,
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
15
|
+
// Kept out of the getter: a closure created inside it would make every read allocate a context.
|
|
16
|
+
function computedFor<T>(instance: T, getter: (this: T) => unknown): ComputedNode {
|
|
17
|
+
return new ComputedNode(() => getter.call(instance));
|
|
18
|
+
}
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Caches a getter as a reactive computed. A `set` accessor declared next to the getter makes the
|
|
22
|
+
* property writable.
|
|
23
|
+
*/
|
|
24
|
+
export function Computed(options?: ComputedOptions): GetterDecorator {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- installed on the class, which calls it with the instance as `this`
|
|
26
|
+
const set = options?.set;
|
|
27
|
+
return createGetterDecorator<Host>({
|
|
28
|
+
label: "@Computed()",
|
|
29
|
+
setup(original, context) {
|
|
30
|
+
const node = Symbol(`praxis.computed.${String(context.name)}`);
|
|
49
31
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
// Shared by every instance; the computed itself is only created the first time it is read,
|
|
33
|
+
// so a getter that a component never reads costs nothing beyond its slot.
|
|
34
|
+
get(this: Host) {
|
|
35
|
+
return readComputed((this[node] ??= computedFor(this, original)));
|
|
36
|
+
},
|
|
37
|
+
set,
|
|
38
|
+
init(instance) {
|
|
39
|
+
// The slot is created up front so every instance keeps the same shape.
|
|
40
|
+
instance[node] = undefined;
|
|
41
|
+
},
|
|
52
42
|
};
|
|
53
43
|
},
|
|
54
|
-
});
|
|
44
|
+
}) as GetterDecorator;
|
|
55
45
|
}
|
|
@@ -1,62 +1,77 @@
|
|
|
1
|
-
import { markStateDirty,
|
|
1
|
+
import { markStateDirty, readSignal, SignalNode, writeSignal } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { type FieldDecorator, type FieldHost, sharedField } from "../field";
|
|
4
4
|
|
|
5
5
|
import type { ReactiveHost } from "../reactive-host";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
// One per decorated field per instance. It is also the handler of every proxy it hands out, so
|
|
8
|
+
// proxying a nested object allocates only the Proxy itself.
|
|
9
|
+
class DeepStateCell implements ProxyHandler<object> {
|
|
10
|
+
readonly version = new SignalNode(0);
|
|
11
|
+
// One proxy per raw object: repeated reads return the same reference (so `a.b === a.b` and
|
|
12
|
+
// identity comparisons in effects hold) and don't allocate a new Proxy on every access.
|
|
13
|
+
private proxies: WeakMap<object, object> | undefined = undefined;
|
|
14
|
+
current: unknown;
|
|
15
|
+
|
|
16
|
+
constructor(private readonly host: object, value: unknown) {
|
|
17
|
+
this.current = this.wrap(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
wrap(value: unknown): unknown {
|
|
21
|
+
if (value === null || typeof value !== "object") return value;
|
|
22
|
+
const proxies = (this.proxies ??= new WeakMap());
|
|
23
|
+
let proxy = proxies.get(value);
|
|
24
|
+
if (proxy === undefined) {
|
|
25
|
+
proxy = new Proxy(value, this);
|
|
26
|
+
proxies.set(value, proxy);
|
|
27
|
+
}
|
|
28
|
+
return proxy;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
changed(): void {
|
|
32
|
+
markStateDirty(this.host);
|
|
33
|
+
writeSignal(this.version, this.version.value + 1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get(obj: object, key: string | symbol, receiver: unknown): unknown {
|
|
37
|
+
return this.wrap(Reflect.get(obj, key, receiver));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set(obj: object, key: string | symbol, value: unknown): boolean {
|
|
41
|
+
const had = Object.hasOwn(obj, key);
|
|
42
|
+
const prev: unknown = Reflect.get(obj, key);
|
|
43
|
+
const result = Reflect.set(obj, key, value);
|
|
44
|
+
if (!had || !Object.is(prev, value)) this.changed();
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteProperty(obj: object, key: string | symbol): boolean {
|
|
49
|
+
const had = Object.hasOwn(obj, key);
|
|
50
|
+
const result = Reflect.deleteProperty(obj, key);
|
|
51
|
+
if (had) this.changed();
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
27
54
|
}
|
|
28
55
|
|
|
29
|
-
export function DeepState() {
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
descriptor: {
|
|
46
|
-
get() {
|
|
47
|
-
version();
|
|
48
|
-
return proxy;
|
|
49
|
-
},
|
|
50
|
-
set(value: unknown) {
|
|
51
|
-
current = value;
|
|
52
|
-
proxy =
|
|
53
|
-
value !== null && typeof value === "object"
|
|
54
|
-
? deepProxy(value, notify)
|
|
55
|
-
: value;
|
|
56
|
-
notify();
|
|
57
|
-
},
|
|
56
|
+
export function DeepState(): FieldDecorator<ReactiveHost> {
|
|
57
|
+
return sharedField<ReactiveHost>((name) => {
|
|
58
|
+
const slot = Symbol(`praxis.deepState.${name}`);
|
|
59
|
+
return {
|
|
60
|
+
descriptor: {
|
|
61
|
+
get(this: FieldHost) {
|
|
62
|
+
const cell = this[slot] as DeepStateCell;
|
|
63
|
+
readSignal(cell.version);
|
|
64
|
+
return cell.current;
|
|
65
|
+
},
|
|
66
|
+
set(this: FieldHost, value: unknown) {
|
|
67
|
+
const cell = this[slot] as DeepStateCell;
|
|
68
|
+
cell.current = cell.wrap(value);
|
|
69
|
+
cell.changed();
|
|
58
70
|
},
|
|
59
|
-
}
|
|
60
|
-
|
|
71
|
+
},
|
|
72
|
+
init(instance, initialValue) {
|
|
73
|
+
instance[slot] = new DeepStateCell(instance, initialValue);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
61
76
|
});
|
|
62
77
|
}
|
|
@@ -4,34 +4,32 @@ import {
|
|
|
4
4
|
setComponentDefault,
|
|
5
5
|
} from "@praxisjs/core/internal";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type FieldDecorator, type FieldHost, sharedField } from "../field";
|
|
8
8
|
|
|
9
|
-
export function FunctionProp() {
|
|
10
|
-
return
|
|
11
|
-
|
|
9
|
+
export function FunctionProp(): FieldDecorator {
|
|
10
|
+
return sharedField((name) => ({
|
|
11
|
+
descriptor: {
|
|
12
|
+
get(this: FieldHost) {
|
|
13
|
+
const fromParent = getComponentRawProp(this, name);
|
|
14
|
+
if (fromParent !== undefined) {
|
|
15
|
+
if (
|
|
16
|
+
process.env.NODE_ENV !== "production" &&
|
|
17
|
+
typeof fromParent !== "function"
|
|
18
|
+
) {
|
|
19
|
+
console.warn(
|
|
20
|
+
`[FunctionProp] "${name}" received a non-function value (${typeof fromParent}). @FunctionProp() expects a function.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return fromParent;
|
|
24
|
+
}
|
|
25
|
+
return getComponentDefault(this, name);
|
|
26
|
+
},
|
|
27
|
+
set(this: FieldHost, value: unknown) {
|
|
28
|
+
setComponentDefault(this, name, value);
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
init(instance, initialValue) {
|
|
12
32
|
setComponentDefault(instance, name, initialValue);
|
|
13
|
-
return {
|
|
14
|
-
descriptor: {
|
|
15
|
-
get() {
|
|
16
|
-
const fromParent = getComponentRawProp(instance, name);
|
|
17
|
-
if (fromParent !== undefined) {
|
|
18
|
-
if (
|
|
19
|
-
process.env.NODE_ENV !== "production" &&
|
|
20
|
-
typeof fromParent !== "function"
|
|
21
|
-
) {
|
|
22
|
-
console.warn(
|
|
23
|
-
`[FunctionProp] "${name}" received a non-function value (${typeof fromParent}). @FunctionProp() expects a function.`,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return fromParent;
|
|
27
|
-
}
|
|
28
|
-
return getComponentDefault(instance, name);
|
|
29
|
-
},
|
|
30
|
-
set(value: unknown) {
|
|
31
|
-
setComponentDefault(instance, name, value);
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
33
|
},
|
|
36
|
-
});
|
|
34
|
+
}));
|
|
37
35
|
}
|