canvasengine 2.0.0-beta.45 → 2.0.0-beta.47
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 +86 -0
- package/dist/components/Container.d.ts.map +1 -0
- package/dist/components/DOMContainer.d.ts +98 -0
- package/dist/components/DOMContainer.d.ts.map +1 -0
- package/dist/components/DOMElement.d.ts +16 -5
- package/dist/components/DOMElement.d.ts.map +1 -1
- package/dist/components/DOMSprite.d.ts +108 -0
- package/dist/components/DOMSprite.d.ts.map +1 -0
- package/dist/components/DisplayObject.d.ts +94 -0
- package/dist/components/DisplayObject.d.ts.map +1 -0
- package/dist/components/FocusContainer.d.ts +129 -0
- package/dist/components/FocusContainer.d.ts.map +1 -0
- package/dist/components/Mesh.d.ts +208 -0
- package/dist/components/Mesh.d.ts.map +1 -0
- package/dist/components/Sprite.d.ts +242 -0
- package/dist/components/Sprite.d.ts.map +1 -0
- package/dist/components/Viewport.d.ts +121 -0
- package/dist/components/Viewport.d.ts.map +1 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +4 -4
- package/dist/directives/Controls.d.ts.map +1 -1
- package/dist/directives/ControlsBase.d.ts +1 -0
- package/dist/directives/ControlsBase.d.ts.map +1 -1
- package/dist/directives/FocusNavigation.d.ts +4 -22
- package/dist/directives/FocusNavigation.d.ts.map +1 -1
- package/dist/directives/KeyboardControls.d.ts +1 -0
- package/dist/directives/KeyboardControls.d.ts.map +1 -1
- package/dist/directives/Scheduler.d.ts.map +1 -1
- package/dist/directives/Shake.d.ts +1 -0
- package/dist/directives/Shake.d.ts.map +1 -1
- package/dist/engine/FocusManager.d.ts +10 -9
- package/dist/engine/FocusManager.d.ts.map +1 -1
- package/dist/engine/bootstrap.d.ts +1 -0
- package/dist/engine/bootstrap.d.ts.map +1 -1
- package/dist/engine/directive.d.ts +1 -1
- package/dist/engine/directive.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/hooks/useFocus.d.ts.map +1 -1
- package/dist/index-DaGekQUW.js +2218 -0
- package/dist/index-DaGekQUW.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.js +3 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +11868 -88
- package/dist/index.js.map +1 -1
- package/dist/utils/tabindex.d.ts +16 -0
- package/dist/utils/tabindex.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/DOMContainer.ts +186 -1
- package/src/components/DOMElement.ts +164 -37
- package/src/components/DOMSprite.ts +759 -0
- package/src/components/DisplayObject.ts +33 -7
- package/src/components/FocusContainer.ts +22 -26
- package/src/components/Sprite.ts +12 -3
- package/src/components/Text.ts +1 -1
- package/src/components/Viewport.ts +5 -5
- package/src/components/index.ts +2 -1
- package/src/directives/Controls.ts +5 -5
- package/src/directives/ControlsBase.ts +1 -0
- package/src/directives/FocusNavigation.ts +8 -146
- package/src/directives/KeyboardControls.ts +11 -2
- package/src/directives/Scheduler.ts +12 -4
- package/src/directives/Shake.ts +9 -6
- package/src/engine/FocusManager.ts +44 -29
- package/src/engine/bootstrap.ts +5 -2
- package/src/engine/directive.ts +2 -2
- package/src/engine/reactive.ts +84 -12
- package/src/hooks/useFocus.ts +2 -5
- package/src/index.ts +2 -1
- package/src/types/pixi-cull.d.ts +7 -0
- package/src/utils/tabindex.ts +70 -0
- package/testing/index.ts +31 -3
- package/tsconfig.json +3 -2
- package/dist/DebugRenderer-CSxse9YI.js +0 -172
- package/dist/DebugRenderer-CSxse9YI.js.map +0 -1
- package/dist/index-DH2ZMhYm.js +0 -13276
- package/dist/index-DH2ZMhYm.js.map +0 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WritableSignal } from '@signe/reactive';
|
|
2
|
+
export type TabindexBoundaryMode = "wrap" | "clamp" | "none";
|
|
3
|
+
export type TabindexBounds = {
|
|
4
|
+
count: () => number;
|
|
5
|
+
min?: number;
|
|
6
|
+
} | {
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
};
|
|
10
|
+
type TabindexNavigator = {
|
|
11
|
+
next: (delta: number) => void;
|
|
12
|
+
set: (value: number) => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function createTabindexNavigator(tabindex: WritableSignal<number>, bounds: TabindexBounds, mode?: TabindexBoundaryMode): TabindexNavigator;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=tabindex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabindex.d.ts","sourceRoot":"","sources":["../../src/utils/tabindex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D,MAAM,MAAM,cAAc,GACtB;IAAE,KAAK,EAAE,MAAM,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAuCF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAChC,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE,oBAA6B,GAClC,iBAAiB,CAenB"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { DOMContainer as PixiDOMContainer } from "pixi.js";
|
|
2
|
+
import { effect } from "@signe/reactive";
|
|
2
3
|
import {
|
|
3
4
|
createComponent,
|
|
4
5
|
Element,
|
|
6
|
+
isElement,
|
|
5
7
|
registerComponent,
|
|
6
8
|
} from "../engine/reactive";
|
|
7
9
|
import { ComponentInstance, DisplayObject } from "./DisplayObject";
|
|
8
10
|
import { ComponentFunction, h } from "../engine/signal";
|
|
9
11
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
10
12
|
import { CanvasDOMElement, DOMElement } from "./DOMElement";
|
|
13
|
+
import { isPercent } from "../utils/functions";
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -107,6 +110,159 @@ const EVENTS = [
|
|
|
107
110
|
|
|
108
111
|
export class CanvasDOMContainer extends DisplayObject(PixiDOMContainer) {
|
|
109
112
|
disableLayout = true;
|
|
113
|
+
private canvasSizeEffect: any = null;
|
|
114
|
+
private static readonly DOM_ROUTING_MAP: Record<string, string> = {
|
|
115
|
+
Sprite: "DOMSprite",
|
|
116
|
+
};
|
|
117
|
+
private static readonly DOM_ALLOWED_TAGS = new Set([
|
|
118
|
+
"DOMContainer",
|
|
119
|
+
"DOMElement",
|
|
120
|
+
"DOMSprite",
|
|
121
|
+
]);
|
|
122
|
+
private static readonly DOM_UNSUPPORTED_TAGS = new Set([
|
|
123
|
+
"Canvas",
|
|
124
|
+
"Container",
|
|
125
|
+
"Graphics",
|
|
126
|
+
"Rect",
|
|
127
|
+
"Circle",
|
|
128
|
+
"Ellipse",
|
|
129
|
+
"Triangle",
|
|
130
|
+
"Svg",
|
|
131
|
+
"Mesh",
|
|
132
|
+
"Scene",
|
|
133
|
+
"ParticlesEmitter",
|
|
134
|
+
"Sprite",
|
|
135
|
+
"Video",
|
|
136
|
+
"Text",
|
|
137
|
+
"TilingSprite",
|
|
138
|
+
"Viewport",
|
|
139
|
+
"NineSliceSprite",
|
|
140
|
+
"Button",
|
|
141
|
+
"Joystick",
|
|
142
|
+
"FocusContainer",
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
private hasDomContainerAncestor(): boolean {
|
|
146
|
+
const element = this.getElement();
|
|
147
|
+
let parent = element?.parent;
|
|
148
|
+
while (parent) {
|
|
149
|
+
if (parent.tag === "DOMContainer") return true;
|
|
150
|
+
parent = parent.parent;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private getPercentRatio(value: string): number | null {
|
|
156
|
+
const parsed = parseFloat(value);
|
|
157
|
+
if (Number.isNaN(parsed)) return null;
|
|
158
|
+
return parsed / 100;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private getCanvasSize() {
|
|
162
|
+
const canvasSize = this.fullProps?.context?.canvasSize;
|
|
163
|
+
return typeof canvasSize === "function" ? canvasSize() : canvasSize;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private shouldUseCanvasPercent(): boolean {
|
|
167
|
+
const widthProp = this.fullProps?.width;
|
|
168
|
+
const heightProp = this.fullProps?.height;
|
|
169
|
+
if (!isPercent(widthProp) && !isPercent(heightProp)) return false;
|
|
170
|
+
return !this.hasDomContainerAncestor();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private syncCanvasSizeEffect() {
|
|
174
|
+
const shouldTrack = this.shouldUseCanvasPercent();
|
|
175
|
+
if (shouldTrack && !this.canvasSizeEffect) {
|
|
176
|
+
const canvasSize = this.fullProps?.context?.canvasSize;
|
|
177
|
+
if (typeof canvasSize === "function") {
|
|
178
|
+
this.canvasSizeEffect = effect(() => {
|
|
179
|
+
canvasSize();
|
|
180
|
+
this.applyElementSize();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} else if (!shouldTrack && this.canvasSizeEffect) {
|
|
184
|
+
this.canvasSizeEffect.subscription?.unsubscribe();
|
|
185
|
+
this.canvasSizeEffect = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private applyElementSize() {
|
|
190
|
+
if (!this.element) return;
|
|
191
|
+
const widthProp = this.fullProps?.width;
|
|
192
|
+
const heightProp = this.fullProps?.height;
|
|
193
|
+
const useCanvasSize = this.shouldUseCanvasPercent();
|
|
194
|
+
const canvasSize = useCanvasSize ? this.getCanvasSize() : null;
|
|
195
|
+
|
|
196
|
+
if (widthProp !== undefined) {
|
|
197
|
+
if (isPercent(widthProp)) {
|
|
198
|
+
if (useCanvasSize) {
|
|
199
|
+
const ratio = this.getPercentRatio(widthProp);
|
|
200
|
+
if (ratio !== null) {
|
|
201
|
+
const baseWidth = (canvasSize?.width !== undefined)
|
|
202
|
+
? canvasSize.width
|
|
203
|
+
: this.getWidth();
|
|
204
|
+
this.element.style.width = `${baseWidth * ratio}px`;
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
this.element.style.width = widthProp;
|
|
208
|
+
}
|
|
209
|
+
} else if (typeof widthProp === "number") {
|
|
210
|
+
this.element.style.width = `${widthProp}px`;
|
|
211
|
+
} else if (typeof widthProp === "string") {
|
|
212
|
+
this.element.style.width = widthProp;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (heightProp !== undefined) {
|
|
217
|
+
if (isPercent(heightProp)) {
|
|
218
|
+
if (useCanvasSize) {
|
|
219
|
+
const ratio = this.getPercentRatio(heightProp);
|
|
220
|
+
if (ratio !== null) {
|
|
221
|
+
const baseHeight = (canvasSize?.height !== undefined)
|
|
222
|
+
? canvasSize.height
|
|
223
|
+
: this.getHeight();
|
|
224
|
+
this.element.style.height = `${baseHeight * ratio}px`;
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
this.element.style.height = heightProp;
|
|
228
|
+
}
|
|
229
|
+
} else if (typeof heightProp === "number") {
|
|
230
|
+
this.element.style.height = `${heightProp}px`;
|
|
231
|
+
} else if (typeof heightProp === "string") {
|
|
232
|
+
this.element.style.height = heightProp;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private routeDomChildren(children: any): any {
|
|
238
|
+
if (!children) return children;
|
|
239
|
+
if (Array.isArray(children)) {
|
|
240
|
+
return children.map((child) => this.routeDomChildren(child));
|
|
241
|
+
}
|
|
242
|
+
if (isElement(children)) {
|
|
243
|
+
if (CanvasDOMContainer.DOM_ALLOWED_TAGS.has(children.tag)) {
|
|
244
|
+
return children;
|
|
245
|
+
}
|
|
246
|
+
const routedTag = CanvasDOMContainer.DOM_ROUTING_MAP[children.tag];
|
|
247
|
+
if (routedTag) {
|
|
248
|
+
children.propSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
249
|
+
children.effectSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
250
|
+
children.effectUnmounts?.forEach((fn) => fn?.());
|
|
251
|
+
const routedProps = children.propObservables ?? children.props;
|
|
252
|
+
return createComponent(routedTag, routedProps);
|
|
253
|
+
}
|
|
254
|
+
if (CanvasDOMContainer.DOM_UNSUPPORTED_TAGS.has(children.tag)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Component ${children.tag} is not implemented for DOMContainer context yet. Only Sprite is supported.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (children.props?.children) {
|
|
260
|
+
children.props.children = this.routeDomChildren(children.props.children);
|
|
261
|
+
}
|
|
262
|
+
return children;
|
|
263
|
+
}
|
|
264
|
+
return children;
|
|
265
|
+
}
|
|
110
266
|
|
|
111
267
|
onInit(props: any) {
|
|
112
268
|
// Handle internal _scopeClass prop for scoped CSS
|
|
@@ -134,9 +290,38 @@ export class CanvasDOMContainer extends DisplayObject(PixiDOMContainer) {
|
|
|
134
290
|
divProps.attrs = props.attrs;
|
|
135
291
|
}
|
|
136
292
|
|
|
137
|
-
const
|
|
293
|
+
const routedChildren = this.routeDomChildren(props.children);
|
|
294
|
+
props.children = routedChildren;
|
|
295
|
+
const div = h(DOMElement, divProps, routedChildren) as unknown as Element<CanvasDOMElement>;
|
|
138
296
|
this.element = div.componentInstance.element;
|
|
139
297
|
}
|
|
298
|
+
|
|
299
|
+
async onMount(element: Element<any>, index?: number) {
|
|
300
|
+
await super.onMount(element, index);
|
|
301
|
+
this.syncCanvasSizeEffect();
|
|
302
|
+
this.applyElementSize();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
onUpdate(props: any) {
|
|
306
|
+
super.onUpdate(props);
|
|
307
|
+
this.syncCanvasSizeEffect();
|
|
308
|
+
this.applyElementSize();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
onLayoutComputed() {
|
|
312
|
+
this.applyElementSize();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async onDestroy(parent: Element<any>, afterDestroy?: () => void) {
|
|
316
|
+
const _afterDestroy = () => {
|
|
317
|
+
if (this.canvasSizeEffect) {
|
|
318
|
+
this.canvasSizeEffect.subscription?.unsubscribe();
|
|
319
|
+
this.canvasSizeEffect = null;
|
|
320
|
+
}
|
|
321
|
+
if (afterDestroy) afterDestroy();
|
|
322
|
+
};
|
|
323
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
324
|
+
}
|
|
140
325
|
}
|
|
141
326
|
|
|
142
327
|
export interface CanvasDOMContainer extends DisplayObjectProps { }
|
|
@@ -10,8 +10,8 @@ import { DisplayObjectProps } from "./types/DisplayObject";
|
|
|
10
10
|
import { isObservable } from "../engine/utils";
|
|
11
11
|
import { isSignal } from "@signe/reactive";
|
|
12
12
|
|
|
13
|
-
interface
|
|
14
|
-
element
|
|
13
|
+
export interface DOMElementProps extends DisplayObjectProps {
|
|
14
|
+
element?:
|
|
15
15
|
| string
|
|
16
16
|
| {
|
|
17
17
|
value: HTMLElement;
|
|
@@ -32,6 +32,14 @@ interface DOMContainerProps extends DisplayObjectProps {
|
|
|
32
32
|
onBeforeDestroy?: OnHook;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface DOMContainerProps extends DOMElementProps {
|
|
36
|
+
element:
|
|
37
|
+
| string
|
|
38
|
+
| {
|
|
39
|
+
value: HTMLElement;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
/**
|
|
36
44
|
* DOMContainer class for managing DOM elements within the canvas engine
|
|
37
45
|
*
|
|
@@ -204,6 +212,8 @@ export class CanvasDOMElement {
|
|
|
204
212
|
private onBeforeDestroy: OnHook | null = null;
|
|
205
213
|
private valueSignal: any = null;
|
|
206
214
|
private isFormElementType: boolean = false;
|
|
215
|
+
private classSubscriptions: Array<{ unsubscribe: () => void }> = [];
|
|
216
|
+
private childTextSubscriptions: Array<{ unsubscribe: () => void }> = [];
|
|
207
217
|
|
|
208
218
|
/**
|
|
209
219
|
* Checks if the element is a form element that supports the value attribute
|
|
@@ -215,12 +225,145 @@ export class CanvasDOMElement {
|
|
|
215
225
|
return formElements.includes(elementType.toLowerCase());
|
|
216
226
|
}
|
|
217
227
|
|
|
218
|
-
|
|
228
|
+
private collectClassTokens(value: any, tokens: string[]) {
|
|
229
|
+
if (!value) return;
|
|
230
|
+
if (isSignal(value)) {
|
|
231
|
+
this.collectClassTokens(value(), tokens);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (typeof value === "string") {
|
|
235
|
+
value.split(/\s+/).filter(Boolean).forEach((token) => tokens.push(token));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (Array.isArray(value)) {
|
|
239
|
+
value.forEach((item) => this.collectClassTokens(item, tokens));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "object") {
|
|
243
|
+
for (const [className, shouldAdd] of Object.entries(value)) {
|
|
244
|
+
const resolved = isSignal(shouldAdd as any) ? (shouldAdd as any)() : shouldAdd;
|
|
245
|
+
if (resolved) {
|
|
246
|
+
tokens.push(className);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private collectClassSignals(value: any, signals: Set<any>) {
|
|
253
|
+
if (!value) return;
|
|
254
|
+
if (isSignal(value)) {
|
|
255
|
+
signals.add(value);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (Array.isArray(value)) {
|
|
259
|
+
value.forEach((item) => this.collectClassSignals(item, signals));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof value === "object") {
|
|
263
|
+
Object.values(value).forEach((item) =>
|
|
264
|
+
this.collectClassSignals(item, signals)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private applyClassList(classList: any) {
|
|
270
|
+
// Clear existing classes first
|
|
271
|
+
this.element.className = "";
|
|
272
|
+
|
|
273
|
+
if (typeof classList === "string") {
|
|
274
|
+
// String: space-separated class names
|
|
275
|
+
this.element.className = classList;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const tokens: string[] = [];
|
|
280
|
+
this.collectClassTokens(classList, tokens);
|
|
281
|
+
if (tokens.length > 0) {
|
|
282
|
+
this.element.classList.add(...tokens);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private syncClassSubscriptions(classList: any) {
|
|
287
|
+
for (const sub of this.classSubscriptions) {
|
|
288
|
+
sub.unsubscribe();
|
|
289
|
+
}
|
|
290
|
+
this.classSubscriptions = [];
|
|
291
|
+
|
|
292
|
+
const signals = new Set<any>();
|
|
293
|
+
this.collectClassSignals(classList, signals);
|
|
294
|
+
if (signals.size === 0) return;
|
|
295
|
+
|
|
296
|
+
signals.forEach((signal) => {
|
|
297
|
+
if (!signal?.observable?.subscribe) return;
|
|
298
|
+
const sub = signal.observable.subscribe(() => {
|
|
299
|
+
this.applyClassList(classList);
|
|
300
|
+
});
|
|
301
|
+
this.classSubscriptions.push(sub);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private appendChildElement(child: any) {
|
|
306
|
+
if (child === null || child === undefined || child === false) return;
|
|
307
|
+
|
|
308
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
309
|
+
this.element.appendChild(document.createTextNode(String(child)));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (isSignal(child)) {
|
|
314
|
+
const textNode = document.createTextNode(
|
|
315
|
+
child() == null ? "" : String(child())
|
|
316
|
+
);
|
|
317
|
+
this.element.appendChild(textNode);
|
|
318
|
+
if (child.observable?.subscribe) {
|
|
319
|
+
const sub = child.observable.subscribe((value: any) => {
|
|
320
|
+
textNode.textContent = value == null ? "" : String(value);
|
|
321
|
+
});
|
|
322
|
+
this.childTextSubscriptions.push(sub);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (isObservable(child)) {
|
|
328
|
+
child.subscribe((value: any) => {
|
|
329
|
+
if (value && typeof value === "object" && "elements" in value) {
|
|
330
|
+
const elements = value.elements || [];
|
|
331
|
+
elements.forEach((element: any) => this.appendChildElement(element));
|
|
332
|
+
} else if (Array.isArray(value)) {
|
|
333
|
+
value.forEach((element) => this.appendChildElement(element));
|
|
334
|
+
} else {
|
|
335
|
+
this.appendChildElement(value);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (Array.isArray(child)) {
|
|
342
|
+
child.forEach((item) => this.appendChildElement(item));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const childElement = child?.componentInstance?.element;
|
|
347
|
+
if (childElement) {
|
|
348
|
+
this.element.appendChild(childElement);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const nestedChildren = child?.props?.children;
|
|
353
|
+
if (nestedChildren) {
|
|
354
|
+
this.appendChildElement(nestedChildren);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
onInit(props: DOMElementProps) {
|
|
219
359
|
if (typeof props.element === "string") {
|
|
220
360
|
this.element = document.createElement(props.element);
|
|
221
361
|
this.isFormElementType = this.isFormElement(props.element);
|
|
222
362
|
} else {
|
|
223
|
-
this.element = props.element
|
|
363
|
+
this.element = props.element?.value;
|
|
364
|
+
if (!this.element) {
|
|
365
|
+
throw new Error("DOMElement requires a valid element.");
|
|
366
|
+
}
|
|
224
367
|
this.isFormElementType = this.isFormElement(this.element.tagName);
|
|
225
368
|
}
|
|
226
369
|
if (props.onBeforeDestroy || props["on-before-destroy"]) {
|
|
@@ -264,17 +407,7 @@ export class CanvasDOMElement {
|
|
|
264
407
|
}
|
|
265
408
|
}
|
|
266
409
|
if (props.children) {
|
|
267
|
-
|
|
268
|
-
if (isObservable(child)) {
|
|
269
|
-
child.subscribe(({ elements }) => {
|
|
270
|
-
for (const element of elements) {
|
|
271
|
-
this.element.appendChild(element.componentInstance.element);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
} else {
|
|
275
|
-
this.element.appendChild(child.componentInstance.element);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
410
|
+
this.appendChildElement(props.children);
|
|
278
411
|
}
|
|
279
412
|
this.onUpdate(props);
|
|
280
413
|
}
|
|
@@ -311,7 +444,7 @@ export class CanvasDOMElement {
|
|
|
311
444
|
}
|
|
312
445
|
}
|
|
313
446
|
|
|
314
|
-
onUpdate(props:
|
|
447
|
+
onUpdate(props: DOMElementProps) {
|
|
315
448
|
if (!this.element) return;
|
|
316
449
|
for (const [key, value] of Object.entries(props.attrs || {})) {
|
|
317
450
|
if (key === "tabindex") {
|
|
@@ -323,25 +456,10 @@ export class CanvasDOMElement {
|
|
|
323
456
|
this.element.removeAttribute('tabindex');
|
|
324
457
|
}
|
|
325
458
|
} else if (key === "class") {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
|
|
331
|
-
if (typeof classList === "string") {
|
|
332
|
-
// String: space-separated class names
|
|
333
|
-
this.element.className = classList;
|
|
334
|
-
} else if (Array.isArray(classList)) {
|
|
335
|
-
// Array: array of class names
|
|
336
|
-
this.element.classList.add(...classList);
|
|
337
|
-
} else if (typeof classList === "object" && classList !== null) {
|
|
338
|
-
// Object: { className: boolean }
|
|
339
|
-
for (const [className, shouldAdd] of Object.entries(classList)) {
|
|
340
|
-
if (shouldAdd) {
|
|
341
|
-
this.element.classList.add(className);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
459
|
+
const rawClassList = value.items || value.value || value;
|
|
460
|
+
const classList = isSignal(rawClassList) ? rawClassList() : rawClassList;
|
|
461
|
+
this.applyClassList(classList);
|
|
462
|
+
this.syncClassSubscriptions(classList);
|
|
345
463
|
} else if (key === "style") {
|
|
346
464
|
const styleValue = value.items || value.value || value;
|
|
347
465
|
|
|
@@ -389,8 +507,9 @@ export class CanvasDOMElement {
|
|
|
389
507
|
this.element.setAttribute(key, value);
|
|
390
508
|
}
|
|
391
509
|
}
|
|
392
|
-
if (props
|
|
393
|
-
|
|
510
|
+
if ("textContent" in props) {
|
|
511
|
+
const textContent = props.textContent;
|
|
512
|
+
this.element.textContent = textContent == null ? "" : String(textContent);
|
|
394
513
|
}
|
|
395
514
|
}
|
|
396
515
|
|
|
@@ -410,6 +529,14 @@ export class CanvasDOMElement {
|
|
|
410
529
|
}
|
|
411
530
|
|
|
412
531
|
this.eventListeners.clear();
|
|
532
|
+
for (const sub of this.classSubscriptions) {
|
|
533
|
+
sub.unsubscribe();
|
|
534
|
+
}
|
|
535
|
+
this.classSubscriptions = [];
|
|
536
|
+
for (const sub of this.childTextSubscriptions) {
|
|
537
|
+
sub.unsubscribe();
|
|
538
|
+
}
|
|
539
|
+
this.childTextSubscriptions = [];
|
|
413
540
|
|
|
414
541
|
this.element.remove();
|
|
415
542
|
|