@praxisjs/decorators 0.7.5 → 0.8.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/__tests__/deep-state.test.d.ts +2 -0
  3. package/dist/__tests__/deep-state.test.d.ts.map +1 -0
  4. package/dist/__tests__/deep-state.test.js +150 -0
  5. package/dist/__tests__/deep-state.test.js.map +1 -0
  6. package/dist/__tests__/properties.test.js +29 -0
  7. package/dist/__tests__/properties.test.js.map +1 -1
  8. package/dist/__tests__/synced.test.d.ts +2 -0
  9. package/dist/__tests__/synced.test.d.ts.map +1 -0
  10. package/dist/__tests__/synced.test.js +142 -0
  11. package/dist/__tests__/synced.test.js.map +1 -0
  12. package/dist/component/virtual.d.ts.map +1 -1
  13. package/dist/component/virtual.js +12 -6
  14. package/dist/component/virtual.js.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/properties/deep-state.d.ts +2 -0
  20. package/dist/properties/deep-state.d.ts.map +1 -0
  21. package/dist/properties/deep-state.js +55 -0
  22. package/dist/properties/deep-state.js.map +1 -0
  23. package/dist/properties/index.d.ts +2 -0
  24. package/dist/properties/index.d.ts.map +1 -1
  25. package/dist/properties/index.js +2 -0
  26. package/dist/properties/index.js.map +1 -1
  27. package/dist/properties/synced.d.ts +2 -0
  28. package/dist/properties/synced.d.ts.map +1 -0
  29. package/dist/properties/synced.js +26 -0
  30. package/dist/properties/synced.js.map +1 -0
  31. package/package.json +2 -2
  32. package/src/__tests__/deep-state.test.ts +185 -0
  33. package/src/__tests__/properties.test.ts +33 -0
  34. package/src/__tests__/synced.test.ts +172 -0
  35. package/src/component/virtual.tsx +36 -12
  36. package/src/index.ts +2 -0
  37. package/src/properties/deep-state.ts +60 -0
  38. package/src/properties/index.ts +2 -0
  39. package/src/properties/synced.ts +28 -0
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Component, Lazy, Virtual } from "./component";
2
2
  export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Until, Watch, type WatchVal, type WatchVals, } from "./functions";
3
- export { Prop, State, Computed, Persisted, Slot, initSlots, History, type HistoryOf, Compose, Resource, type ResourceInstance, type ResourceOptions, } from "./properties";
3
+ export { Prop, State, Computed, Persisted, Slot, initSlots, History, type HistoryOf, Compose, Resource, type ResourceInstance, type ResourceOptions, Synced, DeepState, } from "./properties";
4
4
  export { type Command, createCommand, Emit, OnCommand } from "./events";
5
5
  export { createFieldDecorator, type FieldBehavior, type FieldBinding, } from "./create-field-decorator";
6
6
  export { createClassDecorator, ClassBehavior, type ClassEnhancement, } from "./create-class-decorator";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EACP,KAAK,SAAS,EACd,OAAO,EACP,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,6BAA6B,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EACP,KAAK,SAAS,EACd,OAAO,EACP,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,MAAM,EACN,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,6BAA6B,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Component, Lazy, Virtual } from "./component";
2
2
  export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Until, Watch, } from "./functions";
3
- export { Prop, State, Computed, Persisted, Slot, initSlots, History, Compose, Resource, } from "./properties";
3
+ export { Prop, State, Computed, Persisted, Slot, initSlots, History, Compose, Resource, Synced, DeepState, } from "./properties";
4
4
  export { createCommand, Emit, OnCommand } from "./events";
5
5
  export { createFieldDecorator, } from "./create-field-decorator";
6
6
  export { createClassDecorator, ClassBehavior, } from "./create-class-decorator";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,GAGN,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EAEP,OAAO,EACP,QAAQ,GAGT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAgB,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,GAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,GAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,GAE/B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EAErB,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,GAGN,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EAEP,OAAO,EACP,QAAQ,EAGR,MAAM,EACN,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAgB,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,GAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,GAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,GAE/B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EAErB,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function DeepState(): (_value: undefined, context: ClassFieldDecoratorContext<import("@praxisjs/core").StatefulComponent, unknown>) => void;
2
+ //# sourceMappingURL=deep-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-state.d.ts","sourceRoot":"","sources":["../../src/properties/deep-state.ts"],"names":[],"mappings":"AA0BA,wBAAgB,SAAS,0HAiCxB"}
@@ -0,0 +1,55 @@
1
+ import { signal } from "@praxisjs/core/internal";
2
+ import { createFieldDecorator } from "../create-field-decorator";
3
+ function deepProxy(target, notify) {
4
+ return new Proxy(target, {
5
+ get(obj, key, receiver) {
6
+ const val = Reflect.get(obj, key, receiver);
7
+ if (val !== null && typeof val === "object") {
8
+ return deepProxy(val, notify);
9
+ }
10
+ return val;
11
+ },
12
+ set(obj, key, value) {
13
+ const result = Reflect.set(obj, key, value);
14
+ notify();
15
+ return result;
16
+ },
17
+ deleteProperty(obj, key) {
18
+ const result = Reflect.deleteProperty(obj, key);
19
+ notify();
20
+ return result;
21
+ },
22
+ });
23
+ }
24
+ export function DeepState() {
25
+ return createFieldDecorator({
26
+ bind(instance, _name, initialValue) {
27
+ const version = signal(0);
28
+ let current = initialValue;
29
+ let proxy = current !== null && typeof current === "object"
30
+ ? deepProxy(current, notify)
31
+ : current;
32
+ function notify() {
33
+ instance._stateDirty = true;
34
+ version.update((v) => v + 1);
35
+ }
36
+ return {
37
+ descriptor: {
38
+ get() {
39
+ version();
40
+ return proxy;
41
+ },
42
+ set(value) {
43
+ current = value;
44
+ proxy =
45
+ value !== null && typeof value === "object"
46
+ ? deepProxy(value, notify)
47
+ : value;
48
+ notify();
49
+ },
50
+ },
51
+ };
52
+ },
53
+ });
54
+ }
55
+ //# sourceMappingURL=deep-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-state.js","sourceRoot":"","sources":["../../src/properties/deep-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,SAAS,SAAS,CAAmB,MAAS,EAAE,MAAkB;IAChE,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ;YACpB,MAAM,GAAG,GAAY,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YACrD,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5C,OAAO,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QACD,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK;YACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5C,MAAM,EAAE,CAAC;YACT,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,cAAc,CAAC,GAAG,EAAE,GAAG;YACrB,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAChD,MAAM,EAAE,CAAC;YACT,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,YAAY;YAChC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,OAAO,GAAG,YAAY,CAAC;YAC3B,IAAI,KAAK,GACP,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAC7C,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;gBAC5B,CAAC,CAAC,OAAO,CAAC;YAEd,SAAS,MAAM;gBACb,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC;gBAC5B,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,CAAC;YAED,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG;wBACD,OAAO,EAAE,CAAC;wBACV,OAAO,KAAK,CAAC;oBACf,CAAC;oBACD,GAAG,CAAC,KAAc;wBAChB,OAAO,GAAG,KAAK,CAAC;wBAChB,KAAK;4BACH,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;gCACzC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC;gCAC1B,CAAC,CAAC,KAAK,CAAC;wBACZ,MAAM,EAAE,CAAC;oBACX,CAAC;iBACF;aACF,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -6,4 +6,6 @@ export { Slot, initSlots } from "./slot";
6
6
  export { History, type HistoryOf } from "./history";
7
7
  export { Compose } from "./compose";
8
8
  export { Resource, type ResourceInstance, type ResourceOptions } from "./resource";
9
+ export { Synced } from "./synced";
10
+ export { DeepState } from "./deep-state";
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/properties/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/properties/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
@@ -6,4 +6,6 @@ export { Slot, initSlots } from "./slot";
6
6
  export { History } from "./history";
7
7
  export { Compose } from "./compose";
8
8
  export { Resource } from "./resource";
9
+ export { Synced } from "./synced";
10
+ export { DeepState } from "./deep-state";
9
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/properties/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAA+C,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/properties/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAA+C,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function Synced(channelName?: string): (_value: undefined, context: ClassFieldDecoratorContext<import("@praxisjs/core").StatefulComponent, unknown>) => void;
2
+ //# sourceMappingURL=synced.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synced.d.ts","sourceRoot":"","sources":["../../src/properties/synced.ts"],"names":[],"mappings":"AAMA,wBAAgB,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,yHAqB1C"}
@@ -0,0 +1,26 @@
1
+ import { syncedSignal } from "@praxisjs/core/internal";
2
+ import { createFieldDecorator } from "../create-field-decorator";
3
+ const signalMap = new WeakMap();
4
+ export function Synced(channelName) {
5
+ return createFieldDecorator({
6
+ bind(instance, name, initialValue) {
7
+ const key = channelName ?? name;
8
+ if (!signalMap.has(instance))
9
+ signalMap.set(instance, new Map());
10
+ const map = signalMap.get(instance);
11
+ if (!map.has(key))
12
+ map.set(key, syncedSignal(key, initialValue));
13
+ const sig = map.get(key);
14
+ return {
15
+ descriptor: {
16
+ get: () => sig(),
17
+ set: (value) => { sig.set(value); },
18
+ },
19
+ onUnmount() {
20
+ sig.close();
21
+ },
22
+ };
23
+ },
24
+ });
25
+ }
26
+ //# sourceMappingURL=synced.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synced.js","sourceRoot":"","sources":["../../src/properties/synced.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,SAAS,GAAG,IAAI,OAAO,EAA8C,CAAC;AAE5E,MAAM,UAAU,MAAM,CAAC,WAAoB;IACzC,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,YAAY;YAC/B,MAAM,GAAG,GAAG,WAAW,IAAI,IAAI,CAAC;YAEhC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;YACjE,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAuC,CAAC;YAC1E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC;YACjE,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAA0B,CAAC;YAElD,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE;oBAChB,GAAG,EAAE,CAAC,KAAc,EAAE,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;iBAC7C;gBACD,SAAS;oBACP,GAAG,CAAC,KAAK,EAAE,CAAC;gBACd,CAAC;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.7.5",
3
+ "version": "0.8.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": "1.4.1",
18
+ "@praxisjs/core": "1.5.0",
19
19
  "@praxisjs/shared": "0.2.0"
20
20
  },
21
21
  "scripts": {
@@ -0,0 +1,185 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { StatefulComponent } from "@praxisjs/core";
4
+ import { effect } from "@praxisjs/core/internal";
5
+
6
+ import { DeepState } from "../properties/deep-state";
7
+
8
+ class TestComponent extends StatefulComponent {
9
+ render() { return null; }
10
+ }
11
+
12
+ function fieldCtx(name: string) {
13
+ const initializers: Array<(this: unknown) => void> = [];
14
+ return {
15
+ ctx: {
16
+ name,
17
+ kind: "field" as const,
18
+ addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
19
+ } as ClassFieldDecoratorContext,
20
+ run(instance: unknown) {
21
+ initializers.forEach((fn) => { fn.call(instance); });
22
+ },
23
+ };
24
+ }
25
+
26
+ describe("@DeepState decorator", () => {
27
+ it("reads initial value", () => {
28
+ const { ctx, run } = fieldCtx("config");
29
+ DeepState()(undefined, ctx);
30
+ const instance = new TestComponent();
31
+ (instance as unknown as Record<string, unknown>).config = { theme: "light" };
32
+ run(instance);
33
+
34
+ const inst = instance as unknown as { config: { theme: string } };
35
+ expect(inst.config.theme).toBe("light");
36
+ });
37
+
38
+ it("nested object mutation triggers reactive effect", () => {
39
+ const { ctx, run } = fieldCtx("config");
40
+ DeepState()(undefined, ctx);
41
+ const instance = new TestComponent();
42
+ (instance as unknown as Record<string, unknown>).config = { theme: { mode: "light" } };
43
+ run(instance);
44
+
45
+ const inst = instance as unknown as { config: { theme: { mode: string } } };
46
+ const log: string[] = [];
47
+ effect(() => { log.push(inst.config.theme.mode); });
48
+
49
+ inst.config.theme.mode = "dark";
50
+ expect(log).toEqual(["light", "dark"]);
51
+ });
52
+
53
+ it("array push triggers reactive effect", () => {
54
+ const { ctx, run } = fieldCtx("items");
55
+ DeepState()(undefined, ctx);
56
+ const instance = new TestComponent();
57
+ (instance as unknown as Record<string, unknown>).items = [1, 2];
58
+ run(instance);
59
+
60
+ const inst = instance as unknown as { items: number[] };
61
+ const log: number[][] = [];
62
+ effect(() => { log.push([...inst.items]); });
63
+
64
+ inst.items.push(3);
65
+ expect(log[log.length - 1]).toEqual([1, 2, 3]);
66
+ });
67
+
68
+ it("array index assignment triggers reactive effect", () => {
69
+ const { ctx, run } = fieldCtx("list");
70
+ DeepState()(undefined, ctx);
71
+ const instance = new TestComponent();
72
+ (instance as unknown as Record<string, unknown>).list = ["a", "b"];
73
+ run(instance);
74
+
75
+ const inst = instance as unknown as { list: string[] };
76
+ const log: string[][] = [];
77
+ effect(() => { log.push([...inst.list]); });
78
+
79
+ inst.list[0] = "z";
80
+ expect(log[log.length - 1]).toEqual(["z", "b"]);
81
+ });
82
+
83
+ it("property deletion triggers reactive effect", () => {
84
+ const { ctx, run } = fieldCtx("obj");
85
+ DeepState()(undefined, ctx);
86
+ const instance = new TestComponent();
87
+ (instance as unknown as Record<string, unknown>).obj = { a: 1, b: 2 };
88
+ run(instance);
89
+
90
+ const inst = instance as unknown as { obj: Record<string, number> };
91
+ const log: string[][] = [];
92
+ effect(() => { log.push(Object.keys(inst.obj)); });
93
+
94
+ delete inst.obj.a;
95
+ expect(log[log.length - 1]).not.toContain("a");
96
+ });
97
+
98
+ it("direct assignment replaces root and triggers effect", () => {
99
+ const { ctx, run } = fieldCtx("data");
100
+ DeepState()(undefined, ctx);
101
+ const instance = new TestComponent();
102
+ (instance as unknown as Record<string, unknown>).data = { x: 1 };
103
+ run(instance);
104
+
105
+ const inst = instance as unknown as { data: { x: number } };
106
+ const log: number[] = [];
107
+ effect(() => { log.push(inst.data.x); });
108
+
109
+ inst.data = { x: 99 };
110
+ expect(log).toEqual([1, 99]);
111
+ });
112
+
113
+ it("mutations on new root after reassignment are reactive", () => {
114
+ const { ctx, run } = fieldCtx("cfg");
115
+ DeepState()(undefined, ctx);
116
+ const instance = new TestComponent();
117
+ (instance as unknown as Record<string, unknown>).cfg = { v: 0 };
118
+ run(instance);
119
+
120
+ const inst = instance as unknown as { cfg: { v: number } };
121
+ inst.cfg = { v: 10 };
122
+
123
+ const log: number[] = [];
124
+ effect(() => { log.push(inst.cfg.v); });
125
+
126
+ inst.cfg.v = 20;
127
+ expect(log).toEqual([10, 20]);
128
+ });
129
+
130
+ it("null initial value is handled gracefully", () => {
131
+ const { ctx, run } = fieldCtx("maybe");
132
+ DeepState()(undefined, ctx);
133
+ const instance = new TestComponent();
134
+ (instance as unknown as Record<string, unknown>).maybe = null;
135
+ run(instance);
136
+
137
+ const inst = instance as unknown as { maybe: null | { x: number } };
138
+ expect(inst.maybe).toBeNull();
139
+
140
+ const log: unknown[] = [];
141
+ effect(() => { log.push(inst.maybe); });
142
+
143
+ inst.maybe = { x: 5 };
144
+ expect(log).toEqual([null, { x: 5 }]);
145
+ });
146
+
147
+ it("setter with null value — proxy not created, value is null", () => {
148
+ const { ctx, run } = fieldCtx("nullable");
149
+ DeepState()(undefined, ctx);
150
+ const instance = new TestComponent();
151
+ (instance as unknown as Record<string, unknown>).nullable = { x: 1 };
152
+ run(instance);
153
+
154
+ const inst = instance as unknown as { nullable: { x: number } | null };
155
+ inst.nullable = null;
156
+ expect(inst.nullable).toBeNull();
157
+ });
158
+
159
+ it("setter with primitive value — value stored directly without proxy", () => {
160
+ const { ctx, run } = fieldCtx("num");
161
+ DeepState()(undefined, ctx);
162
+ const instance = new TestComponent();
163
+ (instance as unknown as Record<string, unknown>).num = { v: 0 };
164
+ run(instance);
165
+
166
+ const inst = instance as unknown as { num: number | { v: number } };
167
+ inst.num = 99;
168
+ expect(inst.num).toBe(99);
169
+ });
170
+
171
+ it("deeply nested mutation triggers effect", () => {
172
+ const { ctx, run } = fieldCtx("deep");
173
+ DeepState()(undefined, ctx);
174
+ const instance = new TestComponent();
175
+ (instance as unknown as Record<string, unknown>).deep = { a: { b: { c: 0 } } };
176
+ run(instance);
177
+
178
+ const inst = instance as unknown as { deep: { a: { b: { c: number } } } };
179
+ const log: number[] = [];
180
+ effect(() => { log.push(inst.deep.a.b.c); });
181
+
182
+ inst.deep.a.b.c = 42;
183
+ expect(log).toEqual([0, 42]);
184
+ });
185
+ });
@@ -3,6 +3,7 @@ import { describe, it, expect, vi } from "vitest";
3
3
  import { StatefulComponent } from "@praxisjs/core";
4
4
  import { signal, computed } from "@praxisjs/core/internal";
5
5
 
6
+ import { createFieldDecorator } from "../create-field-decorator";
6
7
  import { Watch } from "../functions/watch";
7
8
  import { When } from "../functions/when";
8
9
  import { Compose } from "../properties/compose";
@@ -984,3 +985,35 @@ describe("@Compose decorator — new cases", () => {
984
985
  expect(view.resolved).toBeUndefined();
985
986
  });
986
987
  });
988
+
989
+ // ── createFieldDecorator — additional property ────────────────────────────────
990
+
991
+ describe("createFieldDecorator — additional property", () => {
992
+ it("defines extra properties on the instance when additional is returned", () => {
993
+ const decorator = createFieldDecorator({
994
+ bind(_instance, _name, initialValue) {
995
+ return {
996
+ descriptor: {
997
+ get() { return initialValue; },
998
+ set() { /* no-op */ },
999
+ },
1000
+ additional: {
1001
+ extraProp: {
1002
+ configurable: true,
1003
+ get() { return "extra"; },
1004
+ },
1005
+ },
1006
+ };
1007
+ },
1008
+ });
1009
+
1010
+ const { ctx, run } = fieldCtx("myField");
1011
+ decorator(undefined, ctx);
1012
+ const instance = new TestComponent();
1013
+ (instance as unknown as Record<string, unknown>).myField = 42;
1014
+ run(instance);
1015
+
1016
+ expect((instance as unknown as Record<string, unknown>).myField).toBe(42);
1017
+ expect((instance as unknown as Record<string, unknown>).extraProp).toBe("extra");
1018
+ });
1019
+ });
@@ -0,0 +1,172 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
+
4
+ import { StatefulComponent } from "@praxisjs/core";
5
+ import { effect } from "@praxisjs/core/internal";
6
+
7
+ import { Synced } from "../properties/synced";
8
+
9
+ // BroadcastChannel mock (same as core test)
10
+ class MockBroadcastChannel {
11
+ static instances: MockBroadcastChannel[] = [];
12
+ static byChannel: Map<string, MockBroadcastChannel[]> = new Map();
13
+
14
+ onmessage: ((event: MessageEvent) => void) | null = null;
15
+ readonly name: string;
16
+ closed = false;
17
+
18
+ constructor(name: string) {
19
+ this.name = name;
20
+ MockBroadcastChannel.instances.push(this);
21
+ if (!MockBroadcastChannel.byChannel.has(name)) {
22
+ MockBroadcastChannel.byChannel.set(name, []);
23
+ }
24
+ MockBroadcastChannel.byChannel.get(name)!.push(this);
25
+ }
26
+
27
+ postMessage(data: unknown) {
28
+ MockBroadcastChannel.byChannel.get(this.name)?.forEach((ch) => {
29
+ if (ch !== this && ch.onmessage) {
30
+ ch.onmessage(new MessageEvent("message", { data }));
31
+ }
32
+ });
33
+ }
34
+
35
+ close() {
36
+ this.closed = true;
37
+ const list = MockBroadcastChannel.byChannel.get(this.name);
38
+ if (list) {
39
+ const idx = list.indexOf(this);
40
+ if (idx !== -1) list.splice(idx, 1);
41
+ }
42
+ MockBroadcastChannel.instances.splice(
43
+ MockBroadcastChannel.instances.indexOf(this),
44
+ 1,
45
+ );
46
+ }
47
+ }
48
+
49
+ beforeEach(() => {
50
+ MockBroadcastChannel.instances = [];
51
+ MockBroadcastChannel.byChannel = new Map();
52
+ vi.stubGlobal("BroadcastChannel", MockBroadcastChannel);
53
+ });
54
+
55
+ class TestComponent extends StatefulComponent {
56
+ render() { return null; }
57
+ }
58
+
59
+ function fieldCtx(name: string) {
60
+ const initializers: Array<(this: unknown) => void> = [];
61
+ return {
62
+ ctx: {
63
+ name,
64
+ kind: "field" as const,
65
+ addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
66
+ } as ClassFieldDecoratorContext,
67
+ run(instance: unknown) {
68
+ initializers.forEach((fn) => { fn.call(instance); });
69
+ },
70
+ };
71
+ }
72
+
73
+ describe("@Synced decorator", () => {
74
+ it("reads and writes the field reactively", () => {
75
+ const { ctx, run } = fieldCtx("count");
76
+ Synced()(undefined, ctx);
77
+ const instance = new TestComponent();
78
+ (instance as unknown as Record<string, unknown>).count = 0;
79
+ run(instance);
80
+
81
+ const inst = instance as unknown as { count: number };
82
+ expect(inst.count).toBe(0);
83
+ inst.count = 5;
84
+ expect(inst.count).toBe(5);
85
+ });
86
+
87
+ it("uses field name as channel name by default", () => {
88
+ const { ctx, run } = fieldCtx("cart");
89
+ Synced()(undefined, ctx);
90
+ const instance = new TestComponent();
91
+ (instance as unknown as Record<string, unknown>).cart = [];
92
+ run(instance);
93
+
94
+ expect(MockBroadcastChannel.instances[0].name).toBe("cart");
95
+ });
96
+
97
+ it("uses custom channel name when provided", () => {
98
+ const { ctx, run } = fieldCtx("items");
99
+ Synced("my-channel")(undefined, ctx);
100
+ const instance = new TestComponent();
101
+ (instance as unknown as Record<string, unknown>).items = [];
102
+ run(instance);
103
+
104
+ expect(MockBroadcastChannel.instances[0].name).toBe("my-channel");
105
+ });
106
+
107
+ it("triggers reactive effects on write", () => {
108
+ const { ctx, run } = fieldCtx("value");
109
+ Synced()(undefined, ctx);
110
+ const instance = new TestComponent();
111
+ (instance as unknown as Record<string, unknown>).value = 0;
112
+ run(instance);
113
+
114
+ const inst = instance as unknown as { value: number };
115
+ const log: number[] = [];
116
+ effect(() => { log.push(inst.value); });
117
+
118
+ inst.value = 10;
119
+ expect(log).toEqual([0, 10]);
120
+ });
121
+
122
+ it("updates field when a remote message arrives", () => {
123
+ const { ctx, run } = fieldCtx("score");
124
+ Synced()(undefined, ctx);
125
+ const instance = new TestComponent();
126
+ (instance as unknown as Record<string, unknown>).score = 0;
127
+ run(instance);
128
+
129
+ const inst = instance as unknown as { score: number };
130
+ const channel = MockBroadcastChannel.instances[0];
131
+ channel.onmessage!(new MessageEvent("message", { data: JSON.stringify(42) }));
132
+
133
+ expect(inst.score).toBe(42);
134
+ });
135
+
136
+ it("two @Synced fields on the same instance share the signalMap entry", () => {
137
+ const { ctx: ctx1, run: run1 } = fieldCtx("a");
138
+ const { ctx: ctx2, run: run2 } = fieldCtx("b");
139
+ Synced()(undefined, ctx1);
140
+ Synced()(undefined, ctx2);
141
+
142
+ const instance = new TestComponent();
143
+ (instance as unknown as Record<string, unknown>).a = 1;
144
+ (instance as unknown as Record<string, unknown>).b = 2;
145
+ run1(instance);
146
+ run2(instance);
147
+
148
+ const inst = instance as unknown as { a: number; b: number };
149
+ inst.a = 10;
150
+ inst.b = 20;
151
+ expect(inst.a).toBe(10);
152
+ expect(inst.b).toBe(20);
153
+
154
+ // Both fields have their own BroadcastChannel
155
+ expect(MockBroadcastChannel.instances.length).toBe(2);
156
+ instance.onUnmount?.();
157
+ expect(MockBroadcastChannel.instances.every((ch) => ch.closed)).toBe(true);
158
+ });
159
+
160
+ it("onUnmount closes the BroadcastChannel", () => {
161
+ const { ctx, run } = fieldCtx("data");
162
+ Synced()(undefined, ctx);
163
+ const instance = new TestComponent();
164
+ (instance as unknown as Record<string, unknown>).data = null;
165
+ run(instance);
166
+
167
+ const channel = MockBroadcastChannel.instances[0];
168
+ expect(channel.closed).toBe(false);
169
+ instance.onUnmount?.();
170
+ expect(channel.closed).toBe(true);
171
+ });
172
+ });