streetui 1.6.0 → 1.7.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
@@ -51,6 +51,7 @@ __export(src_exports, {
51
51
  ServerDOMAdapter: () => ServerDOMAdapter,
52
52
  ServerElement: () => ServerElement,
53
53
  ServerFragment: () => ServerFragment,
54
+ ServerRawHTML: () => ServerRawHTML,
54
55
  ServerStyle: () => ServerStyle,
55
56
  ServerText: () => ServerText,
56
57
  Signal: () => Signal,
@@ -71,6 +72,7 @@ __export(src_exports, {
71
72
  compileGraph: () => compileGraph,
72
73
  consoleDiagnosticSink: () => consoleDiagnosticSink,
73
74
  consoleHydrationDiagnosticSink: () => consoleHydrationDiagnosticSink,
75
+ containFocus: () => containFocus,
74
76
  createApplication: () => createApplication,
75
77
  createBrowserHistory: () => createBrowserHistory,
76
78
  createContext: () => createContext,
@@ -97,12 +99,14 @@ __export(src_exports, {
97
99
  flushSync: () => flushSync,
98
100
  focusById: () => focusById,
99
101
  focusFirst: () => focusFirst,
102
+ focusInitial: () => focusInitial,
100
103
  formatDiagnostic: () => formatDiagnostic,
101
104
  formatDiagnosticContext: () => formatDiagnosticContext,
102
105
  formatHydrationDiagnostic: () => formatHydrationDiagnostic,
103
106
  frameworkError: () => frameworkError,
104
107
  generateApplicationId: () => generateApplicationId,
105
108
  generateNodeId: () => generateNodeId,
109
+ getFocusable: () => getFocusable,
106
110
  globalEventBus: () => globalEventBus,
107
111
  headingUpdate: () => headingUpdate,
108
112
  hydrateGraph: () => hydrateGraph,
@@ -129,6 +133,7 @@ __export(src_exports, {
129
133
  nodeTypeStats: () => nodeTypeStats,
130
134
  normalizePath: () => normalizePath,
131
135
  observerCount: () => observerCount,
136
+ onEscape: () => onEscape,
132
137
  patchNode: () => patchNode,
133
138
  patchProp: () => patchProp,
134
139
  pattern: () => pattern,
@@ -145,8 +150,10 @@ __export(src_exports, {
145
150
  resetIdCounter: () => resetIdCounter,
146
151
  resolveTag: () => resolveTag,
147
152
  resource: () => resource,
153
+ restoreFocus: () => restoreFocus,
148
154
  routerOutlet: () => routerOutlet,
149
155
  runValidators: () => runValidators,
156
+ saveFocus: () => saveFocus,
150
157
  scheduleImmediate: () => scheduleImmediate,
151
158
  scheduleUpdate: () => scheduleUpdate,
152
159
  scheduler: () => scheduler,
@@ -161,15 +168,17 @@ __export(src_exports, {
161
168
  textUpdate: () => textUpdate,
162
169
  toIdToken: () => toIdToken,
163
170
  transformGraph: () => transformGraph,
171
+ trapFocus: () => trapFocus,
164
172
  validateGraph: () => validateGraph,
165
173
  wireEvents: () => wireEvents,
174
+ wireOverlayBehavior: () => wireOverlayBehavior,
166
175
  wireReactiveList: () => wireReactiveList,
167
176
  wireSignalBindings: () => wireSignalBindings
168
177
  });
169
178
  module.exports = __toCommonJS(src_exports);
170
179
 
171
180
  // src/version.ts
172
- var VERSION = "1.6.0";
181
+ var VERSION = "1.7.0";
173
182
 
174
183
  // ../state/src/signal.ts
175
184
  var _activeConsumer = null;
@@ -972,6 +981,7 @@ var ApplicationGraph = class {
972
981
  }
973
982
  this.handlers.delete(`__listbuild__${node.id}`);
974
983
  this.handlers.delete(`__listplan__${node.id}`);
984
+ this.handlers.delete(`__overlay__${node.id}`);
975
985
  }
976
986
  // ── Handler registry ──────────────────────────────────────────────────────
977
987
  registerHandler(key, fn) {
@@ -1086,6 +1096,7 @@ function applyA11yProps(props, options) {
1086
1096
  if (options.ariaCurrent !== void 0) props["aria-current"] = String(options.ariaCurrent);
1087
1097
  if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
1088
1098
  if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
1099
+ if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
1089
1100
  }
1090
1101
  function containerProps(options) {
1091
1102
  const props = {};
@@ -1117,6 +1128,49 @@ function reactiveListItemSignature(item) {
1117
1128
  function reactiveListItemKey(item, index) {
1118
1129
  return itemIdentity(item, index);
1119
1130
  }
1131
+ var OVERLAY_KINDS = {
1132
+ dialog: {
1133
+ role: "dialog",
1134
+ modal: true,
1135
+ takesFocus: true,
1136
+ ariaModal: true,
1137
+ defaultCloseOnEscape: true,
1138
+ defaultRestoreFocus: true
1139
+ },
1140
+ popover: {
1141
+ role: "dialog",
1142
+ modal: false,
1143
+ takesFocus: true,
1144
+ ariaModal: false,
1145
+ defaultCloseOnEscape: true,
1146
+ defaultRestoreFocus: true
1147
+ },
1148
+ tooltip: {
1149
+ role: "tooltip",
1150
+ modal: false,
1151
+ takesFocus: false,
1152
+ ariaModal: false,
1153
+ defaultCloseOnEscape: false,
1154
+ defaultRestoreFocus: false
1155
+ },
1156
+ dropdown: {
1157
+ role: "menu",
1158
+ modal: false,
1159
+ takesFocus: true,
1160
+ ariaModal: false,
1161
+ defaultCloseOnEscape: true,
1162
+ defaultRestoreFocus: true
1163
+ },
1164
+ toast: {
1165
+ role: "status",
1166
+ modal: false,
1167
+ takesFocus: false,
1168
+ ariaModal: false,
1169
+ ariaLive: "polite",
1170
+ defaultCloseOnEscape: false,
1171
+ defaultRestoreFocus: false
1172
+ }
1173
+ };
1120
1174
  var ContentBuilderBase = class {
1121
1175
  constructor(_node, _graph) {
1122
1176
  this._node = _node;
@@ -1389,6 +1443,74 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
1389
1443
  );
1390
1444
  }, { id });
1391
1445
  }
1446
+ // ── Portals & overlays ──────────────────────────────────────────────────────
1447
+ portal(key, builder, options = {}) {
1448
+ const node = this._graph.createNode("portal", {
1449
+ key,
1450
+ parent: this._node,
1451
+ props: containerProps(options)
1452
+ });
1453
+ builder(new ContainerBuilderImpl(node, this._graph));
1454
+ }
1455
+ /**
1456
+ * Shared assembly for every overlay kind: a `portal` node whose single child
1457
+ * is a `when(open, panel)` conditional. The panel container carries the
1458
+ * kind's ARIA semantics; `builder` fills it. An `__overlay__<portalId>`
1459
+ * descriptor is registered so the renderer wires focus/keyboard behavior to
1460
+ * the same `open` signal that drives the panel. Reuses existing primitives
1461
+ * (portal + when + container) — no new render path.
1462
+ */
1463
+ _overlay(kind, key, options, builder) {
1464
+ const graph = this._graph;
1465
+ const portalNode = graph.createNode("portal", {
1466
+ key,
1467
+ parent: this._node,
1468
+ props: { key }
1469
+ });
1470
+ const openBindable = options.open;
1471
+ const openSignal = isSignal(openBindable) ? openBindable : signal(openBindable);
1472
+ const panelOptions = {
1473
+ role: options.role ?? kind.role,
1474
+ ...kind.ariaModal ? { ariaModal: true } : {},
1475
+ ...kind.ariaLive !== void 0 ? { ariaLive: kind.ariaLive } : {},
1476
+ ...options.class !== void 0 ? { class: options.class } : {},
1477
+ ...options.ariaLabel !== void 0 ? { ariaLabel: options.ariaLabel } : {},
1478
+ ...options.ariaLabelledBy !== void 0 ? { ariaLabelledBy: options.ariaLabelledBy } : {},
1479
+ ...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
1480
+ };
1481
+ const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
1482
+ portalBuilder.when(openSignal, (panelHost) => {
1483
+ panelHost.container(`${key}__panel`, builder, panelOptions);
1484
+ });
1485
+ const descriptor = {
1486
+ open: openSignal,
1487
+ modal: kind.modal,
1488
+ takesFocus: kind.takesFocus,
1489
+ closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
1490
+ restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
1491
+ ...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
1492
+ ...options.onClose !== void 0 ? { onClose: options.onClose } : {}
1493
+ };
1494
+ graph.registerHandler(
1495
+ `__overlay__${portalNode.id}`,
1496
+ () => descriptor
1497
+ );
1498
+ }
1499
+ dialog(key, options, builder) {
1500
+ this._overlay(OVERLAY_KINDS.dialog, key, options, builder);
1501
+ }
1502
+ popover(key, options, builder) {
1503
+ this._overlay(OVERLAY_KINDS.popover, key, options, builder);
1504
+ }
1505
+ tooltip(key, options, builder) {
1506
+ this._overlay(OVERLAY_KINDS.tooltip, key, options, builder);
1507
+ }
1508
+ dropdown(key, options, builder) {
1509
+ this._overlay(OVERLAY_KINDS.dropdown, key, options, builder);
1510
+ }
1511
+ toast(key, options, builder) {
1512
+ this._overlay(OVERLAY_KINDS.toast, key, options, builder);
1513
+ }
1392
1514
  };
1393
1515
  var SectionBuilderImpl = class extends ContainerBuilderBase {
1394
1516
  };
@@ -1989,6 +2111,18 @@ var BrowserDOMAdapter = class {
1989
2111
  focus(element) {
1990
2112
  element.focus?.();
1991
2113
  }
2114
+ body() {
2115
+ return document.body ?? null;
2116
+ }
2117
+ activeElement() {
2118
+ return document.activeElement ?? null;
2119
+ }
2120
+ contains(ancestor, node) {
2121
+ return ancestor.contains(node);
2122
+ }
2123
+ matches(element, selector) {
2124
+ return typeof element.matches === "function" && element.matches(selector);
2125
+ }
1992
2126
  isElement(node) {
1993
2127
  return node.nodeType === Node.ELEMENT_NODE;
1994
2128
  }
@@ -2047,6 +2181,14 @@ var ServerFragment = class {
2047
2181
  parent = null;
2048
2182
  children = [];
2049
2183
  };
2184
+ var ServerRawHTML = class {
2185
+ kind = "raw";
2186
+ parent = null;
2187
+ html;
2188
+ constructor(html) {
2189
+ this.html = html;
2190
+ }
2191
+ };
2050
2192
  var ServerElement = class {
2051
2193
  kind = "element";
2052
2194
  parent = null;
@@ -2197,6 +2339,8 @@ function serializeServerNode(node) {
2197
2339
  return `<!--${node.data}-->`;
2198
2340
  case "fragment":
2199
2341
  return serializeChildren(node);
2342
+ case "raw":
2343
+ return node.html;
2200
2344
  case "element": {
2201
2345
  const el = node;
2202
2346
  const tag = el.tagName;
@@ -2236,6 +2380,15 @@ var ServerDOMAdapter = class {
2236
2380
  createFragment() {
2237
2381
  return new ServerFragment();
2238
2382
  }
2383
+ /**
2384
+ * Create a verbatim pre-serialized HTML node (v1.7 static SSR plan, §6).
2385
+ * Server-only: the browser adapter does not implement this, and the renderer
2386
+ * fast path only invokes it when a static SSR plan is present (SSR). The
2387
+ * stored HTML was produced by this same serializer, so it is emitted as-is.
2388
+ */
2389
+ createRawHTML(html) {
2390
+ return new ServerRawHTML(html);
2391
+ }
2239
2392
  appendChild(parent, child) {
2240
2393
  const p = asParent(parent);
2241
2394
  const c = asServer(child);
@@ -2337,6 +2490,18 @@ var ServerDOMAdapter = class {
2337
2490
  }
2338
2491
  focus() {
2339
2492
  }
2493
+ body() {
2494
+ return null;
2495
+ }
2496
+ activeElement() {
2497
+ return null;
2498
+ }
2499
+ contains(_ancestor, _node) {
2500
+ return false;
2501
+ }
2502
+ matches(_element, _selector) {
2503
+ return false;
2504
+ }
2340
2505
  isElement(node) {
2341
2506
  return asServer(node).kind === "element";
2342
2507
  }
@@ -2402,15 +2567,76 @@ function focusFirst(dom, container, selector = FOCUSABLE_SELECTOR) {
2402
2567
  dom.focus(el);
2403
2568
  return true;
2404
2569
  }
2570
+ function getFocusable(dom, container, selector = FOCUSABLE_SELECTOR) {
2571
+ return Array.from(dom.querySelectorAll(container, selector)).filter(
2572
+ (el) => dom.matches(el, selector)
2573
+ );
2574
+ }
2575
+ function saveFocus(dom) {
2576
+ return dom.activeElement();
2577
+ }
2578
+ function restoreFocus(dom, saved) {
2579
+ if (saved !== null) dom.focus(saved);
2580
+ }
2581
+ function focusInitial(dom, container, initialFocusId) {
2582
+ if (initialFocusId !== void 0 && focusById(dom, container, initialFocusId)) return;
2583
+ focusFirst(dom, container);
2584
+ }
2585
+ function trapFocus(dom, container) {
2586
+ const onKeydown = (event) => {
2587
+ if (event.key !== "Tab") return;
2588
+ const items = getFocusable(dom, container);
2589
+ if (items.length === 0) {
2590
+ event.preventDefault();
2591
+ return;
2592
+ }
2593
+ const first = items[0];
2594
+ const last = items[items.length - 1];
2595
+ const active = dom.activeElement();
2596
+ if (active === null || !dom.contains(container, active)) {
2597
+ event.preventDefault();
2598
+ dom.focus(first);
2599
+ } else if (event.shiftKey && active === first) {
2600
+ event.preventDefault();
2601
+ dom.focus(last);
2602
+ } else if (!event.shiftKey && active === last) {
2603
+ event.preventDefault();
2604
+ dom.focus(first);
2605
+ }
2606
+ };
2607
+ dom.addEventListener(container, "keydown", onKeydown);
2608
+ return () => dom.removeEventListener(container, "keydown", onKeydown);
2609
+ }
2610
+ function containFocus(dom, container) {
2611
+ const body = dom.body();
2612
+ if (body === null) return () => {
2613
+ };
2614
+ const onFocusIn = (event) => {
2615
+ const target = event.target;
2616
+ if (target !== null && !dom.contains(container, target)) {
2617
+ focusFirst(dom, container);
2618
+ }
2619
+ };
2620
+ dom.addEventListener(body, "focusin", onFocusIn);
2621
+ return () => dom.removeEventListener(body, "focusin", onFocusIn);
2622
+ }
2623
+ function onEscape(dom, target, handler) {
2624
+ const onKeydown = (event) => {
2625
+ if (event.key === "Escape") handler();
2626
+ };
2627
+ dom.addEventListener(target, "keydown", onKeydown);
2628
+ return () => dom.removeEventListener(target, "keydown", onKeydown);
2629
+ }
2405
2630
 
2406
2631
  // ../renderer/src/render-context.ts
2407
- function createRenderContext(dom, graph, container, hydrationDiagnostics) {
2632
+ function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
2408
2633
  return {
2409
2634
  dom,
2410
2635
  graph,
2411
2636
  instances: /* @__PURE__ */ new Map(),
2412
2637
  container,
2413
- ...hydrationDiagnostics !== void 0 ? { hydrationDiagnostics } : {}
2638
+ ...hydrationDiagnostics !== void 0 ? { hydrationDiagnostics } : {},
2639
+ ...staticHTML !== void 0 ? { staticHTML } : {}
2414
2640
  };
2415
2641
  }
2416
2642
 
@@ -2559,7 +2785,12 @@ var TAG_MAP = {
2559
2785
  component: "div",
2560
2786
  slot: "div",
2561
2787
  fragment: "div",
2562
- "reactive-list": "ul"
2788
+ "reactive-list": "ul",
2789
+ // A portal renders as a neutral inline anchor <div> at its declaration site;
2790
+ // its children are relocated to a document.body container on the browser
2791
+ // (see the portal branch in mount.ts). On the server (no body) it renders
2792
+ // inline, so the anchor tag is what SSR/hydration positionally match on.
2793
+ portal: "div"
2563
2794
  };
2564
2795
  function resolveTag(type) {
2565
2796
  return TAG_MAP[type] ?? "div";
@@ -2838,6 +3069,17 @@ function mountGraph(ctx) {
2838
3069
  }
2839
3070
  function mountNode(ctx, graphNode, parentDom) {
2840
3071
  const { dom, graph } = ctx;
3072
+ const staticHTML = ctx.staticHTML;
3073
+ if (staticHTML !== void 0 && dom.createRawHTML !== void 0) {
3074
+ const precomputed = staticHTML.get(graphNode.id);
3075
+ if (precomputed !== void 0) {
3076
+ const raw = dom.createRawHTML(precomputed);
3077
+ dom.appendChild(parentDom, raw);
3078
+ const instance2 = new NodeInstance(graphNode, raw);
3079
+ ctx.instances.set(graphNode.id, instance2);
3080
+ return instance2;
3081
+ }
3082
+ }
2841
3083
  if (graphNode.type === "application") {
2842
3084
  const instance2 = new NodeInstance(graphNode, parentDom);
2843
3085
  ctx.instances.set(graphNode.id, instance2);
@@ -2960,6 +3202,28 @@ function mountNode(ctx, graphNode, parentDom) {
2960
3202
  wireReactiveList(ctx, graphNode, instance2, el2);
2961
3203
  return instance2;
2962
3204
  }
3205
+ if (graphNode.type === "portal") {
3206
+ const anchor = dom.createElement(resolveTag("portal"));
3207
+ dom.setAttribute(anchor, "data-streetui-portal", "");
3208
+ applyNodeProps(ctx, graphNode, anchor);
3209
+ const instance2 = new NodeInstance(graphNode, anchor);
3210
+ ctx.instances.set(graphNode.id, instance2);
3211
+ const body = dom.body();
3212
+ let target = anchor;
3213
+ if (body !== null) {
3214
+ const portalContainer = dom.createElement("div");
3215
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
3216
+ dom.appendChild(body, portalContainer);
3217
+ instance2.trackCleanup(() => dom.removeChild(body, portalContainer));
3218
+ target = portalContainer;
3219
+ }
3220
+ for (const child of graphNode.children) {
3221
+ instance2.addChild(mountNode(ctx, child, target));
3222
+ }
3223
+ dom.appendChild(parentDom, anchor);
3224
+ wireOverlayBehavior(ctx, graphNode, instance2, target);
3225
+ return instance2;
3226
+ }
2963
3227
  const tag = resolveTag(graphNode.type);
2964
3228
  const el = dom.createElement(tag);
2965
3229
  applyNodeProps(ctx, graphNode, el);
@@ -3109,6 +3373,44 @@ function forgetInstance(ctx, instance) {
3109
3373
  ctx.instances.delete(instance.graphNode.id);
3110
3374
  for (const child of instance.children) forgetInstance(ctx, child);
3111
3375
  }
3376
+ function wireOverlayBehavior(ctx, graphNode, instance, target) {
3377
+ const { dom, graph } = ctx;
3378
+ if (dom.body() === null) return;
3379
+ const descFn = graph.getHandler(`__overlay__${graphNode.id}`);
3380
+ if (descFn === void 0) return;
3381
+ const desc = descFn();
3382
+ const openSig = desc.open;
3383
+ if (openSig === void 0 || typeof openSig.subscribe !== "function") return;
3384
+ let active = [];
3385
+ let saved = null;
3386
+ const teardown = () => {
3387
+ for (const fn of active) fn();
3388
+ active = [];
3389
+ };
3390
+ const onOpenChange = (isOpen) => {
3391
+ if (isOpen) {
3392
+ if (desc.restoreFocus) saved = saveFocus(dom);
3393
+ if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
3394
+ if (desc.modal) {
3395
+ active.push(trapFocus(dom, target));
3396
+ active.push(containFocus(dom, target));
3397
+ }
3398
+ if (desc.closeOnEscape && desc.onClose !== void 0) {
3399
+ active.push(onEscape(dom, target, desc.onClose));
3400
+ }
3401
+ } else {
3402
+ teardown();
3403
+ if (desc.restoreFocus && saved !== null) {
3404
+ restoreFocus(dom, saved);
3405
+ saved = null;
3406
+ }
3407
+ }
3408
+ };
3409
+ const unsub = openSig.subscribe(onOpenChange);
3410
+ instance.trackCleanup(unsub);
3411
+ instance.trackCleanup(teardown);
3412
+ if (openSig.peek() === true) onOpenChange(true);
3413
+ }
3112
3414
 
3113
3415
  // ../renderer/src/hydration-diagnostics.ts
3114
3416
  function formatHydrationDiagnostic(d) {
@@ -3211,6 +3513,25 @@ function hydrateNode(ctx, graphNode, domNode, path) {
3211
3513
  wireReactiveList(ctx, graphNode, instance, domNode);
3212
3514
  return instance;
3213
3515
  }
3516
+ case "portal": {
3517
+ const instance = new NodeInstance(graphNode, domNode);
3518
+ ctx.instances.set(graphNode.id, instance);
3519
+ const body = dom.body();
3520
+ let target = domNode;
3521
+ if (body !== null) {
3522
+ const portalContainer = dom.createElement("div");
3523
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
3524
+ for (const child of dom.childNodes(domNode)) {
3525
+ dom.appendChild(portalContainer, child);
3526
+ }
3527
+ dom.appendChild(body, portalContainer);
3528
+ instance.trackCleanup(() => dom.removeChild(body, portalContainer));
3529
+ target = portalContainer;
3530
+ }
3531
+ hydrateChildren(ctx, graphNode, instance, target, path);
3532
+ wireOverlayBehavior(ctx, graphNode, instance, target);
3533
+ return instance;
3534
+ }
3214
3535
  default: {
3215
3536
  const instance = new NodeInstance(graphNode, domNode);
3216
3537
  ctx.instances.set(graphNode.id, instance);
@@ -3425,11 +3746,115 @@ function readState(dom, root) {
3425
3746
  }
3426
3747
  }
3427
3748
 
3749
+ // ../compiler/dist/diagnostics.js
3750
+ var TEXT_PROP_KEYS = /* @__PURE__ */ new Set(["text", "label", "value"]);
3751
+ function analyzeGraph(graph) {
3752
+ const nodes = /* @__PURE__ */ new Map();
3753
+ const summary = {
3754
+ totalNodes: 0,
3755
+ staticNodes: 0,
3756
+ staticSubtrees: 0,
3757
+ dynamicTextNodes: 0,
3758
+ dynamicAttrNodes: 0,
3759
+ eventNodes: 0,
3760
+ lists: 0,
3761
+ conditionals: 0
3762
+ };
3763
+ const visit = (node) => {
3764
+ let allChildrenStatic = true;
3765
+ for (const child of node.children) {
3766
+ const childSubtreeStatic = visit(child);
3767
+ if (!childSubtreeStatic) allChildrenStatic = false;
3768
+ }
3769
+ let hasDynamicText = false;
3770
+ let hasDynamicAttr = false;
3771
+ for (const ref of node.stateRefs) {
3772
+ if (TEXT_PROP_KEYS.has(ref.propKey)) hasDynamicText = true;
3773
+ else hasDynamicAttr = true;
3774
+ }
3775
+ const hasEvents = node.events.length > 0;
3776
+ const isList = node.type === "reactive-list";
3777
+ const isConditional = node.type === "conditional";
3778
+ const isPortal = node.type === "portal";
3779
+ const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal;
3780
+ const isStaticSubtree = isStatic && allChildrenStatic;
3781
+ nodes.set(node.id, {
3782
+ isStatic,
3783
+ isStaticSubtree,
3784
+ hasDynamicText,
3785
+ hasDynamicAttr,
3786
+ hasEvents,
3787
+ isList,
3788
+ isConditional
3789
+ });
3790
+ summary.totalNodes += 1;
3791
+ if (isStatic) summary.staticNodes += 1;
3792
+ if (isStaticSubtree) summary.staticSubtrees += 1;
3793
+ if (hasDynamicText) summary.dynamicTextNodes += 1;
3794
+ if (hasDynamicAttr) summary.dynamicAttrNodes += 1;
3795
+ if (hasEvents) summary.eventNodes += 1;
3796
+ if (isList) summary.lists += 1;
3797
+ if (isConditional) summary.conditionals += 1;
3798
+ return isStaticSubtree;
3799
+ };
3800
+ visit(graph.root);
3801
+ return { nodes, summary };
3802
+ }
3803
+
3804
+ // ../renderer/src/static-ssr-plan.ts
3805
+ function collectMaximalStaticRoots(graph) {
3806
+ const analysis = analyzeGraph(graph);
3807
+ const roots = [];
3808
+ const walk = (node) => {
3809
+ if (node.type !== "application") {
3810
+ const a = analysis.nodes.get(node.id);
3811
+ if (a !== void 0 && a.isStaticSubtree) {
3812
+ roots.push(node);
3813
+ return;
3814
+ }
3815
+ }
3816
+ for (const child of node.children) walk(child);
3817
+ };
3818
+ walk(graph.root);
3819
+ return roots;
3820
+ }
3821
+ function serializeStaticSubtree(dom, graph, root) {
3822
+ const container = dom.createElement("div");
3823
+ const ctx = createRenderContext(dom, graph, container);
3824
+ const instance = mountNode(ctx, root, container);
3825
+ const html = dom.serializeInner(container);
3826
+ instance.dispose();
3827
+ ctx.instances.clear();
3828
+ return html;
3829
+ }
3830
+ function buildStaticSSRPlan(compiled) {
3831
+ const graph = compiled.graph;
3832
+ const roots = collectMaximalStaticRoots(graph);
3833
+ const plan = /* @__PURE__ */ new Map();
3834
+ if (roots.length === 0) return plan;
3835
+ const dom = new ServerDOMAdapter();
3836
+ for (const root of roots) {
3837
+ plan.set(root.id, serializeStaticSubtree(dom, graph, root));
3838
+ }
3839
+ return plan;
3840
+ }
3841
+ var PLAN_CACHE = /* @__PURE__ */ new WeakMap();
3842
+ function getStaticSSRPlan(compiled) {
3843
+ let plan = PLAN_CACHE.get(compiled);
3844
+ if (plan === void 0) {
3845
+ plan = buildStaticSSRPlan(compiled);
3846
+ PLAN_CACHE.set(compiled, plan);
3847
+ }
3848
+ return plan;
3849
+ }
3850
+
3428
3851
  // ../renderer/src/ssr.ts
3429
3852
  function renderToString(compiled, options = {}) {
3430
3853
  const dom = options.domAdapter ?? new ServerDOMAdapter();
3854
+ const plan = options.staticPlan === null ? void 0 : options.staticPlan ?? getStaticSSRPlan(compiled);
3855
+ const staticHTML = plan !== void 0 && plan.size > 0 ? plan : void 0;
3431
3856
  const container = dom.createElement("div");
3432
- const ctx = createRenderContext(dom, compiled.graph, container);
3857
+ const ctx = createRenderContext(dom, compiled.graph, container, void 0, staticHTML);
3433
3858
  const rootInstance = mountGraph(ctx);
3434
3859
  const html = dom.serializeInner(container);
3435
3860
  rootInstance.dispose();
@@ -4423,6 +4848,7 @@ function defineConfig(config) {
4423
4848
  ServerDOMAdapter,
4424
4849
  ServerElement,
4425
4850
  ServerFragment,
4851
+ ServerRawHTML,
4426
4852
  ServerStyle,
4427
4853
  ServerText,
4428
4854
  Signal,
@@ -4443,6 +4869,7 @@ function defineConfig(config) {
4443
4869
  compileGraph,
4444
4870
  consoleDiagnosticSink,
4445
4871
  consoleHydrationDiagnosticSink,
4872
+ containFocus,
4446
4873
  createApplication,
4447
4874
  createBrowserHistory,
4448
4875
  createContext,
@@ -4469,12 +4896,14 @@ function defineConfig(config) {
4469
4896
  flushSync,
4470
4897
  focusById,
4471
4898
  focusFirst,
4899
+ focusInitial,
4472
4900
  formatDiagnostic,
4473
4901
  formatDiagnosticContext,
4474
4902
  formatHydrationDiagnostic,
4475
4903
  frameworkError,
4476
4904
  generateApplicationId,
4477
4905
  generateNodeId,
4906
+ getFocusable,
4478
4907
  globalEventBus,
4479
4908
  headingUpdate,
4480
4909
  hydrateGraph,
@@ -4501,6 +4930,7 @@ function defineConfig(config) {
4501
4930
  nodeTypeStats,
4502
4931
  normalizePath,
4503
4932
  observerCount,
4933
+ onEscape,
4504
4934
  patchNode,
4505
4935
  patchProp,
4506
4936
  pattern,
@@ -4517,8 +4947,10 @@ function defineConfig(config) {
4517
4947
  resetIdCounter,
4518
4948
  resolveTag,
4519
4949
  resource,
4950
+ restoreFocus,
4520
4951
  routerOutlet,
4521
4952
  runValidators,
4953
+ saveFocus,
4522
4954
  scheduleImmediate,
4523
4955
  scheduleUpdate,
4524
4956
  scheduler,
@@ -4533,8 +4965,10 @@ function defineConfig(config) {
4533
4965
  textUpdate,
4534
4966
  toIdToken,
4535
4967
  transformGraph,
4968
+ trapFocus,
4536
4969
  validateGraph,
4537
4970
  wireEvents,
4971
+ wireOverlayBehavior,
4538
4972
  wireReactiveList,
4539
4973
  wireSignalBindings
4540
4974
  });