canvasengine 2.0.0-beta.25 → 2.0.0-beta.27

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 (36) hide show
  1. package/dist/{DebugRenderer-LPVGoCBJ.js → DebugRenderer-EZWNhJKJ.js} +2 -2
  2. package/dist/{DebugRenderer-LPVGoCBJ.js.map → DebugRenderer-EZWNhJKJ.js.map} +1 -1
  3. package/dist/components/Button.d.ts +136 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Container.d.ts +1 -0
  6. package/dist/components/Container.d.ts.map +1 -1
  7. package/dist/components/DOMContainer.d.ts +1 -0
  8. package/dist/components/DOMContainer.d.ts.map +1 -1
  9. package/dist/components/DisplayObject.d.ts +1 -0
  10. package/dist/components/DisplayObject.d.ts.map +1 -1
  11. package/dist/components/Graphic.d.ts.map +1 -1
  12. package/dist/components/Mesh.d.ts +1 -0
  13. package/dist/components/Mesh.d.ts.map +1 -1
  14. package/dist/components/Sprite.d.ts +1 -0
  15. package/dist/components/Sprite.d.ts.map +1 -1
  16. package/dist/components/Viewport.d.ts +1 -0
  17. package/dist/components/Viewport.d.ts.map +1 -1
  18. package/dist/components/index.d.ts +1 -0
  19. package/dist/components/index.d.ts.map +1 -1
  20. package/dist/engine/bootstrap.d.ts.map +1 -1
  21. package/dist/engine/reactive.d.ts +31 -4
  22. package/dist/engine/reactive.d.ts.map +1 -1
  23. package/dist/{index-5TTorHyA.js → index-JzoygiZz.js} +2577 -2430
  24. package/dist/index-JzoygiZz.js.map +1 -0
  25. package/dist/index.global.js +6 -6
  26. package/dist/index.global.js.map +1 -1
  27. package/dist/index.js +58 -56
  28. package/package.json +1 -1
  29. package/src/components/Button.ts +269 -0
  30. package/src/components/DisplayObject.ts +21 -2
  31. package/src/components/Graphic.ts +1 -4
  32. package/src/components/Sprite.ts +13 -7
  33. package/src/components/index.ts +2 -1
  34. package/src/engine/bootstrap.ts +1 -0
  35. package/src/engine/reactive.ts +169 -50
  36. package/dist/index-5TTorHyA.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { ArrayChange, ObjectChange, Signal, WritableArraySignal, WritableObjectSignal, isComputed, isSignal, signal } from "@signe/reactive";
1
+ import { ArrayChange, ObjectChange, Signal, WritableArraySignal, WritableObjectSignal, isComputed, isSignal, signal, computed } from "@signe/reactive";
2
2
  import {
3
3
  Observable,
4
4
  Subject,
@@ -9,6 +9,11 @@ import {
9
9
  of,
10
10
  share,
11
11
  switchMap,
12
+ debounceTime,
13
+ distinctUntilChanged,
14
+ bufferTime,
15
+ filter,
16
+ throttleTime,
12
17
  } from "rxjs";
13
18
  import { ComponentInstance } from "../components/DisplayObject";
14
19
  import { Directive, applyDirective } from "./directive";
@@ -349,7 +354,7 @@ export function loop<T>(
349
354
  }
350
355
 
351
356
  if (change.type === 'init' || change.type === 'reset') {
352
- elements.forEach(el => el.destroy());
357
+ elements.forEach(el => destroyElement(el));
353
358
  elements = [];
354
359
  elementMap.clear();
355
360
 
@@ -376,7 +381,7 @@ export function loop<T>(
376
381
  } else if (change.type === 'remove' && change.index !== undefined) {
377
382
  const removed = elements.splice(change.index, 1);
378
383
  removed.forEach(el => {
379
- el.destroy();
384
+ destroyElement(el)
380
385
  elementMap.delete(change.index!);
381
386
  });
382
387
  } else if (change.type === 'update' && change.index !== undefined && change.items.length === 1) {
@@ -398,7 +403,7 @@ export function loop<T>(
398
403
  } else {
399
404
  // Treat as a standard update operation
400
405
  const oldElement = elements[index];
401
- oldElement.destroy();
406
+ destroyElement(oldElement)
402
407
  const newElement = createElementFn(newItem as T, index);
403
408
  if (newElement) {
404
409
  elements[index] = newElement;
@@ -419,7 +424,7 @@ export function loop<T>(
419
424
  const key = change.key as string | number
420
425
  if (isFirstSubscription) {
421
426
  isFirstSubscription = false;
422
- elements.forEach(el => el.destroy());
427
+ elements.forEach(el => destroyElement(el));
423
428
  elements = [];
424
429
  elementMap.clear();
425
430
 
@@ -440,7 +445,7 @@ export function loop<T>(
440
445
  }
441
446
 
442
447
  if (change.type === 'init' || change.type === 'reset') {
443
- elements.forEach(el => el.destroy());
448
+ elements.forEach(el => destroyElement(el));
444
449
  elements = [];
445
450
  elementMap.clear();
446
451
 
@@ -464,14 +469,14 @@ export function loop<T>(
464
469
  const index = elements.findIndex(el => elementMap.get(key) === el);
465
470
  if (index !== -1) {
466
471
  const [removed] = elements.splice(index, 1);
467
- removed.destroy();
472
+ destroyElement(removed)
468
473
  elementMap.delete(key);
469
474
  }
470
475
  } else if (change.type === 'update' && change.key && change.value !== undefined) {
471
476
  const index = elements.findIndex(el => elementMap.get(key) === el);
472
477
  if (index !== -1) {
473
478
  const oldElement = elements[index];
474
- oldElement.destroy();
479
+ destroyElement(oldElement)
475
480
  const newElement = createElementFn(change.value as T, key);
476
481
  if (newElement) {
477
482
  elements[index] = newElement;
@@ -491,70 +496,184 @@ export function loop<T>(
491
496
  }
492
497
 
493
498
  /**
494
- * Conditionally creates and destroys elements based on a condition signal.
499
+ * Conditionally creates and destroys elements based on condition signals with support for else if and else.
500
+ *
501
+ * @description This function creates conditional rendering with support for multiple conditions (if/else if/else pattern).
502
+ * It evaluates conditions in order and renders the first matching condition's element.
503
+ * The function maintains full reactivity with signals and ensures proper cleanup of elements.
495
504
  *
496
- * @param {Signal<boolean> | boolean} condition - A signal or boolean that determines whether to create an element.
505
+ * @param {Signal<boolean> | boolean | (() => boolean)} condition - A signal, boolean, or function that determines whether to create an element.
497
506
  * @param {Function} createElementFn - A function that returns an element or a promise that resolves to an element.
498
- * @returns {Observable} An observable that emits the created or destroyed element.
507
+ * @param {...Array} additionalConditions - Additional conditions for else if and else cases.
508
+ * Can be:
509
+ * - A function for else case: `() => Element | Promise<Element>`
510
+ * - An array for else if case: `[Signal<boolean> | boolean | (() => boolean), () => Element | Promise<Element>]`
511
+ * @returns {Observable} An observable that emits the created element based on the matching condition.
512
+ *
513
+ * @example
514
+ * ```typescript
515
+ * // Simple if/else
516
+ * cond(
517
+ * signal(isVisible),
518
+ * () => h(Container),
519
+ * () => h(Text, { text: 'Hidden' }) // else
520
+ * );
521
+ *
522
+ * // Multiple else if + else
523
+ * cond(
524
+ * signal(status === 'loading'),
525
+ * () => h(LoadingSpinner),
526
+ * [signal(status === 'error'), () => h(ErrorMessage)], // else if
527
+ * [signal(status === 'success'), () => h(SuccessMessage)], // else if
528
+ * () => h(DefaultMessage) // else
529
+ * );
530
+ * ```
499
531
  */
500
532
  export function cond(
501
- condition: Signal<boolean> | boolean,
502
- createElementFn: () => Element | Promise<Element>
533
+ condition: Signal<boolean> | boolean | (() => boolean),
534
+ createElementFn: () => Element | Promise<Element>,
535
+ ...additionalConditions: Array<
536
+ | (() => Element | Promise<Element>) // else final
537
+ | [Signal<boolean> | boolean | (() => boolean), () => Element | Promise<Element>] // else if
538
+ >
503
539
  ): FlowObservable {
504
- let element: Element | null = null;
505
-
506
- if (isSignal(condition)) {
507
- const signalCondition = condition as WritableObjectSignal<boolean>;
508
- return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
509
- return signalCondition.observable.subscribe(bool => {
510
- if (bool) {
511
- let _el = createElementFn();
540
+ let currentElement: Element | null = null;
541
+ let currentConditionIndex = -1;
542
+
543
+ // Parse additional conditions
544
+ const elseIfConditions: Array<{
545
+ condition: Signal<boolean>;
546
+ elementFn: () => Element | Promise<Element>;
547
+ }> = [];
548
+ let elseElementFn: (() => Element | Promise<Element>) | null = null;
549
+
550
+ // Convert function conditions to computed signals
551
+ const convertConditionToSignal = (cond: Signal<boolean> | boolean | (() => boolean)): Signal<boolean> => {
552
+ if (isSignal(cond)) {
553
+ return cond as Signal<boolean>;
554
+ } else if (typeof cond === 'function') {
555
+ return computed(cond as () => boolean);
556
+ } else {
557
+ return signal(cond as boolean);
558
+ }
559
+ };
560
+
561
+ // Process additional conditions
562
+ for (const param of additionalConditions) {
563
+ if (Array.isArray(param)) {
564
+ // else if case: [condition, elementFn]
565
+ elseIfConditions.push({
566
+ condition: convertConditionToSignal(param[0]),
567
+ elementFn: param[1],
568
+ });
569
+ } else if (typeof param === 'function') {
570
+ // else case: elementFn (should be the last one)
571
+ elseElementFn = param;
572
+ break; // Stop processing after else
573
+ }
574
+ }
575
+
576
+ // Collect all conditions with their element functions
577
+ const allConditions = [
578
+ { condition: convertConditionToSignal(condition), elementFn: createElementFn },
579
+ ...elseIfConditions,
580
+ ];
581
+
582
+ // All conditions are now signals, so we always use the reactive path
583
+ return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
584
+ const subscriptions: Subscription[] = [];
585
+
586
+ const evaluateConditions = () => {
587
+ // Find the first matching condition
588
+ let matchingIndex = -1;
589
+ for (let i = 0; i < allConditions.length; i++) {
590
+ const condition = allConditions[i].condition;
591
+ const conditionValue = condition();
592
+
593
+ if (conditionValue) {
594
+ matchingIndex = i;
595
+ break;
596
+ }
597
+ }
598
+
599
+ // If no condition matches and we have an else, use else
600
+ const shouldUseElse = matchingIndex === -1 && elseElementFn;
601
+ const newConditionIndex = shouldUseElse ? -2 : matchingIndex; // -2 for else, -1 for nothing
602
+
603
+ // Only update if the condition changed
604
+ if (newConditionIndex !== currentConditionIndex) {
605
+ // Destroy current element if it exists
606
+ if (currentElement) {
607
+ destroyElement(currentElement);
608
+ currentElement = null;
609
+ }
610
+
611
+ currentConditionIndex = newConditionIndex;
612
+
613
+ if (shouldUseElse) {
614
+ // Render else element
615
+ let _el = elseElementFn!();
512
616
  if (isPromise(_el)) {
513
617
  from(_el as Promise<Element>).subscribe(el => {
514
- element = el;
618
+ currentElement = el;
515
619
  subscriber.next({
516
620
  type: "init",
517
621
  elements: [el],
518
622
  });
519
623
  });
520
624
  } else {
521
- element = _el as Element;
625
+ currentElement = _el as Element;
522
626
  subscriber.next({
523
627
  type: "init",
524
- elements: [element],
628
+ elements: [currentElement],
629
+ });
630
+ }
631
+ } else if (matchingIndex >= 0) {
632
+ // Render matching condition element
633
+ let _el = allConditions[matchingIndex].elementFn();
634
+ if (isPromise(_el)) {
635
+ from(_el as Promise<Element>).subscribe(el => {
636
+ currentElement = el;
637
+ subscriber.next({
638
+ type: "init",
639
+ elements: [el],
640
+ });
641
+ });
642
+ } else {
643
+ currentElement = _el as Element;
644
+ subscriber.next({
645
+ type: "init",
646
+ elements: [currentElement],
525
647
  });
526
648
  }
527
- } else if (element) {
528
- destroyElement(element);
529
- subscriber.next({
530
- elements: [],
531
- });
532
649
  } else {
650
+ // No matching condition and no else
533
651
  subscriber.next({
534
652
  elements: [],
535
653
  });
536
654
  }
537
- });
538
- }).pipe(share())
539
- } else {
540
- // Handle boolean case
541
- if (condition) {
542
- let _el = createElementFn();
543
- if (isPromise(_el)) {
544
- return from(_el as Promise<Element>).pipe(
545
- map((el) => ({
546
- type: "init",
547
- elements: [el],
548
- }))
549
- );
550
655
  }
551
- return of({
552
- type: "init",
553
- elements: [_el as Element],
554
- });
555
- }
556
- return of({
557
- elements: [],
656
+ };
657
+
658
+ // Subscribe to all signal conditions
659
+ allConditions.forEach(({ condition }) => {
660
+ const signalCondition = condition as WritableObjectSignal<boolean>;
661
+ subscriptions.push(
662
+ signalCondition.observable.subscribe(() => {
663
+ evaluateConditions();
664
+ })
665
+ );
558
666
  });
559
- }
667
+
668
+ // Initial evaluation
669
+ evaluateConditions();
670
+
671
+ // Return cleanup function
672
+ return () => {
673
+ subscriptions.forEach(sub => sub.unsubscribe());
674
+ if (currentElement) {
675
+ destroyElement(currentElement);
676
+ }
677
+ };
678
+ }).pipe(share());
560
679
  }