streetui 1.9.0 → 2.1.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-Jm3qUydo.d.cts} +353 -3
- package/dist/{compile-DqsMQVJk.d.ts → hydration-diagnostics-Jm3qUydo.d.ts} +353 -3
- package/dist/index.cjs +873 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +389 -147
- package/dist/index.d.ts +389 -147
- package/dist/index.js +858 -8
- package/dist/index.js.map +1 -1
- package/dist/renderer-interface-CPrfslvr.d.ts +26 -0
- package/dist/renderer-interface-moxwy5PA.d.cts +26 -0
- package/dist/{server-BYnmb_hR.d.ts → server-DG0tvuRt.d.ts} +181 -2
- package/dist/{server-DnwqBTfE.d.cts → server-_kf9p9SV.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/index.cjs
CHANGED
|
@@ -37,6 +37,8 @@ __export(src_exports, {
|
|
|
37
37
|
FOCUSABLE_SELECTOR: () => FOCUSABLE_SELECTOR,
|
|
38
38
|
FormBuilderImpl: () => FormBuilderImpl,
|
|
39
39
|
GraphNode: () => GraphNode,
|
|
40
|
+
HeadManager: () => HeadManager,
|
|
41
|
+
HttpError: () => HttpError,
|
|
40
42
|
Lifecycle: () => Lifecycle,
|
|
41
43
|
ListBuilderImpl: () => ListBuilderImpl,
|
|
42
44
|
NodeInstance: () => NodeInstance,
|
|
@@ -77,7 +79,9 @@ __export(src_exports, {
|
|
|
77
79
|
containFocus: () => containFocus,
|
|
78
80
|
createAnnouncer: () => createAnnouncer,
|
|
79
81
|
createApplication: () => createApplication,
|
|
82
|
+
createAuthSession: () => createAuthSession,
|
|
80
83
|
createBrowserHistory: () => createBrowserHistory,
|
|
84
|
+
createClient: () => createClient,
|
|
81
85
|
createContext: () => createContext,
|
|
82
86
|
createDevTools: () => createDevTools,
|
|
83
87
|
createForm: () => createForm,
|
|
@@ -93,10 +97,12 @@ __export(src_exports, {
|
|
|
93
97
|
createStreetEvent: () => createStreetEvent,
|
|
94
98
|
defineConfig: () => defineConfig,
|
|
95
99
|
derived: () => derived,
|
|
100
|
+
describeError: () => describeError,
|
|
96
101
|
diagnosePerformance: () => diagnosePerformance,
|
|
97
102
|
effect: () => effect,
|
|
98
103
|
email: () => email,
|
|
99
104
|
environment: () => environment,
|
|
105
|
+
escapeHtml: () => escapeHtml,
|
|
100
106
|
escapeHtmlAttr: () => escapeHtmlAttr,
|
|
101
107
|
escapeHtmlText: () => escapeHtmlText,
|
|
102
108
|
flushSync: () => flushSync,
|
|
@@ -128,7 +134,9 @@ __export(src_exports, {
|
|
|
128
134
|
interpolate: () => interpolate,
|
|
129
135
|
isBatching: () => isBatching,
|
|
130
136
|
isComponentDefinition: () => isComponentDefinition,
|
|
137
|
+
isHeadContribution: () => isHeadContribution,
|
|
131
138
|
isTransitionConfig: () => isTransitionConfig,
|
|
139
|
+
linkUpdate: () => linkUpdate,
|
|
132
140
|
matchPattern: () => matchPattern,
|
|
133
141
|
matchRoutes: () => matchRoutes,
|
|
134
142
|
maxLength: () => maxLength,
|
|
@@ -136,6 +144,7 @@ __export(src_exports, {
|
|
|
136
144
|
mountGraph: () => mountGraph,
|
|
137
145
|
mountNode: () => mountNode,
|
|
138
146
|
mountRouter: () => mountRouter,
|
|
147
|
+
mutation: () => mutation,
|
|
139
148
|
nextId: () => nextId,
|
|
140
149
|
nodeIdPrefix: () => nodeIdPrefix,
|
|
141
150
|
nodeTypeStats: () => nodeTypeStats,
|
|
@@ -152,10 +161,15 @@ __export(src_exports, {
|
|
|
152
161
|
readState: () => readState,
|
|
153
162
|
reconcileChildren: () => reconcileChildren,
|
|
154
163
|
reconcileChildrenByPlan: () => reconcileChildrenByPlan,
|
|
164
|
+
renderDevToolsHTML: () => renderDevToolsHTML,
|
|
165
|
+
renderDevToolsReport: () => renderDevToolsReport,
|
|
166
|
+
renderHead: () => renderHead,
|
|
155
167
|
renderToString: () => renderToString,
|
|
156
168
|
reportDiagnostic: () => reportDiagnostic,
|
|
169
|
+
reportError: () => reportError,
|
|
157
170
|
required: () => required,
|
|
158
171
|
resetIdCounter: () => resetIdCounter,
|
|
172
|
+
resolveHead: () => resolveHead,
|
|
159
173
|
resolveTag: () => resolveTag,
|
|
160
174
|
resolveTransition: () => resolveTransition,
|
|
161
175
|
resource: () => resource,
|
|
@@ -183,6 +197,7 @@ __export(src_exports, {
|
|
|
183
197
|
validateGraph: () => validateGraph,
|
|
184
198
|
wireComponentBehavior: () => wireComponentBehavior,
|
|
185
199
|
wireEvents: () => wireEvents,
|
|
200
|
+
wireHeadBehavior: () => wireHeadBehavior,
|
|
186
201
|
wireOverlayBehavior: () => wireOverlayBehavior,
|
|
187
202
|
wireReactiveList: () => wireReactiveList,
|
|
188
203
|
wireSignalBindings: () => wireSignalBindings
|
|
@@ -190,7 +205,7 @@ __export(src_exports, {
|
|
|
190
205
|
module.exports = __toCommonJS(src_exports);
|
|
191
206
|
|
|
192
207
|
// src/version.ts
|
|
193
|
-
var VERSION = "1.
|
|
208
|
+
var VERSION = "2.1.0";
|
|
194
209
|
|
|
195
210
|
// ../state/src/signal.ts
|
|
196
211
|
var _activeConsumer = null;
|
|
@@ -530,6 +545,198 @@ function resource(loader, options = {}) {
|
|
|
530
545
|
};
|
|
531
546
|
}
|
|
532
547
|
|
|
548
|
+
// ../state/src/mutation.ts
|
|
549
|
+
function mutation(mutator, options = {}) {
|
|
550
|
+
const status = signal("idle");
|
|
551
|
+
const data = signal(void 0);
|
|
552
|
+
const error = signal(void 0);
|
|
553
|
+
const pending = derived(() => status.get() === "loading");
|
|
554
|
+
let disposed = false;
|
|
555
|
+
let runId = 0;
|
|
556
|
+
const mutate = async (args) => {
|
|
557
|
+
if (disposed) {
|
|
558
|
+
return await mutator(args);
|
|
559
|
+
}
|
|
560
|
+
const myRun = ++runId;
|
|
561
|
+
batch(() => {
|
|
562
|
+
error.set(void 0);
|
|
563
|
+
status.set("loading");
|
|
564
|
+
});
|
|
565
|
+
try {
|
|
566
|
+
const result = await mutator(args);
|
|
567
|
+
if (!disposed && myRun === runId) {
|
|
568
|
+
batch(() => {
|
|
569
|
+
data.set(result);
|
|
570
|
+
error.set(void 0);
|
|
571
|
+
status.set("success");
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
if (!disposed && myRun === runId) {
|
|
575
|
+
await options.onSuccess?.(result, args);
|
|
576
|
+
await options.onSettled?.(args);
|
|
577
|
+
}
|
|
578
|
+
return result;
|
|
579
|
+
} catch (err) {
|
|
580
|
+
if (!disposed && myRun === runId) {
|
|
581
|
+
batch(() => {
|
|
582
|
+
error.set(err);
|
|
583
|
+
status.set("error");
|
|
584
|
+
});
|
|
585
|
+
await options.onError?.(err, args);
|
|
586
|
+
await options.onSettled?.(args);
|
|
587
|
+
}
|
|
588
|
+
throw err;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
const reset = () => {
|
|
592
|
+
batch(() => {
|
|
593
|
+
status.set("idle");
|
|
594
|
+
data.set(void 0);
|
|
595
|
+
error.set(void 0);
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
const dispose = () => {
|
|
599
|
+
disposed = true;
|
|
600
|
+
};
|
|
601
|
+
if (options.onCleanup !== void 0) options.onCleanup(dispose);
|
|
602
|
+
return { status, data, error, pending, mutate, reset, dispose };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ../state/src/client.ts
|
|
606
|
+
var HttpError = class extends Error {
|
|
607
|
+
status;
|
|
608
|
+
statusText;
|
|
609
|
+
url;
|
|
610
|
+
body;
|
|
611
|
+
constructor(status, statusText, url, body) {
|
|
612
|
+
super(`HTTP ${status} ${statusText} for ${url}`);
|
|
613
|
+
this.name = "HttpError";
|
|
614
|
+
this.status = status;
|
|
615
|
+
this.statusText = statusText;
|
|
616
|
+
this.url = url;
|
|
617
|
+
this.body = body;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
function joinUrl(baseUrl, path) {
|
|
621
|
+
if (baseUrl === void 0 || baseUrl === "") return path;
|
|
622
|
+
const b = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
623
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
624
|
+
return `${b}${p}`;
|
|
625
|
+
}
|
|
626
|
+
function withQuery(url, query) {
|
|
627
|
+
if (query === void 0) return url;
|
|
628
|
+
const params = new URLSearchParams();
|
|
629
|
+
for (const [k, v] of Object.entries(query)) params.set(k, String(v));
|
|
630
|
+
const qs = params.toString();
|
|
631
|
+
if (qs === "") return url;
|
|
632
|
+
return url.includes("?") ? `${url}&${qs}` : `${url}?${qs}`;
|
|
633
|
+
}
|
|
634
|
+
function createClient(config = {}) {
|
|
635
|
+
const doFetch = config.fetch ?? ((input, init) => {
|
|
636
|
+
if (typeof fetch === "undefined") {
|
|
637
|
+
throw new Error("createClient: no global fetch; pass { fetch } explicitly");
|
|
638
|
+
}
|
|
639
|
+
return fetch(input, init);
|
|
640
|
+
});
|
|
641
|
+
async function request(method, path, body, reqConfig = {}) {
|
|
642
|
+
const url = withQuery(joinUrl(config.baseUrl, path), reqConfig.query);
|
|
643
|
+
const headers = { ...config.headers, ...reqConfig.headers };
|
|
644
|
+
const init = { method, headers };
|
|
645
|
+
if (reqConfig.signal !== void 0) init.signal = reqConfig.signal;
|
|
646
|
+
if (body !== void 0) {
|
|
647
|
+
if (headers["Content-Type"] === void 0) headers["Content-Type"] = "application/json";
|
|
648
|
+
init.body = JSON.stringify(body);
|
|
649
|
+
}
|
|
650
|
+
const response = await doFetch(url, init);
|
|
651
|
+
const parsed = await parseBody(response);
|
|
652
|
+
if (!response.ok) {
|
|
653
|
+
throw new HttpError(response.status, response.statusText, url, parsed);
|
|
654
|
+
}
|
|
655
|
+
return parsed;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
request,
|
|
659
|
+
get: (path, c) => request("GET", path, void 0, c),
|
|
660
|
+
post: (path, b, c) => request("POST", path, b, c),
|
|
661
|
+
put: (path, b, c) => request("PUT", path, b, c),
|
|
662
|
+
patch: (path, b, c) => request("PATCH", path, b, c),
|
|
663
|
+
del: (path, c) => request("DELETE", path, void 0, c),
|
|
664
|
+
resource: (path, options = {}) => {
|
|
665
|
+
const { query, ...resourceOptions } = options;
|
|
666
|
+
return resource(
|
|
667
|
+
(ctx) => request("GET", path, void 0, { signal: ctx.signal, ...query !== void 0 ? { query } : {} }),
|
|
668
|
+
resourceOptions
|
|
669
|
+
);
|
|
670
|
+
},
|
|
671
|
+
mutation: (method, path, options) => mutation((args) => request(method, path, args), options ?? {})
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
async function parseBody(response) {
|
|
675
|
+
const text = await response.text();
|
|
676
|
+
if (text === "") return void 0;
|
|
677
|
+
const type = response.headers.get("content-type") ?? "";
|
|
678
|
+
if (type.includes("application/json")) {
|
|
679
|
+
try {
|
|
680
|
+
return JSON.parse(text);
|
|
681
|
+
} catch {
|
|
682
|
+
return text;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return text;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// ../state/src/auth.ts
|
|
689
|
+
function createAuthSession(config) {
|
|
690
|
+
const session = resource(
|
|
691
|
+
(ctx) => config.loadUser(ctx),
|
|
692
|
+
{
|
|
693
|
+
...config.immediate !== void 0 ? { immediate: config.immediate } : {},
|
|
694
|
+
...config.onCleanup !== void 0 ? { onCleanup: config.onCleanup } : {}
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
const status = derived(() => {
|
|
698
|
+
const s = session.status.get();
|
|
699
|
+
const d = session.data.get();
|
|
700
|
+
if (s === "error") return "error";
|
|
701
|
+
if (s === "idle" || s === "loading" && d === void 0) return "loading";
|
|
702
|
+
return d === null || d === void 0 ? "unauthenticated" : "authenticated";
|
|
703
|
+
});
|
|
704
|
+
const user = derived(() => {
|
|
705
|
+
const d = session.data.get();
|
|
706
|
+
return d === null ? void 0 : d;
|
|
707
|
+
});
|
|
708
|
+
const authenticated = derived(() => status.get() === "authenticated");
|
|
709
|
+
const unauthenticated = derived(() => status.get() === "unauthenticated");
|
|
710
|
+
const loading = derived(() => status.get() === "loading");
|
|
711
|
+
const logoutMutation = mutation(
|
|
712
|
+
async () => {
|
|
713
|
+
await config.logout?.();
|
|
714
|
+
},
|
|
715
|
+
{ onSuccess: () => session.refetch() }
|
|
716
|
+
);
|
|
717
|
+
const dispose = () => {
|
|
718
|
+
session.dispose();
|
|
719
|
+
status.dispose();
|
|
720
|
+
user.dispose();
|
|
721
|
+
authenticated.dispose();
|
|
722
|
+
unauthenticated.dispose();
|
|
723
|
+
loading.dispose();
|
|
724
|
+
logoutMutation.dispose();
|
|
725
|
+
};
|
|
726
|
+
return {
|
|
727
|
+
status,
|
|
728
|
+
user,
|
|
729
|
+
error: session.error,
|
|
730
|
+
authenticated,
|
|
731
|
+
unauthenticated,
|
|
732
|
+
loading,
|
|
733
|
+
loggingOut: logoutMutation.pending,
|
|
734
|
+
refresh: () => session.refetch(),
|
|
735
|
+
logout: () => logoutMutation.mutate(void 0),
|
|
736
|
+
dispose
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
533
740
|
// ../core/src/a11y-ids.ts
|
|
534
741
|
var UNSAFE = /[^A-Za-z0-9_-]+/g;
|
|
535
742
|
function toIdToken(base) {
|
|
@@ -796,9 +1003,13 @@ function formatDiagnosticContext(context) {
|
|
|
796
1003
|
const parts = [];
|
|
797
1004
|
if (context.package !== void 0) parts.push(`package=${context.package}`);
|
|
798
1005
|
if (context.operation !== void 0) parts.push(`operation=${context.operation}`);
|
|
1006
|
+
if (context.component !== void 0) parts.push(`component=${context.component}`);
|
|
1007
|
+
if (context.phase !== void 0) parts.push(`phase=${context.phase}`);
|
|
799
1008
|
if (context.nodeId !== void 0) parts.push(`node=${context.nodeId}`);
|
|
1009
|
+
if (context.element !== void 0) parts.push(`element=${context.element}`);
|
|
800
1010
|
if (context.route !== void 0) parts.push(`route=${context.route}`);
|
|
801
1011
|
if (context.resource !== void 0) parts.push(`resource=${context.resource}`);
|
|
1012
|
+
if (context.signal !== void 0) parts.push(`signal=${context.signal}`);
|
|
802
1013
|
return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
|
|
803
1014
|
}
|
|
804
1015
|
var StreetFrameworkError = class extends Error {
|
|
@@ -829,6 +1040,42 @@ function consoleDiagnosticSink(logger = console) {
|
|
|
829
1040
|
error: (m, c) => logger.error?.(`${m}${formatDiagnosticContext(c)}`)
|
|
830
1041
|
};
|
|
831
1042
|
}
|
|
1043
|
+
function describeErrorAt(error, context, options, depth) {
|
|
1044
|
+
const isError = error instanceof Error;
|
|
1045
|
+
const name = isError ? error.name : "Error";
|
|
1046
|
+
const message = isError ? error.message : safeStringify(error);
|
|
1047
|
+
const report = { name, message, isError };
|
|
1048
|
+
if (context !== void 0) report.context = context;
|
|
1049
|
+
if (options.includeStack === true && isError && typeof error.stack === "string") {
|
|
1050
|
+
report.stack = error.stack;
|
|
1051
|
+
}
|
|
1052
|
+
if (options.includeCause === true && isError && depth < 4) {
|
|
1053
|
+
const cause = error.cause;
|
|
1054
|
+
if (cause !== void 0 && cause !== null) {
|
|
1055
|
+
report.cause = describeErrorAt(cause, void 0, options, depth + 1);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return report;
|
|
1059
|
+
}
|
|
1060
|
+
function safeStringify(value) {
|
|
1061
|
+
if (typeof value === "string") return value;
|
|
1062
|
+
if (value === null) return "null";
|
|
1063
|
+
if (value === void 0) return "undefined";
|
|
1064
|
+
const t = typeof value;
|
|
1065
|
+
if (t === "number" || t === "boolean" || t === "bigint" || t === "symbol") {
|
|
1066
|
+
return String(value);
|
|
1067
|
+
}
|
|
1068
|
+
const ctor = t === "object" && value !== null ? value.constructor?.name ?? "Object" : t;
|
|
1069
|
+
return `[non-Error ${ctor}]`;
|
|
1070
|
+
}
|
|
1071
|
+
function describeError(error, context, options = {}) {
|
|
1072
|
+
return describeErrorAt(error, context, options, 0);
|
|
1073
|
+
}
|
|
1074
|
+
function reportError(sink, error, context, options = {}) {
|
|
1075
|
+
const report = describeError(error, context, options);
|
|
1076
|
+
reportDiagnostic(sink, "error", report.message, report.context);
|
|
1077
|
+
return report;
|
|
1078
|
+
}
|
|
832
1079
|
|
|
833
1080
|
// ../graph/src/graph-node.ts
|
|
834
1081
|
var GraphNode = class _GraphNode {
|
|
@@ -999,6 +1246,7 @@ var ApplicationGraph = class {
|
|
|
999
1246
|
this.handlers.delete(`__overlay__${node.id}`);
|
|
1000
1247
|
this.handlers.delete(`__component__${node.id}`);
|
|
1001
1248
|
this.handlers.delete(`__transition__${node.id}`);
|
|
1249
|
+
this.handlers.delete(`__head__${node.id}`);
|
|
1002
1250
|
}
|
|
1003
1251
|
// ── Handler registry ──────────────────────────────────────────────────────
|
|
1004
1252
|
registerHandler(key, fn) {
|
|
@@ -1128,6 +1376,121 @@ function isTransitionConfig(value) {
|
|
|
1128
1376
|
return typeof o["name"] === "string" || typeof o["enter"] === "string" || typeof o["enterActive"] === "string" || typeof o["enterFrom"] === "string" || typeof o["leave"] === "string" || typeof o["leaveActive"] === "string" || typeof o["leaveFrom"] === "string";
|
|
1129
1377
|
}
|
|
1130
1378
|
|
|
1379
|
+
// ../dsl/src/head.ts
|
|
1380
|
+
function metaDedupKey(m) {
|
|
1381
|
+
if (m.charset !== void 0) return "meta:charset";
|
|
1382
|
+
if (m.name !== void 0) return `meta:name=${m.name}`;
|
|
1383
|
+
if (m.property !== void 0) return `meta:property=${m.property}`;
|
|
1384
|
+
if (m.httpEquiv !== void 0) return `meta:http-equiv=${m.httpEquiv}`;
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
function metaAttrs(m) {
|
|
1388
|
+
const attrs = {};
|
|
1389
|
+
if (m.charset !== void 0) attrs["charset"] = m.charset;
|
|
1390
|
+
if (m.name !== void 0) attrs["name"] = m.name;
|
|
1391
|
+
if (m.property !== void 0) attrs["property"] = m.property;
|
|
1392
|
+
if (m.httpEquiv !== void 0) attrs["http-equiv"] = m.httpEquiv;
|
|
1393
|
+
if (m.content !== void 0) attrs["content"] = m.content;
|
|
1394
|
+
return attrs;
|
|
1395
|
+
}
|
|
1396
|
+
function linkAttrs(l) {
|
|
1397
|
+
const attrs = { rel: l.rel, href: l.href };
|
|
1398
|
+
if (l.sizes !== void 0) attrs["sizes"] = l.sizes;
|
|
1399
|
+
if (l.type !== void 0) attrs["type"] = l.type;
|
|
1400
|
+
if (l.media !== void 0) attrs["media"] = l.media;
|
|
1401
|
+
if (l.as !== void 0) attrs["as"] = l.as;
|
|
1402
|
+
if (l.crossorigin !== void 0) attrs["crossorigin"] = l.crossorigin;
|
|
1403
|
+
if (l.hreflang !== void 0) attrs["hreflang"] = l.hreflang;
|
|
1404
|
+
return attrs;
|
|
1405
|
+
}
|
|
1406
|
+
function resolveHead(config) {
|
|
1407
|
+
const entries = [];
|
|
1408
|
+
if (config.charset !== void 0) {
|
|
1409
|
+
entries.push({ tag: "meta", dedupKey: "meta:charset", attrs: { charset: config.charset } });
|
|
1410
|
+
}
|
|
1411
|
+
if (config.base !== void 0) {
|
|
1412
|
+
entries.push({ tag: "base", dedupKey: "base", attrs: { href: config.base } });
|
|
1413
|
+
}
|
|
1414
|
+
if (config.title !== void 0) {
|
|
1415
|
+
entries.push({ tag: "title", dedupKey: "title", attrs: {}, text: config.title });
|
|
1416
|
+
}
|
|
1417
|
+
if (config.description !== void 0) {
|
|
1418
|
+
entries.push({
|
|
1419
|
+
tag: "meta",
|
|
1420
|
+
dedupKey: "meta:name=description",
|
|
1421
|
+
attrs: { name: "description", content: config.description }
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (config.canonical !== void 0) {
|
|
1425
|
+
entries.push({
|
|
1426
|
+
tag: "link",
|
|
1427
|
+
dedupKey: "link:rel=canonical",
|
|
1428
|
+
attrs: { rel: "canonical", href: config.canonical }
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
if (config.robots !== void 0) {
|
|
1432
|
+
entries.push({
|
|
1433
|
+
tag: "meta",
|
|
1434
|
+
dedupKey: "meta:name=robots",
|
|
1435
|
+
attrs: { name: "robots", content: config.robots }
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
if (config.themeColor !== void 0) {
|
|
1439
|
+
entries.push({
|
|
1440
|
+
tag: "meta",
|
|
1441
|
+
dedupKey: "meta:name=theme-color",
|
|
1442
|
+
attrs: { name: "theme-color", content: config.themeColor }
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
if (config.viewport !== void 0) {
|
|
1446
|
+
entries.push({
|
|
1447
|
+
tag: "meta",
|
|
1448
|
+
dedupKey: "meta:name=viewport",
|
|
1449
|
+
attrs: { name: "viewport", content: config.viewport }
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
if (config.favicon !== void 0) {
|
|
1453
|
+
const l = typeof config.favicon === "string" ? { rel: "icon", href: config.favicon } : config.favicon;
|
|
1454
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}`, attrs: linkAttrs(l) });
|
|
1455
|
+
}
|
|
1456
|
+
if (config.openGraph !== void 0) {
|
|
1457
|
+
for (const key of Object.keys(config.openGraph)) {
|
|
1458
|
+
const property = `og:${key}`;
|
|
1459
|
+
entries.push({
|
|
1460
|
+
tag: "meta",
|
|
1461
|
+
dedupKey: `meta:property=${property}`,
|
|
1462
|
+
attrs: { property, content: config.openGraph[key] }
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (config.twitter !== void 0) {
|
|
1467
|
+
for (const key of Object.keys(config.twitter)) {
|
|
1468
|
+
const name = `twitter:${key}`;
|
|
1469
|
+
entries.push({
|
|
1470
|
+
tag: "meta",
|
|
1471
|
+
dedupKey: `meta:name=${name}`,
|
|
1472
|
+
attrs: { name, content: config.twitter[key] }
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (config.meta !== void 0) {
|
|
1477
|
+
for (const m of config.meta) {
|
|
1478
|
+
const key = metaDedupKey(m);
|
|
1479
|
+
if (key === void 0) continue;
|
|
1480
|
+
entries.push({ tag: "meta", dedupKey: key, attrs: metaAttrs(m) });
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (config.link !== void 0) {
|
|
1484
|
+
for (const l of config.link) {
|
|
1485
|
+
entries.push({ tag: "link", dedupKey: `link:rel=${l.rel}:href=${l.href}`, attrs: linkAttrs(l) });
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return { entries };
|
|
1489
|
+
}
|
|
1490
|
+
function isHeadContribution(value) {
|
|
1491
|
+
return value !== null && typeof value === "object" && Array.isArray(value["entries"]);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1131
1494
|
// ../dsl/src/builders.ts
|
|
1132
1495
|
function isSignal(v) {
|
|
1133
1496
|
return v !== null && typeof v === "object" && typeof v["get"] === "function" && typeof v["subscribe"] === "function";
|
|
@@ -1509,8 +1872,14 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
1509
1872
|
this.container(id, (c) => {
|
|
1510
1873
|
c.when(
|
|
1511
1874
|
hasError,
|
|
1512
|
-
// Error state → fallback.
|
|
1513
|
-
|
|
1875
|
+
// Error state → fallback. The fallback branch mounts exactly when the
|
|
1876
|
+
// boundary enters its error state, so this is also where the optional
|
|
1877
|
+
// `onError` reporting hook fires (observe-only; §6/§7).
|
|
1878
|
+
(fb) => {
|
|
1879
|
+
const currentError = readError();
|
|
1880
|
+
options.onError?.(currentError);
|
|
1881
|
+
options.fallback(fb, currentError, retry);
|
|
1882
|
+
},
|
|
1514
1883
|
// Healthy state → body, guarded against synchronous build throws.
|
|
1515
1884
|
(body) => {
|
|
1516
1885
|
try {
|
|
@@ -1522,6 +1891,24 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
1522
1891
|
);
|
|
1523
1892
|
}, { id });
|
|
1524
1893
|
}
|
|
1894
|
+
asyncBoundary(key, res, branches) {
|
|
1895
|
+
const isError = derived(() => res.status.get() === "error");
|
|
1896
|
+
const showSuccess = derived(
|
|
1897
|
+
() => res.status.get() !== "error" && res.data.get() !== void 0
|
|
1898
|
+
);
|
|
1899
|
+
const showLoading = derived(
|
|
1900
|
+
() => res.status.get() !== "error" && res.data.get() === void 0
|
|
1901
|
+
);
|
|
1902
|
+
const dataSignal = derived(() => res.data.get());
|
|
1903
|
+
const retry = () => {
|
|
1904
|
+
void res.refetch();
|
|
1905
|
+
};
|
|
1906
|
+
this.container(key, (c) => {
|
|
1907
|
+
c.when(isError, (fb) => branches.error?.(fb, res.error.peek(), retry));
|
|
1908
|
+
c.when(showSuccess, (sb) => branches.success(sb, dataSignal));
|
|
1909
|
+
c.when(showLoading, (lb) => branches.loading?.(lb));
|
|
1910
|
+
}, { key });
|
|
1911
|
+
}
|
|
1525
1912
|
// ── Portals & overlays ──────────────────────────────────────────────────────
|
|
1526
1913
|
portal(key, builder, options = {}) {
|
|
1527
1914
|
const node = this._graph.createNode("portal", {
|
|
@@ -1531,6 +1918,31 @@ var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
|
1531
1918
|
});
|
|
1532
1919
|
builder(new ContainerBuilderImpl(node, this._graph));
|
|
1533
1920
|
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Declare document metadata (2.0 §1–§3). Creates a `'head'` node — a neutral,
|
|
1923
|
+
* empty inline anchor at this position (one node / one element, so positional
|
|
1924
|
+
* hydration is preserved) — and registers a `__head__<id>` descriptor holding
|
|
1925
|
+
* this call's normalized, dedup-keyed {@link resolveHead} contribution. The
|
|
1926
|
+
* renderer applies it to `document.head` on the browser (adopting server tags
|
|
1927
|
+
* on hydration, cleaning up on unmount / route change) and emits the active
|
|
1928
|
+
* graph's merged metadata as a string on the server (`renderHead`).
|
|
1929
|
+
*
|
|
1930
|
+
* Multiple `head()` nodes may be live at once (app default + route + component)
|
|
1931
|
+
* — the renderer merges them and, per dedup key, the last in document order
|
|
1932
|
+
* wins (see head.ts). No new render path: this reuses the same graph-node +
|
|
1933
|
+
* handler-registry convention as overlays/transitions/components.
|
|
1934
|
+
*/
|
|
1935
|
+
head(metadata) {
|
|
1936
|
+
const node = this._graph.createNode("head", {
|
|
1937
|
+
parent: this._node,
|
|
1938
|
+
props: { "data-streetui-head-anchor": "" }
|
|
1939
|
+
});
|
|
1940
|
+
const contribution = resolveHead(metadata);
|
|
1941
|
+
this._graph.registerHandler(
|
|
1942
|
+
`__head__${node.id}`,
|
|
1943
|
+
() => contribution
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1534
1946
|
/**
|
|
1535
1947
|
* Shared assembly for every overlay kind: a `portal` node whose single child
|
|
1536
1948
|
* is a `when(open, panel)` conditional. The panel container carries the
|
|
@@ -1902,8 +2314,8 @@ var RuntimeNodeInstance = class {
|
|
|
1902
2314
|
this.children.push(instance);
|
|
1903
2315
|
}
|
|
1904
2316
|
/** Subscribe to a signal and register the unsubscribe for cleanup. */
|
|
1905
|
-
trackSignal(
|
|
1906
|
-
const unsub =
|
|
2317
|
+
trackSignal(signal3, handler) {
|
|
2318
|
+
const unsub = signal3.subscribe(handler);
|
|
1907
2319
|
this.cleanup.add(unsub);
|
|
1908
2320
|
}
|
|
1909
2321
|
/** Register an arbitrary cleanup function (e.g. DOM event removal). */
|
|
@@ -2274,6 +2686,9 @@ var BrowserDOMAdapter = class {
|
|
|
2274
2686
|
body() {
|
|
2275
2687
|
return document.body ?? null;
|
|
2276
2688
|
}
|
|
2689
|
+
head() {
|
|
2690
|
+
return document.head ?? null;
|
|
2691
|
+
}
|
|
2277
2692
|
activeElement() {
|
|
2278
2693
|
return document.activeElement ?? null;
|
|
2279
2694
|
}
|
|
@@ -2653,6 +3068,9 @@ var ServerDOMAdapter = class {
|
|
|
2653
3068
|
body() {
|
|
2654
3069
|
return null;
|
|
2655
3070
|
}
|
|
3071
|
+
head() {
|
|
3072
|
+
return null;
|
|
3073
|
+
}
|
|
2656
3074
|
activeElement() {
|
|
2657
3075
|
return null;
|
|
2658
3076
|
}
|
|
@@ -3035,7 +3453,13 @@ var TAG_MAP = {
|
|
|
3035
3453
|
// its children are relocated to a document.body container on the browser
|
|
3036
3454
|
// (see the portal branch in mount.ts). On the server (no body) it renders
|
|
3037
3455
|
// inline, so the anchor tag is what SSR/hydration positionally match on.
|
|
3038
|
-
portal: "div"
|
|
3456
|
+
portal: "div",
|
|
3457
|
+
// A `head()` node renders as a neutral, empty inline anchor <div> at its
|
|
3458
|
+
// declaration site (like a portal anchor). Its actual contribution — title/
|
|
3459
|
+
// meta/link/etc. — is applied to `document.head` by `wireHeadBehavior` on the
|
|
3460
|
+
// browser, and emitted separately by `renderHead()` on the server. Keeping a
|
|
3461
|
+
// one-node/one-element anchor preserves positional hydration.
|
|
3462
|
+
head: "div"
|
|
3039
3463
|
};
|
|
3040
3464
|
function resolveTag(type) {
|
|
3041
3465
|
return TAG_MAP[type] ?? "div";
|
|
@@ -3491,6 +3915,184 @@ var TransitionController = class {
|
|
|
3491
3915
|
}
|
|
3492
3916
|
};
|
|
3493
3917
|
|
|
3918
|
+
// ../renderer/src/head.ts
|
|
3919
|
+
var HEAD_MARKER = "data-streetui-head";
|
|
3920
|
+
var HEAD_KEY = "data-streetui-head-key";
|
|
3921
|
+
function isSignalLike(v) {
|
|
3922
|
+
return v !== null && typeof v === "object" && typeof v["subscribe"] === "function" && typeof v["peek"] === "function";
|
|
3923
|
+
}
|
|
3924
|
+
function readValue(v) {
|
|
3925
|
+
if (isSignalLike(v)) return String(v.peek() ?? "");
|
|
3926
|
+
return String(v ?? "");
|
|
3927
|
+
}
|
|
3928
|
+
function resolveEntry(entry) {
|
|
3929
|
+
const attrs = {};
|
|
3930
|
+
for (const key of Object.keys(entry.attrs)) {
|
|
3931
|
+
attrs[key] = readValue(entry.attrs[key]);
|
|
3932
|
+
}
|
|
3933
|
+
const resolved = { tag: entry.tag, attrs };
|
|
3934
|
+
if (entry.tag === "title") {
|
|
3935
|
+
return { ...resolved, text: readValue(entry.text) };
|
|
3936
|
+
}
|
|
3937
|
+
return resolved;
|
|
3938
|
+
}
|
|
3939
|
+
var HeadManager = class {
|
|
3940
|
+
_dom;
|
|
3941
|
+
_head;
|
|
3942
|
+
_contributions = /* @__PURE__ */ new Map();
|
|
3943
|
+
_applied = /* @__PURE__ */ new Map();
|
|
3944
|
+
_order = 0;
|
|
3945
|
+
_adopted = false;
|
|
3946
|
+
constructor(dom, head) {
|
|
3947
|
+
this._dom = dom;
|
|
3948
|
+
this._head = head;
|
|
3949
|
+
}
|
|
3950
|
+
/** Register (or replace) a node's contribution and re-apply the merged result. */
|
|
3951
|
+
register(nodeId, entries) {
|
|
3952
|
+
this._contributions.set(nodeId, { order: this._order++, entries });
|
|
3953
|
+
this.apply();
|
|
3954
|
+
}
|
|
3955
|
+
/** Withdraw a node's contribution (unmount / route change) and re-apply. */
|
|
3956
|
+
unregister(nodeId) {
|
|
3957
|
+
if (this._contributions.delete(nodeId)) this.apply();
|
|
3958
|
+
}
|
|
3959
|
+
/** Recompute the merged head and patch `document.head` to match. */
|
|
3960
|
+
apply() {
|
|
3961
|
+
if (!this._adopted) {
|
|
3962
|
+
this._adoptServerTags();
|
|
3963
|
+
this._adopted = true;
|
|
3964
|
+
}
|
|
3965
|
+
const ordered = [...this._contributions.values()].sort((a, b) => a.order - b.order);
|
|
3966
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3967
|
+
for (const contribution of ordered) {
|
|
3968
|
+
for (const entry of contribution.entries) {
|
|
3969
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
for (const [key, desired] of merged) {
|
|
3973
|
+
const existing = this._applied.get(key);
|
|
3974
|
+
if (existing !== void 0 && existing.tag === desired.tag) {
|
|
3975
|
+
this._reconcileAttrs(existing, desired);
|
|
3976
|
+
} else {
|
|
3977
|
+
if (existing !== void 0) {
|
|
3978
|
+
this._dom.removeChild(this._head, existing.el);
|
|
3979
|
+
this._applied.delete(key);
|
|
3980
|
+
}
|
|
3981
|
+
const el = this._createTag(key, desired);
|
|
3982
|
+
this._dom.appendChild(this._head, el);
|
|
3983
|
+
this._applied.set(key, { el, attrKeys: new Set(Object.keys(desired.attrs)), tag: desired.tag });
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
for (const [key, record] of [...this._applied]) {
|
|
3987
|
+
if (!merged.has(key)) {
|
|
3988
|
+
this._dom.removeChild(this._head, record.el);
|
|
3989
|
+
this._applied.delete(key);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
_createTag(key, desired) {
|
|
3994
|
+
const el = this._dom.createElement(desired.tag);
|
|
3995
|
+
this._dom.setAttribute(el, HEAD_MARKER, "");
|
|
3996
|
+
this._dom.setAttribute(el, HEAD_KEY, key);
|
|
3997
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
3998
|
+
this._dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
3999
|
+
}
|
|
4000
|
+
if (desired.tag === "title") this._dom.setTextContent(el, desired.text ?? "");
|
|
4001
|
+
return el;
|
|
4002
|
+
}
|
|
4003
|
+
_reconcileAttrs(record, desired) {
|
|
4004
|
+
const nextKeys = new Set(Object.keys(desired.attrs));
|
|
4005
|
+
for (const attr of record.attrKeys) {
|
|
4006
|
+
if (!nextKeys.has(attr)) this._dom.removeAttribute(record.el, attr);
|
|
4007
|
+
}
|
|
4008
|
+
for (const attr of nextKeys) {
|
|
4009
|
+
this._dom.setAttribute(record.el, attr, desired.attrs[attr]);
|
|
4010
|
+
}
|
|
4011
|
+
if (desired.tag === "title") this._dom.setTextContent(record.el, desired.text ?? "");
|
|
4012
|
+
record.attrKeys = nextKeys;
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Seed `_applied` from server-emitted `[data-streetui-head-key]` tags already
|
|
4016
|
+
* in `document.head`. The subsequent diff reuses these elements when the
|
|
4017
|
+
* client desires the same key (no duplicate), rewrites them if the value
|
|
4018
|
+
* changed, or removes them if the client graph no longer wants them.
|
|
4019
|
+
*/
|
|
4020
|
+
_adoptServerTags() {
|
|
4021
|
+
for (const child of this._dom.childNodes(this._head)) {
|
|
4022
|
+
if (!this._dom.isElement(child)) continue;
|
|
4023
|
+
const el = child;
|
|
4024
|
+
const key = this._dom.getAttribute(el, HEAD_KEY);
|
|
4025
|
+
if (key === null) continue;
|
|
4026
|
+
this._applied.set(key, {
|
|
4027
|
+
el,
|
|
4028
|
+
attrKeys: new Set(this._attrNames(el)),
|
|
4029
|
+
tag: this._dom.tagName(el)
|
|
4030
|
+
});
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
/** The framework-managed attribute names currently on a server tag. */
|
|
4034
|
+
_attrNames(el) {
|
|
4035
|
+
const names = [];
|
|
4036
|
+
if (this._dom.getAttribute(el, HEAD_MARKER) !== null) names.push(HEAD_MARKER);
|
|
4037
|
+
if (this._dom.getAttribute(el, HEAD_KEY) !== null) names.push(HEAD_KEY);
|
|
4038
|
+
return names;
|
|
4039
|
+
}
|
|
4040
|
+
};
|
|
4041
|
+
function getHeadManager(ctx) {
|
|
4042
|
+
const head = ctx.dom.head();
|
|
4043
|
+
if (head === null) return null;
|
|
4044
|
+
const mutable = ctx;
|
|
4045
|
+
if (mutable.head === void 0) mutable.head = new HeadManager(ctx.dom, head);
|
|
4046
|
+
return mutable.head;
|
|
4047
|
+
}
|
|
4048
|
+
function wireHeadBehavior(ctx, graphNode, instance) {
|
|
4049
|
+
const manager = getHeadManager(ctx);
|
|
4050
|
+
if (manager === null) return;
|
|
4051
|
+
const descFn = ctx.graph.getHandler(`__head__${graphNode.id}`);
|
|
4052
|
+
if (descFn === void 0) return;
|
|
4053
|
+
const contribution = descFn();
|
|
4054
|
+
const nodeId = graphNode.id;
|
|
4055
|
+
manager.register(nodeId, contribution.entries);
|
|
4056
|
+
for (const entry of contribution.entries) {
|
|
4057
|
+
for (const attrKey of Object.keys(entry.attrs)) {
|
|
4058
|
+
const v = entry.attrs[attrKey];
|
|
4059
|
+
if (isSignalLike(v)) {
|
|
4060
|
+
instance.trackCleanup(v.subscribe(() => manager.apply()));
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
if (isSignalLike(entry.text)) {
|
|
4064
|
+
instance.trackCleanup(entry.text.subscribe(() => manager.apply()));
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
instance.trackCleanup(() => manager.unregister(nodeId));
|
|
4068
|
+
}
|
|
4069
|
+
function renderHead(compiled) {
|
|
4070
|
+
const graph = compiled.graph;
|
|
4071
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4072
|
+
graph.walk((node) => {
|
|
4073
|
+
if (node.type !== "head") return;
|
|
4074
|
+
const descFn = graph.getHandler(`__head__${node.id}`);
|
|
4075
|
+
if (descFn === void 0) return;
|
|
4076
|
+
for (const entry of descFn().entries) {
|
|
4077
|
+
merged.set(entry.dedupKey, resolveEntry(entry));
|
|
4078
|
+
}
|
|
4079
|
+
});
|
|
4080
|
+
if (merged.size === 0) return "";
|
|
4081
|
+
const dom = new ServerDOMAdapter();
|
|
4082
|
+
let out = "";
|
|
4083
|
+
for (const [key, desired] of merged) {
|
|
4084
|
+
const el = dom.createElement(desired.tag);
|
|
4085
|
+
dom.setAttribute(el, HEAD_MARKER, "");
|
|
4086
|
+
dom.setAttribute(el, HEAD_KEY, key);
|
|
4087
|
+
for (const attr of Object.keys(desired.attrs)) {
|
|
4088
|
+
dom.setAttribute(el, attr, desired.attrs[attr]);
|
|
4089
|
+
}
|
|
4090
|
+
if (desired.tag === "title") dom.setTextContent(el, desired.text ?? "");
|
|
4091
|
+
out += dom.serializeOuter(el);
|
|
4092
|
+
}
|
|
4093
|
+
return out;
|
|
4094
|
+
}
|
|
4095
|
+
|
|
3494
4096
|
// ../renderer/src/mount.ts
|
|
3495
4097
|
var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
3496
4098
|
"text",
|
|
@@ -3613,6 +4215,9 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
3613
4215
|
const instance2 = new NodeInstance(graphNode, el2);
|
|
3614
4216
|
ctx.instances.set(graphNode.id, instance2);
|
|
3615
4217
|
wireEvents(dom, graph, graphNode, el2, instance2);
|
|
4218
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
4219
|
+
wireSignalBindings(ctx, graphNode, instance2, linkUpdate(dom, el2));
|
|
4220
|
+
}
|
|
3616
4221
|
dom.appendChild(parentDom, el2);
|
|
3617
4222
|
return instance2;
|
|
3618
4223
|
}
|
|
@@ -3668,6 +4273,16 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
3668
4273
|
wireOverlayBehavior(ctx, graphNode, instance2, target);
|
|
3669
4274
|
return instance2;
|
|
3670
4275
|
}
|
|
4276
|
+
if (graphNode.type === "head") {
|
|
4277
|
+
const anchor = dom.createElement(resolveTag("head"));
|
|
4278
|
+
dom.setAttribute(anchor, "data-streetui-head-anchor", "");
|
|
4279
|
+
applyNodeProps(ctx, graphNode, anchor);
|
|
4280
|
+
const instance2 = new NodeInstance(graphNode, anchor);
|
|
4281
|
+
ctx.instances.set(graphNode.id, instance2);
|
|
4282
|
+
dom.appendChild(parentDom, anchor);
|
|
4283
|
+
wireHeadBehavior(ctx, graphNode, instance2);
|
|
4284
|
+
return instance2;
|
|
4285
|
+
}
|
|
3671
4286
|
const tag = resolveTag(graphNode.type);
|
|
3672
4287
|
const el = dom.createElement(tag);
|
|
3673
4288
|
applyNodeProps(ctx, graphNode, el);
|
|
@@ -3732,6 +4347,17 @@ function buttonUpdate(dom, el) {
|
|
|
3732
4347
|
}
|
|
3733
4348
|
};
|
|
3734
4349
|
}
|
|
4350
|
+
function linkUpdate(dom, el) {
|
|
4351
|
+
return (propKey, value) => {
|
|
4352
|
+
if (propKey === "label") {
|
|
4353
|
+
dom.setTextContent(el, String(value ?? ""));
|
|
4354
|
+
} else if (propKey === "href") {
|
|
4355
|
+
dom.setAttribute(el, "href", String(value ?? ""));
|
|
4356
|
+
} else {
|
|
4357
|
+
applyProp(dom, el, propKey, value);
|
|
4358
|
+
}
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
3735
4361
|
function applyNodeProps(ctx, graphNode, el) {
|
|
3736
4362
|
const props = graphNode.props;
|
|
3737
4363
|
for (const key in props) {
|
|
@@ -3966,7 +4592,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
3966
4592
|
case "link": {
|
|
3967
4593
|
const instance = new NodeInstance(graphNode, domNode);
|
|
3968
4594
|
ctx.instances.set(graphNode.id, instance);
|
|
3969
|
-
if (graphNode.type === "link")
|
|
4595
|
+
if (graphNode.type === "link") {
|
|
4596
|
+
wireEvents(dom, graph, graphNode, domNode, instance);
|
|
4597
|
+
if (graphNode.stateRefs.length !== 0) {
|
|
4598
|
+
wireSignalBindings(ctx, graphNode, instance, linkUpdate(dom, domNode));
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
3970
4601
|
return instance;
|
|
3971
4602
|
}
|
|
3972
4603
|
case "reactive-list":
|
|
@@ -3996,6 +4627,12 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
3996
4627
|
wireOverlayBehavior(ctx, graphNode, instance, target);
|
|
3997
4628
|
return instance;
|
|
3998
4629
|
}
|
|
4630
|
+
case "head": {
|
|
4631
|
+
const instance = new NodeInstance(graphNode, domNode);
|
|
4632
|
+
ctx.instances.set(graphNode.id, instance);
|
|
4633
|
+
wireHeadBehavior(ctx, graphNode, instance);
|
|
4634
|
+
return instance;
|
|
4635
|
+
}
|
|
3999
4636
|
default: {
|
|
4000
4637
|
const instance = new NodeInstance(graphNode, domNode);
|
|
4001
4638
|
ctx.instances.set(graphNode.id, instance);
|
|
@@ -4242,7 +4879,8 @@ function analyzeGraph(graph) {
|
|
|
4242
4879
|
const isConditional = node.type === "conditional";
|
|
4243
4880
|
const isPortal = node.type === "portal";
|
|
4244
4881
|
const isComponent = node.type === "component";
|
|
4245
|
-
const
|
|
4882
|
+
const isHead = node.type === "head";
|
|
4883
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent && !isHead;
|
|
4246
4884
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
4247
4885
|
nodes.set(node.id, {
|
|
4248
4886
|
isStatic,
|
|
@@ -5304,11 +5942,22 @@ function capture(compiled, sources, options) {
|
|
|
5304
5942
|
snapshot: app.perf,
|
|
5305
5943
|
diagnostics: diagnosePerformance(compiled, options.perfThresholds)
|
|
5306
5944
|
};
|
|
5945
|
+
const components = inspectComponents(compiled.graph);
|
|
5946
|
+
const interactions = inspectInteractions(compiled.graph);
|
|
5947
|
+
const diagnostics = {
|
|
5948
|
+
errors: app.diagnostics.errors,
|
|
5949
|
+
warnings: app.diagnostics.warnings,
|
|
5950
|
+
messages: app.diagnostics.messages
|
|
5951
|
+
};
|
|
5307
5952
|
const snapshot = {
|
|
5308
5953
|
application,
|
|
5309
5954
|
graph: app.graph,
|
|
5310
5955
|
signals,
|
|
5311
5956
|
performance,
|
|
5957
|
+
components,
|
|
5958
|
+
overlays: interactions.overlays,
|
|
5959
|
+
transitions: interactions.transitions,
|
|
5960
|
+
diagnostics,
|
|
5312
5961
|
...sources.router !== void 0 ? { router: inspectRouter(sources.router) } : {},
|
|
5313
5962
|
...sources.resources !== void 0 ? { resources: mapInspect(sources.resources, (r) => inspectResource(r)) } : {},
|
|
5314
5963
|
...sources.forms !== void 0 ? { forms: mapInspect(sources.forms, (f) => inspectForm(f)) } : {},
|
|
@@ -5349,6 +5998,25 @@ function formatSnapshot(s) {
|
|
|
5349
5998
|
for (const [label, sig] of Object.entries(s.signals.live)) {
|
|
5350
5999
|
lines.push(` ${label} [${sig.kind}] = ${format(sig.value)} \xB7 observers ${sig.observerCount ?? "?"}`);
|
|
5351
6000
|
}
|
|
6001
|
+
if (s.components.length > 0) {
|
|
6002
|
+
lines.push(`Components: ${s.components.length}`);
|
|
6003
|
+
for (const c of s.components) {
|
|
6004
|
+
lines.push(` ${" ".repeat(c.depth)}${c.name}${c.key !== void 0 ? ` (#${c.key})` : ""} \xB7 children ${c.childCount}`);
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
6007
|
+
if (s.overlays.length > 0) {
|
|
6008
|
+
lines.push(`Overlays: ${s.overlays.length}`);
|
|
6009
|
+
for (const o of s.overlays) {
|
|
6010
|
+
const kind = o.modal ? "modal" : o.menu ? "menu" : o.takesFocus ? "focusable" : "non-modal";
|
|
6011
|
+
lines.push(` ${o.key ?? o.id} [${kind}] ${o.open ? "open" : "closed"}`);
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
if (s.transitions.length > 0) {
|
|
6015
|
+
lines.push(`Transitions: ${s.transitions.length}`);
|
|
6016
|
+
for (const t of s.transitions) {
|
|
6017
|
+
lines.push(` ${t.key ?? t.id} on <${t.nodeType}> \xB7 ${t.duration}ms${t.appear ? " \xB7 appear" : ""}`);
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
5352
6020
|
if (s.router !== void 0) {
|
|
5353
6021
|
lines.push(`Router: ${s.router.path} (${s.router.pattern})${s.router.isFallback ? " [fallback]" : ""}`);
|
|
5354
6022
|
}
|
|
@@ -5382,6 +6050,12 @@ function formatSnapshot(s) {
|
|
|
5382
6050
|
for (const d of s.performance.diagnostics) {
|
|
5383
6051
|
lines.push(` ${d.code}: ${d.message}`);
|
|
5384
6052
|
}
|
|
6053
|
+
lines.push(
|
|
6054
|
+
`Diagnostics: ${s.diagnostics.errors} error(s), ${s.diagnostics.warnings} warning(s)`
|
|
6055
|
+
);
|
|
6056
|
+
for (const m of s.diagnostics.messages) {
|
|
6057
|
+
lines.push(` ${m}`);
|
|
6058
|
+
}
|
|
5385
6059
|
return lines.join("\n");
|
|
5386
6060
|
}
|
|
5387
6061
|
function format(value) {
|
|
@@ -5397,6 +6071,182 @@ function format(value) {
|
|
|
5397
6071
|
return String(value);
|
|
5398
6072
|
}
|
|
5399
6073
|
|
|
6074
|
+
// ../devtools/src/view.ts
|
|
6075
|
+
function escapeHtml(value) {
|
|
6076
|
+
const s = typeof value === "string" ? value : stringifyValue(value);
|
|
6077
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6078
|
+
}
|
|
6079
|
+
function stringifyValue(value) {
|
|
6080
|
+
if (value === null || value === void 0) return String(value);
|
|
6081
|
+
if (typeof value === "object") {
|
|
6082
|
+
try {
|
|
6083
|
+
return JSON.stringify(value);
|
|
6084
|
+
} catch {
|
|
6085
|
+
return "[object]";
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
return String(value);
|
|
6089
|
+
}
|
|
6090
|
+
function section(title, count, body) {
|
|
6091
|
+
const badge = count === void 0 ? "" : ` <span class="st-count">${count}</span>`;
|
|
6092
|
+
return `<section class="st-panel"><h2>${escapeHtml(title)}${badge}</h2>${body}</section>`;
|
|
6093
|
+
}
|
|
6094
|
+
function ul(items) {
|
|
6095
|
+
if (items.length === 0) return '<p class="st-empty">(none)</p>';
|
|
6096
|
+
return `<ul>${items.map((i) => `<li>${i}</li>`).join("")}</ul>`;
|
|
6097
|
+
}
|
|
6098
|
+
function renderDevToolsHTML(s) {
|
|
6099
|
+
const app = s.application;
|
|
6100
|
+
const parts = [];
|
|
6101
|
+
parts.push(
|
|
6102
|
+
section(
|
|
6103
|
+
"Application",
|
|
6104
|
+
void 0,
|
|
6105
|
+
`<p>${escapeHtml(app.identity.name)} <span class="st-dim">v${escapeHtml(app.identity.version)}</span></p><p class="st-dim">${app.nodeCount} nodes \xB7 depth ${app.maxDepth} \xB7 ${app.pages.length} page(s) \xB7 ${app.signalCount} signals \xB7 ${app.eventHandlers} handlers \xB7 ${app.stateBindings} bindings</p>`
|
|
6106
|
+
)
|
|
6107
|
+
);
|
|
6108
|
+
parts.push(
|
|
6109
|
+
section(
|
|
6110
|
+
"Components",
|
|
6111
|
+
s.components.length,
|
|
6112
|
+
ul(
|
|
6113
|
+
s.components.map(
|
|
6114
|
+
(c) => `<span class="st-depth" style="--d:${c.depth}"></span><code>${escapeHtml(c.name)}</code>` + (c.key !== void 0 ? ` <span class="st-key">#${escapeHtml(c.key)}</span>` : "") + ` <span class="st-dim">${c.childCount} child(ren)</span>`
|
|
6115
|
+
)
|
|
6116
|
+
)
|
|
6117
|
+
)
|
|
6118
|
+
);
|
|
6119
|
+
parts.push(section("Graph", void 0, `<pre class="st-tree">${escapeHtml(renderNodeTree(s.graph))}</pre>`));
|
|
6120
|
+
parts.push(
|
|
6121
|
+
section(
|
|
6122
|
+
"Signals",
|
|
6123
|
+
s.signals.boundSignalIds.length,
|
|
6124
|
+
`<p class="st-dim">${s.signals.boundSignalIds.length} bound in graph \xB7 effects shown as observer counts (no global effect registry)</p>` + ul(
|
|
6125
|
+
Object.entries(s.signals.live).map(
|
|
6126
|
+
([label, sig]) => `<code>${escapeHtml(label)}</code> <span class="st-key">[${escapeHtml(sig.kind)}]</span> = <code>${escapeHtml(sig.value)}</code> <span class="st-dim">observers ${escapeHtml(sig.observerCount ?? "?")}</span>`
|
|
6127
|
+
)
|
|
6128
|
+
)
|
|
6129
|
+
)
|
|
6130
|
+
);
|
|
6131
|
+
if (s.router !== void 0) {
|
|
6132
|
+
parts.push(
|
|
6133
|
+
section(
|
|
6134
|
+
"Router",
|
|
6135
|
+
void 0,
|
|
6136
|
+
`<p><code>${escapeHtml(s.router.path)}</code> <span class="st-dim">(${escapeHtml(s.router.pattern)})${s.router.isFallback ? " \xB7 fallback" : ""}</span></p>`
|
|
6137
|
+
)
|
|
6138
|
+
);
|
|
6139
|
+
}
|
|
6140
|
+
if (s.resources !== void 0) {
|
|
6141
|
+
parts.push(
|
|
6142
|
+
section(
|
|
6143
|
+
"Resources",
|
|
6144
|
+
Object.keys(s.resources).length,
|
|
6145
|
+
ul(
|
|
6146
|
+
Object.entries(s.resources).map(
|
|
6147
|
+
([label, r]) => `<code>${escapeHtml(label)}</code>: ${escapeHtml(r.status)}` + (r.loading ? ' <span class="st-dim">(loading)</span>' : "") + (r.hasError ? ` <span class="st-err">!${escapeHtml(r.errorName)}</span>` : "")
|
|
6148
|
+
)
|
|
6149
|
+
)
|
|
6150
|
+
)
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
6153
|
+
parts.push(
|
|
6154
|
+
section(
|
|
6155
|
+
"Overlays",
|
|
6156
|
+
s.overlays.length,
|
|
6157
|
+
ul(
|
|
6158
|
+
s.overlays.map((o) => {
|
|
6159
|
+
const kind = o.modal ? "modal" : o.menu ? "menu" : o.takesFocus ? "focusable" : "non-modal";
|
|
6160
|
+
return `<code>${escapeHtml(o.key ?? o.id)}</code> <span class="st-key">[${kind}]</span> <span class="st-dim">${o.open ? "open" : "closed"}${o.closeOnEscape ? " \xB7 esc" : ""}${o.restoreFocus ? " \xB7 restore" : ""}</span>`;
|
|
6161
|
+
})
|
|
6162
|
+
)
|
|
6163
|
+
)
|
|
6164
|
+
);
|
|
6165
|
+
parts.push(
|
|
6166
|
+
section(
|
|
6167
|
+
"Transitions",
|
|
6168
|
+
s.transitions.length,
|
|
6169
|
+
ul(
|
|
6170
|
+
s.transitions.map(
|
|
6171
|
+
(t) => `<code>${escapeHtml(t.key ?? t.id)}</code> on <code><${escapeHtml(t.nodeType)}></code> <span class="st-dim">${t.duration}ms${t.appear ? " \xB7 appear" : ""}</span>`
|
|
6172
|
+
)
|
|
6173
|
+
)
|
|
6174
|
+
)
|
|
6175
|
+
);
|
|
6176
|
+
if (s.forms !== void 0) {
|
|
6177
|
+
parts.push(
|
|
6178
|
+
section(
|
|
6179
|
+
"Forms",
|
|
6180
|
+
Object.keys(s.forms).length,
|
|
6181
|
+
ul(
|
|
6182
|
+
Object.entries(s.forms).map(
|
|
6183
|
+
([label, f]) => `<code>${escapeHtml(label)}</code>: ${f.valid ? "valid" : "invalid"} \xB7 ${escapeHtml(f.status)} \xB7 ${f.fields.length} field(s)`
|
|
6184
|
+
)
|
|
6185
|
+
)
|
|
6186
|
+
)
|
|
6187
|
+
);
|
|
6188
|
+
}
|
|
6189
|
+
if (s.i18n !== void 0) {
|
|
6190
|
+
const missing = s.i18n.missingKeys;
|
|
6191
|
+
parts.push(
|
|
6192
|
+
section(
|
|
6193
|
+
"i18n",
|
|
6194
|
+
void 0,
|
|
6195
|
+
`<p><code>${escapeHtml(s.i18n.locale)}</code> of [${s.i18n.locales.map(escapeHtml).join(", ")}]${missing !== void 0 ? ` <span class="st-dim">\xB7 missing ${missing.length}</span>` : ""}</p>`
|
|
6196
|
+
)
|
|
6197
|
+
);
|
|
6198
|
+
}
|
|
6199
|
+
parts.push(
|
|
6200
|
+
section(
|
|
6201
|
+
"Performance",
|
|
6202
|
+
s.performance.diagnostics.length,
|
|
6203
|
+
`<p class="st-dim">Structural counts, not runtime timings \u2014 not a production profiler.</p>` + ul(s.performance.diagnostics.map((d) => `<code>${escapeHtml(d.code)}</code>: ${escapeHtml(d.message)}`))
|
|
6204
|
+
)
|
|
6205
|
+
);
|
|
6206
|
+
parts.push(
|
|
6207
|
+
section(
|
|
6208
|
+
"Diagnostics",
|
|
6209
|
+
s.diagnostics.errors + s.diagnostics.warnings,
|
|
6210
|
+
`<p class="st-dim">${s.diagnostics.errors} error(s) \xB7 ${s.diagnostics.warnings} warning(s)</p>` + ul(s.diagnostics.messages.map((m) => escapeHtml(m)))
|
|
6211
|
+
)
|
|
6212
|
+
);
|
|
6213
|
+
const title = `StreetUI DevTools \u2014 ${escapeHtml(app.identity.name)}`;
|
|
6214
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${title}</title><style>${DEVTOOLS_CSS}</style></head><body class="st-devtools"><header class="st-header"><h1>${title} <span class="st-dim">v${escapeHtml(app.identity.version)}</span></h1></header><main>${parts.join("")}</main></body></html>`;
|
|
6215
|
+
}
|
|
6216
|
+
function renderNodeTree(node) {
|
|
6217
|
+
const lines = [];
|
|
6218
|
+
const walk = (n) => {
|
|
6219
|
+
const indent = " ".repeat(n.depth);
|
|
6220
|
+
const key = n.key !== void 0 ? ` #${n.key}` : "";
|
|
6221
|
+
lines.push(`${indent}<${n.type}${key}> ${n.id}`);
|
|
6222
|
+
for (const c of n.children) walk(c);
|
|
6223
|
+
};
|
|
6224
|
+
walk(node);
|
|
6225
|
+
return lines.join("\n");
|
|
6226
|
+
}
|
|
6227
|
+
var DEVTOOLS_CSS = [
|
|
6228
|
+
".st-devtools{font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;margin:0;color:#e6e6e6;background:#1e1e28}",
|
|
6229
|
+
".st-header{padding:12px 16px;border-bottom:1px solid #333;background:#15151c}",
|
|
6230
|
+
".st-header h1{font-size:14px;margin:0}",
|
|
6231
|
+
"main{padding:8px 16px}",
|
|
6232
|
+
".st-panel{margin:12px 0;border:1px solid #2c2c38;border-radius:6px;overflow:hidden}",
|
|
6233
|
+
".st-panel h2{font-size:12px;text-transform:uppercase;letter-spacing:.04em;margin:0;padding:6px 10px;background:#23232e}",
|
|
6234
|
+
".st-panel ul{list-style:none;margin:0;padding:6px 10px}",
|
|
6235
|
+
".st-panel li{padding:1px 0}",
|
|
6236
|
+
".st-panel p{margin:6px 10px}",
|
|
6237
|
+
".st-count{background:#3a3a4a;border-radius:10px;padding:0 7px;font-size:11px;float:right}",
|
|
6238
|
+
".st-dim{color:#8a8a9a}.st-key{color:#7db4ff}.st-err{color:#ff8a8a}.st-empty{color:#6a6a7a}",
|
|
6239
|
+
".st-tree{margin:6px 10px;white-space:pre;overflow:auto;color:#c8c8d4}",
|
|
6240
|
+
".st-depth{display:inline-block}.st-depth{width:calc(var(--d,0)*12px)}",
|
|
6241
|
+
"code{color:#d7d7e0}"
|
|
6242
|
+
].join("");
|
|
6243
|
+
|
|
6244
|
+
// ../devtools/src/devtools-report.ts
|
|
6245
|
+
function renderDevToolsReport(compiled, sources = {}, options = {}) {
|
|
6246
|
+
const session = createDevTools(compiled, sources, options);
|
|
6247
|
+
return renderDevToolsHTML(session.snapshot);
|
|
6248
|
+
}
|
|
6249
|
+
|
|
5400
6250
|
// src/config.ts
|
|
5401
6251
|
function defineConfig(config) {
|
|
5402
6252
|
return config;
|
|
@@ -5420,6 +6270,8 @@ function defineConfig(config) {
|
|
|
5420
6270
|
FOCUSABLE_SELECTOR,
|
|
5421
6271
|
FormBuilderImpl,
|
|
5422
6272
|
GraphNode,
|
|
6273
|
+
HeadManager,
|
|
6274
|
+
HttpError,
|
|
5423
6275
|
Lifecycle,
|
|
5424
6276
|
ListBuilderImpl,
|
|
5425
6277
|
NodeInstance,
|
|
@@ -5460,7 +6312,9 @@ function defineConfig(config) {
|
|
|
5460
6312
|
containFocus,
|
|
5461
6313
|
createAnnouncer,
|
|
5462
6314
|
createApplication,
|
|
6315
|
+
createAuthSession,
|
|
5463
6316
|
createBrowserHistory,
|
|
6317
|
+
createClient,
|
|
5464
6318
|
createContext,
|
|
5465
6319
|
createDevTools,
|
|
5466
6320
|
createForm,
|
|
@@ -5476,10 +6330,12 @@ function defineConfig(config) {
|
|
|
5476
6330
|
createStreetEvent,
|
|
5477
6331
|
defineConfig,
|
|
5478
6332
|
derived,
|
|
6333
|
+
describeError,
|
|
5479
6334
|
diagnosePerformance,
|
|
5480
6335
|
effect,
|
|
5481
6336
|
email,
|
|
5482
6337
|
environment,
|
|
6338
|
+
escapeHtml,
|
|
5483
6339
|
escapeHtmlAttr,
|
|
5484
6340
|
escapeHtmlText,
|
|
5485
6341
|
flushSync,
|
|
@@ -5511,7 +6367,9 @@ function defineConfig(config) {
|
|
|
5511
6367
|
interpolate,
|
|
5512
6368
|
isBatching,
|
|
5513
6369
|
isComponentDefinition,
|
|
6370
|
+
isHeadContribution,
|
|
5514
6371
|
isTransitionConfig,
|
|
6372
|
+
linkUpdate,
|
|
5515
6373
|
matchPattern,
|
|
5516
6374
|
matchRoutes,
|
|
5517
6375
|
maxLength,
|
|
@@ -5519,6 +6377,7 @@ function defineConfig(config) {
|
|
|
5519
6377
|
mountGraph,
|
|
5520
6378
|
mountNode,
|
|
5521
6379
|
mountRouter,
|
|
6380
|
+
mutation,
|
|
5522
6381
|
nextId,
|
|
5523
6382
|
nodeIdPrefix,
|
|
5524
6383
|
nodeTypeStats,
|
|
@@ -5535,10 +6394,15 @@ function defineConfig(config) {
|
|
|
5535
6394
|
readState,
|
|
5536
6395
|
reconcileChildren,
|
|
5537
6396
|
reconcileChildrenByPlan,
|
|
6397
|
+
renderDevToolsHTML,
|
|
6398
|
+
renderDevToolsReport,
|
|
6399
|
+
renderHead,
|
|
5538
6400
|
renderToString,
|
|
5539
6401
|
reportDiagnostic,
|
|
6402
|
+
reportError,
|
|
5540
6403
|
required,
|
|
5541
6404
|
resetIdCounter,
|
|
6405
|
+
resolveHead,
|
|
5542
6406
|
resolveTag,
|
|
5543
6407
|
resolveTransition,
|
|
5544
6408
|
resource,
|
|
@@ -5566,6 +6430,7 @@ function defineConfig(config) {
|
|
|
5566
6430
|
validateGraph,
|
|
5567
6431
|
wireComponentBehavior,
|
|
5568
6432
|
wireEvents,
|
|
6433
|
+
wireHeadBehavior,
|
|
5569
6434
|
wireOverlayBehavior,
|
|
5570
6435
|
wireReactiveList,
|
|
5571
6436
|
wireSignalBindings
|