@streetui/renderer 1.6.1 → 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,6 +47,7 @@ __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
  });
@@ -192,6 +193,9 @@ function wireEvents(dom, graph, node, element, instance) {
192
193
  }
193
194
  }
194
195
 
196
+ // src/mount.ts
197
+ var import_dom = require("@streetui/dom");
198
+
195
199
  // src/tag-map.ts
196
200
  var TAG_MAP = {
197
201
  application: "div",
@@ -210,7 +214,12 @@ var TAG_MAP = {
210
214
  component: "div",
211
215
  slot: "div",
212
216
  fragment: "div",
213
- "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"
214
223
  };
215
224
  function resolveTag(type) {
216
225
  return TAG_MAP[type] ?? "div";
@@ -622,6 +631,28 @@ function mountNode(ctx, graphNode, parentDom) {
622
631
  wireReactiveList(ctx, graphNode, instance2, el2);
623
632
  return instance2;
624
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
+ }
625
656
  const tag = resolveTag(graphNode.type);
626
657
  const el = dom.createElement(tag);
627
658
  applyNodeProps(ctx, graphNode, el);
@@ -771,9 +802,47 @@ function forgetInstance(ctx, instance) {
771
802
  ctx.instances.delete(instance.graphNode.id);
772
803
  for (const child of instance.children) forgetInstance(ctx, child);
773
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
+ }
774
843
 
775
844
  // src/renderer.ts
776
- var import_dom = require("@streetui/dom");
845
+ var import_dom2 = require("@streetui/dom");
777
846
 
778
847
  // src/hydration-diagnostics.ts
779
848
  function formatHydrationDiagnostic(d) {
@@ -876,6 +945,25 @@ function hydrateNode(ctx, graphNode, domNode, path) {
876
945
  wireReactiveList(ctx, graphNode, instance, domNode);
877
946
  return instance;
878
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
+ }
879
967
  default: {
880
968
  const instance = new NodeInstance(graphNode, domNode);
881
969
  ctx.instances.set(graphNode.id, instance);
@@ -1018,7 +1106,7 @@ var StreetRendererImpl = class {
1018
1106
  _dom;
1019
1107
  _hydrationDiagnostics;
1020
1108
  constructor(options = {}) {
1021
- this._dom = options.domAdapter ?? new import_dom.BrowserDOMAdapter();
1109
+ this._dom = options.domAdapter ?? new import_dom2.BrowserDOMAdapter();
1022
1110
  if (options.hydrationDiagnostics !== void 0) {
1023
1111
  this._hydrationDiagnostics = options.hydrationDiagnostics;
1024
1112
  }
@@ -1091,11 +1179,11 @@ function readState(dom, root) {
1091
1179
  }
1092
1180
 
1093
1181
  // src/ssr.ts
1094
- var import_dom3 = require("@streetui/dom");
1182
+ var import_dom4 = require("@streetui/dom");
1095
1183
 
1096
1184
  // src/static-ssr-plan.ts
1097
1185
  var import_diagnostics = require("@streetui/compiler/diagnostics");
1098
- var import_dom2 = require("@streetui/dom");
1186
+ var import_dom3 = require("@streetui/dom");
1099
1187
  function collectMaximalStaticRoots(graph) {
1100
1188
  const analysis = (0, import_diagnostics.analyzeGraph)(graph);
1101
1189
  const roots = [];
@@ -1126,7 +1214,7 @@ function buildStaticSSRPlan(compiled) {
1126
1214
  const roots = collectMaximalStaticRoots(graph);
1127
1215
  const plan = /* @__PURE__ */ new Map();
1128
1216
  if (roots.length === 0) return plan;
1129
- const dom = new import_dom2.ServerDOMAdapter();
1217
+ const dom = new import_dom3.ServerDOMAdapter();
1130
1218
  for (const root of roots) {
1131
1219
  plan.set(root.id, serializeStaticSubtree(dom, graph, root));
1132
1220
  }
@@ -1144,7 +1232,7 @@ function getStaticSSRPlan(compiled) {
1144
1232
 
1145
1233
  // src/ssr.ts
1146
1234
  function renderToString(compiled, options = {}) {
1147
- const dom = options.domAdapter ?? new import_dom3.ServerDOMAdapter();
1235
+ const dom = options.domAdapter ?? new import_dom4.ServerDOMAdapter();
1148
1236
  const plan = options.staticPlan === null ? void 0 : options.staticPlan ?? getStaticSSRPlan(compiled);
1149
1237
  const staticHTML = plan !== void 0 && plan.size > 0 ? plan : void 0;
1150
1238
  const container = dom.createElement("div");
@@ -1184,6 +1272,7 @@ function renderToString(compiled, options = {}) {
1184
1272
  serializeState,
1185
1273
  textUpdate,
1186
1274
  wireEvents,
1275
+ wireOverlayBehavior,
1187
1276
  wireReactiveList,
1188
1277
  wireSignalBindings
1189
1278
  });