canvasengine 2.0.0-beta.26 → 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.
- package/dist/{DebugRenderer-C8qYAVLT.js → DebugRenderer-EZWNhJKJ.js} +2 -2
- package/dist/{DebugRenderer-C8qYAVLT.js.map → DebugRenderer-EZWNhJKJ.js.map} +1 -1
- package/dist/components/Button.d.ts +136 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Container.d.ts +1 -0
- package/dist/components/Container.d.ts.map +1 -1
- package/dist/components/DOMContainer.d.ts +1 -0
- package/dist/components/DOMContainer.d.ts.map +1 -1
- package/dist/components/DisplayObject.d.ts +1 -0
- package/dist/components/DisplayObject.d.ts.map +1 -1
- package/dist/components/Mesh.d.ts +1 -0
- package/dist/components/Mesh.d.ts.map +1 -1
- package/dist/components/Sprite.d.ts +1 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/Viewport.d.ts +1 -0
- package/dist/components/Viewport.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts +31 -4
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/{index-6NvvNj5_.js → index-JzoygiZz.js} +2576 -2430
- package/dist/index-JzoygiZz.js.map +1 -0
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +58 -56
- package/package.json +1 -1
- package/src/components/Button.ts +269 -0
- package/src/components/DisplayObject.ts +21 -2
- package/src/components/Graphic.ts +1 -1
- package/src/components/Sprite.ts +13 -7
- package/src/components/index.ts +2 -1
- package/src/engine/reactive.ts +169 -50
- package/dist/index-6NvvNj5_.js.map +0 -1
package/src/engine/reactive.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
* @
|
|
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
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
618
|
+
currentElement = el;
|
|
515
619
|
subscriber.next({
|
|
516
620
|
type: "init",
|
|
517
621
|
elements: [el],
|
|
518
622
|
});
|
|
519
623
|
});
|
|
520
624
|
} else {
|
|
521
|
-
|
|
625
|
+
currentElement = _el as Element;
|
|
522
626
|
subscriber.next({
|
|
523
627
|
type: "init",
|
|
524
|
-
elements: [
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
}
|