canvasengine 2.0.0-beta.37 → 2.0.0-beta.39
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/{DebugRenderer-CTWPthRt.js → DebugRenderer-Rrw9FlTd.js} +2 -2
- package/dist/{DebugRenderer-CTWPthRt.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
- package/dist/components/Button.d.ts +50 -3
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Joystick.d.ts +36 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/Sprite.d.ts +2 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/types/Spritesheet.d.ts +0 -118
- package/dist/components/types/Spritesheet.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +16 -7
- package/dist/directives/Controls.d.ts.map +1 -1
- package/dist/directives/GamepadControls.d.ts +3 -1
- package/dist/directives/GamepadControls.d.ts.map +1 -1
- package/dist/directives/JoystickControls.d.ts +172 -0
- package/dist/directives/JoystickControls.d.ts.map +1 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/{index-BqwprEPH.js → index-BQ99FClW.js} +6057 -5433
- package/dist/index-BQ99FClW.js.map +1 -0
- package/dist/index.global.js +7 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +59 -57
- package/dist/utils/GlobalAssetLoader.d.ts +141 -0
- package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Button.ts +168 -41
- package/src/components/Canvas.ts +3 -0
- package/src/components/Joystick.ts +361 -0
- package/src/components/Sprite.ts +82 -16
- package/src/components/index.ts +2 -1
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/Controls.ts +42 -8
- package/src/directives/GamepadControls.ts +40 -18
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/index.ts +1 -0
- package/src/engine/reactive.ts +362 -242
- package/src/engine/signal.ts +8 -2
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/dist/index-BqwprEPH.js.map +0 -1
package/src/engine/reactive.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
bufferTime,
|
|
15
15
|
filter,
|
|
16
16
|
throttleTime,
|
|
17
|
+
combineLatest,
|
|
17
18
|
} from "rxjs";
|
|
18
19
|
import { ComponentInstance } from "../components/DisplayObject";
|
|
19
20
|
import { Directive, applyDirective } from "./directive";
|
|
@@ -181,8 +182,8 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
181
182
|
instance.onUpdate?.(
|
|
182
183
|
path == ""
|
|
183
184
|
? {
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
[key]: value,
|
|
186
|
+
}
|
|
186
187
|
: set({}, path + "." + key, value)
|
|
187
188
|
);
|
|
188
189
|
})
|
|
@@ -217,9 +218,79 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Checks if all dependencies are ready (not undefined).
|
|
223
|
+
* Handles signals synchronously and promises asynchronously.
|
|
224
|
+
* For reactive signals, sets up subscriptions to mount when all become ready.
|
|
225
|
+
*
|
|
226
|
+
* @param deps - Array of signals, promises, or direct values
|
|
227
|
+
* @returns Promise<boolean> - true if all dependencies are ready
|
|
228
|
+
*/
|
|
229
|
+
async function checkDependencies(
|
|
230
|
+
deps: any[]
|
|
231
|
+
): Promise<boolean> {
|
|
232
|
+
const values = await Promise.all(
|
|
233
|
+
deps.map(async (dep) => {
|
|
234
|
+
if (isSignal(dep)) {
|
|
235
|
+
return dep(); // Read current signal value
|
|
236
|
+
} else if (isPromise(dep)) {
|
|
237
|
+
return await dep; // Await promise resolution
|
|
238
|
+
}
|
|
239
|
+
return dep; // Direct value
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
return values.every((v) => v !== undefined);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sets up subscriptions to reactive signal dependencies.
|
|
247
|
+
* When all signals become defined, mounts the component.
|
|
248
|
+
*/
|
|
249
|
+
function setupDependencySubscriptions(
|
|
250
|
+
parent: Element,
|
|
251
|
+
element: Element,
|
|
252
|
+
deps: any[],
|
|
253
|
+
index?: number
|
|
254
|
+
) {
|
|
255
|
+
const signalDeps = deps.filter((dep) => isSignal(dep));
|
|
256
|
+
const promiseDeps = deps.filter((dep) => isPromise(dep));
|
|
257
|
+
|
|
258
|
+
if (signalDeps.length === 0) {
|
|
259
|
+
// No reactive signals, nothing to subscribe to
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create observables from signals
|
|
264
|
+
const signalObservables = signalDeps.map((sig) => sig.observable);
|
|
265
|
+
|
|
266
|
+
// Combine all signal observables
|
|
267
|
+
const subscription = combineLatest(signalObservables).subscribe(
|
|
268
|
+
async () => {
|
|
269
|
+
// Check if all dependencies are now ready
|
|
270
|
+
const allReady = await checkDependencies(deps);
|
|
271
|
+
if (allReady) {
|
|
272
|
+
// Unsubscribe - we only need to mount once
|
|
273
|
+
subscription.unsubscribe();
|
|
274
|
+
// Remove from subscriptions
|
|
275
|
+
const idx = element.propSubscriptions.indexOf(subscription);
|
|
276
|
+
if (idx > -1) {
|
|
277
|
+
element.propSubscriptions.splice(idx, 1);
|
|
278
|
+
}
|
|
279
|
+
// Now mount the component
|
|
280
|
+
performMount(parent, element, index);
|
|
281
|
+
propagateContext(element);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Store subscription for cleanup
|
|
287
|
+
element.propSubscriptions.push(subscription);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Performs the actual mounting of the component.
|
|
292
|
+
*/
|
|
293
|
+
function performMount(parent: Element, element: Element, index?: number) {
|
|
223
294
|
element.componentInstance.onMount?.(element, index);
|
|
224
295
|
for (let name in element.directives) {
|
|
225
296
|
element.directives[name].onMount?.(element);
|
|
@@ -227,6 +298,29 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
227
298
|
element.effectMounts.forEach((fn: any) => {
|
|
228
299
|
element.effectUnmounts.push(fn(element));
|
|
229
300
|
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function onMount(parent: Element, element: Element, index?: number) {
|
|
304
|
+
let actualParent = parent;
|
|
305
|
+
while (actualParent?.tag === 'fragment') {
|
|
306
|
+
actualParent = actualParent.parent;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
element.props.context = actualParent.props.context;
|
|
310
|
+
element.parent = actualParent;
|
|
311
|
+
|
|
312
|
+
// Check dependencies before mounting
|
|
313
|
+
if (element.props.dependencies && Array.isArray(element.props.dependencies)) {
|
|
314
|
+
const deps = element.props.dependencies;
|
|
315
|
+
const ready = await checkDependencies(deps);
|
|
316
|
+
if (!ready) {
|
|
317
|
+
// Set up subscriptions for reactive signals to trigger mount later
|
|
318
|
+
setupDependencySubscriptions(actualParent, element, deps, index);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
performMount(actualParent, element, index);
|
|
230
324
|
};
|
|
231
325
|
|
|
232
326
|
async function propagateContext(element) {
|
|
@@ -237,19 +331,19 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
237
331
|
}
|
|
238
332
|
else {
|
|
239
333
|
await new Promise((resolve) => {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
334
|
+
let lastElement = null
|
|
335
|
+
element.propSubscriptions.push(element.propObservables.attach.observable.subscribe(async (args) => {
|
|
336
|
+
const value = args?.value ?? args
|
|
337
|
+
if (!value) {
|
|
338
|
+
throw new Error(`attach in ${element.tag} is undefined or null, add a component`)
|
|
339
|
+
}
|
|
340
|
+
if (lastElement) {
|
|
341
|
+
destroyElement(lastElement)
|
|
342
|
+
}
|
|
343
|
+
lastElement = value
|
|
344
|
+
await createElement(element, value)
|
|
345
|
+
resolve(undefined)
|
|
346
|
+
}))
|
|
253
347
|
})
|
|
254
348
|
}
|
|
255
349
|
}
|
|
@@ -262,39 +356,39 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
262
356
|
}
|
|
263
357
|
};
|
|
264
358
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Creates and mounts a child element to a parent element.
|
|
361
|
+
* Handles different types of children: Elements, Promises resolving to Elements, and Observables.
|
|
362
|
+
*
|
|
363
|
+
* @description This function is designed to handle reactive child components that can be:
|
|
364
|
+
* - Direct Element instances
|
|
365
|
+
* - Promises that resolve to Elements (for async components)
|
|
366
|
+
* - Observables that emit Elements, arrays of Elements, or FlowObservable results
|
|
367
|
+
* - Nested observables within arrays or FlowObservable results (handled recursively)
|
|
368
|
+
*
|
|
369
|
+
* For Observables, it subscribes to the stream and automatically mounts/unmounts elements
|
|
370
|
+
* as they are emitted. The function handles nested observables recursively, ensuring that
|
|
371
|
+
* observables within arrays or FlowObservable results are also properly subscribed to.
|
|
372
|
+
* All subscriptions are stored in the parent's effectSubscriptions for automatic cleanup.
|
|
373
|
+
*
|
|
374
|
+
* @param {Element} parent - The parent element to mount the child to
|
|
375
|
+
* @param {Element | Observable<any> | Promise<Element>} child - The child to create and mount
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```typescript
|
|
379
|
+
* // Direct element
|
|
380
|
+
* await createElement(parent, childElement);
|
|
381
|
+
*
|
|
382
|
+
* // Observable of elements (from cond, loop, etc.)
|
|
383
|
+
* await createElement(parent, cond(signal(visible), () => h(Container)));
|
|
384
|
+
*
|
|
385
|
+
* // Observable that emits arrays containing other observables
|
|
386
|
+
* await createElement(parent, observableOfObservables);
|
|
387
|
+
*
|
|
388
|
+
* // Promise resolving to element
|
|
389
|
+
* await createElement(parent, import('./MyComponent').then(mod => h(mod.default)));
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
298
392
|
async function createElement(parent: Element, child: Element | Observable<any> | Promise<Element>) {
|
|
299
393
|
if (isPromise(child)) {
|
|
300
394
|
child = await child;
|
|
@@ -313,62 +407,62 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
313
407
|
elements: Element[];
|
|
314
408
|
prev?: Element;
|
|
315
409
|
} = value;
|
|
316
|
-
|
|
410
|
+
|
|
317
411
|
const components = comp.filter((c) => c !== null);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
412
|
+
if (prev) {
|
|
413
|
+
components.forEach(async (c) => {
|
|
414
|
+
const index = parent.props.children.indexOf(prev.props.key);
|
|
415
|
+
if (c instanceof Observable) {
|
|
416
|
+
// Handle observable component recursively
|
|
417
|
+
await createElement(parent, c);
|
|
418
|
+
} else if (isElement(c)) {
|
|
419
|
+
onMount(parent, c, index + 1);
|
|
420
|
+
propagateContext(c);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
components.forEach(async (component) => {
|
|
426
|
+
if (!Array.isArray(component)) {
|
|
427
|
+
if (component instanceof Observable) {
|
|
428
|
+
// Handle observable component recursively
|
|
429
|
+
await createElement(parent, component);
|
|
430
|
+
} else if (isElement(component)) {
|
|
431
|
+
onMount(parent, component);
|
|
432
|
+
propagateContext(component);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
component.forEach(async (comp) => {
|
|
436
|
+
if (comp instanceof Observable) {
|
|
437
|
+
// Handle observable component recursively
|
|
438
|
+
await createElement(parent, comp);
|
|
439
|
+
} else if (isElement(comp)) {
|
|
440
|
+
onMount(parent, comp);
|
|
441
|
+
propagateContext(comp);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
});
|
|
352
446
|
} else if (isElement(value)) {
|
|
353
447
|
// Handle direct Element emission
|
|
354
448
|
onMount(parent, value);
|
|
355
449
|
propagateContext(value);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
450
|
+
} else if (Array.isArray(value)) {
|
|
451
|
+
// Handle array of elements (which can also be observables)
|
|
452
|
+
value.forEach(async (element) => {
|
|
453
|
+
if (element instanceof Observable) {
|
|
454
|
+
// Handle observable element recursively
|
|
455
|
+
await createElement(parent, element);
|
|
456
|
+
} else if (isElement(element)) {
|
|
457
|
+
onMount(parent, element);
|
|
458
|
+
propagateContext(element);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
368
462
|
elementsListen.next(undefined);
|
|
369
463
|
}
|
|
370
464
|
);
|
|
371
|
-
|
|
465
|
+
|
|
372
466
|
// Store subscription for cleanup
|
|
373
467
|
parent.effectSubscriptions.push(subscription);
|
|
374
468
|
} else if (isElement(child)) {
|
|
@@ -405,172 +499,198 @@ export function loop<T>(
|
|
|
405
499
|
let elementMap = new Map<string | number, Element>();
|
|
406
500
|
let isFirstSubscription = true;
|
|
407
501
|
|
|
408
|
-
const
|
|
502
|
+
const ensureElement = (itemResult: any): Element | null => {
|
|
503
|
+
if (!itemResult) return null;
|
|
504
|
+
if (isElement(itemResult)) return itemResult;
|
|
505
|
+
return {
|
|
506
|
+
tag: 'fragment',
|
|
507
|
+
props: { children: Array.isArray(itemResult) ? itemResult : [itemResult] },
|
|
508
|
+
componentInstance: {} as any,
|
|
509
|
+
propSubscriptions: [],
|
|
510
|
+
effectSubscriptions: [],
|
|
511
|
+
effectMounts: [],
|
|
512
|
+
effectUnmounts: [],
|
|
513
|
+
propObservables: {},
|
|
514
|
+
parent: null,
|
|
515
|
+
directives: {},
|
|
516
|
+
destroy() { destroyElement(this) },
|
|
517
|
+
allElements: new Subject()
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
|
|
409
522
|
Array.isArray(signal());
|
|
410
523
|
|
|
411
524
|
return new Observable<FlowResult>(subscriber => {
|
|
412
525
|
const subscription = isArraySignal(itemsSubject)
|
|
413
526
|
? itemsSubject.observable.subscribe(change => {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
subscriber.next({
|
|
431
|
-
elements: [...elements]
|
|
527
|
+
if (isFirstSubscription) {
|
|
528
|
+
isFirstSubscription = false;
|
|
529
|
+
elements.forEach(el => el.destroy());
|
|
530
|
+
elements = [];
|
|
531
|
+
elementMap.clear();
|
|
532
|
+
|
|
533
|
+
const items = itemsSubject();
|
|
534
|
+
if (items) {
|
|
535
|
+
items.forEach((item, index) => {
|
|
536
|
+
const element = ensureElement(createElementFn(item, index));
|
|
537
|
+
if (element) {
|
|
538
|
+
elements.push(element);
|
|
539
|
+
elementMap.set(index, element);
|
|
540
|
+
}
|
|
432
541
|
});
|
|
433
|
-
return;
|
|
434
542
|
}
|
|
543
|
+
subscriber.next({
|
|
544
|
+
elements: [...elements]
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
435
548
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
} else if (change.type === 'add' && change.index !== undefined) {
|
|
452
|
-
const newElements = change.items.map((item, i) => {
|
|
453
|
-
const element = createElementFn(item as T, change.index! + i);
|
|
549
|
+
// Handle computed signals that emit array values directly (not ArrayChange objects)
|
|
550
|
+
// When a computed emits, `change` is the array itself, not an object with `type`
|
|
551
|
+
const isDirectArrayChange = Array.isArray(change) || (change && typeof change === 'object' && !('type' in change));
|
|
552
|
+
|
|
553
|
+
if (change.type === 'init' || change.type === 'reset' || isDirectArrayChange) {
|
|
554
|
+
elements.forEach(el => destroyElement(el));
|
|
555
|
+
elements = [];
|
|
556
|
+
elementMap.clear();
|
|
557
|
+
|
|
558
|
+
const items = itemsSubject();
|
|
559
|
+
if (items) {
|
|
560
|
+
items.forEach((item, index) => {
|
|
561
|
+
const element = ensureElement(createElementFn(item, index));
|
|
454
562
|
if (element) {
|
|
455
|
-
|
|
563
|
+
elements.push(element);
|
|
564
|
+
elementMap.set(index, element);
|
|
456
565
|
}
|
|
457
|
-
return element;
|
|
458
|
-
}).filter((el): el is Element => el !== null);
|
|
459
|
-
|
|
460
|
-
elements.splice(change.index, 0, ...newElements);
|
|
461
|
-
} else if (change.type === 'remove' && change.index !== undefined) {
|
|
462
|
-
const removed = elements.splice(change.index, 1);
|
|
463
|
-
removed.forEach(el => {
|
|
464
|
-
destroyElement(el)
|
|
465
|
-
elementMap.delete(change.index!);
|
|
466
566
|
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
567
|
+
}
|
|
568
|
+
} else if (change.type === 'add' && change.index !== undefined) {
|
|
569
|
+
const newElements = change.items.map((item, i) => {
|
|
570
|
+
const element = ensureElement(createElementFn(item as T, change.index! + i));
|
|
571
|
+
if (element) {
|
|
572
|
+
elementMap.set(change.index! + i, element);
|
|
573
|
+
}
|
|
574
|
+
return element;
|
|
575
|
+
}).filter((el): el is Element => el !== null);
|
|
576
|
+
|
|
577
|
+
elements.splice(change.index, 0, ...newElements);
|
|
578
|
+
} else if (change.type === 'remove' && change.index !== undefined) {
|
|
579
|
+
const removed = elements.splice(change.index, 1);
|
|
580
|
+
removed.forEach(el => {
|
|
581
|
+
destroyElement(el)
|
|
582
|
+
elementMap.delete(change.index!);
|
|
583
|
+
});
|
|
584
|
+
} else if (change.type === 'update' && change.index !== undefined && change.items.length === 1) {
|
|
585
|
+
const index = change.index;
|
|
586
|
+
const newItem = change.items[0];
|
|
587
|
+
|
|
588
|
+
// Check if the previous item at this index was effectively undefined or non-existent
|
|
589
|
+
if (index >= elements.length || elements[index] === undefined || !elementMap.has(index)) {
|
|
590
|
+
// Treat as add operation
|
|
591
|
+
const newElement = ensureElement(createElementFn(newItem as T, index));
|
|
592
|
+
if (newElement) {
|
|
593
|
+
elements.splice(index, 0, newElement); // Insert at the correct index
|
|
594
|
+
elementMap.set(index, newElement);
|
|
595
|
+
// Adjust indices in elementMap for subsequent elements might be needed if map relied on exact indices
|
|
596
|
+
// This simple implementation assumes keys are stable or createElementFn handles context correctly
|
|
483
597
|
} else {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
598
|
+
console.warn(`Element creation returned null for index ${index} during add-like update.`);
|
|
599
|
+
}
|
|
600
|
+
} else {
|
|
601
|
+
// Treat as a standard update operation
|
|
602
|
+
const oldElement = elements[index];
|
|
603
|
+
destroyElement(oldElement)
|
|
604
|
+
const newElement = ensureElement(createElementFn(newItem as T, index));
|
|
605
|
+
if (newElement) {
|
|
606
|
+
elements[index] = newElement;
|
|
607
|
+
elementMap.set(index, newElement);
|
|
608
|
+
} else {
|
|
609
|
+
// Handle case where new element creation returns null
|
|
610
|
+
elements.splice(index, 1);
|
|
611
|
+
elementMap.delete(index);
|
|
496
612
|
}
|
|
497
613
|
}
|
|
614
|
+
}
|
|
498
615
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
616
|
+
subscriber.next({
|
|
617
|
+
elements: [...elements] // Create a new array to ensure change detection
|
|
618
|
+
});
|
|
619
|
+
})
|
|
503
620
|
: (itemsSubject as WritableObjectSignal<T>).observable.subscribe(change => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
|
-
subscriber.next({
|
|
522
|
-
elements: [...elements]
|
|
621
|
+
const key = change.key as string | number
|
|
622
|
+
if (isFirstSubscription) {
|
|
623
|
+
isFirstSubscription = false;
|
|
624
|
+
elements.forEach(el => destroyElement(el));
|
|
625
|
+
elements = [];
|
|
626
|
+
elementMap.clear();
|
|
627
|
+
|
|
628
|
+
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
629
|
+
if (items) {
|
|
630
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
631
|
+
const element = ensureElement(createElementFn(value, key));
|
|
632
|
+
if (element) {
|
|
633
|
+
elements.push(element);
|
|
634
|
+
elementMap.set(key, element);
|
|
635
|
+
}
|
|
523
636
|
});
|
|
524
|
-
return;
|
|
525
637
|
}
|
|
638
|
+
subscriber.next({
|
|
639
|
+
elements: [...elements]
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
526
643
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
} else if (change.type === 'add' && change.key && change.value !== undefined) {
|
|
543
|
-
const element = createElementFn(change.value as T, key);
|
|
544
|
-
if (element) {
|
|
545
|
-
elements.push(element);
|
|
546
|
-
elementMap.set(key, element);
|
|
547
|
-
}
|
|
548
|
-
} else if (change.type === 'remove' && change.key) {
|
|
549
|
-
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
550
|
-
if (index !== -1) {
|
|
551
|
-
const [removed] = elements.splice(index, 1);
|
|
552
|
-
destroyElement(removed)
|
|
553
|
-
elementMap.delete(key);
|
|
554
|
-
}
|
|
555
|
-
} else if (change.type === 'update' && change.key && change.value !== undefined) {
|
|
556
|
-
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
557
|
-
if (index !== -1) {
|
|
558
|
-
const oldElement = elements[index];
|
|
559
|
-
destroyElement(oldElement)
|
|
560
|
-
const newElement = createElementFn(change.value as T, key);
|
|
561
|
-
if (newElement) {
|
|
562
|
-
elements[index] = newElement;
|
|
563
|
-
elementMap.set(key, newElement);
|
|
644
|
+
if (change.type === 'init' || change.type === 'reset') {
|
|
645
|
+
elements.forEach(el => destroyElement(el));
|
|
646
|
+
elements = [];
|
|
647
|
+
elementMap.clear();
|
|
648
|
+
|
|
649
|
+
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
650
|
+
if (items) {
|
|
651
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
652
|
+
const element = ensureElement(createElementFn(value, key));
|
|
653
|
+
if (element) {
|
|
654
|
+
elements.push(element);
|
|
655
|
+
elementMap.set(key, element);
|
|
564
656
|
}
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} else if (change.type === 'add' && change.key && change.value !== undefined) {
|
|
660
|
+
const element = ensureElement(createElementFn(change.value as T, key));
|
|
661
|
+
if (element) {
|
|
662
|
+
elements.push(element);
|
|
663
|
+
elementMap.set(key, element);
|
|
664
|
+
}
|
|
665
|
+
} else if (change.type === 'remove' && change.key) {
|
|
666
|
+
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
667
|
+
if (index !== -1) {
|
|
668
|
+
const [removed] = elements.splice(index, 1);
|
|
669
|
+
destroyElement(removed)
|
|
670
|
+
elementMap.delete(key);
|
|
671
|
+
}
|
|
672
|
+
} else if (change.type === 'update' && change.key && change.value !== undefined) {
|
|
673
|
+
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
674
|
+
if (index !== -1) {
|
|
675
|
+
const oldElement = elements[index];
|
|
676
|
+
destroyElement(oldElement)
|
|
677
|
+
const newElement = ensureElement(createElementFn(change.value as T, key));
|
|
678
|
+
if (newElement) {
|
|
679
|
+
elements[index] = newElement;
|
|
680
|
+
elementMap.set(key, newElement);
|
|
565
681
|
}
|
|
566
682
|
}
|
|
683
|
+
}
|
|
567
684
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
});
|
|
685
|
+
subscriber.next({
|
|
686
|
+
elements: [...elements] // Create a new array to ensure change detection
|
|
571
687
|
});
|
|
688
|
+
});
|
|
572
689
|
|
|
573
|
-
return
|
|
690
|
+
return () => {
|
|
691
|
+
subscription.unsubscribe();
|
|
692
|
+
elements.forEach(el => destroyElement(el));
|
|
693
|
+
};
|
|
574
694
|
});
|
|
575
695
|
});
|
|
576
696
|
}
|
|
@@ -660,7 +780,7 @@ export function cond(
|
|
|
660
780
|
];
|
|
661
781
|
|
|
662
782
|
// All conditions are now signals, so we always use the reactive path
|
|
663
|
-
return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
|
|
783
|
+
return new Observable<{ elements: Element[], type?: "init" | "remove" }>(subscriber => {
|
|
664
784
|
const subscriptions: Subscription[] = [];
|
|
665
785
|
|
|
666
786
|
const evaluateConditions = () => {
|
|
@@ -669,7 +789,7 @@ export function cond(
|
|
|
669
789
|
for (let i = 0; i < allConditions.length; i++) {
|
|
670
790
|
const condition = allConditions[i].condition;
|
|
671
791
|
const conditionValue = condition();
|
|
672
|
-
|
|
792
|
+
|
|
673
793
|
if (conditionValue) {
|
|
674
794
|
matchingIndex = i;
|
|
675
795
|
break;
|