streetui 1.6.1 → 1.8.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
@@ -70,8 +70,10 @@ __export(src_exports, {
70
70
  buttonUpdate: () => buttonUpdate,
71
71
  compile: () => compile,
72
72
  compileGraph: () => compileGraph,
73
+ component: () => component,
73
74
  consoleDiagnosticSink: () => consoleDiagnosticSink,
74
75
  consoleHydrationDiagnosticSink: () => consoleHydrationDiagnosticSink,
76
+ containFocus: () => containFocus,
75
77
  createApplication: () => createApplication,
76
78
  createBrowserHistory: () => createBrowserHistory,
77
79
  createContext: () => createContext,
@@ -98,17 +100,20 @@ __export(src_exports, {
98
100
  flushSync: () => flushSync,
99
101
  focusById: () => focusById,
100
102
  focusFirst: () => focusFirst,
103
+ focusInitial: () => focusInitial,
101
104
  formatDiagnostic: () => formatDiagnostic,
102
105
  formatDiagnosticContext: () => formatDiagnosticContext,
103
106
  formatHydrationDiagnostic: () => formatHydrationDiagnostic,
104
107
  frameworkError: () => frameworkError,
105
108
  generateApplicationId: () => generateApplicationId,
106
109
  generateNodeId: () => generateNodeId,
110
+ getFocusable: () => getFocusable,
107
111
  globalEventBus: () => globalEventBus,
108
112
  headingUpdate: () => headingUpdate,
109
113
  hydrateGraph: () => hydrateGraph,
110
114
  inputUpdate: () => inputUpdate,
111
115
  inspectApplication: () => inspectApplication,
116
+ inspectComponents: () => inspectComponents,
112
117
  inspectContext: () => inspectContext,
113
118
  inspectForm: () => inspectForm,
114
119
  inspectGraph: () => inspectGraph,
@@ -118,6 +123,7 @@ __export(src_exports, {
118
123
  inspectSignal: () => inspectSignal,
119
124
  interpolate: () => interpolate,
120
125
  isBatching: () => isBatching,
126
+ isComponentDefinition: () => isComponentDefinition,
121
127
  matchPattern: () => matchPattern,
122
128
  matchRoutes: () => matchRoutes,
123
129
  maxLength: () => maxLength,
@@ -130,6 +136,7 @@ __export(src_exports, {
130
136
  nodeTypeStats: () => nodeTypeStats,
131
137
  normalizePath: () => normalizePath,
132
138
  observerCount: () => observerCount,
139
+ onEscape: () => onEscape,
133
140
  patchNode: () => patchNode,
134
141
  patchProp: () => patchProp,
135
142
  pattern: () => pattern,
@@ -146,8 +153,10 @@ __export(src_exports, {
146
153
  resetIdCounter: () => resetIdCounter,
147
154
  resolveTag: () => resolveTag,
148
155
  resource: () => resource,
156
+ restoreFocus: () => restoreFocus,
149
157
  routerOutlet: () => routerOutlet,
150
158
  runValidators: () => runValidators,
159
+ saveFocus: () => saveFocus,
151
160
  scheduleImmediate: () => scheduleImmediate,
152
161
  scheduleUpdate: () => scheduleUpdate,
153
162
  scheduler: () => scheduler,
@@ -162,15 +171,18 @@ __export(src_exports, {
162
171
  textUpdate: () => textUpdate,
163
172
  toIdToken: () => toIdToken,
164
173
  transformGraph: () => transformGraph,
174
+ trapFocus: () => trapFocus,
165
175
  validateGraph: () => validateGraph,
176
+ wireComponentBehavior: () => wireComponentBehavior,
166
177
  wireEvents: () => wireEvents,
178
+ wireOverlayBehavior: () => wireOverlayBehavior,
167
179
  wireReactiveList: () => wireReactiveList,
168
180
  wireSignalBindings: () => wireSignalBindings
169
181
  });
170
182
  module.exports = __toCommonJS(src_exports);
171
183
 
172
184
  // src/version.ts
173
- var VERSION = "1.6.0";
185
+ var VERSION = "1.8.0";
174
186
 
175
187
  // ../state/src/signal.ts
176
188
  var _activeConsumer = null;
@@ -973,6 +985,8 @@ var ApplicationGraph = class {
973
985
  }
974
986
  this.handlers.delete(`__listbuild__${node.id}`);
975
987
  this.handlers.delete(`__listplan__${node.id}`);
988
+ this.handlers.delete(`__overlay__${node.id}`);
989
+ this.handlers.delete(`__component__${node.id}`);
976
990
  }
977
991
  // ── Handler registry ──────────────────────────────────────────────────────
978
992
  registerHandler(key, fn) {
@@ -1087,6 +1101,7 @@ function applyA11yProps(props, options) {
1087
1101
  if (options.ariaCurrent !== void 0) props["aria-current"] = String(options.ariaCurrent);
1088
1102
  if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
1089
1103
  if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
1104
+ if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
1090
1105
  }
1091
1106
  function containerProps(options) {
1092
1107
  const props = {};
@@ -1118,6 +1133,49 @@ function reactiveListItemSignature(item) {
1118
1133
  function reactiveListItemKey(item, index) {
1119
1134
  return itemIdentity(item, index);
1120
1135
  }
1136
+ var OVERLAY_KINDS = {
1137
+ dialog: {
1138
+ role: "dialog",
1139
+ modal: true,
1140
+ takesFocus: true,
1141
+ ariaModal: true,
1142
+ defaultCloseOnEscape: true,
1143
+ defaultRestoreFocus: true
1144
+ },
1145
+ popover: {
1146
+ role: "dialog",
1147
+ modal: false,
1148
+ takesFocus: true,
1149
+ ariaModal: false,
1150
+ defaultCloseOnEscape: true,
1151
+ defaultRestoreFocus: true
1152
+ },
1153
+ tooltip: {
1154
+ role: "tooltip",
1155
+ modal: false,
1156
+ takesFocus: false,
1157
+ ariaModal: false,
1158
+ defaultCloseOnEscape: false,
1159
+ defaultRestoreFocus: false
1160
+ },
1161
+ dropdown: {
1162
+ role: "menu",
1163
+ modal: false,
1164
+ takesFocus: true,
1165
+ ariaModal: false,
1166
+ defaultCloseOnEscape: true,
1167
+ defaultRestoreFocus: true
1168
+ },
1169
+ toast: {
1170
+ role: "status",
1171
+ modal: false,
1172
+ takesFocus: false,
1173
+ ariaModal: false,
1174
+ ariaLive: "polite",
1175
+ defaultCloseOnEscape: false,
1176
+ defaultRestoreFocus: false
1177
+ }
1178
+ };
1121
1179
  var ContentBuilderBase = class {
1122
1180
  constructor(_node, _graph) {
1123
1181
  this._node = _node;
@@ -1390,6 +1448,131 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1390
1448
  );
1391
1449
  }, { id });
1392
1450
  }
1451
+ // ── Portals & overlays ──────────────────────────────────────────────────────
1452
+ portal(key, builder, options = {}) {
1453
+ const node = this._graph.createNode("portal", {
1454
+ key,
1455
+ parent: this._node,
1456
+ props: containerProps(options)
1457
+ });
1458
+ builder(new ContainerBuilderImpl(node, this._graph));
1459
+ }
1460
+ /**
1461
+ * Shared assembly for every overlay kind: a `portal` node whose single child
1462
+ * is a `when(open, panel)` conditional. The panel container carries the
1463
+ * kind's ARIA semantics; `builder` fills it. An `__overlay__<portalId>`
1464
+ * descriptor is registered so the renderer wires focus/keyboard behavior to
1465
+ * the same `open` signal that drives the panel. Reuses existing primitives
1466
+ * (portal + when + container) — no new render path.
1467
+ */
1468
+ _overlay(kind, key, options, builder) {
1469
+ const graph = this._graph;
1470
+ const portalNode = graph.createNode("portal", {
1471
+ key,
1472
+ parent: this._node,
1473
+ props: { key }
1474
+ });
1475
+ const openBindable = options.open;
1476
+ const openSignal = isSignal(openBindable) ? openBindable : signal(openBindable);
1477
+ const panelOptions = {
1478
+ role: options.role ?? kind.role,
1479
+ ...kind.ariaModal ? { ariaModal: true } : {},
1480
+ ...kind.ariaLive !== void 0 ? { ariaLive: kind.ariaLive } : {},
1481
+ ...options.class !== void 0 ? { class: options.class } : {},
1482
+ ...options.ariaLabel !== void 0 ? { ariaLabel: options.ariaLabel } : {},
1483
+ ...options.ariaLabelledBy !== void 0 ? { ariaLabelledBy: options.ariaLabelledBy } : {},
1484
+ ...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
1485
+ };
1486
+ const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
1487
+ portalBuilder.when(openSignal, (panelHost) => {
1488
+ panelHost.container(`${key}__panel`, builder, panelOptions);
1489
+ });
1490
+ const descriptor = {
1491
+ open: openSignal,
1492
+ modal: kind.modal,
1493
+ takesFocus: kind.takesFocus,
1494
+ closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
1495
+ restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
1496
+ ...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
1497
+ ...options.onClose !== void 0 ? { onClose: options.onClose } : {}
1498
+ };
1499
+ graph.registerHandler(
1500
+ `__overlay__${portalNode.id}`,
1501
+ () => descriptor
1502
+ );
1503
+ }
1504
+ dialog(key, options, builder) {
1505
+ this._overlay(OVERLAY_KINDS.dialog, key, options, builder);
1506
+ }
1507
+ popover(key, options, builder) {
1508
+ this._overlay(OVERLAY_KINDS.popover, key, options, builder);
1509
+ }
1510
+ tooltip(key, options, builder) {
1511
+ this._overlay(OVERLAY_KINDS.tooltip, key, options, builder);
1512
+ }
1513
+ dropdown(key, options, builder) {
1514
+ this._overlay(OVERLAY_KINDS.dropdown, key, options, builder);
1515
+ }
1516
+ toast(key, options, builder) {
1517
+ this._overlay(OVERLAY_KINDS.toast, key, options, builder);
1518
+ }
1519
+ // ── Components ────────────────────────────────────────────────────────────
1520
+ /**
1521
+ * Instantiate a reusable component (§3–§9). Creates a `'component'` node
1522
+ * (rendered as a `<div>` wrapper — preserves the one-node/one-element
1523
+ * positional-hydration invariant), then runs `def.setup(props, ctx)`
1524
+ * synchronously to obtain the render function and fills the component's own
1525
+ * container scope with it — structurally identical to `container`/
1526
+ * `errorBoundary`. Cleanups the setup registers via `ctx.effect`/
1527
+ * `ctx.onCleanup` are collected into a closure and exposed to the renderer
1528
+ * through a `__component__<id>` handler (mirroring `__overlay__`); the mount/
1529
+ * hydrate paths read it and route each into `NodeInstance.trackCleanup`, so
1530
+ * teardown runs (children-first) when the component leaves the graph.
1531
+ *
1532
+ * `setup` runs once per instance here at build time. When this component sits
1533
+ * inside a keyed list / conditional, a rebuild disposes the old instance
1534
+ * (running its cleanups + pruning its `__component__` entry) and re-runs this
1535
+ * method for the new node — so re-invocation is safe and leak-free.
1536
+ */
1537
+ component(key, def, props, children) {
1538
+ const graph = this._graph;
1539
+ const node = graph.createNode("component", {
1540
+ key,
1541
+ parent: this._node,
1542
+ // `data-streetui-component` is a non-underscore prop, so it reaches the
1543
+ // DOM as an attribute and is visible to DevTools (§21) — unlike the
1544
+ // internal `_`-prefixed metadata the renderer hides.
1545
+ props: { key, "data-streetui-component": def.name }
1546
+ });
1547
+ const cleanups = [];
1548
+ const ctx = {
1549
+ key,
1550
+ onCleanup(fn) {
1551
+ cleanups.push(fn);
1552
+ },
1553
+ effect(fn) {
1554
+ cleanups.push(effect(fn));
1555
+ },
1556
+ renderChildren(content) {
1557
+ if (children !== void 0) children(content);
1558
+ }
1559
+ };
1560
+ let render;
1561
+ try {
1562
+ render = def.setup(props, ctx);
1563
+ render(new ContainerBuilderImpl(node, graph));
1564
+ } catch (err) {
1565
+ queueMicrotask(() => {
1566
+ throw err;
1567
+ });
1568
+ }
1569
+ if (cleanups.length > 0) {
1570
+ graph.registerHandler(
1571
+ `__component__${node.id}`,
1572
+ () => cleanups
1573
+ );
1574
+ }
1575
+ }
1393
1576
  };
1394
1577
  var SectionBuilderImpl = class extends ContainerBuilderBase {
1395
1578
  };
@@ -1423,6 +1606,18 @@ var AppBuilder = class {
1423
1606
  }
1424
1607
  };
1425
1608
 
1609
+ // ../dsl/src/component.ts
1610
+ function component(setup, options = {}) {
1611
+ return {
1612
+ __streetui_component: true,
1613
+ name: options.name ?? setup.name ?? "Component",
1614
+ setup
1615
+ };
1616
+ }
1617
+ function isComponentDefinition(value) {
1618
+ return value !== null && typeof value === "object" && value.__streetui_component === true;
1619
+ }
1620
+
1426
1621
  // ../dsl/src/dsl.ts
1427
1622
  var StreetApp = class {
1428
1623
  _graph;
@@ -1990,6 +2185,18 @@ var BrowserDOMAdapter = class {
1990
2185
  focus(element) {
1991
2186
  element.focus?.();
1992
2187
  }
2188
+ body() {
2189
+ return document.body ?? null;
2190
+ }
2191
+ activeElement() {
2192
+ return document.activeElement ?? null;
2193
+ }
2194
+ contains(ancestor, node) {
2195
+ return ancestor.contains(node);
2196
+ }
2197
+ matches(element, selector) {
2198
+ return typeof element.matches === "function" && element.matches(selector);
2199
+ }
1993
2200
  isElement(node) {
1994
2201
  return node.nodeType === Node.ELEMENT_NODE;
1995
2202
  }
@@ -2357,6 +2564,18 @@ var ServerDOMAdapter = class {
2357
2564
  }
2358
2565
  focus() {
2359
2566
  }
2567
+ body() {
2568
+ return null;
2569
+ }
2570
+ activeElement() {
2571
+ return null;
2572
+ }
2573
+ contains(_ancestor, _node) {
2574
+ return false;
2575
+ }
2576
+ matches(_element, _selector) {
2577
+ return false;
2578
+ }
2360
2579
  isElement(node) {
2361
2580
  return asServer(node).kind === "element";
2362
2581
  }
@@ -2422,6 +2641,66 @@ function focusFirst(dom, container, selector = FOCUSABLE_SELECTOR) {
2422
2641
  dom.focus(el);
2423
2642
  return true;
2424
2643
  }
2644
+ function getFocusable(dom, container, selector = FOCUSABLE_SELECTOR) {
2645
+ return Array.from(dom.querySelectorAll(container, selector)).filter(
2646
+ (el) => dom.matches(el, selector)
2647
+ );
2648
+ }
2649
+ function saveFocus(dom) {
2650
+ return dom.activeElement();
2651
+ }
2652
+ function restoreFocus(dom, saved) {
2653
+ if (saved !== null) dom.focus(saved);
2654
+ }
2655
+ function focusInitial(dom, container, initialFocusId) {
2656
+ if (initialFocusId !== void 0 && focusById(dom, container, initialFocusId)) return;
2657
+ focusFirst(dom, container);
2658
+ }
2659
+ function trapFocus(dom, container) {
2660
+ const onKeydown = (event) => {
2661
+ if (event.key !== "Tab") return;
2662
+ const items = getFocusable(dom, container);
2663
+ if (items.length === 0) {
2664
+ event.preventDefault();
2665
+ return;
2666
+ }
2667
+ const first = items[0];
2668
+ const last = items[items.length - 1];
2669
+ const active = dom.activeElement();
2670
+ if (active === null || !dom.contains(container, active)) {
2671
+ event.preventDefault();
2672
+ dom.focus(first);
2673
+ } else if (event.shiftKey && active === first) {
2674
+ event.preventDefault();
2675
+ dom.focus(last);
2676
+ } else if (!event.shiftKey && active === last) {
2677
+ event.preventDefault();
2678
+ dom.focus(first);
2679
+ }
2680
+ };
2681
+ dom.addEventListener(container, "keydown", onKeydown);
2682
+ return () => dom.removeEventListener(container, "keydown", onKeydown);
2683
+ }
2684
+ function containFocus(dom, container) {
2685
+ const body = dom.body();
2686
+ if (body === null) return () => {
2687
+ };
2688
+ const onFocusIn = (event) => {
2689
+ const target = event.target;
2690
+ if (target !== null && !dom.contains(container, target)) {
2691
+ focusFirst(dom, container);
2692
+ }
2693
+ };
2694
+ dom.addEventListener(body, "focusin", onFocusIn);
2695
+ return () => dom.removeEventListener(body, "focusin", onFocusIn);
2696
+ }
2697
+ function onEscape(dom, target, handler) {
2698
+ const onKeydown = (event) => {
2699
+ if (event.key === "Escape") handler();
2700
+ };
2701
+ dom.addEventListener(target, "keydown", onKeydown);
2702
+ return () => dom.removeEventListener(target, "keydown", onKeydown);
2703
+ }
2425
2704
 
2426
2705
  // ../renderer/src/render-context.ts
2427
2706
  function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
@@ -2580,7 +2859,12 @@ var TAG_MAP = {
2580
2859
  component: "div",
2581
2860
  slot: "div",
2582
2861
  fragment: "div",
2583
- "reactive-list": "ul"
2862
+ "reactive-list": "ul",
2863
+ // A portal renders as a neutral inline anchor <div> at its declaration site;
2864
+ // its children are relocated to a document.body container on the browser
2865
+ // (see the portal branch in mount.ts). On the server (no body) it renders
2866
+ // inline, so the anchor tag is what SSR/hydration positionally match on.
2867
+ portal: "div"
2584
2868
  };
2585
2869
  function resolveTag(type) {
2586
2870
  return TAG_MAP[type] ?? "div";
@@ -2992,6 +3276,28 @@ function mountNode(ctx, graphNode, parentDom) {
2992
3276
  wireReactiveList(ctx, graphNode, instance2, el2);
2993
3277
  return instance2;
2994
3278
  }
3279
+ if (graphNode.type === "portal") {
3280
+ const anchor = dom.createElement(resolveTag("portal"));
3281
+ dom.setAttribute(anchor, "data-streetui-portal", "");
3282
+ applyNodeProps(ctx, graphNode, anchor);
3283
+ const instance2 = new NodeInstance(graphNode, anchor);
3284
+ ctx.instances.set(graphNode.id, instance2);
3285
+ const body = dom.body();
3286
+ let target = anchor;
3287
+ if (body !== null) {
3288
+ const portalContainer = dom.createElement("div");
3289
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
3290
+ dom.appendChild(body, portalContainer);
3291
+ instance2.trackCleanup(() => dom.removeChild(body, portalContainer));
3292
+ target = portalContainer;
3293
+ }
3294
+ for (const child of graphNode.children) {
3295
+ instance2.addChild(mountNode(ctx, child, target));
3296
+ }
3297
+ dom.appendChild(parentDom, anchor);
3298
+ wireOverlayBehavior(ctx, graphNode, instance2, target);
3299
+ return instance2;
3300
+ }
2995
3301
  const tag = resolveTag(graphNode.type);
2996
3302
  const el = dom.createElement(tag);
2997
3303
  applyNodeProps(ctx, graphNode, el);
@@ -3011,6 +3317,7 @@ function mountNode(ctx, graphNode, parentDom) {
3011
3317
  instance.addChild(childInstance);
3012
3318
  }
3013
3319
  dom.appendChild(parentDom, el);
3320
+ if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
3014
3321
  return instance;
3015
3322
  }
3016
3323
  function textUpdate(dom, el, textNode) {
@@ -3141,6 +3448,49 @@ function forgetInstance(ctx, instance) {
3141
3448
  ctx.instances.delete(instance.graphNode.id);
3142
3449
  for (const child of instance.children) forgetInstance(ctx, child);
3143
3450
  }
3451
+ function wireOverlayBehavior(ctx, graphNode, instance, target) {
3452
+ const { dom, graph } = ctx;
3453
+ if (dom.body() === null) return;
3454
+ const descFn = graph.getHandler(`__overlay__${graphNode.id}`);
3455
+ if (descFn === void 0) return;
3456
+ const desc = descFn();
3457
+ const openSig = desc.open;
3458
+ if (openSig === void 0 || typeof openSig.subscribe !== "function") return;
3459
+ let active = [];
3460
+ let saved = null;
3461
+ const teardown = () => {
3462
+ for (const fn of active) fn();
3463
+ active = [];
3464
+ };
3465
+ const onOpenChange = (isOpen) => {
3466
+ if (isOpen) {
3467
+ if (desc.restoreFocus) saved = saveFocus(dom);
3468
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
3469
+ if (desc.modal) {
3470
+ active.push(trapFocus(dom, target));
3471
+ active.push(containFocus(dom, target));
3472
+ }
3473
+ if (desc.closeOnEscape && desc.onClose !== void 0) {
3474
+ active.push(onEscape(dom, target, desc.onClose));
3475
+ }
3476
+ } else {
3477
+ teardown();
3478
+ if (desc.restoreFocus && saved !== null) {
3479
+ restoreFocus(dom, saved);
3480
+ saved = null;
3481
+ }
3482
+ }
3483
+ };
3484
+ const unsub = openSig.subscribe(onOpenChange);
3485
+ instance.trackCleanup(unsub);
3486
+ instance.trackCleanup(teardown);
3487
+ if (openSig.peek() === true) onOpenChange(true);
3488
+ }
3489
+ function wireComponentBehavior(ctx, graphNode, instance) {
3490
+ const fn = ctx.graph.getHandler(`__component__${graphNode.id}`);
3491
+ if (fn === void 0) return;
3492
+ for (const cleanup of fn()) instance.trackCleanup(cleanup);
3493
+ }
3144
3494
 
3145
3495
  // ../renderer/src/hydration-diagnostics.ts
3146
3496
  function formatHydrationDiagnostic(d) {
@@ -3243,6 +3593,25 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3243
3593
  wireReactiveList(ctx, graphNode, instance, domNode);
3244
3594
  return instance;
3245
3595
  }
3596
+ case "portal": {
3597
+ const instance = new NodeInstance(graphNode, domNode);
3598
+ ctx.instances.set(graphNode.id, instance);
3599
+ const body = dom.body();
3600
+ let target = domNode;
3601
+ if (body !== null) {
3602
+ const portalContainer = dom.createElement("div");
3603
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
3604
+ for (const child of dom.childNodes(domNode)) {
3605
+ dom.appendChild(portalContainer, child);
3606
+ }
3607
+ dom.appendChild(body, portalContainer);
3608
+ instance.trackCleanup(() => dom.removeChild(body, portalContainer));
3609
+ target = portalContainer;
3610
+ }
3611
+ hydrateChildren(ctx, graphNode, instance, target, path);
3612
+ wireOverlayBehavior(ctx, graphNode, instance, target);
3613
+ return instance;
3614
+ }
3246
3615
  default: {
3247
3616
  const instance = new NodeInstance(graphNode, domNode);
3248
3617
  ctx.instances.set(graphNode.id, instance);
@@ -3253,6 +3622,7 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3253
3622
  return instance;
3254
3623
  }
3255
3624
  hydrateChildren(ctx, graphNode, instance, domNode, path);
3625
+ if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
3256
3626
  return instance;
3257
3627
  }
3258
3628
  }
@@ -3486,7 +3856,9 @@ function analyzeGraph(graph) {
3486
3856
  const hasEvents = node.events.length > 0;
3487
3857
  const isList = node.type === "reactive-list";
3488
3858
  const isConditional = node.type === "conditional";
3489
- const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional;
3859
+ const isPortal = node.type === "portal";
3860
+ const isComponent = node.type === "component";
3861
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
3490
3862
  const isStaticSubtree = isStatic && allChildrenStatic;
3491
3863
  nodes.set(node.id, {
3492
3864
  isStatic,
@@ -4193,6 +4565,21 @@ function nodeTypeStats(graph) {
4193
4565
  });
4194
4566
  return counts;
4195
4567
  }
4568
+ function inspectComponents(graph) {
4569
+ const out = [];
4570
+ graph.walk((node, depth) => {
4571
+ if (node.type !== "component") return;
4572
+ const name = node.props["data-streetui-component"];
4573
+ out.push({
4574
+ id: node.id,
4575
+ key: node.key,
4576
+ name: typeof name === "string" ? name : "Component",
4577
+ depth,
4578
+ childCount: node.children.length
4579
+ });
4580
+ });
4581
+ return out;
4582
+ }
4196
4583
 
4197
4584
  // ../devtools/src/application.ts
4198
4585
  function collectPerf(node, distinctSignals, acc) {
@@ -4577,8 +4964,10 @@ function defineConfig(config) {
4577
4964
  buttonUpdate,
4578
4965
  compile,
4579
4966
  compileGraph,
4967
+ component,
4580
4968
  consoleDiagnosticSink,
4581
4969
  consoleHydrationDiagnosticSink,
4970
+ containFocus,
4582
4971
  createApplication,
4583
4972
  createBrowserHistory,
4584
4973
  createContext,
@@ -4605,17 +4994,20 @@ function defineConfig(config) {
4605
4994
  flushSync,
4606
4995
  focusById,
4607
4996
  focusFirst,
4997
+ focusInitial,
4608
4998
  formatDiagnostic,
4609
4999
  formatDiagnosticContext,
4610
5000
  formatHydrationDiagnostic,
4611
5001
  frameworkError,
4612
5002
  generateApplicationId,
4613
5003
  generateNodeId,
5004
+ getFocusable,
4614
5005
  globalEventBus,
4615
5006
  headingUpdate,
4616
5007
  hydrateGraph,
4617
5008
  inputUpdate,
4618
5009
  inspectApplication,
5010
+ inspectComponents,
4619
5011
  inspectContext,
4620
5012
  inspectForm,
4621
5013
  inspectGraph,
@@ -4625,6 +5017,7 @@ function defineConfig(config) {
4625
5017
  inspectSignal,
4626
5018
  interpolate,
4627
5019
  isBatching,
5020
+ isComponentDefinition,
4628
5021
  matchPattern,
4629
5022
  matchRoutes,
4630
5023
  maxLength,
@@ -4637,6 +5030,7 @@ function defineConfig(config) {
4637
5030
  nodeTypeStats,
4638
5031
  normalizePath,
4639
5032
  observerCount,
5033
+ onEscape,
4640
5034
  patchNode,
4641
5035
  patchProp,
4642
5036
  pattern,
@@ -4653,8 +5047,10 @@ function defineConfig(config) {
4653
5047
  resetIdCounter,
4654
5048
  resolveTag,
4655
5049
  resource,
5050
+ restoreFocus,
4656
5051
  routerOutlet,
4657
5052
  runValidators,
5053
+ saveFocus,
4658
5054
  scheduleImmediate,
4659
5055
  scheduleUpdate,
4660
5056
  scheduler,
@@ -4669,8 +5065,11 @@ function defineConfig(config) {
4669
5065
  textUpdate,
4670
5066
  toIdToken,
4671
5067
  transformGraph,
5068
+ trapFocus,
4672
5069
  validateGraph,
5070
+ wireComponentBehavior,
4673
5071
  wireEvents,
5072
+ wireOverlayBehavior,
4674
5073
  wireReactiveList,
4675
5074
  wireSignalBindings
4676
5075
  });