@geektech/tsone 0.0.2 → 0.1.0

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.
@@ -345,38 +345,100 @@ function isReadonly(value) {
345
345
  return isObject(value) && hasReactiveFlag(value, IS_READONLY);
346
346
  }
347
347
 
348
- // lib/core/renderer/props.ts
349
- function isEventProp(key) {
350
- return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
348
+ // lib/core/vnode.ts
349
+ function isComponentNode(vnode) {
350
+ return typeof vnode === "object" && vnode !== null && "component" in vnode;
351
351
  }
352
- function eventNameFromProp(key) {
353
- return key.slice(2).toLowerCase();
352
+ function isHTMLNode(vnode) {
353
+ return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag !== "slot";
354
354
  }
355
- function parseEventName(event) {
356
- const [eventName, ...modifiers] = event.split(".");
357
- return { eventName, modifiers: new Set(modifiers) };
355
+ function isSlotProvider(vnode) {
356
+ return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag === "slot";
358
357
  }
359
- function wrapEventHandler(handler, modifiers) {
360
- const eventHandler = (event) => {
361
- if (modifiers.has("stop")) {
362
- event.stopPropagation();
363
- }
364
- if (modifiers.has("prevent")) {
365
- event.preventDefault();
366
- }
367
- if (modifiers.has("self") && event.currentTarget !== event.target) {
368
- return;
369
- }
370
- if (modifiers.has("once")) {
371
- event.currentTarget.removeEventListener(event.type, eventHandler);
372
- }
373
- handler(event);
358
+ function h(tag, props, children, listeners, key, directions) {
359
+ return {
360
+ tag,
361
+ props,
362
+ children,
363
+ listeners,
364
+ key,
365
+ directions
374
366
  };
375
- return eventHandler;
376
367
  }
377
- function setStyleValue(style, property, value) {
378
- const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
379
- style.setProperty(cssProperty, String(value));
368
+ function Tag(tag, options = {}) {
369
+ return {
370
+ tag,
371
+ ...options
372
+ };
373
+ }
374
+ function createElementFactory(tag) {
375
+ return (options = {}) => Tag(tag, options);
376
+ }
377
+ var Div = createElementFactory("div");
378
+ var Span = createElementFactory("span");
379
+ var P = createElementFactory("p");
380
+ var Button = createElementFactory("button");
381
+ var Input = createElementFactory("input");
382
+ var Section = createElementFactory("section");
383
+ var Main = createElementFactory("main");
384
+ var Header = createElementFactory("header");
385
+ var Footer = createElementFactory("footer");
386
+ var Nav = createElementFactory("nav");
387
+ var Article = createElementFactory("article");
388
+ var Aside = createElementFactory("aside");
389
+ var H1 = createElementFactory("h1");
390
+ var H2 = createElementFactory("h2");
391
+ var H3 = createElementFactory("h3");
392
+ var H4 = createElementFactory("h4");
393
+ var H5 = createElementFactory("h5");
394
+ var H6 = createElementFactory("h6");
395
+ var Strong = createElementFactory("strong");
396
+ var Em = createElementFactory("em");
397
+ var Small = createElementFactory("small");
398
+ var Pre = createElementFactory("pre");
399
+ var Code = createElementFactory("code");
400
+ var Blockquote = createElementFactory("blockquote");
401
+ var Ul = createElementFactory("ul");
402
+ var Ol = createElementFactory("ol");
403
+ var Li = createElementFactory("li");
404
+ var A = createElementFactory("a");
405
+ var Img = createElementFactory("img");
406
+ var Form = createElementFactory("form");
407
+ var Label = createElementFactory("label");
408
+ var Textarea = createElementFactory("textarea");
409
+ var Select = createElementFactory("select");
410
+ var Option = createElementFactory("option");
411
+ var Table = createElementFactory("table");
412
+ var Thead = createElementFactory("thead");
413
+ var Tbody = createElementFactory("tbody");
414
+ var Tr = createElementFactory("tr");
415
+ var Th = createElementFactory("th");
416
+ var Td = createElementFactory("td");
417
+ function createComponent(componentClass, props, children, key, directions) {
418
+ return {
419
+ component: componentClass,
420
+ props,
421
+ children,
422
+ key,
423
+ directions
424
+ };
425
+ }
426
+ function slot(name, key, directions) {
427
+ return {
428
+ tag: "slot",
429
+ props: { name },
430
+ key,
431
+ directions
432
+ };
433
+ }
434
+ function each(items, render, key) {
435
+ return items.map((item, index) => {
436
+ const vnode = render(item, index);
437
+ if (typeof vnode === "string") {
438
+ throw new Error("each render callback must return a VNode");
439
+ }
440
+ return { ...vnode, key: key(item, index) };
441
+ });
380
442
  }
381
443
 
382
444
  // lib/core/model.ts
@@ -560,63 +622,551 @@ class ModelBindingController {
560
622
  }
561
623
  }
562
624
 
563
- // lib/core/vnode.ts
564
- function isComponentNode(vnode) {
565
- return typeof vnode === "object" && vnode !== null && "component" in vnode;
566
- }
567
- function isHTMLNode(vnode) {
568
- return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag !== "slot";
569
- }
570
- function isSlotProvider(vnode) {
571
- return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag === "slot";
625
+ // lib/core/renderer/props.ts
626
+ function isEventProp(key) {
627
+ return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
572
628
  }
573
- function h(tag, props, children, listeners, key, directions) {
574
- return {
575
- tag,
576
- props,
577
- children,
578
- listeners,
579
- key,
580
- directions
581
- };
629
+ function eventNameFromProp(key) {
630
+ return key.slice(2).toLowerCase();
582
631
  }
583
- function createElementFactory(tag) {
584
- return (options = {}) => ({
585
- tag,
586
- ...options
587
- });
632
+ function parseEventName(event) {
633
+ const [eventName, ...modifiers] = event.split(".");
634
+ return { eventName, modifiers: new Set(modifiers) };
588
635
  }
589
- var Div = createElementFactory("div");
590
- var Span = createElementFactory("span");
591
- var P = createElementFactory("p");
592
- var Button = createElementFactory("button");
593
- var Input = createElementFactory("input");
594
- function createComponent(componentClass, props, children, key, directions) {
595
- return {
596
- component: componentClass,
597
- props,
598
- children,
599
- key,
600
- directions
636
+ function wrapEventHandler(handler, modifiers) {
637
+ const eventHandler = (event) => {
638
+ if (modifiers.has("stop")) {
639
+ event.stopPropagation();
640
+ }
641
+ if (modifiers.has("prevent")) {
642
+ event.preventDefault();
643
+ }
644
+ if (modifiers.has("self") && event.currentTarget !== event.target) {
645
+ return;
646
+ }
647
+ if (modifiers.has("once")) {
648
+ event.currentTarget.removeEventListener(event.type, eventHandler);
649
+ }
650
+ handler(event);
601
651
  };
652
+ return eventHandler;
602
653
  }
603
- function slot(name, key, directions) {
604
- return {
605
- tag: "slot",
606
- props: { name },
607
- key,
608
- directions
609
- };
654
+ function setStyleValue(style, property, value) {
655
+ const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
656
+ style.setProperty(cssProperty, String(value));
610
657
  }
611
- function each(items, render, key) {
612
- return items.map((item, index) => {
613
- const vnode = render(item, index);
658
+
659
+ // lib/core/renderer/element-strategy.ts
660
+ class ElementRenderStrategy {
661
+ listeners = new WeakMap;
662
+ effects = new WeakMap;
663
+ modelBindings = new ModelBindingController;
664
+ matches(vnode) {
665
+ return typeof vnode === "object" && vnode !== null && isHTMLNode(vnode);
666
+ }
667
+ mount(vnode, context) {
668
+ if (vnode.directions?.if === false) {
669
+ return document.createComment("if");
670
+ }
671
+ const element = document.createElement(vnode.tag);
672
+ this.applyProps(element, {}, vnode.props ?? {}, context);
673
+ this.updateListeners(element, {}, this.collectListeners(vnode));
674
+ this.mountChildren(element, vnode, context);
675
+ this.applyDirections(element, undefined, vnode.directions, context);
676
+ return element;
677
+ }
678
+ patch(oldVNode, newVNode, currentNode, context) {
679
+ if (oldVNode.tag !== newVNode.tag || currentNode.nodeType === Node.COMMENT_NODE) {
680
+ const nextNode = this.mount(newVNode, context);
681
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
682
+ this.unmount(oldVNode, currentNode, context);
683
+ return nextNode;
684
+ }
685
+ if (!(currentNode instanceof HTMLElement)) {
686
+ return currentNode;
687
+ }
688
+ if (newVNode.directions?.if === false) {
689
+ const nextNode = document.createComment("if");
690
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
691
+ this.unmount(oldVNode, currentNode, context);
692
+ return nextNode;
693
+ }
694
+ this.applyProps(currentNode, oldVNode.props ?? {}, newVNode.props ?? {}, context);
695
+ this.updateListeners(currentNode, this.collectListeners(oldVNode), this.collectListeners(newVNode));
696
+ this.updateChildren(currentNode, oldVNode, newVNode, context);
697
+ this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
698
+ return currentNode;
699
+ }
700
+ unmount(vnode, currentNode, context) {
701
+ if (!(currentNode instanceof HTMLElement)) {
702
+ return;
703
+ }
704
+ this.effects.get(currentNode)?.forEach((item) => stop(item));
705
+ this.effects.delete(currentNode);
706
+ this.listeners.get(currentNode)?.forEach(({ eventName, listener }) => {
707
+ currentNode.removeEventListener(eventName, listener);
708
+ });
709
+ this.listeners.delete(currentNode);
710
+ this.modelBindings.cleanup(currentNode);
711
+ this.unmountChildren(currentNode, vnode, context);
712
+ }
713
+ mountChildren(element, vnode, context) {
714
+ (vnode.children ?? []).forEach((child) => {
715
+ element.appendChild(context.renderer.mount(child, context));
716
+ });
717
+ }
718
+ updateChildren(element, oldVNode, newVNode, context) {
719
+ this.updateOrdinaryChildren(element, oldVNode.children ?? [], newVNode.children ?? [], context);
720
+ }
721
+ unmountChildren(element, vnode, context) {
722
+ (vnode.children ?? []).forEach((child, index) => {
723
+ const childNode = element.childNodes[index];
724
+ if (childNode) {
725
+ context.renderer.unmount(child, childNode, context);
726
+ }
727
+ });
728
+ }
729
+ applyProps(element, oldProps, newProps, context) {
730
+ Object.keys(oldProps).forEach((key) => {
731
+ if (isEventProp(key) || key in newProps) {
732
+ return;
733
+ }
734
+ if (key === "className" || key === "class") {
735
+ element.removeAttribute("class");
736
+ } else if (key === "style") {
737
+ element.removeAttribute("style");
738
+ } else {
739
+ element.removeAttribute(key);
740
+ }
741
+ });
742
+ Object.entries(newProps).forEach(([key, value]) => {
743
+ if (isEventProp(key)) {
744
+ return;
745
+ }
746
+ if (key === "className" || key === "class") {
747
+ element.className = String(value ?? "");
748
+ return;
749
+ }
750
+ if (key === "style" && typeof value === "object" && value !== null) {
751
+ element.removeAttribute("style");
752
+ Object.entries(value).forEach(([cssKey, cssValue]) => {
753
+ setStyleValue(element.style, cssKey, cssValue);
754
+ });
755
+ return;
756
+ }
757
+ if (value === false || value === undefined || value === null) {
758
+ element.removeAttribute(key);
759
+ return;
760
+ }
761
+ if (value === true) {
762
+ element.setAttribute(key, "");
763
+ return;
764
+ }
765
+ if (typeof value === "string" && context.templateEngine.hasExpressions(value)) {
766
+ this.setupReactiveAttribute(element, key, value, context);
767
+ return;
768
+ }
769
+ element.setAttribute(key, String(value));
770
+ });
771
+ }
772
+ applyDirections(element, oldDirections, newDirections, context) {
773
+ if (newDirections && "show" in newDirections) {
774
+ element.style.display = newDirections.show ? "" : "none";
775
+ } else if (oldDirections && "show" in oldDirections) {
776
+ element.style.display = "";
777
+ }
778
+ if (!newDirections?.model) {
779
+ this.modelBindings.cleanup(element);
780
+ return;
781
+ }
782
+ this.modelBindings.bind(element, newDirections.model, context.templateEngine.state);
783
+ }
784
+ updateOrdinaryChildren(element, oldChildren, newChildren, context) {
785
+ this.assertNoDuplicateKeys(oldChildren);
786
+ this.assertNoDuplicateKeys(newChildren);
787
+ if (this.hasOnlyKeyedChildren(oldChildren, newChildren)) {
788
+ this.updateKeyedChildren(element, oldChildren, newChildren, context);
789
+ return;
790
+ }
791
+ const sharedLength = Math.min(oldChildren.length, newChildren.length);
792
+ for (let index = 0;index < sharedLength; index += 1) {
793
+ const childNode = element.childNodes[index];
794
+ if (!childNode) {
795
+ element.appendChild(context.renderer.mount(newChildren[index], context));
796
+ continue;
797
+ }
798
+ context.renderer.patch(oldChildren[index], newChildren[index], childNode, context);
799
+ }
800
+ for (let index = sharedLength;index < newChildren.length; index += 1) {
801
+ element.appendChild(context.renderer.mount(newChildren[index], context));
802
+ }
803
+ for (let index = oldChildren.length - 1;index >= newChildren.length; index -= 1) {
804
+ const childNode = element.childNodes[index];
805
+ if (childNode) {
806
+ context.renderer.unmount(oldChildren[index], childNode, context);
807
+ if (childNode.parentNode === element) {
808
+ element.removeChild(childNode);
809
+ }
810
+ }
811
+ }
812
+ }
813
+ updateKeyedChildren(element, oldChildren, newChildren, context) {
814
+ const oldEntries = oldChildren.map((vnode, index) => ({
815
+ vnode,
816
+ node: element.childNodes[index],
817
+ index
818
+ }));
819
+ const keyedOldEntries = new Map;
820
+ const usedOldIndexes = new Set;
821
+ oldEntries.forEach((entry) => {
822
+ const key = this.getVNodeKey(entry.vnode);
823
+ if (key !== undefined && entry.node) {
824
+ keyedOldEntries.set(key, {
825
+ vnode: entry.vnode,
826
+ node: entry.node,
827
+ index: entry.index
828
+ });
829
+ }
830
+ });
831
+ newChildren.forEach((newChild, newIndex) => {
832
+ const key = this.getVNodeKey(newChild);
833
+ const oldEntry = key === undefined ? undefined : keyedOldEntries.get(key);
834
+ let nextNode;
835
+ if (oldEntry) {
836
+ nextNode = context.renderer.patch(oldEntry.vnode, newChild, oldEntry.node, context);
837
+ usedOldIndexes.add(oldEntry.index);
838
+ } else {
839
+ nextNode = context.renderer.mount(newChild, context);
840
+ }
841
+ const referenceNode = element.childNodes[newIndex] ?? null;
842
+ if (nextNode !== referenceNode) {
843
+ element.insertBefore(nextNode, referenceNode);
844
+ }
845
+ });
846
+ oldEntries.forEach((entry) => {
847
+ if (!entry.node || usedOldIndexes.has(entry.index)) {
848
+ return;
849
+ }
850
+ context.renderer.unmount(entry.vnode, entry.node, context);
851
+ if (entry.node.parentNode === element) {
852
+ element.removeChild(entry.node);
853
+ }
854
+ });
855
+ }
856
+ hasOnlyKeyedChildren(oldChildren, newChildren) {
857
+ return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
858
+ }
859
+ assertNoDuplicateKeys(children) {
860
+ const keys = new Set;
861
+ children.forEach((child) => {
862
+ const key = this.getVNodeKey(child);
863
+ if (key === undefined) {
864
+ return;
865
+ }
866
+ if (keys.has(key)) {
867
+ throw new Error(`Duplicate key "${key}"`);
868
+ }
869
+ keys.add(key);
870
+ });
871
+ }
872
+ getVNodeKey(vnode) {
614
873
  if (typeof vnode === "string") {
615
- throw new Error("each render callback must return a VNode");
874
+ return;
616
875
  }
617
- return { ...vnode, key: key(item, index) };
876
+ return vnode.key;
877
+ }
878
+ collectListeners(vnode) {
879
+ const listeners = {};
880
+ Object.entries(vnode.props ?? {}).forEach(([key, value]) => {
881
+ if (isEventProp(key) && typeof value === "function") {
882
+ listeners[eventNameFromProp(key)] = value;
883
+ }
884
+ });
885
+ return {
886
+ ...listeners,
887
+ ...vnode.listeners ?? {}
888
+ };
889
+ }
890
+ updateListeners(element, oldListeners, newListeners) {
891
+ const store = this.listeners.get(element) ?? new Map;
892
+ const oldKeys = new Set(Object.keys(oldListeners));
893
+ const newKeys = new Set(Object.keys(newListeners));
894
+ oldKeys.forEach((event) => {
895
+ if (!newKeys.has(event) || oldListeners[event] !== newListeners[event]) {
896
+ const stored = store.get(event);
897
+ if (stored) {
898
+ element.removeEventListener(stored.eventName, stored.listener);
899
+ store.delete(event);
900
+ }
901
+ }
902
+ });
903
+ newKeys.forEach((event) => {
904
+ if (!oldKeys.has(event) || oldListeners[event] !== newListeners[event]) {
905
+ const { eventName, modifiers } = parseEventName(event);
906
+ const listener = wrapEventHandler(newListeners[event], modifiers);
907
+ element.addEventListener(eventName, listener);
908
+ store.set(event, { eventName, listener });
909
+ }
910
+ });
911
+ this.listeners.set(element, store);
912
+ }
913
+ setupReactiveAttribute(element, attrName, attrValue, context) {
914
+ const effectRef = effect(() => {
915
+ element.setAttribute(attrName, context.templateEngine.evaluateTemplateValue(attrValue));
916
+ });
917
+ this.trackEffect(element, effectRef);
918
+ }
919
+ trackEffect(element, effectRef) {
920
+ const effects = this.effects.get(element) ?? new Set;
921
+ effects.add(effectRef);
922
+ this.effects.set(element, effects);
923
+ }
924
+ }
925
+
926
+ // lib/core/animation/list-animation-controller.ts
927
+ var ENTER_KEYFRAMES = {
928
+ fade: [{ opacity: 0 }, { opacity: 1 }],
929
+ "slide-up": [
930
+ { opacity: 0, transform: "translateY(12px)" },
931
+ { opacity: 1, transform: "translateY(0)" }
932
+ ],
933
+ "slide-down": [
934
+ { opacity: 0, transform: "translateY(-12px)" },
935
+ { opacity: 1, transform: "translateY(0)" }
936
+ ],
937
+ "slide-left": [
938
+ { opacity: 0, transform: "translateX(12px)" },
939
+ { opacity: 1, transform: "translateX(0)" }
940
+ ],
941
+ "slide-right": [
942
+ { opacity: 0, transform: "translateX(-12px)" },
943
+ { opacity: 1, transform: "translateX(0)" }
944
+ ],
945
+ scale: [
946
+ { opacity: 0, transform: "scale(0.95)" },
947
+ { opacity: 1, transform: "scale(1)" }
948
+ ]
949
+ };
950
+
951
+ class ListAnimationController {
952
+ runs = new WeakMap;
953
+ playEnter(element, options) {
954
+ return this.play(element, options, "enter");
955
+ }
956
+ playExit(element, options) {
957
+ return this.play(element, options, "exit");
958
+ }
959
+ cancel(element) {
960
+ const current = this.runs.get(element);
961
+ if (!current) {
962
+ return;
963
+ }
964
+ this.runs.delete(element);
965
+ current.animation.cancel();
966
+ }
967
+ play(element, transition, phase) {
968
+ this.cancel(element);
969
+ if (!this.canAnimate(element)) {
970
+ return null;
971
+ }
972
+ const enterKeyframes = ENTER_KEYFRAMES[transition.type];
973
+ const keyframes = phase === "enter" ? [...enterKeyframes] : [...enterKeyframes].reverse();
974
+ const options = {
975
+ duration: transition.duration,
976
+ easing: "ease",
977
+ fill: "both"
978
+ };
979
+ const animation = element.animate(keyframes, options);
980
+ const token = Symbol("list-animation");
981
+ const finished = animation.finished.then(() => "finished", () => "cancelled");
982
+ const run = {
983
+ animation,
984
+ token,
985
+ keyframes,
986
+ options,
987
+ finished
988
+ };
989
+ this.runs.set(element, run);
990
+ finished.then((result) => {
991
+ if (this.runs.get(element)?.token !== token) {
992
+ return;
993
+ }
994
+ this.runs.delete(element);
995
+ if (phase === "enter" && result === "finished") {
996
+ animation.cancel();
997
+ }
998
+ });
999
+ return run;
1000
+ }
1001
+ canAnimate(element) {
1002
+ const reduced = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1003
+ return !reduced && typeof element.animate === "function";
1004
+ }
1005
+ }
1006
+
1007
+ // lib/core/animation/types.ts
1008
+ var TRANSITION_ANIMATION_TYPES = [
1009
+ "fade",
1010
+ "slide-up",
1011
+ "slide-down",
1012
+ "slide-left",
1013
+ "slide-right",
1014
+ "scale"
1015
+ ];
1016
+ function normalizeTransitionGroupProps(props) {
1017
+ const tag = (props.tag ?? "div").trim();
1018
+ const type = props.type ?? "fade";
1019
+ const duration = props.duration ?? 300;
1020
+ if (!tag) {
1021
+ throw new Error("TransitionGroup tag must not be empty");
1022
+ }
1023
+ if (!TRANSITION_ANIMATION_TYPES.includes(type)) {
1024
+ throw new Error(`Unknown TransitionGroup animation type "${type}"`);
1025
+ }
1026
+ if (!Number.isFinite(duration) || duration < 0) {
1027
+ throw new Error("TransitionGroup duration must be a non-negative finite number");
1028
+ }
1029
+ return { tag, type, duration };
1030
+ }
1031
+ function validateTransitionGroupChildren(children) {
1032
+ const keys = new Set;
1033
+ return children.map((child) => {
1034
+ if (typeof child === "string" || child.key === undefined || keys.has(child.key)) {
1035
+ throw new Error("TransitionGroup children must have unique keys");
1036
+ }
1037
+ keys.add(child.key);
1038
+ return child;
618
1039
  });
619
1040
  }
1041
+ function isTransitionGroupNode(vnode) {
1042
+ return typeof vnode === "object" && vnode !== null && "transitionGroup" in vnode && isHTMLNode(vnode);
1043
+ }
1044
+
1045
+ // lib/core/animation/transition-group-strategy.ts
1046
+ class TransitionGroupRenderStrategy extends ElementRenderStrategy {
1047
+ entries = new WeakMap;
1048
+ animations = new ListAnimationController;
1049
+ matches(vnode) {
1050
+ return isTransitionGroupNode(vnode);
1051
+ }
1052
+ mountChildren(element, groupVNode, context) {
1053
+ const keyedChildren = validateTransitionGroupChildren(groupVNode.children ?? []);
1054
+ const entries = new Map;
1055
+ keyedChildren.forEach((childVNode) => {
1056
+ const node = context.renderer.mount(childVNode, context);
1057
+ element.appendChild(node);
1058
+ const entry = {
1059
+ key: childVNode.key,
1060
+ vnode: childVNode,
1061
+ node,
1062
+ status: "active"
1063
+ };
1064
+ entries.set(entry.key, entry);
1065
+ this.playEnter(entry, node, groupVNode.transitionGroup);
1066
+ });
1067
+ this.entries.set(element, entries);
1068
+ }
1069
+ updateChildren(element, _oldVNode, newVNode, context) {
1070
+ const entries = this.entries.get(element) ?? new Map;
1071
+ const nextChildren = validateTransitionGroupChildren(newVNode.children ?? []);
1072
+ const nextKeys = new Set(nextChildren.map((child) => child.key));
1073
+ const ordered = [];
1074
+ nextChildren.forEach((childVNode) => {
1075
+ const key = childVNode.key;
1076
+ const current = entries.get(key);
1077
+ if (current) {
1078
+ const wasExiting = current.status === "exiting";
1079
+ if (wasExiting && current.node instanceof HTMLElement) {
1080
+ this.animations.cancel(current.node);
1081
+ }
1082
+ current.status = "active";
1083
+ current.animationToken = undefined;
1084
+ current.node = context.renderer.patch(current.vnode, childVNode, current.node, context);
1085
+ current.vnode = childVNode;
1086
+ ordered.push(current);
1087
+ if (wasExiting) {
1088
+ this.playEnter(current, current.node, newVNode.transitionGroup);
1089
+ }
1090
+ return;
1091
+ }
1092
+ const node = context.renderer.mount(childVNode, context);
1093
+ const entry = {
1094
+ key,
1095
+ vnode: childVNode,
1096
+ node,
1097
+ status: "active"
1098
+ };
1099
+ entries.set(key, entry);
1100
+ ordered.push(entry);
1101
+ this.playEnter(entry, node, newVNode.transitionGroup);
1102
+ });
1103
+ entries.forEach((entry, key) => {
1104
+ if (nextKeys.has(key) || entry.status !== "active") {
1105
+ return;
1106
+ }
1107
+ this.startExit(element, entry, newVNode.transitionGroup, context);
1108
+ });
1109
+ this.placeActiveEntries(element, ordered);
1110
+ this.entries.set(element, entries);
1111
+ }
1112
+ unmountChildren(element, vnode, context) {
1113
+ const entries = this.entries.get(element);
1114
+ if (!entries) {
1115
+ super.unmountChildren(element, vnode, context);
1116
+ return;
1117
+ }
1118
+ entries.forEach((entry) => {
1119
+ if (entry.node instanceof HTMLElement) {
1120
+ this.animations.cancel(entry.node);
1121
+ }
1122
+ context.renderer.unmount(entry.vnode, entry.node, context);
1123
+ if (entry.node.parentNode === element) {
1124
+ element.removeChild(entry.node);
1125
+ }
1126
+ });
1127
+ entries.clear();
1128
+ this.entries.delete(element);
1129
+ }
1130
+ playEnter(entry, node, options) {
1131
+ if (!(node instanceof HTMLElement)) {
1132
+ return;
1133
+ }
1134
+ const run = this.animations.playEnter(node, options);
1135
+ entry.animationToken = run?.token;
1136
+ }
1137
+ startExit(wrapper, entry, options, context) {
1138
+ entry.status = "exiting";
1139
+ const run = entry.node instanceof HTMLElement ? this.animations.playExit(entry.node, options) : null;
1140
+ if (!run) {
1141
+ this.finishExit(wrapper, entry, context);
1142
+ return;
1143
+ }
1144
+ entry.animationToken = run.token;
1145
+ run.finished.then((result) => {
1146
+ if (result === "finished" && entry.status === "exiting" && entry.animationToken === run.token) {
1147
+ this.finishExit(wrapper, entry, context);
1148
+ }
1149
+ });
1150
+ }
1151
+ finishExit(wrapper, entry, context) {
1152
+ const entries = this.entries.get(wrapper);
1153
+ if (entries?.get(entry.key) !== entry) {
1154
+ return;
1155
+ }
1156
+ context.renderer.unmount(entry.vnode, entry.node, context);
1157
+ if (entry.node.parentNode === wrapper) {
1158
+ wrapper.removeChild(entry.node);
1159
+ }
1160
+ entries.delete(entry.key);
1161
+ }
1162
+ placeActiveEntries(wrapper, ordered) {
1163
+ let reference = null;
1164
+ for (let index = ordered.length - 1;index >= 0; index -= 1) {
1165
+ wrapper.insertBefore(ordered[index].node, reference);
1166
+ reference = ordered[index].node;
1167
+ }
1168
+ }
1169
+ }
620
1170
 
621
1171
  // lib/core/renderer.ts
622
1172
  class RendererContext {
@@ -626,6 +1176,7 @@ class RendererContext {
626
1176
  new TextRenderStrategy,
627
1177
  new ComponentRenderStrategy,
628
1178
  new SlotRenderStrategy,
1179
+ new TransitionGroupRenderStrategy,
629
1180
  new ElementRenderStrategy
630
1181
  ];
631
1182
  }
@@ -843,263 +1394,6 @@ class SlotRenderStrategy {
843
1394
  }
844
1395
  }
845
1396
 
846
- class ElementRenderStrategy {
847
- listeners = new WeakMap;
848
- effects = new WeakMap;
849
- modelBindings = new ModelBindingController;
850
- matches(vnode) {
851
- return typeof vnode === "object" && vnode !== null && isHTMLNode(vnode);
852
- }
853
- mount(vnode, context) {
854
- if (vnode.directions?.if === false) {
855
- return document.createComment("if");
856
- }
857
- const element = document.createElement(vnode.tag);
858
- this.applyProps(element, {}, vnode.props ?? {}, context);
859
- this.updateListeners(element, {}, this.collectListeners(vnode));
860
- (vnode.children ?? []).forEach((child) => {
861
- element.appendChild(context.renderer.mount(child, context));
862
- });
863
- this.applyDirections(element, undefined, vnode.directions, context);
864
- return element;
865
- }
866
- patch(oldVNode, newVNode, currentNode, context) {
867
- if (oldVNode.tag !== newVNode.tag || currentNode.nodeType === Node.COMMENT_NODE) {
868
- const nextNode = this.mount(newVNode, context);
869
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
870
- this.unmount(oldVNode, currentNode, context);
871
- return nextNode;
872
- }
873
- if (!(currentNode instanceof HTMLElement)) {
874
- return currentNode;
875
- }
876
- if (newVNode.directions?.if === false) {
877
- const nextNode = document.createComment("if");
878
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
879
- this.unmount(oldVNode, currentNode, context);
880
- return nextNode;
881
- }
882
- this.applyProps(currentNode, oldVNode.props ?? {}, newVNode.props ?? {}, context);
883
- this.updateListeners(currentNode, this.collectListeners(oldVNode), this.collectListeners(newVNode));
884
- this.updateChildren(currentNode, oldVNode.children ?? [], newVNode.children ?? [], context);
885
- this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
886
- return currentNode;
887
- }
888
- unmount(vnode, currentNode, context) {
889
- if (!(currentNode instanceof HTMLElement)) {
890
- return;
891
- }
892
- this.effects.get(currentNode)?.forEach((item) => stop(item));
893
- this.effects.delete(currentNode);
894
- this.listeners.get(currentNode)?.forEach(({ eventName, listener }) => {
895
- currentNode.removeEventListener(eventName, listener);
896
- });
897
- this.listeners.delete(currentNode);
898
- this.modelBindings.cleanup(currentNode);
899
- (vnode.children ?? []).forEach((child, index) => {
900
- const childNode = currentNode.childNodes[index];
901
- if (childNode) {
902
- context.renderer.unmount(child, childNode, context);
903
- }
904
- });
905
- }
906
- applyProps(element, oldProps, newProps, context) {
907
- Object.keys(oldProps).forEach((key) => {
908
- if (isEventProp(key) || key in newProps) {
909
- return;
910
- }
911
- if (key === "className" || key === "class") {
912
- element.removeAttribute("class");
913
- } else if (key === "style") {
914
- element.removeAttribute("style");
915
- } else {
916
- element.removeAttribute(key);
917
- }
918
- });
919
- Object.entries(newProps).forEach(([key, value]) => {
920
- if (isEventProp(key)) {
921
- return;
922
- }
923
- if (key === "className" || key === "class") {
924
- element.className = String(value ?? "");
925
- return;
926
- }
927
- if (key === "style" && typeof value === "object" && value !== null) {
928
- element.removeAttribute("style");
929
- Object.entries(value).forEach(([cssKey, cssValue]) => {
930
- setStyleValue(element.style, cssKey, cssValue);
931
- });
932
- return;
933
- }
934
- if (value === false || value === undefined || value === null) {
935
- element.removeAttribute(key);
936
- return;
937
- }
938
- if (value === true) {
939
- element.setAttribute(key, "");
940
- return;
941
- }
942
- if (typeof value === "string" && context.templateEngine.hasExpressions(value)) {
943
- this.setupReactiveAttribute(element, key, value, context);
944
- return;
945
- }
946
- element.setAttribute(key, String(value));
947
- });
948
- }
949
- applyDirections(element, oldDirections, newDirections, context) {
950
- if (newDirections && "show" in newDirections) {
951
- element.style.display = newDirections.show ? "" : "none";
952
- } else if (oldDirections && "show" in oldDirections) {
953
- element.style.display = "";
954
- }
955
- if (!newDirections?.model) {
956
- this.modelBindings.cleanup(element);
957
- return;
958
- }
959
- this.modelBindings.bind(element, newDirections.model, context.templateEngine.state);
960
- }
961
- updateChildren(element, oldChildren, newChildren, context) {
962
- this.assertNoDuplicateKeys(oldChildren);
963
- this.assertNoDuplicateKeys(newChildren);
964
- if (this.hasOnlyKeyedChildren(oldChildren, newChildren)) {
965
- this.updateKeyedChildren(element, oldChildren, newChildren, context);
966
- return;
967
- }
968
- const sharedLength = Math.min(oldChildren.length, newChildren.length);
969
- for (let index = 0;index < sharedLength; index += 1) {
970
- const childNode = element.childNodes[index];
971
- if (!childNode) {
972
- element.appendChild(context.renderer.mount(newChildren[index], context));
973
- continue;
974
- }
975
- context.renderer.patch(oldChildren[index], newChildren[index], childNode, context);
976
- }
977
- for (let index = sharedLength;index < newChildren.length; index += 1) {
978
- element.appendChild(context.renderer.mount(newChildren[index], context));
979
- }
980
- for (let index = oldChildren.length - 1;index >= newChildren.length; index -= 1) {
981
- const childNode = element.childNodes[index];
982
- if (childNode) {
983
- context.renderer.unmount(oldChildren[index], childNode, context);
984
- if (childNode.parentNode === element) {
985
- element.removeChild(childNode);
986
- }
987
- }
988
- }
989
- }
990
- updateKeyedChildren(element, oldChildren, newChildren, context) {
991
- const oldEntries = oldChildren.map((vnode, index) => ({
992
- vnode,
993
- node: element.childNodes[index],
994
- index
995
- }));
996
- const keyedOldEntries = new Map;
997
- const usedOldIndexes = new Set;
998
- oldEntries.forEach((entry) => {
999
- const key = this.getVNodeKey(entry.vnode);
1000
- if (key !== undefined && entry.node) {
1001
- keyedOldEntries.set(key, {
1002
- vnode: entry.vnode,
1003
- node: entry.node,
1004
- index: entry.index
1005
- });
1006
- }
1007
- });
1008
- newChildren.forEach((newChild, newIndex) => {
1009
- const key = this.getVNodeKey(newChild);
1010
- const oldEntry = key === undefined ? undefined : keyedOldEntries.get(key);
1011
- let nextNode;
1012
- if (oldEntry) {
1013
- nextNode = context.renderer.patch(oldEntry.vnode, newChild, oldEntry.node, context);
1014
- usedOldIndexes.add(oldEntry.index);
1015
- } else {
1016
- nextNode = context.renderer.mount(newChild, context);
1017
- }
1018
- const referenceNode = element.childNodes[newIndex] ?? null;
1019
- if (nextNode !== referenceNode) {
1020
- element.insertBefore(nextNode, referenceNode);
1021
- }
1022
- });
1023
- oldEntries.forEach((entry) => {
1024
- if (!entry.node || usedOldIndexes.has(entry.index)) {
1025
- return;
1026
- }
1027
- context.renderer.unmount(entry.vnode, entry.node, context);
1028
- if (entry.node.parentNode === element) {
1029
- element.removeChild(entry.node);
1030
- }
1031
- });
1032
- }
1033
- hasOnlyKeyedChildren(oldChildren, newChildren) {
1034
- return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
1035
- }
1036
- assertNoDuplicateKeys(children) {
1037
- const keys = new Set;
1038
- children.forEach((child) => {
1039
- const key = this.getVNodeKey(child);
1040
- if (key === undefined) {
1041
- return;
1042
- }
1043
- if (keys.has(key)) {
1044
- throw new Error(`Duplicate key "${key}"`);
1045
- }
1046
- keys.add(key);
1047
- });
1048
- }
1049
- getVNodeKey(vnode) {
1050
- if (typeof vnode === "string") {
1051
- return;
1052
- }
1053
- return vnode.key;
1054
- }
1055
- collectListeners(vnode) {
1056
- const listeners = {};
1057
- Object.entries(vnode.props ?? {}).forEach(([key, value]) => {
1058
- if (isEventProp(key) && typeof value === "function") {
1059
- listeners[eventNameFromProp(key)] = value;
1060
- }
1061
- });
1062
- return {
1063
- ...listeners,
1064
- ...vnode.listeners ?? {}
1065
- };
1066
- }
1067
- updateListeners(element, oldListeners, newListeners) {
1068
- const store = this.listeners.get(element) ?? new Map;
1069
- const oldKeys = new Set(Object.keys(oldListeners));
1070
- const newKeys = new Set(Object.keys(newListeners));
1071
- oldKeys.forEach((event) => {
1072
- if (!newKeys.has(event) || oldListeners[event] !== newListeners[event]) {
1073
- const stored = store.get(event);
1074
- if (stored) {
1075
- element.removeEventListener(stored.eventName, stored.listener);
1076
- store.delete(event);
1077
- }
1078
- }
1079
- });
1080
- newKeys.forEach((event) => {
1081
- if (!oldKeys.has(event) || oldListeners[event] !== newListeners[event]) {
1082
- const { eventName, modifiers } = parseEventName(event);
1083
- const listener = wrapEventHandler(newListeners[event], modifiers);
1084
- element.addEventListener(eventName, listener);
1085
- store.set(event, { eventName, listener });
1086
- }
1087
- });
1088
- this.listeners.set(element, store);
1089
- }
1090
- setupReactiveAttribute(element, attrName, attrValue, context) {
1091
- const effectRef = effect(() => {
1092
- element.setAttribute(attrName, context.templateEngine.evaluateTemplateValue(attrValue));
1093
- });
1094
- this.trackEffect(element, effectRef);
1095
- }
1096
- trackEffect(element, effectRef) {
1097
- const effects = this.effects.get(element) ?? new Set;
1098
- effects.add(effectRef);
1099
- this.effects.set(element, effects);
1100
- }
1101
- }
1102
-
1103
1397
  // lib/core/template.ts
1104
1398
  class TemplateEngine {
1105
1399
  state;
@@ -1812,7 +2106,7 @@ function createRouter(options) {
1812
2106
  return new Router(options);
1813
2107
  }
1814
2108
 
1815
- export { ReactiveSystem, reactive, readonly, effect, computed, ref, isRef, unref, stop, isReactive, isReadonly, TemplateEngine, modelPath, getModelValue, setModelValue, ModelBindingController, isComponentNode, isHTMLNode, isSlotProvider, h, Div, Span, P, Button, Input, createComponent, slot, each, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, ElementRenderStrategy, Component, useRouter, Router, RouterLink, RouterView, createRouter };
2109
+ export { isComponentNode, isHTMLNode, isSlotProvider, h, Tag, Div, Span, P, Button, Input, Section, Main, Header, Footer, Nav, Article, Aside, H1, H2, H3, H4, H5, H6, Strong, Em, Small, Pre, Code, Blockquote, Ul, Ol, Li, A, Img, Form, Label, Textarea, Select, Option, Table, Thead, Tbody, Tr, Th, Td, createComponent, slot, each, ReactiveSystem, reactive, readonly, effect, computed, ref, isRef, unref, stop, isReactive, isReadonly, modelPath, getModelValue, setModelValue, ModelBindingController, ElementRenderStrategy, normalizeTransitionGroupProps, validateTransitionGroupChildren, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, TemplateEngine, Component, useRouter, Router, RouterLink, RouterView, createRouter };
1816
2110
 
1817
- //# debugId=EA20D7B3780F196864756E2164756E21
1818
- //# sourceMappingURL=index-ycmc7ga1.js.map
2111
+ //# debugId=91AF0EEFB67A584364756E2164756E21
2112
+ //# sourceMappingURL=index-wv9gyjqt.js.map