@praxisjs/decorators 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/__tests__/component.test.js +10 -1
  3. package/dist/__tests__/component.test.js.map +1 -1
  4. package/dist/__tests__/compose.test.d.ts +2 -0
  5. package/dist/__tests__/compose.test.d.ts.map +1 -0
  6. package/dist/__tests__/compose.test.js +171 -0
  7. package/dist/__tests__/compose.test.js.map +1 -0
  8. package/dist/__tests__/events.test.js +26 -0
  9. package/dist/__tests__/events.test.js.map +1 -1
  10. package/dist/__tests__/functions.test.js +142 -47
  11. package/dist/__tests__/functions.test.js.map +1 -1
  12. package/dist/__tests__/lazy.test.js +5 -7
  13. package/dist/__tests__/lazy.test.js.map +1 -1
  14. package/dist/__tests__/memo.test.js +61 -25
  15. package/dist/__tests__/memo.test.js.map +1 -1
  16. package/dist/__tests__/resource.test.d.ts +2 -0
  17. package/dist/__tests__/resource.test.d.ts.map +1 -0
  18. package/dist/__tests__/resource.test.js +103 -0
  19. package/dist/__tests__/resource.test.js.map +1 -0
  20. package/dist/__tests__/slot.test.js +32 -0
  21. package/dist/__tests__/slot.test.js.map +1 -1
  22. package/dist/__tests__/virtual.test.js +56 -15
  23. package/dist/__tests__/virtual.test.js.map +1 -1
  24. package/dist/component/component.d.ts +1 -1
  25. package/dist/component/component.d.ts.map +1 -1
  26. package/dist/component/component.js +15 -10
  27. package/dist/component/component.js.map +1 -1
  28. package/dist/component/lazy.d.ts.map +1 -1
  29. package/dist/component/lazy.js +29 -30
  30. package/dist/component/lazy.js.map +1 -1
  31. package/dist/component/virtual.d.ts.map +1 -1
  32. package/dist/component/virtual.js +46 -50
  33. package/dist/component/virtual.js.map +1 -1
  34. package/dist/create-class-decorator.d.ts +16 -0
  35. package/dist/create-class-decorator.d.ts.map +1 -0
  36. package/dist/create-class-decorator.js +35 -0
  37. package/dist/create-class-decorator.js.map +1 -0
  38. package/dist/create-field-decorator.d.ts +14 -0
  39. package/dist/create-field-decorator.d.ts.map +1 -0
  40. package/dist/create-field-decorator.js +37 -0
  41. package/dist/create-field-decorator.js.map +1 -0
  42. package/dist/create-getter-decorator.d.ts +18 -0
  43. package/dist/create-getter-decorator.d.ts.map +1 -0
  44. package/dist/create-getter-decorator.js +15 -0
  45. package/dist/create-getter-decorator.js.map +1 -0
  46. package/dist/create-lifecycle-method-decorator.d.ts +6 -0
  47. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
  48. package/dist/create-lifecycle-method-decorator.js +22 -0
  49. package/dist/create-lifecycle-method-decorator.js.map +1 -0
  50. package/dist/create-method-decorator.d.ts +6 -0
  51. package/dist/create-method-decorator.d.ts.map +1 -0
  52. package/dist/create-method-decorator.js +14 -0
  53. package/dist/create-method-decorator.js.map +1 -0
  54. package/dist/events/emit.d.ts +1 -1
  55. package/dist/events/emit.d.ts.map +1 -1
  56. package/dist/events/emit.js +24 -29
  57. package/dist/events/emit.js.map +1 -1
  58. package/dist/events/on-command.d.ts +1 -2
  59. package/dist/events/on-command.d.ts.map +1 -1
  60. package/dist/events/on-command.js +15 -32
  61. package/dist/events/on-command.js.map +1 -1
  62. package/dist/functions/bind.d.ts +1 -2
  63. package/dist/functions/bind.d.ts.map +1 -1
  64. package/dist/functions/bind.js +4 -11
  65. package/dist/functions/bind.js.map +1 -1
  66. package/dist/functions/debounce.d.ts +1 -2
  67. package/dist/functions/debounce.d.ts.map +1 -1
  68. package/dist/functions/debounce.js +12 -20
  69. package/dist/functions/debounce.js.map +1 -1
  70. package/dist/functions/log.d.ts +1 -2
  71. package/dist/functions/log.d.ts.map +1 -1
  72. package/dist/functions/log.js +40 -38
  73. package/dist/functions/log.js.map +1 -1
  74. package/dist/functions/memo.d.ts +1 -2
  75. package/dist/functions/memo.d.ts.map +1 -1
  76. package/dist/functions/memo.js +15 -27
  77. package/dist/functions/memo.js.map +1 -1
  78. package/dist/functions/once.d.ts +1 -2
  79. package/dist/functions/once.d.ts.map +1 -1
  80. package/dist/functions/once.js +14 -12
  81. package/dist/functions/once.js.map +1 -1
  82. package/dist/functions/retry.d.ts +1 -2
  83. package/dist/functions/retry.d.ts.map +1 -1
  84. package/dist/functions/retry.js +23 -20
  85. package/dist/functions/retry.js.map +1 -1
  86. package/dist/functions/throttle.d.ts +1 -2
  87. package/dist/functions/throttle.d.ts.map +1 -1
  88. package/dist/functions/throttle.js +9 -16
  89. package/dist/functions/throttle.js.map +1 -1
  90. package/dist/functions/watch.d.ts +1 -1
  91. package/dist/functions/watch.d.ts.map +1 -1
  92. package/dist/functions/watch.js +25 -39
  93. package/dist/functions/watch.js.map +1 -1
  94. package/dist/functions/when.d.ts +1 -2
  95. package/dist/functions/when.d.ts.map +1 -1
  96. package/dist/functions/when.js +10 -26
  97. package/dist/functions/when.js.map +1 -1
  98. package/dist/index.d.ts +6 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +6 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/properties/compose.d.ts +3 -0
  103. package/dist/properties/compose.d.ts.map +1 -0
  104. package/dist/properties/compose.js +40 -0
  105. package/dist/properties/compose.js.map +1 -0
  106. package/dist/properties/computed.d.ts +1 -2
  107. package/dist/properties/computed.d.ts.map +1 -1
  108. package/dist/properties/computed.js +10 -9
  109. package/dist/properties/computed.js.map +1 -1
  110. package/dist/properties/history.d.ts.map +1 -1
  111. package/dist/properties/history.js +37 -32
  112. package/dist/properties/history.js.map +1 -1
  113. package/dist/properties/index.d.ts +2 -0
  114. package/dist/properties/index.d.ts.map +1 -1
  115. package/dist/properties/index.js +2 -0
  116. package/dist/properties/index.js.map +1 -1
  117. package/dist/properties/persisted.d.ts +1 -2
  118. package/dist/properties/persisted.d.ts.map +1 -1
  119. package/dist/properties/persisted.js +13 -29
  120. package/dist/properties/persisted.js.map +1 -1
  121. package/dist/properties/prop.d.ts +1 -2
  122. package/dist/properties/prop.d.ts.map +1 -1
  123. package/dist/properties/prop.js +21 -25
  124. package/dist/properties/prop.js.map +1 -1
  125. package/dist/properties/resource.d.ts +14 -0
  126. package/dist/properties/resource.d.ts.map +1 -0
  127. package/dist/properties/resource.js +27 -0
  128. package/dist/properties/resource.js.map +1 -0
  129. package/dist/properties/slot.d.ts +1 -2
  130. package/dist/properties/slot.d.ts.map +1 -1
  131. package/dist/properties/slot.js +15 -20
  132. package/dist/properties/slot.js.map +1 -1
  133. package/dist/properties/state.d.ts +1 -2
  134. package/dist/properties/state.d.ts.map +1 -1
  135. package/dist/properties/state.js +13 -18
  136. package/dist/properties/state.js.map +1 -1
  137. package/package.json +2 -2
  138. package/src/__tests__/component.test.ts +10 -0
  139. package/src/__tests__/compose.test.ts +202 -0
  140. package/src/__tests__/events.test.ts +32 -0
  141. package/src/__tests__/functions.test.ts +177 -46
  142. package/src/__tests__/lazy.test.ts +3 -5
  143. package/src/__tests__/memo.test.ts +76 -25
  144. package/src/__tests__/resource.test.ts +113 -0
  145. package/src/__tests__/slot.test.ts +38 -0
  146. package/src/__tests__/virtual.test.ts +26 -7
  147. package/src/component/component.ts +27 -12
  148. package/src/component/lazy.ts +33 -37
  149. package/src/component/virtual.tsx +62 -75
  150. package/src/create-class-decorator.ts +61 -0
  151. package/src/create-field-decorator.ts +66 -0
  152. package/src/create-getter-decorator.ts +52 -0
  153. package/src/create-lifecycle-method-decorator.ts +38 -0
  154. package/src/create-method-decorator.ts +26 -0
  155. package/src/events/emit.ts +22 -33
  156. package/src/events/on-command.ts +22 -47
  157. package/src/functions/bind.ts +4 -15
  158. package/src/functions/debounce.ts +11 -26
  159. package/src/functions/log.ts +40 -43
  160. package/src/functions/memo.ts +15 -45
  161. package/src/functions/once.ts +13 -16
  162. package/src/functions/retry.ts +21 -22
  163. package/src/functions/throttle.ts +9 -23
  164. package/src/functions/watch.ts +27 -43
  165. package/src/functions/when.ts +11 -36
  166. package/src/index.ts +28 -0
  167. package/src/properties/compose.ts +53 -0
  168. package/src/properties/computed.ts +11 -13
  169. package/src/properties/history.ts +42 -41
  170. package/src/properties/index.ts +2 -0
  171. package/src/properties/persisted.ts +17 -58
  172. package/src/properties/prop.ts +21 -31
  173. package/src/properties/resource.ts +34 -0
  174. package/src/properties/slot.ts +17 -30
  175. package/src/properties/state.ts +14 -24
@@ -1,29 +1,15 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createMethodDecorator } from "../create-method-decorator";
2
2
 
3
3
  export function Throttle(ms: number) {
4
- const lastRun = new WeakMap<object, number>();
5
-
6
- return function (
7
- value: (this: object, ...args: unknown[]) => unknown,
8
- context: ClassMethodDecoratorContext<StatefulComponent>,
9
- ): void {
10
- context.addInitializer(function (this: unknown) {
11
- const instance = this as object;
12
- const name = context.name as string;
13
- const bound = (...args: unknown[]) => {
4
+ return createMethodDecorator({
5
+ wrap(original, instance) {
6
+ let last = 0;
7
+ return (...args: unknown[]) => {
14
8
  const now = Date.now();
15
- const last = lastRun.get(instance) ?? 0;
16
-
17
9
  if (now - last < ms) return;
18
-
19
- lastRun.set(instance, now);
20
- return value.apply(instance, args);
10
+ last = now;
11
+ return original.apply(instance, args);
21
12
  };
22
- Object.defineProperty(instance, name, {
23
- value: bound,
24
- configurable: true,
25
- writable: true,
26
- });
27
- });
28
- };
13
+ },
14
+ });
29
15
  }
@@ -3,6 +3,8 @@ import { effect } from "@praxisjs/core/internal";
3
3
  import type { Computed } from "@praxisjs/shared";
4
4
  import { isComputed } from "@praxisjs/shared/internal";
5
5
 
6
+ import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
7
+
6
8
  type BaseComponentKeys = keyof StatefulComponent;
7
9
 
8
10
  type WatchableKeys<T> = {
@@ -54,51 +56,33 @@ export function Watch<
54
56
  T extends StatefulComponent,
55
57
  const Keys extends ReadonlyArray<WatchableKeys<T>>,
56
58
  >(...propNames: ValidateKeys<T, Keys>) {
57
- return function (
58
- value: (this: T, ...args: unknown[]) => void,
59
- context: ClassMethodDecoratorContext<T>,
60
- ): void {
61
- const props = propNames as unknown as string[];
59
+ const props = propNames as unknown as string[];
62
60
 
63
- context.addInitializer(function (this: unknown) {
64
- const instance = this as T & Record<string, unknown>;
65
- // eslint-disable-next-line @typescript-eslint/unbound-method
66
- const originalOnMount = instance.onMount;
67
- // eslint-disable-next-line @typescript-eslint/unbound-method
68
- const originalOnUnmount = instance.onUnmount;
69
- let stopEffect: (() => void) | undefined;
61
+ return createLifecycleMethodDecorator<T>({
62
+ register(callback, instance) {
63
+ const inst = instance as T & Record<string, unknown>;
70
64
 
71
- instance.onMount = function (this: T & Record<string, unknown>) {
72
- originalOnMount?.call(this);
65
+ if (props.length === 1) {
66
+ let oldVal = readValue(inst, props[0]);
67
+ return effect(() => {
68
+ const newVal = readValue(inst, props[0]);
69
+ if (!Object.is(newVal, oldVal)) {
70
+ callback(newVal, oldVal);
71
+ oldVal = newVal;
72
+ }
73
+ });
74
+ }
73
75
 
74
- if (props.length === 1) {
75
- let oldVal = readValue(this, props[0]);
76
- stopEffect = effect(() => {
77
- const newVal = readValue(this, props[0]);
78
- if (!Object.is(newVal, oldVal)) {
79
- value.call(this as T, newVal, oldVal);
80
- oldVal = newVal;
81
- }
82
- });
83
- } else {
84
- const read = () =>
85
- Object.fromEntries(props.map((p) => [p, readValue(this, p)]));
86
- let oldVals = read();
87
- stopEffect = effect(() => {
88
- const newVals = read();
89
- if (props.some((p) => !Object.is(newVals[p], oldVals[p]))) {
90
- value.call(this as T, newVals, oldVals);
91
- oldVals = newVals;
92
- }
93
- });
76
+ const read = () =>
77
+ Object.fromEntries(props.map((p) => [p, readValue(inst, p)]));
78
+ let oldVals = read();
79
+ return effect(() => {
80
+ const newVals = read();
81
+ if (props.some((p) => !Object.is(newVals[p], oldVals[p]))) {
82
+ callback(newVals, oldVals);
83
+ oldVals = newVals;
94
84
  }
95
- };
96
-
97
- instance.onUnmount = function (this: T) {
98
- originalOnUnmount?.call(this);
99
- stopEffect?.();
100
- stopEffect = undefined;
101
- };
102
- });
103
- };
85
+ });
86
+ },
87
+ });
104
88
  }
@@ -1,42 +1,17 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
1
  import { when } from "@praxisjs/core/internal";
3
2
  import type { Computed, Signal } from "@praxisjs/shared";
4
3
  import { isComputed } from "@praxisjs/shared/internal";
5
4
 
6
- export function When(propName: string) {
7
- return function (
8
- value: (this: StatefulComponent, val: unknown) => void,
9
- context: ClassMethodDecoratorContext<StatefulComponent>,
10
- ): void {
11
- const cancels = new WeakMap<object, () => void>();
12
-
13
- context.addInitializer(function (this: unknown) {
14
- const instance = this as StatefulComponent;
15
- // eslint-disable-next-line @typescript-eslint/unbound-method
16
- const originalOnMount = instance.onMount;
17
- // eslint-disable-next-line @typescript-eslint/unbound-method
18
- const originalOnUnmount = instance.onUnmount;
19
-
20
- instance.onMount = function (this: StatefulComponent) {
21
- originalOnMount?.call(this);
22
-
23
- const raw = (this as unknown as Record<string, unknown>)[propName];
24
- const source = isComputed(raw)
25
- ? (raw as Computed<unknown>)
26
- : (raw as Signal<unknown>);
5
+ import { createLifecycleMethodDecorator } from "../create-lifecycle-method-decorator";
27
6
 
28
- const cancel = when(source, (val) => {
29
- value.call(this, val);
30
- });
31
-
32
- cancels.set(this, cancel);
33
- };
34
-
35
- instance.onUnmount = function (this: StatefulComponent) {
36
- originalOnUnmount?.call(this);
37
- cancels.get(this)?.();
38
- cancels.delete(this);
39
- };
40
- });
41
- };
7
+ export function When(propName: string) {
8
+ return createLifecycleMethodDecorator({
9
+ register(callback, instance) {
10
+ const raw = (instance as unknown as Record<string, unknown>)[propName];
11
+ const source = isComputed(raw)
12
+ ? (raw as Computed<unknown>)
13
+ : (raw as Signal<unknown>);
14
+ return when(source, (val) => { callback(val); });
15
+ },
16
+ });
42
17
  }
package/src/index.ts CHANGED
@@ -21,5 +21,33 @@ export {
21
21
  initSlots,
22
22
  History,
23
23
  type WithHistory,
24
+ Compose,
25
+ Resource,
26
+ type ResourceInstance,
27
+ type ResourceOptions,
24
28
  } from "./properties";
25
29
  export { type Command, createCommand, Emit, OnCommand } from "./events";
30
+ export {
31
+ createFieldDecorator,
32
+ type FieldBehavior,
33
+ type FieldBinding,
34
+ } from "./create-field-decorator";
35
+ export {
36
+ createClassDecorator,
37
+ ClassBehavior,
38
+ type ClassEnhancement,
39
+ } from "./create-class-decorator";
40
+ export {
41
+ createMethodDecorator,
42
+ type MethodBehavior,
43
+ } from "./create-method-decorator";
44
+ export {
45
+ createLifecycleMethodDecorator,
46
+ type LifecycleMethodBehavior,
47
+ } from "./create-lifecycle-method-decorator";
48
+ export {
49
+ createGetterDecorator,
50
+ type GetterBehavior,
51
+ createGetterObserverDecorator,
52
+ type GetterObserverBehavior,
53
+ } from "./create-getter-decorator";
@@ -0,0 +1,53 @@
1
+ import type { StatefulComponent, Composable } from "@praxisjs/core";
2
+
3
+ import { createFieldDecorator } from "../create-field-decorator";
4
+
5
+ function isReactive(
6
+ value: unknown,
7
+ ): value is () => unknown {
8
+ return (
9
+ typeof value === "function" &&
10
+ ("__isSignal" in (value as object) || "__isComputed" in (value as object))
11
+ );
12
+ }
13
+
14
+ function buildView(bindings: Record<string, unknown>): object {
15
+ const view: Record<string, unknown> = {};
16
+ for (const [key, value] of Object.entries(bindings)) {
17
+ if (isReactive(value)) {
18
+ Object.defineProperty(view, key, {
19
+ get: () => (value as () => unknown)(),
20
+ enumerable: true,
21
+ configurable: true,
22
+ });
23
+ } else {
24
+ view[key] = value;
25
+ }
26
+ }
27
+ return view;
28
+ }
29
+
30
+ export function Compose(
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ ComposableClass: new (...args: any[]) => Composable,
33
+ ...ctorArgs: unknown[]
34
+ ) {
35
+ return createFieldDecorator({
36
+ bind(instance: StatefulComponent, _name: string, _initialValue: unknown) {
37
+ const resolvedArgs = ctorArgs.map((arg) =>
38
+ typeof arg === "string"
39
+ ? (instance as unknown as Record<string, unknown>)[arg]
40
+ : arg,
41
+ );
42
+
43
+ const composable = new ComposableClass(...resolvedArgs);
44
+ const view = buildView(composable.setup());
45
+
46
+ return {
47
+ descriptor: { get: () => view },
48
+ onMount: composable.onMount?.bind(composable),
49
+ onUnmount: composable.onUnmount?.bind(composable),
50
+ };
51
+ },
52
+ });
53
+ }
@@ -1,20 +1,18 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
1
  import { computed } from "@praxisjs/core/internal";
3
2
 
3
+ import { createGetterDecorator } from "../create-getter-decorator";
4
+
4
5
  export function Computed() {
5
- return function <This extends StatefulComponent, T>(
6
- target: (this: This) => T,
7
- _context: ClassGetterDecoratorContext<This, T>,
8
- ): (this: This) => T {
9
- const computedMap = new WeakMap<object, () => T>();
6
+ const cache = new WeakMap<object, () => unknown>();
10
7
 
11
- return function (this: This): T {
12
- let c = computedMap.get(this);
8
+ return createGetterDecorator({
9
+ wrap(original, instance) {
10
+ let c = cache.get(instance);
13
11
  if (!c) {
14
- c = computed(() => target.call(this));
15
- computedMap.set(this, c);
12
+ c = computed(() => original.call(instance));
13
+ cache.set(instance, c);
16
14
  }
17
- return c();
18
- };
19
- };
15
+ return c;
16
+ },
17
+ });
20
18
  }
@@ -2,52 +2,53 @@ import type { StatefulComponent } from "@praxisjs/core";
2
2
  import { type HistoryElement, history } from "@praxisjs/core/internal";
3
3
  import type { Signal } from "@praxisjs/shared";
4
4
 
5
+ import { createFieldDecorator } from "../create-field-decorator";
6
+
5
7
  export type WithHistory<T, K extends keyof T> = Record<
6
8
  `${string & K}History`,
7
9
  HistoryElement<T[K]>
8
10
  >;
9
11
 
10
12
  export function History(limit = 50) {
11
- return function (
12
- _value: undefined,
13
- context: ClassFieldDecoratorContext<StatefulComponent>,
14
- ): void {
15
- const histories = new WeakMap<object, HistoryElement<unknown>>();
16
-
17
- context.addInitializer(function (this: unknown) {
18
- const instance = this as Record<string, unknown>;
19
- const propertyKey = context.name as string;
20
- const historyKey = `${propertyKey}History`;
21
-
22
- Object.defineProperty(instance, historyKey, {
23
- get(this: Record<string, unknown>): HistoryElement<unknown> {
24
- if (!histories.has(this)) {
25
- const source = () => this[propertyKey];
26
-
27
- const h = history(source as Signal<unknown>, limit);
28
-
29
- const _undo = h.undo.bind(h);
30
- const _redo = h.redo.bind(h);
31
-
32
- h.undo = () => {
33
- const prev = h.values()[h.values().length - 2];
34
- if (prev === undefined) return;
35
- _undo();
36
- this[propertyKey] = prev;
37
- };
38
-
39
- h.redo = () => {
40
- _redo();
41
- this[propertyKey] = h.current();
42
- };
43
-
44
- histories.set(this, h);
45
- }
46
- return histories.get(this) as HistoryElement<unknown>;
13
+ return createFieldDecorator({
14
+ bind(instance: StatefulComponent, name: string, _initialValue: unknown) {
15
+ const historyKey = `${name}History`;
16
+ const inst = instance as unknown as Record<string, unknown>;
17
+ let h: HistoryElement<unknown> | undefined;
18
+
19
+ return {
20
+ // descriptor omitted — @History does not replace the original field
21
+ additional: {
22
+ [historyKey]: {
23
+ get(): HistoryElement<unknown> {
24
+ if (!h) {
25
+ const source = () => inst[name];
26
+ const hInst = history(source as Signal<unknown>, limit);
27
+
28
+ const _undo = hInst.undo.bind(hInst);
29
+ const _redo = hInst.redo.bind(hInst);
30
+
31
+ hInst.undo = () => {
32
+ const prev = hInst.values()[hInst.values().length - 2];
33
+ if (prev === undefined) return;
34
+ _undo();
35
+ inst[name] = prev;
36
+ };
37
+
38
+ hInst.redo = () => {
39
+ _redo();
40
+ inst[name] = hInst.current();
41
+ };
42
+
43
+ h = hInst;
44
+ }
45
+ return h;
46
+ },
47
+ enumerable: false,
48
+ configurable: true,
49
+ },
47
50
  },
48
- enumerable: false,
49
- configurable: true,
50
- });
51
- });
52
- };
51
+ };
52
+ },
53
+ });
53
54
  }
@@ -4,3 +4,5 @@ export { Computed } from "./computed";
4
4
  export { Persisted } from "./persisted";
5
5
  export { Slot, initSlots } from "./slot";
6
6
  export { History, type WithHistory } from "./history";
7
+ export { Compose } from "./compose";
8
+ export { Resource, type ResourceInstance, type ResourceOptions } from "./resource";
@@ -1,19 +1,9 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
2
- import {
3
- persistedSignal,
4
- type PersistedSignalOptions,
5
- } from "@praxisjs/core/internal";
1
+ import { persistedSignal, type PersistedSignalOptions } from "@praxisjs/core/internal";
6
2
  import type { Signal } from "@praxisjs/shared";
7
3
 
8
- const signalMap = new WeakMap<object, Map<string, Signal<unknown>>>();
9
- const initMap = new WeakMap<object, Set<string>>();
4
+ import { createFieldDecorator } from "../create-field-decorator";
10
5
 
11
- function markInitialized(instance: object, propKey: string): void {
12
- if (!initMap.has(instance)) {
13
- initMap.set(instance, new Set());
14
- }
15
- (initMap.get(instance) as Set<string>).add(propKey);
16
- }
6
+ const signalMap = new WeakMap<object, Map<string, Signal<unknown>>>();
17
7
 
18
8
  function getOrCreateSignal<T>(
19
9
  instance: object,
@@ -21,18 +11,14 @@ function getOrCreateSignal<T>(
21
11
  initialValue: T,
22
12
  options: PersistedSignalOptions<T>,
23
13
  ): Signal<T> {
24
- if (!signalMap.has(instance)) {
25
- signalMap.set(instance, new Map());
26
- }
14
+ if (!signalMap.has(instance)) signalMap.set(instance, new Map());
27
15
  const map = signalMap.get(instance) as Map<string, Signal<unknown>>;
28
-
29
16
  if (!map.has(storageKey)) {
30
17
  map.set(
31
18
  storageKey,
32
19
  persistedSignal(storageKey, initialValue, options) as Signal<unknown>,
33
20
  );
34
21
  }
35
-
36
22
  return map.get(storageKey) as Signal<T>;
37
23
  }
38
24
 
@@ -40,45 +26,18 @@ export function Persisted<T>(
40
26
  key?: string,
41
27
  options: PersistedSignalOptions<T> = {},
42
28
  ) {
43
- return function (
44
- _value: undefined,
45
- context: ClassFieldDecoratorContext<StatefulComponent>,
46
- ): void {
47
- context.addInitializer(function (this: unknown) {
48
- const instance = this as object;
49
- const propertyKey = context.name as string;
50
- const storageKey = key ?? propertyKey;
51
- const initialValue = (instance as Record<string, unknown>)[
52
- propertyKey
53
- ] as T;
54
-
55
- Reflect.deleteProperty(instance, propertyKey);
56
-
57
- markInitialized(instance, propertyKey);
58
- getOrCreateSignal(instance, storageKey, initialValue, options);
59
-
60
- Object.defineProperty(instance, propertyKey, {
61
- get(): T {
62
- return getOrCreateSignal<T>(
63
- instance,
64
- storageKey,
65
- undefined as T,
66
- options,
67
- )();
29
+ return createFieldDecorator({
30
+ bind(instance, name, initialValue) {
31
+ const storageKey = key ?? name;
32
+ getOrCreateSignal(instance, storageKey, initialValue as T, options);
33
+ return {
34
+ descriptor: {
35
+ get: () =>
36
+ getOrCreateSignal<T>(instance, storageKey, undefined as T, options)(),
37
+ set: (value: T) =>
38
+ { getOrCreateSignal<T>(instance, storageKey, undefined as T, options).set(value); },
68
39
  },
69
-
70
- set(value: T): void {
71
- getOrCreateSignal<T>(
72
- instance,
73
- storageKey,
74
- undefined as T,
75
- options,
76
- ).set(value);
77
- },
78
-
79
- enumerable: true,
80
- configurable: true,
81
- });
82
- });
83
- };
40
+ };
41
+ },
42
+ });
84
43
  }
@@ -1,35 +1,25 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createFieldDecorator } from "../create-field-decorator";
2
2
 
3
3
  export function Prop() {
4
- return function (
5
- _value: undefined,
6
- context: ClassFieldDecoratorContext<StatefulComponent>,
7
- ): void {
8
- context.addInitializer(function (this: unknown) {
9
- const instance = this as StatefulComponent;
10
- const name = context.name as string;
11
- const raw = instance as unknown as Record<string, unknown>;
12
- const defaultValue = raw[name];
13
- Reflect.deleteProperty(raw, name);
14
-
15
- instance._defaults[name] = defaultValue;
16
-
17
- Object.defineProperty(instance, name, {
18
- get(this: StatefulComponent) {
19
- const fromParent = this._rawProps[name];
20
- if (fromParent !== undefined) {
21
- return typeof fromParent === "function"
22
- ? (fromParent as () => unknown)()
23
- : fromParent;
24
- }
25
- return this._defaults[name];
26
- },
27
- set(this: StatefulComponent, value: unknown) {
28
- this._defaults[name] = value;
4
+ return createFieldDecorator({
5
+ bind(instance, name, initialValue) {
6
+ instance._defaults[name] = initialValue;
7
+ return {
8
+ descriptor: {
9
+ get() {
10
+ const fromParent = (instance._rawProps)[name];
11
+ if (fromParent !== undefined) {
12
+ return typeof fromParent === "function"
13
+ ? (fromParent as () => unknown)()
14
+ : fromParent;
15
+ }
16
+ return instance._defaults[name];
17
+ },
18
+ set(value: unknown) {
19
+ instance._defaults[name] = value;
20
+ },
29
21
  },
30
- enumerable: true,
31
- configurable: true,
32
- });
33
- });
34
- };
22
+ };
23
+ },
24
+ });
35
25
  }
@@ -0,0 +1,34 @@
1
+ import {
2
+ resource,
3
+ type Resource as ResourceInstance,
4
+ type ResourceOptions,
5
+ } from "@praxisjs/core/internal";
6
+
7
+ import { createFieldDecorator, type FieldBinding } from "../create-field-decorator";
8
+
9
+ export type { ResourceInstance, ResourceOptions };
10
+
11
+ /**
12
+ * Binds an async resource to the field. The field holds a Resource<T> object
13
+ * with reactive data, pending, error, and status signals, plus refetch/cancel/mutate methods.
14
+ *
15
+ * class MyComponent extends StatefulComponent {
16
+ * @Resource(() => api.getUsers())
17
+ * users!: ResourceInstance<User[]>;
18
+ * // access: this.users.data(), this.users.pending(), this.users.refetch()
19
+ * }
20
+ */
21
+ export function Resource<T>(fetcher: () => Promise<T>, options: ResourceOptions<T> = {}) {
22
+ return createFieldDecorator({
23
+ bind(_instance, _name, _initialValue): FieldBinding {
24
+ const r = resource(fetcher, options);
25
+ return {
26
+ descriptor: {
27
+ get(): ResourceInstance<T> { return r; },
28
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
29
+ set(): void {},
30
+ },
31
+ };
32
+ },
33
+ });
34
+ }
@@ -1,4 +1,4 @@
1
- import type { StatefulComponent } from "@praxisjs/core";
1
+ import { createFieldDecorator } from "../create-field-decorator";
2
2
 
3
3
  const slotsMap = new WeakMap<object, Map<string, unknown[]>>();
4
4
 
@@ -14,7 +14,6 @@ function resolveSlots(children: unknown): Map<string, unknown[]> {
14
14
  : [children];
15
15
 
16
16
  for (const child of arr) {
17
- // Named slots via `slot` attribute on DOM elements
18
17
  if (child instanceof Element) {
19
18
  const slotName = child.getAttribute("slot");
20
19
  if (slotName) {
@@ -39,33 +38,21 @@ export function getSlot(instance: object, name: string): unknown[] {
39
38
  }
40
39
 
41
40
  export function Slot(name?: string) {
42
- return function (
43
- _value: undefined,
44
- context: ClassFieldDecoratorContext<StatefulComponent>,
45
- ): void {
46
- context.addInitializer(function (this: unknown) {
47
- const instance = this as StatefulComponent;
48
- const propKey = context.name as string;
49
- const slotName = name ?? (propKey === "default" ? "default" : propKey);
50
-
51
- Reflect.deleteProperty(instance, propKey);
52
-
53
- Object.defineProperty(instance, propKey, {
54
- get(this: StatefulComponent): unknown[] {
55
- return getSlot(this, slotName);
41
+ return createFieldDecorator({
42
+ bind(instance, propName, _initialValue) {
43
+ const slotName = name ?? (propName === "default" ? "default" : propName);
44
+ return {
45
+ descriptor: {
46
+ get: () => getSlot(instance, slotName),
47
+ set: (_value: unknown) => {
48
+ if (process.env.NODE_ENV !== "production") {
49
+ console.warn(
50
+ `[Slot] "${propName}" is a slot and cannot be assigned directly. Slots are filled by the parent component.`,
51
+ );
52
+ }
53
+ },
56
54
  },
57
-
58
- set(_value: unknown): void {
59
- if (process.env.NODE_ENV !== "production") {
60
- console.warn(
61
- `[Slot] "${propKey}" is a slot and cannot be assigned directly. Slots are filled by the parent component.`,
62
- );
63
- }
64
- },
65
-
66
- enumerable: true,
67
- configurable: true,
68
- });
69
- });
70
- };
55
+ };
56
+ },
57
+ });
71
58
  }