streetui 1.9.0 → 2.0.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/bin.cjs +1 -1
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/create-bin.cjs +1 -1
- package/dist/create-bin.cjs.map +1 -1
- package/dist/create-bin.js +1 -1
- package/dist/create-bin.js.map +1 -1
- package/dist/{compile-DqsMQVJk.d.cts → hydration-diagnostics-CFYDXBlm.d.cts} +353 -3
- package/dist/{compile-DqsMQVJk.d.ts → hydration-diagnostics-CFYDXBlm.d.ts} +353 -3
- package/dist/index.cjs +865 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +355 -147
- package/dist/index.d.ts +355 -147
- package/dist/index.js +851 -8
- package/dist/index.js.map +1 -1
- package/dist/renderer-interface-BUhHV7ja.d.cts +26 -0
- package/dist/renderer-interface-B_-hWnKk.d.ts +26 -0
- package/dist/{server-BYnmb_hR.d.ts → server-C0PvFBzD.d.ts} +181 -2
- package/dist/{server-DnwqBTfE.d.cts → server-DnAOFpMP.d.cts} +181 -2
- package/dist/server.cjs +217 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +216 -3
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +368 -6
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +368 -6
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/hydration-diagnostics-4pwt6v7b.d.cts +0 -89
- package/dist/hydration-diagnostics-B2FmY05M.d.ts +0 -89
package/dist/testing.cjs
CHANGED
|
@@ -315,6 +315,9 @@ var BrowserDOMAdapter = class {
|
|
|
315
315
|
body() {
|
|
316
316
|
return document.body ?? null;
|
|
317
317
|
}
|
|
318
|
+
head() {
|
|
319
|
+
return document.head ?? null;
|
|
320
|
+
}
|
|
318
321
|
activeElement() {
|
|
319
322
|
return document.activeElement ?? null;
|
|
320
323
|
}
|
|
@@ -693,6 +696,9 @@ var ServerDOMAdapter = class {
|
|
|
693
696
|
body() {
|
|
694
697
|
return null;
|
|
695
698
|
}
|
|
699
|
+
head() {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
696
702
|
activeElement() {
|
|
697
703
|
return null;
|
|
698
704
|
}
|
|
@@ -1023,7 +1029,13 @@ var TAG_MAP = {
|
|
|
1023
1029
|
// its children are relocated to a document.body container on the browser
|
|
1024
1030
|
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
1025
1031
|
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
1026
|
-
portal: "div"
|
|
1032
|
+
portal: "div",
|
|
1033
|
+
// A `head()` node renders as a neutral, empty inline anchor <div> at its
|
|
1034
|
+
// declaration site (like a portal anchor). Its actual contribution — title/
|
|
1035
|
+
// meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
|
|
1036
|
+
// browser, and emitted separately by `renderHead()` on the server. Keeping a
|
|
1037
|
+
// one-node/one-element anchor preserves positional hydration.
|
|
1038
|
+
head: "div"
|
|
1027
1039
|
};
|
|
1028
1040
|
function resolveTag(type) {
|
|
1029
1041
|
return TAG_MAP[type] ?? "div";
|
|
@@ -1468,6 +1480,158 @@ var TransitionController = class {
|
|
|
1468
1480
|
}
|
|
1469
1481
|
};
|
|
1470
1482
|
|
|
1483
|
+
// ../renderer/src/head.ts
|
|
1484
|
+
var HEAD_MARKER = "data-streetui-head";
|
|
1485
|
+
var HEAD_KEY = "data-streetui-head-key";
|
|
1486
|
+
function isSignalLike(v) {
|
|
1487
|
+
return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
|
|
1488
|
+
}
|
|
1489
|
+
function readValue(v) {
|
|
1490
|
+
if (isSignalLike(v)) return String(v.peek() ?? "");
|
|
1491
|
+
return String(v ?? "");
|
|
1492
|
+
}
|
|
1493
|
+
function resolveEntry(entry) {
|
|
1494
|
+
const attrs = {};
|
|
1495
|
+
for (const key of Object.keys(entry.attrs)) {
|
|
1496
|
+
attrs[key] = readValue(entry.attrs[key]);
|
|
1497
|
+
}
|
|
1498
|
+
const resolved = { tag: entry.tag, attrs };
|
|
1499
|
+
if (entry.tag === "title") {
|
|
1500
|
+
return { ...resolved, text: readValue(entry.text) };
|
|
1501
|
+
}
|
|
1502
|
+
return resolved;
|
|
1503
|
+
}
|
|
1504
|
+
var HeadManager = class {
|
|
1505
|
+
_dom;
|
|
1506
|
+
_head;
|
|
1507
|
+
_contributions = /* @__PURE__ */ new Map();
|
|
1508
|
+
_applied = /* @__PURE__ */ new Map();
|
|
1509
|
+
_order = 0;
|
|
1510
|
+
_adopted = false;
|
|
1511
|
+
constructor(dom, head) {
|
|
1512
|
+
this._dom = dom;
|
|
1513
|
+
this._head = head;
|
|
1514
|
+
}
|
|
1515
|
+
/** Register (or replace) a node's contribution and re-apply the merged result. */
|
|
1516
|
+
register(nodeId, entries) {
|
|
1517
|
+
this._contributions.set(nodeId, { order: this._order++, entries });
|
|
1518
|
+
this.apply();
|
|
1519
|
+
}
|
|
1520
|
+
/** Withdraw a node's contribution (unmount / route change) and re-apply. */
|
|
1521
|
+
unregister(nodeId) {
|
|
1522
|
+
if (this._contributions.delete(nodeId)) this.apply();
|
|
1523
|
+
}
|
|
1524
|
+
/** Recompute the merged head and patch `document.head` to match. */
|
|
1525
|
+
apply() {
|
|
1526
|
+
if (!this._adopted) {
|
|
1527
|
+
this._adoptServerTags();
|
|
1528
|
+
this._adopted = true;
|
|
1529
|
+
}
|
|
1530
|
+
const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
|
|
1531
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1532
|
+
for (const contribution of ordered) {
|
|
1533
|
+
for (const entry of contribution.entries) {
|
|
1534
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
for (const [key, desired] of merged) {
|
|
1538
|
+
const existing = this._applied.get(key);
|
|
1539
|
+
if (existing !== void 0 && existing.tag === desired.tag) {
|
|
1540
|
+
this._reconcileAttrs(existing, desired);
|
|
1541
|
+
} else {
|
|
1542
|
+
if (existing !== void 0) {
|
|
1543
|
+
this._dom.removeChild(this._head, existing.el);
|
|
1544
|
+
this._applied.delete(key);
|
|
1545
|
+
}
|
|
1546
|
+
const el = this._createTag(key, desired);
|
|
1547
|
+
this._dom.appendChild(this._head, el);
|
|
1548
|
+
this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
for (const [key, record] of [...this._applied]) {
|
|
1552
|
+
if (!merged.has(key)) {
|
|
1553
|
+
this._dom.removeChild(this._head, record.el);
|
|
1554
|
+
this._applied.delete(key);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
_createTag(key, desired) {
|
|
1559
|
+
const el = this._dom.createElement(desired.tag);
|
|
1560
|
+
this._dom.setAttribute(el, HEAD_MARKER, "");
|
|
1561
|
+
this._dom.setAttribute(el, HEAD_KEY, key);
|
|
1562
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
1563
|
+
this._dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
1564
|
+
}
|
|
1565
|
+
if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
|
|
1566
|
+
return el;
|
|
1567
|
+
}
|
|
1568
|
+
_reconcileAttrs(record, desired) {
|
|
1569
|
+
const nextKeys = new Set(Object.keys(desired.attrs));
|
|
1570
|
+
for (const attr of record.attrKeys) {
|
|
1571
|
+
if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
|
|
1572
|
+
}
|
|
1573
|
+
for (const attr of nextKeys) {
|
|
1574
|
+
this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
|
|
1575
|
+
}
|
|
1576
|
+
if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
|
|
1577
|
+
record.attrKeys = nextKeys;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
|
|
1581
|
+
* in `document.head`. The subsequent diff reuses these elements when the
|
|
1582
|
+
* client desires the same key (no duplicate), rewrites them if the value
|
|
1583
|
+
* changed, or removes them if the client graph no longer wants them.
|
|
1584
|
+
*/
|
|
1585
|
+
_adoptServerTags() {
|
|
1586
|
+
for (const child of this._dom.childNodes(this._head)) {
|
|
1587
|
+
if (!this._dom.isElement(child)) continue;
|
|
1588
|
+
const el = child;
|
|
1589
|
+
const key = this._dom.getAttribute(el, HEAD_KEY);
|
|
1590
|
+
if (key === null) continue;
|
|
1591
|
+
this._applied.set(key, {
|
|
1592
|
+
el,
|
|
1593
|
+
attrKeys: new Set(this._attrNames(el)),
|
|
1594
|
+
tag: this._dom.tagName(el)
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
/** The framework-managed attribute names currently on a server tag. */
|
|
1599
|
+
_attrNames(el) {
|
|
1600
|
+
const names = [];
|
|
1601
|
+
if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
|
|
1602
|
+
if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
|
|
1603
|
+
return names;
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
function getHeadManager(ctx) {
|
|
1607
|
+
const head = ctx.dom.head();
|
|
1608
|
+
if (head === null) return null;
|
|
1609
|
+
const mutable = ctx;
|
|
1610
|
+
if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
|
|
1611
|
+
return mutable.head;
|
|
1612
|
+
}
|
|
1613
|
+
function wireHeadBehavior(ctx, graphNode, instance) {
|
|
1614
|
+
const manager = getHeadManager(ctx);
|
|
1615
|
+
if (manager === null) return;
|
|
1616
|
+
const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
|
|
1617
|
+
if (descFn === void 0) return;
|
|
1618
|
+
const contribution = descFn();
|
|
1619
|
+
const nodeId = graphNode.id;
|
|
1620
|
+
manager.register(nodeId, contribution.entries);
|
|
1621
|
+
for (const entry of contribution.entries) {
|
|
1622
|
+
for (const attrKey of Object.keys(entry.attrs)) {
|
|
1623
|
+
const v = entry.attrs[attrKey];
|
|
1624
|
+
if (isSignalLike(v)) {
|
|
1625
|
+
instance.trackCleanup(v.subscribe(() => manager.apply()));
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (isSignalLike(entry.text)) {
|
|
1629
|
+
instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
instance.trackCleanup(() => manager.unregister(nodeId));
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1471
1635
|
// ../renderer/src/mount.ts
|
|
1472
1636
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
1473
1637
|
"text",
|
|
@@ -1590,6 +1754,9 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1590
1754
|
const instance2 = new NodeInstance(graphNode, el2);
|
|
1591
1755
|
ctx.instances.set(graphNode.id, instance2);
|
|
1592
1756
|
wireEvents(dom, graph, graphNode, el2, instance2);
|
|
1757
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
1758
|
+
wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
|
|
1759
|
+
}
|
|
1593
1760
|
dom.appendChild(parentDom, el2);
|
|
1594
1761
|
return instance2;
|
|
1595
1762
|
}
|
|
@@ -1645,6 +1812,16 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1645
1812
|
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
1646
1813
|
return instance2;
|
|
1647
1814
|
}
|
|
1815
|
+
if (graphNode.type === "head") {
|
|
1816
|
+
const anchor = dom.createElement(resolveTag("head"));
|
|
1817
|
+
dom.setAttribute(anchor, "data-streetui-head-anchor", "");
|
|
1818
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
1819
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
1820
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
1821
|
+
dom.appendChild(parentDom, anchor);
|
|
1822
|
+
wireHeadBehavior(ctx, graphNode, instance2);
|
|
1823
|
+
return instance2;
|
|
1824
|
+
}
|
|
1648
1825
|
const tag = resolveTag(graphNode.type);
|
|
1649
1826
|
const el = dom.createElement(tag);
|
|
1650
1827
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -1709,6 +1886,17 @@ function buttonUpdate(dom, el) {
|
|
|
1709
1886
|
}
|
|
1710
1887
|
};
|
|
1711
1888
|
}
|
|
1889
|
+
function linkUpdate(dom, el) {
|
|
1890
|
+
return (propKey, value) => {
|
|
1891
|
+
if (propKey === "label") {
|
|
1892
|
+
dom.setTextContent(el, String(value ?? ""));
|
|
1893
|
+
} else if (propKey === "href") {
|
|
1894
|
+
dom.setAttribute(el, "href", String(value ?? ""));
|
|
1895
|
+
} else {
|
|
1896
|
+
applyProp(dom, el, propKey, value);
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1712
1900
|
function applyNodeProps(ctx, graphNode, el) {
|
|
1713
1901
|
const props = graphNode.props;
|
|
1714
1902
|
for (const key in props) {
|
|
@@ -1936,7 +2124,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1936
2124
|
case "link": {
|
|
1937
2125
|
const instance = new NodeInstance(graphNode, domNode);
|
|
1938
2126
|
ctx.instances.set(graphNode.id, instance);
|
|
1939
|
-
if (graphNode.type === "link")
|
|
2127
|
+
if (graphNode.type === "link") {
|
|
2128
|
+
wireEvents(dom, graph, graphNode, domNode, instance);
|
|
2129
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
2130
|
+
wireSignalBindings(ctx, graphNode, instance, linkUpdate(dom, domNode));
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
1940
2133
|
return instance;
|
|
1941
2134
|
}
|
|
1942
2135
|
case "reactive-list":
|
|
@@ -1966,6 +2159,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1966
2159
|
wireOverlayBehavior(ctx, graphNode, instance, target);
|
|
1967
2160
|
return instance;
|
|
1968
2161
|
}
|
|
2162
|
+
case "head": {
|
|
2163
|
+
const instance = new NodeInstance(graphNode, domNode);
|
|
2164
|
+
ctx.instances.set(graphNode.id, instance);
|
|
2165
|
+
wireHeadBehavior(ctx, graphNode, instance);
|
|
2166
|
+
return instance;
|
|
2167
|
+
}
|
|
1969
2168
|
default: {
|
|
1970
2169
|
const instance = new NodeInstance(graphNode, domNode);
|
|
1971
2170
|
ctx.instances.set(graphNode.id, instance);
|
|
@@ -2176,7 +2375,8 @@ function analyzeGraph(graph) {
|
|
|
2176
2375
|
const isConditional = node.type === "conditional";
|
|
2177
2376
|
const isPortal = node.type === "portal";
|
|
2178
2377
|
const isComponent = node.type === "component";
|
|
2179
|
-
const
|
|
2378
|
+
const isHead = node.type === "head";
|
|
2379
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
|
|
2180
2380
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
2181
2381
|
nodes.set(node.id, {
|
|
2182
2382
|
isStatic,
|
|
@@ -2668,6 +2868,118 @@ function resolveTransition(config) {
|
|
|
2668
2868
|
};
|
|
2669
2869
|
}
|
|
2670
2870
|
|
|
2871
|
+
// ../dsl/src/head.ts
|
|
2872
|
+
function metaDedupKey(m) {
|
|
2873
|
+
if (m.charset !== void 0) return "meta:charset";
|
|
2874
|
+
if (m.name !== void 0) return `meta:name=${m.name}`;
|
|
2875
|
+
if (m.property !== void 0) return `meta:property=${m.property}`;
|
|
2876
|
+
if (m.httpEquiv !== void 0) return `meta:http-equiv=${m.httpEquiv}`;
|
|
2877
|
+
return void 0;
|
|
2878
|
+
}
|
|
2879
|
+
function metaAttrs(m) {
|
|
2880
|
+
const attrs = {};
|
|
2881
|
+
if (m.charset !== void 0) attrs["charset"] = m.charset;
|
|
2882
|
+
if (m.name !== void 0) attrs["name"] = m.name;
|
|
2883
|
+
if (m.property !== void 0) attrs["property"] = m.property;
|
|
2884
|
+
if (m.httpEquiv !== void 0) attrs["http-equiv"] = m.httpEquiv;
|
|
2885
|
+
if (m.content !== void 0) attrs["content"] = m.content;
|
|
2886
|
+
return attrs;
|
|
2887
|
+
}
|
|
2888
|
+
function linkAttrs(l) {
|
|
2889
|
+
const attrs = { rel: l.rel, href: l.href };
|
|
2890
|
+
if (l.sizes !== void 0) attrs["sizes"] = l.sizes;
|
|
2891
|
+
if (l.type !== void 0) attrs["type"] = l.type;
|
|
2892
|
+
if (l.media !== void 0) attrs["media"] = l.media;
|
|
2893
|
+
if (l.as !== void 0) attrs["as"] = l.as;
|
|
2894
|
+
if (l.crossorigin !== void 0) attrs["crossorigin"] = l.crossorigin;
|
|
2895
|
+
if (l.hreflang !== void 0) attrs["hreflang"] = l.hreflang;
|
|
2896
|
+
return attrs;
|
|
2897
|
+
}
|
|
2898
|
+
function resolveHead(config) {
|
|
2899
|
+
const entries = [];
|
|
2900
|
+
if (config.charset !== void 0) {
|
|
2901
|
+
entries.push({ tag: "meta", dedupKey: "meta:charset", attrs: { charset: config.charset } });
|
|
2902
|
+
}
|
|
2903
|
+
if (config.base !== void 0) {
|
|
2904
|
+
entries.push({ tag: "base", dedupKey: "base", attrs: { href: config.base } });
|
|
2905
|
+
}
|
|
2906
|
+
if (config.title !== void 0) {
|
|
2907
|
+
entries.push({ tag: "title", dedupKey: "title", attrs: {}, text: config.title });
|
|
2908
|
+
}
|
|
2909
|
+
if (config.description !== void 0) {
|
|
2910
|
+
entries.push({
|
|
2911
|
+
tag: "meta",
|
|
2912
|
+
dedupKey: "meta:name=description",
|
|
2913
|
+
attrs: { name: "description", content: config.description }
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
if (config.canonical !== void 0) {
|
|
2917
|
+
entries.push({
|
|
2918
|
+
tag: "link",
|
|
2919
|
+
dedupKey: "link:rel=canonical",
|
|
2920
|
+
attrs: { rel: "canonical", href: config.canonical }
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
if (config.robots !== void 0) {
|
|
2924
|
+
entries.push({
|
|
2925
|
+
tag: "meta",
|
|
2926
|
+
dedupKey: "meta:name=robots",
|
|
2927
|
+
attrs: { name: "robots", content: config.robots }
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
if (config.themeColor !== void 0) {
|
|
2931
|
+
entries.push({
|
|
2932
|
+
tag: "meta",
|
|
2933
|
+
dedupKey: "meta:name=theme-color",
|
|
2934
|
+
attrs: { name: "theme-color", content: config.themeColor }
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
if (config.viewport !== void 0) {
|
|
2938
|
+
entries.push({
|
|
2939
|
+
tag: "meta",
|
|
2940
|
+
dedupKey: "meta:name=viewport",
|
|
2941
|
+
attrs: { name: "viewport", content: config.viewport }
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
if (config.favicon !== void 0) {
|
|
2945
|
+
const l = typeof config.favicon === "string" ? { rel: "icon", href: config.favicon } : config.favicon;
|
|
2946
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}`, attrs: linkAttrs(l) });
|
|
2947
|
+
}
|
|
2948
|
+
if (config.openGraph !== void 0) {
|
|
2949
|
+
for (const key of Object.keys(config.openGraph)) {
|
|
2950
|
+
const property = `og:${key}`;
|
|
2951
|
+
entries.push({
|
|
2952
|
+
tag: "meta",
|
|
2953
|
+
dedupKey: `meta:property=${property}`,
|
|
2954
|
+
attrs: { property, content: config.openGraph[key] }
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (config.twitter !== void 0) {
|
|
2959
|
+
for (const key of Object.keys(config.twitter)) {
|
|
2960
|
+
const name = `twitter:${key}`;
|
|
2961
|
+
entries.push({
|
|
2962
|
+
tag: "meta",
|
|
2963
|
+
dedupKey: `meta:name=${name}`,
|
|
2964
|
+
attrs: { name, content: config.twitter[key] }
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
if (config.meta !== void 0) {
|
|
2969
|
+
for (const m of config.meta) {
|
|
2970
|
+
const key = metaDedupKey(m);
|
|
2971
|
+
if (key === void 0) continue;
|
|
2972
|
+
entries.push({ tag: "meta", dedupKey: key, attrs: metaAttrs(m) });
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
if (config.link !== void 0) {
|
|
2976
|
+
for (const l of config.link) {
|
|
2977
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}:href=${l.href}`, attrs: linkAttrs(l) });
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return { entries };
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2671
2983
|
// ../state/src/signal.ts
|
|
2672
2984
|
var _activeConsumer = null;
|
|
2673
2985
|
function withConsumer(consumer, fn) {
|
|
@@ -3231,8 +3543,14 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
3231
3543
|
this.container(id, (c) => {
|
|
3232
3544
|
c.when(
|
|
3233
3545
|
hasError,
|
|
3234
|
-
// Error state → fallback.
|
|
3235
|
-
|
|
3546
|
+
// Error state → fallback. The fallback branch mounts exactly when the
|
|
3547
|
+
// boundary enters its error state, so this is also where the optional
|
|
3548
|
+
// `onError` reporting hook fires (observe-only; §6/§7).
|
|
3549
|
+
(fb) => {
|
|
3550
|
+
const currentError = readError();
|
|
3551
|
+
options.onError?.(currentError);
|
|
3552
|
+
options.fallback(fb, currentError, retry);
|
|
3553
|
+
},
|
|
3236
3554
|
// Healthy state → body, guarded against synchronous build throws.
|
|
3237
3555
|
(body) => {
|
|
3238
3556
|
try {
|
|
@@ -3244,6 +3562,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
3244
3562
|
);
|
|
3245
3563
|
}, { id });
|
|
3246
3564
|
}
|
|
3565
|
+
asyncBoundary(key, res, branches) {
|
|
3566
|
+
const isError = derived(() => res.status.get() === "error");
|
|
3567
|
+
const showSuccess = derived(
|
|
3568
|
+
() => res.status.get() !== "error" && res.data.get() !== void 0
|
|
3569
|
+
);
|
|
3570
|
+
const showLoading = derived(
|
|
3571
|
+
() => res.status.get() !== "error" && res.data.get() === void 0
|
|
3572
|
+
);
|
|
3573
|
+
const dataSignal = derived(() => res.data.get());
|
|
3574
|
+
const retry = () => {
|
|
3575
|
+
void res.refetch();
|
|
3576
|
+
};
|
|
3577
|
+
this.container(key, (c) => {
|
|
3578
|
+
c.when(isError, (fb) => branches.error?.(fb, res.error.peek(), retry));
|
|
3579
|
+
c.when(showSuccess, (sb) => branches.success(sb, dataSignal));
|
|
3580
|
+
c.when(showLoading, (lb) => branches.loading?.(lb));
|
|
3581
|
+
}, { key });
|
|
3582
|
+
}
|
|
3247
3583
|
// ── Portals & overlays ──────────────────────────────────────────────────────
|
|
3248
3584
|
portal(key, builder, options = {}) {
|
|
3249
3585
|
const node = this._graph.createNode("portal", {
|
|
@@ -3253,6 +3589,31 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
3253
3589
|
});
|
|
3254
3590
|
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3255
3591
|
}
|
|
3592
|
+
/**
|
|
3593
|
+
* Declare document metadata (2.0 §1–§3). Creates a `'head'` node — a neutral,
|
|
3594
|
+
* empty inline anchor at this position (one node / one element, so positional
|
|
3595
|
+
* hydration is preserved) — and registers a `__head__<id>` descriptor holding
|
|
3596
|
+
* this call's normalized, dedup-keyed {@link resolveHead} contribution. The
|
|
3597
|
+
* renderer applies it to `document.head` on the browser (adopting server tags
|
|
3598
|
+
* on hydration, cleaning up on unmount / route change) and emits the active
|
|
3599
|
+
* graph's merged metadata as a string on the server (`renderHead`).
|
|
3600
|
+
*
|
|
3601
|
+
* Multiple `head()` nodes may be live at once (app default + route + component)
|
|
3602
|
+
* — the renderer merges them and, per dedup key, the last in document order
|
|
3603
|
+
* wins (see head.ts). No new render path: this reuses the same graph-node +
|
|
3604
|
+
* handler-registry convention as overlays/transitions/components.
|
|
3605
|
+
*/
|
|
3606
|
+
head(metadata) {
|
|
3607
|
+
const node = this._graph.createNode("head", {
|
|
3608
|
+
parent: this._node,
|
|
3609
|
+
props: { "data-streetui-head-anchor": "" }
|
|
3610
|
+
});
|
|
3611
|
+
const contribution = resolveHead(metadata);
|
|
3612
|
+
this._graph.registerHandler(
|
|
3613
|
+
`__head__${node.id}`,
|
|
3614
|
+
() => contribution
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3256
3617
|
/**
|
|
3257
3618
|
* Shared assembly for every overlay kind: a `portal` node whose single child
|
|
3258
3619
|
* is a `when(open, panel)` conditional. The panel container carries the
|
|
@@ -3583,6 +3944,7 @@ var ApplicationGraph = class {
|
|
|
3583
3944
|
this.handlers.delete(`__overlay__${node.id}`);
|
|
3584
3945
|
this.handlers.delete(`__component__${node.id}`);
|
|
3585
3946
|
this.handlers.delete(`__transition__${node.id}`);
|
|
3947
|
+
this.handlers.delete(`__head__${node.id}`);
|
|
3586
3948
|
}
|
|
3587
3949
|
// ── Handler registry ──────────────────────────────────────────────────────
|
|
3588
3950
|
registerHandler(key, fn) {
|
|
@@ -3760,7 +4122,7 @@ function trigger(el, type, init = {}) {
|
|
|
3760
4122
|
}
|
|
3761
4123
|
|
|
3762
4124
|
// src/version.ts
|
|
3763
|
-
var VERSION = "
|
|
4125
|
+
var VERSION = "2.0.0";
|
|
3764
4126
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3765
4127
|
0 && (module.exports = {
|
|
3766
4128
|
VERSION,
|