@praxisjs/decorators 0.1.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 +14 -0
- package/LICENSE +21 -0
- package/dist/component/component.d.ts +3 -0
- package/dist/component/component.d.ts.map +1 -0
- package/dist/component/component.js +13 -0
- package/dist/component/component.js.map +1 -0
- package/dist/component/index.d.ts +5 -0
- package/dist/component/index.d.ts.map +1 -0
- package/dist/component/index.js +5 -0
- package/dist/component/index.js.map +1 -0
- package/dist/component/lazy.d.ts +3 -0
- package/dist/component/lazy.d.ts.map +1 -0
- package/dist/component/lazy.js +44 -0
- package/dist/component/lazy.js.map +1 -0
- package/dist/component/lifecycle.d.ts +3 -0
- package/dist/component/lifecycle.d.ts.map +1 -0
- package/dist/component/lifecycle.js +35 -0
- package/dist/component/lifecycle.js.map +1 -0
- package/dist/component/memoize.d.ts +5 -0
- package/dist/component/memoize.d.ts.map +1 -0
- package/dist/component/memoize.js +23 -0
- package/dist/component/memoize.js.map +1 -0
- package/dist/component/virtual.d.ts +3 -0
- package/dist/component/virtual.d.ts.map +1 -0
- package/dist/component/virtual.js +66 -0
- package/dist/component/virtual.js.map +1 -0
- package/dist/events/command.d.ts +6 -0
- package/dist/events/command.d.ts.map +1 -0
- package/dist/events/command.js +13 -0
- package/dist/events/command.js.map +1 -0
- package/dist/events/emit.d.ts +2 -0
- package/dist/events/emit.d.ts.map +1 -0
- package/dist/events/emit.js +36 -0
- package/dist/events/emit.js.map +1 -0
- package/dist/events/helper.d.ts +3 -0
- package/dist/events/helper.d.ts.map +1 -0
- package/dist/events/helper.js +7 -0
- package/dist/events/helper.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +4 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/on-command.d.ts +2 -0
- package/dist/events/on-command.d.ts.map +1 -0
- package/dist/events/on-command.js +33 -0
- package/dist/events/on-command.js.map +1 -0
- package/dist/functions/bind.d.ts +2 -0
- package/dist/functions/bind.d.ts.map +1 -0
- package/dist/functions/bind.js +19 -0
- package/dist/functions/bind.js.map +1 -0
- package/dist/functions/debounce.d.ts +2 -0
- package/dist/functions/debounce.d.ts.map +1 -0
- package/dist/functions/debounce.js +29 -0
- package/dist/functions/debounce.js.map +1 -0
- package/dist/functions/index.d.ts +10 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +10 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/log.d.ts +9 -0
- package/dist/functions/log.d.ts.map +1 -0
- package/dist/functions/log.js +43 -0
- package/dist/functions/log.js.map +1 -0
- package/dist/functions/memo.d.ts +2 -0
- package/dist/functions/memo.d.ts.map +1 -0
- package/dist/functions/memo.js +45 -0
- package/dist/functions/memo.js.map +1 -0
- package/dist/functions/once.d.ts +2 -0
- package/dist/functions/once.d.ts.map +1 -0
- package/dist/functions/once.js +17 -0
- package/dist/functions/once.js.map +1 -0
- package/dist/functions/retry.d.ts +7 -0
- package/dist/functions/retry.d.ts.map +1 -0
- package/dist/functions/retry.js +28 -0
- package/dist/functions/retry.js.map +1 -0
- package/dist/functions/throttle.d.ts +2 -0
- package/dist/functions/throttle.d.ts.map +1 -0
- package/dist/functions/throttle.js +27 -0
- package/dist/functions/throttle.js.map +1 -0
- package/dist/functions/watch.d.ts +19 -0
- package/dist/functions/watch.d.ts.map +1 -0
- package/dist/functions/watch.js +45 -0
- package/dist/functions/watch.js.map +1 -0
- package/dist/functions/when.d.ts +2 -0
- package/dist/functions/when.d.ts.map +1 -0
- package/dist/functions/when.js +31 -0
- package/dist/functions/when.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/properties/history.d.ts +2 -0
- package/dist/properties/history.d.ts.map +1 -0
- package/dist/properties/history.js +39 -0
- package/dist/properties/history.js.map +1 -0
- package/dist/properties/index.d.ts +6 -0
- package/dist/properties/index.d.ts.map +1 -0
- package/dist/properties/index.js +6 -0
- package/dist/properties/index.js.map +1 -0
- package/dist/properties/persisted.d.ts +3 -0
- package/dist/properties/persisted.d.ts.map +1 -0
- package/dist/properties/persisted.js +46 -0
- package/dist/properties/persisted.js.map +1 -0
- package/dist/properties/prop.d.ts +2 -0
- package/dist/properties/prop.d.ts.map +1 -0
- package/dist/properties/prop.js +18 -0
- package/dist/properties/prop.js.map +1 -0
- package/dist/properties/slot.d.ts +9 -0
- package/dist/properties/slot.d.ts.map +1 -0
- package/dist/properties/slot.js +46 -0
- package/dist/properties/slot.js.map +1 -0
- package/dist/properties/state.d.ts +2 -0
- package/dist/properties/state.d.ts.map +1 -0
- package/dist/properties/state.js +24 -0
- package/dist/properties/state.js.map +1 -0
- package/package.json +26 -0
- package/src/component/component.ts +18 -0
- package/src/component/index.ts +4 -0
- package/src/component/lazy.ts +56 -0
- package/src/component/lifecycle.ts +47 -0
- package/src/component/memoize.ts +31 -0
- package/src/component/virtual.tsx +104 -0
- package/src/events/command.ts +17 -0
- package/src/events/emit.ts +47 -0
- package/src/events/helper.ts +7 -0
- package/src/events/index.ts +3 -0
- package/src/events/on-command.ts +55 -0
- package/src/functions/bind.ts +22 -0
- package/src/functions/debounce.ts +34 -0
- package/src/functions/index.ts +9 -0
- package/src/functions/log.ts +68 -0
- package/src/functions/memo.ts +63 -0
- package/src/functions/once.ts +22 -0
- package/src/functions/retry.ts +43 -0
- package/src/functions/throttle.ts +32 -0
- package/src/functions/watch.ts +99 -0
- package/src/functions/when.ts +44 -0
- package/src/index.ts +16 -0
- package/src/properties/history.ts +62 -0
- package/src/properties/index.ts +5 -0
- package/src/properties/persisted.ts +75 -0
- package/src/properties/prop.ts +18 -0
- package/src/properties/slot.ts +75 -0
- package/src/properties/state.ts +24 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { signal } from "@praxisjs/core";
|
|
2
|
+
export function State() {
|
|
3
|
+
return function (target, propertyKey) {
|
|
4
|
+
const signalMap = new WeakMap();
|
|
5
|
+
Object.defineProperty(target, propertyKey, {
|
|
6
|
+
get() {
|
|
7
|
+
if (!signalMap.has(this))
|
|
8
|
+
signalMap.set(this, signal(undefined));
|
|
9
|
+
return signalMap.get(this)();
|
|
10
|
+
},
|
|
11
|
+
set(value) {
|
|
12
|
+
if (!signalMap.has(this))
|
|
13
|
+
signalMap.set(this, signal(value));
|
|
14
|
+
else {
|
|
15
|
+
this._stateDirty = true;
|
|
16
|
+
signalMap.get(this).set(value);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/properties/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,MAAM,UAAU,KAAK;IACnB,OAAO,UAAU,MAAc,EAAE,WAAmB;QAClD,MAAM,SAAS,GAAG,IAAI,OAAO,EAA2B,CAAC;QAEzD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE;YACzC,GAAG;gBACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;gBACjE,OAAQ,SAAS,CAAC,GAAG,CAAC,IAAI,CAAqB,EAAE,CAAC;YACpD,CAAC;YACD,GAAG,CAAe,KAAc;gBAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;qBACxD,CAAC;oBACH,IAA0B,CAAC,WAAW,GAAG,IAAI,CAAC;oBAC9C,SAAS,CAAC,GAAG,CAAC,IAAI,CAAqB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;YACD,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@praxisjs/decorators",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "^25.3.0",
|
|
15
|
+
"typescript": "^5.9.3"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@praxisjs/core": "0.1.0",
|
|
19
|
+
"@praxisjs/jsx": "0.1.0",
|
|
20
|
+
"@praxisjs/shared": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"dev": "tsc --watch"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentConstructor, ComponentInstance } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
export function Component() {
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
return function <T extends new (...args: any[]) => ComponentInstance>(
|
|
6
|
+
constructor: T,
|
|
7
|
+
): T & ComponentConstructor {
|
|
8
|
+
const Enhanced = class extends constructor {
|
|
9
|
+
static isComponent = true as const;
|
|
10
|
+
} as unknown as T & ComponentConstructor;
|
|
11
|
+
|
|
12
|
+
Object.defineProperty(Enhanced, "name", {
|
|
13
|
+
value: constructor.name,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return Enhanced;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type BaseComponent, signal } from "@praxisjs/core";
|
|
2
|
+
import type { VNode } from "@praxisjs/shared";
|
|
3
|
+
|
|
4
|
+
export function Lazy(placeholder = 200) {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
return function <T extends new (...args: any[]) => BaseComponent>(
|
|
7
|
+
constructor: T,
|
|
8
|
+
): T {
|
|
9
|
+
return class LazyWrapper extends constructor {
|
|
10
|
+
private readonly _lazyVisible = signal(false);
|
|
11
|
+
private _observer?: IntersectionObserver;
|
|
12
|
+
private readonly _originalRender: () => VNode | null = this.render.bind(this);
|
|
13
|
+
|
|
14
|
+
onMount() {
|
|
15
|
+
super.onMount?.();
|
|
16
|
+
|
|
17
|
+
const el = document.querySelector<HTMLElement>(
|
|
18
|
+
`[data-component="${constructor.name}"]`,
|
|
19
|
+
);
|
|
20
|
+
if (!el) return;
|
|
21
|
+
|
|
22
|
+
if (!("IntersectionObserver" in window)) {
|
|
23
|
+
this._lazyVisible.set(true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!this._lazyVisible()) {
|
|
28
|
+
el.style.minHeight = `${String(placeholder)}px`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this._observer = new IntersectionObserver(
|
|
32
|
+
(entries) => {
|
|
33
|
+
if (entries[0]?.isIntersecting) {
|
|
34
|
+
this._lazyVisible.set(true);
|
|
35
|
+
el.style.minHeight = "";
|
|
36
|
+
this._observer?.disconnect();
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{ rootMargin: "100px" },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
this._observer.observe(el);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onUnmount() {
|
|
46
|
+
super.onUnmount?.();
|
|
47
|
+
this._observer?.disconnect();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render(): VNode | null {
|
|
51
|
+
if (!this._lazyVisible()) return null;
|
|
52
|
+
return this._originalRender();
|
|
53
|
+
}
|
|
54
|
+
} as unknown as T;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type BaseComponent, VALID_LIFECYCLE_HOOK_SIGNATURES } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
function getAllMethods(proto: object): string[] {
|
|
4
|
+
const methods: string[] = [];
|
|
5
|
+
let current: object | null = proto;
|
|
6
|
+
|
|
7
|
+
while (current !== null && current !== Object.prototype) {
|
|
8
|
+
const names = Object.getOwnPropertyNames(current);
|
|
9
|
+
for (const name of names) {
|
|
10
|
+
if (
|
|
11
|
+
name !== "constructor" &&
|
|
12
|
+
typeof (current as Record<string, unknown>)[name] === "function"
|
|
13
|
+
) {
|
|
14
|
+
if (!methods.includes(name)) methods.push(name);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
current = Object.getPrototypeOf(current) as object | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return methods;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
export function LifeCycle<T extends new (...args: any[]) => BaseComponent>(
|
|
25
|
+
constructor: T,
|
|
26
|
+
): T {
|
|
27
|
+
if (process.env.NODE_ENV === "production") return constructor;
|
|
28
|
+
|
|
29
|
+
const validHooks = new Set<string>(
|
|
30
|
+
Object.keys(VALID_LIFECYCLE_HOOK_SIGNATURES),
|
|
31
|
+
);
|
|
32
|
+
const proto = constructor.prototype as Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
const allMethods = getAllMethods(proto);
|
|
35
|
+
for (const method of allMethods) {
|
|
36
|
+
if (!method.startsWith("on")) continue;
|
|
37
|
+
|
|
38
|
+
if (!validHooks.has(method)) {
|
|
39
|
+
console.warn(
|
|
40
|
+
`[LifeCycle] "${constructor.name}.${method}" appears to be a lifecycle hook but is not recognized.\n` +
|
|
41
|
+
`Valid hooks: ${[...validHooks].join(", ")}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return constructor;
|
|
47
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentInstance } from "@praxisjs/shared";
|
|
2
|
+
|
|
3
|
+
type PropsRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
function shallowEqual(a: PropsRecord, b: PropsRecord) {
|
|
6
|
+
const keysA = Object.keys(a);
|
|
7
|
+
const keysB = Object.keys(b);
|
|
8
|
+
|
|
9
|
+
if (keysA.length !== keysB.length) return false;
|
|
10
|
+
|
|
11
|
+
const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(b);
|
|
12
|
+
|
|
13
|
+
for (const aKey of keysA) {
|
|
14
|
+
if (!bHasOwnProperty(aKey)) return false;
|
|
15
|
+
if (!Object.is(a[aKey], b[aKey])) return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Memoize(
|
|
21
|
+
areEqual: (prev: PropsRecord, next: PropsRecord) => boolean = shallowEqual,
|
|
22
|
+
) {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
return function <T extends new (...args: any[]) => ComponentInstance>(
|
|
25
|
+
constructor: T,
|
|
26
|
+
): T {
|
|
27
|
+
(constructor as unknown as ComponentInstance)._isMemorized = true;
|
|
28
|
+
(constructor as unknown as ComponentInstance)._arePropsEqual = areEqual;
|
|
29
|
+
return constructor;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type BaseComponent, computed, signal } from "@praxisjs/core";
|
|
2
|
+
import type { VNode } from "@praxisjs/shared";
|
|
3
|
+
|
|
4
|
+
export function Virtual(itemHeight: number, buffer = 3) {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
return function <T extends new (...args: any[]) => BaseComponent>(
|
|
7
|
+
constructor: T,
|
|
8
|
+
): T {
|
|
9
|
+
return class VirtualWrapper extends constructor {
|
|
10
|
+
private readonly _scrollTop = signal(0);
|
|
11
|
+
private readonly _viewHeight = signal(600);
|
|
12
|
+
private _container?: HTMLElement;
|
|
13
|
+
private _scrollHandler?: () => void;
|
|
14
|
+
|
|
15
|
+
onMount() {
|
|
16
|
+
super.onMount?.();
|
|
17
|
+
|
|
18
|
+
this._container =
|
|
19
|
+
document.querySelector<HTMLElement>(
|
|
20
|
+
`[data-component="${constructor.name}"]`,
|
|
21
|
+
) ?? undefined;
|
|
22
|
+
|
|
23
|
+
if (!this._container) return;
|
|
24
|
+
|
|
25
|
+
this._container.style.overflowY = "auto";
|
|
26
|
+
this._container.style.position = "relative";
|
|
27
|
+
|
|
28
|
+
this._viewHeight.set(this._container.clientHeight || 600);
|
|
29
|
+
|
|
30
|
+
this._scrollHandler = () => {
|
|
31
|
+
if (this._container) this._scrollTop.set(this._container.scrollTop);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this._container.addEventListener("scroll", this._scrollHandler);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onUnmount() {
|
|
38
|
+
super.onUnmount?.();
|
|
39
|
+
if (this._container && this._scrollHandler) {
|
|
40
|
+
this._container.removeEventListener("scroll", this._scrollHandler);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
render() {
|
|
45
|
+
const instance = this as unknown as Record<string, unknown>;
|
|
46
|
+
const items = (instance.items as unknown[] | undefined) ?? [];
|
|
47
|
+
const total = items.length;
|
|
48
|
+
const totalH = total * itemHeight;
|
|
49
|
+
|
|
50
|
+
const renderItem = instance.renderItem as
|
|
51
|
+
| ((item: unknown, i: number) => VNode | null)
|
|
52
|
+
| undefined;
|
|
53
|
+
if (!renderItem) {
|
|
54
|
+
console.warn(
|
|
55
|
+
`[Virtual] ${constructor.name} must implement renderItem(item, index)`,
|
|
56
|
+
);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const startIdx = computed(() => {
|
|
61
|
+
const start = Math.floor(this._scrollTop() / itemHeight) - buffer;
|
|
62
|
+
return Math.max(0, start);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const endIdx = computed(() => {
|
|
66
|
+
const end =
|
|
67
|
+
Math.ceil((this._scrollTop() + this._viewHeight()) / itemHeight) +
|
|
68
|
+
buffer;
|
|
69
|
+
return Math.min(total - 1, end);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const visibleItems = computed(() => {
|
|
73
|
+
const result = [];
|
|
74
|
+
for (let i = startIdx(); i <= endIdx(); i++) {
|
|
75
|
+
result.push({ item: items[i], index: i });
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const offsetTop = computed(() => startIdx() * itemHeight);
|
|
81
|
+
const offsetBottom = computed(
|
|
82
|
+
() => (total - 1 - endIdx()) * itemHeight,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div style={`height:${String(totalH)}px; position:relative;`}>
|
|
87
|
+
<div style={() => `height:${String(offsetTop())}px;`} />
|
|
88
|
+
{() =>
|
|
89
|
+
visibleItems().map(({ item, index }) => (
|
|
90
|
+
<div
|
|
91
|
+
key={String(index)}
|
|
92
|
+
style={`height:${String(itemHeight)}px; overflow:hidden;`}
|
|
93
|
+
>
|
|
94
|
+
{renderItem.call(this, item, index)}
|
|
95
|
+
</div>
|
|
96
|
+
))
|
|
97
|
+
}
|
|
98
|
+
<div style={() => `height:${String(offsetBottom())}px;`} />
|
|
99
|
+
</div>
|
|
100
|
+
) as unknown as VNode;
|
|
101
|
+
}
|
|
102
|
+
} as unknown as T;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Command<T = void> {
|
|
2
|
+
trigger(arg: T): void;
|
|
3
|
+
subscribe(handler: (arg: T) => void): () => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function createCommand<T = void>(): Command<T> {
|
|
7
|
+
const handlers = new Set<(arg: T) => void>();
|
|
8
|
+
return {
|
|
9
|
+
trigger(arg: T) {
|
|
10
|
+
handlers.forEach((h) => { h(arg); });
|
|
11
|
+
},
|
|
12
|
+
subscribe(handler) {
|
|
13
|
+
handlers.add(handler);
|
|
14
|
+
return () => handlers.delete(handler);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BaseComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
import { readProp } from "./helper";
|
|
4
|
+
|
|
5
|
+
export function Emit(propName: string) {
|
|
6
|
+
return function (
|
|
7
|
+
_target: object,
|
|
8
|
+
methodKey: string,
|
|
9
|
+
descriptor: PropertyDescriptor,
|
|
10
|
+
): PropertyDescriptor {
|
|
11
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
12
|
+
|
|
13
|
+
const wrapped = function (
|
|
14
|
+
this: BaseComponent,
|
|
15
|
+
...args: unknown[]
|
|
16
|
+
): unknown {
|
|
17
|
+
const result = originalMethod.apply(this, args);
|
|
18
|
+
|
|
19
|
+
const callback = readProp(this, propName);
|
|
20
|
+
if (typeof callback !== "function") return result;
|
|
21
|
+
|
|
22
|
+
if (result !== undefined) {
|
|
23
|
+
(callback as (v: unknown) => void)(result);
|
|
24
|
+
} else if (args.length > 0) {
|
|
25
|
+
(callback as (...a: unknown[]) => void)(...args);
|
|
26
|
+
} else {
|
|
27
|
+
(callback as () => void)();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
configurable: true,
|
|
35
|
+
enumerable: false,
|
|
36
|
+
get(this: BaseComponent) {
|
|
37
|
+
const bound = wrapped.bind(this);
|
|
38
|
+
Object.defineProperty(this, methodKey, {
|
|
39
|
+
value: bound,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
});
|
|
43
|
+
return bound;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BaseComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
export function readProp(instance: BaseComponent, propName: string): unknown {
|
|
4
|
+
const fromParent = instance._rawProps[propName];
|
|
5
|
+
if (fromParent !== undefined) return fromParent;
|
|
6
|
+
return instance._defaults[propName];
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { BaseComponent } from "@praxisjs/core";
|
|
2
|
+
|
|
3
|
+
import { readProp } from "./helper";
|
|
4
|
+
|
|
5
|
+
import type { Command } from "./command";
|
|
6
|
+
|
|
7
|
+
export function OnCommand(propName: string) {
|
|
8
|
+
return function (
|
|
9
|
+
target: object,
|
|
10
|
+
_methodKey: string,
|
|
11
|
+
descriptor: PropertyDescriptor,
|
|
12
|
+
): PropertyDescriptor {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
14
|
+
const originalOnMount = (target as { onMount?(): void }).onMount;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
16
|
+
const originalOnUnmount = (target as { onUnmount?(): void }).onUnmount;
|
|
17
|
+
|
|
18
|
+
const method = descriptor.value as (...args: unknown[]) => void;
|
|
19
|
+
const cleanups = new WeakMap<object, () => void>();
|
|
20
|
+
|
|
21
|
+
(target as { onMount?(): void }).onMount = function (this: BaseComponent) {
|
|
22
|
+
originalOnMount?.call(this);
|
|
23
|
+
|
|
24
|
+
const command = readProp(this, propName) as Command<unknown> | undefined;
|
|
25
|
+
|
|
26
|
+
if (!command) {
|
|
27
|
+
console.warn(
|
|
28
|
+
`[OnCommand] prop "${propName}" was not provided to ${this.constructor.name}`,
|
|
29
|
+
);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof command.subscribe !== "function") {
|
|
34
|
+
console.warn(
|
|
35
|
+
`[OnCommand] prop "${propName}" is not a valid Command in ${this.constructor.name}`,
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const bound = method.bind(this);
|
|
41
|
+
const unsub = command.subscribe((...args: unknown[]) => { bound(...args); });
|
|
42
|
+
cleanups.set(this, unsub);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
(target as { onUnmount?(): void }).onUnmount = function (
|
|
46
|
+
this: BaseComponent,
|
|
47
|
+
) {
|
|
48
|
+
originalOnUnmount?.call(this);
|
|
49
|
+
cleanups.get(this)?.();
|
|
50
|
+
cleanups.delete(this);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return descriptor;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function Bind() {
|
|
2
|
+
return function (
|
|
3
|
+
_target: object,
|
|
4
|
+
methodKey: string,
|
|
5
|
+
descriptor: PropertyDescriptor,
|
|
6
|
+
): PropertyDescriptor {
|
|
7
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
8
|
+
return {
|
|
9
|
+
enumerable: false,
|
|
10
|
+
configurable: true,
|
|
11
|
+
get(this: object) {
|
|
12
|
+
const bound = originalMethod.bind(this);
|
|
13
|
+
Object.defineProperty(this, methodKey, {
|
|
14
|
+
value: bound,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
});
|
|
18
|
+
return bound;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function Debounce(ms: number) {
|
|
2
|
+
const timers = new WeakMap<object, ReturnType<typeof setTimeout>>();
|
|
3
|
+
|
|
4
|
+
return function (
|
|
5
|
+
_target: object,
|
|
6
|
+
methodKey: string,
|
|
7
|
+
descriptor: PropertyDescriptor,
|
|
8
|
+
): PropertyDescriptor {
|
|
9
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
10
|
+
return {
|
|
11
|
+
enumerable: false,
|
|
12
|
+
configurable: true,
|
|
13
|
+
get(this: object) {
|
|
14
|
+
const bound = (...args: unknown[]) => {
|
|
15
|
+
const existing = timers.get(this);
|
|
16
|
+
if (existing !== undefined) clearTimeout(existing);
|
|
17
|
+
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
timers.delete(this);
|
|
20
|
+
originalMethod.apply(this, args);
|
|
21
|
+
}, ms);
|
|
22
|
+
|
|
23
|
+
timers.set(this, timer);
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(this, methodKey, {
|
|
26
|
+
value: bound,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
});
|
|
30
|
+
return bound;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Memo } from "./memo";
|
|
2
|
+
export { Bind } from "./bind";
|
|
3
|
+
export { Log } from "./log";
|
|
4
|
+
export { Once } from "./once";
|
|
5
|
+
export { Retry } from "./retry";
|
|
6
|
+
export { Debounce } from "./debounce";
|
|
7
|
+
export { Throttle } from "./throttle";
|
|
8
|
+
export { When } from "./when";
|
|
9
|
+
export { Watch, type WatchVal, type WatchVals } from "./watch";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface LogOptions {
|
|
2
|
+
level?: "log" | "warn" | "error" | "debug";
|
|
3
|
+
args?: boolean;
|
|
4
|
+
result?: boolean;
|
|
5
|
+
time?: boolean;
|
|
6
|
+
devOnly?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Log(options: LogOptions = {}) {
|
|
10
|
+
const {
|
|
11
|
+
level = "log",
|
|
12
|
+
args: logArgs = true,
|
|
13
|
+
result = true,
|
|
14
|
+
time = false,
|
|
15
|
+
devOnly = true,
|
|
16
|
+
} = options;
|
|
17
|
+
|
|
18
|
+
return function (
|
|
19
|
+
_target: object,
|
|
20
|
+
methodKey: string,
|
|
21
|
+
descriptor: PropertyDescriptor,
|
|
22
|
+
): PropertyDescriptor {
|
|
23
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
24
|
+
|
|
25
|
+
descriptor.value = function (this: object, ...args: unknown[]) {
|
|
26
|
+
if (
|
|
27
|
+
devOnly &&
|
|
28
|
+
typeof process !== "undefined" &&
|
|
29
|
+
process.env.NODE_ENV === "production"
|
|
30
|
+
) {
|
|
31
|
+
return originalMethod.apply(this, args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const className = (this.constructor as { name?: string }).name ?? "Unknown";
|
|
35
|
+
const label = `[${className}.${methodKey}]`;
|
|
36
|
+
const logger = console[level].bind(console);
|
|
37
|
+
|
|
38
|
+
if (logArgs) logger(`${label} args:`, args);
|
|
39
|
+
|
|
40
|
+
const start = time ? performance.now() : 0;
|
|
41
|
+
const output = originalMethod.apply(this, args);
|
|
42
|
+
|
|
43
|
+
if (output instanceof Promise) {
|
|
44
|
+
return (output as Promise<unknown>)
|
|
45
|
+
.then((resolved) => {
|
|
46
|
+
const elapsed = time
|
|
47
|
+
? ` (${(performance.now() - start).toFixed(2)}ms)`
|
|
48
|
+
: "";
|
|
49
|
+
if (result) logger(`${label} resolved:`, resolved, elapsed);
|
|
50
|
+
return resolved;
|
|
51
|
+
})
|
|
52
|
+
.catch((e: unknown) => {
|
|
53
|
+
logger(`${label} rejected:`, e);
|
|
54
|
+
throw e;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const elapsed = time
|
|
59
|
+
? ` (${(performance.now() - start).toFixed(2)}ms)`
|
|
60
|
+
: "";
|
|
61
|
+
if (result) logger(`${label} returned:`, output, elapsed);
|
|
62
|
+
|
|
63
|
+
return output;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return descriptor;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { computed } from "@praxisjs/core";
|
|
2
|
+
import type { Computed } from "@praxisjs/shared";
|
|
3
|
+
|
|
4
|
+
const instanceCache = new WeakMap<
|
|
5
|
+
object,
|
|
6
|
+
Map<string, Map<string, Computed<unknown>>>
|
|
7
|
+
>();
|
|
8
|
+
|
|
9
|
+
function getCache(
|
|
10
|
+
instance: object,
|
|
11
|
+
methodName: string,
|
|
12
|
+
argKey: string,
|
|
13
|
+
factory: () => unknown,
|
|
14
|
+
): Computed<unknown> {
|
|
15
|
+
let methodMap = instanceCache.get(instance);
|
|
16
|
+
if (!methodMap) {
|
|
17
|
+
methodMap = new Map();
|
|
18
|
+
instanceCache.set(instance, methodMap);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let argMap = methodMap.get(methodName);
|
|
22
|
+
if (!argMap) {
|
|
23
|
+
argMap = new Map();
|
|
24
|
+
methodMap.set(methodName, argMap);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let cached = argMap.get(argKey);
|
|
28
|
+
if (!cached) {
|
|
29
|
+
cached = computed(factory);
|
|
30
|
+
argMap.set(argKey, cached);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function serializeArgs(args: unknown[]) {
|
|
37
|
+
if (args.length === 0) return "__no_args__";
|
|
38
|
+
return args
|
|
39
|
+
.map((a) => {
|
|
40
|
+
if (a === null || typeof a === "object") return JSON.stringify(a);
|
|
41
|
+
if (typeof a === "symbol") return a.toString();
|
|
42
|
+
return String(a as string | number | boolean | bigint | undefined);
|
|
43
|
+
})
|
|
44
|
+
.join("|");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Memo() {
|
|
48
|
+
return function (
|
|
49
|
+
_target: object,
|
|
50
|
+
methodName: string,
|
|
51
|
+
descriptor: PropertyDescriptor,
|
|
52
|
+
): PropertyDescriptor {
|
|
53
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
54
|
+
descriptor.value = function (this: object, ...args: unknown[]) {
|
|
55
|
+
const argKey = serializeArgs(args);
|
|
56
|
+
const memoized = getCache(this, methodName, argKey, () =>
|
|
57
|
+
originalMethod.apply(this, args),
|
|
58
|
+
);
|
|
59
|
+
return memoized();
|
|
60
|
+
};
|
|
61
|
+
return descriptor;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function Once() {
|
|
2
|
+
const called = new WeakMap<object, boolean>();
|
|
3
|
+
const results = new WeakMap<object, unknown>();
|
|
4
|
+
|
|
5
|
+
return function (
|
|
6
|
+
_target: object,
|
|
7
|
+
_methodKey: string,
|
|
8
|
+
descriptor: PropertyDescriptor,
|
|
9
|
+
): PropertyDescriptor {
|
|
10
|
+
const originalMethod = descriptor.value as (...args: unknown[]) => unknown;
|
|
11
|
+
|
|
12
|
+
descriptor.value = function (this: object, ...args: unknown[]) {
|
|
13
|
+
if (called.get(this)) return results.get(this);
|
|
14
|
+
const result = originalMethod.apply(this, args);
|
|
15
|
+
called.set(this, true);
|
|
16
|
+
results.set(this, result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return descriptor;
|
|
21
|
+
};
|
|
22
|
+
}
|