@praxisjs/decorators 1.7.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +8 -15
- 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 +44 -36
- package/dist/component/lazy.js.map +1 -1
- package/dist/create-class-decorator.d.ts +27 -5
- package/dist/create-class-decorator.d.ts.map +1 -1
- package/dist/create-class-decorator.js +53 -18
- package/dist/create-class-decorator.js.map +1 -1
- package/dist/create-field-decorator.d.ts +42 -3
- package/dist/create-field-decorator.d.ts.map +1 -1
- package/dist/create-field-decorator.js +63 -27
- package/dist/create-field-decorator.js.map +1 -1
- package/dist/create-getter-decorator.d.ts +81 -16
- package/dist/create-getter-decorator.d.ts.map +1 -1
- package/dist/create-getter-decorator.js +127 -15
- package/dist/create-getter-decorator.js.map +1 -1
- package/dist/create-lifecycle-method-decorator.d.ts +21 -3
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
- package/dist/create-lifecycle-method-decorator.js +25 -18
- package/dist/create-lifecycle-method-decorator.js.map +1 -1
- package/dist/create-method-decorator.d.ts +43 -3
- package/dist/create-method-decorator.d.ts.map +1 -1
- package/dist/create-method-decorator.js +49 -8
- package/dist/create-method-decorator.js.map +1 -1
- package/dist/events/command.d.ts +2 -1
- package/dist/events/command.d.ts.map +1 -1
- package/dist/events/command.js +10 -8
- package/dist/events/command.js.map +1 -1
- package/dist/events/emit.d.ts +2 -2
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +18 -24
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +2 -1
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +12 -14
- package/dist/events/on-command.js.map +1 -1
- package/dist/field.d.ts +51 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +82 -0
- package/dist/field.js.map +1 -0
- package/dist/functions/bind.d.ts +2 -1
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -4
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +2 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +25 -23
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/log.d.ts +2 -1
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +38 -40
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +2 -1
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +41 -40
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +2 -1
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +10 -14
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +2 -1
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +21 -23
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +2 -1
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +12 -14
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/until.d.ts +2 -1
- package/dist/functions/until.d.ts.map +1 -1
- package/dist/functions/until.js +13 -14
- package/dist/functions/until.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 +36 -34
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -1
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +19 -21
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +24 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +55 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/properties/compose.d.ts +3 -2
- package/dist/properties/compose.d.ts.map +1 -1
- package/dist/properties/compose.js +33 -19
- package/dist/properties/compose.js.map +1 -1
- package/dist/properties/computed.d.ts +12 -10
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +26 -21
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/deep-state.d.ts +2 -1
- package/dist/properties/deep-state.d.ts.map +1 -1
- package/dist/properties/deep-state.js +66 -60
- package/dist/properties/deep-state.js.map +1 -1
- package/dist/properties/function-prop.d.ts +2 -1
- package/dist/properties/function-prop.d.ts.map +1 -1
- package/dist/properties/function-prop.js +20 -22
- package/dist/properties/function-prop.js.map +1 -1
- package/dist/properties/history.d.ts +2 -2
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +34 -33
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +1 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +1 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +2 -1
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +24 -18
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +2 -1
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +26 -22
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/ref.d.ts +2 -1
- package/dist/properties/ref.d.ts.map +1 -1
- package/dist/properties/ref.js +11 -8
- package/dist/properties/ref.js.map +1 -1
- package/dist/properties/resource.d.ts.map +1 -1
- package/dist/properties/resource.js +21 -16
- package/dist/properties/resource.js.map +1 -1
- package/dist/properties/selector.d.ts +12 -0
- package/dist/properties/selector.d.ts.map +1 -0
- package/dist/properties/selector.js +25 -0
- package/dist/properties/selector.js.map +1 -0
- package/dist/properties/slot.d.ts +2 -1
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +22 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +27 -16
- package/dist/properties/state.js.map +1 -1
- package/dist/properties/synced.d.ts +2 -1
- package/dist/properties/synced.d.ts.map +1 -1
- package/dist/properties/synced.js +27 -19
- package/dist/properties/synced.js.map +1 -1
- package/dist/shared-method.d.ts +14 -0
- package/dist/shared-method.d.ts.map +1 -0
- package/dist/shared-method.js +87 -0
- package/dist/shared-method.js.map +1 -0
- package/package.json +14 -5
- package/src/component/component.ts +8 -27
- package/src/component/lazy.ts +51 -41
- package/src/create-class-decorator.ts +90 -31
- package/src/create-field-decorator.ts +120 -43
- package/src/create-getter-decorator.ts +263 -66
- package/src/create-lifecycle-method-decorator.ts +50 -28
- package/src/create-method-decorator.ts +119 -13
- package/src/events/command.ts +11 -9
- package/src/events/emit.ts +16 -22
- package/src/events/on-command.ts +17 -18
- package/src/field.ts +116 -0
- package/src/functions/bind.ts +4 -4
- package/src/functions/debounce.ts +25 -23
- package/src/functions/log.ts +44 -46
- package/src/functions/memo.ts +39 -37
- package/src/functions/once.ts +10 -13
- package/src/functions/retry.ts +19 -21
- package/src/functions/throttle.ts +12 -13
- package/src/functions/until.ts +14 -14
- package/src/functions/watch.ts +37 -36
- package/src/functions/when.ts +18 -20
- package/src/index.ts +12 -5
- package/src/lifecycle.ts +74 -0
- package/src/properties/compose.ts +33 -20
- package/src/properties/computed.ts +35 -45
- package/src/properties/deep-state.ts +66 -64
- package/src/properties/function-prop.ts +25 -27
- package/src/properties/history.ts +34 -35
- package/src/properties/index.ts +1 -0
- package/src/properties/persisted.ts +30 -20
- package/src/properties/prop.ts +30 -22
- package/src/properties/ref.ts +12 -10
- package/src/properties/resource.ts +22 -17
- package/src/properties/selector.ts +45 -0
- package/src/properties/slot.ts +28 -22
- package/src/properties/state.ts +32 -16
- package/src/properties/synced.ts +31 -19
- package/src/shared-method.ts +101 -0
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -147
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/compose.test.d.ts +0 -2
- package/dist/__tests__/compose.test.d.ts.map +0 -1
- package/dist/__tests__/compose.test.js +0 -164
- package/dist/__tests__/compose.test.js.map +0 -1
- package/dist/__tests__/deep-state.test.d.ts +0 -2
- package/dist/__tests__/deep-state.test.d.ts.map +0 -1
- package/dist/__tests__/deep-state.test.js +0 -221
- package/dist/__tests__/deep-state.test.js.map +0 -1
- package/dist/__tests__/events.test.d.ts +0 -2
- package/dist/__tests__/events.test.d.ts.map +0 -1
- package/dist/__tests__/events.test.js +0 -295
- package/dist/__tests__/events.test.js.map +0 -1
- package/dist/__tests__/functions.test.d.ts +0 -2
- package/dist/__tests__/functions.test.d.ts.map +0 -1
- package/dist/__tests__/functions.test.js +0 -618
- package/dist/__tests__/functions.test.js.map +0 -1
- package/dist/__tests__/lazy.test.d.ts +0 -2
- package/dist/__tests__/lazy.test.d.ts.map +0 -1
- package/dist/__tests__/lazy.test.js +0 -227
- package/dist/__tests__/lazy.test.js.map +0 -1
- package/dist/__tests__/memo.test.d.ts +0 -2
- package/dist/__tests__/memo.test.d.ts.map +0 -1
- package/dist/__tests__/memo.test.js +0 -249
- package/dist/__tests__/memo.test.js.map +0 -1
- package/dist/__tests__/properties-browser.test.d.ts +0 -2
- package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
- package/dist/__tests__/properties-browser.test.js +0 -227
- package/dist/__tests__/properties-browser.test.js.map +0 -1
- package/dist/__tests__/properties.test.d.ts +0 -2
- package/dist/__tests__/properties.test.d.ts.map +0 -1
- package/dist/__tests__/properties.test.js +0 -1253
- package/dist/__tests__/properties.test.js.map +0 -1
- package/dist/__tests__/ref.test.d.ts +0 -2
- package/dist/__tests__/ref.test.d.ts.map +0 -1
- package/dist/__tests__/ref.test.js +0 -106
- package/dist/__tests__/ref.test.js.map +0 -1
- package/dist/__tests__/resource.test.d.ts +0 -2
- package/dist/__tests__/resource.test.d.ts.map +0 -1
- package/dist/__tests__/resource.test.js +0 -217
- package/dist/__tests__/resource.test.js.map +0 -1
- package/dist/__tests__/slot.test.d.ts +0 -2
- package/dist/__tests__/slot.test.d.ts.map +0 -1
- package/dist/__tests__/slot.test.js +0 -147
- package/dist/__tests__/slot.test.js.map +0 -1
- package/dist/__tests__/synced.test.d.ts +0 -2
- package/dist/__tests__/synced.test.d.ts.map +0 -1
- package/dist/__tests__/synced.test.js +0 -161
- package/dist/__tests__/synced.test.js.map +0 -1
- package/dist/__tests__/until.test.d.ts +0 -2
- package/dist/__tests__/until.test.d.ts.map +0 -1
- package/dist/__tests__/until.test.js +0 -88
- package/dist/__tests__/until.test.js.map +0 -1
- package/dist/create-accessor-decorator.d.ts +0 -14
- package/dist/create-accessor-decorator.d.ts.map +0 -1
- package/dist/create-accessor-decorator.js +0 -27
- package/dist/create-accessor-decorator.js.map +0 -1
- package/src/__tests__/component.test.ts +0 -167
- package/src/__tests__/compose.test.ts +0 -202
- package/src/__tests__/deep-state.test.ts +0 -265
- package/src/__tests__/events.test.ts +0 -366
- package/src/__tests__/functions.test.ts +0 -803
- package/src/__tests__/lazy.test.ts +0 -296
- package/src/__tests__/memo.test.ts +0 -293
- package/src/__tests__/properties-browser.test.ts +0 -288
- package/src/__tests__/properties.test.ts +0 -1556
- package/src/__tests__/ref.test.ts +0 -131
- package/src/__tests__/resource.test.ts +0 -246
- package/src/__tests__/slot.test.ts +0 -179
- package/src/__tests__/synced.test.ts +0 -193
- package/src/__tests__/until.test.ts +0 -113
- package/src/create-accessor-decorator.ts +0 -45
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Replacements installed through sharedMethod, and the ones an outer decorator wrapped again. Only
|
|
2
|
+
// read while decorating and while building the first instance of a class.
|
|
3
|
+
const shared = new WeakSet();
|
|
4
|
+
const superseded = new WeakSet();
|
|
5
|
+
function isPrototype(object) {
|
|
6
|
+
// Class prototypes own their `constructor`; instances inherit it.
|
|
7
|
+
return Object.hasOwn(object, "constructor");
|
|
8
|
+
}
|
|
9
|
+
function ownerOf(object, name) {
|
|
10
|
+
let owner = object;
|
|
11
|
+
while (owner !== null && !Object.hasOwn(owner, name))
|
|
12
|
+
owner = Object.getPrototypeOf(owner);
|
|
13
|
+
return owner;
|
|
14
|
+
}
|
|
15
|
+
function defineOwnMethod(object, name, value) {
|
|
16
|
+
Object.defineProperty(object, name, { value, configurable: true, writable: true });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns `method`, the decorator's replacement for `value`, set up so that reading it from an
|
|
20
|
+
* instance returns it bound to that instance, the same function on every read: detached calls
|
|
21
|
+
* (`onClick={this.save}`) keep working. `method` is shared by every instance and keeps any
|
|
22
|
+
* per-instance state in symbol slots.
|
|
23
|
+
*
|
|
24
|
+
* Rather than defining a bound wrapper on each instance as it is built, the first instance turns
|
|
25
|
+
* the method's prototype entry into an accessor: an instance pays for its bound copy only when the
|
|
26
|
+
* method is first read from it, and nothing if it never is.
|
|
27
|
+
*/
|
|
28
|
+
export function sharedMethod(context, value, method = value) {
|
|
29
|
+
if (value === method) {
|
|
30
|
+
// @Bind over a decorator that already binds: nothing left to do.
|
|
31
|
+
if (shared.has(method))
|
|
32
|
+
return method;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// The inner decorator's replacement now only runs through this one, which does the binding.
|
|
36
|
+
superseded.add(value);
|
|
37
|
+
}
|
|
38
|
+
shared.add(method);
|
|
39
|
+
// `this.#method` can't be read detached through a property, so there is nothing to bind.
|
|
40
|
+
if (context.private)
|
|
41
|
+
return method;
|
|
42
|
+
const name = context.name;
|
|
43
|
+
let installed = false;
|
|
44
|
+
let home;
|
|
45
|
+
const accessor = {
|
|
46
|
+
configurable: true,
|
|
47
|
+
enumerable: false,
|
|
48
|
+
get() {
|
|
49
|
+
// Read from a prototype, or through `super.method()` from a subclass override: caching a
|
|
50
|
+
// bound copy on the instance would then shadow the override. Read with no receiver at all,
|
|
51
|
+
// as `vi.spyOn(instance, name)` does with a getter it finds on the prototype: the spy then
|
|
52
|
+
// wraps the method itself, which takes `this` from each call.
|
|
53
|
+
if (this == null || isPrototype(this) || ownerOf(this, name) !== home)
|
|
54
|
+
return method;
|
|
55
|
+
const bound = method.bind(this);
|
|
56
|
+
defineOwnMethod(this, name, bound);
|
|
57
|
+
return bound;
|
|
58
|
+
},
|
|
59
|
+
set(next) {
|
|
60
|
+
defineOwnMethod(this, name, next);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
context.addInitializer(function () {
|
|
64
|
+
if (installed)
|
|
65
|
+
return;
|
|
66
|
+
const target = this;
|
|
67
|
+
if (superseded.has(method)) {
|
|
68
|
+
installed = true;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!context.static) {
|
|
72
|
+
for (let proto = Object.getPrototypeOf(target); proto !== null; proto = Object.getPrototypeOf(proto)) {
|
|
73
|
+
if (Object.getOwnPropertyDescriptor(proto, name)?.value === method) {
|
|
74
|
+
installed = true;
|
|
75
|
+
home = proto;
|
|
76
|
+
Object.defineProperty(proto, name, accessor);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// A static method (bound to its class, as before), or a decorator applied by hand whose
|
|
82
|
+
// replacement never reached a prototype: bind it on the object itself.
|
|
83
|
+
defineOwnMethod(target, name, method.bind(target));
|
|
84
|
+
});
|
|
85
|
+
return method;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=shared-method.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-method.js","sourceRoot":"","sources":["../src/shared-method.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/decorators",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
@@ -10,16 +11,24 @@
|
|
|
10
11
|
"types": "./dist/index.d.ts"
|
|
11
12
|
}
|
|
12
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"!dist/**/__tests__",
|
|
18
|
+
"!src/**/__tests__",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
13
21
|
"devDependencies": {
|
|
14
22
|
"@types/node": "^25.9.1",
|
|
15
23
|
"typescript": "^6.0.3"
|
|
16
24
|
},
|
|
17
25
|
"dependencies": {
|
|
18
|
-
"@praxisjs/core": "2.
|
|
19
|
-
"@praxisjs/shared": "0.3.
|
|
26
|
+
"@praxisjs/core": "^2.3.1",
|
|
27
|
+
"@praxisjs/shared": "^0.3.2"
|
|
20
28
|
},
|
|
21
29
|
"scripts": {
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"dev": "tsc --watch"
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
31
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
24
33
|
}
|
|
25
34
|
}
|
|
@@ -1,40 +1,21 @@
|
|
|
1
1
|
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
-
import type { RootComponent } from "@praxisjs/core/internal";
|
|
3
2
|
import type {
|
|
4
3
|
ComponentConstructor,
|
|
5
4
|
ComponentInstance,
|
|
6
5
|
} from "@praxisjs/shared/internal";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type ClassEnhancement,
|
|
12
|
-
} from "../create-class-decorator";
|
|
13
|
-
|
|
14
|
-
class ComponentBehavior extends ClassBehavior {
|
|
15
|
-
create(_instance: RootComponent): ClassEnhancement {
|
|
16
|
-
return {};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
initialize(Enhanced: new (...args: any[]) => unknown, original: new (...args: any[]) => unknown): void {
|
|
21
|
-
const isStateless = (original.prototype as object) instanceof StatelessComponent;
|
|
22
|
-
Object.defineProperty(Enhanced, "__isComponent", { value: true, configurable: true });
|
|
23
|
-
Object.defineProperty(Enhanced, "__isStateless", { value: isStateless, configurable: true });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const behavior = new ComponentBehavior();
|
|
28
|
-
|
|
7
|
+
// @Component only marks the class for the runtime. It adds no behavior, so it marks the class itself
|
|
8
|
+
// instead of wrapping it in an enhanced subclass: every instance would otherwise pay for the
|
|
9
|
+
// wrapper's per-instance object and per-render closure.
|
|
29
10
|
export function Component() {
|
|
30
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
12
|
return function <T extends new (...args: any[]) => ComponentInstance>(
|
|
32
13
|
constructor: T,
|
|
33
|
-
|
|
14
|
+
_context: ClassDecoratorContext,
|
|
34
15
|
): T & ComponentConstructor {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
const isStateless = (constructor.prototype as object) instanceof StatelessComponent;
|
|
17
|
+
Object.defineProperty(constructor, "__isComponent", { value: true, configurable: true });
|
|
18
|
+
Object.defineProperty(constructor, "__isStateless", { value: isStateless, configurable: true });
|
|
19
|
+
return constructor as T & ComponentConstructor;
|
|
39
20
|
};
|
|
40
21
|
}
|
package/src/component/lazy.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getComponentAnchor,
|
|
3
|
+
readSignal,
|
|
3
4
|
type RootComponent,
|
|
4
|
-
|
|
5
|
+
SignalNode,
|
|
6
|
+
writeSignal,
|
|
5
7
|
} from "@praxisjs/core/internal";
|
|
6
8
|
|
|
7
9
|
import { ClassBehavior, createClassDecorator, type ClassEnhancement } from "../create-class-decorator";
|
|
@@ -19,56 +21,64 @@ export interface LazyOptions {
|
|
|
19
21
|
rootMargin?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
// One object per instance whose methods live on the prototype, instead of three closures.
|
|
25
|
+
class LazyEnhancement implements ClassEnhancement {
|
|
26
|
+
private readonly visible = new SignalNode(false);
|
|
27
|
+
private observer: IntersectionObserver | undefined = undefined;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly instance: RootComponent,
|
|
31
|
+
private readonly options: LazyOptions,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
onMount(): void {
|
|
35
|
+
const el = getComponentAnchor(this.instance)?.parentElement ?? null;
|
|
36
|
+
if (!el) return;
|
|
37
|
+
|
|
38
|
+
if (!("IntersectionObserver" in window)) {
|
|
39
|
+
writeSignal(this.visible, true);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
26
42
|
|
|
27
|
-
create(instance: RootComponent): ClassEnhancement {
|
|
28
43
|
const { placeholder = 200, root, rootMargin = "100px" } = this.options;
|
|
29
|
-
|
|
30
|
-
let observer: IntersectionObserver | undefined;
|
|
44
|
+
if (!this.visible.value) el.style.minHeight = `${String(placeholder)}px`;
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const el = getComponentAnchor(instance)?.parentElement ?? null;
|
|
35
|
-
if (!el) return;
|
|
46
|
+
const rootEl =
|
|
47
|
+
root === undefined || root === null ? null : root.current ?? null;
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
this.observer = new IntersectionObserver(
|
|
50
|
+
(entries) => {
|
|
51
|
+
if (entries[0]?.isIntersecting) {
|
|
52
|
+
writeSignal(this.visible, true);
|
|
53
|
+
el.style.minHeight = "";
|
|
54
|
+
this.observer?.disconnect();
|
|
40
55
|
}
|
|
56
|
+
},
|
|
57
|
+
{ root: rootEl, rootMargin },
|
|
58
|
+
);
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const rootEl =
|
|
45
|
-
root === undefined || root === null ? null : root.current ?? null;
|
|
60
|
+
this.observer.observe(el);
|
|
61
|
+
}
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
visible.set(true);
|
|
51
|
-
el.style.minHeight = "";
|
|
52
|
-
observer?.disconnect();
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
{ root: rootEl, rootMargin },
|
|
56
|
-
);
|
|
63
|
+
onUnmount(): void {
|
|
64
|
+
this.observer?.disconnect();
|
|
65
|
+
}
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
render(originalRender: () => Node | Node[] | null): Node | Node[] | null {
|
|
68
|
+
return (() => {
|
|
69
|
+
if (!readSignal(this.visible)) return null;
|
|
70
|
+
return originalRender();
|
|
71
|
+
}) as unknown as Node;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
class LazyBehavior extends ClassBehavior {
|
|
76
|
+
constructor(private readonly options: LazyOptions) {
|
|
77
|
+
super();
|
|
78
|
+
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!visible()) return null;
|
|
68
|
-
return originalRender();
|
|
69
|
-
}) as unknown as Node;
|
|
70
|
-
},
|
|
71
|
-
};
|
|
80
|
+
create(instance: RootComponent): ClassEnhancement {
|
|
81
|
+
return new LazyEnhancement(instance, this.options);
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -1,68 +1,127 @@
|
|
|
1
1
|
import type { RootComponent } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
+
import { assertMemberKind } from "./field";
|
|
4
|
+
|
|
5
|
+
type RenderResult = Node | Node[] | null;
|
|
6
|
+
|
|
3
7
|
export interface ClassEnhancement {
|
|
4
8
|
onMount?(): void;
|
|
5
9
|
onUnmount?(): void;
|
|
6
|
-
render?(
|
|
7
|
-
originalRender: () => Node | Node[] | null,
|
|
8
|
-
): Node | Node[] | null;
|
|
10
|
+
render?(originalRender: () => RenderResult): RenderResult;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
type AnyConstructor = new (...args: any[]) => unknown;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
type AnyComponent = RootComponent<Record<string, any>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The behavior a class decorator adds. Every method is optional: hooks that take the instance
|
|
20
|
+
* (`onMount`, `onUnmount`, `render`) live on the behavior itself and allocate nothing per
|
|
21
|
+
* instance; `create` returns a per-instance `ClassEnhancement` when hooks need their own closures.
|
|
22
|
+
*/
|
|
23
|
+
export abstract class ClassBehavior<Instance extends object = AnyComponent> {
|
|
24
|
+
/** Names the decorator in error messages, e.g. `"@Analytics()"`. */
|
|
25
|
+
label?: string;
|
|
26
|
+
|
|
27
|
+
/** Runs once per instance, after its own constructor. */
|
|
28
|
+
create?(instance: Instance): ClassEnhancement;
|
|
29
|
+
|
|
30
|
+
/** Runs on every mount of each instance, after the class's own `onMount`. */
|
|
31
|
+
onMount?(instance: Instance): void;
|
|
32
|
+
|
|
33
|
+
/** Runs on every unmount of each instance, after the class's own `onUnmount`. */
|
|
34
|
+
onUnmount?(instance: Instance): void;
|
|
35
|
+
|
|
36
|
+
/** Wraps every render of each instance; `originalRender` renders the class's own output. */
|
|
37
|
+
render?(instance: Instance, originalRender: () => RenderResult): RenderResult;
|
|
13
38
|
|
|
14
39
|
/**
|
|
15
40
|
* Called once after the enhanced class is created.
|
|
16
41
|
* Use this to set static properties on the class constructor itself.
|
|
17
42
|
*/
|
|
18
|
-
|
|
19
|
-
initialize?(_Enhanced: new (...args: any[]) => unknown, _original: new (...args: any[]) => unknown): void;
|
|
43
|
+
initialize?(_Enhanced: AnyConstructor, _original: AnyConstructor): void;
|
|
20
44
|
}
|
|
21
45
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
46
|
+
// Keyed by symbols so they can't collide with the component's own fields.
|
|
47
|
+
const ENHANCEMENT: unique symbol = Symbol("praxis.classEnhancement");
|
|
48
|
+
// Set on an instance while its render is being called as `originalRender` from within an
|
|
49
|
+
// enhancement. While set, the Enhanced render skips the enhancement's render so stacked decorators
|
|
50
|
+
// (e.g. @Lazy + another render-overriding decorator) don't re-enter the outermost one recursively.
|
|
51
|
+
const IN_ORIGINAL_RENDER: unique symbol = Symbol("praxis.inOriginalRender");
|
|
52
|
+
|
|
53
|
+
const NO_ENHANCEMENT: ClassEnhancement = Object.freeze({});
|
|
27
54
|
|
|
28
|
-
|
|
55
|
+
type Render = (...args: unknown[]) => RenderResult;
|
|
56
|
+
|
|
57
|
+
interface EnhancedHost {
|
|
58
|
+
[ENHANCEMENT]: ClassEnhancement;
|
|
59
|
+
[IN_ORIGINAL_RENDER]?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds a class decorator from a `ClassBehavior`. The class is extended by a subclass that runs
|
|
64
|
+
* the behavior's hooks; a behavior with only `initialize` leaves the class as it is.
|
|
65
|
+
*/
|
|
66
|
+
export function createClassDecorator<Instance extends object = AnyComponent>(behavior: ClassBehavior<Instance>) {
|
|
67
|
+
const label = behavior.label ?? "This class decorator";
|
|
29
68
|
return function <
|
|
30
69
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
-
T extends new (...args: any[]) =>
|
|
32
|
-
>(constructor: T,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
T extends new (...args: any[]) => Instance,
|
|
71
|
+
>(constructor: T, context: ClassDecoratorContext): T {
|
|
72
|
+
assertMemberKind(context, "class", label);
|
|
73
|
+
const hasCreate = behavior.create !== undefined;
|
|
74
|
+
const sharedRender = behavior.render !== undefined;
|
|
75
|
+
if (!hasCreate && !sharedRender && behavior.onMount === undefined && behavior.onUnmount === undefined) {
|
|
76
|
+
behavior.initialize?.(constructor, constructor);
|
|
77
|
+
return constructor;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const original = (constructor.prototype as { render?: Render }).render;
|
|
81
|
+
|
|
82
|
+
const Enhanced = class extends (constructor as unknown as new (...args: unknown[]) => RootComponent) {
|
|
83
|
+
readonly [ENHANCEMENT]: ClassEnhancement = hasCreate
|
|
84
|
+
? (behavior as Required<Pick<ClassBehavior<Instance>, "create">>).create(this as unknown as Instance)
|
|
85
|
+
: NO_ENHANCEMENT;
|
|
37
86
|
|
|
38
87
|
onMount() {
|
|
39
88
|
super.onMount?.();
|
|
40
|
-
|
|
89
|
+
behavior.onMount?.(this as unknown as Instance);
|
|
90
|
+
this[ENHANCEMENT].onMount?.();
|
|
41
91
|
}
|
|
42
92
|
|
|
43
93
|
onUnmount() {
|
|
44
94
|
super.onUnmount?.();
|
|
45
|
-
|
|
95
|
+
behavior.onUnmount?.(this as unknown as Instance);
|
|
96
|
+
this[ENHANCEMENT].onUnmount?.();
|
|
46
97
|
}
|
|
47
98
|
|
|
48
|
-
render(...args: unknown[]):
|
|
49
|
-
const
|
|
50
|
-
|
|
99
|
+
render(...args: unknown[]): RenderResult {
|
|
100
|
+
const render = original as Render;
|
|
101
|
+
const host = this as unknown as EnhancedHost;
|
|
102
|
+
const enhancement = host[ENHANCEMENT];
|
|
103
|
+
if ((!sharedRender && enhancement.render === undefined) || host[IN_ORIGINAL_RENDER] === true) {
|
|
104
|
+
return render.apply(this, args);
|
|
105
|
+
}
|
|
106
|
+
const renderOriginal = (): RenderResult => {
|
|
107
|
+
host[IN_ORIGINAL_RENDER] = true;
|
|
51
108
|
try {
|
|
52
|
-
return
|
|
109
|
+
return render.apply(this, args);
|
|
53
110
|
} finally {
|
|
54
|
-
|
|
111
|
+
host[IN_ORIGINAL_RENDER] = false;
|
|
55
112
|
}
|
|
56
113
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
114
|
+
const inner =
|
|
115
|
+
enhancement.render === undefined
|
|
116
|
+
? renderOriginal
|
|
117
|
+
: () => (enhancement as Required<ClassEnhancement>).render(renderOriginal);
|
|
118
|
+
if (!sharedRender) return inner();
|
|
119
|
+
return (behavior as Required<Pick<ClassBehavior<Instance>, "render">>).render(this as unknown as Instance, inner);
|
|
61
120
|
}
|
|
62
121
|
};
|
|
63
122
|
|
|
64
123
|
// If the original class has no render, remove the wrapper so instances see `undefined`.
|
|
65
|
-
if (typeof
|
|
124
|
+
if (typeof original !== "function") {
|
|
66
125
|
delete (Enhanced.prototype as { render?: unknown }).render;
|
|
67
126
|
}
|
|
68
127
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { StatefulComponent } from "@praxisjs/core";
|
|
2
2
|
|
|
3
|
+
import { addClaimableInitializer, assertFieldContext, describeValue, type FieldDecorator, type FieldHost } from "./field";
|
|
4
|
+
import { addLifecycleHooks } from "./lifecycle";
|
|
5
|
+
|
|
3
6
|
export interface FieldBinding {
|
|
4
7
|
/** Replaces the decorated property. If absent, the original property is left untouched. */
|
|
5
8
|
descriptor?: PropertyDescriptor;
|
|
@@ -9,58 +12,132 @@ export interface FieldBinding {
|
|
|
9
12
|
onUnmount?: () => void;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
export interface FieldBehavior<T = object> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export interface FieldBehavior<T = object, Value = unknown> {
|
|
16
|
+
/** Names the decorator in error messages, e.g. `"@SessionValue()"`. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Runs once per instance, while it is built. */
|
|
19
|
+
bind(instance: T, name: string, initialValue: Value): FieldBinding;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* What a shared field decorator installs. `get` and `set` become one accessor pair shared by every
|
|
24
|
+
* instance (`this` is the instance), so per-instance state belongs in symbol slots on the instance,
|
|
25
|
+
* created in `init`.
|
|
26
|
+
*/
|
|
27
|
+
export interface FieldSetup<This = object, Value = unknown> {
|
|
28
|
+
get?(this: This): Value;
|
|
29
|
+
set?(this: This, value: Value): void;
|
|
30
|
+
/**
|
|
31
|
+
* Runs once per instance, before `get`/`set` are installed, with the field's initial value. A
|
|
32
|
+
* decorator stacked under this one has already installed its own property.
|
|
33
|
+
*/
|
|
34
|
+
init?: (instance: This, initialValue: Value) => void;
|
|
35
|
+
onMount?: (instance: This) => void;
|
|
36
|
+
onUnmount?: (instance: This) => void;
|
|
37
|
+
/** Defaults to `true`, like a plain field. */
|
|
38
|
+
enumerable?: boolean;
|
|
18
39
|
}
|
|
19
40
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
):
|
|
25
|
-
|
|
26
|
-
const instance = this as This & StatefulComponent & Record<string, unknown>;
|
|
27
|
-
const name = context.name as string;
|
|
28
|
-
const initialValue = instance[name];
|
|
41
|
+
export interface SharedFieldBehavior<Host = object, Value = unknown> {
|
|
42
|
+
/** Names the decorator in error messages, e.g. `"@Clamp()"`. */
|
|
43
|
+
label?: string;
|
|
44
|
+
/** Runs once per decorated field, when the class is defined. */
|
|
45
|
+
setup(name: string, context: ClassFieldDecoratorContext<Host, Value>): FieldSetup<Host, Value>;
|
|
46
|
+
}
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
const DEFAULT_LABEL = "This field decorator";
|
|
49
|
+
|
|
50
|
+
function defineValue(instance: object, name: string, value: unknown): void {
|
|
51
|
+
Object.defineProperty(instance, name, { value, writable: true, enumerable: true, configurable: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function bindField<T, Value>(behavior: FieldBehavior<T, Value>): FieldDecorator<T, Value> {
|
|
55
|
+
const label = behavior.label ?? DEFAULT_LABEL;
|
|
56
|
+
return function (_value, context) {
|
|
57
|
+
assertFieldContext(context, label);
|
|
58
|
+
const name = context.name as string;
|
|
59
|
+
addClaimableInitializer(context, function (initialValue) {
|
|
60
|
+
const instance = this as unknown as T & object;
|
|
61
|
+
const binding: unknown = behavior.bind(instance, name, initialValue as Value);
|
|
62
|
+
if (typeof binding !== "object" || binding === null) {
|
|
63
|
+
throw new TypeError(`${label}: bind() must return an object (a FieldBinding), got ${describeValue(binding)}.`);
|
|
64
|
+
}
|
|
65
|
+
const { descriptor, additional, onMount, onUnmount } = binding as FieldBinding;
|
|
35
66
|
|
|
36
67
|
if (descriptor) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
// The descriptor is fresh from bind(), so defaults are filled in place rather than copied.
|
|
69
|
+
descriptor.enumerable ??= true;
|
|
70
|
+
descriptor.configurable ??= true;
|
|
71
|
+
Object.defineProperty(instance, name, descriptor);
|
|
72
|
+
} else if (!Object.hasOwn(instance, name)) {
|
|
73
|
+
// A binding without a descriptor keeps the plain field, which a claimed field has not got yet.
|
|
74
|
+
defineValue(instance, name, initialValue);
|
|
43
75
|
}
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
if (additional) {
|
|
78
|
+
for (const key in additional) {
|
|
79
|
+
const desc = additional[key];
|
|
80
|
+
desc.configurable ??= true;
|
|
81
|
+
Object.defineProperty(instance, key, desc);
|
|
82
|
+
}
|
|
50
83
|
}
|
|
51
84
|
|
|
52
|
-
if (onMount ?? onUnmount)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
if (onMount ?? onUnmount) addLifecycleHooks(instance, onMount, onUnmount);
|
|
86
|
+
});
|
|
87
|
+
} as FieldDecorator<T, Value>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sharedFieldDecorator<Host, Value>(behavior: SharedFieldBehavior<Host, Value>): FieldDecorator<Host, Value> {
|
|
91
|
+
const label = behavior.label ?? DEFAULT_LABEL;
|
|
92
|
+
return function (_value, context) {
|
|
93
|
+
assertFieldContext(context, label);
|
|
94
|
+
const name = context.name as string;
|
|
95
|
+
const result: unknown = behavior.setup(name, context as ClassFieldDecoratorContext<Host, Value>);
|
|
96
|
+
if (typeof result !== "object" || result === null) {
|
|
97
|
+
throw new TypeError(`${label}: setup() must return an object (a FieldSetup), got ${describeValue(result)}.`);
|
|
98
|
+
}
|
|
99
|
+
const field = result as FieldSetup<Host, Value>;
|
|
100
|
+
const { init, onMount, onUnmount } = field;
|
|
101
|
+
const descriptor: PropertyDescriptor | undefined =
|
|
102
|
+
field.get === undefined && field.set === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method -- installed as accessors, which call them with the instance as `this`
|
|
105
|
+
: { get: field.get, set: field.set, enumerable: field.enumerable ?? true, configurable: true };
|
|
106
|
+
const hooks = onMount !== undefined || onUnmount !== undefined;
|
|
107
|
+
addClaimableInitializer(context, function (value) {
|
|
108
|
+
const instance = this as unknown as Host & FieldHost;
|
|
109
|
+
init?.(instance, value as Value);
|
|
110
|
+
if (descriptor !== undefined) Object.defineProperty(instance, name, descriptor);
|
|
111
|
+
else if (!Object.hasOwn(instance, name)) defineValue(instance, name, value);
|
|
112
|
+
if (hooks) {
|
|
113
|
+
addLifecycleHooks(
|
|
114
|
+
instance,
|
|
115
|
+
onMount as ((instance: object) => void) | undefined,
|
|
116
|
+
onUnmount as ((instance: object) => void) | undefined,
|
|
117
|
+
);
|
|
63
118
|
}
|
|
64
119
|
});
|
|
65
|
-
}
|
|
120
|
+
} as FieldDecorator<Host, Value>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Builds a field decorator.
|
|
125
|
+
*
|
|
126
|
+
* With `setup`, the recommended form, the decorator's accessors are created once per decorated
|
|
127
|
+
* field and shared by every instance, which keeps its state in symbol slots: building an instance
|
|
128
|
+
* allocates no closures and every instance keeps the same hidden class.
|
|
129
|
+
*
|
|
130
|
+
* With `bind`, a binding (descriptor, hooks, extra properties) is created for each instance, which
|
|
131
|
+
* suits decorators that wrap whatever property a decorator stacked under them installed.
|
|
132
|
+
*/
|
|
133
|
+
export function createFieldDecorator<Host = object, Value = unknown>(
|
|
134
|
+
behavior: SharedFieldBehavior<Host, Value>,
|
|
135
|
+
): FieldDecorator<Host, Value>;
|
|
136
|
+
export function createFieldDecorator<T = StatefulComponent, Value = unknown>(
|
|
137
|
+
behavior: FieldBehavior<T, Value>,
|
|
138
|
+
): FieldDecorator<T, Value>;
|
|
139
|
+
export function createFieldDecorator(
|
|
140
|
+
behavior: SharedFieldBehavior | FieldBehavior,
|
|
141
|
+
): FieldDecorator<object> {
|
|
142
|
+
return "setup" in behavior ? sharedFieldDecorator(behavior) : bindField(behavior);
|
|
66
143
|
}
|