canvasengine 2.0.0-rc.2 → 2.0.0-rc.3
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/dist/components/Container.d.ts +10 -1
- package/dist/components/Container.d.ts.map +1 -1
- package/dist/components/DOMContainer.d.ts.map +1 -1
- package/dist/components/Graphic.d.ts.map +1 -1
- package/dist/components/Mesh.d.ts +4 -4
- package/dist/components/Mesh.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts +1 -0
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/hooks/useProps.d.ts.map +1 -1
- package/dist/index.global.js +10 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +6824 -6738
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Container.ts +10 -2
- package/src/components/DOMContainer.ts +8 -0
- package/src/components/Graphic.ts +6 -0
- package/src/components/Mesh.ts +38 -21
- package/src/components/Viewport.ts +4 -4
- package/src/components/index.ts +1 -1
- package/src/engine/reactive.ts +119 -4
- package/src/engine/signal.ts +74 -4
- package/src/hooks/useProps.ts +5 -1
package/package.json
CHANGED
|
@@ -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)
|
package/src/components/Mesh.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
101
|
+
const geometry = resolveProp(props.geometry);
|
|
102
|
+
if (isValidGeometry(geometry)) {
|
|
91
103
|
try {
|
|
92
|
-
this.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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
135
|
+
const geometry = resolveProp(props.geometry);
|
|
136
|
+
if (isValidGeometry(geometry)) {
|
|
123
137
|
try {
|
|
124
|
-
this.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
|
-
|
|
132
|
-
|
|
145
|
+
const shader = resolveProp(props.shader);
|
|
146
|
+
if (shader) {
|
|
147
|
+
this.shader = shader;
|
|
133
148
|
}
|
|
134
149
|
|
|
135
150
|
// Handle texture updates
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 =
|
|
157
|
+
this.texture = texture;
|
|
141
158
|
}
|
|
142
|
-
} else if (
|
|
143
|
-
this.texture = await Assets.load(
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
this.#mask
|
|
185
|
+
.clear()
|
|
186
|
+
.rect(0, 0, this.viewport.screenWidth, this.viewport.screenHeight)
|
|
187
|
+
.fill(0xffffff)
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/**
|
package/src/components/index.ts
CHANGED
|
@@ -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'
|
package/src/engine/reactive.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/engine/signal.ts
CHANGED
|
@@ -22,7 +22,12 @@ 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
|
|
|
28
33
|
const getHotComponentRegistry = (): Map<string, HotComponentRecord> => {
|
|
@@ -163,11 +168,19 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
|
|
|
163
168
|
): ReturnType<C> {
|
|
164
169
|
const allSubscriptions = new Set<Subscription>();
|
|
165
170
|
const allMounts = new Set<MountCallback>();
|
|
171
|
+
let allDefinePropSignals: Record<string, any> | null = null;
|
|
166
172
|
|
|
167
173
|
currentSubscriptionsTracker = (subscription) => {
|
|
168
174
|
allSubscriptions.add(subscription);
|
|
169
175
|
};
|
|
170
176
|
|
|
177
|
+
currentDefinePropsTracker = (signals) => {
|
|
178
|
+
allDefinePropSignals = {
|
|
179
|
+
...(allDefinePropSignals ?? {}),
|
|
180
|
+
...signals,
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
171
184
|
mountTracker = (fn: any) => {
|
|
172
185
|
allMounts.add(fn);
|
|
173
186
|
};
|
|
@@ -177,6 +190,7 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
|
|
|
177
190
|
component = componentFunction(props) as ReturnType<C>;
|
|
178
191
|
} finally {
|
|
179
192
|
currentSubscriptionsTracker = null;
|
|
193
|
+
currentDefinePropsTracker = null;
|
|
180
194
|
mountTracker = null;
|
|
181
195
|
}
|
|
182
196
|
|
|
@@ -190,6 +204,9 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
|
|
|
190
204
|
...Array.from(allMounts),
|
|
191
205
|
...((element as any).effectMounts ?? [])
|
|
192
206
|
];
|
|
207
|
+
if (allDefinePropSignals) {
|
|
208
|
+
(element as any)[DEFINE_PROPS_SIGNALS] = allDefinePropSignals;
|
|
209
|
+
}
|
|
193
210
|
};
|
|
194
211
|
|
|
195
212
|
if (component instanceof Promise) {
|
|
@@ -208,6 +225,9 @@ function createTrackedComponent<C extends ComponentFunction<any>>(
|
|
|
208
225
|
...Array.from(allMounts),
|
|
209
226
|
...((component as any).effectMounts ?? [])
|
|
210
227
|
];
|
|
228
|
+
if (allDefinePropSignals) {
|
|
229
|
+
(component as any)[DEFINE_PROPS_SIGNALS] = allDefinePropSignals;
|
|
230
|
+
}
|
|
211
231
|
} else {
|
|
212
232
|
applyTrackedEffects(component as Element);
|
|
213
233
|
}
|
|
@@ -235,14 +255,55 @@ export function createHotComponent<P>(
|
|
|
235
255
|
|
|
236
256
|
if (!record.wrapper) {
|
|
237
257
|
record.wrapper = ((props: P) => {
|
|
238
|
-
|
|
258
|
+
let currentProps = props;
|
|
259
|
+
|
|
260
|
+
const observable = new Observable<HotFlowResult>((subscriber) => {
|
|
239
261
|
let disposed = false;
|
|
240
262
|
let currentElement: Element | null = null;
|
|
241
263
|
|
|
242
|
-
const
|
|
243
|
-
|
|
264
|
+
const patchElement = (target: Element, source: Element) => {
|
|
265
|
+
if (target.tag !== source.tag) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const nextProps = { ...source.props };
|
|
270
|
+
if (target.props.context) {
|
|
271
|
+
nextProps.context = target.props.context;
|
|
272
|
+
}
|
|
273
|
+
if (target.props.children && !source.props.children) {
|
|
274
|
+
nextProps.children = target.props.children;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
target.props = nextProps;
|
|
278
|
+
target.propObservables = source.propObservables;
|
|
279
|
+
target.componentInstance.onUpdate?.(nextProps);
|
|
280
|
+
Object.entries(target.directives).forEach(([name, directive]) => {
|
|
281
|
+
if (name in nextProps) {
|
|
282
|
+
directive.onUpdate?.(nextProps[name], target);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
source.propSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
287
|
+
source.effectSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
288
|
+
source.effectUnmounts?.forEach((fn) => fn?.());
|
|
289
|
+
|
|
290
|
+
return true;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const emit = (preserveCurrentElement = false) => {
|
|
294
|
+
const rendered = createTrackedComponent(record!.component, currentProps);
|
|
244
295
|
const next = (element: Element | null | undefined) => {
|
|
245
296
|
if (!disposed) {
|
|
297
|
+
if (
|
|
298
|
+
preserveCurrentElement &&
|
|
299
|
+
currentElement &&
|
|
300
|
+
element &&
|
|
301
|
+
patchElement(currentElement, element)
|
|
302
|
+
) {
|
|
303
|
+
subscriber.next({ elements: [currentElement] });
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
246
307
|
subscriber.next({ elements: element ? [element] : [] });
|
|
247
308
|
if (currentElement && currentElement !== element) {
|
|
248
309
|
destroyElement(currentElement);
|
|
@@ -259,7 +320,12 @@ export function createHotComponent<P>(
|
|
|
259
320
|
};
|
|
260
321
|
|
|
261
322
|
emit();
|
|
262
|
-
|
|
323
|
+
(observable as any)[HOT_COMPONENT_UPDATE_PROPS] = (nextProps: P) => {
|
|
324
|
+
currentProps = nextProps;
|
|
325
|
+
emit(true);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const subscription = record!.updates.subscribe(() => emit());
|
|
263
329
|
|
|
264
330
|
return () => {
|
|
265
331
|
disposed = true;
|
|
@@ -270,6 +336,10 @@ export function createHotComponent<P>(
|
|
|
270
336
|
subscription.unsubscribe();
|
|
271
337
|
};
|
|
272
338
|
}) as any;
|
|
339
|
+
|
|
340
|
+
(observable as any)[HOT_COMPONENT_PROPS] = props;
|
|
341
|
+
|
|
342
|
+
return observable;
|
|
273
343
|
}) as ComponentFunction<any>;
|
|
274
344
|
}
|
|
275
345
|
|
package/src/hooks/useProps.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isSignal, signal } from "@signe/reactive"
|
|
2
2
|
import { isPrimitive } from "../engine/reactive"
|
|
3
|
+
import { currentDefinePropsTracker } from "../engine/signal"
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Converts props into reactive signals if they are primitive values.
|
|
@@ -122,10 +123,13 @@ export const useDefineProps = (props: any) => {
|
|
|
122
123
|
validatedProps[key] = toPropSignal(validatedValue)
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
const definedProps = {
|
|
126
127
|
...definePropSignals(rawProps),
|
|
127
128
|
...validatedProps
|
|
128
129
|
}
|
|
130
|
+
currentDefinePropsTracker?.(definedProps)
|
|
131
|
+
|
|
132
|
+
return definedProps
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|