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.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(server_exports, {
|
|
|
24
24
|
ServerDOMAdapter: () => ServerDOMAdapter,
|
|
25
25
|
VERSION: () => VERSION,
|
|
26
26
|
readState: () => readState,
|
|
27
|
+
renderHead: () => renderHead,
|
|
27
28
|
renderToString: () => renderToString,
|
|
28
29
|
serializeState: () => serializeState
|
|
29
30
|
});
|
|
@@ -531,6 +532,9 @@ var ServerDOMAdapter = class {
|
|
|
531
532
|
body() {
|
|
532
533
|
return null;
|
|
533
534
|
}
|
|
535
|
+
head() {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
534
538
|
activeElement() {
|
|
535
539
|
return null;
|
|
536
540
|
}
|
|
@@ -644,18 +648,25 @@ function trapFocus(dom, container) {
|
|
|
644
648
|
dom.addEventListener(container, "keydown", onKeydown);
|
|
645
649
|
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
646
650
|
}
|
|
651
|
+
var containmentStack = [];
|
|
647
652
|
function containFocus(dom, container) {
|
|
648
653
|
const body = dom.body();
|
|
649
654
|
if (body === null) return () => {
|
|
650
655
|
};
|
|
656
|
+
containmentStack.push(container);
|
|
651
657
|
const onFocusIn = (event) => {
|
|
658
|
+
if (containmentStack[containmentStack.length - 1] !== container) return;
|
|
652
659
|
const target = event.target;
|
|
653
660
|
if (target !== null && !dom.contains(container, target)) {
|
|
654
661
|
focusFirst(dom, container);
|
|
655
662
|
}
|
|
656
663
|
};
|
|
657
664
|
dom.addEventListener(body, "focusin", onFocusIn);
|
|
658
|
-
return () =>
|
|
665
|
+
return () => {
|
|
666
|
+
dom.removeEventListener(body, "focusin", onFocusIn);
|
|
667
|
+
const index = containmentStack.lastIndexOf(container);
|
|
668
|
+
if (index !== -1) containmentStack.splice(index, 1);
|
|
669
|
+
};
|
|
659
670
|
}
|
|
660
671
|
function onEscape(dom, target, handler) {
|
|
661
672
|
const onKeydown = (event) => {
|
|
@@ -664,6 +675,33 @@ function onEscape(dom, target, handler) {
|
|
|
664
675
|
dom.addEventListener(target, "keydown", onKeydown);
|
|
665
676
|
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
666
677
|
}
|
|
678
|
+
function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
679
|
+
const onKeydown = (event) => {
|
|
680
|
+
const key = event.key;
|
|
681
|
+
const isActivate = key === "Enter" || key === " " || key === "Spacebar";
|
|
682
|
+
const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
|
|
683
|
+
if (!isActivate && !isMove) return;
|
|
684
|
+
const items = getFocusable(dom, container, selector);
|
|
685
|
+
if (items.length === 0) return;
|
|
686
|
+
const active = dom.activeElement();
|
|
687
|
+
const index = active === null ? -1 : items.indexOf(active);
|
|
688
|
+
if (isActivate) {
|
|
689
|
+
if (index < 0) return;
|
|
690
|
+
event.preventDefault();
|
|
691
|
+
items[index].click?.();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
event.preventDefault();
|
|
695
|
+
let next;
|
|
696
|
+
if (key === "Home") next = 0;
|
|
697
|
+
else if (key === "End") next = items.length - 1;
|
|
698
|
+
else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
|
|
699
|
+
else next = index <= 0 ? items.length - 1 : index - 1;
|
|
700
|
+
dom.focus(items[next]);
|
|
701
|
+
};
|
|
702
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
703
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
704
|
+
}
|
|
667
705
|
|
|
668
706
|
// ../renderer/src/tag-map.ts
|
|
669
707
|
var TAG_MAP = {
|
|
@@ -688,7 +726,13 @@ var TAG_MAP = {
|
|
|
688
726
|
// its children are relocated to a document.body container on the browser
|
|
689
727
|
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
690
728
|
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
691
|
-
portal: "div"
|
|
729
|
+
portal: "div",
|
|
730
|
+
// A `head()` node renders as a neutral, empty inline anchor <div> at its
|
|
731
|
+
// declaration site (like a portal anchor). Its actual contribution — title/
|
|
732
|
+
// meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
|
|
733
|
+
// browser, and emitted separately by `renderHead()` on the server. Keeping a
|
|
734
|
+
// one-node/one-element anchor preserves positional hydration.
|
|
735
|
+
head: "div"
|
|
692
736
|
};
|
|
693
737
|
function resolveTag(type) {
|
|
694
738
|
return TAG_MAP[type] ?? "div";
|
|
@@ -736,7 +780,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
|
|
|
736
780
|
}
|
|
737
781
|
|
|
738
782
|
// ../renderer/src/reconciliation.ts
|
|
739
|
-
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
783
|
+
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
|
|
740
784
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
741
785
|
for (const inst of oldInstances) {
|
|
742
786
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
@@ -757,28 +801,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
|
757
801
|
}
|
|
758
802
|
newInstances.push(existing);
|
|
759
803
|
} else {
|
|
760
|
-
const
|
|
761
|
-
|
|
804
|
+
const reclaimed = hooks?.takeLeaving(key);
|
|
805
|
+
if (reclaimed !== void 0) {
|
|
806
|
+
patchExistingInstance(ctx, reclaimed, newNode);
|
|
807
|
+
reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
|
|
808
|
+
hooks?.onEnter(reclaimed);
|
|
809
|
+
newInstances.push(reclaimed);
|
|
810
|
+
} else {
|
|
811
|
+
const inst = mountFn(newNode, parentDom);
|
|
812
|
+
hooks?.onEnter(inst);
|
|
813
|
+
newInstances.push(inst);
|
|
814
|
+
}
|
|
762
815
|
}
|
|
763
816
|
}
|
|
764
817
|
const removed = [];
|
|
765
818
|
for (const inst of oldInstances) {
|
|
766
819
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
767
|
-
if (
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
for (const inst of removed) {
|
|
820
|
+
if (usedKeys.has(key)) continue;
|
|
821
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
772
822
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
773
|
-
if (parent !== null)
|
|
774
|
-
ctx.dom.removeChild(parent, inst.domNode);
|
|
775
|
-
}
|
|
823
|
+
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
776
824
|
inst.dispose();
|
|
825
|
+
removed.push(inst);
|
|
777
826
|
}
|
|
778
827
|
reorderDom(ctx, parentDom, newInstances);
|
|
779
828
|
return { instances: newInstances, removed };
|
|
780
829
|
}
|
|
781
|
-
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
830
|
+
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
|
|
782
831
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
783
832
|
for (const inst of oldInstances) {
|
|
784
833
|
oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
|
|
@@ -805,21 +854,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
|
805
854
|
}
|
|
806
855
|
newInstances.push(existing);
|
|
807
856
|
} else {
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
857
|
+
const reclaimed = hooks?.takeLeaving(entry.key);
|
|
858
|
+
if (reclaimed !== void 0) {
|
|
859
|
+
const freshNode = entry.build();
|
|
860
|
+
built.push(freshNode);
|
|
861
|
+
patchExistingInstance(ctx, reclaimed, freshNode);
|
|
862
|
+
reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
|
|
863
|
+
reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
|
|
864
|
+
reclaimed.graphNode.setProp("_item", entry.item);
|
|
865
|
+
hooks?.onEnter(reclaimed);
|
|
866
|
+
newInstances.push(reclaimed);
|
|
867
|
+
} else {
|
|
868
|
+
const freshNode = entry.build();
|
|
869
|
+
built.push(freshNode);
|
|
870
|
+
const inst = mountFn(freshNode, parentDom);
|
|
871
|
+
hooks?.onEnter(inst);
|
|
872
|
+
newInstances.push(inst);
|
|
873
|
+
}
|
|
812
874
|
}
|
|
813
875
|
}
|
|
814
876
|
const removed = [];
|
|
815
877
|
for (const inst of oldInstances) {
|
|
816
878
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
817
|
-
if (
|
|
818
|
-
|
|
819
|
-
for (const inst of removed) {
|
|
879
|
+
if (usedKeys.has(key)) continue;
|
|
880
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
820
881
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
821
882
|
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
822
883
|
inst.dispose();
|
|
884
|
+
removed.push(inst);
|
|
823
885
|
}
|
|
824
886
|
reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
|
|
825
887
|
return { instances: newInstances, removed, built };
|
|
@@ -945,6 +1007,354 @@ function patchExistingInstance(ctx, instance, newNode) {
|
|
|
945
1007
|
}
|
|
946
1008
|
}
|
|
947
1009
|
|
|
1010
|
+
// ../renderer/src/transition.ts
|
|
1011
|
+
function getResolvedTransition(graph, nodeId) {
|
|
1012
|
+
const fn = graph.getHandler(`__transition__${nodeId}`);
|
|
1013
|
+
return fn === void 0 ? void 0 : fn();
|
|
1014
|
+
}
|
|
1015
|
+
function host() {
|
|
1016
|
+
return globalThis;
|
|
1017
|
+
}
|
|
1018
|
+
function nextFrame(cb) {
|
|
1019
|
+
const h = host();
|
|
1020
|
+
const raf = h.requestAnimationFrame;
|
|
1021
|
+
if (typeof raf === "function") {
|
|
1022
|
+
raf(() => raf(cb));
|
|
1023
|
+
} else {
|
|
1024
|
+
h.setTimeout(cb, 0);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function splitClass(value) {
|
|
1028
|
+
if (value === null) return [];
|
|
1029
|
+
const out = [];
|
|
1030
|
+
for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
|
|
1031
|
+
return out;
|
|
1032
|
+
}
|
|
1033
|
+
function addClasses(dom, el, classes) {
|
|
1034
|
+
if (classes.length === 0) return;
|
|
1035
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1036
|
+
let changed = false;
|
|
1037
|
+
for (const c of classes) {
|
|
1038
|
+
if (!current.includes(c)) {
|
|
1039
|
+
current.push(c);
|
|
1040
|
+
changed = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (changed) dom.setAttribute(el, "class", current.join(" "));
|
|
1044
|
+
}
|
|
1045
|
+
function removeClasses(dom, el, classes) {
|
|
1046
|
+
if (classes.length === 0) return;
|
|
1047
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1048
|
+
const next = current.filter((c) => !classes.includes(c));
|
|
1049
|
+
if (next.length !== current.length) {
|
|
1050
|
+
if (next.length === 0) dom.removeAttribute(el, "class");
|
|
1051
|
+
else dom.setAttribute(el, "class", next.join(" "));
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function startRun(dom, el, active, from, to, duration, onDone) {
|
|
1055
|
+
const h = host();
|
|
1056
|
+
let finished = false;
|
|
1057
|
+
let timer = null;
|
|
1058
|
+
const onEvent = (e) => {
|
|
1059
|
+
if (e.target !== el) return;
|
|
1060
|
+
finish();
|
|
1061
|
+
};
|
|
1062
|
+
const detach = () => {
|
|
1063
|
+
dom.removeEventListener(el, "transitionend", onEvent);
|
|
1064
|
+
dom.removeEventListener(el, "animationend", onEvent);
|
|
1065
|
+
if (timer !== null) {
|
|
1066
|
+
h.clearTimeout(timer);
|
|
1067
|
+
timer = null;
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
const finish = () => {
|
|
1071
|
+
if (finished) return;
|
|
1072
|
+
finished = true;
|
|
1073
|
+
detach();
|
|
1074
|
+
removeClasses(dom, el, active);
|
|
1075
|
+
removeClasses(dom, el, to);
|
|
1076
|
+
removeClasses(dom, el, from);
|
|
1077
|
+
onDone();
|
|
1078
|
+
};
|
|
1079
|
+
addClasses(dom, el, from);
|
|
1080
|
+
addClasses(dom, el, active);
|
|
1081
|
+
dom.addEventListener(el, "transitionend", onEvent);
|
|
1082
|
+
dom.addEventListener(el, "animationend", onEvent);
|
|
1083
|
+
timer = h.setTimeout(finish, duration);
|
|
1084
|
+
nextFrame(() => {
|
|
1085
|
+
if (finished) return;
|
|
1086
|
+
removeClasses(dom, el, from);
|
|
1087
|
+
addClasses(dom, el, to);
|
|
1088
|
+
});
|
|
1089
|
+
return {
|
|
1090
|
+
cancel: () => {
|
|
1091
|
+
if (finished) return;
|
|
1092
|
+
finished = true;
|
|
1093
|
+
detach();
|
|
1094
|
+
removeClasses(dom, el, active);
|
|
1095
|
+
removeClasses(dom, el, to);
|
|
1096
|
+
removeClasses(dom, el, from);
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
var TransitionController = class {
|
|
1101
|
+
constructor(dom, graph, finalize) {
|
|
1102
|
+
this.dom = dom;
|
|
1103
|
+
this.graph = graph;
|
|
1104
|
+
this.finalize = finalize;
|
|
1105
|
+
}
|
|
1106
|
+
leaving = /* @__PURE__ */ new Map();
|
|
1107
|
+
/** True only in a real DOM environment (browser). */
|
|
1108
|
+
get browser() {
|
|
1109
|
+
return this.dom.body() !== null;
|
|
1110
|
+
}
|
|
1111
|
+
keyOf(inst) {
|
|
1112
|
+
return inst.graphNode.key ?? inst.graphNode.id;
|
|
1113
|
+
}
|
|
1114
|
+
resolved(node) {
|
|
1115
|
+
return getResolvedTransition(this.graph, node.id);
|
|
1116
|
+
}
|
|
1117
|
+
/** Run the enter animation for `inst` if it carries a transition (browser only). */
|
|
1118
|
+
enter(inst) {
|
|
1119
|
+
if (!this.browser) return;
|
|
1120
|
+
const rt = this.resolved(inst.graphNode);
|
|
1121
|
+
if (rt === void 0) return;
|
|
1122
|
+
const el = inst.domNode;
|
|
1123
|
+
if (!this.dom.isElement(el)) return;
|
|
1124
|
+
startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Play `appear` for any initial child that opted into it (fresh browser mount
|
|
1129
|
+
* only — hydration must never animate appear, §22, and this is called only on
|
|
1130
|
+
* the mount path).
|
|
1131
|
+
*/
|
|
1132
|
+
appear(children) {
|
|
1133
|
+
if (!this.browser) return;
|
|
1134
|
+
for (const child of children) {
|
|
1135
|
+
const rt = this.resolved(child.graphNode);
|
|
1136
|
+
if (rt !== void 0 && rt.appear) this.enter(child);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
hooks() {
|
|
1140
|
+
return {
|
|
1141
|
+
takeLeaving: (key) => {
|
|
1142
|
+
const entry = this.leaving.get(key);
|
|
1143
|
+
if (entry === void 0) return void 0;
|
|
1144
|
+
entry.run.cancel();
|
|
1145
|
+
this.leaving.delete(key);
|
|
1146
|
+
return entry.inst;
|
|
1147
|
+
},
|
|
1148
|
+
beginLeave: (inst) => {
|
|
1149
|
+
if (!this.browser) return false;
|
|
1150
|
+
const rt = this.resolved(inst.graphNode);
|
|
1151
|
+
if (rt === void 0) return false;
|
|
1152
|
+
const el = inst.domNode;
|
|
1153
|
+
if (!this.dom.isElement(el)) return false;
|
|
1154
|
+
const key = this.keyOf(inst);
|
|
1155
|
+
const prior = this.leaving.get(key);
|
|
1156
|
+
if (prior !== void 0) prior.run.cancel();
|
|
1157
|
+
const run = startRun(
|
|
1158
|
+
this.dom,
|
|
1159
|
+
el,
|
|
1160
|
+
rt.leaveActive,
|
|
1161
|
+
rt.leaveFrom,
|
|
1162
|
+
rt.leaveTo,
|
|
1163
|
+
rt.duration,
|
|
1164
|
+
() => {
|
|
1165
|
+
const current = this.leaving.get(key);
|
|
1166
|
+
if (current !== void 0 && current.run === run) {
|
|
1167
|
+
this.leaving.delete(key);
|
|
1168
|
+
this.finalize(inst);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
this.leaving.set(key, { inst, run });
|
|
1173
|
+
return true;
|
|
1174
|
+
},
|
|
1175
|
+
onEnter: (inst) => this.enter(inst)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// ../renderer/src/head.ts
|
|
1181
|
+
var HEAD_MARKER = "data-streetui-head";
|
|
1182
|
+
var HEAD_KEY = "data-streetui-head-key";
|
|
1183
|
+
function isSignalLike(v) {
|
|
1184
|
+
return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
|
|
1185
|
+
}
|
|
1186
|
+
function readValue(v) {
|
|
1187
|
+
if (isSignalLike(v)) return String(v.peek() ?? "");
|
|
1188
|
+
return String(v ?? "");
|
|
1189
|
+
}
|
|
1190
|
+
function resolveEntry(entry) {
|
|
1191
|
+
const attrs = {};
|
|
1192
|
+
for (const key of Object.keys(entry.attrs)) {
|
|
1193
|
+
attrs[key] = readValue(entry.attrs[key]);
|
|
1194
|
+
}
|
|
1195
|
+
const resolved = { tag: entry.tag, attrs };
|
|
1196
|
+
if (entry.tag === "title") {
|
|
1197
|
+
return { ...resolved, text: readValue(entry.text) };
|
|
1198
|
+
}
|
|
1199
|
+
return resolved;
|
|
1200
|
+
}
|
|
1201
|
+
var HeadManager = class {
|
|
1202
|
+
_dom;
|
|
1203
|
+
_head;
|
|
1204
|
+
_contributions = /* @__PURE__ */ new Map();
|
|
1205
|
+
_applied = /* @__PURE__ */ new Map();
|
|
1206
|
+
_order = 0;
|
|
1207
|
+
_adopted = false;
|
|
1208
|
+
constructor(dom, head) {
|
|
1209
|
+
this._dom = dom;
|
|
1210
|
+
this._head = head;
|
|
1211
|
+
}
|
|
1212
|
+
/** Register (or replace) a node's contribution and re-apply the merged result. */
|
|
1213
|
+
register(nodeId, entries) {
|
|
1214
|
+
this._contributions.set(nodeId, { order: this._order++, entries });
|
|
1215
|
+
this.apply();
|
|
1216
|
+
}
|
|
1217
|
+
/** Withdraw a node's contribution (unmount / route change) and re-apply. */
|
|
1218
|
+
unregister(nodeId) {
|
|
1219
|
+
if (this._contributions.delete(nodeId)) this.apply();
|
|
1220
|
+
}
|
|
1221
|
+
/** Recompute the merged head and patch `document.head` to match. */
|
|
1222
|
+
apply() {
|
|
1223
|
+
if (!this._adopted) {
|
|
1224
|
+
this._adoptServerTags();
|
|
1225
|
+
this._adopted = true;
|
|
1226
|
+
}
|
|
1227
|
+
const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
|
|
1228
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1229
|
+
for (const contribution of ordered) {
|
|
1230
|
+
for (const entry of contribution.entries) {
|
|
1231
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
for (const [key, desired] of merged) {
|
|
1235
|
+
const existing = this._applied.get(key);
|
|
1236
|
+
if (existing !== void 0 && existing.tag === desired.tag) {
|
|
1237
|
+
this._reconcileAttrs(existing, desired);
|
|
1238
|
+
} else {
|
|
1239
|
+
if (existing !== void 0) {
|
|
1240
|
+
this._dom.removeChild(this._head, existing.el);
|
|
1241
|
+
this._applied.delete(key);
|
|
1242
|
+
}
|
|
1243
|
+
const el = this._createTag(key, desired);
|
|
1244
|
+
this._dom.appendChild(this._head, el);
|
|
1245
|
+
this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
for (const [key, record] of [...this._applied]) {
|
|
1249
|
+
if (!merged.has(key)) {
|
|
1250
|
+
this._dom.removeChild(this._head, record.el);
|
|
1251
|
+
this._applied.delete(key);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
_createTag(key, desired) {
|
|
1256
|
+
const el = this._dom.createElement(desired.tag);
|
|
1257
|
+
this._dom.setAttribute(el, HEAD_MARKER, "");
|
|
1258
|
+
this._dom.setAttribute(el, HEAD_KEY, key);
|
|
1259
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1260
|
+
this._dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1261
|
+
}
|
|
1262
|
+
if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
|
|
1263
|
+
return el;
|
|
1264
|
+
}
|
|
1265
|
+
_reconcileAttrs(record, desired) {
|
|
1266
|
+
const nextKeys = new Set(Object.keys(desired.attrs));
|
|
1267
|
+
for (const attr of record.attrKeys) {
|
|
1268
|
+
if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
|
|
1269
|
+
}
|
|
1270
|
+
for (const attr of nextKeys) {
|
|
1271
|
+
this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
|
|
1272
|
+
}
|
|
1273
|
+
if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
|
|
1274
|
+
record.attrKeys = nextKeys;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
|
|
1278
|
+
* in `document.head`. The subsequent diff reuses these elements when the
|
|
1279
|
+
* client desires the same key (no duplicate), rewrites them if the value
|
|
1280
|
+
* changed, or removes them if the client graph no longer wants them.
|
|
1281
|
+
*/
|
|
1282
|
+
_adoptServerTags() {
|
|
1283
|
+
for (const child of this._dom.childNodes(this._head)) {
|
|
1284
|
+
if (!this._dom.isElement(child)) continue;
|
|
1285
|
+
const el = child;
|
|
1286
|
+
const key = this._dom.getAttribute(el, HEAD_KEY);
|
|
1287
|
+
if (key === null) continue;
|
|
1288
|
+
this._applied.set(key, {
|
|
1289
|
+
el,
|
|
1290
|
+
attrKeys: new Set(this._attrNames(el)),
|
|
1291
|
+
tag: this._dom.tagName(el)
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/** The framework-managed attribute names currently on a server tag. */
|
|
1296
|
+
_attrNames(el) {
|
|
1297
|
+
const names = [];
|
|
1298
|
+
if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
|
|
1299
|
+
if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
|
|
1300
|
+
return names;
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
function getHeadManager(ctx) {
|
|
1304
|
+
const head = ctx.dom.head();
|
|
1305
|
+
if (head === null) return null;
|
|
1306
|
+
const mutable = ctx;
|
|
1307
|
+
if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
|
|
1308
|
+
return mutable.head;
|
|
1309
|
+
}
|
|
1310
|
+
function wireHeadBehavior(ctx, graphNode, instance) {
|
|
1311
|
+
const manager = getHeadManager(ctx);
|
|
1312
|
+
if (manager === null) return;
|
|
1313
|
+
const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
|
|
1314
|
+
if (descFn === void 0) return;
|
|
1315
|
+
const contribution = descFn();
|
|
1316
|
+
const nodeId = graphNode.id;
|
|
1317
|
+
manager.register(nodeId, contribution.entries);
|
|
1318
|
+
for (const entry of contribution.entries) {
|
|
1319
|
+
for (const attrKey of Object.keys(entry.attrs)) {
|
|
1320
|
+
const v = entry.attrs[attrKey];
|
|
1321
|
+
if (isSignalLike(v)) {
|
|
1322
|
+
instance.trackCleanup(v.subscribe(() => manager.apply()));
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (isSignalLike(entry.text)) {
|
|
1326
|
+
instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
instance.trackCleanup(() => manager.unregister(nodeId));
|
|
1330
|
+
}
|
|
1331
|
+
function renderHead(compiled) {
|
|
1332
|
+
const graph = compiled.graph;
|
|
1333
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1334
|
+
graph.walk((node) => {
|
|
1335
|
+
if (node.type !== "head") return;
|
|
1336
|
+
const descFn = graph.getHandler(`__head__${node.id}`);
|
|
1337
|
+
if (descFn === void 0) return;
|
|
1338
|
+
for (const entry of descFn().entries) {
|
|
1339
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
if (merged.size === 0) return "";
|
|
1343
|
+
const dom = new ServerDOMAdapter();
|
|
1344
|
+
let out = "";
|
|
1345
|
+
for (const [key, desired] of merged) {
|
|
1346
|
+
const el = dom.createElement(desired.tag);
|
|
1347
|
+
dom.setAttribute(el, HEAD_MARKER, "");
|
|
1348
|
+
dom.setAttribute(el, HEAD_KEY, key);
|
|
1349
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1350
|
+
dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1351
|
+
}
|
|
1352
|
+
if (desired.tag === "title") dom.setTextContent(el, desired.text ?? "");
|
|
1353
|
+
out += dom.serializeOuter(el);
|
|
1354
|
+
}
|
|
1355
|
+
return out;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
948
1358
|
// ../renderer/src/mount.ts
|
|
949
1359
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
950
1360
|
"text",
|
|
@@ -1067,6 +1477,9 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1067
1477
|
const instance2 = new NodeInstance(graphNode, el2);
|
|
1068
1478
|
ctx.instances.set(graphNode.id, instance2);
|
|
1069
1479
|
wireEvents(dom, graph, graphNode, el2, instance2);
|
|
1480
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
1481
|
+
wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
|
|
1482
|
+
}
|
|
1070
1483
|
dom.appendChild(parentDom, el2);
|
|
1071
1484
|
return instance2;
|
|
1072
1485
|
}
|
|
@@ -1097,7 +1510,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1097
1510
|
instance2.addChild(childInstance);
|
|
1098
1511
|
}
|
|
1099
1512
|
dom.appendChild(parentDom, el2);
|
|
1100
|
-
wireReactiveList(ctx, graphNode, instance2, el2);
|
|
1513
|
+
wireReactiveList(ctx, graphNode, instance2, el2, true);
|
|
1101
1514
|
return instance2;
|
|
1102
1515
|
}
|
|
1103
1516
|
if (graphNode.type === "portal") {
|
|
@@ -1122,6 +1535,16 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1122
1535
|
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
1123
1536
|
return instance2;
|
|
1124
1537
|
}
|
|
1538
|
+
if (graphNode.type === "head") {
|
|
1539
|
+
const anchor = dom.createElement(resolveTag("head"));
|
|
1540
|
+
dom.setAttribute(anchor, "data-streetui-head-anchor", "");
|
|
1541
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
1542
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
1543
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
1544
|
+
dom.appendChild(parentDom, anchor);
|
|
1545
|
+
wireHeadBehavior(ctx, graphNode, instance2);
|
|
1546
|
+
return instance2;
|
|
1547
|
+
}
|
|
1125
1548
|
const tag = resolveTag(graphNode.type);
|
|
1126
1549
|
const el = dom.createElement(tag);
|
|
1127
1550
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -1186,6 +1609,17 @@ function buttonUpdate(dom, el) {
|
|
|
1186
1609
|
}
|
|
1187
1610
|
};
|
|
1188
1611
|
}
|
|
1612
|
+
function linkUpdate(dom, el) {
|
|
1613
|
+
return (propKey, value) => {
|
|
1614
|
+
if (propKey === "label") {
|
|
1615
|
+
dom.setTextContent(el, String(value ?? ""));
|
|
1616
|
+
} else if (propKey === "href") {
|
|
1617
|
+
dom.setAttribute(el, "href", String(value ?? ""));
|
|
1618
|
+
} else {
|
|
1619
|
+
applyProp(dom, el, propKey, value);
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1189
1623
|
function applyNodeProps(ctx, graphNode, el) {
|
|
1190
1624
|
const props = graphNode.props;
|
|
1191
1625
|
for (const key in props) {
|
|
@@ -1206,32 +1640,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
|
|
|
1206
1640
|
instance.trackCleanup(unsub);
|
|
1207
1641
|
}
|
|
1208
1642
|
}
|
|
1209
|
-
function wireReactiveList(ctx, graphNode, instance, el) {
|
|
1643
|
+
function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
|
|
1210
1644
|
const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
|
|
1211
1645
|
const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
|
|
1212
1646
|
if (plan === void 0 && build === void 0) return;
|
|
1647
|
+
const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
|
|
1648
|
+
const parent = ctx.dom.parentNode(leaving.domNode);
|
|
1649
|
+
if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
|
|
1650
|
+
leaving.dispose();
|
|
1651
|
+
forgetInstance(ctx, leaving);
|
|
1652
|
+
ctx.graph.detachNode(leaving.graphNode);
|
|
1653
|
+
});
|
|
1654
|
+
const hooks = controller.hooks();
|
|
1655
|
+
if (runAppear) controller.appear(instance.children);
|
|
1213
1656
|
for (const stateRef of graphNode.stateRefs) {
|
|
1214
1657
|
if (stateRef.propKey !== "items") continue;
|
|
1215
1658
|
const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
|
|
1216
1659
|
if (sig === void 0 || typeof sig.subscribe !== "function") continue;
|
|
1217
1660
|
const unsub = sig.subscribe((value) => {
|
|
1218
1661
|
if (plan !== void 0) {
|
|
1219
|
-
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
|
|
1662
|
+
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
|
|
1220
1663
|
} else {
|
|
1221
|
-
reconcileReactiveList(ctx, graphNode, instance, el, build(value));
|
|
1664
|
+
reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
|
|
1222
1665
|
}
|
|
1223
1666
|
});
|
|
1224
1667
|
instance.trackCleanup(unsub);
|
|
1225
1668
|
}
|
|
1226
1669
|
}
|
|
1227
|
-
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
|
|
1670
|
+
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
|
|
1228
1671
|
const oldInstances = [...listInstance.children];
|
|
1229
1672
|
const result = reconcileChildrenByPlan(
|
|
1230
1673
|
ctx,
|
|
1231
1674
|
listEl,
|
|
1232
1675
|
oldInstances,
|
|
1233
1676
|
plan,
|
|
1234
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1677
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1678
|
+
hooks
|
|
1235
1679
|
);
|
|
1236
1680
|
listInstance.children.length = 0;
|
|
1237
1681
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1246,14 +1690,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
|
|
|
1246
1690
|
for (const child of [...listNode.children]) listNode.removeChild(child);
|
|
1247
1691
|
for (const inst of result.instances) listNode.appendChild(inst.graphNode);
|
|
1248
1692
|
}
|
|
1249
|
-
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
|
|
1693
|
+
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
|
|
1250
1694
|
const oldInstances = [...listInstance.children];
|
|
1251
1695
|
const result = reconcileChildren(
|
|
1252
1696
|
ctx,
|
|
1253
1697
|
listEl,
|
|
1254
1698
|
oldInstances,
|
|
1255
1699
|
newNodes,
|
|
1256
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1700
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1701
|
+
hooks
|
|
1257
1702
|
);
|
|
1258
1703
|
listInstance.children.length = 0;
|
|
1259
1704
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1289,11 +1734,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1289
1734
|
const onOpenChange = (isOpen) => {
|
|
1290
1735
|
if (isOpen) {
|
|
1291
1736
|
if (desc.restoreFocus) saved = saveFocus(dom);
|
|
1292
|
-
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1293
1737
|
if (desc.modal) {
|
|
1294
1738
|
active.push(trapFocus(dom, target));
|
|
1295
1739
|
active.push(containFocus(dom, target));
|
|
1296
1740
|
}
|
|
1741
|
+
if (desc.menu) {
|
|
1742
|
+
active.push(rovingMenu(dom, target));
|
|
1743
|
+
}
|
|
1744
|
+
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1297
1745
|
if (desc.closeOnEscape && desc.onClose !== void 0) {
|
|
1298
1746
|
active.push(onEscape(dom, target, desc.onClose));
|
|
1299
1747
|
}
|
|
@@ -1383,7 +1831,8 @@ function analyzeGraph(graph) {
|
|
|
1383
1831
|
const isConditional = node.type === "conditional";
|
|
1384
1832
|
const isPortal = node.type === "portal";
|
|
1385
1833
|
const isComponent = node.type === "component";
|
|
1386
|
-
const
|
|
1834
|
+
const isHead = node.type === "head";
|
|
1835
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
|
|
1387
1836
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
1388
1837
|
nodes.set(node.id, {
|
|
1389
1838
|
isStatic,
|
|
@@ -1470,13 +1919,14 @@ function renderToString(compiled, options = {}) {
|
|
|
1470
1919
|
}
|
|
1471
1920
|
|
|
1472
1921
|
// src/version.ts
|
|
1473
|
-
var VERSION = "
|
|
1922
|
+
var VERSION = "2.0.0";
|
|
1474
1923
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1475
1924
|
0 && (module.exports = {
|
|
1476
1925
|
STATE_MARKER_ATTR,
|
|
1477
1926
|
ServerDOMAdapter,
|
|
1478
1927
|
VERSION,
|
|
1479
1928
|
readState,
|
|
1929
|
+
renderHead,
|
|
1480
1930
|
renderToString,
|
|
1481
1931
|
serializeState
|
|
1482
1932
|
});
|