@streetui/renderer 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
@@ -47,19 +47,21 @@ __export(index_exports, {
47
47
  serializeState: () => serializeState,
48
48
  textUpdate: () => textUpdate,
49
49
  wireEvents: () => wireEvents,
50
+ wireOverlayBehavior: () => wireOverlayBehavior,
50
51
  wireReactiveList: () => wireReactiveList,
51
52
  wireSignalBindings: () => wireSignalBindings
52
53
  });
53
54
  module.exports = __toCommonJS(index_exports);
54
55
 
55
56
  // src/render-context.ts
56
- function createRenderContext(dom, graph, container, hydrationDiagnostics) {
57
+ function createRenderContext(dom, graph, container, hydrationDiagnostics, staticHTML) {
57
58
  return {
58
59
  dom,
59
60
  graph,
60
61
  instances: /* @__PURE__ */ new Map(),
61
62
  container,
62
- ...hydrationDiagnostics !== void 0 ? { hydrationDiagnostics } : {}
63
+ ...hydrationDiagnostics !== void 0 ? { hydrationDiagnostics } : {},
64
+ ...staticHTML !== void 0 ? { staticHTML } : {}
63
65
  };
64
66
  }
65
67
 
@@ -191,6 +193,9 @@ function wireEvents(dom, graph, node, element, instance) {
191
193
  }
192
194
  }
193
195
 
196
+ // src/mount.ts
197
+ var import_dom = require("@streetui/dom");
198
+
194
199
  // src/tag-map.ts
195
200
  var TAG_MAP = {
196
201
  application: "div",
@@ -209,7 +214,12 @@ var TAG_MAP = {
209
214
  component: "div",
210
215
  slot: "div",
211
216
  fragment: "div",
212
- "reactive-list": "ul"
217
+ "reactive-list": "ul",
218
+ // A portal renders as a neutral inline anchor <div> at its declaration site;
219
+ // its children are relocated to a document.body container on the browser
220
+ // (see the portal branch in mount.ts). On the server (no body) it renders
221
+ // inline, so the anchor tag is what SSR/hydration positionally match on.
222
+ portal: "div"
213
223
  };
214
224
  function resolveTag(type) {
215
225
  return TAG_MAP[type] ?? "div";
@@ -488,6 +498,17 @@ function mountGraph(ctx) {
488
498
  }
489
499
  function mountNode(ctx, graphNode, parentDom) {
490
500
  const { dom, graph } = ctx;
501
+ const staticHTML = ctx.staticHTML;
502
+ if (staticHTML !== void 0 && dom.createRawHTML !== void 0) {
503
+ const precomputed = staticHTML.get(graphNode.id);
504
+ if (precomputed !== void 0) {
505
+ const raw = dom.createRawHTML(precomputed);
506
+ dom.appendChild(parentDom, raw);
507
+ const instance2 = new NodeInstance(graphNode, raw);
508
+ ctx.instances.set(graphNode.id, instance2);
509
+ return instance2;
510
+ }
511
+ }
491
512
  if (graphNode.type === "application") {
492
513
  const instance2 = new NodeInstance(graphNode, parentDom);
493
514
  ctx.instances.set(graphNode.id, instance2);
@@ -610,6 +631,28 @@ function mountNode(ctx, graphNode, parentDom) {
610
631
  wireReactiveList(ctx, graphNode, instance2, el2);
611
632
  return instance2;
612
633
  }
634
+ if (graphNode.type === "portal") {
635
+ const anchor = dom.createElement(resolveTag("portal"));
636
+ dom.setAttribute(anchor, "data-streetui-portal", "");
637
+ applyNodeProps(ctx, graphNode, anchor);
638
+ const instance2 = new NodeInstance(graphNode, anchor);
639
+ ctx.instances.set(graphNode.id, instance2);
640
+ const body = dom.body();
641
+ let target = anchor;
642
+ if (body !== null) {
643
+ const portalContainer = dom.createElement("div");
644
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
645
+ dom.appendChild(body, portalContainer);
646
+ instance2.trackCleanup(() => dom.removeChild(body, portalContainer));
647
+ target = portalContainer;
648
+ }
649
+ for (const child of graphNode.children) {
650
+ instance2.addChild(mountNode(ctx, child, target));
651
+ }
652
+ dom.appendChild(parentDom, anchor);
653
+ wireOverlayBehavior(ctx, graphNode, instance2, target);
654
+ return instance2;
655
+ }
613
656
  const tag = resolveTag(graphNode.type);
614
657
  const el = dom.createElement(tag);
615
658
  applyNodeProps(ctx, graphNode, el);
@@ -759,9 +802,47 @@ function forgetInstance(ctx, instance) {
759
802
  ctx.instances.delete(instance.graphNode.id);
760
803
  for (const child of instance.children) forgetInstance(ctx, child);
761
804
  }
805
+ function wireOverlayBehavior(ctx, graphNode, instance, target) {
806
+ const { dom, graph } = ctx;
807
+ if (dom.body() === null) return;
808
+ const descFn = graph.getHandler(`__overlay__${graphNode.id}`);
809
+ if (descFn === void 0) return;
810
+ const desc = descFn();
811
+ const openSig = desc.open;
812
+ if (openSig === void 0 || typeof openSig.subscribe !== "function") return;
813
+ let active = [];
814
+ let saved = null;
815
+ const teardown = () => {
816
+ for (const fn of active) fn();
817
+ active = [];
818
+ };
819
+ const onOpenChange = (isOpen) => {
820
+ if (isOpen) {
821
+ if (desc.restoreFocus) saved = (0, import_dom.saveFocus)(dom);
822
+ if (desc.takesFocus) (0, import_dom.focusInitial)(dom, target, desc.initialFocusId);
823
+ if (desc.modal) {
824
+ active.push((0, import_dom.trapFocus)(dom, target));
825
+ active.push((0, import_dom.containFocus)(dom, target));
826
+ }
827
+ if (desc.closeOnEscape && desc.onClose !== void 0) {
828
+ active.push((0, import_dom.onEscape)(dom, target, desc.onClose));
829
+ }
830
+ } else {
831
+ teardown();
832
+ if (desc.restoreFocus && saved !== null) {
833
+ (0, import_dom.restoreFocus)(dom, saved);
834
+ saved = null;
835
+ }
836
+ }
837
+ };
838
+ const unsub = openSig.subscribe(onOpenChange);
839
+ instance.trackCleanup(unsub);
840
+ instance.trackCleanup(teardown);
841
+ if (openSig.peek() === true) onOpenChange(true);
842
+ }
762
843
 
763
844
  // src/renderer.ts
764
- var import_dom = require("@streetui/dom");
845
+ var import_dom2 = require("@streetui/dom");
765
846
 
766
847
  // src/hydration-diagnostics.ts
767
848
  function formatHydrationDiagnostic(d) {
@@ -864,6 +945,25 @@ function hydrateNode(ctx, graphNode, domNode, path) {
864
945
  wireReactiveList(ctx, graphNode, instance, domNode);
865
946
  return instance;
866
947
  }
948
+ case "portal": {
949
+ const instance = new NodeInstance(graphNode, domNode);
950
+ ctx.instances.set(graphNode.id, instance);
951
+ const body = dom.body();
952
+ let target = domNode;
953
+ if (body !== null) {
954
+ const portalContainer = dom.createElement("div");
955
+ dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
956
+ for (const child of dom.childNodes(domNode)) {
957
+ dom.appendChild(portalContainer, child);
958
+ }
959
+ dom.appendChild(body, portalContainer);
960
+ instance.trackCleanup(() => dom.removeChild(body, portalContainer));
961
+ target = portalContainer;
962
+ }
963
+ hydrateChildren(ctx, graphNode, instance, target, path);
964
+ wireOverlayBehavior(ctx, graphNode, instance, target);
965
+ return instance;
966
+ }
867
967
  default: {
868
968
  const instance = new NodeInstance(graphNode, domNode);
869
969
  ctx.instances.set(graphNode.id, instance);
@@ -1006,7 +1106,7 @@ var StreetRendererImpl = class {
1006
1106
  _dom;
1007
1107
  _hydrationDiagnostics;
1008
1108
  constructor(options = {}) {
1009
- this._dom = options.domAdapter ?? new import_dom.BrowserDOMAdapter();
1109
+ this._dom = options.domAdapter ?? new import_dom2.BrowserDOMAdapter();
1010
1110
  if (options.hydrationDiagnostics !== void 0) {
1011
1111
  this._hydrationDiagnostics = options.hydrationDiagnostics;
1012
1112
  }
@@ -1079,11 +1179,64 @@ function readState(dom, root) {
1079
1179
  }
1080
1180
 
1081
1181
  // src/ssr.ts
1082
- var import_dom2 = require("@streetui/dom");
1182
+ var import_dom4 = require("@streetui/dom");
1183
+
1184
+ // src/static-ssr-plan.ts
1185
+ var import_diagnostics = require("@streetui/compiler/diagnostics");
1186
+ var import_dom3 = require("@streetui/dom");
1187
+ function collectMaximalStaticRoots(graph) {
1188
+ const analysis = (0, import_diagnostics.analyzeGraph)(graph);
1189
+ const roots = [];
1190
+ const walk = (node) => {
1191
+ if (node.type !== "application") {
1192
+ const a = analysis.nodes.get(node.id);
1193
+ if (a !== void 0 && a.isStaticSubtree) {
1194
+ roots.push(node);
1195
+ return;
1196
+ }
1197
+ }
1198
+ for (const child of node.children) walk(child);
1199
+ };
1200
+ walk(graph.root);
1201
+ return roots;
1202
+ }
1203
+ function serializeStaticSubtree(dom, graph, root) {
1204
+ const container = dom.createElement("div");
1205
+ const ctx = createRenderContext(dom, graph, container);
1206
+ const instance = mountNode(ctx, root, container);
1207
+ const html = dom.serializeInner(container);
1208
+ instance.dispose();
1209
+ ctx.instances.clear();
1210
+ return html;
1211
+ }
1212
+ function buildStaticSSRPlan(compiled) {
1213
+ const graph = compiled.graph;
1214
+ const roots = collectMaximalStaticRoots(graph);
1215
+ const plan = /* @__PURE__ */ new Map();
1216
+ if (roots.length === 0) return plan;
1217
+ const dom = new import_dom3.ServerDOMAdapter();
1218
+ for (const root of roots) {
1219
+ plan.set(root.id, serializeStaticSubtree(dom, graph, root));
1220
+ }
1221
+ return plan;
1222
+ }
1223
+ var PLAN_CACHE = /* @__PURE__ */ new WeakMap();
1224
+ function getStaticSSRPlan(compiled) {
1225
+ let plan = PLAN_CACHE.get(compiled);
1226
+ if (plan === void 0) {
1227
+ plan = buildStaticSSRPlan(compiled);
1228
+ PLAN_CACHE.set(compiled, plan);
1229
+ }
1230
+ return plan;
1231
+ }
1232
+
1233
+ // src/ssr.ts
1083
1234
  function renderToString(compiled, options = {}) {
1084
- const dom = options.domAdapter ?? new import_dom2.ServerDOMAdapter();
1235
+ const dom = options.domAdapter ?? new import_dom4.ServerDOMAdapter();
1236
+ const plan = options.staticPlan === null ? void 0 : options.staticPlan ?? getStaticSSRPlan(compiled);
1237
+ const staticHTML = plan !== void 0 && plan.size > 0 ? plan : void 0;
1085
1238
  const container = dom.createElement("div");
1086
- const ctx = createRenderContext(dom, compiled.graph, container);
1239
+ const ctx = createRenderContext(dom, compiled.graph, container, void 0, staticHTML);
1087
1240
  const rootInstance = mountGraph(ctx);
1088
1241
  const html = dom.serializeInner(container);
1089
1242
  rootInstance.dispose();
@@ -1119,6 +1272,7 @@ function renderToString(compiled, options = {}) {
1119
1272
  serializeState,
1120
1273
  textUpdate,
1121
1274
  wireEvents,
1275
+ wireOverlayBehavior,
1122
1276
  wireReactiveList,
1123
1277
  wireSignalBindings
1124
1278
  });