@praxisjs/decorators 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/__tests__/component.test.d.ts +2 -0
- package/dist/__tests__/component.test.d.ts.map +1 -0
- package/dist/__tests__/component.test.js +146 -0
- package/dist/__tests__/component.test.js.map +1 -0
- package/dist/__tests__/compose.test.d.ts +2 -0
- package/dist/__tests__/compose.test.d.ts.map +1 -0
- package/dist/__tests__/compose.test.js +171 -0
- package/dist/__tests__/compose.test.js.map +1 -0
- package/dist/__tests__/events.test.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.js +216 -0
- package/dist/__tests__/events.test.js.map +1 -0
- package/dist/__tests__/functions.test.js +283 -31
- package/dist/__tests__/functions.test.js.map +1 -1
- package/dist/__tests__/lazy.test.d.ts +2 -0
- package/dist/__tests__/lazy.test.d.ts.map +1 -0
- package/dist/__tests__/lazy.test.js +163 -0
- package/dist/__tests__/lazy.test.js.map +1 -0
- package/dist/__tests__/memo.test.js +71 -22
- package/dist/__tests__/memo.test.js.map +1 -1
- package/dist/__tests__/properties.test.js +191 -1
- package/dist/__tests__/properties.test.js.map +1 -1
- package/dist/__tests__/resource.test.d.ts +2 -0
- package/dist/__tests__/resource.test.d.ts.map +1 -0
- package/dist/__tests__/resource.test.js +103 -0
- package/dist/__tests__/resource.test.js.map +1 -0
- package/dist/__tests__/slot.test.d.ts +2 -0
- package/dist/__tests__/slot.test.d.ts.map +1 -0
- package/dist/__tests__/slot.test.js +147 -0
- package/dist/__tests__/slot.test.js.map +1 -0
- package/dist/__tests__/virtual.test.d.ts +2 -0
- package/dist/__tests__/virtual.test.d.ts.map +1 -0
- package/dist/__tests__/virtual.test.js +242 -0
- package/dist/__tests__/virtual.test.js.map +1 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +15 -10
- package/dist/component/component.js.map +1 -1
- package/dist/component/lazy.d.ts +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +29 -27
- package/dist/component/lazy.js.map +1 -1
- package/dist/component/virtual.d.ts +1 -1
- package/dist/component/virtual.d.ts.map +1 -1
- package/dist/component/virtual.js +46 -50
- package/dist/component/virtual.js.map +1 -1
- package/dist/create-class-decorator.d.ts +16 -0
- package/dist/create-class-decorator.d.ts.map +1 -0
- package/dist/create-class-decorator.js +35 -0
- package/dist/create-class-decorator.js.map +1 -0
- package/dist/create-field-decorator.d.ts +14 -0
- package/dist/create-field-decorator.d.ts.map +1 -0
- package/dist/create-field-decorator.js +37 -0
- package/dist/create-field-decorator.js.map +1 -0
- package/dist/create-getter-decorator.d.ts +18 -0
- package/dist/create-getter-decorator.d.ts.map +1 -0
- package/dist/create-getter-decorator.js +15 -0
- package/dist/create-getter-decorator.js.map +1 -0
- package/dist/create-lifecycle-method-decorator.d.ts +6 -0
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
- package/dist/create-lifecycle-method-decorator.js +22 -0
- package/dist/create-lifecycle-method-decorator.js.map +1 -0
- package/dist/create-method-decorator.d.ts +6 -0
- package/dist/create-method-decorator.d.ts.map +1 -0
- package/dist/create-method-decorator.js +14 -0
- package/dist/create-method-decorator.js.map +1 -0
- package/dist/events/emit.d.ts +1 -1
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +24 -29
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +1 -2
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +15 -32
- package/dist/events/on-command.js.map +1 -1
- package/dist/functions/bind.d.ts +1 -2
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -11
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +1 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +12 -20
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/log.d.ts +1 -2
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +40 -38
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +1 -2
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +15 -27
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +1 -2
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +14 -12
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +1 -2
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +23 -20
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +1 -2
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +9 -16
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/watch.d.ts +1 -1
- package/dist/functions/watch.d.ts.map +1 -1
- package/dist/functions/watch.js +25 -31
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -2
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +10 -26
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/properties/compose.d.ts +3 -0
- package/dist/properties/compose.d.ts.map +1 -0
- package/dist/properties/compose.js +40 -0
- package/dist/properties/compose.js.map +1 -0
- package/dist/properties/computed.d.ts +1 -2
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +10 -9
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +37 -32
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +2 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +2 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +1 -2
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +13 -29
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +1 -2
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +21 -25
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/resource.d.ts +14 -0
- package/dist/properties/resource.d.ts.map +1 -0
- package/dist/properties/resource.js +27 -0
- package/dist/properties/resource.js.map +1 -0
- package/dist/properties/slot.d.ts +1 -2
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +15 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts +1 -2
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +13 -18
- package/dist/properties/state.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/component.test.ts +166 -0
- package/src/__tests__/compose.test.ts +202 -0
- package/src/__tests__/events.test.ts +268 -0
- package/src/__tests__/functions.test.ts +368 -30
- package/src/__tests__/lazy.test.ts +218 -0
- package/src/__tests__/memo.test.ts +89 -22
- package/src/__tests__/properties.test.ts +249 -1
- package/src/__tests__/resource.test.ts +113 -0
- package/src/__tests__/slot.test.ts +179 -0
- package/src/__tests__/virtual.test.ts +260 -0
- package/src/component/component.ts +27 -12
- package/src/component/lazy.ts +33 -35
- package/src/component/virtual.tsx +62 -75
- package/src/create-class-decorator.ts +61 -0
- package/src/create-field-decorator.ts +66 -0
- package/src/create-getter-decorator.ts +52 -0
- package/src/create-lifecycle-method-decorator.ts +38 -0
- package/src/create-method-decorator.ts +26 -0
- package/src/events/emit.ts +22 -33
- package/src/events/on-command.ts +22 -47
- package/src/functions/bind.ts +4 -15
- package/src/functions/debounce.ts +11 -26
- package/src/functions/log.ts +40 -43
- package/src/functions/memo.ts +15 -45
- package/src/functions/once.ts +13 -16
- package/src/functions/retry.ts +21 -22
- package/src/functions/throttle.ts +9 -23
- package/src/functions/watch.ts +27 -34
- package/src/functions/when.ts +11 -36
- package/src/index.ts +28 -0
- package/src/properties/compose.ts +53 -0
- package/src/properties/computed.ts +11 -13
- package/src/properties/history.ts +42 -41
- package/src/properties/index.ts +2 -0
- package/src/properties/persisted.ts +17 -58
- package/src/properties/prop.ts +21 -31
- package/src/properties/resource.ts +34 -0
- package/src/properties/slot.ts +17 -30
- package/src/properties/state.ts +14 -24
package/src/component/lazy.ts
CHANGED
|
@@ -1,58 +1,56 @@
|
|
|
1
1
|
import { type RootComponent, signal } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
-
return function <T extends new (...args: any[]) => RootComponent>(
|
|
6
|
-
constructor: T,
|
|
7
|
-
_context: ClassDecoratorContext,
|
|
8
|
-
): T {
|
|
9
|
-
return class LazyWrapper extends constructor {
|
|
10
|
-
private readonly _lazyVisible = signal(false);
|
|
11
|
-
private _observer?: IntersectionObserver;
|
|
12
|
-
private readonly _originalRender: () => Node | Node[] | null =
|
|
13
|
-
this.render.bind(this);
|
|
3
|
+
import { ClassBehavior, createClassDecorator, type ClassEnhancement } from "../create-class-decorator";
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
class LazyBehavior extends ClassBehavior {
|
|
6
|
+
constructor(private readonly placeholder: number) {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
17
9
|
|
|
18
|
-
|
|
10
|
+
create(instance: RootComponent): ClassEnhancement {
|
|
11
|
+
const placeholder = this.placeholder;
|
|
12
|
+
const visible = signal(false);
|
|
13
|
+
let observer: IntersectionObserver | undefined;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
onMount() {
|
|
19
17
|
const el =
|
|
20
|
-
(
|
|
21
|
-
?.parentElement ?? null;
|
|
18
|
+
(instance as { _anchor?: Comment })._anchor?.parentElement ?? null;
|
|
22
19
|
if (!el) return;
|
|
23
20
|
|
|
24
21
|
if (!("IntersectionObserver" in window)) {
|
|
25
|
-
|
|
22
|
+
visible.set(true);
|
|
26
23
|
return;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
if (!
|
|
30
|
-
el.style.minHeight = `${String(placeholder)}px`;
|
|
31
|
-
}
|
|
26
|
+
if (!visible()) el.style.minHeight = `${String(placeholder)}px`;
|
|
32
27
|
|
|
33
|
-
|
|
28
|
+
observer = new IntersectionObserver(
|
|
34
29
|
(entries) => {
|
|
35
30
|
if (entries[0]?.isIntersecting) {
|
|
36
|
-
|
|
31
|
+
visible.set(true);
|
|
37
32
|
el.style.minHeight = "";
|
|
38
|
-
|
|
33
|
+
observer?.disconnect();
|
|
39
34
|
}
|
|
40
35
|
},
|
|
41
36
|
{ rootMargin: "100px" },
|
|
42
37
|
);
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
}
|
|
39
|
+
observer.observe(el);
|
|
40
|
+
},
|
|
46
41
|
|
|
47
42
|
onUnmount() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
observer?.disconnect();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
render(originalRender) {
|
|
47
|
+
if (!visible()) return null;
|
|
48
|
+
return originalRender();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function Lazy(placeholder = 200) {
|
|
55
|
+
return createClassDecorator(new LazyBehavior(placeholder));
|
|
58
56
|
}
|
|
@@ -1,81 +1,76 @@
|
|
|
1
1
|
import { computed, effect, type RootComponent, signal } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
+
import { ClassBehavior, createClassDecorator, type ClassEnhancement } from "../create-class-decorator";
|
|
4
|
+
|
|
3
5
|
interface VirtualHost {
|
|
4
6
|
_anchor?: Comment;
|
|
5
7
|
items?: unknown[];
|
|
6
8
|
renderItem(item: unknown, index: number): Node | Node[] | null;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
class VirtualBehavior extends ClassBehavior {
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly itemHeight: number,
|
|
14
|
+
private readonly buffer: number,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
create(instance: RootComponent): ClassEnhancement {
|
|
20
|
+
const { itemHeight, buffer } = this;
|
|
21
|
+
const host = instance as unknown as VirtualHost;
|
|
22
|
+
const scrollTop = signal(0);
|
|
23
|
+
const viewHeight = signal(600);
|
|
24
|
+
let container: HTMLElement | undefined;
|
|
25
|
+
let scrollHandler: (() => void) | undefined;
|
|
26
|
+
const cleanups: Array<() => void> = [];
|
|
27
|
+
|
|
28
|
+
return {
|
|
22
29
|
onMount() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Use the end anchor set by the runtime to locate the parent element
|
|
26
|
-
this._container =
|
|
27
|
-
(this as unknown as VirtualHost)._anchor?.parentElement ?? undefined;
|
|
28
|
-
|
|
29
|
-
if (!this._container) return;
|
|
30
|
+
container = host._anchor?.parentElement ?? undefined;
|
|
31
|
+
if (!container) return;
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
container.style.overflowY = "auto";
|
|
34
|
+
container.style.position = "relative";
|
|
35
|
+
viewHeight.set(container.clientHeight || 600);
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this._scrollHandler = () => {
|
|
37
|
-
if (this._container) this._scrollTop.set(this._container.scrollTop);
|
|
37
|
+
scrollHandler = () => {
|
|
38
|
+
if (container) scrollTop.set(container.scrollTop);
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
40
|
+
container.addEventListener("scroll", scrollHandler);
|
|
41
|
+
},
|
|
42
42
|
|
|
43
43
|
onUnmount() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this._container.removeEventListener("scroll", this._scrollHandler);
|
|
44
|
+
if (container && scrollHandler) {
|
|
45
|
+
container.removeEventListener("scroll", scrollHandler);
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
47
|
+
cleanups.forEach((c) => { c(); });
|
|
48
|
+
cleanups.length = 0;
|
|
49
|
+
},
|
|
51
50
|
|
|
52
|
-
render() {
|
|
53
|
-
const host = this as unknown as VirtualHost;
|
|
51
|
+
render(originalRender) {
|
|
54
52
|
const items = host.items ?? [];
|
|
55
53
|
const total = items.length;
|
|
56
54
|
const totalH = total * itemHeight;
|
|
57
55
|
|
|
58
56
|
if (typeof host.renderItem !== "function") {
|
|
59
57
|
console.warn(
|
|
60
|
-
`[Virtual]
|
|
58
|
+
`[Virtual] component must implement renderItem(item, index)`,
|
|
61
59
|
);
|
|
62
|
-
return
|
|
60
|
+
return originalRender();
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
const renderItem = host.renderItem.bind(host);
|
|
66
64
|
|
|
67
|
-
const startIdx = computed(() =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return Math.min(total - 1, end);
|
|
77
|
-
});
|
|
78
|
-
|
|
65
|
+
const startIdx = computed(() =>
|
|
66
|
+
Math.max(0, Math.floor(scrollTop() / itemHeight) - buffer),
|
|
67
|
+
);
|
|
68
|
+
const endIdx = computed(() =>
|
|
69
|
+
Math.min(
|
|
70
|
+
total - 1,
|
|
71
|
+
Math.ceil((scrollTop() + viewHeight()) / itemHeight) + buffer,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
79
74
|
const visibleItems = computed(() => {
|
|
80
75
|
const result: Array<{ item: unknown; index: number }> = [];
|
|
81
76
|
for (let i = startIdx(); i <= endIdx(); i++) {
|
|
@@ -83,31 +78,23 @@ export function Virtual(itemHeight: number, buffer = 3) {
|
|
|
83
78
|
}
|
|
84
79
|
return result;
|
|
85
80
|
});
|
|
86
|
-
|
|
87
81
|
const offsetTop = computed(() => startIdx() * itemHeight);
|
|
88
|
-
const offsetBottom = computed(
|
|
89
|
-
() => (total - 1 - endIdx()) * itemHeight,
|
|
90
|
-
);
|
|
82
|
+
const offsetBottom = computed(() => (total - 1 - endIdx()) * itemHeight);
|
|
91
83
|
|
|
92
84
|
const outer = document.createElement("div");
|
|
93
|
-
outer.setAttribute(
|
|
94
|
-
"style",
|
|
95
|
-
`height:${String(totalH)}px; position:relative;`,
|
|
96
|
-
);
|
|
85
|
+
outer.setAttribute("style", `height:${String(totalH)}px; position:relative;`);
|
|
97
86
|
|
|
98
87
|
const spacerTop = document.createElement("div");
|
|
99
|
-
|
|
88
|
+
cleanups.push(
|
|
100
89
|
effect(() => {
|
|
101
90
|
spacerTop.setAttribute("style", `height:${String(offsetTop())}px;`);
|
|
102
91
|
}),
|
|
103
92
|
);
|
|
104
93
|
|
|
105
94
|
const itemsSlot = document.createElement("div");
|
|
106
|
-
|
|
95
|
+
cleanups.push(
|
|
107
96
|
effect(() => {
|
|
108
|
-
while (itemsSlot.firstChild)
|
|
109
|
-
itemsSlot.removeChild(itemsSlot.firstChild);
|
|
110
|
-
}
|
|
97
|
+
while (itemsSlot.firstChild) itemsSlot.removeChild(itemsSlot.firstChild);
|
|
111
98
|
visibleItems().forEach(({ item, index }) => {
|
|
112
99
|
const wrapper = document.createElement("div");
|
|
113
100
|
wrapper.setAttribute(
|
|
@@ -116,10 +103,9 @@ export function Virtual(itemHeight: number, buffer = 3) {
|
|
|
116
103
|
);
|
|
117
104
|
const rendered = renderItem(item, index);
|
|
118
105
|
if (rendered) {
|
|
119
|
-
|
|
120
|
-
|
|
106
|
+
(Array.isArray(rendered) ? rendered.flat() : [rendered]).forEach(
|
|
107
|
+
(n) => { wrapper.appendChild(n); },
|
|
121
108
|
);
|
|
122
|
-
nodes.forEach((n) => wrapper.appendChild(n));
|
|
123
109
|
}
|
|
124
110
|
itemsSlot.appendChild(wrapper);
|
|
125
111
|
});
|
|
@@ -127,12 +113,9 @@ export function Virtual(itemHeight: number, buffer = 3) {
|
|
|
127
113
|
);
|
|
128
114
|
|
|
129
115
|
const spacerBottom = document.createElement("div");
|
|
130
|
-
|
|
116
|
+
cleanups.push(
|
|
131
117
|
effect(() => {
|
|
132
|
-
spacerBottom.setAttribute(
|
|
133
|
-
"style",
|
|
134
|
-
`height:${String(offsetBottom())}px;`,
|
|
135
|
-
);
|
|
118
|
+
spacerBottom.setAttribute("style", `height:${String(offsetBottom())}px;`);
|
|
136
119
|
}),
|
|
137
120
|
);
|
|
138
121
|
|
|
@@ -141,7 +124,11 @@ export function Virtual(itemHeight: number, buffer = 3) {
|
|
|
141
124
|
outer.appendChild(spacerBottom);
|
|
142
125
|
|
|
143
126
|
return outer;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function Virtual(itemHeight: number, buffer = 3) {
|
|
133
|
+
return createClassDecorator(new VirtualBehavior(itemHeight, buffer));
|
|
147
134
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { RootComponent } from "@praxisjs/core/internal";
|
|
2
|
+
|
|
3
|
+
export interface ClassEnhancement {
|
|
4
|
+
onMount?(): void;
|
|
5
|
+
onUnmount?(): void;
|
|
6
|
+
render?(
|
|
7
|
+
originalRender: () => Node | Node[] | null,
|
|
8
|
+
): Node | Node[] | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export abstract class ClassBehavior {
|
|
12
|
+
abstract create(instance: RootComponent): ClassEnhancement;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Called once after the enhanced class is created.
|
|
16
|
+
* Use this to set static properties on the class constructor itself.
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
initialize?(_Enhanced: new (...args: any[]) => unknown, _original: new (...args: any[]) => unknown): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createClassDecorator(behavior: ClassBehavior) {
|
|
23
|
+
return function <
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
T extends new (...args: any[]) => RootComponent<Record<string, any>>,
|
|
26
|
+
>(constructor: T, _context: ClassDecoratorContext): T {
|
|
27
|
+
const Enhanced = class extends constructor {
|
|
28
|
+
private readonly _enh: ClassEnhancement = behavior.create(
|
|
29
|
+
this as unknown as RootComponent,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
onMount() {
|
|
33
|
+
super.onMount?.();
|
|
34
|
+
this._enh.onMount?.();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onUnmount() {
|
|
38
|
+
super.onUnmount?.();
|
|
39
|
+
this._enh.onUnmount?.();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
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) {
|
|
46
|
+
return this._enh.render(originalRender);
|
|
47
|
+
}
|
|
48
|
+
return originalRender();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// If the original class has no render, remove the wrapper so instances see `undefined`.
|
|
53
|
+
if (typeof (constructor.prototype as { render?: unknown }).render !== "function") {
|
|
54
|
+
delete (Enhanced.prototype as { render?: unknown }).render;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Object.defineProperty(Enhanced, "name", { value: constructor.name });
|
|
58
|
+
behavior.initialize?.(Enhanced, constructor);
|
|
59
|
+
return Enhanced as unknown as T;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
export interface FieldBinding {
|
|
4
|
+
/** Replaces the decorated property. If absent, the original property is left untouched. */
|
|
5
|
+
descriptor?: PropertyDescriptor;
|
|
6
|
+
/** Extra properties to define on the instance (e.g. companion properties like `nameHistory`). */
|
|
7
|
+
additional?: Record<string, PropertyDescriptor>;
|
|
8
|
+
onMount?: () => void;
|
|
9
|
+
onUnmount?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FieldBehavior {
|
|
13
|
+
bind(
|
|
14
|
+
instance: StatefulComponent,
|
|
15
|
+
name: string,
|
|
16
|
+
initialValue: unknown,
|
|
17
|
+
): FieldBinding;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createFieldDecorator(behavior: FieldBehavior) {
|
|
21
|
+
return function (
|
|
22
|
+
_value: undefined,
|
|
23
|
+
context: ClassFieldDecoratorContext<StatefulComponent>,
|
|
24
|
+
): void {
|
|
25
|
+
context.addInitializer(function (this: unknown) {
|
|
26
|
+
const instance = this as StatefulComponent & Record<string, unknown>;
|
|
27
|
+
const name = context.name as string;
|
|
28
|
+
const initialValue = instance[name];
|
|
29
|
+
|
|
30
|
+
const { descriptor, additional, onMount, onUnmount } = behavior.bind(
|
|
31
|
+
instance,
|
|
32
|
+
name,
|
|
33
|
+
initialValue,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (descriptor) {
|
|
37
|
+
Reflect.deleteProperty(instance, name);
|
|
38
|
+
Object.defineProperty(instance, name, {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
...descriptor,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const [key, desc] of Object.entries(additional ?? {})) {
|
|
46
|
+
Object.defineProperty(instance, key, {
|
|
47
|
+
configurable: true,
|
|
48
|
+
...desc,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (onMount ?? onUnmount) {
|
|
53
|
+
const prevMount = instance.onMount?.bind(instance);
|
|
54
|
+
const prevUnmount = instance.onUnmount?.bind(instance);
|
|
55
|
+
instance.onMount = () => {
|
|
56
|
+
prevMount?.();
|
|
57
|
+
onMount?.();
|
|
58
|
+
};
|
|
59
|
+
instance.onUnmount = () => {
|
|
60
|
+
prevUnmount?.();
|
|
61
|
+
onUnmount?.();
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface GetterBehavior {
|
|
2
|
+
/**
|
|
3
|
+
* Given the original getter and the current instance, returns a function
|
|
4
|
+
* that produces the getter value. Called on every property access.
|
|
5
|
+
*/
|
|
6
|
+
wrap(
|
|
7
|
+
original: (this: object) => unknown,
|
|
8
|
+
instance: object,
|
|
9
|
+
): () => unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createGetterDecorator(behavior: GetterBehavior) {
|
|
13
|
+
return function <This, T>(
|
|
14
|
+
target: (this: This) => T,
|
|
15
|
+
_context: ClassGetterDecoratorContext<This, T>,
|
|
16
|
+
): (this: This) => T {
|
|
17
|
+
return function (this: This): T {
|
|
18
|
+
return behavior.wrap(
|
|
19
|
+
target as (this: object) => unknown,
|
|
20
|
+
this as object,
|
|
21
|
+
)() as T;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GetterObserverBehavior {
|
|
27
|
+
/**
|
|
28
|
+
* Called once per instance when the class is initialized.
|
|
29
|
+
* Use this to set up observation side-effects on the getter without
|
|
30
|
+
* changing its return value (e.g. tracking changes for devtools).
|
|
31
|
+
*/
|
|
32
|
+
observe(
|
|
33
|
+
getter: (this: object) => unknown,
|
|
34
|
+
instance: object,
|
|
35
|
+
name: string,
|
|
36
|
+
): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createGetterObserverDecorator(behavior: GetterObserverBehavior) {
|
|
40
|
+
return function <This, T>(
|
|
41
|
+
target: (this: This) => T,
|
|
42
|
+
context: ClassGetterDecoratorContext<This, T>,
|
|
43
|
+
): void {
|
|
44
|
+
context.addInitializer(function (this: unknown) {
|
|
45
|
+
behavior.observe(
|
|
46
|
+
target as (this: object) => unknown,
|
|
47
|
+
this as object,
|
|
48
|
+
context.name as string,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
export interface LifecycleMethodBehavior<
|
|
4
|
+
T extends StatefulComponent = StatefulComponent,
|
|
5
|
+
> {
|
|
6
|
+
register(
|
|
7
|
+
callback: (...args: unknown[]) => void,
|
|
8
|
+
instance: T,
|
|
9
|
+
): (() => void) | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createLifecycleMethodDecorator<
|
|
13
|
+
T extends StatefulComponent = StatefulComponent,
|
|
14
|
+
>(behavior: LifecycleMethodBehavior<T>) {
|
|
15
|
+
return function (
|
|
16
|
+
value: (this: T, ...args: unknown[]) => void,
|
|
17
|
+
_context: ClassMethodDecoratorContext<T>,
|
|
18
|
+
): void {
|
|
19
|
+
_context.addInitializer(function (this: unknown) {
|
|
20
|
+
const instance = this as T;
|
|
21
|
+
const prevMount = instance.onMount?.bind(instance);
|
|
22
|
+
const prevUnmount = instance.onUnmount?.bind(instance);
|
|
23
|
+
let cleanup: (() => void) | undefined;
|
|
24
|
+
|
|
25
|
+
instance.onMount = function (this: T) {
|
|
26
|
+
prevMount?.call(this);
|
|
27
|
+
const result = behavior.register(value.bind(this), this);
|
|
28
|
+
if (result) cleanup = result;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
instance.onUnmount = function (this: T) {
|
|
32
|
+
prevUnmount?.call(this);
|
|
33
|
+
cleanup?.();
|
|
34
|
+
cleanup = undefined;
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
export interface MethodBehavior {
|
|
4
|
+
wrap(
|
|
5
|
+
original: (...args: unknown[]) => unknown,
|
|
6
|
+
instance: object,
|
|
7
|
+
name: string,
|
|
8
|
+
): (...args: unknown[]) => unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createMethodDecorator(behavior: MethodBehavior) {
|
|
12
|
+
return function (
|
|
13
|
+
value: (this: object, ...args: unknown[]) => unknown,
|
|
14
|
+
context: ClassMethodDecoratorContext<StatefulComponent>,
|
|
15
|
+
): void {
|
|
16
|
+
context.addInitializer(function (this: unknown) {
|
|
17
|
+
const instance = this as object;
|
|
18
|
+
const name = context.name as string;
|
|
19
|
+
Object.defineProperty(instance, name, {
|
|
20
|
+
value: behavior.wrap(value, instance, name),
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
package/src/events/emit.ts
CHANGED
|
@@ -1,42 +1,31 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
+
import { createMethodDecorator } from "../create-method-decorator";
|
|
3
4
|
import { readProp } from "./helper";
|
|
4
5
|
|
|
5
6
|
export function Emit(propName: string) {
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
this: StatefulComponent,
|
|
13
|
-
...args: unknown[]
|
|
14
|
-
): unknown {
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
16
|
-
const result = value.apply(this, args);
|
|
7
|
+
return createMethodDecorator({
|
|
8
|
+
wrap(original, instance) {
|
|
9
|
+
const comp = instance as StatefulComponent;
|
|
10
|
+
return (...args: unknown[]) => {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
12
|
+
const result = original.apply(instance, args) as any;
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const callback = readProp(comp, propName);
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
16
|
+
if (typeof callback !== "function") return result;
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
if (result !== undefined) {
|
|
19
|
+
(callback as (v: unknown) => void)(result);
|
|
20
|
+
} else if (args.length > 0) {
|
|
21
|
+
(callback as (...a: unknown[]) => void)(...args);
|
|
22
|
+
} else {
|
|
23
|
+
(callback as () => void)();
|
|
24
|
+
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const bound = wrapped.bind(this as StatefulComponent);
|
|
35
|
-
Object.defineProperty(this as object, name, {
|
|
36
|
-
value: bound,
|
|
37
|
-
configurable: true,
|
|
38
|
-
writable: true,
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
};
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
});
|
|
42
31
|
}
|