@praxisjs/decorators 0.4.2 → 0.5.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 +43 -0
- package/dist/__tests__/component.test.d.ts +2 -0
- package/dist/__tests__/component.test.d.ts.map +1 -0
- package/dist/__tests__/component.test.js +146 -0
- package/dist/__tests__/component.test.js.map +1 -0
- package/dist/__tests__/compose.test.d.ts +2 -0
- package/dist/__tests__/compose.test.d.ts.map +1 -0
- package/dist/__tests__/compose.test.js +171 -0
- package/dist/__tests__/compose.test.js.map +1 -0
- package/dist/__tests__/events.test.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.js +216 -0
- package/dist/__tests__/events.test.js.map +1 -0
- package/dist/__tests__/functions.test.js +283 -31
- package/dist/__tests__/functions.test.js.map +1 -1
- package/dist/__tests__/lazy.test.d.ts +2 -0
- package/dist/__tests__/lazy.test.d.ts.map +1 -0
- package/dist/__tests__/lazy.test.js +163 -0
- package/dist/__tests__/lazy.test.js.map +1 -0
- package/dist/__tests__/memo.test.js +71 -22
- package/dist/__tests__/memo.test.js.map +1 -1
- package/dist/__tests__/properties.test.js +191 -1
- package/dist/__tests__/properties.test.js.map +1 -1
- package/dist/__tests__/resource.test.d.ts +2 -0
- package/dist/__tests__/resource.test.d.ts.map +1 -0
- package/dist/__tests__/resource.test.js +103 -0
- package/dist/__tests__/resource.test.js.map +1 -0
- package/dist/__tests__/slot.test.d.ts +2 -0
- package/dist/__tests__/slot.test.d.ts.map +1 -0
- package/dist/__tests__/slot.test.js +147 -0
- package/dist/__tests__/slot.test.js.map +1 -0
- package/dist/__tests__/virtual.test.d.ts +2 -0
- package/dist/__tests__/virtual.test.d.ts.map +1 -0
- package/dist/__tests__/virtual.test.js +242 -0
- package/dist/__tests__/virtual.test.js.map +1 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +15 -10
- 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 +29 -27
- package/dist/component/lazy.js.map +1 -1
- package/dist/component/virtual.d.ts +1 -1
- package/dist/component/virtual.d.ts.map +1 -1
- package/dist/component/virtual.js +46 -50
- package/dist/component/virtual.js.map +1 -1
- package/dist/create-class-decorator.d.ts +16 -0
- package/dist/create-class-decorator.d.ts.map +1 -0
- package/dist/create-class-decorator.js +35 -0
- package/dist/create-class-decorator.js.map +1 -0
- package/dist/create-field-decorator.d.ts +14 -0
- package/dist/create-field-decorator.d.ts.map +1 -0
- package/dist/create-field-decorator.js +37 -0
- package/dist/create-field-decorator.js.map +1 -0
- package/dist/create-getter-decorator.d.ts +18 -0
- package/dist/create-getter-decorator.d.ts.map +1 -0
- package/dist/create-getter-decorator.js +15 -0
- package/dist/create-getter-decorator.js.map +1 -0
- package/dist/create-lifecycle-method-decorator.d.ts +6 -0
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
- package/dist/create-lifecycle-method-decorator.js +22 -0
- package/dist/create-lifecycle-method-decorator.js.map +1 -0
- package/dist/create-method-decorator.d.ts +6 -0
- package/dist/create-method-decorator.d.ts.map +1 -0
- package/dist/create-method-decorator.js +14 -0
- package/dist/create-method-decorator.js.map +1 -0
- package/dist/events/emit.d.ts +1 -1
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +24 -29
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +1 -2
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +15 -32
- package/dist/events/on-command.js.map +1 -1
- package/dist/functions/bind.d.ts +1 -2
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -11
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +1 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +12 -20
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/log.d.ts +1 -2
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +40 -38
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +1 -2
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +15 -27
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +1 -2
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +14 -12
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +1 -2
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +23 -20
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +1 -2
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +9 -16
- package/dist/functions/throttle.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 +25 -31
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -2
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +10 -26
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/properties/compose.d.ts +3 -0
- package/dist/properties/compose.d.ts.map +1 -0
- package/dist/properties/compose.js +40 -0
- package/dist/properties/compose.js.map +1 -0
- package/dist/properties/computed.d.ts +1 -2
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +10 -9
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +37 -32
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +2 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +2 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +1 -2
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +13 -29
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +1 -2
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +21 -25
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/resource.d.ts +14 -0
- package/dist/properties/resource.d.ts.map +1 -0
- package/dist/properties/resource.js +27 -0
- package/dist/properties/resource.js.map +1 -0
- package/dist/properties/slot.d.ts +1 -2
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +15 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts +1 -2
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +13 -18
- package/dist/properties/state.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/component.test.ts +166 -0
- package/src/__tests__/compose.test.ts +202 -0
- package/src/__tests__/events.test.ts +268 -0
- package/src/__tests__/functions.test.ts +368 -30
- package/src/__tests__/lazy.test.ts +218 -0
- package/src/__tests__/memo.test.ts +89 -22
- package/src/__tests__/properties.test.ts +249 -1
- package/src/__tests__/resource.test.ts +113 -0
- package/src/__tests__/slot.test.ts +179 -0
- package/src/__tests__/virtual.test.ts +260 -0
- package/src/component/component.ts +27 -12
- package/src/component/lazy.ts +33 -35
- package/src/component/virtual.tsx +62 -75
- package/src/create-class-decorator.ts +61 -0
- package/src/create-field-decorator.ts +66 -0
- package/src/create-getter-decorator.ts +52 -0
- package/src/create-lifecycle-method-decorator.ts +38 -0
- package/src/create-method-decorator.ts +26 -0
- package/src/events/emit.ts +22 -33
- package/src/events/on-command.ts +22 -47
- package/src/functions/bind.ts +4 -15
- package/src/functions/debounce.ts +11 -26
- package/src/functions/log.ts +40 -43
- package/src/functions/memo.ts +15 -45
- package/src/functions/once.ts +13 -16
- package/src/functions/retry.ts +21 -22
- package/src/functions/throttle.ts +9 -23
- package/src/functions/watch.ts +27 -34
- package/src/functions/when.ts +11 -36
- package/src/index.ts +28 -0
- package/src/properties/compose.ts +53 -0
- package/src/properties/computed.ts +11 -13
- package/src/properties/history.ts +42 -41
- package/src/properties/index.ts +2 -0
- package/src/properties/persisted.ts +17 -58
- package/src/properties/prop.ts +21 -31
- package/src/properties/resource.ts +34 -0
- package/src/properties/slot.ts +17 -30
- package/src/properties/state.ts +14 -24
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resource, } from "@praxisjs/core/internal";
|
|
2
|
+
import { createFieldDecorator } from "../create-field-decorator";
|
|
3
|
+
/**
|
|
4
|
+
* Binds an async resource to the field. The field holds a Resource<T> object
|
|
5
|
+
* with reactive data, pending, error, and status signals, plus refetch/cancel/mutate methods.
|
|
6
|
+
*
|
|
7
|
+
* class MyComponent extends StatefulComponent {
|
|
8
|
+
* @Resource(() => api.getUsers())
|
|
9
|
+
* users!: ResourceInstance<User[]>;
|
|
10
|
+
* // access: this.users.data(), this.users.pending(), this.users.refetch()
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
export function Resource(fetcher, options = {}) {
|
|
14
|
+
return createFieldDecorator({
|
|
15
|
+
bind(_instance, _name, _initialValue) {
|
|
16
|
+
const r = resource(fetcher, options);
|
|
17
|
+
return {
|
|
18
|
+
descriptor: {
|
|
19
|
+
get() { return r; },
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
21
|
+
set() { },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/properties/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,GAGT,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,oBAAoB,EAAqB,MAAM,2BAA2B,CAAC;AAIpF;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAI,OAAyB,EAAE,UAA8B,EAAE;IACrF,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa;YAClC,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACrC,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,KAA0B,OAAO,CAAC,CAAC,CAAC,CAAC;oBACxC,gEAAgE;oBAChE,GAAG,KAAU,CAAC;iBACf;aACF,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
1
|
export declare function initSlots(instance: object, children: unknown): void;
|
|
3
2
|
export declare function getSlot(instance: object, name: string): unknown[];
|
|
4
|
-
export declare function Slot(name?: string): (_value: undefined, context: ClassFieldDecoratorContext<StatefulComponent>) => void;
|
|
3
|
+
export declare function Slot(name?: string): (_value: undefined, context: ClassFieldDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
5
4
|
//# sourceMappingURL=slot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../src/properties/slot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../src/properties/slot.ts"],"names":[],"mappings":"AA+BA,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAEnE;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAEjE;AAED,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,gHAkBjC"}
|
package/dist/properties/slot.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createFieldDecorator } from "../create-field-decorator";
|
|
1
2
|
const slotsMap = new WeakMap();
|
|
2
3
|
function resolveSlots(children) {
|
|
3
4
|
const slots = new Map();
|
|
@@ -9,7 +10,6 @@ function resolveSlots(children) {
|
|
|
9
10
|
? children.flat(Infinity)
|
|
10
11
|
: [children];
|
|
11
12
|
for (const child of arr) {
|
|
12
|
-
// Named slots via `slot` attribute on DOM elements
|
|
13
13
|
if (child instanceof Element) {
|
|
14
14
|
const slotName = child.getAttribute("slot");
|
|
15
15
|
if (slotName) {
|
|
@@ -32,25 +32,20 @@ export function getSlot(instance, name) {
|
|
|
32
32
|
return slotsMap.get(instance)?.get(name) ?? [];
|
|
33
33
|
}
|
|
34
34
|
export function Slot(name) {
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
return createFieldDecorator({
|
|
36
|
+
bind(instance, propName, _initialValue) {
|
|
37
|
+
const slotName = name ?? (propName === "default" ? "default" : propName);
|
|
38
|
+
return {
|
|
39
|
+
descriptor: {
|
|
40
|
+
get: () => getSlot(instance, slotName),
|
|
41
|
+
set: (_value) => {
|
|
42
|
+
if (process.env.NODE_ENV !== "production") {
|
|
43
|
+
console.warn(`[Slot] "${propName}" is a slot and cannot be assigned directly. Slots are filled by the parent component.`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
44
46
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
enumerable: true,
|
|
51
|
-
configurable: true,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
};
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
55
50
|
}
|
|
56
51
|
//# sourceMappingURL=slot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/properties/slot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/properties/slot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAkC,CAAC;AAE/D,SAAS,YAAY,CAAC,QAAiB;IACrC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC3C,MAAM,WAAW,GAAc,EAAE,CAAC;IAClC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAElC,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAEnC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QACjC,CAAC,CAAE,QAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC;QACxC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAEf,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACxB,IAAI,KAAK,YAAY,OAAO,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;gBACjD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC/C,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,KAAK,IAAI,IAAI;YAAE,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,QAAiB;IAC3D,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,QAAgB,EAAE,IAAY;IACpD,OAAO,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAa;IAChC,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa;YACpC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACzE,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC;oBACtC,GAAG,EAAE,CAAC,MAAe,EAAE,EAAE;wBACvB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;4BAC1C,OAAO,CAAC,IAAI,CACV,WAAW,QAAQ,wFAAwF,CAC5G,CAAC;wBACJ,CAAC;oBACH,CAAC;iBACF;aACF,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function State(): (_value: undefined, context: ClassFieldDecoratorContext<StatefulComponent>) => void;
|
|
1
|
+
export declare function State(): (_value: undefined, context: ClassFieldDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
3
2
|
//# sourceMappingURL=state.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/properties/state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/properties/state.ts"],"names":[],"mappings":"AAIA,wBAAgB,KAAK,iHAepB"}
|
package/dist/properties/state.js
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { signal } from "@praxisjs/core/internal";
|
|
2
|
+
import { createFieldDecorator } from "../create-field-decorator";
|
|
2
3
|
export function State() {
|
|
3
|
-
return
|
|
4
|
-
|
|
5
|
-
const instance = this;
|
|
6
|
-
const name = context.name;
|
|
7
|
-
const initialValue = instance[name];
|
|
8
|
-
Reflect.deleteProperty(instance, name);
|
|
4
|
+
return createFieldDecorator({
|
|
5
|
+
bind(instance, _name, initialValue) {
|
|
9
6
|
const sig = signal(initialValue);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
return {
|
|
8
|
+
descriptor: {
|
|
9
|
+
get: () => sig(),
|
|
10
|
+
set: (value) => {
|
|
11
|
+
instance._stateDirty = true;
|
|
12
|
+
sig.set(value);
|
|
13
|
+
},
|
|
13
14
|
},
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
enumerable: true,
|
|
19
|
-
configurable: true,
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
};
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
});
|
|
23
18
|
}
|
|
24
19
|
//# sourceMappingURL=state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/properties/state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/properties/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,UAAU,KAAK;IACnB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,YAAY;YAChC,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;YACjC,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE;oBAChB,GAAG,EAAE,CAAC,KAAc,EAAE,EAAE;wBACtB,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC;wBAC5B,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;iBACF;aACF,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/decorators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"typescript": "^5.9.3"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@praxisjs/core": "0.
|
|
18
|
+
"@praxisjs/core": "1.0.0",
|
|
19
19
|
"@praxisjs/shared": "0.2.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { StatefulComponent, StatelessComponent } from "@praxisjs/core";
|
|
4
|
+
|
|
5
|
+
import { Component } from "../component/component";
|
|
6
|
+
|
|
7
|
+
// ── Component decorator ───────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
describe("Component decorator", () => {
|
|
10
|
+
it("sets __isComponent = true on the enhanced class", () => {
|
|
11
|
+
class MyComp extends StatefulComponent {
|
|
12
|
+
render() { return null; }
|
|
13
|
+
}
|
|
14
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
15
|
+
expect(Enhanced.__isComponent).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("preserves the original class name", () => {
|
|
19
|
+
class MyButton extends StatefulComponent {
|
|
20
|
+
render() { return null; }
|
|
21
|
+
}
|
|
22
|
+
const Enhanced = Component()(MyButton, {} as ClassDecoratorContext);
|
|
23
|
+
expect(Enhanced.name).toBe("MyButton");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("marks __isStateless = false for StatefulComponent subclasses", () => {
|
|
27
|
+
class MyStateful extends StatefulComponent {
|
|
28
|
+
render() { return null; }
|
|
29
|
+
}
|
|
30
|
+
const Enhanced = Component()(MyStateful, {} as ClassDecoratorContext);
|
|
31
|
+
expect(Enhanced.__isStateless).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("marks __isStateless = true for StatelessComponent subclasses", () => {
|
|
35
|
+
class MyStateless extends StatelessComponent {
|
|
36
|
+
render() { return null; }
|
|
37
|
+
}
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const Enhanced = Component()(MyStateless as any, {} as ClassDecoratorContext);
|
|
40
|
+
expect(Enhanced.__isStateless).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("enhanced class can still be instantiated", () => {
|
|
44
|
+
class Counter extends StatefulComponent {
|
|
45
|
+
render() { return null; }
|
|
46
|
+
}
|
|
47
|
+
const Enhanced = Component()(Counter, {} as ClassDecoratorContext);
|
|
48
|
+
const instance = new Enhanced();
|
|
49
|
+
expect(instance).toBeInstanceOf(Counter);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns a subclass of the original constructor", () => {
|
|
53
|
+
class Base extends StatefulComponent {
|
|
54
|
+
render() { return null; }
|
|
55
|
+
}
|
|
56
|
+
const Enhanced = Component()(Base, {} as ClassDecoratorContext);
|
|
57
|
+
expect(Enhanced.prototype).toBeInstanceOf(Base);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── Happy paths ──────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
it("forwards constructor args — props reach the base constructor", () => {
|
|
63
|
+
class WithProps extends StatefulComponent {
|
|
64
|
+
render() { return null; }
|
|
65
|
+
}
|
|
66
|
+
const Enhanced = Component()(WithProps, {} as ClassDecoratorContext);
|
|
67
|
+
const instance = new Enhanced({ greeting: "hello" });
|
|
68
|
+
expect(instance.props.greeting).toBe("hello");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("instance methods from the original class remain accessible", () => {
|
|
72
|
+
class Greeter extends StatefulComponent {
|
|
73
|
+
greet() { return "hello"; }
|
|
74
|
+
render() { return null; }
|
|
75
|
+
}
|
|
76
|
+
const Enhanced = Component()(Greeter, {} as ClassDecoratorContext);
|
|
77
|
+
const instance = new Enhanced();
|
|
78
|
+
expect((instance as Greeter).greet()).toBe("hello");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("static properties of the original class are preserved on Enhanced", () => {
|
|
82
|
+
class WithStatic extends StatefulComponent {
|
|
83
|
+
static readonly version = "1.0";
|
|
84
|
+
render() { return null; }
|
|
85
|
+
}
|
|
86
|
+
const Enhanced = Component()(WithStatic, {} as ClassDecoratorContext);
|
|
87
|
+
expect((Enhanced as typeof WithStatic).version).toBe("1.0");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── Edge cases ───────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
it("applying Component() twice preserves name and flags", () => {
|
|
93
|
+
class MyComp extends StatefulComponent {
|
|
94
|
+
render() { return null; }
|
|
95
|
+
}
|
|
96
|
+
const E1 = Component()(MyComp, {} as ClassDecoratorContext);
|
|
97
|
+
const E2 = Component()(E1, {} as ClassDecoratorContext);
|
|
98
|
+
expect(E2.__isComponent).toBe(true);
|
|
99
|
+
expect(E2.__isStateless).toBe(false);
|
|
100
|
+
expect(E2.name).toBe("MyComp"); // name survives double-wrap
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("deep StatelessComponent inheritance — __isStateless is still true", () => {
|
|
104
|
+
class BaseStateless extends StatelessComponent {
|
|
105
|
+
render() { return null; }
|
|
106
|
+
}
|
|
107
|
+
class DeepChild extends BaseStateless {
|
|
108
|
+
render() { return null; }
|
|
109
|
+
}
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
const Enhanced = Component()(DeepChild as any, {} as ClassDecoratorContext);
|
|
112
|
+
expect(Enhanced.__isStateless).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("anonymous class gets an empty name", () => {
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
const Enhanced = Component()(
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
class extends StatefulComponent { render() { return null; } } as any,
|
|
120
|
+
{} as ClassDecoratorContext,
|
|
121
|
+
);
|
|
122
|
+
expect(Enhanced.name).toBe("");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("__isComponent and __isStateless are static — not own properties of instances", () => {
|
|
126
|
+
class MyComp extends StatefulComponent {
|
|
127
|
+
render() { return null; }
|
|
128
|
+
}
|
|
129
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
130
|
+
const instance = new Enhanced();
|
|
131
|
+
// Static fields live on the class, not on instances
|
|
132
|
+
expect(Object.prototype.hasOwnProperty.call(instance, "__isComponent")).toBe(false);
|
|
133
|
+
expect(Object.prototype.hasOwnProperty.call(instance, "__isStateless")).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("subclassing Enhanced inherits __isComponent and __isStateless", () => {
|
|
137
|
+
class MyComp extends StatefulComponent {
|
|
138
|
+
render() { return null; }
|
|
139
|
+
}
|
|
140
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
141
|
+
// `class Sub extends Enhanced` sets Object.getPrototypeOf(Sub) === Enhanced,
|
|
142
|
+
// making static fields visible through the chain. Simulate that here to
|
|
143
|
+
// avoid TS2510 (conflicting constructor return types in the intersection type).
|
|
144
|
+
const Sub = Object.create(Enhanced) as typeof Enhanced;
|
|
145
|
+
expect(Sub.__isComponent).toBe(true);
|
|
146
|
+
expect(Sub.__isStateless).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("Enhanced is a different reference from the original constructor", () => {
|
|
150
|
+
class MyComp extends StatefulComponent {
|
|
151
|
+
render() { return null; }
|
|
152
|
+
}
|
|
153
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
154
|
+
expect(Enhanced).not.toBe(MyComp);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("render() falls through to originalRender when enhancement has no render override", () => {
|
|
158
|
+
class MyComp extends StatefulComponent {
|
|
159
|
+
render() { return null; }
|
|
160
|
+
}
|
|
161
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
162
|
+
const instance = new Enhanced();
|
|
163
|
+
// ComponentBehavior.create() returns {} (no render), so line 48 is hit
|
|
164
|
+
expect((instance as StatefulComponent).render()).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
4
|
+
import { signal } from "@praxisjs/core/internal";
|
|
5
|
+
import type { Composable } from "@praxisjs/core";
|
|
6
|
+
|
|
7
|
+
import { Compose } from "../properties/compose";
|
|
8
|
+
|
|
9
|
+
function fieldCtx(name: string) {
|
|
10
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
11
|
+
return {
|
|
12
|
+
ctx: {
|
|
13
|
+
name,
|
|
14
|
+
kind: "field" as const,
|
|
15
|
+
addInitializer(fn: (this: unknown) => void) {
|
|
16
|
+
initializers.push(fn);
|
|
17
|
+
},
|
|
18
|
+
} as ClassFieldDecoratorContext,
|
|
19
|
+
run(instance: unknown) {
|
|
20
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TestComponent extends StatefulComponent {
|
|
26
|
+
render() { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Simple composable returning plain values
|
|
30
|
+
class CounterComposable implements Composable {
|
|
31
|
+
count = 0;
|
|
32
|
+
setup() {
|
|
33
|
+
return { count: this.count, label: "counter" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Composable returning a signal (reactive property)
|
|
38
|
+
class ReactiveComposable implements Composable {
|
|
39
|
+
_count = signal(0);
|
|
40
|
+
setup() {
|
|
41
|
+
return { count: this._count };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Composable with lifecycle hooks
|
|
46
|
+
class LifecycleComposable implements Composable {
|
|
47
|
+
onMount = vi.fn();
|
|
48
|
+
onUnmount = vi.fn();
|
|
49
|
+
setup() {
|
|
50
|
+
return { active: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Composable that uses constructor args
|
|
55
|
+
class ArgsComposable implements Composable {
|
|
56
|
+
constructor(private multiplier: number) {}
|
|
57
|
+
setup() {
|
|
58
|
+
return { multiplier: this.multiplier };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Composable returning a plain function (non-reactive) in its view
|
|
63
|
+
class PlainFnComposable {
|
|
64
|
+
setup() {
|
|
65
|
+
return { handler: () => 42 };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("@Compose decorator", () => {
|
|
70
|
+
it("creates a view getter on the field", () => {
|
|
71
|
+
const { ctx, run } = fieldCtx("counter");
|
|
72
|
+
Compose(CounterComposable)(undefined, ctx);
|
|
73
|
+
|
|
74
|
+
const instance = new TestComponent();
|
|
75
|
+
run(instance);
|
|
76
|
+
|
|
77
|
+
const view = (instance as unknown as Record<string, unknown>).counter as Record<string, unknown>;
|
|
78
|
+
expect(view).toBeDefined();
|
|
79
|
+
expect(view.count).toBe(0);
|
|
80
|
+
expect(view.label).toBe("counter");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("reactive signal values are resolved via getter", () => {
|
|
84
|
+
const { ctx, run } = fieldCtx("reactive");
|
|
85
|
+
Compose(ReactiveComposable)(undefined, ctx);
|
|
86
|
+
|
|
87
|
+
const instance = new TestComponent();
|
|
88
|
+
run(instance);
|
|
89
|
+
|
|
90
|
+
const view = (instance as unknown as Record<string, unknown>).reactive as Record<string, unknown>;
|
|
91
|
+
expect(view.count).toBe(0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("calls onMount when component mounts", () => {
|
|
95
|
+
const { ctx, run } = fieldCtx("lc");
|
|
96
|
+
Compose(LifecycleComposable)(undefined, ctx);
|
|
97
|
+
|
|
98
|
+
const instance = new TestComponent();
|
|
99
|
+
run(instance);
|
|
100
|
+
|
|
101
|
+
const view = (instance as unknown as Record<string, unknown>).lc as Record<string, unknown>;
|
|
102
|
+
expect(view).toBeDefined();
|
|
103
|
+
|
|
104
|
+
// Trigger onMount
|
|
105
|
+
instance.onMount!();
|
|
106
|
+
const lc = new LifecycleComposable();
|
|
107
|
+
// The composable's onMount would have been called
|
|
108
|
+
// We verify the instance has an onMount wiring
|
|
109
|
+
expect(typeof instance.onMount).toBe("function");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("calls onUnmount when component unmounts", () => {
|
|
113
|
+
const { ctx, run } = fieldCtx("lc2");
|
|
114
|
+
Compose(LifecycleComposable)(undefined, ctx);
|
|
115
|
+
|
|
116
|
+
const instance = new TestComponent();
|
|
117
|
+
run(instance);
|
|
118
|
+
expect(typeof instance.onUnmount).toBe("function");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("invoking instance.onUnmount() executes the composable's onUnmount", () => {
|
|
122
|
+
const unmountSpy = vi.fn();
|
|
123
|
+
|
|
124
|
+
class SpyComposable {
|
|
125
|
+
onUnmount = unmountSpy;
|
|
126
|
+
setup() { return {}; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { ctx, run } = fieldCtx("spyComp");
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
+
Compose(SpyComposable as any)(undefined, ctx);
|
|
132
|
+
|
|
133
|
+
const instance = new TestComponent();
|
|
134
|
+
run(instance);
|
|
135
|
+
|
|
136
|
+
instance.onUnmount!();
|
|
137
|
+
expect(unmountSpy).toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("passes constructor args to the composable", () => {
|
|
141
|
+
const { ctx, run } = fieldCtx("args");
|
|
142
|
+
Compose(ArgsComposable, 5)(undefined, ctx);
|
|
143
|
+
|
|
144
|
+
const instance = new TestComponent();
|
|
145
|
+
run(instance);
|
|
146
|
+
|
|
147
|
+
const view = (instance as unknown as Record<string, unknown>).args as Record<string, unknown>;
|
|
148
|
+
expect(view.multiplier).toBe(5);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("resolves string constructor args from instance properties", () => {
|
|
152
|
+
const { ctx, run } = fieldCtx("resolved");
|
|
153
|
+
Compose(ArgsComposable, "myMultiplier")(undefined, ctx);
|
|
154
|
+
|
|
155
|
+
const instance = new TestComponent();
|
|
156
|
+
(instance as unknown as Record<string, unknown>).myMultiplier = 10;
|
|
157
|
+
run(instance);
|
|
158
|
+
|
|
159
|
+
const view = (instance as unknown as Record<string, unknown>).resolved as Record<string, unknown>;
|
|
160
|
+
expect(view.multiplier).toBe(10);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("each instance gets its own composable view", () => {
|
|
164
|
+
const { ctx, run } = fieldCtx("c");
|
|
165
|
+
Compose(CounterComposable)(undefined, ctx);
|
|
166
|
+
|
|
167
|
+
const a = new TestComponent();
|
|
168
|
+
const b = new TestComponent();
|
|
169
|
+
run(a);
|
|
170
|
+
run(b);
|
|
171
|
+
|
|
172
|
+
const viewA = (a as unknown as Record<string, unknown>).c;
|
|
173
|
+
const viewB = (b as unknown as Record<string, unknown>).c;
|
|
174
|
+
expect(viewA).not.toBe(viewB);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("plain function value in view is stored as-is (not treated as reactive)", () => {
|
|
178
|
+
const { ctx, run } = fieldCtx("pfn");
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
Compose(PlainFnComposable as any)(undefined, ctx);
|
|
181
|
+
|
|
182
|
+
const instance = new TestComponent();
|
|
183
|
+
run(instance);
|
|
184
|
+
|
|
185
|
+
const view = (instance as unknown as Record<string, unknown>).pfn as Record<string, unknown>;
|
|
186
|
+
// handler is a plain function — isReactive returns false, stored directly
|
|
187
|
+
expect(typeof view.handler).toBe("function");
|
|
188
|
+
expect((view.handler as () => number)()).toBe(42);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("view getter always returns same view object", () => {
|
|
192
|
+
const { ctx, run } = fieldCtx("stable");
|
|
193
|
+
Compose(CounterComposable)(undefined, ctx);
|
|
194
|
+
|
|
195
|
+
const instance = new TestComponent();
|
|
196
|
+
run(instance);
|
|
197
|
+
|
|
198
|
+
const v1 = (instance as unknown as Record<string, unknown>).stable;
|
|
199
|
+
const v2 = (instance as unknown as Record<string, unknown>).stable;
|
|
200
|
+
expect(v1).toBe(v2);
|
|
201
|
+
});
|
|
202
|
+
});
|