@praxisjs/decorators 0.8.1 → 1.0.1

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 (47) hide show
  1. package/CHANGELOG.md +134 -3
  2. package/dist/__tests__/lazy.test.js +74 -10
  3. package/dist/__tests__/lazy.test.js.map +1 -1
  4. package/dist/__tests__/properties.test.js +61 -3
  5. package/dist/__tests__/properties.test.js.map +1 -1
  6. package/dist/component/index.d.ts +0 -1
  7. package/dist/component/index.d.ts.map +1 -1
  8. package/dist/component/index.js +0 -1
  9. package/dist/component/index.js.map +1 -1
  10. package/dist/component/lazy.d.ts +15 -1
  11. package/dist/component/lazy.d.ts.map +1 -1
  12. package/dist/component/lazy.js +15 -9
  13. package/dist/component/lazy.js.map +1 -1
  14. package/dist/create-class-decorator.d.ts.map +1 -1
  15. package/dist/create-class-decorator.js +16 -3
  16. package/dist/create-class-decorator.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/properties/compose.d.ts +7 -0
  22. package/dist/properties/compose.d.ts.map +1 -1
  23. package/dist/properties/compose.js +18 -3
  24. package/dist/properties/compose.js.map +1 -1
  25. package/dist/properties/index.d.ts +1 -1
  26. package/dist/properties/index.d.ts.map +1 -1
  27. package/dist/properties/index.js +1 -1
  28. package/dist/properties/index.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/__tests__/lazy.test.ts +88 -10
  31. package/src/__tests__/properties.test.ts +71 -3
  32. package/src/component/index.ts +0 -1
  33. package/src/component/lazy.ts +29 -7
  34. package/src/create-class-decorator.ts +16 -4
  35. package/src/index.ts +2 -1
  36. package/src/properties/compose.ts +21 -8
  37. package/src/properties/index.ts +1 -1
  38. package/dist/__tests__/virtual.test.d.ts +0 -2
  39. package/dist/__tests__/virtual.test.d.ts.map +0 -1
  40. package/dist/__tests__/virtual.test.js +0 -352
  41. package/dist/__tests__/virtual.test.js.map +0 -1
  42. package/dist/component/virtual.d.ts +0 -3
  43. package/dist/component/virtual.d.ts.map +0 -1
  44. package/dist/component/virtual.js +0 -102
  45. package/dist/component/virtual.js.map +0 -1
  46. package/src/__tests__/virtual.test.ts +0 -371
  47. package/src/component/virtual.tsx +0 -163
@@ -1,12 +1,12 @@
1
1
  import { signal } from "@praxisjs/core/internal";
2
2
  import { ClassBehavior, createClassDecorator } from "../create-class-decorator";
3
3
  class LazyBehavior extends ClassBehavior {
4
- constructor(placeholder) {
4
+ constructor(options) {
5
5
  super();
6
- this.placeholder = placeholder;
6
+ this.options = options;
7
7
  }
8
8
  create(instance) {
9
- const placeholder = this.placeholder;
9
+ const { placeholder = 200, root, rootMargin = "100px" } = this.options;
10
10
  const visible = signal(false);
11
11
  let observer;
12
12
  return {
@@ -20,27 +20,33 @@ class LazyBehavior extends ClassBehavior {
20
20
  }
21
21
  if (!visible())
22
22
  el.style.minHeight = `${String(placeholder)}px`;
23
+ const rootEl = root === undefined || root === null ? null : root.current ?? null;
23
24
  observer = new IntersectionObserver((entries) => {
24
25
  if (entries[0]?.isIntersecting) {
25
26
  visible.set(true);
26
27
  el.style.minHeight = "";
27
28
  observer?.disconnect();
28
29
  }
29
- }, { rootMargin: "100px" });
30
+ }, { root: rootEl, rootMargin });
30
31
  observer.observe(el);
31
32
  },
32
33
  onUnmount() {
33
34
  observer?.disconnect();
34
35
  },
35
36
  render(originalRender) {
36
- if (!visible())
37
- return null;
38
- return originalRender();
37
+ return (() => {
38
+ if (!visible())
39
+ return null;
40
+ return originalRender();
41
+ });
39
42
  },
40
43
  };
41
44
  }
42
45
  }
43
- export function Lazy(placeholder = 200) {
44
- return createClassDecorator(new LazyBehavior(placeholder));
46
+ export function Lazy(placeholderOrOptions = 200) {
47
+ const options = typeof placeholderOrOptions === "number"
48
+ ? { placeholder: placeholderOrOptions }
49
+ : placeholderOrOptions;
50
+ return createClassDecorator(new LazyBehavior(options));
45
51
  }
46
52
  //# sourceMappingURL=lazy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/component/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAyB,MAAM,2BAA2B,CAAC;AAEvG,MAAM,YAAa,SAAQ,aAAa;IACtC,YAA6B,WAAmB;QAC9C,KAAK,EAAE,CAAC;QADmB,gBAAW,GAAX,WAAW,CAAQ;IAEhD,CAAC;IAED,MAAM,CAAC,QAAuB;QAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,QAA0C,CAAC;QAE/C,OAAO;YACL,OAAO;gBACL,MAAM,EAAE,GACL,QAAkC,CAAC,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;gBACrE,IAAI,CAAC,EAAE;oBAAE,OAAO;gBAEhB,IAAI,CAAC,CAAC,sBAAsB,IAAI,MAAM,CAAC,EAAE,CAAC;oBACxC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAClB,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE;oBAAE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;gBAEhE,QAAQ,GAAG,IAAI,oBAAoB,CACjC,CAAC,OAAO,EAAE,EAAE;oBACV,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;wBAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAClB,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;wBACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;oBACzB,CAAC;gBACH,CAAC,EACD,EAAE,UAAU,EAAE,OAAO,EAAE,CACxB,CAAC;gBAEF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;YAED,SAAS;gBACP,QAAQ,EAAE,UAAU,EAAE,CAAC;YACzB,CAAC;YAED,MAAM,CAAC,cAAc;gBACnB,IAAI,CAAC,OAAO,EAAE;oBAAE,OAAO,IAAI,CAAC;gBAC5B,OAAO,cAAc,EAAE,CAAC;YAC1B,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,IAAI,CAAC,WAAW,GAAG,GAAG;IACpC,OAAO,oBAAoB,CAAC,IAAI,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7D,CAAC"}
1
+ {"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/component/lazy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAyB,MAAM,2BAA2B,CAAC;AAevG,MAAM,YAAa,SAAQ,aAAa;IACtC,YAA6B,OAAoB;QAC/C,KAAK,EAAE,CAAC;QADmB,YAAO,GAAP,OAAO,CAAa;IAEjD,CAAC;IAED,MAAM,CAAC,QAAuB;QAC5B,MAAM,EAAE,WAAW,GAAG,GAAG,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACvE,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,QAA0C,CAAC;QAE/C,OAAO;YACL,OAAO;gBACL,MAAM,EAAE,GACL,QAAkC,CAAC,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;gBACrE,IAAI,CAAC,EAAE;oBAAE,OAAO;gBAEhB,IAAI,CAAC,CAAC,sBAAsB,IAAI,MAAM,CAAC,EAAE,CAAC;oBACxC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAClB,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE;oBAAE,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC;gBAEhE,MAAM,MAAM,GACV,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;gBAEpE,QAAQ,GAAG,IAAI,oBAAoB,CACjC,CAAC,OAAO,EAAE,EAAE;oBACV,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;wBAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAClB,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC;wBACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;oBACzB,CAAC;gBACH,CAAC,EACD,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAC7B,CAAC;gBAEF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;YAED,SAAS;gBACP,QAAQ,EAAE,UAAU,EAAE,CAAC;YACzB,CAAC;YAED,MAAM,CAAC,cAAc;gBACnB,OAAO,CAAC,GAAG,EAAE;oBACX,IAAI,CAAC,OAAO,EAAE;wBAAE,OAAO,IAAI,CAAC;oBAC5B,OAAO,cAAc,EAAE,CAAC;gBAC1B,CAAC,CAAoB,CAAC;YACxB,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,IAAI,CAAC,uBAA6C,GAAG;IACnE,MAAM,OAAO,GACX,OAAO,oBAAoB,KAAK,QAAQ;QACtC,CAAC,CAAC,EAAE,WAAW,EAAE,oBAAoB,EAAE;QACvC,CAAC,CAAC,oBAAoB,CAAC;IAC3B,OAAO,oBAAoB,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACzD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-class-decorator.d.ts","sourceRoot":"","sources":["../src/create-class-decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,SAAS,CAAC,IAAI,IAAI,CAAC;IACnB,MAAM,CAAC,CACL,cAAc,EAAE,MAAM,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,GACzC,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;CACzB;AAED,8BAAsB,aAAa;IACjC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,gBAAgB;IAE1D;;;OAGG;IAEH,UAAU,CAAC,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,GAAG,IAAI;CAC1G;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,IAGxD,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EACpE,aAAa,CAAC,EAAE,UAAU,qBAAqB,KAAG,CAAC,CAmCtD"}
1
+ {"version":3,"file":"create-class-decorator.d.ts","sourceRoot":"","sources":["../src/create-class-decorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,IAAI,IAAI,CAAC;IACjB,SAAS,CAAC,IAAI,IAAI,CAAC;IACnB,MAAM,CAAC,CACL,cAAc,EAAE,MAAM,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,GACzC,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;CACzB;AAED,8BAAsB,aAAa;IACjC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,aAAa,GAAG,gBAAgB;IAE1D;;;OAGG;IAEH,UAAU,CAAC,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,GAAG,IAAI;CAC1G;AAQD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,IAGxD,CAAC,SAAS,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EACpE,aAAa,CAAC,EAAE,UAAU,qBAAqB,KAAG,CAAC,CAyCtD"}
@@ -1,5 +1,10 @@
1
1
  export class ClassBehavior {
2
2
  }
3
+ // Tracks instances whose render is currently being called as `originalRender`
4
+ // from within an enhancement. When set, the Enhanced render skips the
5
+ // this._enh.render dispatch so stacked decorators (e.g. @Lazy + @Component)
6
+ // don't re-enter the outermost enhancement recursively.
7
+ const _originalRenderInProgress = new WeakSet();
3
8
  export function createClassDecorator(behavior) {
4
9
  return function (constructor, _context) {
5
10
  const Enhanced = class extends constructor {
@@ -16,11 +21,19 @@ export function createClassDecorator(behavior) {
16
21
  this._enh.onUnmount?.();
17
22
  }
18
23
  render(...args) {
19
- const originalRender = () => constructor.prototype.render.apply(this, args);
20
- if (this._enh.render) {
24
+ const originalRender = () => {
25
+ _originalRenderInProgress.add(this);
26
+ try {
27
+ return constructor.prototype.render.apply(this, args);
28
+ }
29
+ finally {
30
+ _originalRenderInProgress.delete(this);
31
+ }
32
+ };
33
+ if (this._enh.render && !_originalRenderInProgress.has(this)) {
21
34
  return this._enh.render(originalRender);
22
35
  }
23
- return originalRender();
36
+ return constructor.prototype.render.apply(this, args);
24
37
  }
25
38
  };
26
39
  // If the original class has no render, remove the wrapper so instances see `undefined`.
@@ -1 +1 @@
1
- {"version":3,"file":"create-class-decorator.js","sourceRoot":"","sources":["../src/create-class-decorator.ts"],"names":[],"mappings":"AAUA,MAAM,OAAgB,aAAa;CASlC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAuB;IAC1D,OAAO,UAGL,WAAc,EAAE,QAA+B;QAC/C,MAAM,QAAQ,GAAG,KAAM,SAAQ,WAAW;YAAzB;;gBACE,SAAI,GAAqB,QAAQ,CAAC,MAAM,CACvD,IAAgC,CACjC,CAAC;YAoBJ,CAAC;YAlBC,OAAO;gBACL,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBAClB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACxB,CAAC;YAED,SAAS;gBACP,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YAC1B,CAAC;YAED,MAAM,CAAC,GAAG,IAAe;gBACvB,MAAM,cAAc,GAAG,GAAG,EAAE,CACzB,WAAW,CAAC,SAAmE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC5G,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACrB,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC1C,CAAC;gBACD,OAAO,cAAc,EAAE,CAAC;YAC1B,CAAC;SACF,CAAC;QAEF,wFAAwF;QACxF,IAAI,OAAQ,WAAW,CAAC,SAAkC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACjF,OAAQ,QAAQ,CAAC,SAAkC,CAAC,MAAM,CAAC;QAC7D,CAAC;QAED,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QACrE,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC7C,OAAO,QAAwB,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"create-class-decorator.js","sourceRoot":"","sources":["../src/create-class-decorator.ts"],"names":[],"mappings":"AAUA,MAAM,OAAgB,aAAa;CASlC;AAED,8EAA8E;AAC9E,sEAAsE;AACtE,4EAA4E;AAC5E,wDAAwD;AACxD,MAAM,yBAAyB,GAAG,IAAI,OAAO,EAAE,CAAC;AAEhD,MAAM,UAAU,oBAAoB,CAAC,QAAuB;IAC1D,OAAO,UAGL,WAAc,EAAE,QAA+B;QAC/C,MAAM,QAAQ,GAAG,KAAM,SAAQ,WAAW;YAAzB;;gBACE,SAAI,GAAqB,QAAQ,CAAC,MAAM,CACvD,IAAgC,CACjC,CAAC;YA0BJ,CAAC;YAxBC,OAAO;gBACL,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;gBAClB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACxB,CAAC;YAED,SAAS;gBACP,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YAC1B,CAAC;YAED,MAAM,CAAC,GAAG,IAAe;gBACvB,MAAM,cAAc,GAAG,GAAG,EAAE;oBAC1B,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACpC,IAAI,CAAC;wBACH,OAAQ,WAAW,CAAC,SAAmE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACnH,CAAC;4BAAS,CAAC;wBACT,yBAAyB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBACzC,CAAC;gBACH,CAAC,CAAC;gBACF,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;gBAC1C,CAAC;gBACD,OAAQ,WAAW,CAAC,SAAmE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACnH,CAAC;SACF,CAAC;QAEF,wFAAwF;QACxF,IAAI,OAAQ,WAAW,CAAC,SAAkC,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACjF,OAAQ,QAAQ,CAAC,SAAkC,CAAC,MAAM,CAAC;QAC7D,CAAC;QAED,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QACrE,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC7C,OAAO,QAAwB,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { Component, Lazy, Virtual } from "./component";
1
+ export { Component, Lazy } 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, Synced, DeepState, } from "./properties";
3
+ export { Prop, State, Computed, Persisted, Slot, initSlots, History, type HistoryOf, Compose, getter, 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 { type ReactiveHost } from "./reactive-host";
@@ -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,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,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,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,MAAM,aAAa,CAAC;AAC9C,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,MAAM,EACN,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,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,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
- export { Component, Lazy, Virtual } from "./component";
1
+ export { Component, Lazy } 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, Synced, DeepState, } from "./properties";
3
+ export { Prop, State, Computed, Persisted, Slot, initSlots, History, Compose, getter, 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,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;AAElC,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,MAAM,aAAa,CAAC;AAC9C,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,MAAM,EACN,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;AAElC,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,3 +1,10 @@
1
1
  import type { StatefulComponent, Composable } from "@praxisjs/core";
2
+ interface ComposeFactory {
3
+ __isComposeFactory: true;
4
+ resolve: (instance: object) => unknown;
5
+ }
6
+ /** Wraps a property as a live getter — passes `() => instance[propName]` to the composable. */
7
+ export declare function getter(propName: string): ComposeFactory;
2
8
  export declare function Compose(ComposableClass: new (...args: any[]) => Composable, ...ctorArgs: unknown[]): <This extends StatefulComponent, Value = unknown>(_value: undefined, context: ClassFieldDecoratorContext<This, Value>) => void;
9
+ export {};
3
10
  //# sourceMappingURL=compose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/properties/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA6BpE,wBAAgB,OAAO,CAErB,eAAe,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,EACnD,GAAG,QAAQ,EAAE,OAAO,EAAE,kIAoBvB"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/properties/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIpE,UAAU,cAAc;IAAG,kBAAkB,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE;AAE7F,+FAA+F;AAC/F,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAKvD;AA6BD,wBAAgB,OAAO,CAErB,eAAe,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,EACnD,GAAG,QAAQ,EAAE,OAAO,EAAE,kIAqBvB"}
@@ -1,4 +1,14 @@
1
1
  import { createFieldDecorator } from "../create-field-decorator";
2
+ /** Wraps a property as a live getter — passes `() => instance[propName]` to the composable. */
3
+ export function getter(propName) {
4
+ return {
5
+ __isComposeFactory: true,
6
+ resolve: (instance) => () => instance[propName],
7
+ };
8
+ }
9
+ function isFactory(value) {
10
+ return typeof value === "object" && value !== null && "__isComposeFactory" in value;
11
+ }
2
12
  function isReactive(value) {
3
13
  return (typeof value === "function" &&
4
14
  ("__isSignal" in value || "__isComputed" in value));
@@ -24,9 +34,14 @@ export function Compose(
24
34
  ComposableClass, ...ctorArgs) {
25
35
  return createFieldDecorator({
26
36
  bind(instance, _name, _initialValue) {
27
- const resolvedArgs = ctorArgs.map((arg) => typeof arg === "string"
28
- ? instance[arg]
29
- : arg);
37
+ const resolvedArgs = ctorArgs.map((arg) => {
38
+ if (isFactory(arg))
39
+ return arg.resolve(instance);
40
+ if (typeof arg !== "string")
41
+ return arg;
42
+ const prop = instance[arg];
43
+ return prop !== undefined ? prop : arg;
44
+ });
30
45
  const composable = new ComposableClass(...resolvedArgs);
31
46
  const view = buildView(composable.setup());
32
47
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/properties/compose.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,SAAS,UAAU,CACjB,KAAc;IAEd,OAAO,CACL,OAAO,KAAK,KAAK,UAAU;QAC3B,CAAC,YAAY,IAAK,KAAgB,IAAI,cAAc,IAAK,KAAgB,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAAiC;IAClD,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE;gBAC/B,GAAG,EAAE,GAAG,EAAE,CAAE,KAAuB,EAAE;gBACrC,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,OAAO;AACrB,8DAA8D;AAC9D,eAAmD,EACnD,GAAG,QAAmB;IAEtB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAA2B,EAAE,KAAa,EAAE,aAAsB;YACrE,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACxC,OAAO,GAAG,KAAK,QAAQ;gBACrB,CAAC,CAAE,QAA+C,CAAC,GAAG,CAAC;gBACvD,CAAC,CAAC,GAAG,CACR,CAAC;YAEF,MAAM,UAAU,GAAG,IAAI,eAAe,CAAC,GAAG,YAAY,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;YAE3C,OAAO;gBACL,UAAU,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE;gBAC/B,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC;gBAC7C,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC;aAClD,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/properties/compose.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAIjE,+FAA+F;AAC/F,MAAM,UAAU,MAAM,CAAC,QAAgB;IACrC,OAAO;QACL,kBAAkB,EAAE,IAAI;QACxB,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,EAAE,CAAE,QAAoC,CAAC,QAAQ,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,oBAAoB,IAAI,KAAK,CAAC;AACtF,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,OAAO,KAAK,KAAK,UAAU;QAC3B,CAAC,YAAY,IAAK,KAAgB,IAAI,cAAc,IAAK,KAAgB,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAAiC;IAClD,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE;gBAC/B,GAAG,EAAE,GAAG,EAAE,CAAE,KAAuB,EAAE;gBACrC,UAAU,EAAE,IAAI;gBAChB,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,OAAO;AACrB,8DAA8D;AAC9D,eAAmD,EACnD,GAAG,QAAmB;IAEtB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,QAA2B,EAAE,KAAa,EAAE,aAAsB;YACrE,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACxC,IAAI,SAAS,CAAC,GAAG,CAAC;oBAAE,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACjD,IAAI,OAAO,GAAG,KAAK,QAAQ;oBAAE,OAAO,GAAG,CAAC;gBACxC,MAAM,IAAI,GAAI,QAA+C,CAAC,GAAG,CAAC,CAAC;gBACnE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;YACzC,CAAC,CAAC,CAAC;YAEH,MAAM,UAAU,GAAG,IAAI,eAAe,CAAC,GAAG,YAAY,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;YAE3C,OAAO;gBACL,UAAU,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE;gBAC/B,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC;gBAC7C,SAAS,EAAE,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC;aAClD,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -4,7 +4,7 @@ export { Computed } from "./computed";
4
4
  export { Persisted } from "./persisted";
5
5
  export { Slot, initSlots } from "./slot";
6
6
  export { History, type HistoryOf } from "./history";
7
- export { Compose } from "./compose";
7
+ export { Compose, getter } from "./compose";
8
8
  export { Resource, type ResourceInstance, type ResourceOptions } from "./resource";
9
9
  export { Synced } from "./synced";
10
10
  export { DeepState } from "./deep-state";
@@ -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;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,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,EAAE,MAAM,WAAW,CAAC;AAC5C,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"}
@@ -4,7 +4,7 @@ export { Computed } from "./computed";
4
4
  export { Persisted } from "./persisted";
5
5
  export { Slot, initSlots } from "./slot";
6
6
  export { History } from "./history";
7
- export { Compose } from "./compose";
7
+ export { Compose, getter } from "./compose";
8
8
  export { Resource } from "./resource";
9
9
  export { Synced } from "./synced";
10
10
  export { DeepState } from "./deep-state";
@@ -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;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,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,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAA+C,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/decorators",
3
- "version": "0.8.1",
3
+ "version": "1.0.1",
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.5.0",
18
+ "@praxisjs/core": "1.6.0",
19
19
  "@praxisjs/shared": "0.2.0"
20
20
  },
21
21
  "scripts": {
@@ -3,13 +3,20 @@ import { beforeEach, afterEach, describe, it, expect, vi } from "vitest";
3
3
 
4
4
  import { StatefulComponent } from "@praxisjs/core";
5
5
  import { Lazy } from "../component/lazy";
6
+ import type { LazyOptions } from "../component/lazy";
6
7
 
7
8
  type AnyConstructor = new (...args: unknown[]) => StatefulComponent;
8
9
 
9
- function applyLazy<T extends AnyConstructor>(cls: T, placeholder?: number): T {
10
- return placeholder !== undefined
11
- ? Lazy(placeholder)(cls, {} as ClassDecoratorContext)
12
- : Lazy()(cls, {} as ClassDecoratorContext);
10
+ function applyLazy<T extends AnyConstructor>(cls: T, placeholderOrOptions?: number | LazyOptions): T {
11
+ if (placeholderOrOptions === undefined) return Lazy()(cls, {} as ClassDecoratorContext);
12
+ return Lazy(placeholderOrOptions)(cls, {} as ClassDecoratorContext);
13
+ }
14
+
15
+ /** render() now returns a reactive thunk — resolve it to get the actual DOM output. */
16
+ function resolveRender(instance: StatefulComponent): Node | Node[] | null {
17
+ const result = instance.render();
18
+ if (typeof result === "function") return (result as () => Node | Node[] | null)();
19
+ return result;
13
20
  }
14
21
 
15
22
  class BaseComp extends StatefulComponent {
@@ -45,7 +52,7 @@ describe("Lazy decorator", () => {
45
52
  it("returns null from render before visibility", () => {
46
53
  const Wrapped = applyLazy(BaseComp as AnyConstructor);
47
54
  const instance = new Wrapped();
48
- expect(instance.render()).toBeNull();
55
+ expect(resolveRender(instance)).toBeNull();
49
56
  });
50
57
 
51
58
  it("sets _lazyVisible to true when IntersectionObserver is not supported", () => {
@@ -95,7 +102,7 @@ describe("Lazy decorator", () => {
95
102
  (instance as unknown as { _anchor: Comment })._anchor = anchor;
96
103
 
97
104
  instance.onMount?.();
98
- expect(instance.render()).toBeNull();
105
+ expect(resolveRender(instance)).toBeNull();
99
106
 
100
107
  intersectionCallback(
101
108
  [{ isIntersecting: true } as IntersectionObserverEntry],
@@ -103,7 +110,7 @@ describe("Lazy decorator", () => {
103
110
  );
104
111
 
105
112
  expect(parent.style.minHeight).toBe("");
106
- expect(instance.render()).not.toBeNull(); // component became visible after intersection
113
+ expect(resolveRender(instance)).not.toBeNull(); // component became visible after intersection
107
114
  expect(mockDisconnect).toHaveBeenCalled();
108
115
  document.body.removeChild(parent);
109
116
  });
@@ -153,7 +160,7 @@ describe("Lazy decorator", () => {
153
160
  (instance as unknown as { _anchor: Comment })._anchor = anchor;
154
161
 
155
162
  instance.onMount?.();
156
- expect(instance.render()).toBeNull(); // not yet visible
163
+ expect(resolveRender(instance)).toBeNull(); // not yet visible
157
164
 
158
165
  intersectionCallback(
159
166
  [{ isIntersecting: true } as IntersectionObserverEntry],
@@ -161,7 +168,7 @@ describe("Lazy decorator", () => {
161
168
  );
162
169
 
163
170
  // Now visible: render() must delegate to the original render()
164
- const result = instance.render();
171
+ const result = resolveRender(instance);
165
172
  expect(result).not.toBeNull();
166
173
  document.body.removeChild(parent);
167
174
  });
@@ -184,12 +191,83 @@ describe("Lazy decorator", () => {
184
191
  null as unknown as IntersectionObserver,
185
192
  );
186
193
 
187
- expect(instance.render()).toBeNull();
194
+ expect(resolveRender(instance)).toBeNull();
188
195
  expect(mockDisconnect).not.toHaveBeenCalled();
189
196
  expect(parent.style.minHeight).toBe("200px");
190
197
  document.body.removeChild(parent);
191
198
  });
192
199
 
200
+ it("accepts a LazyOptions object instead of a number (line 74 false branch)", () => {
201
+ const Wrapped = applyLazy(BaseComp as AnyConstructor, { placeholder: 150, rootMargin: "50px" });
202
+ const instance = new Wrapped();
203
+
204
+ const parent = document.createElement("div");
205
+ document.body.appendChild(parent);
206
+ const anchor = document.createComment("end");
207
+ parent.appendChild(anchor);
208
+ (instance as unknown as { _anchor: Comment })._anchor = anchor;
209
+
210
+ instance.onMount?.();
211
+ expect(parent.style.minHeight).toBe("150px");
212
+ document.body.removeChild(parent);
213
+ });
214
+
215
+ it("uses root.current as the IntersectionObserver root when provided (line 42 false branch)", () => {
216
+ const rootEl = document.createElement("div");
217
+ document.body.appendChild(rootEl);
218
+
219
+ let capturedInit: IntersectionObserverInit | undefined;
220
+ globalThis.IntersectionObserver = class {
221
+ constructor(cb: IntersectionObserverCallback, init?: IntersectionObserverInit) {
222
+ intersectionCallback = cb;
223
+ capturedInit = init;
224
+ }
225
+ observe = mockObserve;
226
+ disconnect = mockDisconnect;
227
+ } as unknown as typeof IntersectionObserver;
228
+
229
+ const Wrapped = applyLazy(BaseComp as AnyConstructor, { root: { current: rootEl } });
230
+ const instance = new Wrapped();
231
+
232
+ const parent = document.createElement("div");
233
+ document.body.appendChild(parent);
234
+ const anchor = document.createComment("end");
235
+ parent.appendChild(anchor);
236
+ (instance as unknown as { _anchor: Comment })._anchor = anchor;
237
+
238
+ instance.onMount?.();
239
+ expect(capturedInit?.root).toBe(rootEl);
240
+
241
+ document.body.removeChild(parent);
242
+ document.body.removeChild(rootEl);
243
+ });
244
+
245
+ it("falls back to null root when root.current is null (line 42 ?? null branch)", () => {
246
+ let capturedInit: IntersectionObserverInit | undefined;
247
+ globalThis.IntersectionObserver = class {
248
+ constructor(cb: IntersectionObserverCallback, init?: IntersectionObserverInit) {
249
+ intersectionCallback = cb;
250
+ capturedInit = init;
251
+ }
252
+ observe = mockObserve;
253
+ disconnect = mockDisconnect;
254
+ } as unknown as typeof IntersectionObserver;
255
+
256
+ const Wrapped = applyLazy(BaseComp as AnyConstructor, { root: { current: null } });
257
+ const instance = new Wrapped();
258
+
259
+ const parent = document.createElement("div");
260
+ document.body.appendChild(parent);
261
+ const anchor = document.createComment("end");
262
+ parent.appendChild(anchor);
263
+ (instance as unknown as { _anchor: Comment })._anchor = anchor;
264
+
265
+ instance.onMount?.();
266
+ expect(capturedInit?.root).toBeNull();
267
+
268
+ document.body.removeChild(parent);
269
+ });
270
+
193
271
  it("does not re-apply minHeight on remount when already visible", () => {
194
272
  const Wrapped = applyLazy(BaseComp as AnyConstructor, 300);
195
273
  const instance = new Wrapped();
@@ -967,7 +967,7 @@ describe("@Compose decorator — new cases", () => {
967
967
  }).toThrow("setup failed");
968
968
  });
969
969
 
970
- it("string argument resolution where the instance property is undefined", () => {
970
+ it("string argument that matches no instance property falls back to the literal string", () => {
971
971
  class ArgComposable {
972
972
  constructor(private val: unknown) {}
973
973
  setup() { return { resolved: this.val }; }
@@ -978,11 +978,79 @@ describe("@Compose decorator — new cases", () => {
978
978
  Compose(ArgComposable as any, "missingProp")(undefined, ctx);
979
979
 
980
980
  const instance = new TestComponent();
981
- // missingProp is not set on instance — resolves to undefined
981
+ // missingProp is not set on instance — falls back to the literal "missingProp"
982
982
  run(instance);
983
983
 
984
984
  const view = (instance as unknown as Record<string, unknown>).strArgUndef as Record<string, unknown>;
985
- expect(view.resolved).toBeUndefined();
985
+ expect(view.resolved).toBe("missingProp");
986
+ });
987
+
988
+ it("string argument that matches an instance property resolves to its value", () => {
989
+ class ArgComposable {
990
+ constructor(private val: unknown) {}
991
+ setup() { return { resolved: this.val }; }
992
+ }
993
+
994
+ const { ctx, run } = fieldCtx("strArgFound");
995
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
996
+ Compose(ArgComposable as any, "myProp")(undefined, ctx);
997
+
998
+ const instance = new TestComponent();
999
+ (instance as unknown as Record<string, unknown>).myProp = 42;
1000
+ run(instance);
1001
+
1002
+ const view = (instance as unknown as Record<string, unknown>).strArgFound as Record<string, unknown>;
1003
+ expect(view.resolved).toBe(42);
1004
+ });
1005
+ });
1006
+
1007
+ // ── getter() helper ───────────────────────────────────────────────────────────
1008
+
1009
+ import { getter } from "../properties/compose";
1010
+
1011
+ describe("getter() factory", () => {
1012
+ it("resolves to a function that reads the named property from the instance", () => {
1013
+ class ArgComposable {
1014
+ constructor(private getVal: () => unknown) {}
1015
+ setup() { return { current: this.getVal() }; }
1016
+ }
1017
+
1018
+ const { ctx, run } = fieldCtx("withGetter");
1019
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1020
+ Compose(ArgComposable as any, getter("dynProp"))(undefined, ctx);
1021
+
1022
+ const instance = new TestComponent();
1023
+ (instance as unknown as Record<string, unknown>).dynProp = 99;
1024
+ run(instance);
1025
+
1026
+ const view = (instance as unknown as Record<string, unknown>).withGetter as Record<string, unknown>;
1027
+ expect(view.current).toBe(99);
1028
+ });
1029
+
1030
+ it("getter() produces a live reader — reads the property at call time", () => {
1031
+ class ArgComposable {
1032
+ constructor(private getVal: () => unknown) {}
1033
+ setup() { return { read: this.getVal }; }
1034
+ }
1035
+
1036
+ const { ctx, run } = fieldCtx("liveGetter");
1037
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1038
+ Compose(ArgComposable as any, getter("mutableProp"))(undefined, ctx);
1039
+
1040
+ const instance = new TestComponent();
1041
+ (instance as unknown as Record<string, unknown>).mutableProp = "first";
1042
+ run(instance);
1043
+
1044
+ const view = (instance as unknown as Record<string, unknown>).liveGetter as Record<string, () => unknown>;
1045
+ expect(view.read()).toBe("first");
1046
+
1047
+ (instance as unknown as Record<string, unknown>).mutableProp = "second";
1048
+ expect(view.read()).toBe("second");
1049
+ });
1050
+
1051
+ it("getter() is marked as a ComposeFactory (has __isComposeFactory)", () => {
1052
+ const g = getter("foo");
1053
+ expect((g as unknown as Record<string, unknown>).__isComposeFactory).toBe(true);
986
1054
  });
987
1055
  });
988
1056
 
@@ -1,3 +1,2 @@
1
1
  export { Component } from "./component";
2
2
  export { Lazy } from "./lazy";
3
- export { Virtual } from "./virtual";
@@ -2,13 +2,26 @@ import { type RootComponent, signal } from "@praxisjs/core/internal";
2
2
 
3
3
  import { ClassBehavior, createClassDecorator, type ClassEnhancement } from "../create-class-decorator";
4
4
 
5
+ export interface LazyOptions {
6
+ /** Placeholder height in pixels while the component is off-screen. */
7
+ placeholder?: number;
8
+ /**
9
+ * Scroll container to observe against. Defaults to the viewport (`null`).
10
+ * Pass a ref object `{ current: HTMLElement | null }` to scope intersection
11
+ * to a specific scrollable container.
12
+ */
13
+ root?: { current: HTMLElement | null } | null;
14
+ /** Extra margin around the root before triggering (e.g. `"200px"`). Defaults to `"100px"`. */
15
+ rootMargin?: string;
16
+ }
17
+
5
18
  class LazyBehavior extends ClassBehavior {
6
- constructor(private readonly placeholder: number) {
19
+ constructor(private readonly options: LazyOptions) {
7
20
  super();
8
21
  }
9
22
 
10
23
  create(instance: RootComponent): ClassEnhancement {
11
- const placeholder = this.placeholder;
24
+ const { placeholder = 200, root, rootMargin = "100px" } = this.options;
12
25
  const visible = signal(false);
13
26
  let observer: IntersectionObserver | undefined;
14
27
 
@@ -25,6 +38,9 @@ class LazyBehavior extends ClassBehavior {
25
38
 
26
39
  if (!visible()) el.style.minHeight = `${String(placeholder)}px`;
27
40
 
41
+ const rootEl =
42
+ root === undefined || root === null ? null : root.current ?? null;
43
+
28
44
  observer = new IntersectionObserver(
29
45
  (entries) => {
30
46
  if (entries[0]?.isIntersecting) {
@@ -33,7 +49,7 @@ class LazyBehavior extends ClassBehavior {
33
49
  observer?.disconnect();
34
50
  }
35
51
  },
36
- { rootMargin: "100px" },
52
+ { root: rootEl, rootMargin },
37
53
  );
38
54
 
39
55
  observer.observe(el);
@@ -44,13 +60,19 @@ class LazyBehavior extends ClassBehavior {
44
60
  },
45
61
 
46
62
  render(originalRender) {
47
- if (!visible()) return null;
48
- return originalRender();
63
+ return (() => {
64
+ if (!visible()) return null;
65
+ return originalRender();
66
+ }) as unknown as Node;
49
67
  },
50
68
  };
51
69
  }
52
70
  }
53
71
 
54
- export function Lazy(placeholder = 200) {
55
- return createClassDecorator(new LazyBehavior(placeholder));
72
+ export function Lazy(placeholderOrOptions: number | LazyOptions = 200) {
73
+ const options: LazyOptions =
74
+ typeof placeholderOrOptions === "number"
75
+ ? { placeholder: placeholderOrOptions }
76
+ : placeholderOrOptions;
77
+ return createClassDecorator(new LazyBehavior(options));
56
78
  }
@@ -19,6 +19,12 @@ export abstract class ClassBehavior {
19
19
  initialize?(_Enhanced: new (...args: any[]) => unknown, _original: new (...args: any[]) => unknown): void;
20
20
  }
21
21
 
22
+ // Tracks instances whose render is currently being called as `originalRender`
23
+ // from within an enhancement. When set, the Enhanced render skips the
24
+ // this._enh.render dispatch so stacked decorators (e.g. @Lazy + @Component)
25
+ // don't re-enter the outermost enhancement recursively.
26
+ const _originalRenderInProgress = new WeakSet();
27
+
22
28
  export function createClassDecorator(behavior: ClassBehavior) {
23
29
  return function <
24
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -40,12 +46,18 @@ export function createClassDecorator(behavior: ClassBehavior) {
40
46
  }
41
47
 
42
48
  render(...args: unknown[]): Node | Node[] | null {
43
- const originalRender = () =>
44
- (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
45
- if (this._enh.render) {
49
+ const originalRender = () => {
50
+ _originalRenderInProgress.add(this);
51
+ try {
52
+ return (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
53
+ } finally {
54
+ _originalRenderInProgress.delete(this);
55
+ }
56
+ };
57
+ if (this._enh.render && !_originalRenderInProgress.has(this)) {
46
58
  return this._enh.render(originalRender);
47
59
  }
48
- return originalRender();
60
+ return (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
49
61
  }
50
62
  };
51
63
 
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Component, Lazy, Virtual } from "./component";
1
+ export { Component, Lazy } from "./component";
2
2
  export {
3
3
  Memo,
4
4
  Bind,
@@ -23,6 +23,7 @@ export {
23
23
  History,
24
24
  type HistoryOf,
25
25
  Compose,
26
+ getter,
26
27
  Resource,
27
28
  type ResourceInstance,
28
29
  type ResourceOptions,