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.
Files changed (45) hide show
  1. package/dist/{DebugRenderer-CTWPthRt.js → DebugRenderer-Rrw9FlTd.js} +2 -2
  2. package/dist/{DebugRenderer-CTWPthRt.js.map → DebugRenderer-Rrw9FlTd.js.map} +1 -1
  3. package/dist/components/Button.d.ts +50 -3
  4. package/dist/components/Button.d.ts.map +1 -1
  5. package/dist/components/Canvas.d.ts.map +1 -1
  6. package/dist/components/Joystick.d.ts +36 -0
  7. package/dist/components/Joystick.d.ts.map +1 -0
  8. package/dist/components/Sprite.d.ts +2 -0
  9. package/dist/components/Sprite.d.ts.map +1 -1
  10. package/dist/components/index.d.ts +1 -0
  11. package/dist/components/index.d.ts.map +1 -1
  12. package/dist/components/types/Spritesheet.d.ts +0 -118
  13. package/dist/components/types/Spritesheet.d.ts.map +1 -1
  14. package/dist/directives/Controls.d.ts +16 -7
  15. package/dist/directives/Controls.d.ts.map +1 -1
  16. package/dist/directives/GamepadControls.d.ts +3 -1
  17. package/dist/directives/GamepadControls.d.ts.map +1 -1
  18. package/dist/directives/JoystickControls.d.ts +172 -0
  19. package/dist/directives/JoystickControls.d.ts.map +1 -0
  20. package/dist/directives/index.d.ts +1 -0
  21. package/dist/directives/index.d.ts.map +1 -1
  22. package/dist/engine/reactive.d.ts.map +1 -1
  23. package/dist/engine/signal.d.ts.map +1 -1
  24. package/dist/{index-BqwprEPH.js → index-BQ99FClW.js} +6057 -5433
  25. package/dist/index-BQ99FClW.js.map +1 -0
  26. package/dist/index.global.js +7 -7
  27. package/dist/index.global.js.map +1 -1
  28. package/dist/index.js +59 -57
  29. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  30. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  31. package/package.json +1 -1
  32. package/src/components/Button.ts +168 -41
  33. package/src/components/Canvas.ts +3 -0
  34. package/src/components/Joystick.ts +361 -0
  35. package/src/components/Sprite.ts +82 -16
  36. package/src/components/index.ts +2 -1
  37. package/src/components/types/Spritesheet.ts +0 -118
  38. package/src/directives/Controls.ts +42 -8
  39. package/src/directives/GamepadControls.ts +40 -18
  40. package/src/directives/JoystickControls.ts +396 -0
  41. package/src/directives/index.ts +1 -0
  42. package/src/engine/reactive.ts +362 -242
  43. package/src/engine/signal.ts +8 -2
  44. package/src/utils/GlobalAssetLoader.ts +257 -0
  45. package/dist/index-BqwprEPH.js.map +0 -1
@@ -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
- [key]: value,
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
- function onMount(parent: Element, element: Element, index?: number) {
221
- element.props.context = parent.props.context;
222
- element.parent = parent;
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
- let lastElement = null
241
- element.propSubscriptions.push(element.propObservables.attach.observable.subscribe(async (args) => {
242
- const value = args?.value ?? args
243
- if (!value) {
244
- throw new Error(`attach in ${element.tag} is undefined or null, add a component`)
245
- }
246
- if (lastElement) {
247
- destroyElement(lastElement)
248
- }
249
- lastElement = value
250
- await createElement(element, value)
251
- resolve(undefined)
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
- * Creates and mounts a child element to a parent element.
267
- * Handles different types of children: Elements, Promises resolving to Elements, and Observables.
268
- *
269
- * @description This function is designed to handle reactive child components that can be:
270
- * - Direct Element instances
271
- * - Promises that resolve to Elements (for async components)
272
- * - Observables that emit Elements, arrays of Elements, or FlowObservable results
273
- * - Nested observables within arrays or FlowObservable results (handled recursively)
274
- *
275
- * For Observables, it subscribes to the stream and automatically mounts/unmounts elements
276
- * as they are emitted. The function handles nested observables recursively, ensuring that
277
- * observables within arrays or FlowObservable results are also properly subscribed to.
278
- * All subscriptions are stored in the parent's effectSubscriptions for automatic cleanup.
279
- *
280
- * @param {Element} parent - The parent element to mount the child to
281
- * @param {Element | Observable<any> | Promise<Element>} child - The child to create and mount
282
- *
283
- * @example
284
- * ```typescript
285
- * // Direct element
286
- * await createElement(parent, childElement);
287
- *
288
- * // Observable of elements (from cond, loop, etc.)
289
- * await createElement(parent, cond(signal(visible), () => h(Container)));
290
- *
291
- * // Observable that emits arrays containing other observables
292
- * await createElement(parent, observableOfObservables);
293
- *
294
- * // Promise resolving to element
295
- * await createElement(parent, import('./MyComponent').then(mod => h(mod.default)));
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
- if (prev) {
319
- components.forEach(async (c) => {
320
- const index = parent.props.children.indexOf(prev.props.key);
321
- if (c instanceof Observable) {
322
- // Handle observable component recursively
323
- await createElement(parent, c);
324
- } else if (isElement(c)) {
325
- onMount(parent, c, index + 1);
326
- propagateContext(c);
327
- }
328
- });
329
- return;
330
- }
331
- components.forEach(async (component) => {
332
- if (!Array.isArray(component)) {
333
- if (component instanceof Observable) {
334
- // Handle observable component recursively
335
- await createElement(parent, component);
336
- } else if (isElement(component)) {
337
- onMount(parent, component);
338
- propagateContext(component);
339
- }
340
- } else {
341
- component.forEach(async (comp) => {
342
- if (comp instanceof Observable) {
343
- // Handle observable component recursively
344
- await createElement(parent, comp);
345
- } else if (isElement(comp)) {
346
- onMount(parent, comp);
347
- propagateContext(comp);
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
- } else if (Array.isArray(value)) {
357
- // Handle array of elements (which can also be observables)
358
- value.forEach(async (element) => {
359
- if (element instanceof Observable) {
360
- // Handle observable element recursively
361
- await createElement(parent, element);
362
- } else if (isElement(element)) {
363
- onMount(parent, element);
364
- propagateContext(element);
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 isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
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
- if (isFirstSubscription) {
415
- isFirstSubscription = false;
416
- elements.forEach(el => el.destroy());
417
- elements = [];
418
- elementMap.clear();
419
-
420
- const items = itemsSubject();
421
- if (items) {
422
- items.forEach((item, index) => {
423
- const element = createElementFn(item, index);
424
- if (element) {
425
- elements.push(element);
426
- elementMap.set(index, element);
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
- if (change.type === 'init' || change.type === 'reset') {
437
- elements.forEach(el => destroyElement(el));
438
- elements = [];
439
- elementMap.clear();
440
-
441
- const items = itemsSubject();
442
- if (items) {
443
- items.forEach((item, index) => {
444
- const element = createElementFn(item, index);
445
- if (element) {
446
- elements.push(element);
447
- elementMap.set(index, element);
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
- elementMap.set(change.index! + i, element);
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
- } else if (change.type === 'update' && change.index !== undefined && change.items.length === 1) {
468
- const index = change.index;
469
- const newItem = change.items[0];
470
-
471
- // Check if the previous item at this index was effectively undefined or non-existent
472
- if (index >= elements.length || elements[index] === undefined || !elementMap.has(index)) {
473
- // Treat as add operation
474
- const newElement = createElementFn(newItem as T, index);
475
- if (newElement) {
476
- elements.splice(index, 0, newElement); // Insert at the correct index
477
- elementMap.set(index, newElement);
478
- // Adjust indices in elementMap for subsequent elements might be needed if map relied on exact indices
479
- // This simple implementation assumes keys are stable or createElementFn handles context correctly
480
- } else {
481
- console.warn(`Element creation returned null for index ${index} during add-like update.`);
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
- // Treat as a standard update operation
485
- const oldElement = elements[index];
486
- destroyElement(oldElement)
487
- const newElement = createElementFn(newItem as T, index);
488
- if (newElement) {
489
- elements[index] = newElement;
490
- elementMap.set(index, newElement);
491
- } else {
492
- // Handle case where new element creation returns null
493
- elements.splice(index, 1);
494
- elementMap.delete(index);
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
- subscriber.next({
500
- elements: [...elements] // Create a new array to ensure change detection
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
- const key = change.key as string | number
505
- if (isFirstSubscription) {
506
- isFirstSubscription = false;
507
- elements.forEach(el => destroyElement(el));
508
- elements = [];
509
- elementMap.clear();
510
-
511
- const items = (itemsSubject as WritableObjectSignal<T>)();
512
- if (items) {
513
- Object.entries(items).forEach(([key, value]) => {
514
- const element = createElementFn(value, key);
515
- if (element) {
516
- elements.push(element);
517
- elementMap.set(key, element);
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
- if (change.type === 'init' || change.type === 'reset') {
528
- elements.forEach(el => destroyElement(el));
529
- elements = [];
530
- elementMap.clear();
531
-
532
- const items = (itemsSubject as WritableObjectSignal<T>)();
533
- if (items) {
534
- Object.entries(items).forEach(([key, value]) => {
535
- const element = createElementFn(value, key);
536
- if (element) {
537
- elements.push(element);
538
- elementMap.set(key, element);
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
- subscriber.next({
569
- elements: [...elements] // Create a new array to ensure change detection
570
- });
685
+ subscriber.next({
686
+ elements: [...elements] // Create a new array to ensure change detection
571
687
  });
688
+ });
572
689
 
573
- return subscription;
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;