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/testing.js
CHANGED
|
@@ -265,6 +265,9 @@ var BrowserDOMAdapter = class {
|
|
|
265
265
|
body() {
|
|
266
266
|
return document.body ?? null;
|
|
267
267
|
}
|
|
268
|
+
head() {
|
|
269
|
+
return document.head ?? null;
|
|
270
|
+
}
|
|
268
271
|
activeElement() {
|
|
269
272
|
return document.activeElement ?? null;
|
|
270
273
|
}
|
|
@@ -643,6 +646,9 @@ var ServerDOMAdapter = class {
|
|
|
643
646
|
body() {
|
|
644
647
|
return null;
|
|
645
648
|
}
|
|
649
|
+
head() {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
646
652
|
activeElement() {
|
|
647
653
|
return null;
|
|
648
654
|
}
|
|
@@ -756,18 +762,25 @@ function trapFocus(dom, container) {
|
|
|
756
762
|
dom.addEventListener(container, "keydown", onKeydown);
|
|
757
763
|
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
758
764
|
}
|
|
765
|
+
var containmentStack = [];
|
|
759
766
|
function containFocus(dom, container) {
|
|
760
767
|
const body = dom.body();
|
|
761
768
|
if (body === null) return () => {
|
|
762
769
|
};
|
|
770
|
+
containmentStack.push(container);
|
|
763
771
|
const onFocusIn = (event) => {
|
|
772
|
+
if (containmentStack[containmentStack.length - 1] !== container) return;
|
|
764
773
|
const target = event.target;
|
|
765
774
|
if (target !== null && !dom.contains(container, target)) {
|
|
766
775
|
focusFirst(dom, container);
|
|
767
776
|
}
|
|
768
777
|
};
|
|
769
778
|
dom.addEventListener(body, "focusin", onFocusIn);
|
|
770
|
-
return () =>
|
|
779
|
+
return () => {
|
|
780
|
+
dom.removeEventListener(body, "focusin", onFocusIn);
|
|
781
|
+
const index = containmentStack.lastIndexOf(container);
|
|
782
|
+
if (index !== -1) containmentStack.splice(index, 1);
|
|
783
|
+
};
|
|
771
784
|
}
|
|
772
785
|
function onEscape(dom, target, handler) {
|
|
773
786
|
const onKeydown = (event) => {
|
|
@@ -776,6 +789,33 @@ function onEscape(dom, target, handler) {
|
|
|
776
789
|
dom.addEventListener(target, "keydown", onKeydown);
|
|
777
790
|
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
778
791
|
}
|
|
792
|
+
function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
793
|
+
const onKeydown = (event) => {
|
|
794
|
+
const key = event.key;
|
|
795
|
+
const isActivate = key === "Enter" || key === " " || key === "Spacebar";
|
|
796
|
+
const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
|
|
797
|
+
if (!isActivate && !isMove) return;
|
|
798
|
+
const items = getFocusable(dom, container, selector);
|
|
799
|
+
if (items.length === 0) return;
|
|
800
|
+
const active = dom.activeElement();
|
|
801
|
+
const index = active === null ? -1 : items.indexOf(active);
|
|
802
|
+
if (isActivate) {
|
|
803
|
+
if (index < 0) return;
|
|
804
|
+
event.preventDefault();
|
|
805
|
+
items[index].click?.();
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
event.preventDefault();
|
|
809
|
+
let next;
|
|
810
|
+
if (key === "Home") next = 0;
|
|
811
|
+
else if (key === "End") next = items.length - 1;
|
|
812
|
+
else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
|
|
813
|
+
else next = index <= 0 ? items.length - 1 : index - 1;
|
|
814
|
+
dom.focus(items[next]);
|
|
815
|
+
};
|
|
816
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
817
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
818
|
+
}
|
|
779
819
|
|
|
780
820
|
// ../renderer/src/render-context.ts
|
|
781
821
|
function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
|
|
@@ -939,7 +979,13 @@ var TAG_MAP = {
|
|
|
939
979
|
// its children are relocated to a document.body container on the browser
|
|
940
980
|
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
941
981
|
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
942
|
-
portal: "div"
|
|
982
|
+
portal: "div",
|
|
983
|
+
// A `head()` node renders as a neutral, empty inline anchor <div> at its
|
|
984
|
+
// declaration site (like a portal anchor). Its actual contribution — title/
|
|
985
|
+
// meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
|
|
986
|
+
// browser, and emitted separately by `renderHead()` on the server. Keeping a
|
|
987
|
+
// one-node/one-element anchor preserves positional hydration.
|
|
988
|
+
head: "div"
|
|
943
989
|
};
|
|
944
990
|
function resolveTag(type) {
|
|
945
991
|
return TAG_MAP[type] ?? "div";
|
|
@@ -987,7 +1033,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
|
|
|
987
1033
|
}
|
|
988
1034
|
|
|
989
1035
|
// ../renderer/src/reconciliation.ts
|
|
990
|
-
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
1036
|
+
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
|
|
991
1037
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
992
1038
|
for (const inst of oldInstances) {
|
|
993
1039
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
@@ -1008,28 +1054,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
|
1008
1054
|
}
|
|
1009
1055
|
newInstances.push(existing);
|
|
1010
1056
|
} else {
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1057
|
+
const reclaimed = hooks?.takeLeaving(key);
|
|
1058
|
+
if (reclaimed !== void 0) {
|
|
1059
|
+
patchExistingInstance(ctx, reclaimed, newNode);
|
|
1060
|
+
reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
|
|
1061
|
+
hooks?.onEnter(reclaimed);
|
|
1062
|
+
newInstances.push(reclaimed);
|
|
1063
|
+
} else {
|
|
1064
|
+
const inst = mountFn(newNode, parentDom);
|
|
1065
|
+
hooks?.onEnter(inst);
|
|
1066
|
+
newInstances.push(inst);
|
|
1067
|
+
}
|
|
1013
1068
|
}
|
|
1014
1069
|
}
|
|
1015
1070
|
const removed = [];
|
|
1016
1071
|
for (const inst of oldInstances) {
|
|
1017
1072
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
1018
|
-
if (
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
for (const inst of removed) {
|
|
1073
|
+
if (usedKeys.has(key)) continue;
|
|
1074
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
1023
1075
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
1024
|
-
if (parent !== null)
|
|
1025
|
-
ctx.dom.removeChild(parent, inst.domNode);
|
|
1026
|
-
}
|
|
1076
|
+
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
1027
1077
|
inst.dispose();
|
|
1078
|
+
removed.push(inst);
|
|
1028
1079
|
}
|
|
1029
1080
|
reorderDom(ctx, parentDom, newInstances);
|
|
1030
1081
|
return { instances: newInstances, removed };
|
|
1031
1082
|
}
|
|
1032
|
-
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
1083
|
+
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
|
|
1033
1084
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
1034
1085
|
for (const inst of oldInstances) {
|
|
1035
1086
|
oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
|
|
@@ -1056,21 +1107,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
|
1056
1107
|
}
|
|
1057
1108
|
newInstances.push(existing);
|
|
1058
1109
|
} else {
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1110
|
+
const reclaimed = hooks?.takeLeaving(entry.key);
|
|
1111
|
+
if (reclaimed !== void 0) {
|
|
1112
|
+
const freshNode = entry.build();
|
|
1113
|
+
built.push(freshNode);
|
|
1114
|
+
patchExistingInstance(ctx, reclaimed, freshNode);
|
|
1115
|
+
reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
|
|
1116
|
+
reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
|
|
1117
|
+
reclaimed.graphNode.setProp("_item", entry.item);
|
|
1118
|
+
hooks?.onEnter(reclaimed);
|
|
1119
|
+
newInstances.push(reclaimed);
|
|
1120
|
+
} else {
|
|
1121
|
+
const freshNode = entry.build();
|
|
1122
|
+
built.push(freshNode);
|
|
1123
|
+
const inst = mountFn(freshNode, parentDom);
|
|
1124
|
+
hooks?.onEnter(inst);
|
|
1125
|
+
newInstances.push(inst);
|
|
1126
|
+
}
|
|
1063
1127
|
}
|
|
1064
1128
|
}
|
|
1065
1129
|
const removed = [];
|
|
1066
1130
|
for (const inst of oldInstances) {
|
|
1067
1131
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
1068
|
-
if (
|
|
1069
|
-
|
|
1070
|
-
for (const inst of removed) {
|
|
1132
|
+
if (usedKeys.has(key)) continue;
|
|
1133
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
1071
1134
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
1072
1135
|
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
1073
1136
|
inst.dispose();
|
|
1137
|
+
removed.push(inst);
|
|
1074
1138
|
}
|
|
1075
1139
|
reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
|
|
1076
1140
|
return { instances: newInstances, removed, built };
|
|
@@ -1196,6 +1260,328 @@ function patchExistingInstance(ctx, instance, newNode) {
|
|
|
1196
1260
|
}
|
|
1197
1261
|
}
|
|
1198
1262
|
|
|
1263
|
+
// ../renderer/src/transition.ts
|
|
1264
|
+
function getResolvedTransition(graph, nodeId) {
|
|
1265
|
+
const fn = graph.getHandler(`__transition__${nodeId}`);
|
|
1266
|
+
return fn === void 0 ? void 0 : fn();
|
|
1267
|
+
}
|
|
1268
|
+
function host() {
|
|
1269
|
+
return globalThis;
|
|
1270
|
+
}
|
|
1271
|
+
function nextFrame(cb) {
|
|
1272
|
+
const h = host();
|
|
1273
|
+
const raf = h.requestAnimationFrame;
|
|
1274
|
+
if (typeof raf === "function") {
|
|
1275
|
+
raf(() => raf(cb));
|
|
1276
|
+
} else {
|
|
1277
|
+
h.setTimeout(cb, 0);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function splitClass(value) {
|
|
1281
|
+
if (value === null) return [];
|
|
1282
|
+
const out = [];
|
|
1283
|
+
for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
|
|
1284
|
+
return out;
|
|
1285
|
+
}
|
|
1286
|
+
function addClasses(dom, el, classes2) {
|
|
1287
|
+
if (classes2.length === 0) return;
|
|
1288
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1289
|
+
let changed = false;
|
|
1290
|
+
for (const c of classes2) {
|
|
1291
|
+
if (!current.includes(c)) {
|
|
1292
|
+
current.push(c);
|
|
1293
|
+
changed = true;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (changed) dom.setAttribute(el, "class", current.join(" "));
|
|
1297
|
+
}
|
|
1298
|
+
function removeClasses(dom, el, classes2) {
|
|
1299
|
+
if (classes2.length === 0) return;
|
|
1300
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1301
|
+
const next = current.filter((c) => !classes2.includes(c));
|
|
1302
|
+
if (next.length !== current.length) {
|
|
1303
|
+
if (next.length === 0) dom.removeAttribute(el, "class");
|
|
1304
|
+
else dom.setAttribute(el, "class", next.join(" "));
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function startRun(dom, el, active, from, to, duration, onDone) {
|
|
1308
|
+
const h = host();
|
|
1309
|
+
let finished = false;
|
|
1310
|
+
let timer = null;
|
|
1311
|
+
const onEvent = (e) => {
|
|
1312
|
+
if (e.target !== el) return;
|
|
1313
|
+
finish();
|
|
1314
|
+
};
|
|
1315
|
+
const detach = () => {
|
|
1316
|
+
dom.removeEventListener(el, "transitionend", onEvent);
|
|
1317
|
+
dom.removeEventListener(el, "animationend", onEvent);
|
|
1318
|
+
if (timer !== null) {
|
|
1319
|
+
h.clearTimeout(timer);
|
|
1320
|
+
timer = null;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
const finish = () => {
|
|
1324
|
+
if (finished) return;
|
|
1325
|
+
finished = true;
|
|
1326
|
+
detach();
|
|
1327
|
+
removeClasses(dom, el, active);
|
|
1328
|
+
removeClasses(dom, el, to);
|
|
1329
|
+
removeClasses(dom, el, from);
|
|
1330
|
+
onDone();
|
|
1331
|
+
};
|
|
1332
|
+
addClasses(dom, el, from);
|
|
1333
|
+
addClasses(dom, el, active);
|
|
1334
|
+
dom.addEventListener(el, "transitionend", onEvent);
|
|
1335
|
+
dom.addEventListener(el, "animationend", onEvent);
|
|
1336
|
+
timer = h.setTimeout(finish, duration);
|
|
1337
|
+
nextFrame(() => {
|
|
1338
|
+
if (finished) return;
|
|
1339
|
+
removeClasses(dom, el, from);
|
|
1340
|
+
addClasses(dom, el, to);
|
|
1341
|
+
});
|
|
1342
|
+
return {
|
|
1343
|
+
cancel: () => {
|
|
1344
|
+
if (finished) return;
|
|
1345
|
+
finished = true;
|
|
1346
|
+
detach();
|
|
1347
|
+
removeClasses(dom, el, active);
|
|
1348
|
+
removeClasses(dom, el, to);
|
|
1349
|
+
removeClasses(dom, el, from);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
var TransitionController = class {
|
|
1354
|
+
constructor(dom, graph, finalize) {
|
|
1355
|
+
this.dom = dom;
|
|
1356
|
+
this.graph = graph;
|
|
1357
|
+
this.finalize = finalize;
|
|
1358
|
+
}
|
|
1359
|
+
leaving = /* @__PURE__ */ new Map();
|
|
1360
|
+
/** True only in a real DOM environment (browser). */
|
|
1361
|
+
get browser() {
|
|
1362
|
+
return this.dom.body() !== null;
|
|
1363
|
+
}
|
|
1364
|
+
keyOf(inst) {
|
|
1365
|
+
return inst.graphNode.key ?? inst.graphNode.id;
|
|
1366
|
+
}
|
|
1367
|
+
resolved(node) {
|
|
1368
|
+
return getResolvedTransition(this.graph, node.id);
|
|
1369
|
+
}
|
|
1370
|
+
/** Run the enter animation for `inst` if it carries a transition (browser only). */
|
|
1371
|
+
enter(inst) {
|
|
1372
|
+
if (!this.browser) return;
|
|
1373
|
+
const rt = this.resolved(inst.graphNode);
|
|
1374
|
+
if (rt === void 0) return;
|
|
1375
|
+
const el = inst.domNode;
|
|
1376
|
+
if (!this.dom.isElement(el)) return;
|
|
1377
|
+
startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Play `appear` for any initial child that opted into it (fresh browser mount
|
|
1382
|
+
* only — hydration must never animate appear, §22, and this is called only on
|
|
1383
|
+
* the mount path).
|
|
1384
|
+
*/
|
|
1385
|
+
appear(children) {
|
|
1386
|
+
if (!this.browser) return;
|
|
1387
|
+
for (const child of children) {
|
|
1388
|
+
const rt = this.resolved(child.graphNode);
|
|
1389
|
+
if (rt !== void 0 && rt.appear) this.enter(child);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
hooks() {
|
|
1393
|
+
return {
|
|
1394
|
+
takeLeaving: (key) => {
|
|
1395
|
+
const entry = this.leaving.get(key);
|
|
1396
|
+
if (entry === void 0) return void 0;
|
|
1397
|
+
entry.run.cancel();
|
|
1398
|
+
this.leaving.delete(key);
|
|
1399
|
+
return entry.inst;
|
|
1400
|
+
},
|
|
1401
|
+
beginLeave: (inst) => {
|
|
1402
|
+
if (!this.browser) return false;
|
|
1403
|
+
const rt = this.resolved(inst.graphNode);
|
|
1404
|
+
if (rt === void 0) return false;
|
|
1405
|
+
const el = inst.domNode;
|
|
1406
|
+
if (!this.dom.isElement(el)) return false;
|
|
1407
|
+
const key = this.keyOf(inst);
|
|
1408
|
+
const prior = this.leaving.get(key);
|
|
1409
|
+
if (prior !== void 0) prior.run.cancel();
|
|
1410
|
+
const run = startRun(
|
|
1411
|
+
this.dom,
|
|
1412
|
+
el,
|
|
1413
|
+
rt.leaveActive,
|
|
1414
|
+
rt.leaveFrom,
|
|
1415
|
+
rt.leaveTo,
|
|
1416
|
+
rt.duration,
|
|
1417
|
+
() => {
|
|
1418
|
+
const current = this.leaving.get(key);
|
|
1419
|
+
if (current !== void 0 && current.run === run) {
|
|
1420
|
+
this.leaving.delete(key);
|
|
1421
|
+
this.finalize(inst);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
1425
|
+
this.leaving.set(key, { inst, run });
|
|
1426
|
+
return true;
|
|
1427
|
+
},
|
|
1428
|
+
onEnter: (inst) => this.enter(inst)
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
// ../renderer/src/head.ts
|
|
1434
|
+
var HEAD_MARKER = "data-streetui-head";
|
|
1435
|
+
var HEAD_KEY = "data-streetui-head-key";
|
|
1436
|
+
function isSignalLike(v) {
|
|
1437
|
+
return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
|
|
1438
|
+
}
|
|
1439
|
+
function readValue(v) {
|
|
1440
|
+
if (isSignalLike(v)) return String(v.peek() ?? "");
|
|
1441
|
+
return String(v ?? "");
|
|
1442
|
+
}
|
|
1443
|
+
function resolveEntry(entry) {
|
|
1444
|
+
const attrs = {};
|
|
1445
|
+
for (const key of Object.keys(entry.attrs)) {
|
|
1446
|
+
attrs[key] = readValue(entry.attrs[key]);
|
|
1447
|
+
}
|
|
1448
|
+
const resolved = { tag: entry.tag, attrs };
|
|
1449
|
+
if (entry.tag === "title") {
|
|
1450
|
+
return { ...resolved, text: readValue(entry.text) };
|
|
1451
|
+
}
|
|
1452
|
+
return resolved;
|
|
1453
|
+
}
|
|
1454
|
+
var HeadManager = class {
|
|
1455
|
+
_dom;
|
|
1456
|
+
_head;
|
|
1457
|
+
_contributions = /* @__PURE__ */ new Map();
|
|
1458
|
+
_applied = /* @__PURE__ */ new Map();
|
|
1459
|
+
_order = 0;
|
|
1460
|
+
_adopted = false;
|
|
1461
|
+
constructor(dom, head) {
|
|
1462
|
+
this._dom = dom;
|
|
1463
|
+
this._head = head;
|
|
1464
|
+
}
|
|
1465
|
+
/** Register (or replace) a node's contribution and re-apply the merged result. */
|
|
1466
|
+
register(nodeId, entries) {
|
|
1467
|
+
this._contributions.set(nodeId, { order: this._order++, entries });
|
|
1468
|
+
this.apply();
|
|
1469
|
+
}
|
|
1470
|
+
/** Withdraw a node's contribution (unmount / route change) and re-apply. */
|
|
1471
|
+
unregister(nodeId) {
|
|
1472
|
+
if (this._contributions.delete(nodeId)) this.apply();
|
|
1473
|
+
}
|
|
1474
|
+
/** Recompute the merged head and patch `document.head` to match. */
|
|
1475
|
+
apply() {
|
|
1476
|
+
if (!this._adopted) {
|
|
1477
|
+
this._adoptServerTags();
|
|
1478
|
+
this._adopted = true;
|
|
1479
|
+
}
|
|
1480
|
+
const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
|
|
1481
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1482
|
+
for (const contribution of ordered) {
|
|
1483
|
+
for (const entry of contribution.entries) {
|
|
1484
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
for (const [key, desired] of merged) {
|
|
1488
|
+
const existing = this._applied.get(key);
|
|
1489
|
+
if (existing !== void 0 && existing.tag === desired.tag) {
|
|
1490
|
+
this._reconcileAttrs(existing, desired);
|
|
1491
|
+
} else {
|
|
1492
|
+
if (existing !== void 0) {
|
|
1493
|
+
this._dom.removeChild(this._head, existing.el);
|
|
1494
|
+
this._applied.delete(key);
|
|
1495
|
+
}
|
|
1496
|
+
const el = this._createTag(key, desired);
|
|
1497
|
+
this._dom.appendChild(this._head, el);
|
|
1498
|
+
this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
for (const [key, record] of [...this._applied]) {
|
|
1502
|
+
if (!merged.has(key)) {
|
|
1503
|
+
this._dom.removeChild(this._head, record.el);
|
|
1504
|
+
this._applied.delete(key);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
_createTag(key, desired) {
|
|
1509
|
+
const el = this._dom.createElement(desired.tag);
|
|
1510
|
+
this._dom.setAttribute(el, HEAD_MARKER, "");
|
|
1511
|
+
this._dom.setAttribute(el, HEAD_KEY, key);
|
|
1512
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1513
|
+
this._dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1514
|
+
}
|
|
1515
|
+
if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
|
|
1516
|
+
return el;
|
|
1517
|
+
}
|
|
1518
|
+
_reconcileAttrs(record, desired) {
|
|
1519
|
+
const nextKeys = new Set(Object.keys(desired.attrs));
|
|
1520
|
+
for (const attr of record.attrKeys) {
|
|
1521
|
+
if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
|
|
1522
|
+
}
|
|
1523
|
+
for (const attr of nextKeys) {
|
|
1524
|
+
this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
|
|
1525
|
+
}
|
|
1526
|
+
if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
|
|
1527
|
+
record.attrKeys = nextKeys;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
|
|
1531
|
+
* in `document.head`. The subsequent diff reuses these elements when the
|
|
1532
|
+
* client desires the same key (no duplicate), rewrites them if the value
|
|
1533
|
+
* changed, or removes them if the client graph no longer wants them.
|
|
1534
|
+
*/
|
|
1535
|
+
_adoptServerTags() {
|
|
1536
|
+
for (const child of this._dom.childNodes(this._head)) {
|
|
1537
|
+
if (!this._dom.isElement(child)) continue;
|
|
1538
|
+
const el = child;
|
|
1539
|
+
const key = this._dom.getAttribute(el, HEAD_KEY);
|
|
1540
|
+
if (key === null) continue;
|
|
1541
|
+
this._applied.set(key, {
|
|
1542
|
+
el,
|
|
1543
|
+
attrKeys: new Set(this._attrNames(el)),
|
|
1544
|
+
tag: this._dom.tagName(el)
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
/** The framework-managed attribute names currently on a server tag. */
|
|
1549
|
+
_attrNames(el) {
|
|
1550
|
+
const names = [];
|
|
1551
|
+
if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
|
|
1552
|
+
if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
|
|
1553
|
+
return names;
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
function getHeadManager(ctx) {
|
|
1557
|
+
const head = ctx.dom.head();
|
|
1558
|
+
if (head === null) return null;
|
|
1559
|
+
const mutable = ctx;
|
|
1560
|
+
if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
|
|
1561
|
+
return mutable.head;
|
|
1562
|
+
}
|
|
1563
|
+
function wireHeadBehavior(ctx, graphNode, instance) {
|
|
1564
|
+
const manager = getHeadManager(ctx);
|
|
1565
|
+
if (manager === null) return;
|
|
1566
|
+
const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
|
|
1567
|
+
if (descFn === void 0) return;
|
|
1568
|
+
const contribution = descFn();
|
|
1569
|
+
const nodeId = graphNode.id;
|
|
1570
|
+
manager.register(nodeId, contribution.entries);
|
|
1571
|
+
for (const entry of contribution.entries) {
|
|
1572
|
+
for (const attrKey of Object.keys(entry.attrs)) {
|
|
1573
|
+
const v = entry.attrs[attrKey];
|
|
1574
|
+
if (isSignalLike(v)) {
|
|
1575
|
+
instance.trackCleanup(v.subscribe(() => manager.apply()));
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
if (isSignalLike(entry.text)) {
|
|
1579
|
+
instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
instance.trackCleanup(() => manager.unregister(nodeId));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1199
1585
|
// ../renderer/src/mount.ts
|
|
1200
1586
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
1201
1587
|
"text",
|
|
@@ -1318,6 +1704,9 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1318
1704
|
const instance2 = new NodeInstance(graphNode, el2);
|
|
1319
1705
|
ctx.instances.set(graphNode.id, instance2);
|
|
1320
1706
|
wireEvents(dom, graph, graphNode, el2, instance2);
|
|
1707
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
1708
|
+
wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
|
|
1709
|
+
}
|
|
1321
1710
|
dom.appendChild(parentDom, el2);
|
|
1322
1711
|
return instance2;
|
|
1323
1712
|
}
|
|
@@ -1348,7 +1737,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1348
1737
|
instance2.addChild(childInstance);
|
|
1349
1738
|
}
|
|
1350
1739
|
dom.appendChild(parentDom, el2);
|
|
1351
|
-
wireReactiveList(ctx, graphNode, instance2, el2);
|
|
1740
|
+
wireReactiveList(ctx, graphNode, instance2, el2, true);
|
|
1352
1741
|
return instance2;
|
|
1353
1742
|
}
|
|
1354
1743
|
if (graphNode.type === "portal") {
|
|
@@ -1373,6 +1762,16 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1373
1762
|
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
1374
1763
|
return instance2;
|
|
1375
1764
|
}
|
|
1765
|
+
if (graphNode.type === "head") {
|
|
1766
|
+
const anchor = dom.createElement(resolveTag("head"));
|
|
1767
|
+
dom.setAttribute(anchor, "data-streetui-head-anchor", "");
|
|
1768
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
1769
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
1770
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
1771
|
+
dom.appendChild(parentDom, anchor);
|
|
1772
|
+
wireHeadBehavior(ctx, graphNode, instance2);
|
|
1773
|
+
return instance2;
|
|
1774
|
+
}
|
|
1376
1775
|
const tag = resolveTag(graphNode.type);
|
|
1377
1776
|
const el = dom.createElement(tag);
|
|
1378
1777
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -1437,6 +1836,17 @@ function buttonUpdate(dom, el) {
|
|
|
1437
1836
|
}
|
|
1438
1837
|
};
|
|
1439
1838
|
}
|
|
1839
|
+
function linkUpdate(dom, el) {
|
|
1840
|
+
return (propKey, value) => {
|
|
1841
|
+
if (propKey === "label") {
|
|
1842
|
+
dom.setTextContent(el, String(value ?? ""));
|
|
1843
|
+
} else if (propKey === "href") {
|
|
1844
|
+
dom.setAttribute(el, "href", String(value ?? ""));
|
|
1845
|
+
} else {
|
|
1846
|
+
applyProp(dom, el, propKey, value);
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1440
1850
|
function applyNodeProps(ctx, graphNode, el) {
|
|
1441
1851
|
const props = graphNode.props;
|
|
1442
1852
|
for (const key in props) {
|
|
@@ -1457,32 +1867,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
|
|
|
1457
1867
|
instance.trackCleanup(unsub);
|
|
1458
1868
|
}
|
|
1459
1869
|
}
|
|
1460
|
-
function wireReactiveList(ctx, graphNode, instance, el) {
|
|
1870
|
+
function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
|
|
1461
1871
|
const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
|
|
1462
1872
|
const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
|
|
1463
1873
|
if (plan === void 0 && build === void 0) return;
|
|
1874
|
+
const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
|
|
1875
|
+
const parent = ctx.dom.parentNode(leaving.domNode);
|
|
1876
|
+
if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
|
|
1877
|
+
leaving.dispose();
|
|
1878
|
+
forgetInstance(ctx, leaving);
|
|
1879
|
+
ctx.graph.detachNode(leaving.graphNode);
|
|
1880
|
+
});
|
|
1881
|
+
const hooks = controller.hooks();
|
|
1882
|
+
if (runAppear) controller.appear(instance.children);
|
|
1464
1883
|
for (const stateRef of graphNode.stateRefs) {
|
|
1465
1884
|
if (stateRef.propKey !== "items") continue;
|
|
1466
1885
|
const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
|
|
1467
1886
|
if (sig === void 0 || typeof sig.subscribe !== "function") continue;
|
|
1468
1887
|
const unsub = sig.subscribe((value) => {
|
|
1469
1888
|
if (plan !== void 0) {
|
|
1470
|
-
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
|
|
1889
|
+
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
|
|
1471
1890
|
} else {
|
|
1472
|
-
reconcileReactiveList(ctx, graphNode, instance, el, build(value));
|
|
1891
|
+
reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
|
|
1473
1892
|
}
|
|
1474
1893
|
});
|
|
1475
1894
|
instance.trackCleanup(unsub);
|
|
1476
1895
|
}
|
|
1477
1896
|
}
|
|
1478
|
-
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
|
|
1897
|
+
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
|
|
1479
1898
|
const oldInstances = [...listInstance.children];
|
|
1480
1899
|
const result = reconcileChildrenByPlan(
|
|
1481
1900
|
ctx,
|
|
1482
1901
|
listEl,
|
|
1483
1902
|
oldInstances,
|
|
1484
1903
|
plan,
|
|
1485
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1904
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1905
|
+
hooks
|
|
1486
1906
|
);
|
|
1487
1907
|
listInstance.children.length = 0;
|
|
1488
1908
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1497,14 +1917,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
|
|
|
1497
1917
|
for (const child of [...listNode.children]) listNode.removeChild(child);
|
|
1498
1918
|
for (const inst of result.instances) listNode.appendChild(inst.graphNode);
|
|
1499
1919
|
}
|
|
1500
|
-
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
|
|
1920
|
+
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
|
|
1501
1921
|
const oldInstances = [...listInstance.children];
|
|
1502
1922
|
const result = reconcileChildren(
|
|
1503
1923
|
ctx,
|
|
1504
1924
|
listEl,
|
|
1505
1925
|
oldInstances,
|
|
1506
1926
|
newNodes,
|
|
1507
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1927
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1928
|
+
hooks
|
|
1508
1929
|
);
|
|
1509
1930
|
listInstance.children.length = 0;
|
|
1510
1931
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1540,11 +1961,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1540
1961
|
const onOpenChange = (isOpen) => {
|
|
1541
1962
|
if (isOpen) {
|
|
1542
1963
|
if (desc.restoreFocus) saved = saveFocus(dom);
|
|
1543
|
-
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1544
1964
|
if (desc.modal) {
|
|
1545
1965
|
active.push(trapFocus(dom, target));
|
|
1546
1966
|
active.push(containFocus(dom, target));
|
|
1547
1967
|
}
|
|
1968
|
+
if (desc.menu) {
|
|
1969
|
+
active.push(rovingMenu(dom, target));
|
|
1970
|
+
}
|
|
1971
|
+
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1548
1972
|
if (desc.closeOnEscape && desc.onClose !== void 0) {
|
|
1549
1973
|
active.push(onEscape(dom, target, desc.onClose));
|
|
1550
1974
|
}
|
|
@@ -1650,7 +2074,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1650
2074
|
case "link": {
|
|
1651
2075
|
const instance = new NodeInstance(graphNode, domNode);
|
|
1652
2076
|
ctx.instances.set(graphNode.id, instance);
|
|
1653
|
-
if (graphNode.type === "link")
|
|
2077
|
+
if (graphNode.type === "link") {
|
|
2078
|
+
wireEvents(dom, graph, graphNode, domNode, instance);
|
|
2079
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
2080
|
+
wireSignalBindings(ctx, graphNode, instance, linkUpdate(dom, domNode));
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
1654
2083
|
return instance;
|
|
1655
2084
|
}
|
|
1656
2085
|
case "reactive-list":
|
|
@@ -1680,6 +2109,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1680
2109
|
wireOverlayBehavior(ctx, graphNode, instance, target);
|
|
1681
2110
|
return instance;
|
|
1682
2111
|
}
|
|
2112
|
+
case "head": {
|
|
2113
|
+
const instance = new NodeInstance(graphNode, domNode);
|
|
2114
|
+
ctx.instances.set(graphNode.id, instance);
|
|
2115
|
+
wireHeadBehavior(ctx, graphNode, instance);
|
|
2116
|
+
return instance;
|
|
2117
|
+
}
|
|
1683
2118
|
default: {
|
|
1684
2119
|
const instance = new NodeInstance(graphNode, domNode);
|
|
1685
2120
|
ctx.instances.set(graphNode.id, instance);
|
|
@@ -1890,7 +2325,8 @@ function analyzeGraph(graph) {
|
|
|
1890
2325
|
const isConditional = node.type === "conditional";
|
|
1891
2326
|
const isPortal = node.type === "portal";
|
|
1892
2327
|
const isComponent = node.type === "component";
|
|
1893
|
-
const
|
|
2328
|
+
const isHead = node.type === "head";
|
|
2329
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
|
|
1894
2330
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
1895
2331
|
nodes.set(node.id, {
|
|
1896
2332
|
isStatic,
|
|
@@ -2221,6 +2657,37 @@ async function waitFor(check, options = {}) {
|
|
|
2221
2657
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
2222
2658
|
}
|
|
2223
2659
|
}
|
|
2660
|
+
function focus(el) {
|
|
2661
|
+
el.focus?.();
|
|
2662
|
+
}
|
|
2663
|
+
function blur(el) {
|
|
2664
|
+
el.blur?.();
|
|
2665
|
+
}
|
|
2666
|
+
function pressKey(key, el = document.activeElement, init = {}) {
|
|
2667
|
+
if (el === null) return;
|
|
2668
|
+
el.dispatchEvent(
|
|
2669
|
+
new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true, ...init })
|
|
2670
|
+
);
|
|
2671
|
+
}
|
|
2672
|
+
function clickOutside(container, target = document.body) {
|
|
2673
|
+
if (container.contains(target)) {
|
|
2674
|
+
throw new Error("[StreetUI Testing] clickOutside: target is inside the container");
|
|
2675
|
+
}
|
|
2676
|
+
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
|
|
2677
|
+
target.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
2678
|
+
}
|
|
2679
|
+
async function openOverlay(open) {
|
|
2680
|
+
open.set(true);
|
|
2681
|
+
await flushUpdates();
|
|
2682
|
+
}
|
|
2683
|
+
async function closeOverlay(open) {
|
|
2684
|
+
open.set(false);
|
|
2685
|
+
await flushUpdates();
|
|
2686
|
+
}
|
|
2687
|
+
async function waitForTransition(el) {
|
|
2688
|
+
el.dispatchEvent(new Event("transitionend", { bubbles: true }));
|
|
2689
|
+
await flushUpdates();
|
|
2690
|
+
}
|
|
2224
2691
|
function findByText(container, text) {
|
|
2225
2692
|
const match = Array.from(container.querySelectorAll("*")).find(
|
|
2226
2693
|
(el) => el.children.length === 0 && (el.textContent?.includes(text) ?? false)
|
|
@@ -2315,6 +2782,154 @@ function renderServerThenHydrate(build, options = {}) {
|
|
|
2315
2782
|
};
|
|
2316
2783
|
}
|
|
2317
2784
|
|
|
2785
|
+
// ../dsl/src/transition.ts
|
|
2786
|
+
function classes(value) {
|
|
2787
|
+
if (value === void 0) return [];
|
|
2788
|
+
const out = [];
|
|
2789
|
+
for (const token of value.split(/\s+/)) {
|
|
2790
|
+
if (token.length > 0 && !out.includes(token)) out.push(token);
|
|
2791
|
+
}
|
|
2792
|
+
return out;
|
|
2793
|
+
}
|
|
2794
|
+
function merge(a, b) {
|
|
2795
|
+
const out = [...a];
|
|
2796
|
+
for (const token of b) if (!out.includes(token)) out.push(token);
|
|
2797
|
+
return out;
|
|
2798
|
+
}
|
|
2799
|
+
function resolveTransition(config) {
|
|
2800
|
+
const n = config.name;
|
|
2801
|
+
const enterActive = merge(
|
|
2802
|
+
classes(config.enter),
|
|
2803
|
+
classes(config.enterActive ?? (n !== void 0 ? `${n}-enter-active` : void 0))
|
|
2804
|
+
);
|
|
2805
|
+
const leaveActive = merge(
|
|
2806
|
+
classes(config.leave),
|
|
2807
|
+
classes(config.leaveActive ?? (n !== void 0 ? `${n}-leave-active` : void 0))
|
|
2808
|
+
);
|
|
2809
|
+
return {
|
|
2810
|
+
enterActive,
|
|
2811
|
+
enterFrom: classes(config.enterFrom ?? (n !== void 0 ? `${n}-enter-from` : void 0)),
|
|
2812
|
+
enterTo: classes(config.enterTo ?? (n !== void 0 ? `${n}-enter-to` : void 0)),
|
|
2813
|
+
leaveActive,
|
|
2814
|
+
leaveFrom: classes(config.leaveFrom ?? (n !== void 0 ? `${n}-leave-from` : void 0)),
|
|
2815
|
+
leaveTo: classes(config.leaveTo ?? (n !== void 0 ? `${n}-leave-to` : void 0)),
|
|
2816
|
+
appear: config.appear ?? false,
|
|
2817
|
+
duration: config.duration ?? 1e3
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// ../dsl/src/head.ts
|
|
2822
|
+
function metaDedupKey(m) {
|
|
2823
|
+
if (m.charset !== void 0) return "meta:charset";
|
|
2824
|
+
if (m.name !== void 0) return `meta:name=${m.name}`;
|
|
2825
|
+
if (m.property !== void 0) return `meta:property=${m.property}`;
|
|
2826
|
+
if (m.httpEquiv !== void 0) return `meta:http-equiv=${m.httpEquiv}`;
|
|
2827
|
+
return void 0;
|
|
2828
|
+
}
|
|
2829
|
+
function metaAttrs(m) {
|
|
2830
|
+
const attrs = {};
|
|
2831
|
+
if (m.charset !== void 0) attrs["charset"] = m.charset;
|
|
2832
|
+
if (m.name !== void 0) attrs["name"] = m.name;
|
|
2833
|
+
if (m.property !== void 0) attrs["property"] = m.property;
|
|
2834
|
+
if (m.httpEquiv !== void 0) attrs["http-equiv"] = m.httpEquiv;
|
|
2835
|
+
if (m.content !== void 0) attrs["content"] = m.content;
|
|
2836
|
+
return attrs;
|
|
2837
|
+
}
|
|
2838
|
+
function linkAttrs(l) {
|
|
2839
|
+
const attrs = { rel: l.rel, href: l.href };
|
|
2840
|
+
if (l.sizes !== void 0) attrs["sizes"] = l.sizes;
|
|
2841
|
+
if (l.type !== void 0) attrs["type"] = l.type;
|
|
2842
|
+
if (l.media !== void 0) attrs["media"] = l.media;
|
|
2843
|
+
if (l.as !== void 0) attrs["as"] = l.as;
|
|
2844
|
+
if (l.crossorigin !== void 0) attrs["crossorigin"] = l.crossorigin;
|
|
2845
|
+
if (l.hreflang !== void 0) attrs["hreflang"] = l.hreflang;
|
|
2846
|
+
return attrs;
|
|
2847
|
+
}
|
|
2848
|
+
function resolveHead(config) {
|
|
2849
|
+
const entries = [];
|
|
2850
|
+
if (config.charset !== void 0) {
|
|
2851
|
+
entries.push({ tag: "meta", dedupKey: "meta:charset", attrs: { charset: config.charset } });
|
|
2852
|
+
}
|
|
2853
|
+
if (config.base !== void 0) {
|
|
2854
|
+
entries.push({ tag: "base", dedupKey: "base", attrs: { href: config.base } });
|
|
2855
|
+
}
|
|
2856
|
+
if (config.title !== void 0) {
|
|
2857
|
+
entries.push({ tag: "title", dedupKey: "title", attrs: {}, text: config.title });
|
|
2858
|
+
}
|
|
2859
|
+
if (config.description !== void 0) {
|
|
2860
|
+
entries.push({
|
|
2861
|
+
tag: "meta",
|
|
2862
|
+
dedupKey: "meta:name=description",
|
|
2863
|
+
attrs: { name: "description", content: config.description }
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
if (config.canonical !== void 0) {
|
|
2867
|
+
entries.push({
|
|
2868
|
+
tag: "link",
|
|
2869
|
+
dedupKey: "link:rel=canonical",
|
|
2870
|
+
attrs: { rel: "canonical", href: config.canonical }
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
if (config.robots !== void 0) {
|
|
2874
|
+
entries.push({
|
|
2875
|
+
tag: "meta",
|
|
2876
|
+
dedupKey: "meta:name=robots",
|
|
2877
|
+
attrs: { name: "robots", content: config.robots }
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
if (config.themeColor !== void 0) {
|
|
2881
|
+
entries.push({
|
|
2882
|
+
tag: "meta",
|
|
2883
|
+
dedupKey: "meta:name=theme-color",
|
|
2884
|
+
attrs: { name: "theme-color", content: config.themeColor }
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
if (config.viewport !== void 0) {
|
|
2888
|
+
entries.push({
|
|
2889
|
+
tag: "meta",
|
|
2890
|
+
dedupKey: "meta:name=viewport",
|
|
2891
|
+
attrs: { name: "viewport", content: config.viewport }
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
if (config.favicon !== void 0) {
|
|
2895
|
+
const l = typeof config.favicon === "string" ? { rel: "icon", href: config.favicon } : config.favicon;
|
|
2896
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}`, attrs: linkAttrs(l) });
|
|
2897
|
+
}
|
|
2898
|
+
if (config.openGraph !== void 0) {
|
|
2899
|
+
for (const key of Object.keys(config.openGraph)) {
|
|
2900
|
+
const property = `og:${key}`;
|
|
2901
|
+
entries.push({
|
|
2902
|
+
tag: "meta",
|
|
2903
|
+
dedupKey: `meta:property=${property}`,
|
|
2904
|
+
attrs: { property, content: config.openGraph[key] }
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
if (config.twitter !== void 0) {
|
|
2909
|
+
for (const key of Object.keys(config.twitter)) {
|
|
2910
|
+
const name = `twitter:${key}`;
|
|
2911
|
+
entries.push({
|
|
2912
|
+
tag: "meta",
|
|
2913
|
+
dedupKey: `meta:name=${name}`,
|
|
2914
|
+
attrs: { name, content: config.twitter[key] }
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
if (config.meta !== void 0) {
|
|
2919
|
+
for (const m of config.meta) {
|
|
2920
|
+
const key = metaDedupKey(m);
|
|
2921
|
+
if (key === void 0) continue;
|
|
2922
|
+
entries.push({ tag: "meta", dedupKey: key, attrs: metaAttrs(m) });
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
if (config.link !== void 0) {
|
|
2926
|
+
for (const l of config.link) {
|
|
2927
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}:href=${l.href}`, attrs: linkAttrs(l) });
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
return { entries };
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2318
2933
|
// ../state/src/signal.ts
|
|
2319
2934
|
var _activeConsumer = null;
|
|
2320
2935
|
function withConsumer(consumer, fn) {
|
|
@@ -2524,6 +3139,18 @@ function applyA11yProps(props, options) {
|
|
|
2524
3139
|
if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
|
|
2525
3140
|
if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
|
|
2526
3141
|
if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
|
|
3142
|
+
if (options.ariaOwns !== void 0) props["aria-owns"] = options.ariaOwns;
|
|
3143
|
+
if (options.ariaActiveDescendant !== void 0) props["aria-activedescendant"] = options.ariaActiveDescendant;
|
|
3144
|
+
if (options.ariaHasPopup !== void 0) props["aria-haspopup"] = String(options.ariaHasPopup);
|
|
3145
|
+
if (options.ariaSelected !== void 0) props["aria-selected"] = String(options.ariaSelected);
|
|
3146
|
+
}
|
|
3147
|
+
function registerTransition(graph, node, config) {
|
|
3148
|
+
if (config === void 0) return;
|
|
3149
|
+
const resolved = resolveTransition(config);
|
|
3150
|
+
graph.registerHandler(
|
|
3151
|
+
`__transition__${node.id}`,
|
|
3152
|
+
() => resolved
|
|
3153
|
+
);
|
|
2527
3154
|
}
|
|
2528
3155
|
function containerProps(options) {
|
|
2529
3156
|
const props = {};
|
|
@@ -2584,6 +3211,7 @@ var OVERLAY_KINDS = {
|
|
|
2584
3211
|
role: "menu",
|
|
2585
3212
|
modal: false,
|
|
2586
3213
|
takesFocus: true,
|
|
3214
|
+
menu: true,
|
|
2587
3215
|
ariaModal: false,
|
|
2588
3216
|
defaultCloseOnEscape: true,
|
|
2589
3217
|
defaultRestoreFocus: true
|
|
@@ -2716,6 +3344,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2716
3344
|
parent: this._node,
|
|
2717
3345
|
props: containerProps(options)
|
|
2718
3346
|
});
|
|
3347
|
+
registerTransition(this._graph, node, options.transition);
|
|
2719
3348
|
builder(new SectionBuilderImpl(node, this._graph));
|
|
2720
3349
|
}
|
|
2721
3350
|
container(key, builder, options = {}) {
|
|
@@ -2724,6 +3353,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2724
3353
|
parent: this._node,
|
|
2725
3354
|
props: containerProps(options)
|
|
2726
3355
|
});
|
|
3356
|
+
registerTransition(this._graph, node, options.transition);
|
|
2727
3357
|
builder(new ContainerBuilderImpl(node, this._graph));
|
|
2728
3358
|
}
|
|
2729
3359
|
list(key, builder, options = {}) {
|
|
@@ -2732,6 +3362,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2732
3362
|
parent: this._node,
|
|
2733
3363
|
props: containerProps(options)
|
|
2734
3364
|
});
|
|
3365
|
+
registerTransition(this._graph, node, options.transition);
|
|
2735
3366
|
builder(new ListBuilderImpl(node, this._graph));
|
|
2736
3367
|
}
|
|
2737
3368
|
listOf(key, items, renderItem, options = {}) {
|
|
@@ -2741,6 +3372,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2741
3372
|
parent: this._node,
|
|
2742
3373
|
props: containerProps(options)
|
|
2743
3374
|
});
|
|
3375
|
+
registerTransition(graph, node, options.transition);
|
|
2744
3376
|
const signalId = `${node.id}:items`;
|
|
2745
3377
|
node.stateRefs.push({ signalId, propKey: "items" });
|
|
2746
3378
|
graph.registerHandler(`__signal__${signalId}`, items);
|
|
@@ -2762,6 +3394,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2762
3394
|
}
|
|
2763
3395
|
});
|
|
2764
3396
|
renderItem(item, index, new ContainerBuilderImpl(itemNode, graph));
|
|
3397
|
+
registerTransition(graph, itemNode, options.itemTransition);
|
|
2765
3398
|
return itemNode;
|
|
2766
3399
|
};
|
|
2767
3400
|
const buildPlan = (raw) => {
|
|
@@ -2793,6 +3426,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2793
3426
|
parent: this._node,
|
|
2794
3427
|
props
|
|
2795
3428
|
});
|
|
3429
|
+
registerTransition(this._graph, node, options.transition);
|
|
2796
3430
|
if (options.onSubmit !== void 0) {
|
|
2797
3431
|
const handlerKey = `submit:${node.id}`;
|
|
2798
3432
|
this._graph.registerHandler(handlerKey, options.onSubmit);
|
|
@@ -2800,18 +3434,20 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2800
3434
|
}
|
|
2801
3435
|
builder(new FormBuilderImpl(node, this._graph));
|
|
2802
3436
|
}
|
|
2803
|
-
when(condition, builder, elseBuilder) {
|
|
3437
|
+
when(condition, builder, elseBuilder, options = {}) {
|
|
2804
3438
|
const graph = this._graph;
|
|
2805
3439
|
const node = graph.createNode("conditional", {
|
|
2806
3440
|
parent: this._node,
|
|
2807
3441
|
props: containerProps({})
|
|
2808
3442
|
});
|
|
3443
|
+
const branchTransition = options.transition !== void 0 && options.appear === true ? { ...options.transition, appear: true } : options.transition;
|
|
2809
3444
|
const buildBranch = (build, tag) => {
|
|
2810
3445
|
const branchKey = `when-${tag}:${node.id}`;
|
|
2811
3446
|
const branch = graph.createNode("container", {
|
|
2812
3447
|
key: branchKey,
|
|
2813
3448
|
props: { key: branchKey }
|
|
2814
3449
|
});
|
|
3450
|
+
registerTransition(graph, branch, branchTransition);
|
|
2815
3451
|
build(new ContainerBuilderImpl(branch, graph));
|
|
2816
3452
|
return branch;
|
|
2817
3453
|
};
|
|
@@ -2857,8 +3493,14 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2857
3493
|
this.container(id, (c) => {
|
|
2858
3494
|
c.when(
|
|
2859
3495
|
hasError,
|
|
2860
|
-
// Error state → fallback.
|
|
2861
|
-
|
|
3496
|
+
// Error state → fallback. The fallback branch mounts exactly when the
|
|
3497
|
+
// boundary enters its error state, so this is also where the optional
|
|
3498
|
+
// `onError` reporting hook fires (observe-only; §6/§7).
|
|
3499
|
+
(fb) => {
|
|
3500
|
+
const currentError = readError();
|
|
3501
|
+
options.onError?.(currentError);
|
|
3502
|
+
options.fallback(fb, currentError, retry);
|
|
3503
|
+
},
|
|
2862
3504
|
// Healthy state → body, guarded against synchronous build throws.
|
|
2863
3505
|
(body) => {
|
|
2864
3506
|
try {
|
|
@@ -2870,6 +3512,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2870
3512
|
);
|
|
2871
3513
|
}, { id });
|
|
2872
3514
|
}
|
|
3515
|
+
asyncBoundary(key, res, branches) {
|
|
3516
|
+
const isError = derived(() => res.status.get() === "error");
|
|
3517
|
+
const showSuccess = derived(
|
|
3518
|
+
() => res.status.get() !== "error" && res.data.get() !== void 0
|
|
3519
|
+
);
|
|
3520
|
+
const showLoading = derived(
|
|
3521
|
+
() => res.status.get() !== "error" && res.data.get() === void 0
|
|
3522
|
+
);
|
|
3523
|
+
const dataSignal = derived(() => res.data.get());
|
|
3524
|
+
const retry = () => {
|
|
3525
|
+
void res.refetch();
|
|
3526
|
+
};
|
|
3527
|
+
this.container(key, (c) => {
|
|
3528
|
+
c.when(isError, (fb) => branches.error?.(fb, res.error.peek(), retry));
|
|
3529
|
+
c.when(showSuccess, (sb) => branches.success(sb, dataSignal));
|
|
3530
|
+
c.when(showLoading, (lb) => branches.loading?.(lb));
|
|
3531
|
+
}, { key });
|
|
3532
|
+
}
|
|
2873
3533
|
// ── Portals & overlays ──────────────────────────────────────────────────────
|
|
2874
3534
|
portal(key, builder, options = {}) {
|
|
2875
3535
|
const node = this._graph.createNode("portal", {
|
|
@@ -2879,6 +3539,31 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2879
3539
|
});
|
|
2880
3540
|
builder(new ContainerBuilderImpl(node, this._graph));
|
|
2881
3541
|
}
|
|
3542
|
+
/**
|
|
3543
|
+
* Declare document metadata (2.0 §1–§3). Creates a `'head'` node — a neutral,
|
|
3544
|
+
* empty inline anchor at this position (one node / one element, so positional
|
|
3545
|
+
* hydration is preserved) — and registers a `__head__<id>` descriptor holding
|
|
3546
|
+
* this call's normalized, dedup-keyed {@link resolveHead} contribution. The
|
|
3547
|
+
* renderer applies it to `document.head` on the browser (adopting server tags
|
|
3548
|
+
* on hydration, cleaning up on unmount / route change) and emits the active
|
|
3549
|
+
* graph's merged metadata as a string on the server (`renderHead`).
|
|
3550
|
+
*
|
|
3551
|
+
* Multiple `head()` nodes may be live at once (app default + route + component)
|
|
3552
|
+
* — the renderer merges them and, per dedup key, the last in document order
|
|
3553
|
+
* wins (see head.ts). No new render path: this reuses the same graph-node +
|
|
3554
|
+
* handler-registry convention as overlays/transitions/components.
|
|
3555
|
+
*/
|
|
3556
|
+
head(metadata) {
|
|
3557
|
+
const node = this._graph.createNode("head", {
|
|
3558
|
+
parent: this._node,
|
|
3559
|
+
props: { "data-streetui-head-anchor": "" }
|
|
3560
|
+
});
|
|
3561
|
+
const contribution = resolveHead(metadata);
|
|
3562
|
+
this._graph.registerHandler(
|
|
3563
|
+
`__head__${node.id}`,
|
|
3564
|
+
() => contribution
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
2882
3567
|
/**
|
|
2883
3568
|
* Shared assembly for every overlay kind: a `portal` node whose single child
|
|
2884
3569
|
* is a `when(open, panel)` conditional. The panel container carries the
|
|
@@ -2906,13 +3591,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
2906
3591
|
...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
|
|
2907
3592
|
};
|
|
2908
3593
|
const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
|
|
2909
|
-
portalBuilder.when(
|
|
2910
|
-
|
|
2911
|
-
|
|
3594
|
+
portalBuilder.when(
|
|
3595
|
+
openSignal,
|
|
3596
|
+
(panelHost) => {
|
|
3597
|
+
panelHost.container(`${key}__panel`, builder, panelOptions);
|
|
3598
|
+
},
|
|
3599
|
+
void 0,
|
|
3600
|
+
// Overlay open/close rides the panel's `when`; a transition animates the
|
|
3601
|
+
// panel in on open and — via the reconciler's deferred-leave — plays the
|
|
3602
|
+
// leave before the panel is removed (§10). Focus is restored at close-
|
|
3603
|
+
// request time (see wireOverlayBehavior), so it never stays trapped inside
|
|
3604
|
+
// a panel that is animating away.
|
|
3605
|
+
options.transition !== void 0 ? { transition: options.transition } : {}
|
|
3606
|
+
);
|
|
2912
3607
|
const descriptor = {
|
|
2913
3608
|
open: openSignal,
|
|
2914
3609
|
modal: kind.modal,
|
|
2915
3610
|
takesFocus: kind.takesFocus,
|
|
3611
|
+
menu: kind.menu ?? false,
|
|
2916
3612
|
closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
|
|
2917
3613
|
restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
|
|
2918
3614
|
...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
|
|
@@ -3009,6 +3705,7 @@ var ListBuilderImpl = class extends ContentBuilderBase {
|
|
|
3009
3705
|
parent: this._node,
|
|
3010
3706
|
props: containerProps(options)
|
|
3011
3707
|
});
|
|
3708
|
+
registerTransition(this._graph, node, options.transition);
|
|
3012
3709
|
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3013
3710
|
}
|
|
3014
3711
|
};
|
|
@@ -3196,6 +3893,8 @@ var ApplicationGraph = class {
|
|
|
3196
3893
|
this.handlers.delete(`__listplan__${node.id}`);
|
|
3197
3894
|
this.handlers.delete(`__overlay__${node.id}`);
|
|
3198
3895
|
this.handlers.delete(`__component__${node.id}`);
|
|
3896
|
+
this.handlers.delete(`__transition__${node.id}`);
|
|
3897
|
+
this.handlers.delete(`__head__${node.id}`);
|
|
3199
3898
|
}
|
|
3200
3899
|
// ── Handler registry ──────────────────────────────────────────────────────
|
|
3201
3900
|
registerHandler(key, fn) {
|
|
@@ -3373,25 +4072,32 @@ function trigger(el, type, init = {}) {
|
|
|
3373
4072
|
}
|
|
3374
4073
|
|
|
3375
4074
|
// src/version.ts
|
|
3376
|
-
var VERSION = "
|
|
4075
|
+
var VERSION = "2.0.0";
|
|
3377
4076
|
export {
|
|
3378
4077
|
VERSION,
|
|
3379
4078
|
analyzeGraph,
|
|
4079
|
+
blur,
|
|
4080
|
+
clickOutside,
|
|
4081
|
+
closeOverlay,
|
|
3380
4082
|
findAllByRole,
|
|
3381
4083
|
findAllComponents,
|
|
3382
4084
|
findByRole,
|
|
3383
4085
|
findByText,
|
|
3384
4086
|
findComponent,
|
|
3385
4087
|
flushUpdates,
|
|
4088
|
+
focus,
|
|
3386
4089
|
formatInspection,
|
|
3387
4090
|
getComponentName,
|
|
3388
4091
|
hydrateComponent,
|
|
3389
4092
|
inspectCompilation,
|
|
4093
|
+
openOverlay,
|
|
4094
|
+
pressKey,
|
|
3390
4095
|
render,
|
|
3391
4096
|
renderComponent,
|
|
3392
4097
|
renderOnce,
|
|
3393
4098
|
renderServerThenHydrate,
|
|
3394
4099
|
trigger,
|
|
3395
|
-
waitFor
|
|
4100
|
+
waitFor,
|
|
4101
|
+
waitForTransition
|
|
3396
4102
|
};
|
|
3397
4103
|
//# sourceMappingURL=testing.js.map
|