streetui 1.8.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/testing.js CHANGED
@@ -756,18 +756,25 @@ function trapFocus(dom, container) {
756
756
  dom.addEventListener(container, "keydown", onKeydown);
757
757
  return () => dom.removeEventListener(container, "keydown", onKeydown);
758
758
  }
759
+ var containmentStack = [];
759
760
  function containFocus(dom, container) {
760
761
  const body = dom.body();
761
762
  if (body === null) return () => {
762
763
  };
764
+ containmentStack.push(container);
763
765
  const onFocusIn = (event) => {
766
+ if (containmentStack[containmentStack.length - 1] !== container) return;
764
767
  const target = event.target;
765
768
  if (target !== null && !dom.contains(container, target)) {
766
769
  focusFirst(dom, container);
767
770
  }
768
771
  };
769
772
  dom.addEventListener(body, "focusin", onFocusIn);
770
- return () => dom.removeEventListener(body, "focusin", onFocusIn);
773
+ return () => {
774
+ dom.removeEventListener(body, "focusin", onFocusIn);
775
+ const index = containmentStack.lastIndexOf(container);
776
+ if (index !== -1) containmentStack.splice(index, 1);
777
+ };
771
778
  }
772
779
  function onEscape(dom, target, handler) {
773
780
  const onKeydown = (event) => {
@@ -776,6 +783,33 @@ function onEscape(dom, target, handler) {
776
783
  dom.addEventListener(target, "keydown", onKeydown);
777
784
  return () => dom.removeEventListener(target, "keydown", onKeydown);
778
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
+ }
779
813
 
780
814
  // ../renderer/src/render-context.ts
781
815
  function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
@@ -987,7 +1021,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
987
1021
  }
988
1022
 
989
1023
  // ../renderer/src/reconciliation.ts
990
- function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
1024
+ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
991
1025
  const oldByKey = /* @__PURE__ */ new Map();
992
1026
  for (const inst of oldInstances) {
993
1027
  const key = inst.graphNode.key ?? inst.graphNode.id;
@@ -1008,28 +1042,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
1008
1042
  }
1009
1043
  newInstances.push(existing);
1010
1044
  } else {
1011
- const inst = mountFn(newNode, parentDom);
1012
- newInstances.push(inst);
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
+ }
1013
1056
  }
1014
1057
  }
1015
1058
  const removed = [];
1016
1059
  for (const inst of oldInstances) {
1017
1060
  const key = inst.graphNode.key ?? inst.graphNode.id;
1018
- if (!usedKeys.has(key)) {
1019
- removed.push(inst);
1020
- }
1021
- }
1022
- for (const inst of removed) {
1061
+ if (usedKeys.has(key)) continue;
1062
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
1023
1063
  const parent = ctx.dom.parentNode(inst.domNode);
1024
- if (parent !== null) {
1025
- ctx.dom.removeChild(parent, inst.domNode);
1026
- }
1064
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
1027
1065
  inst.dispose();
1066
+ removed.push(inst);
1028
1067
  }
1029
1068
  reorderDom(ctx, parentDom, newInstances);
1030
1069
  return { instances: newInstances, removed };
1031
1070
  }
1032
- function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
1071
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
1033
1072
  const oldByKey = /* @__PURE__ */ new Map();
1034
1073
  for (const inst of oldInstances) {
1035
1074
  oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
@@ -1056,21 +1095,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
1056
1095
  }
1057
1096
  newInstances.push(existing);
1058
1097
  } else {
1059
- const freshNode = entry.build();
1060
- built.push(freshNode);
1061
- const inst = mountFn(freshNode, parentDom);
1062
- newInstances.push(inst);
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
+ }
1063
1115
  }
1064
1116
  }
1065
1117
  const removed = [];
1066
1118
  for (const inst of oldInstances) {
1067
1119
  const key = inst.graphNode.key ?? inst.graphNode.id;
1068
- if (!usedKeys.has(key)) removed.push(inst);
1069
- }
1070
- for (const inst of removed) {
1120
+ if (usedKeys.has(key)) continue;
1121
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
1071
1122
  const parent = ctx.dom.parentNode(inst.domNode);
1072
1123
  if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
1073
1124
  inst.dispose();
1125
+ removed.push(inst);
1074
1126
  }
1075
1127
  reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
1076
1128
  return { instances: newInstances, removed, built };
@@ -1196,6 +1248,176 @@ function patchExistingInstance(ctx, instance, newNode) {
1196
1248
  }
1197
1249
  }
1198
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
+
1199
1421
  // ../renderer/src/mount.ts
1200
1422
  var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
1201
1423
  "text",
@@ -1348,7 +1570,7 @@ function mountNode(ctx, graphNode, parentDom) {
1348
1570
  instance2.addChild(childInstance);
1349
1571
  }
1350
1572
  dom.appendChild(parentDom, el2);
1351
- wireReactiveList(ctx, graphNode, instance2, el2);
1573
+ wireReactiveList(ctx, graphNode, instance2, el2, true);
1352
1574
  return instance2;
1353
1575
  }
1354
1576
  if (graphNode.type === "portal") {
@@ -1457,32 +1679,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1457
1679
  instance.trackCleanup(unsub);
1458
1680
  }
1459
1681
  }
1460
- function wireReactiveList(ctx, graphNode, instance, el) {
1682
+ function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
1461
1683
  const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
1462
1684
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1463
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);
1464
1695
  for (const stateRef of graphNode.stateRefs) {
1465
1696
  if (stateRef.propKey !== "items") continue;
1466
1697
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1467
1698
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1468
1699
  const unsub = sig.subscribe((value) => {
1469
1700
  if (plan !== void 0) {
1470
- reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
1701
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
1471
1702
  } else {
1472
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1703
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
1473
1704
  }
1474
1705
  });
1475
1706
  instance.trackCleanup(unsub);
1476
1707
  }
1477
1708
  }
1478
- function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
1709
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
1479
1710
  const oldInstances = [...listInstance.children];
1480
1711
  const result = reconcileChildrenByPlan(
1481
1712
  ctx,
1482
1713
  listEl,
1483
1714
  oldInstances,
1484
1715
  plan,
1485
- (node, parent) => mountNode(ctx, node, parent)
1716
+ (node, parent) => mountNode(ctx, node, parent),
1717
+ hooks
1486
1718
  );
1487
1719
  listInstance.children.length = 0;
1488
1720
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1497,14 +1729,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
1497
1729
  for (const child of [...listNode.children]) listNode.removeChild(child);
1498
1730
  for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1499
1731
  }
1500
- function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1732
+ function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
1501
1733
  const oldInstances = [...listInstance.children];
1502
1734
  const result = reconcileChildren(
1503
1735
  ctx,
1504
1736
  listEl,
1505
1737
  oldInstances,
1506
1738
  newNodes,
1507
- (node, parent) => mountNode(ctx, node, parent)
1739
+ (node, parent) => mountNode(ctx, node, parent),
1740
+ hooks
1508
1741
  );
1509
1742
  listInstance.children.length = 0;
1510
1743
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -1540,11 +1773,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
1540
1773
  const onOpenChange = (isOpen) => {
1541
1774
  if (isOpen) {
1542
1775
  if (desc.restoreFocus) saved = saveFocus(dom);
1543
- if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1544
1776
  if (desc.modal) {
1545
1777
  active.push(trapFocus(dom, target));
1546
1778
  active.push(containFocus(dom, target));
1547
1779
  }
1780
+ if (desc.menu) {
1781
+ active.push(rovingMenu(dom, target));
1782
+ }
1783
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
1548
1784
  if (desc.closeOnEscape && desc.onClose !== void 0) {
1549
1785
  active.push(onEscape(dom, target, desc.onClose));
1550
1786
  }
@@ -2221,6 +2457,37 @@ async function waitFor(check, options = {}) {
2221
2457
  await new Promise((resolve) => setTimeout(resolve, interval));
2222
2458
  }
2223
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
+ }
2224
2491
  function findByText(container, text) {
2225
2492
  const match = Array.from(container.querySelectorAll("*")).find(
2226
2493
  (el) => el.children.length === 0 && (el.textContent?.includes(text) ?? false)
@@ -2315,6 +2582,42 @@ function renderServerThenHydrate(build, options = {}) {
2315
2582
  };
2316
2583
  }
2317
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
+
2318
2621
  // ../state/src/signal.ts
2319
2622
  var _activeConsumer = null;
2320
2623
  function withConsumer(consumer, fn) {
@@ -2524,6 +2827,18 @@ function applyA11yProps(props, options) {
2524
2827
  if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
2525
2828
  if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
2526
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
+ );
2527
2842
  }
2528
2843
  function containerProps(options) {
2529
2844
  const props = {};
@@ -2584,6 +2899,7 @@ var OVERLAY_KINDS = {
2584
2899
  role: "menu",
2585
2900
  modal: false,
2586
2901
  takesFocus: true,
2902
+ menu: true,
2587
2903
  ariaModal: false,
2588
2904
  defaultCloseOnEscape: true,
2589
2905
  defaultRestoreFocus: true
@@ -2716,6 +3032,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2716
3032
  parent: this._node,
2717
3033
  props: containerProps(options)
2718
3034
  });
3035
+ registerTransition(this._graph, node, options.transition);
2719
3036
  builder(new SectionBuilderImpl(node, this._graph));
2720
3037
  }
2721
3038
  container(key, builder, options = {}) {
@@ -2724,6 +3041,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2724
3041
  parent: this._node,
2725
3042
  props: containerProps(options)
2726
3043
  });
3044
+ registerTransition(this._graph, node, options.transition);
2727
3045
  builder(new ContainerBuilderImpl(node, this._graph));
2728
3046
  }
2729
3047
  list(key, builder, options = {}) {
@@ -2732,6 +3050,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2732
3050
  parent: this._node,
2733
3051
  props: containerProps(options)
2734
3052
  });
3053
+ registerTransition(this._graph, node, options.transition);
2735
3054
  builder(new ListBuilderImpl(node, this._graph));
2736
3055
  }
2737
3056
  listOf(key, items, renderItem, options = {}) {
@@ -2741,6 +3060,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2741
3060
  parent: this._node,
2742
3061
  props: containerProps(options)
2743
3062
  });
3063
+ registerTransition(graph, node, options.transition);
2744
3064
  const signalId = `${node.id}:items`;
2745
3065
  node.stateRefs.push({ signalId, propKey: "items" });
2746
3066
  graph.registerHandler(`__signal__${signalId}`, items);
@@ -2762,6 +3082,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2762
3082
  }
2763
3083
  });
2764
3084
  renderItem(item, index, new ContainerBuilderImpl(itemNode, graph));
3085
+ registerTransition(graph, itemNode, options.itemTransition);
2765
3086
  return itemNode;
2766
3087
  };
2767
3088
  const buildPlan = (raw) => {
@@ -2793,6 +3114,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2793
3114
  parent: this._node,
2794
3115
  props
2795
3116
  });
3117
+ registerTransition(this._graph, node, options.transition);
2796
3118
  if (options.onSubmit !== void 0) {
2797
3119
  const handlerKey = `submit:${node.id}`;
2798
3120
  this._graph.registerHandler(handlerKey, options.onSubmit);
@@ -2800,18 +3122,20 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2800
3122
  }
2801
3123
  builder(new FormBuilderImpl(node, this._graph));
2802
3124
  }
2803
- when(condition, builder, elseBuilder) {
3125
+ when(condition, builder, elseBuilder, options = {}) {
2804
3126
  const graph = this._graph;
2805
3127
  const node = graph.createNode("conditional", {
2806
3128
  parent: this._node,
2807
3129
  props: containerProps({})
2808
3130
  });
3131
+ const branchTransition = options.transition !== void 0 && options.appear === true ? { ...options.transition, appear: true } : options.transition;
2809
3132
  const buildBranch = (build, tag) => {
2810
3133
  const branchKey = `when-${tag}:${node.id}`;
2811
3134
  const branch = graph.createNode("container", {
2812
3135
  key: branchKey,
2813
3136
  props: { key: branchKey }
2814
3137
  });
3138
+ registerTransition(graph, branch, branchTransition);
2815
3139
  build(new ContainerBuilderImpl(branch, graph));
2816
3140
  return branch;
2817
3141
  };
@@ -2906,13 +3230,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
2906
3230
  ...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
2907
3231
  };
2908
3232
  const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
2909
- portalBuilder.when(openSignal, (panelHost) => {
2910
- panelHost.container(`${key}__panel`, builder, panelOptions);
2911
- });
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
+ );
2912
3246
  const descriptor = {
2913
3247
  open: openSignal,
2914
3248
  modal: kind.modal,
2915
3249
  takesFocus: kind.takesFocus,
3250
+ menu: kind.menu ?? false,
2916
3251
  closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
2917
3252
  restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
2918
3253
  ...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
@@ -3009,6 +3344,7 @@ var ListBuilderImpl = class extends ContentBuilderBase {
3009
3344
  parent: this._node,
3010
3345
  props: containerProps(options)
3011
3346
  });
3347
+ registerTransition(this._graph, node, options.transition);
3012
3348
  builder(new ContainerBuilderImpl(node, this._graph));
3013
3349
  }
3014
3350
  };
@@ -3196,6 +3532,7 @@ var ApplicationGraph = class {
3196
3532
  this.handlers.delete(`__listplan__${node.id}`);
3197
3533
  this.handlers.delete(`__overlay__${node.id}`);
3198
3534
  this.handlers.delete(`__component__${node.id}`);
3535
+ this.handlers.delete(`__transition__${node.id}`);
3199
3536
  }
3200
3537
  // ── Handler registry ──────────────────────────────────────────────────────
3201
3538
  registerHandler(key, fn) {
@@ -3373,25 +3710,32 @@ function trigger(el, type, init = {}) {
3373
3710
  }
3374
3711
 
3375
3712
  // src/version.ts
3376
- var VERSION = "1.8.0";
3713
+ var VERSION = "1.9.0";
3377
3714
  export {
3378
3715
  VERSION,
3379
3716
  analyzeGraph,
3717
+ blur,
3718
+ clickOutside,
3719
+ closeOverlay,
3380
3720
  findAllByRole,
3381
3721
  findAllComponents,
3382
3722
  findByRole,
3383
3723
  findByText,
3384
3724
  findComponent,
3385
3725
  flushUpdates,
3726
+ focus,
3386
3727
  formatInspection,
3387
3728
  getComponentName,
3388
3729
  hydrateComponent,
3389
3730
  inspectCompilation,
3731
+ openOverlay,
3732
+ pressKey,
3390
3733
  render,
3391
3734
  renderComponent,
3392
3735
  renderOnce,
3393
3736
  renderServerThenHydrate,
3394
3737
  trigger,
3395
- waitFor
3738
+ waitFor,
3739
+ waitForTransition
3396
3740
  };
3397
3741
  //# sourceMappingURL=testing.js.map