streetui 1.7.0 → 1.9.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/{compile-Cte0nf06.d.cts → compile-DqsMQVJk.d.cts} +255 -4
- package/dist/{compile-Cte0nf06.d.ts → compile-DqsMQVJk.d.ts} +255 -4
- 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/{hydration-diagnostics-zK-fihvd.d.cts → hydration-diagnostics-4pwt6v7b.d.cts} +1 -1
- package/dist/{hydration-diagnostics-Ddgs3Puu.d.ts → hydration-diagnostics-B2FmY05M.d.ts} +1 -1
- package/dist/index.cjs +639 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -18
- package/dist/index.d.ts +294 -18
- package/dist/index.js +627 -41
- package/dist/index.js.map +1 -1
- package/dist/{server-cgmCiPsz.d.ts → server-BYnmb_hR.d.ts} +1 -1
- package/dist/{server-D4A8eunr.d.cts → server-DnwqBTfE.d.cts} +1 -1
- package/dist/server.cjs +274 -31
- 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 +274 -31
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +1470 -33
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +101 -4
- package/dist/testing.d.ts +101 -4
- package/dist/testing.js +1456 -32
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
// ../core/src/identity.ts
|
|
2
2
|
var _counter = 0;
|
|
3
|
+
function nextId() {
|
|
4
|
+
return ++_counter;
|
|
5
|
+
}
|
|
3
6
|
function resetIdCounter() {
|
|
4
7
|
_counter = 0;
|
|
5
8
|
}
|
|
9
|
+
function createNodeId(value) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function generateNodeId(prefix = "node") {
|
|
13
|
+
return createNodeId(`${prefix}:${nextId()}`);
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
// ../core/src/lifecycle.ts
|
|
8
17
|
var CleanupRegistry = class {
|
|
@@ -747,18 +756,25 @@ function trapFocus(dom, container) {
|
|
|
747
756
|
dom.addEventListener(container, "keydown", onKeydown);
|
|
748
757
|
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
749
758
|
}
|
|
759
|
+
var containmentStack = [];
|
|
750
760
|
function containFocus(dom, container) {
|
|
751
761
|
const body = dom.body();
|
|
752
762
|
if (body === null) return () => {
|
|
753
763
|
};
|
|
764
|
+
containmentStack.push(container);
|
|
754
765
|
const onFocusIn = (event) => {
|
|
766
|
+
if (containmentStack[containmentStack.length - 1] !== container) return;
|
|
755
767
|
const target = event.target;
|
|
756
768
|
if (target !== null && !dom.contains(container, target)) {
|
|
757
769
|
focusFirst(dom, container);
|
|
758
770
|
}
|
|
759
771
|
};
|
|
760
772
|
dom.addEventListener(body, "focusin", onFocusIn);
|
|
761
|
-
return () =>
|
|
773
|
+
return () => {
|
|
774
|
+
dom.removeEventListener(body, "focusin", onFocusIn);
|
|
775
|
+
const index = containmentStack.lastIndexOf(container);
|
|
776
|
+
if (index !== -1) containmentStack.splice(index, 1);
|
|
777
|
+
};
|
|
762
778
|
}
|
|
763
779
|
function onEscape(dom, target, handler) {
|
|
764
780
|
const onKeydown = (event) => {
|
|
@@ -767,6 +783,33 @@ function onEscape(dom, target, handler) {
|
|
|
767
783
|
dom.addEventListener(target, "keydown", onKeydown);
|
|
768
784
|
return () => dom.removeEventListener(target, "keydown", onKeydown);
|
|
769
785
|
}
|
|
786
|
+
function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
|
|
787
|
+
const onKeydown = (event) => {
|
|
788
|
+
const key = event.key;
|
|
789
|
+
const isActivate = key === "Enter" || key === " " || key === "Spacebar";
|
|
790
|
+
const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
|
|
791
|
+
if (!isActivate && !isMove) return;
|
|
792
|
+
const items = getFocusable(dom, container, selector);
|
|
793
|
+
if (items.length === 0) return;
|
|
794
|
+
const active = dom.activeElement();
|
|
795
|
+
const index = active === null ? -1 : items.indexOf(active);
|
|
796
|
+
if (isActivate) {
|
|
797
|
+
if (index < 0) return;
|
|
798
|
+
event.preventDefault();
|
|
799
|
+
items[index].click?.();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
event.preventDefault();
|
|
803
|
+
let next;
|
|
804
|
+
if (key === "Home") next = 0;
|
|
805
|
+
else if (key === "End") next = items.length - 1;
|
|
806
|
+
else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
|
|
807
|
+
else next = index <= 0 ? items.length - 1 : index - 1;
|
|
808
|
+
dom.focus(items[next]);
|
|
809
|
+
};
|
|
810
|
+
dom.addEventListener(container, "keydown", onKeydown);
|
|
811
|
+
return () => dom.removeEventListener(container, "keydown", onKeydown);
|
|
812
|
+
}
|
|
770
813
|
|
|
771
814
|
// ../renderer/src/render-context.ts
|
|
772
815
|
function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
|
|
@@ -978,7 +1021,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
|
|
|
978
1021
|
}
|
|
979
1022
|
|
|
980
1023
|
// ../renderer/src/reconciliation.ts
|
|
981
|
-
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
1024
|
+
function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
|
|
982
1025
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
983
1026
|
for (const inst of oldInstances) {
|
|
984
1027
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
@@ -999,28 +1042,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
|
|
|
999
1042
|
}
|
|
1000
1043
|
newInstances.push(existing);
|
|
1001
1044
|
} else {
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1045
|
+
const reclaimed = hooks?.takeLeaving(key);
|
|
1046
|
+
if (reclaimed !== void 0) {
|
|
1047
|
+
patchExistingInstance(ctx, reclaimed, newNode);
|
|
1048
|
+
reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
|
|
1049
|
+
hooks?.onEnter(reclaimed);
|
|
1050
|
+
newInstances.push(reclaimed);
|
|
1051
|
+
} else {
|
|
1052
|
+
const inst = mountFn(newNode, parentDom);
|
|
1053
|
+
hooks?.onEnter(inst);
|
|
1054
|
+
newInstances.push(inst);
|
|
1055
|
+
}
|
|
1004
1056
|
}
|
|
1005
1057
|
}
|
|
1006
1058
|
const removed = [];
|
|
1007
1059
|
for (const inst of oldInstances) {
|
|
1008
1060
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
1009
|
-
if (
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
for (const inst of removed) {
|
|
1061
|
+
if (usedKeys.has(key)) continue;
|
|
1062
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
1014
1063
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
1015
|
-
if (parent !== null)
|
|
1016
|
-
ctx.dom.removeChild(parent, inst.domNode);
|
|
1017
|
-
}
|
|
1064
|
+
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
1018
1065
|
inst.dispose();
|
|
1066
|
+
removed.push(inst);
|
|
1019
1067
|
}
|
|
1020
1068
|
reorderDom(ctx, parentDom, newInstances);
|
|
1021
1069
|
return { instances: newInstances, removed };
|
|
1022
1070
|
}
|
|
1023
|
-
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
1071
|
+
function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
|
|
1024
1072
|
const oldByKey = /* @__PURE__ */ new Map();
|
|
1025
1073
|
for (const inst of oldInstances) {
|
|
1026
1074
|
oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
|
|
@@ -1047,21 +1095,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
|
|
|
1047
1095
|
}
|
|
1048
1096
|
newInstances.push(existing);
|
|
1049
1097
|
} else {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1098
|
+
const reclaimed = hooks?.takeLeaving(entry.key);
|
|
1099
|
+
if (reclaimed !== void 0) {
|
|
1100
|
+
const freshNode = entry.build();
|
|
1101
|
+
built.push(freshNode);
|
|
1102
|
+
patchExistingInstance(ctx, reclaimed, freshNode);
|
|
1103
|
+
reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
|
|
1104
|
+
reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
|
|
1105
|
+
reclaimed.graphNode.setProp("_item", entry.item);
|
|
1106
|
+
hooks?.onEnter(reclaimed);
|
|
1107
|
+
newInstances.push(reclaimed);
|
|
1108
|
+
} else {
|
|
1109
|
+
const freshNode = entry.build();
|
|
1110
|
+
built.push(freshNode);
|
|
1111
|
+
const inst = mountFn(freshNode, parentDom);
|
|
1112
|
+
hooks?.onEnter(inst);
|
|
1113
|
+
newInstances.push(inst);
|
|
1114
|
+
}
|
|
1054
1115
|
}
|
|
1055
1116
|
}
|
|
1056
1117
|
const removed = [];
|
|
1057
1118
|
for (const inst of oldInstances) {
|
|
1058
1119
|
const key = inst.graphNode.key ?? inst.graphNode.id;
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1061
|
-
for (const inst of removed) {
|
|
1120
|
+
if (usedKeys.has(key)) continue;
|
|
1121
|
+
if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
|
|
1062
1122
|
const parent = ctx.dom.parentNode(inst.domNode);
|
|
1063
1123
|
if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
|
|
1064
1124
|
inst.dispose();
|
|
1125
|
+
removed.push(inst);
|
|
1065
1126
|
}
|
|
1066
1127
|
reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
|
|
1067
1128
|
return { instances: newInstances, removed, built };
|
|
@@ -1187,6 +1248,176 @@ function patchExistingInstance(ctx, instance, newNode) {
|
|
|
1187
1248
|
}
|
|
1188
1249
|
}
|
|
1189
1250
|
|
|
1251
|
+
// ../renderer/src/transition.ts
|
|
1252
|
+
function getResolvedTransition(graph, nodeId) {
|
|
1253
|
+
const fn = graph.getHandler(`__transition__${nodeId}`);
|
|
1254
|
+
return fn === void 0 ? void 0 : fn();
|
|
1255
|
+
}
|
|
1256
|
+
function host() {
|
|
1257
|
+
return globalThis;
|
|
1258
|
+
}
|
|
1259
|
+
function nextFrame(cb) {
|
|
1260
|
+
const h = host();
|
|
1261
|
+
const raf = h.requestAnimationFrame;
|
|
1262
|
+
if (typeof raf === "function") {
|
|
1263
|
+
raf(() => raf(cb));
|
|
1264
|
+
} else {
|
|
1265
|
+
h.setTimeout(cb, 0);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function splitClass(value) {
|
|
1269
|
+
if (value === null) return [];
|
|
1270
|
+
const out = [];
|
|
1271
|
+
for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
|
|
1272
|
+
return out;
|
|
1273
|
+
}
|
|
1274
|
+
function addClasses(dom, el, classes2) {
|
|
1275
|
+
if (classes2.length === 0) return;
|
|
1276
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1277
|
+
let changed = false;
|
|
1278
|
+
for (const c of classes2) {
|
|
1279
|
+
if (!current.includes(c)) {
|
|
1280
|
+
current.push(c);
|
|
1281
|
+
changed = true;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (changed) dom.setAttribute(el, "class", current.join(" "));
|
|
1285
|
+
}
|
|
1286
|
+
function removeClasses(dom, el, classes2) {
|
|
1287
|
+
if (classes2.length === 0) return;
|
|
1288
|
+
const current = splitClass(dom.getAttribute(el, "class"));
|
|
1289
|
+
const next = current.filter((c) => !classes2.includes(c));
|
|
1290
|
+
if (next.length !== current.length) {
|
|
1291
|
+
if (next.length === 0) dom.removeAttribute(el, "class");
|
|
1292
|
+
else dom.setAttribute(el, "class", next.join(" "));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
function startRun(dom, el, active, from, to, duration, onDone) {
|
|
1296
|
+
const h = host();
|
|
1297
|
+
let finished = false;
|
|
1298
|
+
let timer = null;
|
|
1299
|
+
const onEvent = (e) => {
|
|
1300
|
+
if (e.target !== el) return;
|
|
1301
|
+
finish();
|
|
1302
|
+
};
|
|
1303
|
+
const detach = () => {
|
|
1304
|
+
dom.removeEventListener(el, "transitionend", onEvent);
|
|
1305
|
+
dom.removeEventListener(el, "animationend", onEvent);
|
|
1306
|
+
if (timer !== null) {
|
|
1307
|
+
h.clearTimeout(timer);
|
|
1308
|
+
timer = null;
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
const finish = () => {
|
|
1312
|
+
if (finished) return;
|
|
1313
|
+
finished = true;
|
|
1314
|
+
detach();
|
|
1315
|
+
removeClasses(dom, el, active);
|
|
1316
|
+
removeClasses(dom, el, to);
|
|
1317
|
+
removeClasses(dom, el, from);
|
|
1318
|
+
onDone();
|
|
1319
|
+
};
|
|
1320
|
+
addClasses(dom, el, from);
|
|
1321
|
+
addClasses(dom, el, active);
|
|
1322
|
+
dom.addEventListener(el, "transitionend", onEvent);
|
|
1323
|
+
dom.addEventListener(el, "animationend", onEvent);
|
|
1324
|
+
timer = h.setTimeout(finish, duration);
|
|
1325
|
+
nextFrame(() => {
|
|
1326
|
+
if (finished) return;
|
|
1327
|
+
removeClasses(dom, el, from);
|
|
1328
|
+
addClasses(dom, el, to);
|
|
1329
|
+
});
|
|
1330
|
+
return {
|
|
1331
|
+
cancel: () => {
|
|
1332
|
+
if (finished) return;
|
|
1333
|
+
finished = true;
|
|
1334
|
+
detach();
|
|
1335
|
+
removeClasses(dom, el, active);
|
|
1336
|
+
removeClasses(dom, el, to);
|
|
1337
|
+
removeClasses(dom, el, from);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
var TransitionController = class {
|
|
1342
|
+
constructor(dom, graph, finalize) {
|
|
1343
|
+
this.dom = dom;
|
|
1344
|
+
this.graph = graph;
|
|
1345
|
+
this.finalize = finalize;
|
|
1346
|
+
}
|
|
1347
|
+
leaving = /* @__PURE__ */ new Map();
|
|
1348
|
+
/** True only in a real DOM environment (browser). */
|
|
1349
|
+
get browser() {
|
|
1350
|
+
return this.dom.body() !== null;
|
|
1351
|
+
}
|
|
1352
|
+
keyOf(inst) {
|
|
1353
|
+
return inst.graphNode.key ?? inst.graphNode.id;
|
|
1354
|
+
}
|
|
1355
|
+
resolved(node) {
|
|
1356
|
+
return getResolvedTransition(this.graph, node.id);
|
|
1357
|
+
}
|
|
1358
|
+
/** Run the enter animation for `inst` if it carries a transition (browser only). */
|
|
1359
|
+
enter(inst) {
|
|
1360
|
+
if (!this.browser) return;
|
|
1361
|
+
const rt = this.resolved(inst.graphNode);
|
|
1362
|
+
if (rt === void 0) return;
|
|
1363
|
+
const el = inst.domNode;
|
|
1364
|
+
if (!this.dom.isElement(el)) return;
|
|
1365
|
+
startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Play `appear` for any initial child that opted into it (fresh browser mount
|
|
1370
|
+
* only — hydration must never animate appear, §22, and this is called only on
|
|
1371
|
+
* the mount path).
|
|
1372
|
+
*/
|
|
1373
|
+
appear(children) {
|
|
1374
|
+
if (!this.browser) return;
|
|
1375
|
+
for (const child of children) {
|
|
1376
|
+
const rt = this.resolved(child.graphNode);
|
|
1377
|
+
if (rt !== void 0 && rt.appear) this.enter(child);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
hooks() {
|
|
1381
|
+
return {
|
|
1382
|
+
takeLeaving: (key) => {
|
|
1383
|
+
const entry = this.leaving.get(key);
|
|
1384
|
+
if (entry === void 0) return void 0;
|
|
1385
|
+
entry.run.cancel();
|
|
1386
|
+
this.leaving.delete(key);
|
|
1387
|
+
return entry.inst;
|
|
1388
|
+
},
|
|
1389
|
+
beginLeave: (inst) => {
|
|
1390
|
+
if (!this.browser) return false;
|
|
1391
|
+
const rt = this.resolved(inst.graphNode);
|
|
1392
|
+
if (rt === void 0) return false;
|
|
1393
|
+
const el = inst.domNode;
|
|
1394
|
+
if (!this.dom.isElement(el)) return false;
|
|
1395
|
+
const key = this.keyOf(inst);
|
|
1396
|
+
const prior = this.leaving.get(key);
|
|
1397
|
+
if (prior !== void 0) prior.run.cancel();
|
|
1398
|
+
const run = startRun(
|
|
1399
|
+
this.dom,
|
|
1400
|
+
el,
|
|
1401
|
+
rt.leaveActive,
|
|
1402
|
+
rt.leaveFrom,
|
|
1403
|
+
rt.leaveTo,
|
|
1404
|
+
rt.duration,
|
|
1405
|
+
() => {
|
|
1406
|
+
const current = this.leaving.get(key);
|
|
1407
|
+
if (current !== void 0 && current.run === run) {
|
|
1408
|
+
this.leaving.delete(key);
|
|
1409
|
+
this.finalize(inst);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
);
|
|
1413
|
+
this.leaving.set(key, { inst, run });
|
|
1414
|
+
return true;
|
|
1415
|
+
},
|
|
1416
|
+
onEnter: (inst) => this.enter(inst)
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1190
1421
|
// ../renderer/src/mount.ts
|
|
1191
1422
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
1192
1423
|
"text",
|
|
@@ -1339,7 +1570,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1339
1570
|
instance2.addChild(childInstance);
|
|
1340
1571
|
}
|
|
1341
1572
|
dom.appendChild(parentDom, el2);
|
|
1342
|
-
wireReactiveList(ctx, graphNode, instance2, el2);
|
|
1573
|
+
wireReactiveList(ctx, graphNode, instance2, el2, true);
|
|
1343
1574
|
return instance2;
|
|
1344
1575
|
}
|
|
1345
1576
|
if (graphNode.type === "portal") {
|
|
@@ -1383,6 +1614,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1383
1614
|
instance.addChild(childInstance);
|
|
1384
1615
|
}
|
|
1385
1616
|
dom.appendChild(parentDom, el);
|
|
1617
|
+
if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
|
|
1386
1618
|
return instance;
|
|
1387
1619
|
}
|
|
1388
1620
|
function textUpdate(dom, el, textNode) {
|
|
@@ -1447,32 +1679,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
|
|
|
1447
1679
|
instance.trackCleanup(unsub);
|
|
1448
1680
|
}
|
|
1449
1681
|
}
|
|
1450
|
-
function wireReactiveList(ctx, graphNode, instance, el) {
|
|
1682
|
+
function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
|
|
1451
1683
|
const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
|
|
1452
1684
|
const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
|
|
1453
1685
|
if (plan === void 0 && build === void 0) return;
|
|
1686
|
+
const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
|
|
1687
|
+
const parent = ctx.dom.parentNode(leaving.domNode);
|
|
1688
|
+
if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
|
|
1689
|
+
leaving.dispose();
|
|
1690
|
+
forgetInstance(ctx, leaving);
|
|
1691
|
+
ctx.graph.detachNode(leaving.graphNode);
|
|
1692
|
+
});
|
|
1693
|
+
const hooks = controller.hooks();
|
|
1694
|
+
if (runAppear) controller.appear(instance.children);
|
|
1454
1695
|
for (const stateRef of graphNode.stateRefs) {
|
|
1455
1696
|
if (stateRef.propKey !== "items") continue;
|
|
1456
1697
|
const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
|
|
1457
1698
|
if (sig === void 0 || typeof sig.subscribe !== "function") continue;
|
|
1458
1699
|
const unsub = sig.subscribe((value) => {
|
|
1459
1700
|
if (plan !== void 0) {
|
|
1460
|
-
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
|
|
1701
|
+
reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
|
|
1461
1702
|
} else {
|
|
1462
|
-
reconcileReactiveList(ctx, graphNode, instance, el, build(value));
|
|
1703
|
+
reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
|
|
1463
1704
|
}
|
|
1464
1705
|
});
|
|
1465
1706
|
instance.trackCleanup(unsub);
|
|
1466
1707
|
}
|
|
1467
1708
|
}
|
|
1468
|
-
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
|
|
1709
|
+
function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
|
|
1469
1710
|
const oldInstances = [...listInstance.children];
|
|
1470
1711
|
const result = reconcileChildrenByPlan(
|
|
1471
1712
|
ctx,
|
|
1472
1713
|
listEl,
|
|
1473
1714
|
oldInstances,
|
|
1474
1715
|
plan,
|
|
1475
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1716
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1717
|
+
hooks
|
|
1476
1718
|
);
|
|
1477
1719
|
listInstance.children.length = 0;
|
|
1478
1720
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1487,14 +1729,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
|
|
|
1487
1729
|
for (const child of [...listNode.children]) listNode.removeChild(child);
|
|
1488
1730
|
for (const inst of result.instances) listNode.appendChild(inst.graphNode);
|
|
1489
1731
|
}
|
|
1490
|
-
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
|
|
1732
|
+
function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
|
|
1491
1733
|
const oldInstances = [...listInstance.children];
|
|
1492
1734
|
const result = reconcileChildren(
|
|
1493
1735
|
ctx,
|
|
1494
1736
|
listEl,
|
|
1495
1737
|
oldInstances,
|
|
1496
1738
|
newNodes,
|
|
1497
|
-
(node, parent) => mountNode(ctx, node, parent)
|
|
1739
|
+
(node, parent) => mountNode(ctx, node, parent),
|
|
1740
|
+
hooks
|
|
1498
1741
|
);
|
|
1499
1742
|
listInstance.children.length = 0;
|
|
1500
1743
|
for (const inst of result.instances) listInstance.children.push(inst);
|
|
@@ -1530,11 +1773,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1530
1773
|
const onOpenChange = (isOpen) => {
|
|
1531
1774
|
if (isOpen) {
|
|
1532
1775
|
if (desc.restoreFocus) saved = saveFocus(dom);
|
|
1533
|
-
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1534
1776
|
if (desc.modal) {
|
|
1535
1777
|
active.push(trapFocus(dom, target));
|
|
1536
1778
|
active.push(containFocus(dom, target));
|
|
1537
1779
|
}
|
|
1780
|
+
if (desc.menu) {
|
|
1781
|
+
active.push(rovingMenu(dom, target));
|
|
1782
|
+
}
|
|
1783
|
+
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
1538
1784
|
if (desc.closeOnEscape && desc.onClose !== void 0) {
|
|
1539
1785
|
active.push(onEscape(dom, target, desc.onClose));
|
|
1540
1786
|
}
|
|
@@ -1551,6 +1797,11 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1551
1797
|
instance.trackCleanup(teardown);
|
|
1552
1798
|
if (openSig.peek() === true) onOpenChange(true);
|
|
1553
1799
|
}
|
|
1800
|
+
function wireComponentBehavior(ctx, graphNode, instance) {
|
|
1801
|
+
const fn = ctx.graph.getHandler(`__component__${graphNode.id}`);
|
|
1802
|
+
if (fn === void 0) return;
|
|
1803
|
+
for (const cleanup of fn()) instance.trackCleanup(cleanup);
|
|
1804
|
+
}
|
|
1554
1805
|
|
|
1555
1806
|
// ../renderer/src/hydration-diagnostics.ts
|
|
1556
1807
|
function formatHydrationDiagnostic(d) {
|
|
@@ -1675,6 +1926,7 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1675
1926
|
return instance;
|
|
1676
1927
|
}
|
|
1677
1928
|
hydrateChildren(ctx, graphNode, instance, domNode, path);
|
|
1929
|
+
if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
|
|
1678
1930
|
return instance;
|
|
1679
1931
|
}
|
|
1680
1932
|
}
|
|
@@ -1873,7 +2125,8 @@ function analyzeGraph(graph) {
|
|
|
1873
2125
|
const isList = node.type === "reactive-list";
|
|
1874
2126
|
const isConditional = node.type === "conditional";
|
|
1875
2127
|
const isPortal = node.type === "portal";
|
|
1876
|
-
const
|
|
2128
|
+
const isComponent = node.type === "component";
|
|
2129
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
|
|
1877
2130
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
1878
2131
|
nodes.set(node.id, {
|
|
1879
2132
|
isStatic,
|
|
@@ -2204,6 +2457,37 @@ async function waitFor(check, options = {}) {
|
|
|
2204
2457
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
2205
2458
|
}
|
|
2206
2459
|
}
|
|
2460
|
+
function focus(el) {
|
|
2461
|
+
el.focus?.();
|
|
2462
|
+
}
|
|
2463
|
+
function blur(el) {
|
|
2464
|
+
el.blur?.();
|
|
2465
|
+
}
|
|
2466
|
+
function pressKey(key, el = document.activeElement, init = {}) {
|
|
2467
|
+
if (el === null) return;
|
|
2468
|
+
el.dispatchEvent(
|
|
2469
|
+
new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true, ...init })
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
function clickOutside(container, target = document.body) {
|
|
2473
|
+
if (container.contains(target)) {
|
|
2474
|
+
throw new Error("[StreetUI Testing] clickOutside: target is inside the container");
|
|
2475
|
+
}
|
|
2476
|
+
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
|
|
2477
|
+
target.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
2478
|
+
}
|
|
2479
|
+
async function openOverlay(open) {
|
|
2480
|
+
open.set(true);
|
|
2481
|
+
await flushUpdates();
|
|
2482
|
+
}
|
|
2483
|
+
async function closeOverlay(open) {
|
|
2484
|
+
open.set(false);
|
|
2485
|
+
await flushUpdates();
|
|
2486
|
+
}
|
|
2487
|
+
async function waitForTransition(el) {
|
|
2488
|
+
el.dispatchEvent(new Event("transitionend", { bubbles: true }));
|
|
2489
|
+
await flushUpdates();
|
|
2490
|
+
}
|
|
2207
2491
|
function findByText(container, text) {
|
|
2208
2492
|
const match = Array.from(container.querySelectorAll("*")).find(
|
|
2209
2493
|
(el) => el.children.length === 0 && (el.textContent?.includes(text) ?? false)
|
|
@@ -2298,20 +2582,1160 @@ function renderServerThenHydrate(build, options = {}) {
|
|
|
2298
2582
|
};
|
|
2299
2583
|
}
|
|
2300
2584
|
|
|
2585
|
+
// ../dsl/src/transition.ts
|
|
2586
|
+
function classes(value) {
|
|
2587
|
+
if (value === void 0) return [];
|
|
2588
|
+
const out = [];
|
|
2589
|
+
for (const token of value.split(/\s+/)) {
|
|
2590
|
+
if (token.length > 0 && !out.includes(token)) out.push(token);
|
|
2591
|
+
}
|
|
2592
|
+
return out;
|
|
2593
|
+
}
|
|
2594
|
+
function merge(a, b) {
|
|
2595
|
+
const out = [...a];
|
|
2596
|
+
for (const token of b) if (!out.includes(token)) out.push(token);
|
|
2597
|
+
return out;
|
|
2598
|
+
}
|
|
2599
|
+
function resolveTransition(config) {
|
|
2600
|
+
const n = config.name;
|
|
2601
|
+
const enterActive = merge(
|
|
2602
|
+
classes(config.enter),
|
|
2603
|
+
classes(config.enterActive ?? (n !== void 0 ? `${n}-enter-active` : void 0))
|
|
2604
|
+
);
|
|
2605
|
+
const leaveActive = merge(
|
|
2606
|
+
classes(config.leave),
|
|
2607
|
+
classes(config.leaveActive ?? (n !== void 0 ? `${n}-leave-active` : void 0))
|
|
2608
|
+
);
|
|
2609
|
+
return {
|
|
2610
|
+
enterActive,
|
|
2611
|
+
enterFrom: classes(config.enterFrom ?? (n !== void 0 ? `${n}-enter-from` : void 0)),
|
|
2612
|
+
enterTo: classes(config.enterTo ?? (n !== void 0 ? `${n}-enter-to` : void 0)),
|
|
2613
|
+
leaveActive,
|
|
2614
|
+
leaveFrom: classes(config.leaveFrom ?? (n !== void 0 ? `${n}-leave-from` : void 0)),
|
|
2615
|
+
leaveTo: classes(config.leaveTo ?? (n !== void 0 ? `${n}-leave-to` : void 0)),
|
|
2616
|
+
appear: config.appear ?? false,
|
|
2617
|
+
duration: config.duration ?? 1e3
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
// ../state/src/signal.ts
|
|
2622
|
+
var _activeConsumer = null;
|
|
2623
|
+
function withConsumer(consumer, fn) {
|
|
2624
|
+
const prev = _activeConsumer;
|
|
2625
|
+
_activeConsumer = consumer;
|
|
2626
|
+
try {
|
|
2627
|
+
return fn();
|
|
2628
|
+
} finally {
|
|
2629
|
+
_activeConsumer = prev;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
var _batchDepth = 0;
|
|
2633
|
+
var _pendingFlushes = /* @__PURE__ */ new Map();
|
|
2634
|
+
function _enqueueBatchFlush(sig, value) {
|
|
2635
|
+
_pendingFlushes.set(sig, { signal: sig, value });
|
|
2636
|
+
}
|
|
2637
|
+
var Signal = class {
|
|
2638
|
+
_value;
|
|
2639
|
+
_subscribers = /* @__PURE__ */ new Set();
|
|
2640
|
+
_consumers = /* @__PURE__ */ new Set();
|
|
2641
|
+
constructor(initial) {
|
|
2642
|
+
this._value = initial;
|
|
2643
|
+
}
|
|
2644
|
+
get() {
|
|
2645
|
+
if (_activeConsumer !== null) {
|
|
2646
|
+
this._consumers.add(_activeConsumer);
|
|
2647
|
+
_activeConsumer._addSource(this);
|
|
2648
|
+
}
|
|
2649
|
+
return this._value;
|
|
2650
|
+
}
|
|
2651
|
+
peek() {
|
|
2652
|
+
return this._value;
|
|
2653
|
+
}
|
|
2654
|
+
set(value) {
|
|
2655
|
+
if (Object.is(this._value, value)) return;
|
|
2656
|
+
this._value = value;
|
|
2657
|
+
if (_batchDepth > 0) {
|
|
2658
|
+
_enqueueBatchFlush(this, value);
|
|
2659
|
+
} else {
|
|
2660
|
+
this._flush(value);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
update(fn) {
|
|
2664
|
+
this.set(fn(this._value));
|
|
2665
|
+
}
|
|
2666
|
+
subscribe(fn) {
|
|
2667
|
+
this._subscribers.add(fn);
|
|
2668
|
+
return () => {
|
|
2669
|
+
this._subscribers.delete(fn);
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
_removeConsumer(consumer) {
|
|
2673
|
+
this._consumers.delete(consumer);
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Called by the batch machinery after the batch has completed.
|
|
2677
|
+
* Notifies subscribers with the final coalesced value.
|
|
2678
|
+
*/
|
|
2679
|
+
_flushBatch(value) {
|
|
2680
|
+
this._flush(value);
|
|
2681
|
+
}
|
|
2682
|
+
_flush(value) {
|
|
2683
|
+
for (const sub of [...this._subscribers]) sub(value);
|
|
2684
|
+
for (const consumer of [...this._consumers]) consumer._invalidate();
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* @internal DevTools inspection only. The number of live observers
|
|
2688
|
+
* (direct subscribers plus derived/effect consumers). Read-only; never
|
|
2689
|
+
* mutates reactive state.
|
|
2690
|
+
*/
|
|
2691
|
+
_observerCount() {
|
|
2692
|
+
return this._subscribers.size + this._consumers.size;
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
var DerivedSignal = class {
|
|
2696
|
+
_value = void 0;
|
|
2697
|
+
_dirty = true;
|
|
2698
|
+
_disposed = false;
|
|
2699
|
+
_fn;
|
|
2700
|
+
_subscribers = /* @__PURE__ */ new Set();
|
|
2701
|
+
/** All upstream sources this derived currently reads from. */
|
|
2702
|
+
_sources = /* @__PURE__ */ new Set();
|
|
2703
|
+
/** Downstream consumers that depend on this derived. */
|
|
2704
|
+
_consumers = /* @__PURE__ */ new Set();
|
|
2705
|
+
constructor(fn) {
|
|
2706
|
+
this._fn = fn;
|
|
2707
|
+
}
|
|
2708
|
+
get() {
|
|
2709
|
+
if (_activeConsumer !== null) {
|
|
2710
|
+
this._consumers.add(_activeConsumer);
|
|
2711
|
+
_activeConsumer._addSource(this);
|
|
2712
|
+
}
|
|
2713
|
+
if (this._dirty) this._recompute();
|
|
2714
|
+
return this._value;
|
|
2715
|
+
}
|
|
2716
|
+
peek() {
|
|
2717
|
+
if (this._dirty) this._recompute();
|
|
2718
|
+
return this._value;
|
|
2719
|
+
}
|
|
2720
|
+
subscribe(fn) {
|
|
2721
|
+
if (this._dirty) this._recompute();
|
|
2722
|
+
this._subscribers.add(fn);
|
|
2723
|
+
return () => {
|
|
2724
|
+
this._subscribers.delete(fn);
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
_addSource(src) {
|
|
2728
|
+
this._sources.add(src);
|
|
2729
|
+
}
|
|
2730
|
+
_removeConsumer(consumer) {
|
|
2731
|
+
this._consumers.delete(consumer);
|
|
2732
|
+
}
|
|
2733
|
+
_invalidate() {
|
|
2734
|
+
if (this._disposed) return;
|
|
2735
|
+
this._dirty = true;
|
|
2736
|
+
const newVal = this.peek();
|
|
2737
|
+
for (const sub of [...this._subscribers]) sub(newVal);
|
|
2738
|
+
for (const consumer of [...this._consumers]) consumer._invalidate();
|
|
2739
|
+
}
|
|
2740
|
+
_recompute() {
|
|
2741
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2742
|
+
this._sources.clear();
|
|
2743
|
+
this._value = withConsumer(this, this._fn);
|
|
2744
|
+
this._dirty = false;
|
|
2745
|
+
}
|
|
2746
|
+
dispose() {
|
|
2747
|
+
this._disposed = true;
|
|
2748
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2749
|
+
this._sources.clear();
|
|
2750
|
+
this._subscribers.clear();
|
|
2751
|
+
this._consumers.clear();
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* @internal DevTools inspection only. Live observers (subscribers plus
|
|
2755
|
+
* downstream consumers). Read-only.
|
|
2756
|
+
*/
|
|
2757
|
+
_observerCount() {
|
|
2758
|
+
return this._subscribers.size + this._consumers.size;
|
|
2759
|
+
}
|
|
2760
|
+
};
|
|
2761
|
+
var Effect = class {
|
|
2762
|
+
_fn;
|
|
2763
|
+
_cleanup = void 0;
|
|
2764
|
+
_disposed = false;
|
|
2765
|
+
_sources = /* @__PURE__ */ new Set();
|
|
2766
|
+
constructor(fn) {
|
|
2767
|
+
this._fn = fn;
|
|
2768
|
+
this._run();
|
|
2769
|
+
}
|
|
2770
|
+
_addSource(src) {
|
|
2771
|
+
this._sources.add(src);
|
|
2772
|
+
}
|
|
2773
|
+
_invalidate() {
|
|
2774
|
+
if (this._disposed) return;
|
|
2775
|
+
this._run();
|
|
2776
|
+
}
|
|
2777
|
+
_run() {
|
|
2778
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2779
|
+
this._sources.clear();
|
|
2780
|
+
if (typeof this._cleanup === "function") this._cleanup();
|
|
2781
|
+
const result = withConsumer(this, this._fn);
|
|
2782
|
+
this._cleanup = typeof result === "function" ? result : void 0;
|
|
2783
|
+
}
|
|
2784
|
+
dispose() {
|
|
2785
|
+
this._disposed = true;
|
|
2786
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2787
|
+
this._sources.clear();
|
|
2788
|
+
if (typeof this._cleanup === "function") this._cleanup();
|
|
2789
|
+
this._cleanup = void 0;
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
function signal(initial) {
|
|
2793
|
+
return new Signal(initial);
|
|
2794
|
+
}
|
|
2795
|
+
function derived(fn) {
|
|
2796
|
+
return new DerivedSignal(fn);
|
|
2797
|
+
}
|
|
2798
|
+
function effect(fn) {
|
|
2799
|
+
const e = new Effect(fn);
|
|
2800
|
+
return () => e.dispose();
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// ../dsl/src/builders.ts
|
|
2804
|
+
function isSignal(v) {
|
|
2805
|
+
return v !== null && typeof v === "object" && typeof v["get"] === "function" && typeof v["subscribe"] === "function";
|
|
2806
|
+
}
|
|
2807
|
+
function bindValue(graph, node, propKey, value) {
|
|
2808
|
+
if (isSignal(value)) {
|
|
2809
|
+
const signalId = `${node.id}:${propKey}`;
|
|
2810
|
+
node.stateRefs.push({ signalId, propKey });
|
|
2811
|
+
graph.registerHandler(`__signal__${signalId}`, value);
|
|
2812
|
+
return value.peek();
|
|
2813
|
+
}
|
|
2814
|
+
return value;
|
|
2815
|
+
}
|
|
2816
|
+
function applyA11yProps(props, options) {
|
|
2817
|
+
if (options.role !== void 0) props["role"] = options.role;
|
|
2818
|
+
if (options.tabIndex !== void 0) props["tabindex"] = String(options.tabIndex);
|
|
2819
|
+
if (options.ariaLabel !== void 0) props["aria-label"] = options.ariaLabel;
|
|
2820
|
+
if (options.ariaLabelledBy !== void 0) props["aria-labelledby"] = options.ariaLabelledBy;
|
|
2821
|
+
if (options.ariaDescribedBy !== void 0) props["aria-describedby"] = options.ariaDescribedBy;
|
|
2822
|
+
if (options.ariaExpanded !== void 0) props["aria-expanded"] = String(options.ariaExpanded);
|
|
2823
|
+
if (options.ariaControls !== void 0) props["aria-controls"] = options.ariaControls;
|
|
2824
|
+
if (options.ariaHidden !== void 0) props["aria-hidden"] = String(options.ariaHidden);
|
|
2825
|
+
if (options.ariaLive !== void 0) props["aria-live"] = options.ariaLive;
|
|
2826
|
+
if (options.ariaCurrent !== void 0) props["aria-current"] = String(options.ariaCurrent);
|
|
2827
|
+
if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
|
|
2828
|
+
if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
|
|
2829
|
+
if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
|
|
2830
|
+
if (options.ariaOwns !== void 0) props["aria-owns"] = options.ariaOwns;
|
|
2831
|
+
if (options.ariaActiveDescendant !== void 0) props["aria-activedescendant"] = options.ariaActiveDescendant;
|
|
2832
|
+
if (options.ariaHasPopup !== void 0) props["aria-haspopup"] = String(options.ariaHasPopup);
|
|
2833
|
+
if (options.ariaSelected !== void 0) props["aria-selected"] = String(options.ariaSelected);
|
|
2834
|
+
}
|
|
2835
|
+
function registerTransition(graph, node, config) {
|
|
2836
|
+
if (config === void 0) return;
|
|
2837
|
+
const resolved = resolveTransition(config);
|
|
2838
|
+
graph.registerHandler(
|
|
2839
|
+
`__transition__${node.id}`,
|
|
2840
|
+
() => resolved
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
function containerProps(options) {
|
|
2844
|
+
const props = {};
|
|
2845
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2846
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2847
|
+
if (options.key !== void 0) props["key"] = options.key;
|
|
2848
|
+
applyA11yProps(props, options);
|
|
2849
|
+
return props;
|
|
2850
|
+
}
|
|
2851
|
+
function itemIdentity(item, index) {
|
|
2852
|
+
if (item !== null && typeof item === "object") {
|
|
2853
|
+
const obj = item;
|
|
2854
|
+
if ("id" in obj) return `id:${String(obj["id"])}`;
|
|
2855
|
+
if ("key" in obj) return `key:${String(obj["key"])}`;
|
|
2856
|
+
return `idx:${index}`;
|
|
2857
|
+
}
|
|
2858
|
+
return `val:${String(item)}`;
|
|
2859
|
+
}
|
|
2860
|
+
function itemValueSignature(item) {
|
|
2861
|
+
try {
|
|
2862
|
+
return JSON.stringify(item) ?? String(item);
|
|
2863
|
+
} catch {
|
|
2864
|
+
return String(item);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function reactiveListItemSignature(item) {
|
|
2868
|
+
return itemValueSignature(item);
|
|
2869
|
+
}
|
|
2870
|
+
function reactiveListItemKey(item, index) {
|
|
2871
|
+
return itemIdentity(item, index);
|
|
2872
|
+
}
|
|
2873
|
+
var OVERLAY_KINDS = {
|
|
2874
|
+
dialog: {
|
|
2875
|
+
role: "dialog",
|
|
2876
|
+
modal: true,
|
|
2877
|
+
takesFocus: true,
|
|
2878
|
+
ariaModal: true,
|
|
2879
|
+
defaultCloseOnEscape: true,
|
|
2880
|
+
defaultRestoreFocus: true
|
|
2881
|
+
},
|
|
2882
|
+
popover: {
|
|
2883
|
+
role: "dialog",
|
|
2884
|
+
modal: false,
|
|
2885
|
+
takesFocus: true,
|
|
2886
|
+
ariaModal: false,
|
|
2887
|
+
defaultCloseOnEscape: true,
|
|
2888
|
+
defaultRestoreFocus: true
|
|
2889
|
+
},
|
|
2890
|
+
tooltip: {
|
|
2891
|
+
role: "tooltip",
|
|
2892
|
+
modal: false,
|
|
2893
|
+
takesFocus: false,
|
|
2894
|
+
ariaModal: false,
|
|
2895
|
+
defaultCloseOnEscape: false,
|
|
2896
|
+
defaultRestoreFocus: false
|
|
2897
|
+
},
|
|
2898
|
+
dropdown: {
|
|
2899
|
+
role: "menu",
|
|
2900
|
+
modal: false,
|
|
2901
|
+
takesFocus: true,
|
|
2902
|
+
menu: true,
|
|
2903
|
+
ariaModal: false,
|
|
2904
|
+
defaultCloseOnEscape: true,
|
|
2905
|
+
defaultRestoreFocus: true
|
|
2906
|
+
},
|
|
2907
|
+
toast: {
|
|
2908
|
+
role: "status",
|
|
2909
|
+
modal: false,
|
|
2910
|
+
takesFocus: false,
|
|
2911
|
+
ariaModal: false,
|
|
2912
|
+
ariaLive: "polite",
|
|
2913
|
+
defaultCloseOnEscape: false,
|
|
2914
|
+
defaultRestoreFocus: false
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
var ContentBuilderBase = class {
|
|
2918
|
+
constructor(_node, _graph) {
|
|
2919
|
+
this._node = _node;
|
|
2920
|
+
this._graph = _graph;
|
|
2921
|
+
}
|
|
2922
|
+
heading(text, options = {}) {
|
|
2923
|
+
const props = { level: options.level ?? 1 };
|
|
2924
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2925
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2926
|
+
applyA11yProps(props, options);
|
|
2927
|
+
const node = this._graph.createNode("heading", { parent: this._node, props });
|
|
2928
|
+
const resolved = bindValue(this._graph, node, "text", text);
|
|
2929
|
+
node.setProp("text", resolved);
|
|
2930
|
+
}
|
|
2931
|
+
text(content, options = {}) {
|
|
2932
|
+
const props = {};
|
|
2933
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2934
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2935
|
+
applyA11yProps(props, options);
|
|
2936
|
+
const node = this._graph.createNode("text", { parent: this._node, props });
|
|
2937
|
+
const resolved = bindValue(this._graph, node, "text", content);
|
|
2938
|
+
node.setProp("text", resolved);
|
|
2939
|
+
}
|
|
2940
|
+
button(label, options = {}) {
|
|
2941
|
+
const props = {};
|
|
2942
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2943
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2944
|
+
applyA11yProps(props, options);
|
|
2945
|
+
const node = this._graph.createNode("button", { parent: this._node, props });
|
|
2946
|
+
const resolved = bindValue(this._graph, node, "label", label);
|
|
2947
|
+
node.setProp("label", resolved);
|
|
2948
|
+
if (options.disabled !== void 0) {
|
|
2949
|
+
const resolvedDisabled = bindValue(this._graph, node, "disabled", options.disabled);
|
|
2950
|
+
node.setProp("disabled", resolvedDisabled);
|
|
2951
|
+
}
|
|
2952
|
+
if (options.onClick !== void 0) {
|
|
2953
|
+
const handlerKey = `click:${node.id}`;
|
|
2954
|
+
this._graph.registerHandler(handlerKey, options.onClick);
|
|
2955
|
+
node.addEvent({ type: "click", handlerKey });
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
input(options = {}) {
|
|
2959
|
+
const props = {};
|
|
2960
|
+
props["inputType"] = options.type ?? "text";
|
|
2961
|
+
if (options.placeholder !== void 0) props["placeholder"] = options.placeholder;
|
|
2962
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2963
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2964
|
+
applyA11yProps(props, options);
|
|
2965
|
+
const nodeOpts = {
|
|
2966
|
+
props,
|
|
2967
|
+
parent: this._node
|
|
2968
|
+
};
|
|
2969
|
+
if (options.id !== void 0) nodeOpts.key = options.id;
|
|
2970
|
+
const node = this._graph.createNode("input", nodeOpts);
|
|
2971
|
+
const bindSignal = options.bind;
|
|
2972
|
+
const valueBindable = bindSignal !== void 0 ? bindSignal : options.value;
|
|
2973
|
+
const inputHandler = bindSignal !== void 0 ? (v) => bindSignal.set(v) : options.onInput;
|
|
2974
|
+
if (valueBindable !== void 0) {
|
|
2975
|
+
const resolved = bindValue(this._graph, node, "value", valueBindable);
|
|
2976
|
+
node.setProp("value", resolved);
|
|
2977
|
+
}
|
|
2978
|
+
if (options.disabled !== void 0) {
|
|
2979
|
+
const resolved = bindValue(this._graph, node, "disabled", options.disabled);
|
|
2980
|
+
node.setProp("disabled", resolved);
|
|
2981
|
+
}
|
|
2982
|
+
if (inputHandler !== void 0) {
|
|
2983
|
+
const handlerKey = `input:${node.id}`;
|
|
2984
|
+
this._graph.registerHandler(handlerKey, inputHandler);
|
|
2985
|
+
node.addEvent({ type: "input", handlerKey });
|
|
2986
|
+
}
|
|
2987
|
+
if (options.onChange !== void 0) {
|
|
2988
|
+
const handlerKey = `change:${node.id}`;
|
|
2989
|
+
this._graph.registerHandler(handlerKey, options.onChange);
|
|
2990
|
+
node.addEvent({ type: "change", handlerKey });
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
image(options) {
|
|
2994
|
+
const props = {
|
|
2995
|
+
src: options.src,
|
|
2996
|
+
alt: options.alt
|
|
2997
|
+
};
|
|
2998
|
+
if (options.width !== void 0) props["width"] = options.width;
|
|
2999
|
+
if (options.height !== void 0) props["height"] = options.height;
|
|
3000
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
3001
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
3002
|
+
applyA11yProps(props, options);
|
|
3003
|
+
const nodeOpts = {
|
|
3004
|
+
props,
|
|
3005
|
+
parent: this._node
|
|
3006
|
+
};
|
|
3007
|
+
if (options.id !== void 0) nodeOpts.key = options.id;
|
|
3008
|
+
this._graph.createNode("image", nodeOpts);
|
|
3009
|
+
}
|
|
3010
|
+
link(label, options) {
|
|
3011
|
+
const props = {
|
|
3012
|
+
href: options.href,
|
|
3013
|
+
external: options.external ?? false
|
|
3014
|
+
};
|
|
3015
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
3016
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
3017
|
+
applyA11yProps(props, options);
|
|
3018
|
+
const node = this._graph.createNode("link", { parent: this._node, props });
|
|
3019
|
+
const resolved = bindValue(this._graph, node, "label", label);
|
|
3020
|
+
node.setProp("label", resolved);
|
|
3021
|
+
if (options.onClick !== void 0) {
|
|
3022
|
+
const handlerKey = `click:${node.id}`;
|
|
3023
|
+
this._graph.registerHandler(handlerKey, options.onClick);
|
|
3024
|
+
node.addEvent({ type: "click", handlerKey });
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
};
|
|
3028
|
+
var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
3029
|
+
section(key, builder, options = {}) {
|
|
3030
|
+
const node = this._graph.createNode("section", {
|
|
3031
|
+
key,
|
|
3032
|
+
parent: this._node,
|
|
3033
|
+
props: containerProps(options)
|
|
3034
|
+
});
|
|
3035
|
+
registerTransition(this._graph, node, options.transition);
|
|
3036
|
+
builder(new SectionBuilderImpl(node, this._graph));
|
|
3037
|
+
}
|
|
3038
|
+
container(key, builder, options = {}) {
|
|
3039
|
+
const node = this._graph.createNode("container", {
|
|
3040
|
+
key,
|
|
3041
|
+
parent: this._node,
|
|
3042
|
+
props: containerProps(options)
|
|
3043
|
+
});
|
|
3044
|
+
registerTransition(this._graph, node, options.transition);
|
|
3045
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3046
|
+
}
|
|
3047
|
+
list(key, builder, options = {}) {
|
|
3048
|
+
const node = this._graph.createNode("list", {
|
|
3049
|
+
key,
|
|
3050
|
+
parent: this._node,
|
|
3051
|
+
props: containerProps(options)
|
|
3052
|
+
});
|
|
3053
|
+
registerTransition(this._graph, node, options.transition);
|
|
3054
|
+
builder(new ListBuilderImpl(node, this._graph));
|
|
3055
|
+
}
|
|
3056
|
+
listOf(key, items, renderItem, options = {}) {
|
|
3057
|
+
const graph = this._graph;
|
|
3058
|
+
const node = graph.createNode("reactive-list", {
|
|
3059
|
+
key,
|
|
3060
|
+
parent: this._node,
|
|
3061
|
+
props: containerProps(options)
|
|
3062
|
+
});
|
|
3063
|
+
registerTransition(graph, node, options.transition);
|
|
3064
|
+
const signalId = `${node.id}:items`;
|
|
3065
|
+
node.stateRefs.push({ signalId, propKey: "items" });
|
|
3066
|
+
graph.registerHandler(`__signal__${signalId}`, items);
|
|
3067
|
+
const buildItem = (item, index) => {
|
|
3068
|
+
const itemKey = reactiveListItemKey(item, index);
|
|
3069
|
+
const itemNode = graph.createNode("list-item", {
|
|
3070
|
+
key: itemKey,
|
|
3071
|
+
// `_item` records the source item *reference* so the reconciler can
|
|
3072
|
+
// short-circuit unchanged rows by identity (no signature hashing); `_sig`
|
|
3073
|
+
// is the content signature used to detect an in-place data change when the
|
|
3074
|
+
// reference differs. Both are internal metadata (leading `_`) and never
|
|
3075
|
+
// reach the DOM.
|
|
3076
|
+
props: {
|
|
3077
|
+
key: itemKey,
|
|
3078
|
+
_sig: reactiveListItemSignature(item),
|
|
3079
|
+
// The item reference is stored as opaque internal metadata (never
|
|
3080
|
+
// rendered); cast through `unknown` since `T` is not a `PropValue`.
|
|
3081
|
+
_item: item
|
|
3082
|
+
}
|
|
3083
|
+
});
|
|
3084
|
+
renderItem(item, index, new ContainerBuilderImpl(itemNode, graph));
|
|
3085
|
+
registerTransition(graph, itemNode, options.itemTransition);
|
|
3086
|
+
return itemNode;
|
|
3087
|
+
};
|
|
3088
|
+
const buildPlan = (raw) => {
|
|
3089
|
+
const arr = Array.isArray(raw) ? raw : [];
|
|
3090
|
+
const plan = new Array(arr.length);
|
|
3091
|
+
for (let i = 0; i < arr.length; i++) {
|
|
3092
|
+
const item = arr[i];
|
|
3093
|
+
const index = i;
|
|
3094
|
+
plan[i] = {
|
|
3095
|
+
key: reactiveListItemKey(item, index),
|
|
3096
|
+
item,
|
|
3097
|
+
sig: () => reactiveListItemSignature(item),
|
|
3098
|
+
build: () => buildItem(item, index)
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
return plan;
|
|
3102
|
+
};
|
|
3103
|
+
graph.registerHandler(`__listplan__${node.id}`, buildPlan);
|
|
3104
|
+
const current = isSignal(items) ? items.peek() : items;
|
|
3105
|
+
const initial = Array.isArray(current) ? current : [];
|
|
3106
|
+
initial.forEach((item, i) => {
|
|
3107
|
+
node.appendChild(buildItem(item, i));
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
form(key, builder, options = {}) {
|
|
3111
|
+
const props = containerProps(options);
|
|
3112
|
+
const node = this._graph.createNode("form", {
|
|
3113
|
+
key,
|
|
3114
|
+
parent: this._node,
|
|
3115
|
+
props
|
|
3116
|
+
});
|
|
3117
|
+
registerTransition(this._graph, node, options.transition);
|
|
3118
|
+
if (options.onSubmit !== void 0) {
|
|
3119
|
+
const handlerKey = `submit:${node.id}`;
|
|
3120
|
+
this._graph.registerHandler(handlerKey, options.onSubmit);
|
|
3121
|
+
node.addEvent({ type: "submit", handlerKey });
|
|
3122
|
+
}
|
|
3123
|
+
builder(new FormBuilderImpl(node, this._graph));
|
|
3124
|
+
}
|
|
3125
|
+
when(condition, builder, elseBuilder, options = {}) {
|
|
3126
|
+
const graph = this._graph;
|
|
3127
|
+
const node = graph.createNode("conditional", {
|
|
3128
|
+
parent: this._node,
|
|
3129
|
+
props: containerProps({})
|
|
3130
|
+
});
|
|
3131
|
+
const branchTransition = options.transition !== void 0 && options.appear === true ? { ...options.transition, appear: true } : options.transition;
|
|
3132
|
+
const buildBranch = (build, tag) => {
|
|
3133
|
+
const branchKey = `when-${tag}:${node.id}`;
|
|
3134
|
+
const branch = graph.createNode("container", {
|
|
3135
|
+
key: branchKey,
|
|
3136
|
+
props: { key: branchKey }
|
|
3137
|
+
});
|
|
3138
|
+
registerTransition(graph, branch, branchTransition);
|
|
3139
|
+
build(new ContainerBuilderImpl(branch, graph));
|
|
3140
|
+
return branch;
|
|
3141
|
+
};
|
|
3142
|
+
const buildAll = (raw) => {
|
|
3143
|
+
if (raw) return [buildBranch(builder, "then")];
|
|
3144
|
+
return elseBuilder !== void 0 ? [buildBranch(elseBuilder, "else")] : [];
|
|
3145
|
+
};
|
|
3146
|
+
if (isSignal(condition)) {
|
|
3147
|
+
const signalId = `${node.id}:items`;
|
|
3148
|
+
node.stateRefs.push({ signalId, propKey: "items" });
|
|
3149
|
+
graph.registerHandler(`__signal__${signalId}`, condition);
|
|
3150
|
+
graph.registerHandler(`__listbuild__${node.id}`, buildAll);
|
|
3151
|
+
const current = condition.peek();
|
|
3152
|
+
for (const child of buildAll(current)) node.appendChild(child);
|
|
3153
|
+
} else {
|
|
3154
|
+
for (const child of buildAll(condition)) node.appendChild(child);
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
errorBoundary(id, builder, options) {
|
|
3158
|
+
const sources = options.source === void 0 ? [] : Array.isArray(options.source) ? [...options.source] : [options.source];
|
|
3159
|
+
const localError = signal(void 0);
|
|
3160
|
+
const retryNonce = signal(0);
|
|
3161
|
+
const readError = () => {
|
|
3162
|
+
const local = localError.peek();
|
|
3163
|
+
if (local !== void 0 && local !== null) return local;
|
|
3164
|
+
for (const s of sources) {
|
|
3165
|
+
const e = s.peek();
|
|
3166
|
+
if (e !== void 0 && e !== null) return e;
|
|
3167
|
+
}
|
|
3168
|
+
return void 0;
|
|
3169
|
+
};
|
|
3170
|
+
const hasError = derived(() => {
|
|
3171
|
+
retryNonce.get();
|
|
3172
|
+
localError.get();
|
|
3173
|
+
for (const s of sources) s.get();
|
|
3174
|
+
return readError() !== void 0;
|
|
3175
|
+
});
|
|
3176
|
+
const retry = () => {
|
|
3177
|
+
localError.set(void 0);
|
|
3178
|
+
options.onRetry?.();
|
|
3179
|
+
retryNonce.update((n) => n + 1);
|
|
3180
|
+
};
|
|
3181
|
+
this.container(id, (c) => {
|
|
3182
|
+
c.when(
|
|
3183
|
+
hasError,
|
|
3184
|
+
// Error state → fallback.
|
|
3185
|
+
(fb) => options.fallback(fb, readError(), retry),
|
|
3186
|
+
// Healthy state → body, guarded against synchronous build throws.
|
|
3187
|
+
(body) => {
|
|
3188
|
+
try {
|
|
3189
|
+
builder(body);
|
|
3190
|
+
} catch (err) {
|
|
3191
|
+
queueMicrotask(() => localError.set(err));
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
);
|
|
3195
|
+
}, { id });
|
|
3196
|
+
}
|
|
3197
|
+
// ── Portals & overlays ──────────────────────────────────────────────────────
|
|
3198
|
+
portal(key, builder, options = {}) {
|
|
3199
|
+
const node = this._graph.createNode("portal", {
|
|
3200
|
+
key,
|
|
3201
|
+
parent: this._node,
|
|
3202
|
+
props: containerProps(options)
|
|
3203
|
+
});
|
|
3204
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Shared assembly for every overlay kind: a `portal` node whose single child
|
|
3208
|
+
* is a `when(open, panel)` conditional. The panel container carries the
|
|
3209
|
+
* kind's ARIA semantics; `builder` fills it. An `__overlay__<portalId>`
|
|
3210
|
+
* descriptor is registered so the renderer wires focus/keyboard behavior to
|
|
3211
|
+
* the same `open` signal that drives the panel. Reuses existing primitives
|
|
3212
|
+
* (portal + when + container) — no new render path.
|
|
3213
|
+
*/
|
|
3214
|
+
_overlay(kind, key, options, builder) {
|
|
3215
|
+
const graph = this._graph;
|
|
3216
|
+
const portalNode = graph.createNode("portal", {
|
|
3217
|
+
key,
|
|
3218
|
+
parent: this._node,
|
|
3219
|
+
props: { key }
|
|
3220
|
+
});
|
|
3221
|
+
const openBindable = options.open;
|
|
3222
|
+
const openSignal = isSignal(openBindable) ? openBindable : signal(openBindable);
|
|
3223
|
+
const panelOptions = {
|
|
3224
|
+
role: options.role ?? kind.role,
|
|
3225
|
+
...kind.ariaModal ? { ariaModal: true } : {},
|
|
3226
|
+
...kind.ariaLive !== void 0 ? { ariaLive: kind.ariaLive } : {},
|
|
3227
|
+
...options.class !== void 0 ? { class: options.class } : {},
|
|
3228
|
+
...options.ariaLabel !== void 0 ? { ariaLabel: options.ariaLabel } : {},
|
|
3229
|
+
...options.ariaLabelledBy !== void 0 ? { ariaLabelledBy: options.ariaLabelledBy } : {},
|
|
3230
|
+
...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
|
|
3231
|
+
};
|
|
3232
|
+
const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
|
|
3233
|
+
portalBuilder.when(
|
|
3234
|
+
openSignal,
|
|
3235
|
+
(panelHost) => {
|
|
3236
|
+
panelHost.container(`${key}__panel`, builder, panelOptions);
|
|
3237
|
+
},
|
|
3238
|
+
void 0,
|
|
3239
|
+
// Overlay open/close rides the panel's `when`; a transition animates the
|
|
3240
|
+
// panel in on open and — via the reconciler's deferred-leave — plays the
|
|
3241
|
+
// leave before the panel is removed (§10). Focus is restored at close-
|
|
3242
|
+
// request time (see wireOverlayBehavior), so it never stays trapped inside
|
|
3243
|
+
// a panel that is animating away.
|
|
3244
|
+
options.transition !== void 0 ? { transition: options.transition } : {}
|
|
3245
|
+
);
|
|
3246
|
+
const descriptor = {
|
|
3247
|
+
open: openSignal,
|
|
3248
|
+
modal: kind.modal,
|
|
3249
|
+
takesFocus: kind.takesFocus,
|
|
3250
|
+
menu: kind.menu ?? false,
|
|
3251
|
+
closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
|
|
3252
|
+
restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
|
|
3253
|
+
...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
|
|
3254
|
+
...options.onClose !== void 0 ? { onClose: options.onClose } : {}
|
|
3255
|
+
};
|
|
3256
|
+
graph.registerHandler(
|
|
3257
|
+
`__overlay__${portalNode.id}`,
|
|
3258
|
+
() => descriptor
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
dialog(key, options, builder) {
|
|
3262
|
+
this._overlay(OVERLAY_KINDS.dialog, key, options, builder);
|
|
3263
|
+
}
|
|
3264
|
+
popover(key, options, builder) {
|
|
3265
|
+
this._overlay(OVERLAY_KINDS.popover, key, options, builder);
|
|
3266
|
+
}
|
|
3267
|
+
tooltip(key, options, builder) {
|
|
3268
|
+
this._overlay(OVERLAY_KINDS.tooltip, key, options, builder);
|
|
3269
|
+
}
|
|
3270
|
+
dropdown(key, options, builder) {
|
|
3271
|
+
this._overlay(OVERLAY_KINDS.dropdown, key, options, builder);
|
|
3272
|
+
}
|
|
3273
|
+
toast(key, options, builder) {
|
|
3274
|
+
this._overlay(OVERLAY_KINDS.toast, key, options, builder);
|
|
3275
|
+
}
|
|
3276
|
+
// ── Components ────────────────────────────────────────────────────────────
|
|
3277
|
+
/**
|
|
3278
|
+
* Instantiate a reusable component (§3–§9). Creates a `'component'` node
|
|
3279
|
+
* (rendered as a `<div>` wrapper — preserves the one-node/one-element
|
|
3280
|
+
* positional-hydration invariant), then runs `def.setup(props, ctx)`
|
|
3281
|
+
* synchronously to obtain the render function and fills the component's own
|
|
3282
|
+
* container scope with it — structurally identical to `container`/
|
|
3283
|
+
* `errorBoundary`. Cleanups the setup registers via `ctx.effect`/
|
|
3284
|
+
* `ctx.onCleanup` are collected into a closure and exposed to the renderer
|
|
3285
|
+
* through a `__component__<id>` handler (mirroring `__overlay__`); the mount/
|
|
3286
|
+
* hydrate paths read it and route each into `NodeInstance.trackCleanup`, so
|
|
3287
|
+
* teardown runs (children-first) when the component leaves the graph.
|
|
3288
|
+
*
|
|
3289
|
+
* `setup` runs once per instance here at build time. When this component sits
|
|
3290
|
+
* inside a keyed list / conditional, a rebuild disposes the old instance
|
|
3291
|
+
* (running its cleanups + pruning its `__component__` entry) and re-runs this
|
|
3292
|
+
* method for the new node — so re-invocation is safe and leak-free.
|
|
3293
|
+
*/
|
|
3294
|
+
component(key, def, props, children) {
|
|
3295
|
+
const graph = this._graph;
|
|
3296
|
+
const node = graph.createNode("component", {
|
|
3297
|
+
key,
|
|
3298
|
+
parent: this._node,
|
|
3299
|
+
// `data-streetui-component` is a non-underscore prop, so it reaches the
|
|
3300
|
+
// DOM as an attribute and is visible to DevTools (§21) — unlike the
|
|
3301
|
+
// internal `_`-prefixed metadata the renderer hides.
|
|
3302
|
+
props: { key, "data-streetui-component": def.name }
|
|
3303
|
+
});
|
|
3304
|
+
const cleanups = [];
|
|
3305
|
+
const ctx = {
|
|
3306
|
+
key,
|
|
3307
|
+
onCleanup(fn) {
|
|
3308
|
+
cleanups.push(fn);
|
|
3309
|
+
},
|
|
3310
|
+
effect(fn) {
|
|
3311
|
+
cleanups.push(effect(fn));
|
|
3312
|
+
},
|
|
3313
|
+
renderChildren(content) {
|
|
3314
|
+
if (children !== void 0) children(content);
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
let render2;
|
|
3318
|
+
try {
|
|
3319
|
+
render2 = def.setup(props, ctx);
|
|
3320
|
+
render2(new ContainerBuilderImpl(node, graph));
|
|
3321
|
+
} catch (err) {
|
|
3322
|
+
queueMicrotask(() => {
|
|
3323
|
+
throw err;
|
|
3324
|
+
});
|
|
3325
|
+
}
|
|
3326
|
+
if (cleanups.length > 0) {
|
|
3327
|
+
graph.registerHandler(
|
|
3328
|
+
`__component__${node.id}`,
|
|
3329
|
+
() => cleanups
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
var SectionBuilderImpl = class extends ContainerBuilderBase {
|
|
3335
|
+
};
|
|
3336
|
+
var ContainerBuilderImpl = class extends ContainerBuilderBase {
|
|
3337
|
+
};
|
|
3338
|
+
var FormBuilderImpl = class extends ContainerBuilderBase {
|
|
3339
|
+
};
|
|
3340
|
+
var ListBuilderImpl = class extends ContentBuilderBase {
|
|
3341
|
+
item(key, builder, options = {}) {
|
|
3342
|
+
const node = this._graph.createNode("list-item", {
|
|
3343
|
+
key,
|
|
3344
|
+
parent: this._node,
|
|
3345
|
+
props: containerProps(options)
|
|
3346
|
+
});
|
|
3347
|
+
registerTransition(this._graph, node, options.transition);
|
|
3348
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
var PageBuilderImpl = class extends ContainerBuilderBase {
|
|
3352
|
+
};
|
|
3353
|
+
var AppBuilder = class {
|
|
3354
|
+
constructor(_graph) {
|
|
3355
|
+
this._graph = _graph;
|
|
3356
|
+
}
|
|
3357
|
+
page(key, builder) {
|
|
3358
|
+
const node = this._graph.createNode("page", {
|
|
3359
|
+
key,
|
|
3360
|
+
parent: this._graph.root,
|
|
3361
|
+
props: { key }
|
|
3362
|
+
});
|
|
3363
|
+
builder(new PageBuilderImpl(node, this._graph));
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
|
|
3367
|
+
// ../graph/src/graph-node.ts
|
|
3368
|
+
var GraphNode = class _GraphNode {
|
|
3369
|
+
id;
|
|
3370
|
+
type;
|
|
3371
|
+
key;
|
|
3372
|
+
props;
|
|
3373
|
+
events;
|
|
3374
|
+
stateRefs;
|
|
3375
|
+
children;
|
|
3376
|
+
parent;
|
|
3377
|
+
constructor(type, options = {}) {
|
|
3378
|
+
this.type = type;
|
|
3379
|
+
this.id = options.id ?? generateNodeId(type);
|
|
3380
|
+
this.key = options.key;
|
|
3381
|
+
this.props = options.props ?? {};
|
|
3382
|
+
this.events = options.events ?? [];
|
|
3383
|
+
this.stateRefs = options.stateRefs ?? [];
|
|
3384
|
+
this.children = [];
|
|
3385
|
+
this.parent = null;
|
|
3386
|
+
}
|
|
3387
|
+
// ── Child management ────────────────────────────────────────────────────────
|
|
3388
|
+
appendChild(child) {
|
|
3389
|
+
if (child.parent !== null) {
|
|
3390
|
+
child.parent.removeChild(child);
|
|
3391
|
+
}
|
|
3392
|
+
child.parent = this;
|
|
3393
|
+
this.children.push(child);
|
|
3394
|
+
}
|
|
3395
|
+
insertBefore(child, reference) {
|
|
3396
|
+
const idx = this.children.indexOf(reference);
|
|
3397
|
+
if (idx === -1) {
|
|
3398
|
+
this.appendChild(child);
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
if (child.parent !== null) {
|
|
3402
|
+
child.parent.removeChild(child);
|
|
3403
|
+
}
|
|
3404
|
+
child.parent = this;
|
|
3405
|
+
this.children.splice(idx, 0, child);
|
|
3406
|
+
}
|
|
3407
|
+
removeChild(child) {
|
|
3408
|
+
const idx = this.children.indexOf(child);
|
|
3409
|
+
if (idx === -1) return;
|
|
3410
|
+
this.children.splice(idx, 1);
|
|
3411
|
+
child.parent = null;
|
|
3412
|
+
}
|
|
3413
|
+
replaceChild(newChild, oldChild) {
|
|
3414
|
+
const idx = this.children.indexOf(oldChild);
|
|
3415
|
+
if (idx === -1) {
|
|
3416
|
+
throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);
|
|
3417
|
+
}
|
|
3418
|
+
if (newChild.parent !== null) {
|
|
3419
|
+
newChild.parent.removeChild(newChild);
|
|
3420
|
+
}
|
|
3421
|
+
oldChild.parent = null;
|
|
3422
|
+
newChild.parent = this;
|
|
3423
|
+
this.children.splice(idx, 1, newChild);
|
|
3424
|
+
}
|
|
3425
|
+
// ── Prop helpers ────────────────────────────────────────────────────────────
|
|
3426
|
+
setProp(key, value) {
|
|
3427
|
+
this.props = { ...this.props, [key]: value };
|
|
3428
|
+
}
|
|
3429
|
+
getProp(key) {
|
|
3430
|
+
return this.props[key];
|
|
3431
|
+
}
|
|
3432
|
+
// ── Event helpers ───────────────────────────────────────────────────────────
|
|
3433
|
+
addEvent(descriptor) {
|
|
3434
|
+
this.events.push(descriptor);
|
|
3435
|
+
}
|
|
3436
|
+
removeEvent(type) {
|
|
3437
|
+
this.events = this.events.filter((e) => e.type !== type);
|
|
3438
|
+
}
|
|
3439
|
+
// ── Queries ─────────────────────────────────────────────────────────────────
|
|
3440
|
+
get isLeaf() {
|
|
3441
|
+
return this.children.length === 0;
|
|
3442
|
+
}
|
|
3443
|
+
get depth() {
|
|
3444
|
+
let d = 0;
|
|
3445
|
+
let node = this.parent;
|
|
3446
|
+
while (node !== null) {
|
|
3447
|
+
d++;
|
|
3448
|
+
node = node.parent;
|
|
3449
|
+
}
|
|
3450
|
+
return d;
|
|
3451
|
+
}
|
|
3452
|
+
get root() {
|
|
3453
|
+
let node = this;
|
|
3454
|
+
while (node.parent !== null) {
|
|
3455
|
+
node = node.parent;
|
|
3456
|
+
}
|
|
3457
|
+
return node;
|
|
3458
|
+
}
|
|
3459
|
+
/** Shallow clone — does not clone children. */
|
|
3460
|
+
shallowClone() {
|
|
3461
|
+
const opts = {
|
|
3462
|
+
props: { ...this.props },
|
|
3463
|
+
events: [...this.events],
|
|
3464
|
+
stateRefs: [...this.stateRefs]
|
|
3465
|
+
};
|
|
3466
|
+
if (this.key !== void 0) opts.key = this.key;
|
|
3467
|
+
return new _GraphNode(this.type, opts);
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
|
|
3471
|
+
// ../graph/src/graph.ts
|
|
3472
|
+
var ApplicationGraph = class {
|
|
3473
|
+
root;
|
|
3474
|
+
name;
|
|
3475
|
+
version;
|
|
3476
|
+
_nodeIndex = /* @__PURE__ */ new Map();
|
|
3477
|
+
/** Handler registry — maps handlerKey → actual function */
|
|
3478
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3479
|
+
constructor(options) {
|
|
3480
|
+
this.name = options.name;
|
|
3481
|
+
this.version = options.version ?? "0.0.1";
|
|
3482
|
+
this.root = new GraphNode("application", { props: { name: options.name } });
|
|
3483
|
+
this._nodeIndex.set(this.root.id, this.root);
|
|
3484
|
+
}
|
|
3485
|
+
// ── Node creation & attachment ────────────────────────────────────────────
|
|
3486
|
+
createNode(type, options = {}) {
|
|
3487
|
+
const nodeOpts = {};
|
|
3488
|
+
if (options.key !== void 0) nodeOpts.key = options.key;
|
|
3489
|
+
if (options.props !== void 0) nodeOpts.props = options.props;
|
|
3490
|
+
const node = new GraphNode(type, nodeOpts);
|
|
3491
|
+
this._nodeIndex.set(node.id, node);
|
|
3492
|
+
if (options.parent !== void 0) {
|
|
3493
|
+
options.parent.appendChild(node);
|
|
3494
|
+
}
|
|
3495
|
+
return node;
|
|
3496
|
+
}
|
|
3497
|
+
attachNode(node, parent) {
|
|
3498
|
+
this._nodeIndex.set(node.id, node);
|
|
3499
|
+
parent.appendChild(node);
|
|
3500
|
+
}
|
|
3501
|
+
detachNode(node) {
|
|
3502
|
+
if (node.parent !== null) {
|
|
3503
|
+
node.parent.removeChild(node);
|
|
3504
|
+
}
|
|
3505
|
+
this._removeFromIndex(node);
|
|
3506
|
+
}
|
|
3507
|
+
_removeFromIndex(node) {
|
|
3508
|
+
this._nodeIndex.delete(node.id);
|
|
3509
|
+
this._unregisterNodeHandlers(node);
|
|
3510
|
+
for (const child of node.children) {
|
|
3511
|
+
this._removeFromIndex(child);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Remove every handler-registry entry owned by a single node. A node owns:
|
|
3516
|
+
* - one entry per event descriptor (its `handlerKey`),
|
|
3517
|
+
* - one `__signal__<signalId>` entry per state ref (signalIds are namespaced
|
|
3518
|
+
* by node id, so they are never shared between nodes), and
|
|
3519
|
+
* - a `__listbuild__<id>` entry if it is a reactive-list.
|
|
3520
|
+
* Called for every node in a detached subtree so removing list items (or
|
|
3521
|
+
* discarding freshly-built-but-unadopted item subtrees) leaves no stale
|
|
3522
|
+
* registrations behind.
|
|
3523
|
+
*/
|
|
3524
|
+
_unregisterNodeHandlers(node) {
|
|
3525
|
+
for (const event of node.events) {
|
|
3526
|
+
this.handlers.delete(event.handlerKey);
|
|
3527
|
+
}
|
|
3528
|
+
for (const ref of node.stateRefs) {
|
|
3529
|
+
this.handlers.delete(`__signal__${ref.signalId}`);
|
|
3530
|
+
}
|
|
3531
|
+
this.handlers.delete(`__listbuild__${node.id}`);
|
|
3532
|
+
this.handlers.delete(`__listplan__${node.id}`);
|
|
3533
|
+
this.handlers.delete(`__overlay__${node.id}`);
|
|
3534
|
+
this.handlers.delete(`__component__${node.id}`);
|
|
3535
|
+
this.handlers.delete(`__transition__${node.id}`);
|
|
3536
|
+
}
|
|
3537
|
+
// ── Handler registry ──────────────────────────────────────────────────────
|
|
3538
|
+
registerHandler(key, fn) {
|
|
3539
|
+
this.handlers.set(key, fn);
|
|
3540
|
+
}
|
|
3541
|
+
getHandler(key) {
|
|
3542
|
+
return this.handlers.get(key);
|
|
3543
|
+
}
|
|
3544
|
+
/** True if a handler is currently registered under `key`. Inspection helper. */
|
|
3545
|
+
hasHandler(key) {
|
|
3546
|
+
return this.handlers.has(key);
|
|
3547
|
+
}
|
|
3548
|
+
/** Number of currently-registered handlers. Inspection helper. */
|
|
3549
|
+
get handlerCount() {
|
|
3550
|
+
return this.handlers.size;
|
|
3551
|
+
}
|
|
3552
|
+
// ── Lookup ────────────────────────────────────────────────────────────────
|
|
3553
|
+
findById(id) {
|
|
3554
|
+
return this._nodeIndex.get(id);
|
|
3555
|
+
}
|
|
3556
|
+
findAll(predicate) {
|
|
3557
|
+
const results = [];
|
|
3558
|
+
this._walk(this.root, (node) => {
|
|
3559
|
+
if (predicate(node)) results.push(node);
|
|
3560
|
+
});
|
|
3561
|
+
return results;
|
|
3562
|
+
}
|
|
3563
|
+
findByType(type) {
|
|
3564
|
+
return this.findAll((n) => n.type === type);
|
|
3565
|
+
}
|
|
3566
|
+
// ── Traversal ─────────────────────────────────────────────────────────────
|
|
3567
|
+
walk(visitor) {
|
|
3568
|
+
this._walk(this.root, visitor, 0);
|
|
3569
|
+
}
|
|
3570
|
+
_walk(node, visitor, depth = 0) {
|
|
3571
|
+
visitor(node, depth);
|
|
3572
|
+
for (const child of node.children) {
|
|
3573
|
+
this._walk(child, visitor, depth + 1);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
get nodeCount() {
|
|
3577
|
+
return this._nodeIndex.size;
|
|
3578
|
+
}
|
|
3579
|
+
// ── Validation ────────────────────────────────────────────────────────────
|
|
3580
|
+
validate() {
|
|
3581
|
+
const dc = new DiagnosticCollector();
|
|
3582
|
+
this.walk((node) => {
|
|
3583
|
+
for (const event of node.events) {
|
|
3584
|
+
if (!this.handlers.has(event.handlerKey)) {
|
|
3585
|
+
dc.warn(
|
|
3586
|
+
"GRAPH_MISSING_HANDLER",
|
|
3587
|
+
`Node "${node.id}" references handler "${event.handlerKey}" which is not registered`,
|
|
3588
|
+
{ nodeId: node.id }
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
if (node.type === "page" && node.parent?.type !== "application") {
|
|
3593
|
+
dc.error(
|
|
3594
|
+
"GRAPH_PAGE_DEPTH",
|
|
3595
|
+
`Page node "${node.id}" must be a direct child of the application root`,
|
|
3596
|
+
{ nodeId: node.id }
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
return dc;
|
|
3601
|
+
}
|
|
3602
|
+
// ── Serialization ─────────────────────────────────────────────────────────
|
|
3603
|
+
serialize() {
|
|
3604
|
+
return {
|
|
3605
|
+
name: this.name,
|
|
3606
|
+
version: this.version,
|
|
3607
|
+
root: this._serializeNode(this.root)
|
|
3608
|
+
};
|
|
3609
|
+
}
|
|
3610
|
+
_serializeNode(node) {
|
|
3611
|
+
const result = {
|
|
3612
|
+
id: node.id,
|
|
3613
|
+
type: node.type,
|
|
3614
|
+
key: node.key,
|
|
3615
|
+
props: node.props,
|
|
3616
|
+
events: node.events,
|
|
3617
|
+
stateRefs: node.stateRefs,
|
|
3618
|
+
children: node.children.map((c) => this._serializeNode(c))
|
|
3619
|
+
};
|
|
3620
|
+
return result;
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
|
|
3624
|
+
// ../dsl/src/dsl.ts
|
|
3625
|
+
var StreetApp = class {
|
|
3626
|
+
_graph;
|
|
3627
|
+
_builder;
|
|
3628
|
+
constructor(options) {
|
|
3629
|
+
const graphOpts = { name: options.name };
|
|
3630
|
+
if (options.version !== void 0) graphOpts.version = options.version;
|
|
3631
|
+
this._graph = new ApplicationGraph(graphOpts);
|
|
3632
|
+
this._builder = new AppBuilder(this._graph);
|
|
3633
|
+
}
|
|
3634
|
+
page(key, builder) {
|
|
3635
|
+
this._builder.page(key, builder);
|
|
3636
|
+
return this;
|
|
3637
|
+
}
|
|
3638
|
+
/** Compile to ApplicationGraph — validates and returns the graph. */
|
|
3639
|
+
build() {
|
|
3640
|
+
const dc = this._graph.validate();
|
|
3641
|
+
dc.throwIfErrors();
|
|
3642
|
+
return this._graph;
|
|
3643
|
+
}
|
|
3644
|
+
/** Access graph before building (useful for inspection). */
|
|
3645
|
+
get graph() {
|
|
3646
|
+
return this._graph;
|
|
3647
|
+
}
|
|
3648
|
+
};
|
|
3649
|
+
var streetui = {
|
|
3650
|
+
app(options) {
|
|
3651
|
+
return new StreetApp(options);
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
|
|
3655
|
+
// ../testing/src/component.ts
|
|
3656
|
+
var COMPONENT_ATTR = "data-streetui-component";
|
|
3657
|
+
function renderComponent(def, props, children) {
|
|
3658
|
+
const app = streetui.app({ name: `test:${def.name}` });
|
|
3659
|
+
app.page("host", (page) => {
|
|
3660
|
+
page.component("root", def, props, children);
|
|
3661
|
+
});
|
|
3662
|
+
const result = render(app);
|
|
3663
|
+
const component = result.container.querySelector(`[${COMPONENT_ATTR}]`);
|
|
3664
|
+
if (component === null) {
|
|
3665
|
+
throw new Error(`[StreetUI Testing] renderComponent: no component element rendered for "${def.name}"`);
|
|
3666
|
+
}
|
|
3667
|
+
return { ...result, component };
|
|
3668
|
+
}
|
|
3669
|
+
function hydrateComponent(build, options = {}) {
|
|
3670
|
+
return renderServerThenHydrate(() => {
|
|
3671
|
+
const { def, props, children } = build();
|
|
3672
|
+
const app = streetui.app({ name: `test:${def.name}` });
|
|
3673
|
+
app.page("host", (page) => page.component("root", def, props, children));
|
|
3674
|
+
return app;
|
|
3675
|
+
}, options);
|
|
3676
|
+
}
|
|
3677
|
+
function findAllComponents(container, name) {
|
|
3678
|
+
const selector = name === void 0 ? `[${COMPONENT_ATTR}]` : `[${COMPONENT_ATTR}="${name}"]`;
|
|
3679
|
+
return Array.from(container.querySelectorAll(selector));
|
|
3680
|
+
}
|
|
3681
|
+
function findComponent(container, name) {
|
|
3682
|
+
const matches = findAllComponents(container, name);
|
|
3683
|
+
const named = name !== void 0 ? ` named "${name}"` : "";
|
|
3684
|
+
if (matches.length === 0) {
|
|
3685
|
+
throw new Error(`[StreetUI Testing] No component${named} found`);
|
|
3686
|
+
}
|
|
3687
|
+
if (matches.length > 1) {
|
|
3688
|
+
throw new Error(
|
|
3689
|
+
`[StreetUI Testing] Found ${matches.length} components${named} \u2014 pass a name to disambiguate`
|
|
3690
|
+
);
|
|
3691
|
+
}
|
|
3692
|
+
return matches[0];
|
|
3693
|
+
}
|
|
3694
|
+
function getComponentName(el) {
|
|
3695
|
+
return el.getAttribute(COMPONENT_ATTR);
|
|
3696
|
+
}
|
|
3697
|
+
function trigger(el, type, init = {}) {
|
|
3698
|
+
const base = { bubbles: true, cancelable: true, ...init };
|
|
3699
|
+
let event;
|
|
3700
|
+
if (type.startsWith("key")) {
|
|
3701
|
+
event = new KeyboardEvent(type, base);
|
|
3702
|
+
} else if (type.startsWith("mouse") || type === "click" || type === "dblclick") {
|
|
3703
|
+
event = new MouseEvent(type, base);
|
|
3704
|
+
} else if (type === "input" || type === "change") {
|
|
3705
|
+
event = new Event(type, base);
|
|
3706
|
+
} else {
|
|
3707
|
+
event = new Event(type, base);
|
|
3708
|
+
}
|
|
3709
|
+
el.dispatchEvent(event);
|
|
3710
|
+
}
|
|
3711
|
+
|
|
2301
3712
|
// src/version.ts
|
|
2302
|
-
var VERSION = "1.
|
|
3713
|
+
var VERSION = "1.9.0";
|
|
2303
3714
|
export {
|
|
2304
3715
|
VERSION,
|
|
2305
3716
|
analyzeGraph,
|
|
3717
|
+
blur,
|
|
3718
|
+
clickOutside,
|
|
3719
|
+
closeOverlay,
|
|
2306
3720
|
findAllByRole,
|
|
3721
|
+
findAllComponents,
|
|
2307
3722
|
findByRole,
|
|
2308
3723
|
findByText,
|
|
3724
|
+
findComponent,
|
|
2309
3725
|
flushUpdates,
|
|
3726
|
+
focus,
|
|
2310
3727
|
formatInspection,
|
|
3728
|
+
getComponentName,
|
|
3729
|
+
hydrateComponent,
|
|
2311
3730
|
inspectCompilation,
|
|
3731
|
+
openOverlay,
|
|
3732
|
+
pressKey,
|
|
2312
3733
|
render,
|
|
3734
|
+
renderComponent,
|
|
2313
3735
|
renderOnce,
|
|
2314
3736
|
renderServerThenHydrate,
|
|
2315
|
-
|
|
3737
|
+
trigger,
|
|
3738
|
+
waitFor,
|
|
3739
|
+
waitForTransition
|
|
2316
3740
|
};
|
|
2317
3741
|
//# sourceMappingURL=testing.js.map
|