canvasengine 2.0.0-beta.60 → 2.0.0-beta.61
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/Canvas.d.ts.map +1 -1
- package/dist/components/Graphic.d.ts.map +1 -1
- package/dist/components/Text.d.ts +2 -3
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/directives/Scheduler.d.ts +1 -0
- package/dist/directives/Scheduler.d.ts.map +1 -1
- package/dist/engine/bootstrap.d.ts +8 -4
- package/dist/engine/bootstrap.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts +1 -0
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts +3 -1
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/index.global.js +6 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +6134 -1194
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/Canvas.ts +24 -9
- package/src/components/DisplayObject.ts +3 -0
- package/src/components/Graphic.ts +16 -11
- package/src/components/Text.ts +151 -14
- package/src/directives/Scheduler.ts +9 -1
- package/src/engine/bootstrap.ts +57 -13
- package/src/engine/reactive.ts +1 -1
- package/src/engine/signal.ts +136 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasengine",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@barvynkoa/particle-emitter": "^0.0.1",
|
|
12
|
+
"@chenglou/pretext": "^0.0.6",
|
|
12
13
|
"@pixi/layout": "^3.2.0",
|
|
13
14
|
"@signe/reactive": "^2.9.0",
|
|
14
15
|
"howler": "^2.2.4",
|
package/src/components/Canvas.ts
CHANGED
|
@@ -91,7 +91,7 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
91
91
|
|
|
92
92
|
if (props.tickStart !== false) canvasElement.directives.tick.start()
|
|
93
93
|
|
|
94
|
-
effect(() => {
|
|
94
|
+
const renderEffect = effect(() => {
|
|
95
95
|
canvasElement.propObservables!.tick();
|
|
96
96
|
renderer.render(canvasElement.componentInstance as any);
|
|
97
97
|
});
|
|
@@ -107,7 +107,7 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
107
107
|
|
|
108
108
|
canvasSize.set({ width: app.screen.width, height: app.screen.height })
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
const resizeHandler = (width: number, height: number) => {
|
|
111
111
|
canvasSize.set({ width, height });
|
|
112
112
|
|
|
113
113
|
if (app.stage.layout) {
|
|
@@ -116,33 +116,48 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
116
116
|
height
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
app.renderer.on('resize', resizeHandler);
|
|
120
122
|
|
|
121
123
|
if (props.tickStart !== false) canvasElement.directives.tick.start();
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
const tickerHandler = () => {
|
|
124
126
|
canvasElement.propObservables!.tick();
|
|
125
|
-
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
app.ticker.add(tickerHandler);
|
|
126
130
|
|
|
131
|
+
let cursorEffect: any;
|
|
127
132
|
if (cursorStyles) {
|
|
128
|
-
effect(() => {
|
|
133
|
+
cursorEffect = effect(() => {
|
|
129
134
|
renderer.events.cursorStyles = cursorStyles();
|
|
130
135
|
});
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
let classEffect: any;
|
|
133
139
|
if (className) {
|
|
134
|
-
effect(() => {
|
|
140
|
+
classEffect = effect(() => {
|
|
135
141
|
canvasEl.classList.add(className());
|
|
136
142
|
});
|
|
137
143
|
}
|
|
138
144
|
|
|
139
145
|
const existingCanvas = rootElement.querySelector("canvas");
|
|
140
|
-
if (existingCanvas) {
|
|
146
|
+
if (existingCanvas && existingCanvas !== canvasEl) {
|
|
141
147
|
rootElement.replaceChild(canvasEl, existingCanvas);
|
|
142
|
-
} else {
|
|
148
|
+
} else if (!existingCanvas) {
|
|
143
149
|
rootElement.appendChild(canvasEl);
|
|
144
150
|
}
|
|
145
151
|
|
|
152
|
+
canvasElement.effectUnmounts.push(() => {
|
|
153
|
+
renderEffect?.subscription?.unsubscribe?.();
|
|
154
|
+
cursorEffect?.subscription?.unsubscribe?.();
|
|
155
|
+
classEffect?.subscription?.unsubscribe?.();
|
|
156
|
+
app.ticker.remove(tickerHandler);
|
|
157
|
+
app.renderer.off?.('resize', resizeHandler);
|
|
158
|
+
canvasElement.directives.tick?.stop();
|
|
159
|
+
});
|
|
160
|
+
|
|
146
161
|
options.context!.app.set(app)
|
|
147
162
|
};
|
|
148
163
|
|
|
@@ -395,6 +395,9 @@ export function DisplayObject(extendClass) {
|
|
|
395
395
|
await this.onBeforeDestroy();
|
|
396
396
|
}
|
|
397
397
|
if (afterDestroy) afterDestroy();
|
|
398
|
+
if (this.parent && typeof this.parent.removeChild === "function") {
|
|
399
|
+
this.parent.removeChild(this);
|
|
400
|
+
}
|
|
398
401
|
super.destroy();
|
|
399
402
|
}
|
|
400
403
|
|
|
@@ -184,6 +184,8 @@ const graphicsAnchor = (anchor, width, height) => {
|
|
|
184
184
|
return { x: -ax * width, y: -ay * height };
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
const propValue = (value: any) => isSignal(value) ? value() : value;
|
|
188
|
+
|
|
187
189
|
export function Rect(props: RectProps) {
|
|
188
190
|
const { color, borderRadius, border } = useProps(props, {
|
|
189
191
|
borderRadius: null,
|
|
@@ -198,10 +200,11 @@ export function Rect(props: RectProps) {
|
|
|
198
200
|
} else {
|
|
199
201
|
g.rect(x, y, width, height);
|
|
200
202
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
const borderValue = propValue(border);
|
|
204
|
+
if (borderValue) {
|
|
205
|
+
g.stroke(borderValue);
|
|
203
206
|
}
|
|
204
|
-
g.fill(color
|
|
207
|
+
g.fill(propValue(color));
|
|
205
208
|
},
|
|
206
209
|
...props
|
|
207
210
|
})
|
|
@@ -216,14 +219,15 @@ export function Circle(props: CircleProps) {
|
|
|
216
219
|
draw: (g, width, height, anchor) => {
|
|
217
220
|
const { x, y } = graphicsAnchor(anchor, width, height);
|
|
218
221
|
if (width == height || height == 0) {
|
|
219
|
-
g.circle(x, y, radius
|
|
222
|
+
g.circle(x, y, propValue(radius) || width);
|
|
220
223
|
} else {
|
|
221
224
|
g.ellipse(x, y, width, height);
|
|
222
225
|
}
|
|
223
|
-
|
|
224
|
-
|
|
226
|
+
const borderValue = propValue(border);
|
|
227
|
+
if (borderValue) {
|
|
228
|
+
g.stroke(borderValue);
|
|
225
229
|
}
|
|
226
|
-
g.fill(color
|
|
230
|
+
g.fill(propValue(color));
|
|
227
231
|
},
|
|
228
232
|
...props
|
|
229
233
|
})
|
|
@@ -245,9 +249,10 @@ export function Triangle(props: TriangleProps) {
|
|
|
245
249
|
g.lineTo(x + gWidth / 2, y);
|
|
246
250
|
g.lineTo(x + gWidth, y + gHeight);
|
|
247
251
|
g.lineTo(x, y + gHeight);
|
|
248
|
-
g.fill(color
|
|
249
|
-
|
|
250
|
-
|
|
252
|
+
g.fill(propValue(color));
|
|
253
|
+
const borderValue = propValue(border);
|
|
254
|
+
if (borderValue) {
|
|
255
|
+
g.stroke(borderValue);
|
|
251
256
|
}
|
|
252
257
|
},
|
|
253
258
|
...props
|
|
@@ -305,4 +310,4 @@ export function Svg(props: SvgProps) {
|
|
|
305
310
|
},
|
|
306
311
|
...props
|
|
307
312
|
})
|
|
308
|
-
}
|
|
313
|
+
}
|
package/src/components/Text.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { layout as layoutPretext, prepare as preparePretext, type PreparedText, type PrepareOptions } from "@chenglou/pretext";
|
|
1
2
|
import { Text as PixiText, TextStyle } from "pixi.js";
|
|
2
|
-
import { createComponent, registerComponent, Element
|
|
3
|
-
import { DisplayObject
|
|
3
|
+
import { createComponent, registerComponent, Element } from "../engine/reactive";
|
|
4
|
+
import { DisplayObject } from "./DisplayObject";
|
|
4
5
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
5
6
|
import { Signal } from "@signe/reactive";
|
|
6
7
|
import { on, isTrigger } from "../engine/trigger";
|
|
@@ -14,7 +15,7 @@ export interface TextProps extends DisplayObjectProps {
|
|
|
14
15
|
text?: string;
|
|
15
16
|
style?: Partial<TextStyle>;
|
|
16
17
|
color?: string;
|
|
17
|
-
size?: string;
|
|
18
|
+
size?: string | number;
|
|
18
19
|
fontFamily?: string;
|
|
19
20
|
typewriter?: {
|
|
20
21
|
speed?: number;
|
|
@@ -30,6 +31,22 @@ export interface TextProps extends DisplayObjectProps {
|
|
|
30
31
|
context?: any; // Ensure context is available, ideally typed from a base prop or injected
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
type PretextMeasurement = {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const toFiniteNumber = (value: unknown): number | null => {
|
|
40
|
+
if (typeof value === "number") {
|
|
41
|
+
return Number.isFinite(value) ? value : null;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
const parsed = Number.parseFloat(value);
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
33
50
|
class CanvasText extends DisplayObject(PixiText) {
|
|
34
51
|
private subscriptionTick: any;
|
|
35
52
|
private fullText: string = "";
|
|
@@ -41,6 +58,9 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
41
58
|
private typewriterSound?: Howl;
|
|
42
59
|
private lastSoundTime: number = 0;
|
|
43
60
|
private soundDuration: number = 0; // Duration of the sound in milliseconds
|
|
61
|
+
private pretextPrepared: PreparedText | null = null;
|
|
62
|
+
private pretextPrepareKey: string = "";
|
|
63
|
+
private measuredLayout: PretextMeasurement | null = null;
|
|
44
64
|
|
|
45
65
|
/**
|
|
46
66
|
* Called when the component is mounted to the scene graph.
|
|
@@ -102,12 +122,7 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
102
122
|
this.updateLayout();
|
|
103
123
|
}
|
|
104
124
|
if (props.style) {
|
|
105
|
-
|
|
106
|
-
this.style[key] = props.style[key];
|
|
107
|
-
}
|
|
108
|
-
if (props.style.wordWrapWidth) {
|
|
109
|
-
this._wordWrapWidth = props.style.wordWrapWidth;
|
|
110
|
-
}
|
|
125
|
+
this.applyTextStyle(props.style);
|
|
111
126
|
}
|
|
112
127
|
if (props.color) {
|
|
113
128
|
this.style.fill = props.color;
|
|
@@ -118,6 +133,7 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
118
133
|
if (props.fontFamily) {
|
|
119
134
|
this.style.fontFamily = props.fontFamily;
|
|
120
135
|
}
|
|
136
|
+
this.updateWordWrapWidth();
|
|
121
137
|
|
|
122
138
|
// Use the centralized layout update method
|
|
123
139
|
this.updateLayout();
|
|
@@ -171,12 +187,130 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
171
187
|
* This method ensures consistent width, height and word wrap behavior.
|
|
172
188
|
*/
|
|
173
189
|
private updateLayout() {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
190
|
+
const measured = this.measurePretextLayout();
|
|
191
|
+
const width = measured?.width ?? this.width;
|
|
192
|
+
const height = measured?.height ?? this.height;
|
|
193
|
+
|
|
194
|
+
this.measuredLayout = measured ?? { width, height };
|
|
195
|
+
this.setMeasuredLayout(width, height);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private applyTextStyle(style: Partial<TextStyle>) {
|
|
199
|
+
const assign = (this.style as TextStyle & { assign?: (values: any) => TextStyle }).assign;
|
|
200
|
+
if (typeof assign === "function") {
|
|
201
|
+
assign.call(this.style, style);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const key in style) {
|
|
206
|
+
(this.style as any)[key] = (style as any)[key];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private updateWordWrapWidth() {
|
|
211
|
+
if (!this.style.wordWrap) {
|
|
212
|
+
this._wordWrapWidth = 0;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const wordWrapWidth = toFiniteNumber(this.style.wordWrapWidth);
|
|
216
|
+
this._wordWrapWidth = wordWrapWidth !== null && wordWrapWidth > 0 ? wordWrapWidth : 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private measurePretextLayout(): PretextMeasurement | null {
|
|
220
|
+
if (!this.style.wordWrap || this._wordWrapWidth <= 0) {
|
|
221
|
+
this.pretextPrepared = null;
|
|
222
|
+
this.pretextPrepareKey = "";
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const text = `${this.text ?? ""}`;
|
|
227
|
+
const font = this.resolvePretextFont();
|
|
228
|
+
const lineHeight = this.resolveLineHeight();
|
|
229
|
+
const options = this.resolvePretextOptions();
|
|
230
|
+
const prepareKey = JSON.stringify([text, font, options.whiteSpace, options.wordBreak, options.letterSpacing]);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (this.pretextPrepareKey !== prepareKey || !this.pretextPrepared) {
|
|
234
|
+
this.pretextPrepared = preparePretext(text, font, options);
|
|
235
|
+
this.pretextPrepareKey = prepareKey;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const result = layoutPretext(this.pretextPrepared, this._wordWrapWidth, lineHeight);
|
|
239
|
+
return {
|
|
240
|
+
width: this._wordWrapWidth,
|
|
241
|
+
height: result.height,
|
|
242
|
+
};
|
|
243
|
+
} catch {
|
|
244
|
+
this.pretextPrepared = null;
|
|
245
|
+
this.pretextPrepareKey = "";
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private resolvePretextFont(): string {
|
|
251
|
+
const fontString = (this.style as TextStyle & { _fontString?: string })._fontString;
|
|
252
|
+
if (fontString) return fontString;
|
|
253
|
+
|
|
254
|
+
const fontSize = this.resolveFontSize();
|
|
255
|
+
const fontFamily = Array.isArray(this.style.fontFamily)
|
|
256
|
+
? this.style.fontFamily.join(",")
|
|
257
|
+
: this.style.fontFamily;
|
|
258
|
+
|
|
259
|
+
return `${this.style.fontStyle} ${this.style.fontVariant} ${this.style.fontWeight} ${fontSize}px ${fontFamily}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private resolvePretextOptions(): PrepareOptions {
|
|
263
|
+
return {
|
|
264
|
+
whiteSpace: this.style.whiteSpace === "normal" ? "normal" : "pre-wrap",
|
|
265
|
+
letterSpacing: this.style.letterSpacing || undefined,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private resolveLineHeight(): number {
|
|
270
|
+
const lineHeight = toFiniteNumber(this.style.lineHeight);
|
|
271
|
+
if (lineHeight !== null && lineHeight > 0) return lineHeight;
|
|
272
|
+
return this.resolveFontSize();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private resolveFontSize(): number {
|
|
276
|
+
const fontSize = toFiniteNumber(this.style.fontSize);
|
|
277
|
+
return fontSize !== null && fontSize > 0 ? fontSize : 16;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private setMeasuredLayout(width: number, height: number) {
|
|
281
|
+
const layout: { width?: number; height?: number } = {};
|
|
282
|
+
|
|
283
|
+
if (this.fullProps.width === undefined) {
|
|
284
|
+
this.displayWidth.set(width);
|
|
285
|
+
if (this.parentIsFlex) {
|
|
286
|
+
layout.width = width;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (this.fullProps.height === undefined) {
|
|
291
|
+
this.displayHeight.set(height);
|
|
292
|
+
if (this.parentIsFlex) {
|
|
293
|
+
layout.height = height;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.parentIsFlex && (layout.width !== undefined || layout.height !== undefined)) {
|
|
298
|
+
(this as any).layout = layout;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getWidth(): number {
|
|
303
|
+
if (this.fullProps.width === undefined && this.measuredLayout) {
|
|
304
|
+
return this.measuredLayout.width;
|
|
305
|
+
}
|
|
306
|
+
return super.getWidth();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
getHeight(): number {
|
|
310
|
+
if (this.fullProps.height === undefined && this.measuredLayout) {
|
|
311
|
+
return this.measuredLayout.height;
|
|
178
312
|
}
|
|
179
|
-
|
|
313
|
+
return super.getHeight();
|
|
180
314
|
}
|
|
181
315
|
|
|
182
316
|
private typewriterEffect() {
|
|
@@ -235,6 +369,9 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
235
369
|
this.typewriterSound.unload();
|
|
236
370
|
this.typewriterSound = undefined;
|
|
237
371
|
}
|
|
372
|
+
this.pretextPrepared = null;
|
|
373
|
+
this.pretextPrepareKey = "";
|
|
374
|
+
this.measuredLayout = null;
|
|
238
375
|
if (afterDestroy) {
|
|
239
376
|
afterDestroy();
|
|
240
377
|
}
|
|
@@ -19,13 +19,16 @@ export class Scheduler extends Directive {
|
|
|
19
19
|
private requestedDelay: number = 0
|
|
20
20
|
private lastTimestamp: number = 0
|
|
21
21
|
private _stop: boolean = false
|
|
22
|
+
private running: boolean = false
|
|
22
23
|
private tick: WritableSignal<Tick | null>
|
|
23
24
|
|
|
24
25
|
onInit(element: Element) {
|
|
25
26
|
this.tick = element.propObservables?.tick as any
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
onDestroy() {
|
|
29
|
+
onDestroy() {
|
|
30
|
+
this.stop()
|
|
31
|
+
}
|
|
29
32
|
onMount(element: Element) { }
|
|
30
33
|
onUpdate(props: any) { }
|
|
31
34
|
|
|
@@ -59,6 +62,9 @@ export class Scheduler extends Directive {
|
|
|
59
62
|
fps?: number,
|
|
60
63
|
delay?: number
|
|
61
64
|
} = {}) {
|
|
65
|
+
if (this.running) return this
|
|
66
|
+
this._stop = false
|
|
67
|
+
this.running = true
|
|
62
68
|
if (options.maxFps) this.maxFps = options.maxFps
|
|
63
69
|
if (options.fps) this.fps = options.fps
|
|
64
70
|
if (options.delay) this.requestedDelay = options.delay
|
|
@@ -76,6 +82,7 @@ export class Scheduler extends Directive {
|
|
|
76
82
|
|
|
77
83
|
if (!this.maxFps) {
|
|
78
84
|
const loop = (timestamp: number) => {
|
|
85
|
+
if (this._stop) return
|
|
79
86
|
requestAnimationFrame(loop)
|
|
80
87
|
this.nextTick(timestamp)
|
|
81
88
|
}
|
|
@@ -103,6 +110,7 @@ export class Scheduler extends Directive {
|
|
|
103
110
|
|
|
104
111
|
stop() {
|
|
105
112
|
this._stop = true
|
|
113
|
+
this.running = false
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
package/src/engine/bootstrap.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Application, ApplicationOptions } from "pixi.js";
|
|
2
|
+
import { Observable, Subscription } from "rxjs";
|
|
2
3
|
import { ComponentFunction, h } from "./signal";
|
|
3
4
|
import { useProps } from '../hooks/useProps';
|
|
4
5
|
import { registerAllComponents, registerComponent } from './reactive';
|
|
@@ -33,6 +34,12 @@ export interface BootstrapOptions extends ApplicationOptions {
|
|
|
33
34
|
enableLayout?: boolean; // true by default
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
type BootstrapResult = {
|
|
38
|
+
canvasElement: any;
|
|
39
|
+
app: Application;
|
|
40
|
+
hmrSubscription?: Subscription;
|
|
41
|
+
};
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* Bootstraps a canvas element and renders it to the DOM.
|
|
38
45
|
*
|
|
@@ -61,7 +68,7 @@ export interface BootstrapOptions extends ApplicationOptions {
|
|
|
61
68
|
* });
|
|
62
69
|
* ```
|
|
63
70
|
*/
|
|
64
|
-
export const bootstrapCanvas = async (rootElement: HTMLElement | null, canvas: ComponentFunction<any>, options?: BootstrapOptions) => {
|
|
71
|
+
export const bootstrapCanvas = async (rootElement: HTMLElement | null, canvas: ComponentFunction<any>, options?: BootstrapOptions): Promise<BootstrapResult> => {
|
|
65
72
|
// Extract component registration options
|
|
66
73
|
const { components, autoRegister, enableLayout, ...appOptions } = options ?? {};
|
|
67
74
|
if (enableLayout !== false) {
|
|
@@ -90,20 +97,57 @@ export const bootstrapCanvas = async (rootElement: HTMLElement | null, canvas: C
|
|
|
90
97
|
antialias: true,
|
|
91
98
|
...appOptions
|
|
92
99
|
});
|
|
93
|
-
const canvasElement = await h(canvas);
|
|
94
|
-
if (canvasElement.tag != 'Canvas') {
|
|
95
|
-
throw new Error('Canvas is required');
|
|
96
|
-
}
|
|
97
|
-
(canvasElement as any).render(rootElement, app);
|
|
98
100
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
const renderCanvasElement = (canvasElement: any) => {
|
|
102
|
+
if (canvasElement.tag != 'Canvas') {
|
|
103
|
+
throw new Error('Canvas is required');
|
|
104
|
+
}
|
|
105
|
+
canvasElement.render(rootElement, app);
|
|
106
|
+
|
|
107
|
+
const { backgroundColor } = useProps(canvasElement.props, {
|
|
108
|
+
backgroundColor: 'black'
|
|
109
|
+
});
|
|
102
110
|
|
|
103
|
-
|
|
111
|
+
app.renderer.background.color = backgroundColor()
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
return {
|
|
114
|
+
canvasElement,
|
|
115
|
+
app
|
|
116
|
+
};
|
|
108
117
|
};
|
|
118
|
+
|
|
119
|
+
const canvasElement = h(canvas) as any;
|
|
120
|
+
|
|
121
|
+
if (canvasElement instanceof Observable) {
|
|
122
|
+
return new Promise<BootstrapResult>((resolve, reject) => {
|
|
123
|
+
let resolved = false;
|
|
124
|
+
let hmrSubscription: Subscription;
|
|
125
|
+
|
|
126
|
+
hmrSubscription = canvasElement.subscribe({
|
|
127
|
+
next(value: any) {
|
|
128
|
+
try {
|
|
129
|
+
const nextCanvasElement = value?.elements?.[0] ?? value;
|
|
130
|
+
if (!nextCanvasElement) return;
|
|
131
|
+
|
|
132
|
+
const result = renderCanvasElement(nextCanvasElement);
|
|
133
|
+
|
|
134
|
+
if (!resolved) {
|
|
135
|
+
resolved = true;
|
|
136
|
+
Promise.resolve().then(() => {
|
|
137
|
+
resolve({
|
|
138
|
+
...result,
|
|
139
|
+
hmrSubscription
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
reject(error);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
error: reject
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return renderCanvasElement(await canvasElement);
|
|
109
153
|
};
|
package/src/engine/reactive.ts
CHANGED
|
@@ -294,7 +294,7 @@ function handleAnimatedSignalsFreeze(element: Element, shouldPause: boolean) {
|
|
|
294
294
|
Object.values(element.propObservables).forEach(processValue);
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
function destroyElement(element: Element | Element[]) {
|
|
297
|
+
export function destroyElement(element: Element | Element[]) {
|
|
298
298
|
if (Array.isArray(element)) {
|
|
299
299
|
element.forEach((e) => destroyElement(e));
|
|
300
300
|
return;
|