@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.
Files changed (266) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/component/component.d.ts +1 -1
  3. package/dist/component/component.d.ts.map +1 -1
  4. package/dist/component/component.js +8 -15
  5. package/dist/component/component.js.map +1 -1
  6. package/dist/component/lazy.d.ts +1 -1
  7. package/dist/component/lazy.d.ts.map +1 -1
  8. package/dist/component/lazy.js +44 -36
  9. package/dist/component/lazy.js.map +1 -1
  10. package/dist/create-class-decorator.d.ts +27 -5
  11. package/dist/create-class-decorator.d.ts.map +1 -1
  12. package/dist/create-class-decorator.js +53 -18
  13. package/dist/create-class-decorator.js.map +1 -1
  14. package/dist/create-field-decorator.d.ts +42 -3
  15. package/dist/create-field-decorator.d.ts.map +1 -1
  16. package/dist/create-field-decorator.js +63 -27
  17. package/dist/create-field-decorator.js.map +1 -1
  18. package/dist/create-getter-decorator.d.ts +81 -16
  19. package/dist/create-getter-decorator.d.ts.map +1 -1
  20. package/dist/create-getter-decorator.js +127 -15
  21. package/dist/create-getter-decorator.js.map +1 -1
  22. package/dist/create-lifecycle-method-decorator.d.ts +21 -3
  23. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
  24. package/dist/create-lifecycle-method-decorator.js +25 -18
  25. package/dist/create-lifecycle-method-decorator.js.map +1 -1
  26. package/dist/create-method-decorator.d.ts +43 -3
  27. package/dist/create-method-decorator.d.ts.map +1 -1
  28. package/dist/create-method-decorator.js +49 -8
  29. package/dist/create-method-decorator.js.map +1 -1
  30. package/dist/events/command.d.ts +2 -1
  31. package/dist/events/command.d.ts.map +1 -1
  32. package/dist/events/command.js +10 -8
  33. package/dist/events/command.js.map +1 -1
  34. package/dist/events/emit.d.ts +2 -2
  35. package/dist/events/emit.d.ts.map +1 -1
  36. package/dist/events/emit.js +18 -24
  37. package/dist/events/emit.js.map +1 -1
  38. package/dist/events/on-command.d.ts +2 -1
  39. package/dist/events/on-command.d.ts.map +1 -1
  40. package/dist/events/on-command.js +12 -14
  41. package/dist/events/on-command.js.map +1 -1
  42. package/dist/field.d.ts +51 -0
  43. package/dist/field.d.ts.map +1 -0
  44. package/dist/field.js +82 -0
  45. package/dist/field.js.map +1 -0
  46. package/dist/functions/bind.d.ts +2 -1
  47. package/dist/functions/bind.d.ts.map +1 -1
  48. package/dist/functions/bind.js +4 -4
  49. package/dist/functions/bind.js.map +1 -1
  50. package/dist/functions/debounce.d.ts +2 -2
  51. package/dist/functions/debounce.d.ts.map +1 -1
  52. package/dist/functions/debounce.js +25 -23
  53. package/dist/functions/debounce.js.map +1 -1
  54. package/dist/functions/log.d.ts +2 -1
  55. package/dist/functions/log.d.ts.map +1 -1
  56. package/dist/functions/log.js +38 -40
  57. package/dist/functions/log.js.map +1 -1
  58. package/dist/functions/memo.d.ts +2 -1
  59. package/dist/functions/memo.d.ts.map +1 -1
  60. package/dist/functions/memo.js +41 -40
  61. package/dist/functions/memo.js.map +1 -1
  62. package/dist/functions/once.d.ts +2 -1
  63. package/dist/functions/once.d.ts.map +1 -1
  64. package/dist/functions/once.js +10 -14
  65. package/dist/functions/once.js.map +1 -1
  66. package/dist/functions/retry.d.ts +2 -1
  67. package/dist/functions/retry.d.ts.map +1 -1
  68. package/dist/functions/retry.js +21 -23
  69. package/dist/functions/retry.js.map +1 -1
  70. package/dist/functions/throttle.d.ts +2 -1
  71. package/dist/functions/throttle.d.ts.map +1 -1
  72. package/dist/functions/throttle.js +12 -14
  73. package/dist/functions/throttle.js.map +1 -1
  74. package/dist/functions/until.d.ts +2 -1
  75. package/dist/functions/until.d.ts.map +1 -1
  76. package/dist/functions/until.js +13 -14
  77. package/dist/functions/until.js.map +1 -1
  78. package/dist/functions/watch.d.ts +1 -1
  79. package/dist/functions/watch.d.ts.map +1 -1
  80. package/dist/functions/watch.js +36 -34
  81. package/dist/functions/watch.js.map +1 -1
  82. package/dist/functions/when.d.ts +1 -1
  83. package/dist/functions/when.d.ts.map +1 -1
  84. package/dist/functions/when.js +19 -21
  85. package/dist/functions/when.js.map +1 -1
  86. package/dist/index.d.ts +5 -5
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +2 -2
  89. package/dist/index.js.map +1 -1
  90. package/dist/lifecycle.d.ts +24 -0
  91. package/dist/lifecycle.d.ts.map +1 -0
  92. package/dist/lifecycle.js +55 -0
  93. package/dist/lifecycle.js.map +1 -0
  94. package/dist/properties/compose.d.ts +3 -2
  95. package/dist/properties/compose.d.ts.map +1 -1
  96. package/dist/properties/compose.js +33 -19
  97. package/dist/properties/compose.js.map +1 -1
  98. package/dist/properties/computed.d.ts +12 -10
  99. package/dist/properties/computed.d.ts.map +1 -1
  100. package/dist/properties/computed.js +26 -21
  101. package/dist/properties/computed.js.map +1 -1
  102. package/dist/properties/deep-state.d.ts +2 -1
  103. package/dist/properties/deep-state.d.ts.map +1 -1
  104. package/dist/properties/deep-state.js +66 -60
  105. package/dist/properties/deep-state.js.map +1 -1
  106. package/dist/properties/function-prop.d.ts +2 -1
  107. package/dist/properties/function-prop.d.ts.map +1 -1
  108. package/dist/properties/function-prop.js +20 -22
  109. package/dist/properties/function-prop.js.map +1 -1
  110. package/dist/properties/history.d.ts +2 -2
  111. package/dist/properties/history.d.ts.map +1 -1
  112. package/dist/properties/history.js +34 -33
  113. package/dist/properties/history.js.map +1 -1
  114. package/dist/properties/index.d.ts +1 -0
  115. package/dist/properties/index.d.ts.map +1 -1
  116. package/dist/properties/index.js +1 -0
  117. package/dist/properties/index.js.map +1 -1
  118. package/dist/properties/persisted.d.ts +2 -1
  119. package/dist/properties/persisted.d.ts.map +1 -1
  120. package/dist/properties/persisted.js +24 -18
  121. package/dist/properties/persisted.js.map +1 -1
  122. package/dist/properties/prop.d.ts +2 -1
  123. package/dist/properties/prop.d.ts.map +1 -1
  124. package/dist/properties/prop.js +26 -22
  125. package/dist/properties/prop.js.map +1 -1
  126. package/dist/properties/ref.d.ts +2 -1
  127. package/dist/properties/ref.d.ts.map +1 -1
  128. package/dist/properties/ref.js +11 -8
  129. package/dist/properties/ref.js.map +1 -1
  130. package/dist/properties/resource.d.ts.map +1 -1
  131. package/dist/properties/resource.js +21 -16
  132. package/dist/properties/resource.js.map +1 -1
  133. package/dist/properties/selector.d.ts +12 -0
  134. package/dist/properties/selector.d.ts.map +1 -0
  135. package/dist/properties/selector.js +25 -0
  136. package/dist/properties/selector.js.map +1 -0
  137. package/dist/properties/slot.d.ts +2 -1
  138. package/dist/properties/slot.d.ts.map +1 -1
  139. package/dist/properties/slot.js +22 -20
  140. package/dist/properties/slot.js.map +1 -1
  141. package/dist/properties/state.d.ts.map +1 -1
  142. package/dist/properties/state.js +27 -16
  143. package/dist/properties/state.js.map +1 -1
  144. package/dist/properties/synced.d.ts +2 -1
  145. package/dist/properties/synced.d.ts.map +1 -1
  146. package/dist/properties/synced.js +27 -19
  147. package/dist/properties/synced.js.map +1 -1
  148. package/dist/shared-method.d.ts +14 -0
  149. package/dist/shared-method.d.ts.map +1 -0
  150. package/dist/shared-method.js +87 -0
  151. package/dist/shared-method.js.map +1 -0
  152. package/package.json +14 -5
  153. package/src/component/component.ts +8 -27
  154. package/src/component/lazy.ts +51 -41
  155. package/src/create-class-decorator.ts +90 -31
  156. package/src/create-field-decorator.ts +120 -43
  157. package/src/create-getter-decorator.ts +263 -66
  158. package/src/create-lifecycle-method-decorator.ts +50 -28
  159. package/src/create-method-decorator.ts +119 -13
  160. package/src/events/command.ts +11 -9
  161. package/src/events/emit.ts +16 -22
  162. package/src/events/on-command.ts +17 -18
  163. package/src/field.ts +116 -0
  164. package/src/functions/bind.ts +4 -4
  165. package/src/functions/debounce.ts +25 -23
  166. package/src/functions/log.ts +44 -46
  167. package/src/functions/memo.ts +39 -37
  168. package/src/functions/once.ts +10 -13
  169. package/src/functions/retry.ts +19 -21
  170. package/src/functions/throttle.ts +12 -13
  171. package/src/functions/until.ts +14 -14
  172. package/src/functions/watch.ts +37 -36
  173. package/src/functions/when.ts +18 -20
  174. package/src/index.ts +12 -5
  175. package/src/lifecycle.ts +74 -0
  176. package/src/properties/compose.ts +33 -20
  177. package/src/properties/computed.ts +35 -45
  178. package/src/properties/deep-state.ts +66 -64
  179. package/src/properties/function-prop.ts +25 -27
  180. package/src/properties/history.ts +34 -35
  181. package/src/properties/index.ts +1 -0
  182. package/src/properties/persisted.ts +30 -20
  183. package/src/properties/prop.ts +30 -22
  184. package/src/properties/ref.ts +12 -10
  185. package/src/properties/resource.ts +22 -17
  186. package/src/properties/selector.ts +45 -0
  187. package/src/properties/slot.ts +28 -22
  188. package/src/properties/state.ts +32 -16
  189. package/src/properties/synced.ts +31 -19
  190. package/src/shared-method.ts +101 -0
  191. package/dist/__tests__/component.test.d.ts +0 -2
  192. package/dist/__tests__/component.test.d.ts.map +0 -1
  193. package/dist/__tests__/component.test.js +0 -147
  194. package/dist/__tests__/component.test.js.map +0 -1
  195. package/dist/__tests__/compose.test.d.ts +0 -2
  196. package/dist/__tests__/compose.test.d.ts.map +0 -1
  197. package/dist/__tests__/compose.test.js +0 -164
  198. package/dist/__tests__/compose.test.js.map +0 -1
  199. package/dist/__tests__/deep-state.test.d.ts +0 -2
  200. package/dist/__tests__/deep-state.test.d.ts.map +0 -1
  201. package/dist/__tests__/deep-state.test.js +0 -221
  202. package/dist/__tests__/deep-state.test.js.map +0 -1
  203. package/dist/__tests__/events.test.d.ts +0 -2
  204. package/dist/__tests__/events.test.d.ts.map +0 -1
  205. package/dist/__tests__/events.test.js +0 -295
  206. package/dist/__tests__/events.test.js.map +0 -1
  207. package/dist/__tests__/functions.test.d.ts +0 -2
  208. package/dist/__tests__/functions.test.d.ts.map +0 -1
  209. package/dist/__tests__/functions.test.js +0 -618
  210. package/dist/__tests__/functions.test.js.map +0 -1
  211. package/dist/__tests__/lazy.test.d.ts +0 -2
  212. package/dist/__tests__/lazy.test.d.ts.map +0 -1
  213. package/dist/__tests__/lazy.test.js +0 -227
  214. package/dist/__tests__/lazy.test.js.map +0 -1
  215. package/dist/__tests__/memo.test.d.ts +0 -2
  216. package/dist/__tests__/memo.test.d.ts.map +0 -1
  217. package/dist/__tests__/memo.test.js +0 -249
  218. package/dist/__tests__/memo.test.js.map +0 -1
  219. package/dist/__tests__/properties-browser.test.d.ts +0 -2
  220. package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
  221. package/dist/__tests__/properties-browser.test.js +0 -227
  222. package/dist/__tests__/properties-browser.test.js.map +0 -1
  223. package/dist/__tests__/properties.test.d.ts +0 -2
  224. package/dist/__tests__/properties.test.d.ts.map +0 -1
  225. package/dist/__tests__/properties.test.js +0 -1253
  226. package/dist/__tests__/properties.test.js.map +0 -1
  227. package/dist/__tests__/ref.test.d.ts +0 -2
  228. package/dist/__tests__/ref.test.d.ts.map +0 -1
  229. package/dist/__tests__/ref.test.js +0 -106
  230. package/dist/__tests__/ref.test.js.map +0 -1
  231. package/dist/__tests__/resource.test.d.ts +0 -2
  232. package/dist/__tests__/resource.test.d.ts.map +0 -1
  233. package/dist/__tests__/resource.test.js +0 -217
  234. package/dist/__tests__/resource.test.js.map +0 -1
  235. package/dist/__tests__/slot.test.d.ts +0 -2
  236. package/dist/__tests__/slot.test.d.ts.map +0 -1
  237. package/dist/__tests__/slot.test.js +0 -147
  238. package/dist/__tests__/slot.test.js.map +0 -1
  239. package/dist/__tests__/synced.test.d.ts +0 -2
  240. package/dist/__tests__/synced.test.d.ts.map +0 -1
  241. package/dist/__tests__/synced.test.js +0 -161
  242. package/dist/__tests__/synced.test.js.map +0 -1
  243. package/dist/__tests__/until.test.d.ts +0 -2
  244. package/dist/__tests__/until.test.d.ts.map +0 -1
  245. package/dist/__tests__/until.test.js +0 -88
  246. package/dist/__tests__/until.test.js.map +0 -1
  247. package/dist/create-accessor-decorator.d.ts +0 -14
  248. package/dist/create-accessor-decorator.d.ts.map +0 -1
  249. package/dist/create-accessor-decorator.js +0 -27
  250. package/dist/create-accessor-decorator.js.map +0 -1
  251. package/src/__tests__/component.test.ts +0 -167
  252. package/src/__tests__/compose.test.ts +0 -202
  253. package/src/__tests__/deep-state.test.ts +0 -265
  254. package/src/__tests__/events.test.ts +0 -366
  255. package/src/__tests__/functions.test.ts +0 -803
  256. package/src/__tests__/lazy.test.ts +0 -296
  257. package/src/__tests__/memo.test.ts +0 -293
  258. package/src/__tests__/properties-browser.test.ts +0 -288
  259. package/src/__tests__/properties.test.ts +0 -1556
  260. package/src/__tests__/ref.test.ts +0 -131
  261. package/src/__tests__/resource.test.ts +0 -246
  262. package/src/__tests__/slot.test.ts +0 -179
  263. package/src/__tests__/synced.test.ts +0 -193
  264. package/src/__tests__/until.test.ts +0 -113
  265. package/src/create-accessor-decorator.ts +0 -45
  266. 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": "1.7.0",
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.2.0",
19
- "@praxisjs/shared": "0.3.1"
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
- import {
9
- ClassBehavior,
10
- createClassDecorator,
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
- context: ClassDecoratorContext,
14
+ _context: ClassDecoratorContext,
34
15
  ): T & ComponentConstructor {
35
- return createClassDecorator(behavior)(
36
- constructor as unknown as T & (new(...args: unknown[]) => RootComponent<Record<string, unknown>>),
37
- context,
38
- ) as unknown as T & ComponentConstructor;
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
  }
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  getComponentAnchor,
3
+ readSignal,
3
4
  type RootComponent,
4
- signal,
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
- class LazyBehavior extends ClassBehavior {
23
- constructor(private readonly options: LazyOptions) {
24
- super();
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
- const visible = signal(false);
30
- let observer: IntersectionObserver | undefined;
44
+ if (!this.visible.value) el.style.minHeight = `${String(placeholder)}px`;
31
45
 
32
- return {
33
- onMount() {
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
- if (!("IntersectionObserver" in window)) {
38
- visible.set(true);
39
- return;
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
- if (!visible()) el.style.minHeight = `${String(placeholder)}px`;
43
-
44
- const rootEl =
45
- root === undefined || root === null ? null : root.current ?? null;
60
+ this.observer.observe(el);
61
+ }
46
62
 
47
- observer = new IntersectionObserver(
48
- (entries) => {
49
- if (entries[0]?.isIntersecting) {
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
- observer.observe(el);
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
- onUnmount() {
62
- observer?.disconnect();
63
- },
75
+ class LazyBehavior extends ClassBehavior {
76
+ constructor(private readonly options: LazyOptions) {
77
+ super();
78
+ }
64
79
 
65
- render(originalRender) {
66
- return (() => {
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
- export abstract class ClassBehavior {
12
- abstract create(instance: RootComponent): ClassEnhancement;
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- // Tracks instances whose render is currently being called as `originalRender`
23
- // from within an enhancement. When set, the Enhanced render skips the
24
- // this._enh.render dispatch so stacked decorators (e.g. @Lazy + @Component)
25
- // don't re-enter the outermost enhancement recursively.
26
- const _originalRenderInProgress = new WeakSet();
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
- export function createClassDecorator(behavior: ClassBehavior) {
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[]) => RootComponent<Record<string, any>>,
32
- >(constructor: T, _context: ClassDecoratorContext): T {
33
- const Enhanced = class extends constructor {
34
- private readonly _enh: ClassEnhancement = behavior.create(
35
- this as unknown as RootComponent,
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
- this._enh.onMount?.();
89
+ behavior.onMount?.(this as unknown as Instance);
90
+ this[ENHANCEMENT].onMount?.();
41
91
  }
42
92
 
43
93
  onUnmount() {
44
94
  super.onUnmount?.();
45
- this._enh.onUnmount?.();
95
+ behavior.onUnmount?.(this as unknown as Instance);
96
+ this[ENHANCEMENT].onUnmount?.();
46
97
  }
47
98
 
48
- render(...args: unknown[]): Node | Node[] | null {
49
- const originalRender = () => {
50
- _originalRenderInProgress.add(this);
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 (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
109
+ return render.apply(this, args);
53
110
  } finally {
54
- _originalRenderInProgress.delete(this);
111
+ host[IN_ORIGINAL_RENDER] = false;
55
112
  }
56
113
  };
57
- if (this._enh.render && !_originalRenderInProgress.has(this)) {
58
- return this._enh.render(originalRender);
59
- }
60
- return (constructor.prototype as { render: (...a: unknown[]) => Node | Node[] | null }).render.apply(this, args);
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 (constructor.prototype as { render?: unknown }).render !== "function") {
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
- bind(
14
- instance: T,
15
- name: string,
16
- initialValue: unknown,
17
- ): FieldBinding;
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 function createFieldDecorator<T = StatefulComponent>(behavior: FieldBehavior<T>) {
21
- return function <This extends T, Value = unknown>(
22
- _value: undefined,
23
- context: ClassFieldDecoratorContext<This, Value>,
24
- ): void {
25
- context.addInitializer(function (this: unknown) {
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
- const { descriptor, additional, onMount, onUnmount } = behavior.bind(
31
- instance as T,
32
- name,
33
- initialValue,
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
- Reflect.deleteProperty(instance, name);
38
- Object.defineProperty(instance, name, {
39
- enumerable: true,
40
- configurable: true,
41
- ...descriptor,
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
- for (const [key, desc] of Object.entries(additional ?? {})) {
46
- Object.defineProperty(instance, key, {
47
- configurable: true,
48
- ...desc,
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
- 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
- };
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
  }