@geektech/tsone 0.0.2 → 0.2.1

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
- StyleManager
3
- } from "./index-8wjswsye.js";
2
+ StyleManager,
3
+ renderStyleSheet
4
+ } from "./index-ffzday7h.js";
4
5
 
5
6
  // lib/core/reactive/types.ts
6
7
  var IS_REACTIVE = Symbol("is_reactive");
@@ -345,38 +346,100 @@ function isReadonly(value) {
345
346
  return isObject(value) && hasReactiveFlag(value, IS_READONLY);
346
347
  }
347
348
 
348
- // lib/core/renderer/props.ts
349
- function isEventProp(key) {
350
- return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
349
+ // lib/core/vnode.ts
350
+ function isComponentNode(vnode) {
351
+ return typeof vnode === "object" && vnode !== null && "component" in vnode;
351
352
  }
352
- function eventNameFromProp(key) {
353
- return key.slice(2).toLowerCase();
353
+ function isHTMLNode(vnode) {
354
+ return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag !== "slot";
354
355
  }
355
- function parseEventName(event) {
356
- const [eventName, ...modifiers] = event.split(".");
357
- return { eventName, modifiers: new Set(modifiers) };
356
+ function isSlotProvider(vnode) {
357
+ return typeof vnode === "object" && vnode !== null && "tag" in vnode && vnode.tag === "slot";
358
358
  }
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);
359
+ function h(tag, props, children, listeners, key, directions) {
360
+ return {
361
+ tag,
362
+ props,
363
+ children,
364
+ listeners,
365
+ key,
366
+ directions
374
367
  };
375
- return eventHandler;
376
368
  }
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));
369
+ function Tag(tag, options = {}) {
370
+ return {
371
+ tag,
372
+ ...options
373
+ };
374
+ }
375
+ function createElementFactory(tag) {
376
+ return (options = {}) => Tag(tag, options);
377
+ }
378
+ var Div = createElementFactory("div");
379
+ var Span = createElementFactory("span");
380
+ var P = createElementFactory("p");
381
+ var Button = createElementFactory("button");
382
+ var Input = createElementFactory("input");
383
+ var Section = createElementFactory("section");
384
+ var Main = createElementFactory("main");
385
+ var Header = createElementFactory("header");
386
+ var Footer = createElementFactory("footer");
387
+ var Nav = createElementFactory("nav");
388
+ var Article = createElementFactory("article");
389
+ var Aside = createElementFactory("aside");
390
+ var H1 = createElementFactory("h1");
391
+ var H2 = createElementFactory("h2");
392
+ var H3 = createElementFactory("h3");
393
+ var H4 = createElementFactory("h4");
394
+ var H5 = createElementFactory("h5");
395
+ var H6 = createElementFactory("h6");
396
+ var Strong = createElementFactory("strong");
397
+ var Em = createElementFactory("em");
398
+ var Small = createElementFactory("small");
399
+ var Pre = createElementFactory("pre");
400
+ var Code = createElementFactory("code");
401
+ var Blockquote = createElementFactory("blockquote");
402
+ var Ul = createElementFactory("ul");
403
+ var Ol = createElementFactory("ol");
404
+ var Li = createElementFactory("li");
405
+ var A = createElementFactory("a");
406
+ var Img = createElementFactory("img");
407
+ var Form = createElementFactory("form");
408
+ var Label = createElementFactory("label");
409
+ var Textarea = createElementFactory("textarea");
410
+ var Select = createElementFactory("select");
411
+ var Option = createElementFactory("option");
412
+ var Table = createElementFactory("table");
413
+ var Thead = createElementFactory("thead");
414
+ var Tbody = createElementFactory("tbody");
415
+ var Tr = createElementFactory("tr");
416
+ var Th = createElementFactory("th");
417
+ var Td = createElementFactory("td");
418
+ function createComponent(componentClass, props, children, key, directions) {
419
+ return {
420
+ component: componentClass,
421
+ props,
422
+ children,
423
+ key,
424
+ directions
425
+ };
426
+ }
427
+ function slot(name, key, directions) {
428
+ return {
429
+ tag: "slot",
430
+ props: { name },
431
+ key,
432
+ directions
433
+ };
434
+ }
435
+ function each(items, render, key) {
436
+ return items.map((item, index) => {
437
+ const vnode = render(item, index);
438
+ if (typeof vnode === "string") {
439
+ throw new Error("each render callback must return a VNode");
440
+ }
441
+ return { ...vnode, key: key(item, index) };
442
+ });
380
443
  }
381
444
 
382
445
  // lib/core/model.ts
@@ -560,328 +623,78 @@ class ModelBindingController {
560
623
  }
561
624
  }
562
625
 
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";
572
- }
573
- function h(tag, props, children, listeners, key, directions) {
574
- return {
575
- tag,
576
- props,
577
- children,
578
- listeners,
579
- key,
580
- directions
581
- };
582
- }
583
- function createElementFactory(tag) {
584
- return (options = {}) => ({
585
- tag,
586
- ...options
587
- });
626
+ // lib/core/renderer/props.ts
627
+ function isEventProp(key) {
628
+ return /^on[A-Z]/.test(key) || /^on[a-z]/.test(key);
588
629
  }
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
601
- };
630
+ function eventNameFromProp(key) {
631
+ return key.slice(2).toLowerCase();
602
632
  }
603
- function slot(name, key, directions) {
604
- return {
605
- tag: "slot",
606
- props: { name },
607
- key,
608
- directions
609
- };
633
+ function parseEventName(event) {
634
+ const [eventName, ...modifiers] = event.split(".");
635
+ return { eventName, modifiers: new Set(modifiers) };
610
636
  }
611
- function each(items, render, key) {
612
- return items.map((item, index) => {
613
- const vnode = render(item, index);
614
- if (typeof vnode === "string") {
615
- throw new Error("each render callback must return a VNode");
637
+ function wrapEventHandler(handler, modifiers) {
638
+ const eventHandler = (event) => {
639
+ if (modifiers.has("stop")) {
640
+ event.stopPropagation();
616
641
  }
617
- return { ...vnode, key: key(item, index) };
618
- });
619
- }
620
-
621
- // lib/core/renderer.ts
622
- class RendererContext {
623
- strategies;
624
- constructor() {
625
- this.strategies = [
626
- new TextRenderStrategy,
627
- new ComponentRenderStrategy,
628
- new SlotRenderStrategy,
629
- new ElementRenderStrategy
630
- ];
631
- }
632
- mount(vnode, context) {
633
- return this.findStrategy(vnode).mount(vnode, context);
634
- }
635
- patch(oldVNode, newVNode, currentNode, context) {
636
- const oldStrategy = this.findStrategy(oldVNode);
637
- const newStrategy = this.findStrategy(newVNode);
638
- if (oldStrategy !== newStrategy) {
639
- const nextNode = newStrategy.mount(newVNode, context);
640
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
641
- oldStrategy.unmount(oldVNode, currentNode, context);
642
- return nextNode;
642
+ if (modifiers.has("prevent")) {
643
+ event.preventDefault();
643
644
  }
644
- return oldStrategy.patch(oldVNode, newVNode, currentNode, context);
645
- }
646
- unmount(vnode, currentNode, context) {
647
- this.findStrategy(vnode).unmount(vnode, currentNode, context);
648
- }
649
- findStrategy(vnode) {
650
- const strategy = this.strategies.find((item) => item.matches(vnode));
651
- if (!strategy) {
652
- throw new Error("No render strategy found for vnode");
645
+ if (modifiers.has("self") && event.currentTarget !== event.target) {
646
+ return;
653
647
  }
654
- return strategy;
655
- }
656
- }
657
-
658
- class TextRenderStrategy {
659
- matches(vnode) {
660
- return typeof vnode === "string";
661
- }
662
- mount(vnode, context) {
663
- return context.templateEngine.parseTemplate(vnode);
664
- }
665
- patch(oldVNode, newVNode, currentNode, context) {
666
- if (oldVNode === newVNode) {
667
- return currentNode;
648
+ if (modifiers.has("once")) {
649
+ event.currentTarget.removeEventListener(event.type, eventHandler);
668
650
  }
669
- const nextNode = this.mount(newVNode, context);
670
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
671
- return nextNode;
672
- }
673
- unmount() {}
651
+ handler(event);
652
+ };
653
+ return eventHandler;
654
+ }
655
+ function setStyleValue(style, property, value) {
656
+ const cssProperty = property.includes("-") ? property : property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
657
+ style.setProperty(cssProperty, String(value));
674
658
  }
675
659
 
676
- class ComponentRenderStrategy {
677
- instances = new WeakMap;
678
- instanceNodes = new Map;
679
- emitterUnsubscribers = new WeakMap;
660
+ // lib/core/renderer/element-strategy.ts
661
+ class ElementRenderStrategy {
662
+ listeners = new WeakMap;
663
+ effects = new WeakMap;
664
+ modelBindings = new ModelBindingController;
680
665
  matches(vnode) {
681
- return typeof vnode === "object" && vnode !== null && isComponentNode(vnode);
666
+ return typeof vnode === "object" && vnode !== null && isHTMLNode(vnode);
682
667
  }
683
668
  mount(vnode, context) {
684
669
  if (vnode.directions?.if === false) {
685
670
  return document.createComment("if");
686
671
  }
687
- const ComponentClass = vnode.component;
688
- const instance = new ComponentClass(this.createProps(vnode));
689
- if (context.appContext && instance.setAppContext) {
690
- instance.setAppContext(context.appContext);
691
- }
692
- this.syncEmitters(instance, vnode.emitters ?? {});
693
- context.registerChild(instance);
694
- const node = instance.mountToNode();
695
- this.trackInstanceNode(instance, node);
696
- instance.setElementChangeListener?.((previousNode, nextNode) => {
697
- this.trackInstanceNode(instance, previousNode);
698
- this.trackInstanceNode(instance, nextNode);
699
- });
700
- return node;
672
+ const element = document.createElement(vnode.tag);
673
+ this.applyProps(element, {}, vnode.props ?? {}, context);
674
+ this.updateListeners(element, {}, this.collectListeners(vnode));
675
+ this.mountChildren(element, vnode, context);
676
+ this.applyDirections(element, undefined, vnode.directions, context);
677
+ return element;
701
678
  }
702
679
  patch(oldVNode, newVNode, currentNode, context) {
703
- if (currentNode.nodeType === Node.COMMENT_NODE) {
704
- const nextNode2 = this.mount(newVNode, context);
705
- currentNode.parentNode?.replaceChild(nextNode2, currentNode);
706
- return nextNode2;
680
+ if (oldVNode.tag !== newVNode.tag || currentNode.nodeType === Node.COMMENT_NODE) {
681
+ const nextNode = this.mount(newVNode, context);
682
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
683
+ this.unmount(oldVNode, currentNode, context);
684
+ return nextNode;
685
+ }
686
+ if (!(currentNode instanceof HTMLElement)) {
687
+ return currentNode;
707
688
  }
708
689
  if (newVNode.directions?.if === false) {
709
- const nextNode2 = document.createComment("if");
710
- currentNode.parentNode?.replaceChild(nextNode2, currentNode);
690
+ const nextNode = document.createComment("if");
691
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
711
692
  this.unmount(oldVNode, currentNode, context);
712
- return nextNode2;
713
- }
714
- const instance = this.instances.get(currentNode);
715
- if (instance && oldVNode.component === newVNode.component) {
716
- this.syncEmitters(instance, newVNode.emitters ?? {});
717
- instance.setProps(this.createProps(newVNode));
718
- const nextNode2 = instance.getElement() ?? currentNode;
719
- this.trackInstanceNode(instance, nextNode2);
720
- return nextNode2;
721
- }
722
- const nextNode = this.mount(newVNode, context);
723
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
724
- this.unmount(oldVNode, currentNode, context);
725
- return nextNode;
726
- }
727
- unmount(_vnode, currentNode, context) {
728
- const instance = this.instances.get(currentNode);
729
- if (instance) {
730
- this.clearEmitters(instance);
731
- instance.unmount();
732
- this.clearInstanceNodes(instance);
733
- context.unregisterChild(instance);
734
- }
735
- }
736
- createProps(vnode) {
737
- return {
738
- ...vnode.props ?? {},
739
- children: vnode.children ?? []
740
- };
741
- }
742
- syncEmitters(instance, emitters) {
743
- const current = this.emitterUnsubscribers.get(instance) ?? new Map;
744
- current.forEach(({ listener: currentListener, unsubscribe }, eventName) => {
745
- const listener = emitters[eventName];
746
- if (!listener || listener !== currentListener) {
747
- unsubscribe();
748
- current.delete(eventName);
749
- }
750
- });
751
- Object.entries(emitters).forEach(([eventName, listener]) => {
752
- if (current.get(eventName)?.listener === listener) {
753
- return;
754
- }
755
- current.set(eventName, {
756
- listener,
757
- unsubscribe: instance.on(eventName, listener)
758
- });
759
- });
760
- this.emitterUnsubscribers.set(instance, current);
761
- }
762
- clearEmitters(instance) {
763
- this.emitterUnsubscribers.get(instance)?.forEach(({ unsubscribe }) => {
764
- unsubscribe();
765
- });
766
- this.emitterUnsubscribers.delete(instance);
767
- }
768
- trackInstanceNode(instance, node) {
769
- this.instances.set(node, instance);
770
- const nodes = this.instanceNodes.get(instance) ?? new Set;
771
- nodes.add(node);
772
- this.instanceNodes.set(instance, nodes);
773
- }
774
- clearInstanceNodes(instance) {
775
- this.instanceNodes.get(instance)?.forEach((node) => {
776
- this.instances.delete(node);
777
- });
778
- this.instanceNodes.delete(instance);
779
- }
780
- }
781
-
782
- class SlotRenderStrategy {
783
- renderedChildren = new WeakMap;
784
- matches(vnode) {
785
- return typeof vnode === "object" && vnode !== null && isSlotProvider(vnode);
786
- }
787
- mount(vnode, context) {
788
- if (vnode.directions?.if === false) {
789
- return document.createComment("if");
790
- }
791
- const slotContainer = document.createElement("div");
792
- slotContainer.setAttribute("data-slot", vnode.props.name);
793
- this.mountSlotChildren(slotContainer, this.resolveChildren(vnode, context), context);
794
- return slotContainer;
795
- }
796
- patch(oldVNode, newVNode, currentNode, context) {
797
- if (currentNode.nodeType === Node.COMMENT_NODE) {
798
- const nextNode = this.mount(newVNode, context);
799
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
800
- return nextNode;
801
- }
802
- if (newVNode.directions?.if === false) {
803
- const nextNode = document.createComment("if");
804
- currentNode.parentNode?.replaceChild(nextNode, currentNode);
805
- this.unmount(oldVNode, currentNode, context);
806
- return nextNode;
807
- }
808
- if (currentNode instanceof HTMLElement) {
809
- currentNode.setAttribute("data-slot", newVNode.props.name);
810
- this.replaceSlotChildren(currentNode, oldVNode, newVNode, context);
811
- }
812
- return currentNode;
813
- }
814
- unmount(_vnode, currentNode, context) {
815
- if (!(currentNode instanceof HTMLElement)) {
816
- return;
817
- }
818
- this.unmountSlotChildren(currentNode, context);
819
- this.renderedChildren.delete(currentNode);
820
- }
821
- resolveChildren(vnode, context) {
822
- return context.slots[vnode.props.name] ?? vnode.children ?? [];
823
- }
824
- replaceSlotChildren(element, _oldVNode, newVNode, context) {
825
- this.unmountSlotChildren(element, context);
826
- element.textContent = "";
827
- this.mountSlotChildren(element, this.resolveChildren(newVNode, context), context);
828
- }
829
- mountSlotChildren(element, children, context) {
830
- children.forEach((child) => {
831
- element.appendChild(context.renderer.mount(child, context));
832
- });
833
- this.renderedChildren.set(element, children);
834
- }
835
- unmountSlotChildren(element, context) {
836
- const children = this.renderedChildren.get(element) ?? [];
837
- children.forEach((child, index) => {
838
- const childNode = element.childNodes[index];
839
- if (childNode) {
840
- context.renderer.unmount(child, childNode, context);
841
- }
842
- });
843
- }
844
- }
845
-
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;
693
+ return nextNode;
881
694
  }
882
695
  this.applyProps(currentNode, oldVNode.props ?? {}, newVNode.props ?? {}, context);
883
696
  this.updateListeners(currentNode, this.collectListeners(oldVNode), this.collectListeners(newVNode));
884
- this.updateChildren(currentNode, oldVNode.children ?? [], newVNode.children ?? [], context);
697
+ this.updateChildren(currentNode, oldVNode, newVNode, context);
885
698
  this.applyDirections(currentNode, oldVNode.directions, newVNode.directions, context);
886
699
  return currentNode;
887
700
  }
@@ -896,8 +709,19 @@ class ElementRenderStrategy {
896
709
  });
897
710
  this.listeners.delete(currentNode);
898
711
  this.modelBindings.cleanup(currentNode);
712
+ this.unmountChildren(currentNode, vnode, context);
713
+ }
714
+ mountChildren(element, vnode, context) {
715
+ (vnode.children ?? []).forEach((child) => {
716
+ element.appendChild(context.renderer.mount(child, context));
717
+ });
718
+ }
719
+ updateChildren(element, oldVNode, newVNode, context) {
720
+ this.updateOrdinaryChildren(element, oldVNode.children ?? [], newVNode.children ?? [], context);
721
+ }
722
+ unmountChildren(element, vnode, context) {
899
723
  (vnode.children ?? []).forEach((child, index) => {
900
- const childNode = currentNode.childNodes[index];
724
+ const childNode = element.childNodes[index];
901
725
  if (childNode) {
902
726
  context.renderer.unmount(child, childNode, context);
903
727
  }
@@ -958,7 +782,7 @@ class ElementRenderStrategy {
958
782
  }
959
783
  this.modelBindings.bind(element, newDirections.model, context.templateEngine.state);
960
784
  }
961
- updateChildren(element, oldChildren, newChildren, context) {
785
+ updateOrdinaryChildren(element, oldChildren, newChildren, context) {
962
786
  this.assertNoDuplicateKeys(oldChildren);
963
787
  this.assertNoDuplicateKeys(newChildren);
964
788
  if (this.hasOnlyKeyedChildren(oldChildren, newChildren)) {
@@ -1020,83 +844,554 @@ class ElementRenderStrategy {
1020
844
  element.insertBefore(nextNode, referenceNode);
1021
845
  }
1022
846
  });
1023
- oldEntries.forEach((entry) => {
1024
- if (!entry.node || usedOldIndexes.has(entry.index)) {
847
+ oldEntries.forEach((entry) => {
848
+ if (!entry.node || usedOldIndexes.has(entry.index)) {
849
+ return;
850
+ }
851
+ context.renderer.unmount(entry.vnode, entry.node, context);
852
+ if (entry.node.parentNode === element) {
853
+ element.removeChild(entry.node);
854
+ }
855
+ });
856
+ }
857
+ hasOnlyKeyedChildren(oldChildren, newChildren) {
858
+ return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
859
+ }
860
+ assertNoDuplicateKeys(children) {
861
+ const keys = new Set;
862
+ children.forEach((child) => {
863
+ const key = this.getVNodeKey(child);
864
+ if (key === undefined) {
865
+ return;
866
+ }
867
+ if (keys.has(key)) {
868
+ throw new Error(`Duplicate key "${key}"`);
869
+ }
870
+ keys.add(key);
871
+ });
872
+ }
873
+ getVNodeKey(vnode) {
874
+ if (typeof vnode === "string") {
875
+ return;
876
+ }
877
+ return vnode.key;
878
+ }
879
+ collectListeners(vnode) {
880
+ const listeners = {};
881
+ Object.entries(vnode.props ?? {}).forEach(([key, value]) => {
882
+ if (isEventProp(key) && typeof value === "function") {
883
+ listeners[eventNameFromProp(key)] = value;
884
+ }
885
+ });
886
+ return {
887
+ ...listeners,
888
+ ...vnode.listeners ?? {}
889
+ };
890
+ }
891
+ updateListeners(element, oldListeners, newListeners) {
892
+ const store = this.listeners.get(element) ?? new Map;
893
+ const oldKeys = new Set(Object.keys(oldListeners));
894
+ const newKeys = new Set(Object.keys(newListeners));
895
+ oldKeys.forEach((event) => {
896
+ if (!newKeys.has(event) || oldListeners[event] !== newListeners[event]) {
897
+ const stored = store.get(event);
898
+ if (stored) {
899
+ element.removeEventListener(stored.eventName, stored.listener);
900
+ store.delete(event);
901
+ }
902
+ }
903
+ });
904
+ newKeys.forEach((event) => {
905
+ if (!oldKeys.has(event) || oldListeners[event] !== newListeners[event]) {
906
+ const { eventName, modifiers } = parseEventName(event);
907
+ const listener = wrapEventHandler(newListeners[event], modifiers);
908
+ element.addEventListener(eventName, listener);
909
+ store.set(event, { eventName, listener });
910
+ }
911
+ });
912
+ this.listeners.set(element, store);
913
+ }
914
+ setupReactiveAttribute(element, attrName, attrValue, context) {
915
+ const effectRef = effect(() => {
916
+ element.setAttribute(attrName, context.templateEngine.evaluateTemplateValue(attrValue));
917
+ });
918
+ this.trackEffect(element, effectRef);
919
+ }
920
+ trackEffect(element, effectRef) {
921
+ const effects = this.effects.get(element) ?? new Set;
922
+ effects.add(effectRef);
923
+ this.effects.set(element, effects);
924
+ }
925
+ }
926
+
927
+ // lib/core/animation/list-animation-controller.ts
928
+ var ENTER_KEYFRAMES = {
929
+ fade: [{ opacity: 0 }, { opacity: 1 }],
930
+ "slide-up": [
931
+ { opacity: 0, transform: "translateY(12px)" },
932
+ { opacity: 1, transform: "translateY(0)" }
933
+ ],
934
+ "slide-down": [
935
+ { opacity: 0, transform: "translateY(-12px)" },
936
+ { opacity: 1, transform: "translateY(0)" }
937
+ ],
938
+ "slide-left": [
939
+ { opacity: 0, transform: "translateX(12px)" },
940
+ { opacity: 1, transform: "translateX(0)" }
941
+ ],
942
+ "slide-right": [
943
+ { opacity: 0, transform: "translateX(-12px)" },
944
+ { opacity: 1, transform: "translateX(0)" }
945
+ ],
946
+ scale: [
947
+ { opacity: 0, transform: "scale(0.95)" },
948
+ { opacity: 1, transform: "scale(1)" }
949
+ ]
950
+ };
951
+
952
+ class ListAnimationController {
953
+ runs = new WeakMap;
954
+ playEnter(element, options) {
955
+ return this.play(element, options, "enter");
956
+ }
957
+ playExit(element, options) {
958
+ return this.play(element, options, "exit");
959
+ }
960
+ cancel(element) {
961
+ const current = this.runs.get(element);
962
+ if (!current) {
963
+ return;
964
+ }
965
+ this.runs.delete(element);
966
+ current.animation.cancel();
967
+ }
968
+ play(element, transition, phase) {
969
+ this.cancel(element);
970
+ if (!this.canAnimate(element)) {
971
+ return null;
972
+ }
973
+ const enterKeyframes = ENTER_KEYFRAMES[transition.type];
974
+ const keyframes = phase === "enter" ? [...enterKeyframes] : [...enterKeyframes].reverse();
975
+ const options = {
976
+ duration: transition.duration,
977
+ easing: "ease",
978
+ fill: "both"
979
+ };
980
+ const animation = element.animate(keyframes, options);
981
+ const token = Symbol("list-animation");
982
+ const finished = animation.finished.then(() => "finished", () => "cancelled");
983
+ const run = {
984
+ animation,
985
+ token,
986
+ keyframes,
987
+ options,
988
+ finished
989
+ };
990
+ this.runs.set(element, run);
991
+ finished.then((result) => {
992
+ if (this.runs.get(element)?.token !== token) {
993
+ return;
994
+ }
995
+ this.runs.delete(element);
996
+ if (phase === "enter" && result === "finished") {
997
+ animation.cancel();
998
+ }
999
+ });
1000
+ return run;
1001
+ }
1002
+ canAnimate(element) {
1003
+ const reduced = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1004
+ return !reduced && typeof element.animate === "function";
1005
+ }
1006
+ }
1007
+
1008
+ // lib/core/animation/types.ts
1009
+ var TRANSITION_ANIMATION_TYPES = [
1010
+ "fade",
1011
+ "slide-up",
1012
+ "slide-down",
1013
+ "slide-left",
1014
+ "slide-right",
1015
+ "scale"
1016
+ ];
1017
+ function normalizeTransitionGroupProps(props) {
1018
+ const tag = (props.tag ?? "div").trim();
1019
+ const type = props.type ?? "fade";
1020
+ const duration = props.duration ?? 300;
1021
+ if (!tag) {
1022
+ throw new Error("TransitionGroup tag must not be empty");
1023
+ }
1024
+ if (!TRANSITION_ANIMATION_TYPES.includes(type)) {
1025
+ throw new Error(`Unknown TransitionGroup animation type "${type}"`);
1026
+ }
1027
+ if (!Number.isFinite(duration) || duration < 0) {
1028
+ throw new Error("TransitionGroup duration must be a non-negative finite number");
1029
+ }
1030
+ return { tag, type, duration };
1031
+ }
1032
+ function validateTransitionGroupChildren(children) {
1033
+ const keys = new Set;
1034
+ return children.map((child) => {
1035
+ if (typeof child === "string" || child.key === undefined || keys.has(child.key)) {
1036
+ throw new Error("TransitionGroup children must have unique keys");
1037
+ }
1038
+ keys.add(child.key);
1039
+ return child;
1040
+ });
1041
+ }
1042
+ function isTransitionGroupNode(vnode) {
1043
+ return typeof vnode === "object" && vnode !== null && "transitionGroup" in vnode && isHTMLNode(vnode);
1044
+ }
1045
+
1046
+ // lib/core/animation/transition-group-strategy.ts
1047
+ class TransitionGroupRenderStrategy extends ElementRenderStrategy {
1048
+ entries = new WeakMap;
1049
+ animations = new ListAnimationController;
1050
+ matches(vnode) {
1051
+ return isTransitionGroupNode(vnode);
1052
+ }
1053
+ mountChildren(element, groupVNode, context) {
1054
+ const keyedChildren = validateTransitionGroupChildren(groupVNode.children ?? []);
1055
+ const entries = new Map;
1056
+ keyedChildren.forEach((childVNode) => {
1057
+ const node = context.renderer.mount(childVNode, context);
1058
+ element.appendChild(node);
1059
+ const entry = {
1060
+ key: childVNode.key,
1061
+ vnode: childVNode,
1062
+ node,
1063
+ status: "active"
1064
+ };
1065
+ entries.set(entry.key, entry);
1066
+ this.playEnter(entry, node, groupVNode.transitionGroup);
1067
+ });
1068
+ this.entries.set(element, entries);
1069
+ }
1070
+ updateChildren(element, _oldVNode, newVNode, context) {
1071
+ const entries = this.entries.get(element) ?? new Map;
1072
+ const nextChildren = validateTransitionGroupChildren(newVNode.children ?? []);
1073
+ const nextKeys = new Set(nextChildren.map((child) => child.key));
1074
+ const ordered = [];
1075
+ nextChildren.forEach((childVNode) => {
1076
+ const key = childVNode.key;
1077
+ const current = entries.get(key);
1078
+ if (current) {
1079
+ const wasExiting = current.status === "exiting";
1080
+ if (wasExiting && current.node instanceof HTMLElement) {
1081
+ this.animations.cancel(current.node);
1082
+ }
1083
+ current.status = "active";
1084
+ current.animationToken = undefined;
1085
+ current.node = context.renderer.patch(current.vnode, childVNode, current.node, context);
1086
+ current.vnode = childVNode;
1087
+ ordered.push(current);
1088
+ if (wasExiting) {
1089
+ this.playEnter(current, current.node, newVNode.transitionGroup);
1090
+ }
1091
+ return;
1092
+ }
1093
+ const node = context.renderer.mount(childVNode, context);
1094
+ const entry = {
1095
+ key,
1096
+ vnode: childVNode,
1097
+ node,
1098
+ status: "active"
1099
+ };
1100
+ entries.set(key, entry);
1101
+ ordered.push(entry);
1102
+ this.playEnter(entry, node, newVNode.transitionGroup);
1103
+ });
1104
+ entries.forEach((entry, key) => {
1105
+ if (nextKeys.has(key) || entry.status !== "active") {
1106
+ return;
1107
+ }
1108
+ this.startExit(element, entry, newVNode.transitionGroup, context);
1109
+ });
1110
+ this.placeActiveEntries(element, ordered);
1111
+ this.entries.set(element, entries);
1112
+ }
1113
+ unmountChildren(element, vnode, context) {
1114
+ const entries = this.entries.get(element);
1115
+ if (!entries) {
1116
+ super.unmountChildren(element, vnode, context);
1117
+ return;
1118
+ }
1119
+ entries.forEach((entry) => {
1120
+ if (entry.node instanceof HTMLElement) {
1121
+ this.animations.cancel(entry.node);
1122
+ }
1123
+ context.renderer.unmount(entry.vnode, entry.node, context);
1124
+ if (entry.node.parentNode === element) {
1125
+ element.removeChild(entry.node);
1126
+ }
1127
+ });
1128
+ entries.clear();
1129
+ this.entries.delete(element);
1130
+ }
1131
+ playEnter(entry, node, options) {
1132
+ if (!(node instanceof HTMLElement)) {
1133
+ return;
1134
+ }
1135
+ const run = this.animations.playEnter(node, options);
1136
+ entry.animationToken = run?.token;
1137
+ }
1138
+ startExit(wrapper, entry, options, context) {
1139
+ entry.status = "exiting";
1140
+ const run = entry.node instanceof HTMLElement ? this.animations.playExit(entry.node, options) : null;
1141
+ if (!run) {
1142
+ this.finishExit(wrapper, entry, context);
1143
+ return;
1144
+ }
1145
+ entry.animationToken = run.token;
1146
+ run.finished.then((result) => {
1147
+ if (result === "finished" && entry.status === "exiting" && entry.animationToken === run.token) {
1148
+ this.finishExit(wrapper, entry, context);
1149
+ }
1150
+ });
1151
+ }
1152
+ finishExit(wrapper, entry, context) {
1153
+ const entries = this.entries.get(wrapper);
1154
+ if (entries?.get(entry.key) !== entry) {
1155
+ return;
1156
+ }
1157
+ context.renderer.unmount(entry.vnode, entry.node, context);
1158
+ if (entry.node.parentNode === wrapper) {
1159
+ wrapper.removeChild(entry.node);
1160
+ }
1161
+ entries.delete(entry.key);
1162
+ }
1163
+ placeActiveEntries(wrapper, ordered) {
1164
+ let reference = null;
1165
+ for (let index = ordered.length - 1;index >= 0; index -= 1) {
1166
+ wrapper.insertBefore(ordered[index].node, reference);
1167
+ reference = ordered[index].node;
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ // lib/core/renderer.ts
1173
+ class RendererContext {
1174
+ strategies;
1175
+ constructor() {
1176
+ this.strategies = [
1177
+ new TextRenderStrategy,
1178
+ new ComponentRenderStrategy,
1179
+ new SlotRenderStrategy,
1180
+ new TransitionGroupRenderStrategy,
1181
+ new ElementRenderStrategy
1182
+ ];
1183
+ }
1184
+ mount(vnode, context) {
1185
+ return this.findStrategy(vnode).mount(vnode, context);
1186
+ }
1187
+ patch(oldVNode, newVNode, currentNode, context) {
1188
+ const oldStrategy = this.findStrategy(oldVNode);
1189
+ const newStrategy = this.findStrategy(newVNode);
1190
+ if (oldStrategy !== newStrategy) {
1191
+ const nextNode = newStrategy.mount(newVNode, context);
1192
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1193
+ oldStrategy.unmount(oldVNode, currentNode, context);
1194
+ return nextNode;
1195
+ }
1196
+ return oldStrategy.patch(oldVNode, newVNode, currentNode, context);
1197
+ }
1198
+ unmount(vnode, currentNode, context) {
1199
+ this.findStrategy(vnode).unmount(vnode, currentNode, context);
1200
+ }
1201
+ findStrategy(vnode) {
1202
+ const strategy = this.strategies.find((item) => item.matches(vnode));
1203
+ if (!strategy) {
1204
+ throw new Error("No render strategy found for vnode");
1205
+ }
1206
+ return strategy;
1207
+ }
1208
+ }
1209
+
1210
+ class TextRenderStrategy {
1211
+ matches(vnode) {
1212
+ return typeof vnode === "string";
1213
+ }
1214
+ mount(vnode, context) {
1215
+ return context.templateEngine.parseTemplate(vnode);
1216
+ }
1217
+ patch(oldVNode, newVNode, currentNode, context) {
1218
+ if (oldVNode === newVNode) {
1219
+ return currentNode;
1220
+ }
1221
+ const nextNode = this.mount(newVNode, context);
1222
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1223
+ return nextNode;
1224
+ }
1225
+ unmount() {}
1226
+ }
1227
+
1228
+ class ComponentRenderStrategy {
1229
+ instances = new WeakMap;
1230
+ instanceNodes = new Map;
1231
+ emitterUnsubscribers = new WeakMap;
1232
+ matches(vnode) {
1233
+ return typeof vnode === "object" && vnode !== null && isComponentNode(vnode);
1234
+ }
1235
+ mount(vnode, context) {
1236
+ if (vnode.directions?.if === false) {
1237
+ return document.createComment("if");
1238
+ }
1239
+ const ComponentClass = vnode.component;
1240
+ const instance = new ComponentClass(this.createProps(vnode));
1241
+ if (context.appContext && instance.setAppContext) {
1242
+ instance.setAppContext(context.appContext);
1243
+ }
1244
+ this.syncEmitters(instance, vnode.emitters ?? {});
1245
+ context.registerChild(instance);
1246
+ const node = instance.mountToNode();
1247
+ this.trackInstanceNode(instance, node);
1248
+ instance.setElementChangeListener?.((previousNode, nextNode) => {
1249
+ this.trackInstanceNode(instance, previousNode);
1250
+ this.trackInstanceNode(instance, nextNode);
1251
+ });
1252
+ return node;
1253
+ }
1254
+ patch(oldVNode, newVNode, currentNode, context) {
1255
+ if (currentNode.nodeType === Node.COMMENT_NODE) {
1256
+ const nextNode2 = this.mount(newVNode, context);
1257
+ currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1258
+ return nextNode2;
1259
+ }
1260
+ if (newVNode.directions?.if === false) {
1261
+ const nextNode2 = document.createComment("if");
1262
+ currentNode.parentNode?.replaceChild(nextNode2, currentNode);
1263
+ this.unmount(oldVNode, currentNode, context);
1264
+ return nextNode2;
1265
+ }
1266
+ const instance = this.instances.get(currentNode);
1267
+ if (instance && oldVNode.component === newVNode.component) {
1268
+ this.syncEmitters(instance, newVNode.emitters ?? {});
1269
+ instance.setProps(this.createProps(newVNode));
1270
+ const nextNode2 = instance.getElement() ?? currentNode;
1271
+ this.trackInstanceNode(instance, nextNode2);
1272
+ return nextNode2;
1273
+ }
1274
+ const nextNode = this.mount(newVNode, context);
1275
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1276
+ this.unmount(oldVNode, currentNode, context);
1277
+ return nextNode;
1278
+ }
1279
+ unmount(_vnode, currentNode, context) {
1280
+ const instance = this.instances.get(currentNode);
1281
+ if (instance) {
1282
+ this.clearEmitters(instance);
1283
+ instance.unmount();
1284
+ this.clearInstanceNodes(instance);
1285
+ context.unregisterChild(instance);
1286
+ }
1287
+ }
1288
+ createProps(vnode) {
1289
+ return {
1290
+ ...vnode.props ?? {},
1291
+ children: vnode.children ?? []
1292
+ };
1293
+ }
1294
+ syncEmitters(instance, emitters) {
1295
+ const current = this.emitterUnsubscribers.get(instance) ?? new Map;
1296
+ current.forEach(({ listener: currentListener, unsubscribe }, eventName) => {
1297
+ const listener = emitters[eventName];
1298
+ if (!listener || listener !== currentListener) {
1299
+ unsubscribe();
1300
+ current.delete(eventName);
1301
+ }
1302
+ });
1303
+ Object.entries(emitters).forEach(([eventName, listener]) => {
1304
+ if (current.get(eventName)?.listener === listener) {
1025
1305
  return;
1026
1306
  }
1027
- context.renderer.unmount(entry.vnode, entry.node, context);
1028
- if (entry.node.parentNode === element) {
1029
- element.removeChild(entry.node);
1030
- }
1307
+ current.set(eventName, {
1308
+ listener,
1309
+ unsubscribe: instance.on(eventName, listener)
1310
+ });
1031
1311
  });
1312
+ this.emitterUnsubscribers.set(instance, current);
1032
1313
  }
1033
- hasOnlyKeyedChildren(oldChildren, newChildren) {
1034
- return [...oldChildren, ...newChildren].every((child) => this.getVNodeKey(child) !== undefined);
1314
+ clearEmitters(instance) {
1315
+ this.emitterUnsubscribers.get(instance)?.forEach(({ unsubscribe }) => {
1316
+ unsubscribe();
1317
+ });
1318
+ this.emitterUnsubscribers.delete(instance);
1035
1319
  }
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);
1320
+ trackInstanceNode(instance, node) {
1321
+ this.instances.set(node, instance);
1322
+ const nodes = this.instanceNodes.get(instance) ?? new Set;
1323
+ nodes.add(node);
1324
+ this.instanceNodes.set(instance, nodes);
1325
+ }
1326
+ clearInstanceNodes(instance) {
1327
+ this.instanceNodes.get(instance)?.forEach((node) => {
1328
+ this.instances.delete(node);
1047
1329
  });
1330
+ this.instanceNodes.delete(instance);
1048
1331
  }
1049
- getVNodeKey(vnode) {
1050
- if (typeof vnode === "string") {
1332
+ }
1333
+
1334
+ class SlotRenderStrategy {
1335
+ renderedChildren = new WeakMap;
1336
+ matches(vnode) {
1337
+ return typeof vnode === "object" && vnode !== null && isSlotProvider(vnode);
1338
+ }
1339
+ mount(vnode, context) {
1340
+ if (vnode.directions?.if === false) {
1341
+ return document.createComment("if");
1342
+ }
1343
+ const slotContainer = document.createElement("div");
1344
+ slotContainer.setAttribute("data-slot", vnode.props.name);
1345
+ this.mountSlotChildren(slotContainer, this.resolveChildren(vnode, context), context);
1346
+ return slotContainer;
1347
+ }
1348
+ patch(oldVNode, newVNode, currentNode, context) {
1349
+ if (currentNode.nodeType === Node.COMMENT_NODE) {
1350
+ const nextNode = this.mount(newVNode, context);
1351
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1352
+ return nextNode;
1353
+ }
1354
+ if (newVNode.directions?.if === false) {
1355
+ const nextNode = document.createComment("if");
1356
+ currentNode.parentNode?.replaceChild(nextNode, currentNode);
1357
+ this.unmount(oldVNode, currentNode, context);
1358
+ return nextNode;
1359
+ }
1360
+ if (currentNode instanceof HTMLElement) {
1361
+ currentNode.setAttribute("data-slot", newVNode.props.name);
1362
+ this.replaceSlotChildren(currentNode, oldVNode, newVNode, context);
1363
+ }
1364
+ return currentNode;
1365
+ }
1366
+ unmount(_vnode, currentNode, context) {
1367
+ if (!(currentNode instanceof HTMLElement)) {
1051
1368
  return;
1052
1369
  }
1053
- return vnode.key;
1370
+ this.unmountSlotChildren(currentNode, context);
1371
+ this.renderedChildren.delete(currentNode);
1054
1372
  }
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
- };
1373
+ resolveChildren(vnode, context) {
1374
+ return context.slots[vnode.props.name] ?? vnode.children ?? [];
1066
1375
  }
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);
1376
+ replaceSlotChildren(element, _oldVNode, newVNode, context) {
1377
+ this.unmountSlotChildren(element, context);
1378
+ element.textContent = "";
1379
+ this.mountSlotChildren(element, this.resolveChildren(newVNode, context), context);
1089
1380
  }
1090
- setupReactiveAttribute(element, attrName, attrValue, context) {
1091
- const effectRef = effect(() => {
1092
- element.setAttribute(attrName, context.templateEngine.evaluateTemplateValue(attrValue));
1381
+ mountSlotChildren(element, children, context) {
1382
+ children.forEach((child) => {
1383
+ element.appendChild(context.renderer.mount(child, context));
1093
1384
  });
1094
- this.trackEffect(element, effectRef);
1385
+ this.renderedChildren.set(element, children);
1095
1386
  }
1096
- trackEffect(element, effectRef) {
1097
- const effects = this.effects.get(element) ?? new Set;
1098
- effects.add(effectRef);
1099
- this.effects.set(element, effects);
1387
+ unmountSlotChildren(element, context) {
1388
+ const children = this.renderedChildren.get(element) ?? [];
1389
+ children.forEach((child, index) => {
1390
+ const childNode = element.childNodes[index];
1391
+ if (childNode) {
1392
+ context.renderer.unmount(child, childNode, context);
1393
+ }
1394
+ });
1100
1395
  }
1101
1396
  }
1102
1397
 
@@ -1812,7 +2107,347 @@ function createRouter(options) {
1812
2107
  return new Router(options);
1813
2108
  }
1814
2109
 
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 };
2110
+ // lib/core/document.ts
2111
+ var VOID_HEAD_TAGS = new Set(["base", "link", "meta"]);
2112
+ function renderHtmlDocument(options) {
2113
+ const lang = options.lang ?? "en";
2114
+ const charset = options.charset ?? "utf-8";
2115
+ const viewport = options.viewport ?? "width=device-width, initial-scale=1";
2116
+ const htmlAttributes = renderAttributes({
2117
+ lang,
2118
+ ...options.htmlAttributes ?? {}
2119
+ });
2120
+ const bodyAttributes = renderAttributes(options.bodyAttributes);
2121
+ const bodyHtml = renderDocumentBody(options.body);
2122
+ return [
2123
+ "<!doctype html>",
2124
+ `<html${htmlAttributes}>`,
2125
+ "<head>",
2126
+ ` <meta charset="${escapeHtml(charset)}">`,
2127
+ ` <meta name="viewport" content="${escapeHtml(viewport)}">`,
2128
+ ` <title>${escapeHtml(options.title)}</title>`,
2129
+ options.description ? ` <meta name="description" content="${escapeHtml(options.description)}">` : "",
2130
+ ...(options.head ?? []).map((element) => ` ${renderHeadElement(element)}`),
2131
+ options.styles && options.styles.length > 0 ? ` <style>${renderStyleSheet(options.styles)}</style>` : "",
2132
+ "</head>",
2133
+ `<body${bodyAttributes}>`,
2134
+ bodyHtml,
2135
+ ...(options.scripts ?? []).map((script) => ` ${renderScript(script)}`),
2136
+ "</body>",
2137
+ "</html>"
2138
+ ].filter((line) => line !== "").join(`
2139
+ `);
2140
+ }
2141
+ function renderDocumentBody(body) {
2142
+ if (typeof document === "undefined") {
2143
+ throw new Error("renderHtmlDocument requires a DOM-like document");
2144
+ }
2145
+ const container = document.createElement("div");
2146
+ const renderer = new RendererContext;
2147
+ const mountedComponents = new Set;
2148
+ const renderables = Array.isArray(body) ? body : [body];
2149
+ const context = {
2150
+ templateEngine: new TemplateEngine({}),
2151
+ renderer,
2152
+ slots: { default: [] },
2153
+ registerChild: (component2) => {
2154
+ mountedComponents.add(component2);
2155
+ },
2156
+ unregisterChild: (component2) => {
2157
+ mountedComponents.delete(component2);
2158
+ }
2159
+ };
2160
+ renderables.forEach((renderable) => {
2161
+ container.appendChild(renderer.mount(renderable, context));
2162
+ });
2163
+ const html = container.innerHTML;
2164
+ mountedComponents.forEach((component2) => {
2165
+ component2.unmount();
2166
+ });
2167
+ return html;
2168
+ }
2169
+ function renderHeadElement(element) {
2170
+ const attributes = renderAttributes(element.attributes);
2171
+ if (VOID_HEAD_TAGS.has(element.tag) && !element.text) {
2172
+ return `<${element.tag}${attributes}>`;
2173
+ }
2174
+ return `<${element.tag}${attributes}>${escapeHtml(element.text ?? "")}</${element.tag}>`;
2175
+ }
2176
+ function renderScript(script) {
2177
+ const attributes = renderAttributes({
2178
+ type: script.type,
2179
+ src: script.src,
2180
+ async: script.async,
2181
+ defer: script.defer,
2182
+ ...script.attributes ?? {}
2183
+ });
2184
+ return `<script${attributes}></script>`;
2185
+ }
2186
+ function renderAttributes(attributes = {}) {
2187
+ const rendered = Object.entries(attributes).flatMap(([name, value]) => {
2188
+ if (value === false || value === null || value === undefined) {
2189
+ return [];
2190
+ }
2191
+ return value === true ? [name] : [`${name}="${escapeHtml(String(value))}"`];
2192
+ }).join(" ");
2193
+ return rendered ? ` ${rendered}` : "";
2194
+ }
2195
+ function escapeHtml(value) {
2196
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2197
+ }
2198
+
2199
+ // lib/core/app.ts
2200
+ var DEFAULT_ROOT_ELEMENT = "#app";
2201
+
2202
+ class OneApp {
2203
+ options;
2204
+ container = null;
2205
+ rootInstance = null;
2206
+ mounted = false;
2207
+ templateEngine = null;
2208
+ appContext;
2209
+ providers = new Map;
2210
+ plugins = [];
2211
+ unmountedCallback;
2212
+ router;
2213
+ constructor(options = {}) {
2214
+ this.options = options;
2215
+ this.appContext = {
2216
+ app: this,
2217
+ version: "0.2.1",
2218
+ config: options.config || {}
2219
+ };
2220
+ }
2221
+ handleError(error) {
2222
+ console.error("应用错误:", error);
2223
+ this.renderErrorUI(error);
2224
+ }
2225
+ renderErrorUI(error) {
2226
+ if (!this.container) {
2227
+ return;
2228
+ }
2229
+ this.container.innerHTML = `
2230
+ <div style="padding: 20px; background-color: #ffebee; color: #c62828; font-family: Arial, sans-serif;">
2231
+ <h3>应用错误</h3>
2232
+ <p>${error.message}</p>
2233
+ <pre style="background-color: #fff; padding: 10px; border-radius: 4px; overflow: auto;">${error.stack}</pre>
2234
+ </div>
2235
+ `;
2236
+ }
2237
+ use(plugin, ...args) {
2238
+ if (typeof plugin.install !== "function") {
2239
+ throw new Error("插件必须提供 install 方法");
2240
+ }
2241
+ plugin.install(this, ...args);
2242
+ this.plugins.push({ plugin, args });
2243
+ return this;
2244
+ }
2245
+ mount() {
2246
+ if (this.mounted) {
2247
+ console.warn("应用已经处于运行状态");
2248
+ return;
2249
+ }
2250
+ const mountContainer = this.resolveMountContainer();
2251
+ if (!mountContainer) {
2252
+ return;
2253
+ }
2254
+ try {
2255
+ this.container = mountContainer;
2256
+ globalThis.__APP__ = this;
2257
+ if (this.options.root) {
2258
+ this.rootInstance = new this.options.root(this.options.rootProps);
2259
+ if ("setAppContext" in this.rootInstance) {
2260
+ this.rootInstance.setAppContext(this.appContext);
2261
+ }
2262
+ if (this.options.state && "setState" in this.rootInstance) {
2263
+ this.rootInstance.setState(this.options.state);
2264
+ }
2265
+ this.rootInstance.mount(this.container);
2266
+ this.templateEngine = new TemplateEngine(this.options.state || {});
2267
+ }
2268
+ this.mounted = true;
2269
+ this.onMounted();
2270
+ } catch (error) {
2271
+ this.handleError(error);
2272
+ }
2273
+ }
2274
+ unmount() {
2275
+ if (!this.mounted) {
2276
+ console.warn("应用未处于运行状态");
2277
+ return;
2278
+ }
2279
+ try {
2280
+ this.onBeforeUnmount();
2281
+ if (this.rootInstance) {
2282
+ if ("unmount" in this.rootInstance) {
2283
+ this.rootInstance.unmount();
2284
+ }
2285
+ this.rootInstance = null;
2286
+ this.mounted = false;
2287
+ delete globalThis.__APP__;
2288
+ }
2289
+ if (this.templateEngine) {
2290
+ this.templateEngine.clearBindings();
2291
+ this.templateEngine = null;
2292
+ }
2293
+ if (this.container) {
2294
+ this.container.innerHTML = "";
2295
+ }
2296
+ if (this.unmountedCallback) {
2297
+ this.unmountedCallback();
2298
+ }
2299
+ } catch (error) {
2300
+ console.error("Failed to unmount app:", error);
2301
+ }
2302
+ }
2303
+ isRunning() {
2304
+ return this.mounted;
2305
+ }
2306
+ updateRootComponent(component2) {
2307
+ if (this.mounted) {
2308
+ this.unmount();
2309
+ }
2310
+ this.options.root = component2;
2311
+ this.mount();
2312
+ }
2313
+ update(state) {
2314
+ if (!this.mounted) {
2315
+ console.warn("Cannot update unmounted app");
2316
+ return this;
2317
+ }
2318
+ try {
2319
+ if (state && this.options.state) {
2320
+ this.options.state = { ...this.options.state, ...state };
2321
+ if (this.rootInstance && "setState" in this.rootInstance) {
2322
+ this.rootInstance.setState(state);
2323
+ }
2324
+ if (this.templateEngine) {
2325
+ this.templateEngine.state = this.options.state;
2326
+ }
2327
+ }
2328
+ this.onUpdated();
2329
+ } catch (error) {
2330
+ console.error("Failed to update app:", error);
2331
+ }
2332
+ return this;
2333
+ }
2334
+ getContext() {
2335
+ return this.appContext;
2336
+ }
2337
+ provide(key, value) {
2338
+ this.providers.set(key, value);
2339
+ return this;
2340
+ }
2341
+ inject(key, fallback) {
2342
+ const result = this.resolveInjection(key);
2343
+ return result.found ? result.value : fallback;
2344
+ }
2345
+ resolveInjection(key) {
2346
+ if (!this.providers.has(key)) {
2347
+ return { found: false, value: undefined };
2348
+ }
2349
+ return { found: true, value: this.providers.get(key) };
2350
+ }
2351
+ getState() {
2352
+ return this.options.state;
2353
+ }
2354
+ setState(newState) {
2355
+ this.options.state = newState;
2356
+ if (this.mounted) {
2357
+ this.update();
2358
+ }
2359
+ return this;
2360
+ }
2361
+ onUnmounted(callback) {
2362
+ this.unmountedCallback = callback;
2363
+ return this;
2364
+ }
2365
+ renderHtmlDocument(options = {}) {
2366
+ const appDocument = this.options.document ?? {};
2367
+ const scripts = this.mergeDocumentScripts(appDocument.scripts, options.scripts);
2368
+ return renderHtmlDocument({
2369
+ ...appDocument,
2370
+ ...options,
2371
+ title: options.title ?? appDocument.title ?? "TSone App",
2372
+ body: options.body ?? appDocument.body ?? this.createMountDocumentBody(),
2373
+ scripts
2374
+ });
2375
+ }
2376
+ resolveRootElement(selector) {
2377
+ if (!selector) {
2378
+ return null;
2379
+ }
2380
+ if (typeof selector === "string") {
2381
+ if (typeof document === "undefined") {
2382
+ return null;
2383
+ }
2384
+ return document.querySelector(selector);
2385
+ }
2386
+ return typeof Element !== "undefined" && selector instanceof Element ? selector : null;
2387
+ }
2388
+ resolveMountContainer() {
2389
+ if (typeof document === "undefined") {
2390
+ return null;
2391
+ }
2392
+ const rootElement = this.resolveRootElement(this.options.rootElement ?? DEFAULT_ROOT_ELEMENT);
2393
+ return rootElement instanceof HTMLElement ? rootElement : null;
2394
+ }
2395
+ createMountDocumentBody() {
2396
+ const rootElement = this.options.rootElement ?? DEFAULT_ROOT_ELEMENT;
2397
+ if (typeof rootElement === "string") {
2398
+ return this.createMountElementFromSelector(rootElement);
2399
+ }
2400
+ if (typeof Element !== "undefined" && rootElement instanceof Element) {
2401
+ const props = {};
2402
+ if (rootElement.id) {
2403
+ props.id = rootElement.id;
2404
+ }
2405
+ if (rootElement.className) {
2406
+ props.className = rootElement.className;
2407
+ }
2408
+ return { tag: rootElement.tagName.toLowerCase(), props };
2409
+ }
2410
+ return Div({ props: { id: "app" } });
2411
+ }
2412
+ createMountElementFromSelector(selector) {
2413
+ if (selector.startsWith("#") && selector.length > 1) {
2414
+ return Div({ props: { id: selector.slice(1) } });
2415
+ }
2416
+ if (selector.startsWith(".") && selector.length > 1) {
2417
+ return Div({ props: { className: selector.slice(1) } });
2418
+ }
2419
+ return Div({ props: { "data-tsone-root": selector } });
2420
+ }
2421
+ mergeDocumentScripts(baseScripts, extraScripts) {
2422
+ if (!baseScripts && !extraScripts) {
2423
+ return;
2424
+ }
2425
+ return [...baseScripts ?? [], ...extraScripts ?? []];
2426
+ }
2427
+ onMounted() {
2428
+ this.plugins.forEach(({ plugin: pluginObj }) => {
2429
+ if (pluginObj && typeof pluginObj.onMounted === "function") {
2430
+ pluginObj.onMounted(this);
2431
+ }
2432
+ });
2433
+ }
2434
+ onUpdated() {
2435
+ this.plugins.forEach(({ plugin: pluginObj }) => {
2436
+ if (pluginObj && typeof pluginObj.onUpdated === "function") {
2437
+ pluginObj.onUpdated(this);
2438
+ }
2439
+ });
2440
+ }
2441
+ onBeforeUnmount() {
2442
+ this.plugins.forEach(({ plugin: pluginObj }) => {
2443
+ if (pluginObj && typeof pluginObj.onBeforeUnmount === "function") {
2444
+ pluginObj.onBeforeUnmount(this);
2445
+ }
2446
+ });
2447
+ }
2448
+ }
2449
+
2450
+ export { ReactiveSystem, reactive, readonly, effect, computed, ref, isRef, unref, stop, isReactive, isReadonly, 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, modelPath, getModelValue, setModelValue, ModelBindingController, ElementRenderStrategy, normalizeTransitionGroupProps, validateTransitionGroupChildren, RendererContext, TextRenderStrategy, ComponentRenderStrategy, SlotRenderStrategy, TemplateEngine, Component, renderHtmlDocument, OneApp, useRouter, Router, RouterLink, RouterView, createRouter };
1816
2451
 
1817
- //# debugId=EA20D7B3780F196864756E2164756E21
1818
- //# sourceMappingURL=index-ycmc7ga1.js.map
2452
+ //# debugId=ABE10854C25EBD6364756E2164756E21
2453
+ //# sourceMappingURL=index-1x4ectvr.js.map