@praxisjs/decorators 0.8.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -3
- package/dist/__tests__/lazy.test.js +74 -10
- package/dist/__tests__/lazy.test.js.map +1 -1
- package/dist/__tests__/properties.test.js +61 -3
- package/dist/__tests__/properties.test.js.map +1 -1
- package/dist/component/index.d.ts +0 -1
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/index.js +0 -1
- package/dist/component/index.js.map +1 -1
- package/dist/component/lazy.d.ts +15 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +15 -9
- package/dist/component/lazy.js.map +1 -1
- package/dist/create-class-decorator.d.ts.map +1 -1
- package/dist/create-class-decorator.js +16 -3
- package/dist/create-class-decorator.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/properties/compose.d.ts +7 -0
- package/dist/properties/compose.d.ts.map +1 -1
- package/dist/properties/compose.js +18 -3
- package/dist/properties/compose.js.map +1 -1
- package/dist/properties/index.d.ts +1 -1
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +1 -1
- package/dist/properties/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/lazy.test.ts +88 -10
- package/src/__tests__/properties.test.ts +71 -3
- package/src/component/index.ts +0 -1
- package/src/component/lazy.ts +29 -7
- package/src/create-class-decorator.ts +16 -4
- package/src/index.ts +2 -1
- package/src/properties/compose.ts +21 -8
- package/src/properties/index.ts +1 -1
- package/dist/__tests__/virtual.test.d.ts +0 -2
- package/dist/__tests__/virtual.test.d.ts.map +0 -1
- package/dist/__tests__/virtual.test.js +0 -352
- package/dist/__tests__/virtual.test.js.map +0 -1
- package/dist/component/virtual.d.ts +0 -3
- package/dist/component/virtual.d.ts.map +0 -1
- package/dist/component/virtual.js +0 -102
- package/dist/component/virtual.js.map +0 -1
- package/src/__tests__/virtual.test.ts +0 -371
- package/src/component/virtual.tsx +0 -163
package/dist/component/lazy.js
CHANGED
|
@@ -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(
|
|
4
|
+
constructor(options) {
|
|
5
5
|
super();
|
|
6
|
-
this.
|
|
6
|
+
this.options = options;
|
|
7
7
|
}
|
|
8
8
|
create(instance) {
|
|
9
|
-
const placeholder = this.
|
|
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
|
-
}, {
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
return (() => {
|
|
38
|
+
if (!visible())
|
|
39
|
+
return null;
|
|
40
|
+
return originalRender();
|
|
41
|
+
});
|
|
39
42
|
},
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
export function Lazy(
|
|
44
|
-
|
|
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;
|
|
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;
|
|
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 = () =>
|
|
20
|
-
|
|
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
|
|
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;
|
|
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
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,
|
|
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
|
|
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,
|
|
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;
|
|
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) =>
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/properties/index.js
CHANGED
|
@@ -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;
|
|
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
|
@@ -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,
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 —
|
|
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).
|
|
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
|
|
package/src/component/index.ts
CHANGED
package/src/component/lazy.ts
CHANGED
|
@@ -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
|
|
19
|
+
constructor(private readonly options: LazyOptions) {
|
|
7
20
|
super();
|
|
8
21
|
}
|
|
9
22
|
|
|
10
23
|
create(instance: RootComponent): ClassEnhancement {
|
|
11
|
-
const placeholder = this.
|
|
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
|
-
{
|
|
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
|
-
|
|
48
|
-
|
|
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(
|
|
55
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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,
|