@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 +96 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +96 -1
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,20 @@ declare function wireSignalBindings(ctx: RenderContext, graphNode: GraphNode, in
|
|
|
178
178
|
* eager build factory (it only ever renders 0..1 branch, so eager is fine).
|
|
179
179
|
*/
|
|
180
180
|
declare function wireReactiveList(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, el: Element): void;
|
|
181
|
+
/**
|
|
182
|
+
* Attach overlay focus/keyboard behavior to a mounted portal. Server-safe: on
|
|
183
|
+
* the server `dom.body()` is null so this returns immediately (SSR emits inert
|
|
184
|
+
* markup, no focus concept). A plain portal has no `__overlay__` descriptor, so
|
|
185
|
+
* this also returns immediately — the behavior is purely additive.
|
|
186
|
+
*
|
|
187
|
+
* The panel is mounted/unmounted by the portal's inner `when(open, …)`, whose
|
|
188
|
+
* signal subscription is registered *before* this one (the conditional child is
|
|
189
|
+
* mounted earlier in the portal branch). Signal subscribers fire synchronously
|
|
190
|
+
* in subscription order, so on open→true the panel DOM exists before we move
|
|
191
|
+
* focus into it, and on open→false the panel is torn down before we restore
|
|
192
|
+
* focus. All listeners are tracked on the instance and torn down on unmount.
|
|
193
|
+
*/
|
|
194
|
+
declare function wireOverlayBehavior(ctx: RenderContext, graphNode: GraphNode, instance: NodeInstance, target: Element): void;
|
|
181
195
|
|
|
182
196
|
/**
|
|
183
197
|
* Patch — targeted DOM updates driven by signal changes.
|
|
@@ -431,4 +445,4 @@ declare function renderToString(compiled: CompiledApplication, options?: RenderT
|
|
|
431
445
|
|
|
432
446
|
declare function resolveTag(type: SemanticNodeType): string;
|
|
433
447
|
|
|
434
|
-
export { type HydrationDiagnostic, type HydrationDiagnosticSink, type HydrationMismatchType, type MountFn, NodeInstance, type PlanEntry, type ReconcileResult, type RenderContext, type RenderToStringOptions, STATE_MARKER_ATTR, StreetRenderHandle, StreetRendererImpl, type StreetRendererOptions, applyNodeProps, applyProp, buttonUpdate, consoleHydrationDiagnosticSink, createHydrationDiagnosticCollector, createRenderContext, createRenderer, formatHydrationDiagnostic, headingUpdate, hydrateGraph, inputUpdate, mountGraph, mountNode, patchNode, patchProp, readState, reconcileChildren, reconcileChildrenByPlan, renderToString, resolveTag, serializeState, textUpdate, wireEvents, wireReactiveList, wireSignalBindings };
|
|
448
|
+
export { type HydrationDiagnostic, type HydrationDiagnosticSink, type HydrationMismatchType, type MountFn, NodeInstance, type PlanEntry, type ReconcileResult, type RenderContext, type RenderToStringOptions, STATE_MARKER_ATTR, StreetRenderHandle, StreetRendererImpl, type StreetRendererOptions, applyNodeProps, applyProp, buttonUpdate, consoleHydrationDiagnosticSink, createHydrationDiagnosticCollector, createRenderContext, createRenderer, formatHydrationDiagnostic, headingUpdate, hydrateGraph, inputUpdate, mountGraph, mountNode, patchNode, patchProp, readState, reconcileChildren, reconcileChildrenByPlan, renderToString, resolveTag, serializeState, textUpdate, wireEvents, wireOverlayBehavior, wireReactiveList, wireSignalBindings };
|
package/dist/index.js
CHANGED
|
@@ -138,6 +138,16 @@ function wireEvents(dom, graph, node, element, instance) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// src/mount.ts
|
|
142
|
+
import {
|
|
143
|
+
focusInitial,
|
|
144
|
+
trapFocus,
|
|
145
|
+
containFocus,
|
|
146
|
+
onEscape,
|
|
147
|
+
saveFocus,
|
|
148
|
+
restoreFocus
|
|
149
|
+
} from "@streetui/dom";
|
|
150
|
+
|
|
141
151
|
// src/tag-map.ts
|
|
142
152
|
var TAG_MAP = {
|
|
143
153
|
application: "div",
|
|
@@ -156,7 +166,12 @@ var TAG_MAP = {
|
|
|
156
166
|
component: "div",
|
|
157
167
|
slot: "div",
|
|
158
168
|
fragment: "div",
|
|
159
|
-
"reactive-list": "ul"
|
|
169
|
+
"reactive-list": "ul",
|
|
170
|
+
// A portal renders as a neutral inline anchor <div> at its declaration site;
|
|
171
|
+
// its children are relocated to a document.body container on the browser
|
|
172
|
+
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
173
|
+
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
174
|
+
portal: "div"
|
|
160
175
|
};
|
|
161
176
|
function resolveTag(type) {
|
|
162
177
|
return TAG_MAP[type] ?? "div";
|
|
@@ -568,6 +583,28 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
568
583
|
wireReactiveList(ctx, graphNode, instance2, el2);
|
|
569
584
|
return instance2;
|
|
570
585
|
}
|
|
586
|
+
if (graphNode.type === "portal") {
|
|
587
|
+
const anchor = dom.createElement(resolveTag("portal"));
|
|
588
|
+
dom.setAttribute(anchor, "data-streetui-portal", "");
|
|
589
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
590
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
591
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
592
|
+
const body = dom.body();
|
|
593
|
+
let target = anchor;
|
|
594
|
+
if (body !== null) {
|
|
595
|
+
const portalContainer = dom.createElement("div");
|
|
596
|
+
dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
|
|
597
|
+
dom.appendChild(body, portalContainer);
|
|
598
|
+
instance2.trackCleanup(() => dom.removeChild(body, portalContainer));
|
|
599
|
+
target = portalContainer;
|
|
600
|
+
}
|
|
601
|
+
for (const child of graphNode.children) {
|
|
602
|
+
instance2.addChild(mountNode(ctx, child, target));
|
|
603
|
+
}
|
|
604
|
+
dom.appendChild(parentDom, anchor);
|
|
605
|
+
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
606
|
+
return instance2;
|
|
607
|
+
}
|
|
571
608
|
const tag = resolveTag(graphNode.type);
|
|
572
609
|
const el = dom.createElement(tag);
|
|
573
610
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -717,6 +754,44 @@ function forgetInstance(ctx, instance) {
|
|
|
717
754
|
ctx.instances.delete(instance.graphNode.id);
|
|
718
755
|
for (const child of instance.children) forgetInstance(ctx, child);
|
|
719
756
|
}
|
|
757
|
+
function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
758
|
+
const { dom, graph } = ctx;
|
|
759
|
+
if (dom.body() === null) return;
|
|
760
|
+
const descFn = graph.getHandler(`__overlay__${graphNode.id}`);
|
|
761
|
+
if (descFn === void 0) return;
|
|
762
|
+
const desc = descFn();
|
|
763
|
+
const openSig = desc.open;
|
|
764
|
+
if (openSig === void 0 || typeof openSig.subscribe !== "function") return;
|
|
765
|
+
let active = [];
|
|
766
|
+
let saved = null;
|
|
767
|
+
const teardown = () => {
|
|
768
|
+
for (const fn of active) fn();
|
|
769
|
+
active = [];
|
|
770
|
+
};
|
|
771
|
+
const onOpenChange = (isOpen) => {
|
|
772
|
+
if (isOpen) {
|
|
773
|
+
if (desc.restoreFocus) saved = saveFocus(dom);
|
|
774
|
+
if (desc.takesFocus) focusInitial(dom, target, desc.initialFocusId);
|
|
775
|
+
if (desc.modal) {
|
|
776
|
+
active.push(trapFocus(dom, target));
|
|
777
|
+
active.push(containFocus(dom, target));
|
|
778
|
+
}
|
|
779
|
+
if (desc.closeOnEscape && desc.onClose !== void 0) {
|
|
780
|
+
active.push(onEscape(dom, target, desc.onClose));
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
teardown();
|
|
784
|
+
if (desc.restoreFocus && saved !== null) {
|
|
785
|
+
restoreFocus(dom, saved);
|
|
786
|
+
saved = null;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
const unsub = openSig.subscribe(onOpenChange);
|
|
791
|
+
instance.trackCleanup(unsub);
|
|
792
|
+
instance.trackCleanup(teardown);
|
|
793
|
+
if (openSig.peek() === true) onOpenChange(true);
|
|
794
|
+
}
|
|
720
795
|
|
|
721
796
|
// src/renderer.ts
|
|
722
797
|
import { BrowserDOMAdapter } from "@streetui/dom";
|
|
@@ -822,6 +897,25 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
822
897
|
wireReactiveList(ctx, graphNode, instance, domNode);
|
|
823
898
|
return instance;
|
|
824
899
|
}
|
|
900
|
+
case "portal": {
|
|
901
|
+
const instance = new NodeInstance(graphNode, domNode);
|
|
902
|
+
ctx.instances.set(graphNode.id, instance);
|
|
903
|
+
const body = dom.body();
|
|
904
|
+
let target = domNode;
|
|
905
|
+
if (body !== null) {
|
|
906
|
+
const portalContainer = dom.createElement("div");
|
|
907
|
+
dom.setAttribute(portalContainer, "data-streetui-portal-container", "");
|
|
908
|
+
for (const child of dom.childNodes(domNode)) {
|
|
909
|
+
dom.appendChild(portalContainer, child);
|
|
910
|
+
}
|
|
911
|
+
dom.appendChild(body, portalContainer);
|
|
912
|
+
instance.trackCleanup(() => dom.removeChild(body, portalContainer));
|
|
913
|
+
target = portalContainer;
|
|
914
|
+
}
|
|
915
|
+
hydrateChildren(ctx, graphNode, instance, target, path);
|
|
916
|
+
wireOverlayBehavior(ctx, graphNode, instance, target);
|
|
917
|
+
return instance;
|
|
918
|
+
}
|
|
825
919
|
default: {
|
|
826
920
|
const instance = new NodeInstance(graphNode, domNode);
|
|
827
921
|
ctx.instances.set(graphNode.id, instance);
|
|
@@ -1129,6 +1223,7 @@ export {
|
|
|
1129
1223
|
serializeState,
|
|
1130
1224
|
textUpdate,
|
|
1131
1225
|
wireEvents,
|
|
1226
|
+
wireOverlayBehavior,
|
|
1132
1227
|
wireReactiveList,
|
|
1133
1228
|
wireSignalBindings
|
|
1134
1229
|
};
|