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/index.cjs CHANGED
@@ -60,6 +60,7 @@ __export(src_exports, {
60
60
  StreetFrameworkError: () => StreetFrameworkError,
61
61
  StreetRenderHandle: () => StreetRenderHandle,
62
62
  StreetRendererImpl: () => StreetRendererImpl,
63
+ TransitionController: () => TransitionController,
63
64
  VERSION: () => VERSION,
64
65
  a11yIds: () => a11yIds,
65
66
  applyNodeProps: () => applyNodeProps,
@@ -70,9 +71,11 @@ __export(src_exports, {
70
71
  buttonUpdate: () => buttonUpdate,
71
72
  compile: () => compile,
72
73
  compileGraph: () => compileGraph,
74
+ component: () => component,
73
75
  consoleDiagnosticSink: () => consoleDiagnosticSink,
74
76
  consoleHydrationDiagnosticSink: () => consoleHydrationDiagnosticSink,
75
77
  containFocus: () => containFocus,
78
+ createAnnouncer: () => createAnnouncer,
76
79
  createApplication: () => createApplication,
77
80
  createBrowserHistory: () => createBrowserHistory,
78
81
  createContext: () => createContext,
@@ -107,20 +110,25 @@ __export(src_exports, {
107
110
  generateApplicationId: () => generateApplicationId,
108
111
  generateNodeId: () => generateNodeId,
109
112
  getFocusable: () => getFocusable,
113
+ getResolvedTransition: () => getResolvedTransition,
110
114
  globalEventBus: () => globalEventBus,
111
115
  headingUpdate: () => headingUpdate,
112
116
  hydrateGraph: () => hydrateGraph,
113
117
  inputUpdate: () => inputUpdate,
114
118
  inspectApplication: () => inspectApplication,
119
+ inspectComponents: () => inspectComponents,
115
120
  inspectContext: () => inspectContext,
116
121
  inspectForm: () => inspectForm,
117
122
  inspectGraph: () => inspectGraph,
118
123
  inspectI18n: () => inspectI18n,
124
+ inspectInteractions: () => inspectInteractions,
119
125
  inspectResource: () => inspectResource,
120
126
  inspectRouter: () => inspectRouter,
121
127
  inspectSignal: () => inspectSignal,
122
128
  interpolate: () => interpolate,
123
129
  isBatching: () => isBatching,
130
+ isComponentDefinition: () => isComponentDefinition,
131
+ isTransitionConfig: () => isTransitionConfig,
124
132
  matchPattern: () => matchPattern,
125
133
  matchRoutes: () => matchRoutes,
126
134
  maxLength: () => maxLength,
@@ -149,9 +157,12 @@ __export(src_exports, {
149
157
  required: () => required,
150
158
  resetIdCounter: () => resetIdCounter,
151
159
  resolveTag: () => resolveTag,
160
+ resolveTransition: () => resolveTransition,
152
161
  resource: () => resource,
153
162
  restoreFocus: () => restoreFocus,
154
163
  routerOutlet: () => routerOutlet,
164
+ rovingMenu: () => rovingMenu,
165
+ runElementTransition: () => runElementTransition,
155
166
  runValidators: () => runValidators,
156
167
  saveFocus: () => saveFocus,
157
168
  scheduleImmediate: () => scheduleImmediate,
@@ -170,6 +181,7 @@ __export(src_exports, {
170
181
  transformGraph: () => transformGraph,
171
182
  trapFocus: () => trapFocus,
172
183
  validateGraph: () => validateGraph,
184
+ wireComponentBehavior: () => wireComponentBehavior,
173
185
  wireEvents: () => wireEvents,
174
186
  wireOverlayBehavior: () => wireOverlayBehavior,
175
187
  wireReactiveList: () => wireReactiveList,
@@ -178,7 +190,7 @@ __export(src_exports, {
178
190
  module.exports = __toCommonJS(src_exports);
179
191
 
180
192
  // src/version.ts
181
- var VERSION = "1.7.0";
193
+ var VERSION = "1.9.0";
182
194
 
183
195
  // ../state/src/signal.ts
184
196
  var _activeConsumer = null;
@@ -533,6 +545,9 @@ function a11yIds(base) {
533
545
  description: `${token}-description`,
534
546
  error: `${token}-error`,
535
547
  title: `${token}-title`,
548
+ trigger: `${token}-trigger`,
549
+ controls: `${token}-controls`,
550
+ owns: `${token}-owns`,
536
551
  id: (suffix) => `${token}-${toIdToken(suffix)}`
537
552
  };
538
553
  }
@@ -982,6 +997,8 @@ var ApplicationGraph = class {
982
997
  this.handlers.delete(`__listbuild__${node.id}`);
983
998
  this.handlers.delete(`__listplan__${node.id}`);
984
999
  this.handlers.delete(`__overlay__${node.id}`);
1000
+ this.handlers.delete(`__component__${node.id}`);
1001
+ this.handlers.delete(`__transition__${node.id}`);
985
1002
  }
986
1003
  // ── Handler registry ──────────────────────────────────────────────────────
987
1004
  registerHandler(key, fn) {
@@ -1070,6 +1087,47 @@ var ApplicationGraph = class {
1070
1087
  }
1071
1088
  };
1072
1089
 
1090
+ // ../dsl/src/transition.ts
1091
+ function classes(value) {
1092
+ if (value === void 0) return [];
1093
+ const out = [];
1094
+ for (const token of value.split(/\s+/)) {
1095
+ if (token.length > 0 && !out.includes(token)) out.push(token);
1096
+ }
1097
+ return out;
1098
+ }
1099
+ function merge(a, b) {
1100
+ const out = [...a];
1101
+ for (const token of b) if (!out.includes(token)) out.push(token);
1102
+ return out;
1103
+ }
1104
+ function resolveTransition(config) {
1105
+ const n = config.name;
1106
+ const enterActive = merge(
1107
+ classes(config.enter),
1108
+ classes(config.enterActive ?? (n !== void 0 ? `${n}-enter-active` : void 0))
1109
+ );
1110
+ const leaveActive = merge(
1111
+ classes(config.leave),
1112
+ classes(config.leaveActive ?? (n !== void 0 ? `${n}-leave-active` : void 0))
1113
+ );
1114
+ return {
1115
+ enterActive,
1116
+ enterFrom: classes(config.enterFrom ?? (n !== void 0 ? `${n}-enter-from` : void 0)),
1117
+ enterTo: classes(config.enterTo ?? (n !== void 0 ? `${n}-enter-to` : void 0)),
1118
+ leaveActive,
1119
+ leaveFrom: classes(config.leaveFrom ?? (n !== void 0 ? `${n}-leave-from` : void 0)),
1120
+ leaveTo: classes(config.leaveTo ?? (n !== void 0 ? `${n}-leave-to` : void 0)),
1121
+ appear: config.appear ?? false,
1122
+ duration: config.duration ?? 1e3
1123
+ };
1124
+ }
1125
+ function isTransitionConfig(value) {
1126
+ if (value === null || typeof value !== "object") return false;
1127
+ const o = value;
1128
+ return typeof o["name"] === "string" || typeof o["enter"] === "string" || typeof o["enterActive"] === "string" || typeof o["enterFrom"] === "string" || typeof o["leave"] === "string" || typeof o["leaveActive"] === "string" || typeof o["leaveFrom"] === "string";
1129
+ }
1130
+
1073
1131
  // ../dsl/src/builders.ts
1074
1132
  function isSignal(v) {
1075
1133
  return v !== null && typeof v === "object" && typeof v["get"] === "function" && typeof v["subscribe"] === "function";
@@ -1097,6 +1155,18 @@ function applyA11yProps(props, options) {
1097
1155
  if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
1098
1156
  if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
1099
1157
  if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
1158
+ if (options.ariaOwns !== void 0) props["aria-owns"] = options.ariaOwns;
1159
+ if (options.ariaActiveDescendant !== void 0) props["aria-activedescendant"] = options.ariaActiveDescendant;
1160
+ if (options.ariaHasPopup !== void 0) props["aria-haspopup"] = String(options.ariaHasPopup);
1161
+ if (options.ariaSelected !== void 0) props["aria-selected"] = String(options.ariaSelected);
1162
+ }
1163
+ function registerTransition(graph, node, config) {
1164
+ if (config === void 0) return;
1165
+ const resolved = resolveTransition(config);
1166
+ graph.registerHandler(
1167
+ `__transition__${node.id}`,
1168
+ () => resolved
1169
+ );
1100
1170
  }
1101
1171
  function containerProps(options) {
1102
1172
  const props = {};
@@ -1157,6 +1227,7 @@ var OVERLAY_KINDS = {
1157
1227
  role: "menu",
1158
1228
  modal: false,
1159
1229
  takesFocus: true,
1230
+ menu: true,
1160
1231
  ariaModal: false,
1161
1232
  defaultCloseOnEscape: true,
1162
1233
  defaultRestoreFocus: true
@@ -1289,6 +1360,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1289
1360
  parent: this._node,
1290
1361
  props: containerProps(options)
1291
1362
  });
1363
+ registerTransition(this._graph, node, options.transition);
1292
1364
  builder(new SectionBuilderImpl(node, this._graph));
1293
1365
  }
1294
1366
  container(key, builder, options = {}) {
@@ -1297,6 +1369,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1297
1369
  parent: this._node,
1298
1370
  props: containerProps(options)
1299
1371
  });
1372
+ registerTransition(this._graph, node, options.transition);
1300
1373
  builder(new ContainerBuilderImpl(node, this._graph));
1301
1374
  }
1302
1375
  list(key, builder, options = {}) {
@@ -1305,6 +1378,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1305
1378
  parent: this._node,
1306
1379
  props: containerProps(options)
1307
1380
  });
1381
+ registerTransition(this._graph, node, options.transition);
1308
1382
  builder(new ListBuilderImpl(node, this._graph));
1309
1383
  }
1310
1384
  listOf(key, items, renderItem, options = {}) {
@@ -1314,6 +1388,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1314
1388
  parent: this._node,
1315
1389
  props: containerProps(options)
1316
1390
  });
1391
+ registerTransition(graph, node, options.transition);
1317
1392
  const signalId = `${node.id}:items`;
1318
1393
  node.stateRefs.push({ signalId, propKey: "items" });
1319
1394
  graph.registerHandler(`__signal__${signalId}`, items);
@@ -1335,6 +1410,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1335
1410
  }
1336
1411
  });
1337
1412
  renderItem(item, index, new ContainerBuilderImpl(itemNode, graph));
1413
+ registerTransition(graph, itemNode, options.itemTransition);
1338
1414
  return itemNode;
1339
1415
  };
1340
1416
  const buildPlan = (raw) => {
@@ -1366,6 +1442,7 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1366
1442
  parent: this._node,
1367
1443
  props
1368
1444
  });
1445
+ registerTransition(this._graph, node, options.transition);
1369
1446
  if (options.onSubmit !== void 0) {
1370
1447
  const handlerKey = `submit:${node.id}`;
1371
1448
  this._graph.registerHandler(handlerKey, options.onSubmit);
@@ -1373,18 +1450,20 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1373
1450
  }
1374
1451
  builder(new FormBuilderImpl(node, this._graph));
1375
1452
  }
1376
- when(condition, builder, elseBuilder) {
1453
+ when(condition, builder, elseBuilder, options = {}) {
1377
1454
  const graph = this._graph;
1378
1455
  const node = graph.createNode("conditional", {
1379
1456
  parent: this._node,
1380
1457
  props: containerProps({})
1381
1458
  });
1459
+ const branchTransition = options.transition !== void 0 && options.appear === true ? { ...options.transition, appear: true } : options.transition;
1382
1460
  const buildBranch = (build, tag) => {
1383
1461
  const branchKey = `when-${tag}:${node.id}`;
1384
1462
  const branch = graph.createNode("container", {
1385
1463
  key: branchKey,
1386
1464
  props: { key: branchKey }
1387
1465
  });
1466
+ registerTransition(graph, branch, branchTransition);
1388
1467
  build(new ContainerBuilderImpl(branch, graph));
1389
1468
  return branch;
1390
1469
  };
@@ -1479,13 +1558,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1479
1558
  ...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
1480
1559
  };
1481
1560
  const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
1482
- portalBuilder.when(openSignal, (panelHost) => {
1483
- panelHost.container(`${key}__panel`, builder, panelOptions);
1484
- });
1561
+ portalBuilder.when(
1562
+ openSignal,
1563
+ (panelHost) => {
1564
+ panelHost.container(`${key}__panel`, builder, panelOptions);
1565
+ },
1566
+ void 0,
1567
+ // Overlay open/close rides the panel's `when`; a transition animates the
1568
+ // panel in on open and — via the reconciler's deferred-leave — plays the
1569
+ // leave before the panel is removed (§10). Focus is restored at close-
1570
+ // request time (see wireOverlayBehavior), so it never stays trapped inside
1571
+ // a panel that is animating away.
1572
+ options.transition !== void 0 ? { transition: options.transition } : {}
1573
+ );
1485
1574
  const descriptor = {
1486
1575
  open: openSignal,
1487
1576
  modal: kind.modal,
1488
1577
  takesFocus: kind.takesFocus,
1578
+ menu: kind.menu ?? false,
1489
1579
  closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
1490
1580
  restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
1491
1581
  ...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
@@ -1511,6 +1601,63 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1511
1601
  toast(key, options, builder) {
1512
1602
  this._overlay(OVERLAY_KINDS.toast, key, options, builder);
1513
1603
  }
1604
+ // ── Components ────────────────────────────────────────────────────────────
1605
+ /**
1606
+ * Instantiate a reusable component (§3–§9). Creates a `'component'` node
1607
+ * (rendered as a `<div>` wrapper — preserves the one-node/one-element
1608
+ * positional-hydration invariant), then runs `def.setup(props, ctx)`
1609
+ * synchronously to obtain the render function and fills the component's own
1610
+ * container scope with it — structurally identical to `container`/
1611
+ * `errorBoundary`. Cleanups the setup registers via `ctx.effect`/
1612
+ * `ctx.onCleanup` are collected into a closure and exposed to the renderer
1613
+ * through a `__component__<id>` handler (mirroring `__overlay__`); the mount/
1614
+ * hydrate paths read it and route each into `NodeInstance.trackCleanup`, so
1615
+ * teardown runs (children-first) when the component leaves the graph.
1616
+ *
1617
+ * `setup` runs once per instance here at build time. When this component sits
1618
+ * inside a keyed list / conditional, a rebuild disposes the old instance
1619
+ * (running its cleanups + pruning its `__component__` entry) and re-runs this
1620
+ * method for the new node — so re-invocation is safe and leak-free.
1621
+ */
1622
+ component(key, def, props, children) {
1623
+ const graph = this._graph;
1624
+ const node = graph.createNode("component", {
1625
+ key,
1626
+ parent: this._node,
1627
+ // `data-streetui-component` is a non-underscore prop, so it reaches the
1628
+ // DOM as an attribute and is visible to DevTools (§21) — unlike the
1629
+ // internal `_`-prefixed metadata the renderer hides.
1630
+ props: { key, "data-streetui-component": def.name }
1631
+ });
1632
+ const cleanups = [];
1633
+ const ctx = {
1634
+ key,
1635
+ onCleanup(fn) {
1636
+ cleanups.push(fn);
1637
+ },
1638
+ effect(fn) {
1639
+ cleanups.push(effect(fn));
1640
+ },
1641
+ renderChildren(content) {
1642
+ if (children !== void 0) children(content);
1643
+ }
1644
+ };
1645
+ let render;
1646
+ try {
1647
+ render = def.setup(props, ctx);
1648
+ render(new ContainerBuilderImpl(node, graph));
1649
+ } catch (err) {
1650
+ queueMicrotask(() => {
1651
+ throw err;
1652
+ });
1653
+ }
1654
+ if (cleanups.length > 0) {
1655
+ graph.registerHandler(
1656
+ `__component__${node.id}`,
1657
+ () => cleanups
1658
+ );
1659
+ }
1660
+ }
1514
1661
  };
1515
1662
  var SectionBuilderImpl = class extends ContainerBuilderBase {
1516
1663
  };
@@ -1525,6 +1672,7 @@ var ListBuilderImpl = class extends ContentBuilderBase {
1525
1672
  parent: this._node,
1526
1673
  props: containerProps(options)
1527
1674
  });
1675
+ registerTransition(this._graph, node, options.transition);
1528
1676
  builder(new ContainerBuilderImpl(node, this._graph));
1529
1677
  }
1530
1678
  };
@@ -1544,6 +1692,18 @@ var AppBuilder = class {
1544
1692
  }
1545
1693
  };
1546
1694
 
1695
+ // ../dsl/src/component.ts
1696
+ function component(setup, options = {}) {
1697
+ return {
1698
+ __streetui_component: true,
1699
+ name: options.name ?? setup.name ?? "Component",
1700
+ setup
1701
+ };
1702
+ }
1703
+ function isComponentDefinition(value) {
1704
+ return value !== null && typeof value === "object" && value.__streetui_component === true;
1705
+ }
1706
+
1547
1707
  // ../dsl/src/dsl.ts
1548
1708
  var StreetApp = class {
1549
1709
  _graph;
@@ -2607,18 +2767,25 @@ function trapFocus(dom, container) {
2607
2767
  dom.addEventListener(container, "keydown", onKeydown);
2608
2768
  return () => dom.removeEventListener(container, "keydown", onKeydown);
2609
2769
  }
2770
+ var containmentStack = [];
2610
2771
  function containFocus(dom, container) {
2611
2772
  const body = dom.body();
2612
2773
  if (body === null) return () => {
2613
2774
  };
2775
+ containmentStack.push(container);
2614
2776
  const onFocusIn = (event) => {
2777
+ if (containmentStack[containmentStack.length - 1] !== container) return;
2615
2778
  const target = event.target;
2616
2779
  if (target !== null && !dom.contains(container, target)) {
2617
2780
  focusFirst(dom, container);
2618
2781
  }
2619
2782
  };
2620
2783
  dom.addEventListener(body, "focusin", onFocusIn);
2621
- return () => dom.removeEventListener(body, "focusin", onFocusIn);
2784
+ return () => {
2785
+ dom.removeEventListener(body, "focusin", onFocusIn);
2786
+ const index = containmentStack.lastIndexOf(container);
2787
+ if (index !== -1) containmentStack.splice(index, 1);
2788
+ };
2622
2789
  }
2623
2790
  function onEscape(dom, target, handler) {
2624
2791
  const onKeydown = (event) => {
@@ -2627,6 +2794,84 @@ function onEscape(dom, target, handler) {
2627
2794
  dom.addEventListener(target, "keydown", onKeydown);
2628
2795
  return () => dom.removeEventListener(target, "keydown", onKeydown);
2629
2796
  }
2797
+ function rovingMenu(dom, container, selector = FOCUSABLE_SELECTOR) {
2798
+ const onKeydown = (event) => {
2799
+ const key = event.key;
2800
+ const isActivate = key === "Enter" || key === " " || key === "Spacebar";
2801
+ const isMove = key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End";
2802
+ if (!isActivate && !isMove) return;
2803
+ const items = getFocusable(dom, container, selector);
2804
+ if (items.length === 0) return;
2805
+ const active = dom.activeElement();
2806
+ const index = active === null ? -1 : items.indexOf(active);
2807
+ if (isActivate) {
2808
+ if (index < 0) return;
2809
+ event.preventDefault();
2810
+ items[index].click?.();
2811
+ return;
2812
+ }
2813
+ event.preventDefault();
2814
+ let next;
2815
+ if (key === "Home") next = 0;
2816
+ else if (key === "End") next = items.length - 1;
2817
+ else if (key === "ArrowDown") next = index < 0 ? 0 : (index + 1) % items.length;
2818
+ else next = index <= 0 ? items.length - 1 : index - 1;
2819
+ dom.focus(items[next]);
2820
+ };
2821
+ dom.addEventListener(container, "keydown", onKeydown);
2822
+ return () => dom.removeEventListener(container, "keydown", onKeydown);
2823
+ }
2824
+
2825
+ // ../dom/src/live-region.ts
2826
+ var VISUALLY_HIDDEN = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;";
2827
+ function makeRegion(dom, politeness) {
2828
+ const el = dom.createElement("div");
2829
+ dom.setAttribute(el, "aria-live", politeness);
2830
+ dom.setAttribute(el, "aria-atomic", "true");
2831
+ dom.setAttribute(el, "role", politeness === "assertive" ? "alert" : "status");
2832
+ dom.setAttribute(el, "data-streetui-live", politeness);
2833
+ dom.setAttribute(el, "style", VISUALLY_HIDDEN);
2834
+ return el;
2835
+ }
2836
+ function createAnnouncer(dom) {
2837
+ const body = dom.body();
2838
+ if (body === null) {
2839
+ return { announce() {
2840
+ }, clear() {
2841
+ }, destroy() {
2842
+ } };
2843
+ }
2844
+ const polite = makeRegion(dom, "polite");
2845
+ const assertive = makeRegion(dom, "assertive");
2846
+ dom.appendChild(body, polite);
2847
+ dom.appendChild(body, assertive);
2848
+ let destroyed = false;
2849
+ const write = (region, message) => {
2850
+ dom.setTextContent(region, "");
2851
+ void Promise.resolve().then(() => {
2852
+ if (!destroyed) dom.setTextContent(region, message);
2853
+ });
2854
+ };
2855
+ return {
2856
+ announce(message, options) {
2857
+ if (destroyed) return;
2858
+ write(options?.assertive === true ? assertive : polite, message);
2859
+ },
2860
+ clear() {
2861
+ if (destroyed) return;
2862
+ dom.setTextContent(polite, "");
2863
+ dom.setTextContent(assertive, "");
2864
+ },
2865
+ destroy() {
2866
+ if (destroyed) return;
2867
+ destroyed = true;
2868
+ for (const region of [polite, assertive]) {
2869
+ const parent = dom.parentNode(region);
2870
+ if (parent !== null) dom.removeChild(parent, region);
2871
+ }
2872
+ }
2873
+ };
2874
+ }
2630
2875
 
2631
2876
  // ../renderer/src/render-context.ts
2632
2877
  function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
@@ -2838,7 +3083,7 @@ function patchNode(ctx, graphNode, propKey, newValue) {
2838
3083
  }
2839
3084
 
2840
3085
  // ../renderer/src/reconciliation.ts
2841
- function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
3086
+ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn, hooks) {
2842
3087
  const oldByKey = /* @__PURE__ */ new Map();
2843
3088
  for (const inst of oldInstances) {
2844
3089
  const key = inst.graphNode.key ?? inst.graphNode.id;
@@ -2859,28 +3104,33 @@ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
2859
3104
  }
2860
3105
  newInstances.push(existing);
2861
3106
  } else {
2862
- const inst = mountFn(newNode, parentDom);
2863
- newInstances.push(inst);
3107
+ const reclaimed = hooks?.takeLeaving(key);
3108
+ if (reclaimed !== void 0) {
3109
+ patchExistingInstance(ctx, reclaimed, newNode);
3110
+ reconcileItemChildren(ctx, reclaimed, newNode, mountFn);
3111
+ hooks?.onEnter(reclaimed);
3112
+ newInstances.push(reclaimed);
3113
+ } else {
3114
+ const inst = mountFn(newNode, parentDom);
3115
+ hooks?.onEnter(inst);
3116
+ newInstances.push(inst);
3117
+ }
2864
3118
  }
2865
3119
  }
2866
3120
  const removed = [];
2867
3121
  for (const inst of oldInstances) {
2868
3122
  const key = inst.graphNode.key ?? inst.graphNode.id;
2869
- if (!usedKeys.has(key)) {
2870
- removed.push(inst);
2871
- }
2872
- }
2873
- for (const inst of removed) {
3123
+ if (usedKeys.has(key)) continue;
3124
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
2874
3125
  const parent = ctx.dom.parentNode(inst.domNode);
2875
- if (parent !== null) {
2876
- ctx.dom.removeChild(parent, inst.domNode);
2877
- }
3126
+ if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
2878
3127
  inst.dispose();
3128
+ removed.push(inst);
2879
3129
  }
2880
3130
  reorderDom(ctx, parentDom, newInstances);
2881
3131
  return { instances: newInstances, removed };
2882
3132
  }
2883
- function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
3133
+ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn, hooks) {
2884
3134
  const oldByKey = /* @__PURE__ */ new Map();
2885
3135
  for (const inst of oldInstances) {
2886
3136
  oldByKey.set(inst.graphNode.key ?? inst.graphNode.id, inst);
@@ -2907,21 +3157,34 @@ function reconcileChildrenByPlan(ctx, parentDom, oldInstances, plan, mountFn) {
2907
3157
  }
2908
3158
  newInstances.push(existing);
2909
3159
  } else {
2910
- const freshNode = entry.build();
2911
- built.push(freshNode);
2912
- const inst = mountFn(freshNode, parentDom);
2913
- newInstances.push(inst);
3160
+ const reclaimed = hooks?.takeLeaving(entry.key);
3161
+ if (reclaimed !== void 0) {
3162
+ const freshNode = entry.build();
3163
+ built.push(freshNode);
3164
+ patchExistingInstance(ctx, reclaimed, freshNode);
3165
+ reconcileItemChildren(ctx, reclaimed, freshNode, mountFn);
3166
+ reclaimed.graphNode.setProp("_sig", freshNode.getProp("_sig"));
3167
+ reclaimed.graphNode.setProp("_item", entry.item);
3168
+ hooks?.onEnter(reclaimed);
3169
+ newInstances.push(reclaimed);
3170
+ } else {
3171
+ const freshNode = entry.build();
3172
+ built.push(freshNode);
3173
+ const inst = mountFn(freshNode, parentDom);
3174
+ hooks?.onEnter(inst);
3175
+ newInstances.push(inst);
3176
+ }
2914
3177
  }
2915
3178
  }
2916
3179
  const removed = [];
2917
3180
  for (const inst of oldInstances) {
2918
3181
  const key = inst.graphNode.key ?? inst.graphNode.id;
2919
- if (!usedKeys.has(key)) removed.push(inst);
2920
- }
2921
- for (const inst of removed) {
3182
+ if (usedKeys.has(key)) continue;
3183
+ if (hooks !== void 0 && hooks.beginLeave(inst)) continue;
2922
3184
  const parent = ctx.dom.parentNode(inst.domNode);
2923
3185
  if (parent !== null) ctx.dom.removeChild(parent, inst.domNode);
2924
3186
  inst.dispose();
3187
+ removed.push(inst);
2925
3188
  }
2926
3189
  reorderDomMinimal(ctx, parentDom, oldInstances, newInstances);
2927
3190
  return { instances: newInstances, removed, built };
@@ -3047,6 +3310,187 @@ function patchExistingInstance(ctx, instance, newNode) {
3047
3310
  }
3048
3311
  }
3049
3312
 
3313
+ // ../renderer/src/transition.ts
3314
+ function getResolvedTransition(graph, nodeId) {
3315
+ const fn = graph.getHandler(`__transition__${nodeId}`);
3316
+ return fn === void 0 ? void 0 : fn();
3317
+ }
3318
+ function host() {
3319
+ return globalThis;
3320
+ }
3321
+ function nextFrame(cb) {
3322
+ const h = host();
3323
+ const raf = h.requestAnimationFrame;
3324
+ if (typeof raf === "function") {
3325
+ raf(() => raf(cb));
3326
+ } else {
3327
+ h.setTimeout(cb, 0);
3328
+ }
3329
+ }
3330
+ function splitClass(value) {
3331
+ if (value === null) return [];
3332
+ const out = [];
3333
+ for (const t of value.split(/\s+/)) if (t.length > 0) out.push(t);
3334
+ return out;
3335
+ }
3336
+ function addClasses(dom, el, classes2) {
3337
+ if (classes2.length === 0) return;
3338
+ const current = splitClass(dom.getAttribute(el, "class"));
3339
+ let changed = false;
3340
+ for (const c of classes2) {
3341
+ if (!current.includes(c)) {
3342
+ current.push(c);
3343
+ changed = true;
3344
+ }
3345
+ }
3346
+ if (changed) dom.setAttribute(el, "class", current.join(" "));
3347
+ }
3348
+ function removeClasses(dom, el, classes2) {
3349
+ if (classes2.length === 0) return;
3350
+ const current = splitClass(dom.getAttribute(el, "class"));
3351
+ const next = current.filter((c) => !classes2.includes(c));
3352
+ if (next.length !== current.length) {
3353
+ if (next.length === 0) dom.removeAttribute(el, "class");
3354
+ else dom.setAttribute(el, "class", next.join(" "));
3355
+ }
3356
+ }
3357
+ function startRun(dom, el, active, from, to, duration, onDone) {
3358
+ const h = host();
3359
+ let finished = false;
3360
+ let timer = null;
3361
+ const onEvent = (e) => {
3362
+ if (e.target !== el) return;
3363
+ finish();
3364
+ };
3365
+ const detach = () => {
3366
+ dom.removeEventListener(el, "transitionend", onEvent);
3367
+ dom.removeEventListener(el, "animationend", onEvent);
3368
+ if (timer !== null) {
3369
+ h.clearTimeout(timer);
3370
+ timer = null;
3371
+ }
3372
+ };
3373
+ const finish = () => {
3374
+ if (finished) return;
3375
+ finished = true;
3376
+ detach();
3377
+ removeClasses(dom, el, active);
3378
+ removeClasses(dom, el, to);
3379
+ removeClasses(dom, el, from);
3380
+ onDone();
3381
+ };
3382
+ addClasses(dom, el, from);
3383
+ addClasses(dom, el, active);
3384
+ dom.addEventListener(el, "transitionend", onEvent);
3385
+ dom.addEventListener(el, "animationend", onEvent);
3386
+ timer = h.setTimeout(finish, duration);
3387
+ nextFrame(() => {
3388
+ if (finished) return;
3389
+ removeClasses(dom, el, from);
3390
+ addClasses(dom, el, to);
3391
+ });
3392
+ return {
3393
+ cancel: () => {
3394
+ if (finished) return;
3395
+ finished = true;
3396
+ detach();
3397
+ removeClasses(dom, el, active);
3398
+ removeClasses(dom, el, to);
3399
+ removeClasses(dom, el, from);
3400
+ }
3401
+ };
3402
+ }
3403
+ function runElementTransition(dom, el, rt, phase, onDone) {
3404
+ if (dom.body() === null || !dom.isElement(el)) {
3405
+ onDone();
3406
+ return { cancel: () => {
3407
+ } };
3408
+ }
3409
+ const active = phase === "enter" ? rt.enterActive : rt.leaveActive;
3410
+ const from = phase === "enter" ? rt.enterFrom : rt.leaveFrom;
3411
+ const to = phase === "enter" ? rt.enterTo : rt.leaveTo;
3412
+ return startRun(dom, el, active, from, to, rt.duration, onDone);
3413
+ }
3414
+ var TransitionController = class {
3415
+ constructor(dom, graph, finalize) {
3416
+ this.dom = dom;
3417
+ this.graph = graph;
3418
+ this.finalize = finalize;
3419
+ }
3420
+ leaving = /* @__PURE__ */ new Map();
3421
+ /** True only in a real DOM environment (browser). */
3422
+ get browser() {
3423
+ return this.dom.body() !== null;
3424
+ }
3425
+ keyOf(inst) {
3426
+ return inst.graphNode.key ?? inst.graphNode.id;
3427
+ }
3428
+ resolved(node) {
3429
+ return getResolvedTransition(this.graph, node.id);
3430
+ }
3431
+ /** Run the enter animation for `inst` if it carries a transition (browser only). */
3432
+ enter(inst) {
3433
+ if (!this.browser) return;
3434
+ const rt = this.resolved(inst.graphNode);
3435
+ if (rt === void 0) return;
3436
+ const el = inst.domNode;
3437
+ if (!this.dom.isElement(el)) return;
3438
+ startRun(this.dom, el, rt.enterActive, rt.enterFrom, rt.enterTo, rt.duration, () => {
3439
+ });
3440
+ }
3441
+ /**
3442
+ * Play `appear` for any initial child that opted into it (fresh browser mount
3443
+ * only — hydration must never animate appear, §22, and this is called only on
3444
+ * the mount path).
3445
+ */
3446
+ appear(children) {
3447
+ if (!this.browser) return;
3448
+ for (const child of children) {
3449
+ const rt = this.resolved(child.graphNode);
3450
+ if (rt !== void 0 && rt.appear) this.enter(child);
3451
+ }
3452
+ }
3453
+ hooks() {
3454
+ return {
3455
+ takeLeaving: (key) => {
3456
+ const entry = this.leaving.get(key);
3457
+ if (entry === void 0) return void 0;
3458
+ entry.run.cancel();
3459
+ this.leaving.delete(key);
3460
+ return entry.inst;
3461
+ },
3462
+ beginLeave: (inst) => {
3463
+ if (!this.browser) return false;
3464
+ const rt = this.resolved(inst.graphNode);
3465
+ if (rt === void 0) return false;
3466
+ const el = inst.domNode;
3467
+ if (!this.dom.isElement(el)) return false;
3468
+ const key = this.keyOf(inst);
3469
+ const prior = this.leaving.get(key);
3470
+ if (prior !== void 0) prior.run.cancel();
3471
+ const run = startRun(
3472
+ this.dom,
3473
+ el,
3474
+ rt.leaveActive,
3475
+ rt.leaveFrom,
3476
+ rt.leaveTo,
3477
+ rt.duration,
3478
+ () => {
3479
+ const current = this.leaving.get(key);
3480
+ if (current !== void 0 && current.run === run) {
3481
+ this.leaving.delete(key);
3482
+ this.finalize(inst);
3483
+ }
3484
+ }
3485
+ );
3486
+ this.leaving.set(key, { inst, run });
3487
+ return true;
3488
+ },
3489
+ onEnter: (inst) => this.enter(inst)
3490
+ };
3491
+ }
3492
+ };
3493
+
3050
3494
  // ../renderer/src/mount.ts
3051
3495
  var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
3052
3496
  "text",
@@ -3199,7 +3643,7 @@ function mountNode(ctx, graphNode, parentDom) {
3199
3643
  instance2.addChild(childInstance);
3200
3644
  }
3201
3645
  dom.appendChild(parentDom, el2);
3202
- wireReactiveList(ctx, graphNode, instance2, el2);
3646
+ wireReactiveList(ctx, graphNode, instance2, el2, true);
3203
3647
  return instance2;
3204
3648
  }
3205
3649
  if (graphNode.type === "portal") {
@@ -3243,6 +3687,7 @@ function mountNode(ctx, graphNode, parentDom) {
3243
3687
  instance.addChild(childInstance);
3244
3688
  }
3245
3689
  dom.appendChild(parentDom, el);
3690
+ if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
3246
3691
  return instance;
3247
3692
  }
3248
3693
  function textUpdate(dom, el, textNode) {
@@ -3307,32 +3752,42 @@ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
3307
3752
  instance.trackCleanup(unsub);
3308
3753
  }
3309
3754
  }
3310
- function wireReactiveList(ctx, graphNode, instance, el) {
3755
+ function wireReactiveList(ctx, graphNode, instance, el, runAppear = false) {
3311
3756
  const plan = ctx.graph.getHandler(`__listplan__${graphNode.id}`);
3312
3757
  const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
3313
3758
  if (plan === void 0 && build === void 0) return;
3759
+ const controller = new TransitionController(ctx.dom, ctx.graph, (leaving) => {
3760
+ const parent = ctx.dom.parentNode(leaving.domNode);
3761
+ if (parent !== null) ctx.dom.removeChild(parent, leaving.domNode);
3762
+ leaving.dispose();
3763
+ forgetInstance(ctx, leaving);
3764
+ ctx.graph.detachNode(leaving.graphNode);
3765
+ });
3766
+ const hooks = controller.hooks();
3767
+ if (runAppear) controller.appear(instance.children);
3314
3768
  for (const stateRef of graphNode.stateRefs) {
3315
3769
  if (stateRef.propKey !== "items") continue;
3316
3770
  const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
3317
3771
  if (sig === void 0 || typeof sig.subscribe !== "function") continue;
3318
3772
  const unsub = sig.subscribe((value) => {
3319
3773
  if (plan !== void 0) {
3320
- reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value));
3774
+ reconcileReactiveListByPlan(ctx, graphNode, instance, el, plan(value), hooks);
3321
3775
  } else {
3322
- reconcileReactiveList(ctx, graphNode, instance, el, build(value));
3776
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value), hooks);
3323
3777
  }
3324
3778
  });
3325
3779
  instance.trackCleanup(unsub);
3326
3780
  }
3327
3781
  }
3328
- function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan) {
3782
+ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan, hooks) {
3329
3783
  const oldInstances = [...listInstance.children];
3330
3784
  const result = reconcileChildrenByPlan(
3331
3785
  ctx,
3332
3786
  listEl,
3333
3787
  oldInstances,
3334
3788
  plan,
3335
- (node, parent) => mountNode(ctx, node, parent)
3789
+ (node, parent) => mountNode(ctx, node, parent),
3790
+ hooks
3336
3791
  );
3337
3792
  listInstance.children.length = 0;
3338
3793
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -3347,14 +3802,15 @@ function reconcileReactiveListByPlan(ctx, listNode, listInstance, listEl, plan)
3347
3802
  for (const child of [...listNode.children]) listNode.removeChild(child);
3348
3803
  for (const inst of result.instances) listNode.appendChild(inst.graphNode);
3349
3804
  }
3350
- function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
3805
+ function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes, hooks) {
3351
3806
  const oldInstances = [...listInstance.children];
3352
3807
  const result = reconcileChildren(
3353
3808
  ctx,
3354
3809
  listEl,
3355
3810
  oldInstances,
3356
3811
  newNodes,
3357
- (node, parent) => mountNode(ctx, node, parent)
3812
+ (node, parent) => mountNode(ctx, node, parent),
3813
+ hooks
3358
3814
  );
3359
3815
  listInstance.children.length = 0;
3360
3816
  for (const inst of result.instances) listInstance.children.push(inst);
@@ -3390,11 +3846,14 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
3390
3846
  const onOpenChange = (isOpen) => {
3391
3847
  if (isOpen) {
3392
3848
  if (desc.restoreFocus) saved = saveFocus(dom);
3393
- if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
3394
3849
  if (desc.modal) {
3395
3850
  active.push(trapFocus(dom, target));
3396
3851
  active.push(containFocus(dom, target));
3397
3852
  }
3853
+ if (desc.menu) {
3854
+ active.push(rovingMenu(dom, target));
3855
+ }
3856
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
3398
3857
  if (desc.closeOnEscape && desc.onClose !== void 0) {
3399
3858
  active.push(onEscape(dom, target, desc.onClose));
3400
3859
  }
@@ -3411,6 +3870,11 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
3411
3870
  instance.trackCleanup(teardown);
3412
3871
  if (openSig.peek() === true) onOpenChange(true);
3413
3872
  }
3873
+ function wireComponentBehavior(ctx, graphNode, instance) {
3874
+ const fn = ctx.graph.getHandler(`__component__${graphNode.id}`);
3875
+ if (fn === void 0) return;
3876
+ for (const cleanup of fn()) instance.trackCleanup(cleanup);
3877
+ }
3414
3878
 
3415
3879
  // ../renderer/src/hydration-diagnostics.ts
3416
3880
  function formatHydrationDiagnostic(d) {
@@ -3542,6 +4006,7 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3542
4006
  return instance;
3543
4007
  }
3544
4008
  hydrateChildren(ctx, graphNode, instance, domNode, path);
4009
+ if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
3545
4010
  return instance;
3546
4011
  }
3547
4012
  }
@@ -3776,7 +4241,8 @@ function analyzeGraph(graph) {
3776
4241
  const isList = node.type === "reactive-list";
3777
4242
  const isConditional = node.type === "conditional";
3778
4243
  const isPortal = node.type === "portal";
3779
- const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;
4244
+ const isComponent = node.type === "component";
4245
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
3780
4246
  const isStaticSubtree = isStatic && allChildrenStatic;
3781
4247
  nodes.set(node.id, {
3782
4248
  isStatic,
@@ -4122,16 +4588,38 @@ function mountRouter(router, options) {
4122
4588
  } else {
4123
4589
  outlet = container;
4124
4590
  }
4591
+ const routeTransition = options.transition !== void 0 ? resolveTransition(options.transition) : void 0;
4592
+ const txDom = routeTransition !== void 0 ? new BrowserDOMAdapter() : void 0;
4125
4593
  let active = null;
4594
+ const pendingLeaves = /* @__PURE__ */ new Map();
4126
4595
  let firstRender = hydrateMode;
4596
+ const teardownRoute = (route) => {
4597
+ route.registry.run();
4598
+ route.mounted.unmount();
4599
+ if (route.host !== null && txDom !== void 0) {
4600
+ const parent = txDom.parentNode(route.host);
4601
+ if (parent !== null) txDom.removeChild(parent, route.host);
4602
+ }
4603
+ };
4127
4604
  const disposeActive = () => {
4128
4605
  if (active === null) return;
4129
- active.registry.run();
4130
- active.mounted.unmount();
4606
+ teardownRoute(active);
4131
4607
  active = null;
4132
4608
  };
4133
- const renderRoute = (match) => {
4134
- disposeActive();
4609
+ const wrapOutletChildren = (dom) => {
4610
+ const host2 = dom.createElement("div");
4611
+ dom.setAttribute(host2, "data-streetui-route", "");
4612
+ const moved = [];
4613
+ let child = dom.firstChild(outlet);
4614
+ while (child !== null) {
4615
+ moved.push(child);
4616
+ child = dom.nextSibling(child);
4617
+ }
4618
+ for (const n of moved) dom.appendChild(host2, n);
4619
+ dom.appendChild(outlet, host2);
4620
+ return host2;
4621
+ };
4622
+ const buildRoute = (match, target, hydrate) => {
4135
4623
  const registry = new CleanupRegistry();
4136
4624
  const ctx = {
4137
4625
  path: match.path,
@@ -4144,9 +4632,53 @@ function mountRouter(router, options) {
4144
4632
  routeApp.page("route", (page) => match.route.builder(page, ctx));
4145
4633
  const runtime = createRuntime({ renderer });
4146
4634
  const routeCompiled = compile(routeApp);
4147
- const mounted = firstRender ? runtime.hydrate(routeCompiled, outlet) : runtime.mount(routeCompiled, outlet);
4635
+ const mounted = hydrate ? runtime.hydrate(routeCompiled, target) : runtime.mount(routeCompiled, target);
4636
+ return { registry, mounted, host: null, enterRun: null };
4637
+ };
4638
+ const renderRoute = (match) => {
4639
+ if (routeTransition === void 0 || txDom === void 0) {
4640
+ disposeActive();
4641
+ active = buildRoute(match, outlet, firstRender);
4642
+ firstRender = false;
4643
+ return;
4644
+ }
4645
+ if (active === null) {
4646
+ if (firstRender) {
4647
+ active = buildRoute(match, outlet, true);
4648
+ active.host = wrapOutletChildren(txDom);
4649
+ } else {
4650
+ const host2 = txDom.createElement("div");
4651
+ txDom.setAttribute(host2, "data-streetui-route", "");
4652
+ txDom.appendChild(outlet, host2);
4653
+ active = buildRoute(match, host2, false);
4654
+ active.host = host2;
4655
+ }
4656
+ firstRender = false;
4657
+ return;
4658
+ }
4148
4659
  firstRender = false;
4149
- active = { registry, mounted };
4660
+ const leaving = active;
4661
+ leaving.enterRun?.cancel();
4662
+ leaving.enterRun = null;
4663
+ const leaveHost = leaving.host;
4664
+ const enterHost = txDom.createElement("div");
4665
+ txDom.setAttribute(enterHost, "data-streetui-route", "");
4666
+ txDom.appendChild(outlet, enterHost);
4667
+ const next = buildRoute(match, enterHost, false);
4668
+ next.host = enterHost;
4669
+ next.enterRun = runElementTransition(txDom, enterHost, routeTransition, "enter", () => {
4670
+ next.enterRun = null;
4671
+ });
4672
+ active = next;
4673
+ if (leaveHost !== null) {
4674
+ const run = runElementTransition(txDom, leaveHost, routeTransition, "leave", () => {
4675
+ pendingLeaves.delete(leaving);
4676
+ teardownRoute(leaving);
4677
+ });
4678
+ pendingLeaves.set(leaving, run);
4679
+ } else {
4680
+ teardownRoute(leaving);
4681
+ }
4150
4682
  };
4151
4683
  renderRoute(router.currentRoute.peek());
4152
4684
  const stopRouteSub = router.currentRoute.subscribe((match) => renderRoute(match));
@@ -4174,6 +4706,11 @@ function mountRouter(router, options) {
4174
4706
  unmount() {
4175
4707
  if (interceptLinks) container.removeEventListener("click", onClick);
4176
4708
  stopRouteSub();
4709
+ for (const [route, run] of pendingLeaves) {
4710
+ run.cancel();
4711
+ teardownRoute(route);
4712
+ }
4713
+ pendingLeaves.clear();
4177
4714
  disposeActive();
4178
4715
  shellMounted?.unmount();
4179
4716
  router.destroy();
@@ -4483,6 +5020,55 @@ function nodeTypeStats(graph) {
4483
5020
  });
4484
5021
  return counts;
4485
5022
  }
5023
+ function inspectComponents(graph) {
5024
+ const out = [];
5025
+ graph.walk((node, depth) => {
5026
+ if (node.type !== "component") return;
5027
+ const name = node.props["data-streetui-component"];
5028
+ out.push({
5029
+ id: node.id,
5030
+ key: node.key,
5031
+ name: typeof name === "string" ? name : "Component",
5032
+ depth,
5033
+ childCount: node.children.length
5034
+ });
5035
+ });
5036
+ return out;
5037
+ }
5038
+ function inspectInteractions(graph) {
5039
+ const overlays = [];
5040
+ const transitions = [];
5041
+ graph.walk((node, depth) => {
5042
+ const overlayFn = graph.getHandler(`__overlay__${node.id}`);
5043
+ if (overlayFn !== void 0) {
5044
+ const d = overlayFn();
5045
+ overlays.push({
5046
+ id: node.id,
5047
+ key: node.key,
5048
+ open: d.open.peek(),
5049
+ modal: d.modal,
5050
+ takesFocus: d.takesFocus,
5051
+ menu: d.menu,
5052
+ closeOnEscape: d.closeOnEscape,
5053
+ restoreFocus: d.restoreFocus,
5054
+ depth
5055
+ });
5056
+ }
5057
+ const transitionFn = graph.getHandler(`__transition__${node.id}`);
5058
+ if (transitionFn !== void 0) {
5059
+ const t = transitionFn();
5060
+ transitions.push({
5061
+ id: node.id,
5062
+ key: node.key,
5063
+ nodeType: node.type,
5064
+ duration: t.duration,
5065
+ appear: t.appear,
5066
+ depth
5067
+ });
5068
+ }
5069
+ });
5070
+ return { overlays, transitions };
5071
+ }
4486
5072
 
4487
5073
  // ../devtools/src/application.ts
4488
5074
  function collectPerf(node, distinctSignals, acc) {
@@ -4857,6 +5443,7 @@ function defineConfig(config) {
4857
5443
  StreetFrameworkError,
4858
5444
  StreetRenderHandle,
4859
5445
  StreetRendererImpl,
5446
+ TransitionController,
4860
5447
  VERSION,
4861
5448
  a11yIds,
4862
5449
  applyNodeProps,
@@ -4867,9 +5454,11 @@ function defineConfig(config) {
4867
5454
  buttonUpdate,
4868
5455
  compile,
4869
5456
  compileGraph,
5457
+ component,
4870
5458
  consoleDiagnosticSink,
4871
5459
  consoleHydrationDiagnosticSink,
4872
5460
  containFocus,
5461
+ createAnnouncer,
4873
5462
  createApplication,
4874
5463
  createBrowserHistory,
4875
5464
  createContext,
@@ -4904,20 +5493,25 @@ function defineConfig(config) {
4904
5493
  generateApplicationId,
4905
5494
  generateNodeId,
4906
5495
  getFocusable,
5496
+ getResolvedTransition,
4907
5497
  globalEventBus,
4908
5498
  headingUpdate,
4909
5499
  hydrateGraph,
4910
5500
  inputUpdate,
4911
5501
  inspectApplication,
5502
+ inspectComponents,
4912
5503
  inspectContext,
4913
5504
  inspectForm,
4914
5505
  inspectGraph,
4915
5506
  inspectI18n,
5507
+ inspectInteractions,
4916
5508
  inspectResource,
4917
5509
  inspectRouter,
4918
5510
  inspectSignal,
4919
5511
  interpolate,
4920
5512
  isBatching,
5513
+ isComponentDefinition,
5514
+ isTransitionConfig,
4921
5515
  matchPattern,
4922
5516
  matchRoutes,
4923
5517
  maxLength,
@@ -4946,9 +5540,12 @@ function defineConfig(config) {
4946
5540
  required,
4947
5541
  resetIdCounter,
4948
5542
  resolveTag,
5543
+ resolveTransition,
4949
5544
  resource,
4950
5545
  restoreFocus,
4951
5546
  routerOutlet,
5547
+ rovingMenu,
5548
+ runElementTransition,
4952
5549
  runValidators,
4953
5550
  saveFocus,
4954
5551
  scheduleImmediate,
@@ -4967,6 +5564,7 @@ function defineConfig(config) {
4967
5564
  transformGraph,
4968
5565
  trapFocus,
4969
5566
  validateGraph,
5567
+ wireComponentBehavior,
4970
5568
  wireEvents,
4971
5569
  wireOverlayBehavior,
4972
5570
  wireReactiveList,