canvasengine 2.0.0-rc.2 → 2.0.0-rc.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasengine",
3
- "version": "2.0.0-rc.2",
3
+ "version": "2.0.0-rc.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,4 @@
1
- import { Container as PixiContainer } from "pixi.js";
1
+ import { Container as PixiContainer, type ContainerChild } from "pixi.js";
2
2
  import { createComponent, registerComponent } from "../engine/reactive";
3
3
  import { DisplayObject } from "./DisplayObject";
4
4
  import { ComponentFunction } from "../engine/signal";
@@ -6,8 +6,16 @@ import { DisplayObjectProps } from "./types/DisplayObject";
6
6
  import { setObservablePoint } from "../engine/utils";
7
7
  import { isPercent } from "../utils/functions";
8
8
 
9
- interface ContainerProps extends DisplayObjectProps {
9
+ export interface ContainerProps extends DisplayObjectProps {
10
10
  sortableChildren?: boolean;
11
+ /**
12
+ * Native PixiJS display objects to add to this container when it mounts.
13
+ *
14
+ * This is an escape hatch for rendering PixiJS objects directly inside the
15
+ * CanvasEngine scene graph. CanvasEngine does not manage these children's
16
+ * props or lifecycle; destroy or update them manually when needed.
17
+ */
18
+ pixiChildren?: ContainerChild[];
11
19
  }
12
20
 
13
21
  export class CanvasContainer extends DisplayObject(PixiContainer) {
@@ -343,6 +343,14 @@ export class CanvasDOMContainer extends DisplayObject(PixiDOMContainer) {
343
343
  }
344
344
 
345
345
  async onMount(element: Element<any>, index?: number) {
346
+ const parentTag = element.parent?.tag;
347
+ if (parentTag === "DOMContainer" || parentTag === "DOMElement") {
348
+ this.onUpdate(element.props);
349
+ this.syncCanvasSizeEffect();
350
+ this.applyElementSize();
351
+ return;
352
+ }
353
+
346
354
  await super.onMount(element, index);
347
355
  this.syncCanvasSizeEffect();
348
356
  this.applyElementSize();
@@ -88,6 +88,9 @@ class CanvasGraphics extends DisplayObject(PixiGraphics) {
88
88
  */
89
89
  async onMount(element: Element<any>, index?: number): Promise<void> {
90
90
  await super.onMount(element, index);
91
+ if (this.destroyed || !this.parent) {
92
+ return;
93
+ }
91
94
  const { props, propObservables } = element;
92
95
 
93
96
  // Use original signals from propObservables if available, otherwise create new ones
@@ -111,6 +114,9 @@ class CanvasGraphics extends DisplayObject(PixiGraphics) {
111
114
  if (typeof w == 'string' || typeof h == 'string') {
112
115
  return
113
116
  }
117
+ if (this.destroyed || !this.parent) {
118
+ return
119
+ }
114
120
  this.clear();
115
121
  props.draw?.(this, w, h, a);
116
122
  this.subjectInit.next(this)
@@ -1,9 +1,8 @@
1
- import { Effect, effect } from "@signe/reactive";
1
+ import { isSignal } from "@signe/reactive";
2
2
  import { Mesh as PixiMesh, Geometry, Shader, Texture, Assets, BLEND_MODES } from "pixi.js";
3
3
  import { createComponent, Element, registerComponent } from "../engine/reactive";
4
4
  import { ComponentInstance, DisplayObject } from "./DisplayObject";
5
5
  import { DisplayObjectProps } from "./types/DisplayObject";
6
- import { useProps } from "../hooks/useProps";
7
6
  import { SignalOrPrimitive } from "./types";
8
7
  import { ComponentFunction } from "../engine/signal";
9
8
 
@@ -13,19 +12,31 @@ import { ComponentFunction } from "../engine/signal";
13
12
  */
14
13
  interface MeshProps extends DisplayObjectProps {
15
14
  /** The geometry defining the mesh structure (vertices, indices, UVs, etc.) */
16
- geometry?: Geometry;
15
+ geometry?: SignalOrPrimitive<Geometry>;
17
16
  /** The shader to render the mesh with */
18
- shader?: Shader;
17
+ shader?: SignalOrPrimitive<Shader>;
19
18
  /** The texture to apply to the mesh */
20
- texture?: Texture | string;
19
+ texture?: SignalOrPrimitive<Texture | string>;
21
20
  /** The image URL to load as texture */
22
- image?: string;
21
+ image?: SignalOrPrimitive<string>;
23
22
  /** The tint color to apply to the mesh */
24
23
  tint?: SignalOrPrimitive<number>;
25
24
  /** Whether to round pixels for sharper rendering */
26
25
  roundPixels?: SignalOrPrimitive<boolean>;
27
26
  }
28
27
 
28
+ const resolveProp = <T>(value: SignalOrPrimitive<T> | undefined): T | undefined => {
29
+ return isSignal(value as any) ? (value as any)() : value as T | undefined;
30
+ };
31
+
32
+ const isValidGeometry = (value: Geometry | undefined): value is Geometry => {
33
+ if (!value) return false;
34
+ if (value instanceof Geometry) return true;
35
+
36
+ const geometry = value as any;
37
+ return typeof geometry.on === 'function' && typeof geometry.off === 'function';
38
+ };
39
+
29
40
  /**
30
41
  * Canvas Mesh component class that extends DisplayObject with PixiMesh functionality.
31
42
  * This component allows rendering of custom 3D meshes with shaders and textures.
@@ -87,17 +98,19 @@ class CanvasMesh extends DisplayObject(PixiMesh) {
87
98
  super.onInit(props);
88
99
 
89
100
  // Set initial geometry if provided
90
- if (props.geometry) {
101
+ const geometry = resolveProp(props.geometry);
102
+ if (isValidGeometry(geometry)) {
91
103
  try {
92
- this.geometry = props.geometry;
104
+ this.geometry = geometry;
93
105
  } catch (error) {
94
106
  console.warn('Failed to set geometry:', error);
95
107
  }
96
108
  }
97
109
 
98
110
  // Set initial shader if provided
99
- if (props.shader) {
100
- this.shader = props.shader;
111
+ const shader = resolveProp(props.shader);
112
+ if (shader) {
113
+ this.shader = shader;
101
114
  }
102
115
  }
103
116
 
@@ -119,28 +132,32 @@ class CanvasMesh extends DisplayObject(PixiMesh) {
119
132
  super.onUpdate(props);
120
133
 
121
134
  // Handle geometry updates
122
- if (props.geometry) {
135
+ const geometry = resolveProp(props.geometry);
136
+ if (isValidGeometry(geometry)) {
123
137
  try {
124
- this.geometry = props.geometry;
138
+ this.geometry = geometry;
125
139
  } catch (error) {
126
140
  console.warn('Failed to update geometry:', error);
127
141
  }
128
142
  }
129
143
 
130
144
  // Handle shader/material updates
131
- if (props.shader) {
132
- this.shader = props.shader;
145
+ const shader = resolveProp(props.shader);
146
+ if (shader) {
147
+ this.shader = shader;
133
148
  }
134
149
 
135
150
  // Handle texture updates
136
- if (props.texture) {
137
- if (typeof props.texture === 'string') {
138
- this.texture = await Assets.load(props.texture);
151
+ const texture = resolveProp(props.texture);
152
+ const image = resolveProp(props.image);
153
+ if (texture) {
154
+ if (typeof texture === 'string') {
155
+ this.texture = await Assets.load(texture);
139
156
  } else {
140
- this.texture = props.texture;
157
+ this.texture = texture;
141
158
  }
142
- } else if (props.image) {
143
- this.texture = await Assets.load(props.image);
159
+ } else if (image) {
160
+ this.texture = await Assets.load(image);
144
161
  }
145
162
 
146
163
  // Handle tint updates
@@ -219,4 +236,4 @@ export const Mesh: ComponentFunction<MeshProps> = (props) => {
219
236
  export { CanvasMesh };
220
237
 
221
238
  // Export the props interface for TypeScript users
222
- export type { MeshProps };
239
+ export type { MeshProps };
@@ -181,10 +181,10 @@ export class CanvasViewport extends DisplayObject(Container) {
181
181
 
182
182
  private updateMask() {
183
183
  if (!this.#mask) return
184
- this.#mask.clear()
185
- this.#mask.beginFill(0xffffff)
186
- this.#mask.drawRect(0, 0, this.viewport.screenWidth, this.viewport.screenHeight)
187
- this.#mask.endFill()
184
+ this.#mask
185
+ .clear()
186
+ .rect(0, 0, this.viewport.screenWidth, this.viewport.screenHeight)
187
+ .fill(0xffffff)
188
188
  }
189
189
 
190
190
  /**
@@ -1,5 +1,5 @@
1
1
  export { Canvas } from './Canvas'
2
- export { Container } from './Container'
2
+ export { Container, type ContainerProps } from './Container'
3
3
  export { Graphics, Rect, Circle, Ellipse, Triangle, Svg } from './Graphic'
4
4
  export { Mesh } from './Mesh'
5
5
  export { Scene } from './Scene'
@@ -65,6 +65,9 @@ export interface LoopOptions<T> {
65
65
  }
66
66
 
67
67
  const components: { [key: string]: any } = {};
68
+ const HOT_COMPONENT_PROPS = "__canvasEngineHotProps";
69
+ const HOT_COMPONENT_UPDATE_PROPS = "__canvasEngineUpdateHotProps";
70
+ const DEFINE_PROPS_SIGNALS = "__canvasEngineDefinePropsSignals";
68
71
 
69
72
  export const isElement = (value: any): value is Element => {
70
73
  return (
@@ -114,6 +117,24 @@ const DOM_UNSUPPORTED_TAGS = new Set([
114
117
  "FocusContainer",
115
118
  ]);
116
119
 
120
+ const readSignalValue = (value: any) => isSignal(value) ? value() : value;
121
+
122
+ const patchDefinePropsSignals = (target: Element, source: Element) => {
123
+ const targetSignals = (target as any)[DEFINE_PROPS_SIGNALS];
124
+ const sourceSignals = (source as any)[DEFINE_PROPS_SIGNALS];
125
+
126
+ if (!targetSignals || !sourceSignals) {
127
+ return;
128
+ }
129
+
130
+ Object.entries(sourceSignals as Record<string, any>).forEach(([key, sourceSignal]) => {
131
+ const targetSignal = targetSignals[key];
132
+ if (targetSignal && typeof targetSignal.set === "function") {
133
+ targetSignal.set(readSignalValue(sourceSignal));
134
+ }
135
+ });
136
+ };
137
+
117
138
  const hasDomAncestor = (element: Element | null): boolean => {
118
139
  let current = element;
119
140
  while (current) {
@@ -700,6 +721,78 @@ export function createComponent(tag: string, props?: Props): Element {
700
721
  return getNextGroupIndex();
701
722
  };
702
723
 
724
+ const collectMountedInstances = (
725
+ element: Element,
726
+ instances: any[],
727
+ childIndex: Map<any, number>,
728
+ seen = new Set<Element>()
729
+ ) => {
730
+ if (!element || seen.has(element)) return;
731
+ seen.add(element);
732
+
733
+ const instance = element.componentInstance as any;
734
+ if (childIndex.has(instance)) {
735
+ instances.push(instance);
736
+ return;
737
+ }
738
+
739
+ const nestedGroups = ((element as any).__childGroups ?? [])
740
+ .slice()
741
+ .sort((a, b) => a.order - b.order);
742
+ for (const group of nestedGroups) {
743
+ for (const mounted of group.mounted.values()) {
744
+ collectMountedInstances(mounted, instances, childIndex, seen);
745
+ }
746
+ }
747
+ };
748
+
749
+ const reorderMountedChildGroups = () => {
750
+ if (childGroups.length < 2) return;
751
+
752
+ const parentInstance = parent.componentInstance as any;
753
+ const children = parentInstance?.children;
754
+ if (!children || typeof parentInstance.addChildAt !== "function") return;
755
+
756
+ const childIndex = new Map<any, number>();
757
+ children.forEach((child, index) => {
758
+ childIndex.set(child, index);
759
+ });
760
+
761
+ const orderedInstances: any[] = [];
762
+ const orderedGroups = childGroups
763
+ .slice()
764
+ .sort((a, b) => a.order - b.order);
765
+
766
+ for (const group of orderedGroups) {
767
+ for (const mounted of group.mounted.values()) {
768
+ collectMountedInstances(mounted, orderedInstances, childIndex);
769
+ }
770
+ }
771
+
772
+ const mountedIndices = orderedInstances
773
+ .map((instance) => childIndex.get(instance))
774
+ .filter((index): index is number => index !== undefined);
775
+ if (!mountedIndices.length) return;
776
+
777
+ let targetIndex = Math.min(...mountedIndices);
778
+ for (const instance of orderedInstances) {
779
+ if (children[targetIndex] !== instance) {
780
+ parentInstance.addChildAt(instance, targetIndex);
781
+ }
782
+ targetIndex++;
783
+ }
784
+ };
785
+
786
+ const mountElementAtDeclaredOrder = (
787
+ element: Element,
788
+ sourceIndex: number,
789
+ orderedSources: any[]
790
+ ) => {
791
+ const mountResult = onMount(parent, element, getInsertIndex(sourceIndex, orderedSources));
792
+ void Promise.resolve(mountResult).then(reorderMountedChildGroups);
793
+ return mountResult;
794
+ };
795
+
703
796
  if (child instanceof Observable) {
704
797
  const mountedFlowElements = childGroup.mounted;
705
798
  const flowEffectSubscriptions = ((child as any).effectSubscriptions ?? []) as Subscription[];
@@ -759,7 +852,7 @@ export function createComponent(tag: string, props?: Props): Element {
759
852
  const routed = routeDomComponent(parent, element);
760
853
  applyFlowEffects(routed);
761
854
  mountedFlowElements.set(element, routed);
762
- onMount(parent, routed, getInsertIndex(sourceIndex, orderedSources));
855
+ mountElementAtDeclaredOrder(routed, sourceIndex, orderedSources);
763
856
  propagateContext(routed);
764
857
  };
765
858
 
@@ -840,7 +933,7 @@ export function createComponent(tag: string, props?: Props): Element {
840
933
  const routed = routeDomComponent(parent, value);
841
934
  applyFlowEffects(routed);
842
935
  childGroup.mounted.set(value, routed);
843
- onMount(parent, routed, getInsertIndex(0, [value]));
936
+ mountElementAtDeclaredOrder(routed, 0, [value]);
844
937
  propagateContext(routed);
845
938
  } else if (Array.isArray(value)) {
846
939
  // Handle array of elements (which can also be observables)
@@ -867,7 +960,7 @@ export function createComponent(tag: string, props?: Props): Element {
867
960
  } else if (isElement(child)) {
868
961
  const routed = routeDomComponent(parent, child);
869
962
  childGroup.mounted.set(child, routed);
870
- onMount(parent, routed, getInsertIndex(0, [child]));
963
+ mountElementAtDeclaredOrder(routed, 0, [child]);
871
964
  await propagateContext(routed);
872
965
  }
873
966
  }
@@ -933,14 +1026,36 @@ export function loop<T>(
933
1026
  element.effectUnmounts?.forEach((fn) => fn?.());
934
1027
  };
935
1028
 
1029
+ const updateTrackedHotChildren = (targetChildren: any, sourceChildren: any) => {
1030
+ const targetList = Array.isArray(targetChildren) ? targetChildren : [targetChildren];
1031
+ const sourceList = Array.isArray(sourceChildren) ? sourceChildren : [sourceChildren];
1032
+ let updated = false;
1033
+
1034
+ targetList.forEach((targetChild, index) => {
1035
+ const sourceChild = sourceList[index];
1036
+ const updateProps = targetChild?.[HOT_COMPONENT_UPDATE_PROPS];
1037
+ const nextProps = sourceChild?.[HOT_COMPONENT_PROPS];
1038
+
1039
+ if (typeof updateProps === "function" && nextProps !== undefined) {
1040
+ updateProps(nextProps);
1041
+ updated = true;
1042
+ }
1043
+ });
1044
+
1045
+ return updated;
1046
+ };
1047
+
936
1048
  const patchTrackedElement = (target: Element, source: Element) => {
937
1049
  const nextProps = { ...source.props };
938
1050
  const nextPropObservables = source.propObservables;
1051
+ const updatedHotChildren = updateTrackedHotChildren(target.props.children, source.props.children);
1052
+
1053
+ patchDefinePropsSignals(target, source);
939
1054
 
940
1055
  if (target.props.context) {
941
1056
  nextProps.context = target.props.context;
942
1057
  }
943
- if (target.props.children && !source.props.children) {
1058
+ if (updatedHotChildren || (target.props.children && !source.props.children)) {
944
1059
  nextProps.children = target.props.children;
945
1060
  }
946
1061
 
@@ -22,9 +22,36 @@ type HotComponentRecord = {
22
22
  wrapper?: ComponentFunction<any>;
23
23
  };
24
24
 
25
+ const HOT_COMPONENT_PROPS = "__canvasEngineHotProps";
26
+ const HOT_COMPONENT_UPDATE_PROPS = "__canvasEngineUpdateHotProps";
27
+ const DEFINE_PROPS_SIGNALS = "__canvasEngineDefinePropsSignals";
28
+
25
29
  export let currentSubscriptionsTracker: ((subscription: Subscription) => void) | null = null;
30
+ export let currentDefinePropsTracker: ((signals: Record<string, any>) => void) | null = null;
26
31
  export let mountTracker: MountFunction | null = null;
27
32
 
33
+ const readSignalValue = (value: any) => {
34
+ const callableValueSignal = value?.__canvasEngineCallableSignalValue;
35
+ if (callableValueSignal) return callableValueSignal();
36
+ return isSignal(value) ? value() : value;
37
+ };
38
+
39
+ const patchDefinePropsSignals = (target: Element, source: Element) => {
40
+ const targetSignals = (target as any)[DEFINE_PROPS_SIGNALS];
41
+ const sourceSignals = (source as any)[DEFINE_PROPS_SIGNALS];
42
+
43
+ if (!targetSignals || !sourceSignals) {
44
+ return;
45
+ }
46
+
47
+ Object.entries(sourceSignals as Record<string, any>).forEach(([key, sourceSignal]) => {
48
+ const targetSignal = targetSignals[key];
49
+ if (targetSignal && typeof targetSignal.set === "function") {
50
+ targetSignal.set(readSignalValue(sourceSignal));
51
+ }
52
+ });
53
+ };
54
+
28
55
  const getHotComponentRegistry = (): Map<string, HotComponentRecord> => {
29
56
  const hotGlobal = globalThis as any;
30
57
  if (!hotGlobal.__CANVAS_ENGINE_HOT_COMPONENTS__) {
@@ -163,11 +190,19 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
163
190
  ): ReturnType<C> {
164
191
  const allSubscriptions = new Set<Subscription>();
165
192
  const allMounts = new Set<MountCallback>();
193
+ let allDefinePropSignals: Record<string, any> | null = null;
166
194
 
167
195
  currentSubscriptionsTracker = (subscription) => {
168
196
  allSubscriptions.add(subscription);
169
197
  };
170
198
 
199
+ currentDefinePropsTracker = (signals) => {
200
+ allDefinePropSignals = {
201
+ ...(allDefinePropSignals ?? {}),
202
+ ...signals,
203
+ };
204
+ };
205
+
171
206
  mountTracker = (fn: any) => {
172
207
  allMounts.add(fn);
173
208
  };
@@ -177,6 +212,7 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
177
212
  component = componentFunction(props) as ReturnType<C>;
178
213
  } finally {
179
214
  currentSubscriptionsTracker = null;
215
+ currentDefinePropsTracker = null;
180
216
  mountTracker = null;
181
217
  }
182
218
 
@@ -190,6 +226,9 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
190
226
  ...Array.from(allMounts),
191
227
  ...((element as any).effectMounts ?? [])
192
228
  ];
229
+ if (allDefinePropSignals) {
230
+ (element as any)[DEFINE_PROPS_SIGNALS] = allDefinePropSignals;
231
+ }
193
232
  };
194
233
 
195
234
  if (component instanceof Promise) {
@@ -208,6 +247,9 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
208
247
  ...Array.from(allMounts),
209
248
  ...((component as any).effectMounts ?? [])
210
249
  ];
250
+ if (allDefinePropSignals) {
251
+ (component as any)[DEFINE_PROPS_SIGNALS] = allDefinePropSignals;
252
+ }
211
253
  } else {
212
254
  applyTrackedEffects(component as Element);
213
255
  }
@@ -235,14 +277,57 @@ export function createHotComponent<P>(
235
277
 
236
278
  if (!record.wrapper) {
237
279
  record.wrapper = ((props: P) => {
238
- return new Observable<HotFlowResult>((subscriber) => {
280
+ let currentProps = props;
281
+
282
+ const observable = new Observable<HotFlowResult>((subscriber) => {
239
283
  let disposed = false;
240
284
  let currentElement: Element | null = null;
241
285
 
242
- const emit = () => {
243
- const rendered = createTrackedComponent(record!.component, props);
286
+ const patchElement = (target: Element, source: Element) => {
287
+ if (target.tag !== source.tag) {
288
+ return false;
289
+ }
290
+
291
+ const nextProps = { ...source.props };
292
+ if (target.props.context) {
293
+ nextProps.context = target.props.context;
294
+ }
295
+ if (target.props.children && !source.props.children) {
296
+ nextProps.children = target.props.children;
297
+ }
298
+
299
+ patchDefinePropsSignals(target, source);
300
+
301
+ target.props = nextProps;
302
+ target.propObservables = source.propObservables;
303
+ target.componentInstance.onUpdate?.(nextProps);
304
+ Object.entries(target.directives).forEach(([name, directive]) => {
305
+ if (name in nextProps) {
306
+ directive.onUpdate?.(nextProps[name], target);
307
+ }
308
+ });
309
+
310
+ source.propSubscriptions?.forEach((sub) => sub.unsubscribe());
311
+ source.effectSubscriptions?.forEach((sub) => sub.unsubscribe());
312
+ source.effectUnmounts?.forEach((fn) => fn?.());
313
+
314
+ return true;
315
+ };
316
+
317
+ const emit = (preserveCurrentElement = false) => {
318
+ const rendered = createTrackedComponent(record!.component, currentProps);
244
319
  const next = (element: Element | null | undefined) => {
245
320
  if (!disposed) {
321
+ if (
322
+ preserveCurrentElement &&
323
+ currentElement &&
324
+ element &&
325
+ patchElement(currentElement, element)
326
+ ) {
327
+ subscriber.next({ elements: [currentElement] });
328
+ return;
329
+ }
330
+
246
331
  subscriber.next({ elements: element ? [element] : [] });
247
332
  if (currentElement && currentElement !== element) {
248
333
  destroyElement(currentElement);
@@ -259,7 +344,12 @@ export function createHotComponent<P>(
259
344
  };
260
345
 
261
346
  emit();
262
- const subscription = record!.updates.subscribe(emit);
347
+ (observable as any)[HOT_COMPONENT_UPDATE_PROPS] = (nextProps: P) => {
348
+ currentProps = nextProps;
349
+ emit(true);
350
+ };
351
+
352
+ const subscription = record!.updates.subscribe(() => emit());
263
353
 
264
354
  return () => {
265
355
  disposed = true;
@@ -270,6 +360,10 @@ export function createHotComponent<P>(
270
360
  subscription.unsubscribe();
271
361
  };
272
362
  }) as any;
363
+
364
+ (observable as any)[HOT_COMPONENT_PROPS] = props;
365
+
366
+ return observable;
273
367
  }) as ComponentFunction<any>;
274
368
  }
275
369
 
@@ -1,5 +1,7 @@
1
1
  import { isSignal, signal } from "@signe/reactive"
2
2
  import { isPrimitive } from "../engine/reactive"
3
+ import { currentDefinePropsTracker } from "../engine/signal"
4
+ import { isTrigger } from "../engine/trigger"
3
5
 
4
6
  /**
5
7
  * Converts props into reactive signals if they are primitive values.
@@ -46,7 +48,36 @@ type PropSchema = {
46
48
  [key: string]: PropType | PropType[] | PropConfig;
47
49
  }
48
50
 
49
- const toPropSignal = (value: any) => isSignal(value) ? value : signal(value)
51
+ const DEFINE_PROPS_CALLABLE_SIGNAL_VALUE = "__canvasEngineCallableSignalValue"
52
+
53
+ const createCallablePropSignal = (value: (...args: any[]) => any) => {
54
+ const valueSignal: any = signal(value)
55
+ const callable: any = (...args: any[]) => valueSignal()(...args)
56
+
57
+ callable[DEFINE_PROPS_CALLABLE_SIGNAL_VALUE] = valueSignal
58
+ callable.set = valueSignal.set
59
+ callable.freeze = valueSignal.freeze
60
+ callable.unfreeze = valueSignal.unfreeze
61
+ callable.mutate = valueSignal.mutate
62
+ callable.update = valueSignal.update
63
+ callable.observable = valueSignal.observable
64
+ callable._subject = valueSignal._subject
65
+
66
+ Object.defineProperty(callable, "_isFrozen", {
67
+ get: () => valueSignal._isFrozen,
68
+ set: (value) => {
69
+ valueSignal._isFrozen = value
70
+ }
71
+ })
72
+
73
+ return callable
74
+ }
75
+
76
+ const toPropSignal = (value: any) => {
77
+ if (isSignal(value) || isTrigger(value)) return value
78
+ if (typeof value === 'function') return createCallablePropSignal(value)
79
+ return signal(value)
80
+ }
50
81
 
51
82
  const definePropSignals = (props: any): any => {
52
83
  const obj: any = {}
@@ -122,10 +153,13 @@ export const useDefineProps = (props: any) => {
122
153
  validatedProps[key] = toPropSignal(validatedValue)
123
154
  }
124
155
 
125
- return {
156
+ const definedProps = {
126
157
  ...definePropSignals(rawProps),
127
158
  ...validatedProps
128
159
  }
160
+ currentDefinePropsTracker?.(definedProps)
161
+
162
+ return definedProps
129
163
  }
130
164
  }
131
165