streetui 1.8.0 → 2.0.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.
- package/dist/bin.cjs +1 -1
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/create-bin.cjs +1 -1
- package/dist/create-bin.cjs.map +1 -1
- package/dist/create-bin.js +1 -1
- package/dist/create-bin.js.map +1 -1
- package/dist/{compile-CJJQYi71.d.cts → hydration-diagnostics-CFYDXBlm.d.cts} +471 -5
- package/dist/{compile-CJJQYi71.d.ts → hydration-diagnostics-CFYDXBlm.d.ts} +471 -5
- package/dist/index.cjs +1401 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +598 -147
- package/dist/index.d.ts +598 -147
- package/dist/index.js +1379 -47
- package/dist/index.js.map +1 -1
- package/dist/renderer-interface-BUhHV7ja.d.cts +26 -0
- package/dist/renderer-interface-B_-hWnKk.d.ts +26 -0
- package/dist/{server-CnLEnlYU.d.cts → server-C0PvFBzD.d.ts} +181 -2
- package/dist/{server-DNlg7ou2.d.ts → server-DnAOFpMP.d.cts} +181 -2
- package/dist/server.cjs +482 -32
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +481 -32
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +754 -41
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +42 -4
- package/dist/testing.d.ts +42 -4
- package/dist/testing.js +746 -40
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/hydration-diagnostics-BQeehq3i.d.cts +0 -89
- package/dist/hydration-diagnostics-DNAJumYF.d.ts +0 -89
package/dist/server.js
CHANGED
|
@@ -500,6 +500,9 @@ var ServerDOMAdapter = class {
|
|
|
500
500
|
body() {
|
|
501
501
|
return null;
|
|
502
502
|
}
|
|
503
|
+
head() {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
503
506
|
activeElement() {
|
|
504
507
|
return null;
|
|
505
508
|
}
|
|
@@ -613,18 +616,25 @@ function trapFocus(dom, container) {
|
|
|
613
616
|
dom.addEventListener(container, "keydown", onKeydown);
|
|
614
617
|
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
615
618
|
}
|
|
619
|
+
var containmentStack = [];
|
|
616
620
|
function containFocus(dom, container) {
|
|
617
621
|
const body = dom.body();
|
|
618
622
|
if (body === null) return () => {
|
|
619
623
|
};
|
|
624
|
+
containmentStack.push(container);
|
|
620
625
|
const onFocusIn = (event) => {
|
|
626
|
+
if (containmentStack[containmentStack.length - 1] !== container) return;
|
|
621
627
|
const target = event.target;
|
|
622
628
|
if (target !== null && !dom.contains(container, target)) {
|
|
623
629
|
focusFirst(dom, container);
|
|
624
630
|
}
|
|
625
631
|
};
|
|
626
632
|
dom.addEventListener(body, "focusin", onFocusIn);
|
|
627
|
-
return () =>
|
|
633
|
+
return () => {
|
|
634
|
+
dom.removeEventListener(body, "focusin", onFocusIn);
|
|
635
|
+
const index = containmentStack.lastIndexOf(container);
|
|
636
|
+
if (index !== -1) containmentStack.splice(index, 1);
|
|
637
|
+
};
|
|
628
638
|
}
|
|
629
639
|
function onEscape(dom, target, handler) {
|
|
630
640
|
const onKeydown = (event) => {
|
|
@@ -633,6 +643,33 @@ function onEscape(dom, target, handler) {
|
|
|
633
643
|
dom.addEventListener(target, "keydown", onKeydown);
|
|
634
644
|
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
635
645
|
}
|
|
646
|
+
function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
647
|
+
const onKeydown = (event) => {
|
|
648
|
+
const key = event.key;
|
|
649
|
+
const isActivate = key === "Enter" || key === " " || key === "Spacebar";
|
|
650
|
+
const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
|
|
651
|
+
if (!isActivate && !isMove) return;
|
|
652
|
+
const items = getFocusable(dom, container, selector);
|
|
653
|
+
if (items.length === 0) return;
|
|
654
|
+
const active = dom.activeElement();
|
|
655
|
+
const index = active === null ? -1 : items.indexOf(active);
|
|
656
|
+
if (isActivate) {
|
|
657
|
+
if (index < 0) return;
|
|
658
|
+
event.preventDefault();
|
|
659
|
+
items[index].click?.();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
event.preventDefault();
|
|
663
|
+
let next;
|
|
664
|
+
if (key === "Home") next = 0;
|
|
665
|
+
else if (key === "End") next = items.length - 1;
|
|
666
|
+
else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
|
|
667
|
+
else next = index <= 0 ? items.length - 1 : index - 1;
|
|
668
|
+
dom.focus(items[next]);
|
|
669
|
+
};
|
|
670
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
671
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
672
|
+
}
|
|
636
673
|
|
|
637
674
|
// ../renderer/src/tag-map.ts
|
|
638
675
|
var TAG_MAP = {
|
|
@@ -657,7 +694,13 @@ var TAG_MAP = {
|
|
|
657
694
|
// its children are relocated to a document.body container on the browser
|
|
658
695
|
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
659
696
|
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
660
|
-
portal: "div"
|
|
697
|
+
portal: "div",
|
|
698
|
+
// A `head()` node renders as a neutral, empty inline anchor <div> at its
|
|
699
|
+
// declaration site (like a portal anchor). Its actual contribution — title/
|
|
700
|
+
// meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
|
|
701
|
+
// browser, and emitted separately by `renderHead()` on the server. Keeping a
|
|
702
|
+
// one-node/one-element anchor preserves positional hydration.
|
|
703
|
+
head: "div"
|
|
661
704
|
};
|
|
662
705
|
function resolveTag(type) {
|
|
663
706
|
return TAG_MAP[type] ?? "div";
|
|
@@ -705,7 +748,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
|
|
|
705
748
|
}
|
|
706
749
|
|
|
707
750
|
// ../renderer/src/reconciliation.ts
|
|
708
|
-
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
751
|
+
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
|
|
709
752
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
710
753
|
for (const inst of oldInstances) {
|
|
711
754
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
@@ -726,28 +769,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
|
726
769
|
}
|
|
727
770
|
newInstances.push(existing);
|
|
728
771
|
} else {
|
|
729
|
-
const
|
|
730
|
-
|
|
772
|
+
const reclaimed = hooks?.takeLeaving(key);
|
|
773
|
+
if (reclaimed !== void 0) {
|
|
774
|
+
patchExistingInstance(ctx, reclaimed, newNode);
|
|
775
|
+
reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
|
|
776
|
+
hooks?.onEnter(reclaimed);
|
|
777
|
+
newInstances.push(reclaimed);
|
|
778
|
+
} else {
|
|
779
|
+
const inst = mountFn(newNode, parentDom);
|
|
780
|
+
hooks?.onEnter(inst);
|
|
781
|
+
newInstances.push(inst);
|
|
782
|
+
}
|
|
731
783
|
}
|
|
732
784
|
}
|
|
733
785
|
const removed = [];
|
|
734
786
|
for (const inst of oldInstances) {
|
|
735
787
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
for (const inst of removed) {
|
|
788
|
+
if (usedKeys.has(key)) continue;
|
|
789
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
741
790
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
742
|
-
if (parent !== null)
|
|
743
|
-
ctx.dom.removeChild(parent, inst.domNode);
|
|
744
|
-
}
|
|
791
|
+
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
745
792
|
inst.dispose();
|
|
793
|
+
removed.push(inst);
|
|
746
794
|
}
|
|
747
795
|
reorderDom(ctx, parentDom, newInstances);
|
|
748
796
|
return { instances: newInstances, removed };
|
|
749
797
|
}
|
|
750
|
-
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
798
|
+
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
|
|
751
799
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
752
800
|
for (const inst of oldInstances) {
|
|
753
801
|
oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
|
|
@@ -774,21 +822,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
|
774
822
|
}
|
|
775
823
|
newInstances.push(existing);
|
|
776
824
|
} else {
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
825
|
+
const reclaimed = hooks?.takeLeaving(entry.key);
|
|
826
|
+
if (reclaimed !== void 0) {
|
|
827
|
+
const freshNode = entry.build();
|
|
828
|
+
built.push(freshNode);
|
|
829
|
+
patchExistingInstance(ctx, reclaimed, freshNode);
|
|
830
|
+
reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
|
|
831
|
+
reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
|
|
832
|
+
reclaimed.graphNode.setProp("_item", entry.item);
|
|
833
|
+
hooks?.onEnter(reclaimed);
|
|
834
|
+
newInstances.push(reclaimed);
|
|
835
|
+
} else {
|
|
836
|
+
const freshNode = entry.build();
|
|
837
|
+
built.push(freshNode);
|
|
838
|
+
const inst = mountFn(freshNode, parentDom);
|
|
839
|
+
hooks?.onEnter(inst);
|
|
840
|
+
newInstances.push(inst);
|
|
841
|
+
}
|
|
781
842
|
}
|
|
782
843
|
}
|
|
783
844
|
const removed = [];
|
|
784
845
|
for (const inst of oldInstances) {
|
|
785
846
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
786
|
-
if (
|
|
787
|
-
|
|
788
|
-
for (const inst of removed) {
|
|
847
|
+
if (usedKeys.has(key)) continue;
|
|
848
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
789
849
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
790
850
|
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
791
851
|
inst.dispose();
|
|
852
|
+
removed.push(inst);
|
|
792
853
|
}
|
|
793
854
|
reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
|
|
794
855
|
return { instances: newInstances, removed, built };
|
|
@@ -914,6 +975,354 @@ function patchExistingInstance(ctx, instance, newNode) {
|
|
|
914
975
|
}
|
|
915
976
|
}
|
|
916
977
|
|
|
978
|
+
// ../renderer/src/transition.ts
|
|
979
|
+
function getResolvedTransition(graph, nodeId) {
|
|
980
|
+
const fn = graph.getHandler(`__transition__${nodeId}`);
|
|
981
|
+
return fn === void 0 ? void 0 : fn();
|
|
982
|
+
}
|
|
983
|
+
function host() {
|
|
984
|
+
return globalThis;
|
|
985
|
+
}
|
|
986
|
+
function nextFrame(cb) {
|
|
987
|
+
const h = host();
|
|
988
|
+
const raf = h.requestAnimationFrame;
|
|
989
|
+
if (typeof raf === "function") {
|
|
990
|
+
raf(() => raf(cb));
|
|
991
|
+
} else {
|
|
992
|
+
h.setTimeout(cb, 0);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function splitClass(value) {
|
|
996
|
+
if (value === null) return [];
|
|
997
|
+
const out = [];
|
|
998
|
+
for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
|
|
999
|
+
return out;
|
|
1000
|
+
}
|
|
1001
|
+
function addClasses(dom, el, classes) {
|
|
1002
|
+
if (classes.length === 0) return;
|
|
1003
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1004
|
+
let changed = false;
|
|
1005
|
+
for (const c of classes) {
|
|
1006
|
+
if (!current.includes(c)) {
|
|
1007
|
+
current.push(c);
|
|
1008
|
+
changed = true;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (changed) dom.setAttribute(el, "class", current.join(" "));
|
|
1012
|
+
}
|
|
1013
|
+
function removeClasses(dom, el, classes) {
|
|
1014
|
+
if (classes.length === 0) return;
|
|
1015
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1016
|
+
const next = current.filter((c) => !classes.includes(c));
|
|
1017
|
+
if (next.length !== current.length) {
|
|
1018
|
+
if (next.length === 0) dom.removeAttribute(el, "class");
|
|
1019
|
+
else dom.setAttribute(el, "class", next.join(" "));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function startRun(dom, el, active, from, to, duration, onDone) {
|
|
1023
|
+
const h = host();
|
|
1024
|
+
let finished = false;
|
|
1025
|
+
let timer = null;
|
|
1026
|
+
const onEvent = (e) => {
|
|
1027
|
+
if (e.target !== el) return;
|
|
1028
|
+
finish();
|
|
1029
|
+
};
|
|
1030
|
+
const detach = () => {
|
|
1031
|
+
dom.removeEventListener(el, "transitionend", onEvent);
|
|
1032
|
+
dom.removeEventListener(el, "animationend", onEvent);
|
|
1033
|
+
if (timer !== null) {
|
|
1034
|
+
h.clearTimeout(timer);
|
|
1035
|
+
timer = null;
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
const finish = () => {
|
|
1039
|
+
if (finished) return;
|
|
1040
|
+
finished = true;
|
|
1041
|
+
detach();
|
|
1042
|
+
removeClasses(dom, el, active);
|
|
1043
|
+
removeClasses(dom, el, to);
|
|
1044
|
+
removeClasses(dom, el, from);
|
|
1045
|
+
onDone();
|
|
1046
|
+
};
|
|
1047
|
+
addClasses(dom, el, from);
|
|
1048
|
+
addClasses(dom, el, active);
|
|
1049
|
+
dom.addEventListener(el, "transitionend", onEvent);
|
|
1050
|
+
dom.addEventListener(el, "animationend", onEvent);
|
|
1051
|
+
timer = h.setTimeout(finish, duration);
|
|
1052
|
+
nextFrame(() => {
|
|
1053
|
+
if (finished) return;
|
|
1054
|
+
removeClasses(dom, el, from);
|
|
1055
|
+
addClasses(dom, el, to);
|
|
1056
|
+
});
|
|
1057
|
+
return {
|
|
1058
|
+
cancel: () => {
|
|
1059
|
+
if (finished) return;
|
|
1060
|
+
finished = true;
|
|
1061
|
+
detach();
|
|
1062
|
+
removeClasses(dom, el, active);
|
|
1063
|
+
removeClasses(dom, el, to);
|
|
1064
|
+
removeClasses(dom, el, from);
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
var TransitionController = class {
|
|
1069
|
+
constructor(dom, graph, finalize) {
|
|
1070
|
+
this.dom = dom;
|
|
1071
|
+
this.graph = graph;
|
|
1072
|
+
this.finalize = finalize;
|
|
1073
|
+
}
|
|
1074
|
+
leaving = /* @__PURE__ */ new Map();
|
|
1075
|
+
/** True only in a real DOM environment (browser). */
|
|
1076
|
+
get browser() {
|
|
1077
|
+
return this.dom.body() !== null;
|
|
1078
|
+
}
|
|
1079
|
+
keyOf(inst) {
|
|
1080
|
+
return inst.graphNode.key ?? inst.graphNode.id;
|
|
1081
|
+
}
|
|
1082
|
+
resolved(node) {
|
|
1083
|
+
return getResolvedTransition(this.graph, node.id);
|
|
1084
|
+
}
|
|
1085
|
+
/** Run the enter animation for `inst` if it carries a transition (browser only). */
|
|
1086
|
+
enter(inst) {
|
|
1087
|
+
if (!this.browser) return;
|
|
1088
|
+
const rt = this.resolved(inst.graphNode);
|
|
1089
|
+
if (rt === void 0) return;
|
|
1090
|
+
const el = inst.domNode;
|
|
1091
|
+
if (!this.dom.isElement(el)) return;
|
|
1092
|
+
startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Play `appear` for any initial child that opted into it (fresh browser mount
|
|
1097
|
+
* only — hydration must never animate appear, §22, and this is called only on
|
|
1098
|
+
* the mount path).
|
|
1099
|
+
*/
|
|
1100
|
+
appear(children) {
|
|
1101
|
+
if (!this.browser) return;
|
|
1102
|
+
for (const child of children) {
|
|
1103
|
+
const rt = this.resolved(child.graphNode);
|
|
1104
|
+
if (rt !== void 0 && rt.appear) this.enter(child);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
hooks() {
|
|
1108
|
+
return {
|
|
1109
|
+
takeLeaving: (key) => {
|
|
1110
|
+
const entry = this.leaving.get(key);
|
|
1111
|
+
if (entry === void 0) return void 0;
|
|
1112
|
+
entry.run.cancel();
|
|
1113
|
+
this.leaving.delete(key);
|
|
1114
|
+
return entry.inst;
|
|
1115
|
+
},
|
|
1116
|
+
beginLeave: (inst) => {
|
|
1117
|
+
if (!this.browser) return false;
|
|
1118
|
+
const rt = this.resolved(inst.graphNode);
|
|
1119
|
+
if (rt === void 0) return false;
|
|
1120
|
+
const el = inst.domNode;
|
|
1121
|
+
if (!this.dom.isElement(el)) return false;
|
|
1122
|
+
const key = this.keyOf(inst);
|
|
1123
|
+
const prior = this.leaving.get(key);
|
|
1124
|
+
if (prior !== void 0) prior.run.cancel();
|
|
1125
|
+
const run = startRun(
|
|
1126
|
+
this.dom,
|
|
1127
|
+
el,
|
|
1128
|
+
rt.leaveActive,
|
|
1129
|
+
rt.leaveFrom,
|
|
1130
|
+
rt.leaveTo,
|
|
1131
|
+
rt.duration,
|
|
1132
|
+
() => {
|
|
1133
|
+
const current = this.leaving.get(key);
|
|
1134
|
+
if (current !== void 0 && current.run === run) {
|
|
1135
|
+
this.leaving.delete(key);
|
|
1136
|
+
this.finalize(inst);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
this.leaving.set(key, { inst, run });
|
|
1141
|
+
return true;
|
|
1142
|
+
},
|
|
1143
|
+
onEnter: (inst) => this.enter(inst)
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// ../renderer/src/head.ts
|
|
1149
|
+
var HEAD_MARKER = "data-streetui-head";
|
|
1150
|
+
var HEAD_KEY = "data-streetui-head-key";
|
|
1151
|
+
function isSignalLike(v) {
|
|
1152
|
+
return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
|
|
1153
|
+
}
|
|
1154
|
+
function readValue(v) {
|
|
1155
|
+
if (isSignalLike(v)) return String(v.peek() ?? "");
|
|
1156
|
+
return String(v ?? "");
|
|
1157
|
+
}
|
|
1158
|
+
function resolveEntry(entry) {
|
|
1159
|
+
const attrs = {};
|
|
1160
|
+
for (const key of Object.keys(entry.attrs)) {
|
|
1161
|
+
attrs[key] = readValue(entry.attrs[key]);
|
|
1162
|
+
}
|
|
1163
|
+
const resolved = { tag: entry.tag, attrs };
|
|
1164
|
+
if (entry.tag === "title") {
|
|
1165
|
+
return { ...resolved, text: readValue(entry.text) };
|
|
1166
|
+
}
|
|
1167
|
+
return resolved;
|
|
1168
|
+
}
|
|
1169
|
+
var HeadManager = class {
|
|
1170
|
+
_dom;
|
|
1171
|
+
_head;
|
|
1172
|
+
_contributions = /* @__PURE__ */ new Map();
|
|
1173
|
+
_applied = /* @__PURE__ */ new Map();
|
|
1174
|
+
_order = 0;
|
|
1175
|
+
_adopted = false;
|
|
1176
|
+
constructor(dom, head) {
|
|
1177
|
+
this._dom = dom;
|
|
1178
|
+
this._head = head;
|
|
1179
|
+
}
|
|
1180
|
+
/** Register (or replace) a node's contribution and re-apply the merged result. */
|
|
1181
|
+
register(nodeId, entries) {
|
|
1182
|
+
this._contributions.set(nodeId, { order: this._order++, entries });
|
|
1183
|
+
this.apply();
|
|
1184
|
+
}
|
|
1185
|
+
/** Withdraw a node's contribution (unmount / route change) and re-apply. */
|
|
1186
|
+
unregister(nodeId) {
|
|
1187
|
+
if (this._contributions.delete(nodeId)) this.apply();
|
|
1188
|
+
}
|
|
1189
|
+
/** Recompute the merged head and patch `document.head` to match. */
|
|
1190
|
+
apply() {
|
|
1191
|
+
if (!this._adopted) {
|
|
1192
|
+
this._adoptServerTags();
|
|
1193
|
+
this._adopted = true;
|
|
1194
|
+
}
|
|
1195
|
+
const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
|
|
1196
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1197
|
+
for (const contribution of ordered) {
|
|
1198
|
+
for (const entry of contribution.entries) {
|
|
1199
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
for (const [key, desired] of merged) {
|
|
1203
|
+
const existing = this._applied.get(key);
|
|
1204
|
+
if (existing !== void 0 && existing.tag === desired.tag) {
|
|
1205
|
+
this._reconcileAttrs(existing, desired);
|
|
1206
|
+
} else {
|
|
1207
|
+
if (existing !== void 0) {
|
|
1208
|
+
this._dom.removeChild(this._head, existing.el);
|
|
1209
|
+
this._applied.delete(key);
|
|
1210
|
+
}
|
|
1211
|
+
const el = this._createTag(key, desired);
|
|
1212
|
+
this._dom.appendChild(this._head, el);
|
|
1213
|
+
this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
for (const [key, record] of [...this._applied]) {
|
|
1217
|
+
if (!merged.has(key)) {
|
|
1218
|
+
this._dom.removeChild(this._head, record.el);
|
|
1219
|
+
this._applied.delete(key);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
_createTag(key, desired) {
|
|
1224
|
+
const el = this._dom.createElement(desired.tag);
|
|
1225
|
+
this._dom.setAttribute(el, HEAD_MARKER, "");
|
|
1226
|
+
this._dom.setAttribute(el, HEAD_KEY, key);
|
|
1227
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1228
|
+
this._dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1229
|
+
}
|
|
1230
|
+
if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
|
|
1231
|
+
return el;
|
|
1232
|
+
}
|
|
1233
|
+
_reconcileAttrs(record, desired) {
|
|
1234
|
+
const nextKeys = new Set(Object.keys(desired.attrs));
|
|
1235
|
+
for (const attr of record.attrKeys) {
|
|
1236
|
+
if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
|
|
1237
|
+
}
|
|
1238
|
+
for (const attr of nextKeys) {
|
|
1239
|
+
this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
|
|
1240
|
+
}
|
|
1241
|
+
if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
|
|
1242
|
+
record.attrKeys = nextKeys;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
|
|
1246
|
+
* in `document.head`. The subsequent diff reuses these elements when the
|
|
1247
|
+
* client desires the same key (no duplicate), rewrites them if the value
|
|
1248
|
+
* changed, or removes them if the client graph no longer wants them.
|
|
1249
|
+
*/
|
|
1250
|
+
_adoptServerTags() {
|
|
1251
|
+
for (const child of this._dom.childNodes(this._head)) {
|
|
1252
|
+
if (!this._dom.isElement(child)) continue;
|
|
1253
|
+
const el = child;
|
|
1254
|
+
const key = this._dom.getAttribute(el, HEAD_KEY);
|
|
1255
|
+
if (key === null) continue;
|
|
1256
|
+
this._applied.set(key, {
|
|
1257
|
+
el,
|
|
1258
|
+
attrKeys: new Set(this._attrNames(el)),
|
|
1259
|
+
tag: this._dom.tagName(el)
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
/** The framework-managed attribute names currently on a server tag. */
|
|
1264
|
+
_attrNames(el) {
|
|
1265
|
+
const names = [];
|
|
1266
|
+
if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
|
|
1267
|
+
if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
|
|
1268
|
+
return names;
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
function getHeadManager(ctx) {
|
|
1272
|
+
const head = ctx.dom.head();
|
|
1273
|
+
if (head === null) return null;
|
|
1274
|
+
const mutable = ctx;
|
|
1275
|
+
if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
|
|
1276
|
+
return mutable.head;
|
|
1277
|
+
}
|
|
1278
|
+
function wireHeadBehavior(ctx, graphNode, instance) {
|
|
1279
|
+
const manager = getHeadManager(ctx);
|
|
1280
|
+
if (manager === null) return;
|
|
1281
|
+
const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
|
|
1282
|
+
if (descFn === void 0) return;
|
|
1283
|
+
const contribution = descFn();
|
|
1284
|
+
const nodeId = graphNode.id;
|
|
1285
|
+
manager.register(nodeId, contribution.entries);
|
|
1286
|
+
for (const entry of contribution.entries) {
|
|
1287
|
+
for (const attrKey of Object.keys(entry.attrs)) {
|
|
1288
|
+
const v = entry.attrs[attrKey];
|
|
1289
|
+
if (isSignalLike(v)) {
|
|
1290
|
+
instance.trackCleanup(v.subscribe(() => manager.apply()));
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (isSignalLike(entry.text)) {
|
|
1294
|
+
instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
instance.trackCleanup(() => manager.unregister(nodeId));
|
|
1298
|
+
}
|
|
1299
|
+
function renderHead(compiled) {
|
|
1300
|
+
const graph = compiled.graph;
|
|
1301
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1302
|
+
graph.walk((node) => {
|
|
1303
|
+
if (node.type !== "head") return;
|
|
1304
|
+
const descFn = graph.getHandler(`__head__${node.id}`);
|
|
1305
|
+
if (descFn === void 0) return;
|
|
1306
|
+
for (const entry of descFn().entries) {
|
|
1307
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
if (merged.size === 0) return "";
|
|
1311
|
+
const dom = new ServerDOMAdapter();
|
|
1312
|
+
let out = "";
|
|
1313
|
+
for (const [key, desired] of merged) {
|
|
1314
|
+
const el = dom.createElement(desired.tag);
|
|
1315
|
+
dom.setAttribute(el, HEAD_MARKER, "");
|
|
1316
|
+
dom.setAttribute(el, HEAD_KEY, key);
|
|
1317
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1318
|
+
dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1319
|
+
}
|
|
1320
|
+
if (desired.tag === "title") dom.setTextContent(el, desired.text ?? "");
|
|
1321
|
+
out += dom.serializeOuter(el);
|
|
1322
|
+
}
|
|
1323
|
+
return out;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
917
1326
|
// ../renderer/src/mount.ts
|
|
918
1327
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
919
1328
|
"text",
|
|
@@ -1036,6 +1445,9 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1036
1445
|
const instance2 = new NodeInstance(graphNode, el2);
|
|
1037
1446
|
ctx.instances.set(graphNode.id, instance2);
|
|
1038
1447
|
wireEvents(dom, graph, graphNode, el2, instance2);
|
|
1448
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
1449
|
+
wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
|
|
1450
|
+
}
|
|
1039
1451
|
dom.appendChild(parentDom, el2);
|
|
1040
1452
|
return instance2;
|
|
1041
1453
|
}
|
|
@@ -1066,7 +1478,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1066
1478
|
instance2.addChild(childInstance);
|
|
1067
1479
|
}
|
|
1068
1480
|
dom.appendChild(parentDom, el2);
|
|
1069
|
-
wireReactiveList(ctx, graphNode, instance2, el2);
|
|
1481
|
+
wireReactiveList(ctx, graphNode, instance2, el2, true);
|
|
1070
1482
|
return instance2;
|
|
1071
1483
|
}
|
|
1072
1484
|
if (graphNode.type === "portal") {
|
|
@@ -1091,6 +1503,16 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1091
1503
|
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
1092
1504
|
return instance2;
|
|
1093
1505
|
}
|
|
1506
|
+
if (graphNode.type === "head") {
|
|
1507
|
+
const anchor = dom.createElement(resolveTag("head"));
|
|
1508
|
+
dom.setAttribute(anchor, "data-streetui-head-anchor", "");
|
|
1509
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
1510
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
1511
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
1512
|
+
dom.appendChild(parentDom, anchor);
|
|
1513
|
+
wireHeadBehavior(ctx, graphNode, instance2);
|
|
1514
|
+
return instance2;
|
|
1515
|
+
}
|
|
1094
1516
|
const tag = resolveTag(graphNode.type);
|
|
1095
1517
|
const el = dom.createElement(tag);
|
|
1096
1518
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -1155,6 +1577,17 @@ function buttonUpdate(dom, el) {
|
|
|
1155
1577
|
}
|
|
1156
1578
|
};
|
|
1157
1579
|
}
|
|
1580
|
+
function linkUpdate(dom, el) {
|
|
1581
|
+
return (propKey, value) => {
|
|
1582
|
+
if (propKey === "label") {
|
|
1583
|
+
dom.setTextContent(el, String(value ?? ""));
|
|
1584
|
+
} else if (propKey === "href") {
|
|
1585
|
+
dom.setAttribute(el, "href", String(value ?? ""));
|
|
1586
|
+
} else {
|
|
1587
|
+
applyProp(dom, el, propKey, value);
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1158
1591
|
function applyNodeProps(ctx, graphNode, el) {
|
|
1159
1592
|
const props = graphNode.props;
|
|
1160
1593
|
for (const key in props) {
|
|
@@ -1175,32 +1608,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
|
|
|
1175
1608
|
instance.trackCleanup(unsub);
|
|
1176
1609
|
}
|
|
1177
1610
|
}
|
|
1178
|
-
function wireReactiveList(ctx, graphNode, instance, el) {
|
|
1611
|
+
function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
|
|
1179
1612
|
const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
|
|
1180
1613
|
const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
|
|
1181
1614
|
if (plan === void 0 && build === void 0) return;
|
|
1615
|
+
const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
|
|
1616
|
+
const parent = ctx.dom.parentNode(leaving.domNode);
|
|
1617
|
+
if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
|
|
1618
|
+
leaving.dispose();
|
|
1619
|
+
forgetInstance(ctx, leaving);
|
|
1620
|
+
ctx.graph.detachNode(leaving.graphNode);
|
|
1621
|
+
});
|
|
1622
|
+
const hooks = controller.hooks();
|
|
1623
|
+
if (runAppear) controller.appear(instance.children);
|
|
1182
1624
|
for (const stateRef of graphNode.stateRefs) {
|
|
1183
1625
|
if (stateRef.propKey !== "items") continue;
|
|
1184
1626
|
const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
|
|
1185
1627
|
if (sig === void 0 || typeof sig.subscribe !== "function") continue;
|
|
1186
1628
|
const unsub = sig.subscribe((value) => {
|
|
1187
1629
|
if (plan !== void 0) {
|
|
1188
|
-
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
|
|
1630
|
+
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
|
|
1189
1631
|
} else {
|
|
1190
|
-
reconcileReactiveList(ctx, graphNode, instance, el, build(value));
|
|
1632
|
+
reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
|
|
1191
1633
|
}
|
|
1192
1634
|
});
|
|
1193
1635
|
instance.trackCleanup(unsub);
|
|
1194
1636
|
}
|
|
1195
1637
|
}
|
|
1196
|
-
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
|
|
1638
|
+
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
|
|
1197
1639
|
const oldInstances = [...listInstance.children];
|
|
1198
1640
|
const result = reconcileChildrenByPlan(
|
|
1199
1641
|
ctx,
|
|
1200
1642
|
listEl,
|
|
1201
1643
|
oldInstances,
|
|
1202
1644
|
plan,
|
|
1203
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1645
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1646
|
+
hooks
|
|
1204
1647
|
);
|
|
1205
1648
|
listInstance.children.length = 0;
|
|
1206
1649
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1215,14 +1658,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
|
|
|
1215
1658
|
for (const child of [...listNode.children]) listNode.removeChild(child);
|
|
1216
1659
|
for (const inst of result.instances) listNode.appendChild(inst.graphNode);
|
|
1217
1660
|
}
|
|
1218
|
-
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
|
|
1661
|
+
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
|
|
1219
1662
|
const oldInstances = [...listInstance.children];
|
|
1220
1663
|
const result = reconcileChildren(
|
|
1221
1664
|
ctx,
|
|
1222
1665
|
listEl,
|
|
1223
1666
|
oldInstances,
|
|
1224
1667
|
newNodes,
|
|
1225
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1668
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1669
|
+
hooks
|
|
1226
1670
|
);
|
|
1227
1671
|
listInstance.children.length = 0;
|
|
1228
1672
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1258,11 +1702,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1258
1702
|
const onOpenChange = (isOpen) => {
|
|
1259
1703
|
if (isOpen) {
|
|
1260
1704
|
if (desc.restoreFocus) saved = saveFocus(dom);
|
|
1261
|
-
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1262
1705
|
if (desc.modal) {
|
|
1263
1706
|
active.push(trapFocus(dom, target));
|
|
1264
1707
|
active.push(containFocus(dom, target));
|
|
1265
1708
|
}
|
|
1709
|
+
if (desc.menu) {
|
|
1710
|
+
active.push(rovingMenu(dom, target));
|
|
1711
|
+
}
|
|
1712
|
+
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1266
1713
|
if (desc.closeOnEscape && desc.onClose !== void 0) {
|
|
1267
1714
|
active.push(onEscape(dom, target, desc.onClose));
|
|
1268
1715
|
}
|
|
@@ -1352,7 +1799,8 @@ function analyzeGraph(graph) {
|
|
|
1352
1799
|
const isConditional = node.type === "conditional";
|
|
1353
1800
|
const isPortal = node.type === "portal";
|
|
1354
1801
|
const isComponent = node.type === "component";
|
|
1355
|
-
const
|
|
1802
|
+
const isHead = node.type === "head";
|
|
1803
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
|
|
1356
1804
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
1357
1805
|
nodes.set(node.id, {
|
|
1358
1806
|
isStatic,
|
|
@@ -1439,12 +1887,13 @@ function renderToString(compiled, options = {}) {
|
|
|
1439
1887
|
}
|
|
1440
1888
|
|
|
1441
1889
|
// src/version.ts
|
|
1442
|
-
var VERSION = "
|
|
1890
|
+
var VERSION = "2.0.0";
|
|
1443
1891
|
export {
|
|
1444
1892
|
STATE_MARKER_ATTR,
|
|
1445
1893
|
ServerDOMAdapter,
|
|
1446
1894
|
VERSION,
|
|
1447
1895
|
readState,
|
|
1896
|
+
renderHead,
|
|
1448
1897
|
renderToString,
|
|
1449
1898
|
serializeState
|
|
1450
1899
|
};
|