pattern-dump 0.1.0-alpha.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/LICENSE +8 -0
- package/README.md +92 -0
- package/dist/dev-ui/main.css +1825 -0
- package/dist/dev-ui/main.js +3054 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/pd.js +4759 -0
- package/dist/types/config/index.d.ts +145 -0
- package/dist/types/contracts/dev-protocol.d.ts +624 -0
- package/dist/types/contracts/index.d.ts +294 -0
- package/package.json +76 -0
- package/templates/base/README.md +77 -0
- package/templates/base/_gitignore +3 -0
- package/templates/base/pattern-dump.config.ts +36 -0
- package/templates/base/src/scripts/globals.d.ts +6 -0
- package/templates/base/src/styles/base/_tokens.scss +8 -0
- package/templates/base/src/styles/base/document.scss +18 -0
- package/templates/base/themes/dark.theme.scss +8 -0
- package/templates/base/themes/light.theme.scss +8 -0
- package/templates/base/tsconfig.json +19 -0
- package/templates/examples/src/patterns/atoms/button/button.hbs +1 -0
- package/templates/examples/src/patterns/atoms/button/button.json +1 -0
- package/templates/examples/src/patterns/atoms/button/button.md +21 -0
- package/templates/examples/src/patterns/atoms/button/button.schema.json +11 -0
- package/templates/examples/src/patterns/atoms/button/button~icon.hbs +5 -0
- package/templates/examples/src/patterns/atoms/button/button~secondary.json +1 -0
- package/templates/examples/src/patterns/atoms/index.md +6 -0
- package/templates/examples/src/patterns/index.md +3 -0
- package/templates/examples/src/patterns/molecules/index.md +6 -0
- package/templates/examples/src/patterns/molecules/search-field/input/input.hbs +4 -0
- package/templates/examples/src/patterns/molecules/search-field/input/input.json +1 -0
- package/templates/examples/src/patterns/molecules/search-field/input/input.md +8 -0
- package/templates/examples/src/patterns/molecules/search-field/input/input.schema.json +10 -0
- package/templates/examples/src/patterns/molecules/search-field/search-field.hbs +5 -0
- package/templates/examples/src/patterns/molecules/search-field/search-field.json +1 -0
- package/templates/examples/src/patterns/molecules/search-field/search-field.md +21 -0
- package/templates/examples/src/patterns/molecules/search-field/search-field.schema.json +11 -0
- package/templates/examples/src/patterns/pages/example/example.hbs +11 -0
- package/templates/examples/src/patterns/pages/example/example.json +8 -0
- package/templates/examples/src/patterns/pages/example/example.md +11 -0
- package/templates/examples/src/patterns/pages/example/example.schema.json +14 -0
- package/templates/examples/src/patterns/pages/index.md +6 -0
- package/templates/examples/src/scripts/search-field.ts +16 -0
- package/templates/examples/src/styles/atoms/button.scss +36 -0
- package/templates/examples/src/styles/molecules/search-field/input.scss +21 -0
- package/templates/examples/src/styles/molecules/search-field.scss +13 -0
- package/templates/examples/src/styles/pages/example.scss +28 -0
|
@@ -0,0 +1,3054 @@
|
|
|
1
|
+
// ../../packages/contracts/src/dev-protocol.ts
|
|
2
|
+
var SCOPE_BOUNDARY = `## Scope \u2014 the boundary is part of the artifact
|
|
3
|
+
|
|
4
|
+
**Covers** \u2014 machine-detectable, static, single-render (no browser layout):
|
|
5
|
+
- **HTML content-model** (html-validate) \u2014 nesting/permitted-content violations.
|
|
6
|
+
- **Structural a11y** (axe, alwaysOn tier) \u2014 labels, names, ARIA, roles, image-alt.
|
|
7
|
+
- **Schema** \u2014 required/shape violations against \`component.schema.json\`.
|
|
8
|
+
- **Render outcome** \u2014 blank / error / ok (metadata, never itself a defect).
|
|
9
|
+
|
|
10
|
+
**Deliberately NOT covered \u2014 named boundaries, not misses:**
|
|
11
|
+
- **Rendered-contrast (axe needsRendering tier)** \u2014 contrast, aria-hidden-focus, duplicate-id-aria.
|
|
12
|
+
These require real browser layout/paint; the static jsdom sweep cannot produce them. The
|
|
13
|
+
per-component panel shows them (live iframe); this aggregate does not. Fewer rule types here is a
|
|
14
|
+
scope boundary, not a gap.
|
|
15
|
+
- **Structural/graph findings** \u2014 dead subtrees, a shared-base "component", flattened variant families.
|
|
16
|
+
Those are the style graph's lens, not this one.
|
|
17
|
+
- **PD reporting gaps** \u2014 PD under-claiming (e.g. missing provenance) is not a subject defect.
|
|
18
|
+
- **Interactive / behavioural** \u2014 focus traps, keyboard flow, escape handling. Single static render only.
|
|
19
|
+
- **Data-masked** \u2014 a component that renders empty for want of data won't throw the violations it would
|
|
20
|
+
when populated; findings are annotated with render outcome wherever it is blank.
|
|
21
|
+
|
|
22
|
+
A component with zero findings is **not "clean" \u2014 it is "clean of static machine-detectable defects."**
|
|
23
|
+
That is the honest claim; never "clean".`;
|
|
24
|
+
function buildAuditRollup(graph, opts = {}) {
|
|
25
|
+
const total = graph.components.length;
|
|
26
|
+
const affected = new Set(graph.occurrences.map((o) => o.component));
|
|
27
|
+
const cleanPct = total > 0 ? Math.round(100 * (total - affected.size) / total) : 100;
|
|
28
|
+
const outcomes = graph.components.reduce((a, c) => {
|
|
29
|
+
a[c.render] = (a[c.render] ?? 0) + 1;
|
|
30
|
+
return a;
|
|
31
|
+
}, {});
|
|
32
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const o of graph.occurrences) {
|
|
34
|
+
const k = `${o.engine} ${o.ruleId}`;
|
|
35
|
+
const r = perRule.get(k) ?? { engine: o.engine, ruleId: o.ruleId, occ: 0, comps: /* @__PURE__ */ new Set(), tags: /* @__PURE__ */ new Set() };
|
|
36
|
+
r.occ += 1;
|
|
37
|
+
r.comps.add(o.component);
|
|
38
|
+
r.tags.add(o.tag);
|
|
39
|
+
perRule.set(k, r);
|
|
40
|
+
}
|
|
41
|
+
const rulesByBlast = [...perRule.values()].sort((a, b) => b.comps.size - a.comps.size || b.occ - a.occ || `${a.engine}:${a.ruleId}`.localeCompare(`${b.engine}:${b.ruleId}`)).map((r) => ({ engine: r.engine, ruleId: r.ruleId, components: r.comps.size, occurrences: r.occ, tags: [...r.tags].sort() }));
|
|
42
|
+
const occByComp = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const o of graph.occurrences) {
|
|
44
|
+
const a = occByComp.get(o.component) ?? [];
|
|
45
|
+
a.push(o);
|
|
46
|
+
occByComp.set(o.component, a);
|
|
47
|
+
}
|
|
48
|
+
const perComponent = [];
|
|
49
|
+
for (const c of graph.components) {
|
|
50
|
+
const occ = occByComp.get(c.id) ?? [];
|
|
51
|
+
if (occ.length === 0 && c.render === "ok") continue;
|
|
52
|
+
perComponent.push({
|
|
53
|
+
id: c.id,
|
|
54
|
+
render: c.render,
|
|
55
|
+
occurrences: [...occ].sort((a, b) => `${a.engine}:${a.ruleId}`.localeCompare(`${b.engine}:${b.ruleId}`)).map((o) => ({ engine: o.engine, ruleId: o.ruleId, tag: o.tag, message: o.message, ...o.domPath ? { domPath: o.domPath } : {} }))
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
...opts.subjectSha ? { subjectSha: opts.subjectSha } : {},
|
|
60
|
+
headline: `${graph.occurrences.length} machine-detectable static defects across ${affected.size} of ${total} components \u2014 ${cleanPct}% clean of static machine-detectable defects.`,
|
|
61
|
+
totals: { components: total, affected: affected.size, cleanPct, defects: graph.occurrences.length },
|
|
62
|
+
renderOutcomes: { ok: outcomes["ok"] ?? 0, blank: outcomes["blank"] ?? 0, error: outcomes["error"] ?? 0 },
|
|
63
|
+
rulesByBlast,
|
|
64
|
+
perComponent,
|
|
65
|
+
scope: SCOPE_BOUNDARY
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function previewUrl(id, variant, theme) {
|
|
69
|
+
const params = new URLSearchParams();
|
|
70
|
+
if (variant) params.set("variant", variant);
|
|
71
|
+
if (theme) params.set("theme", theme);
|
|
72
|
+
const q = params.toString() ? `?${params.toString()}` : "";
|
|
73
|
+
return `${PD_ROUTE_PREFIX.preview}${id}${q}`;
|
|
74
|
+
}
|
|
75
|
+
function renderedSourceUrl(id, variant) {
|
|
76
|
+
const q = variant ? `?variant=${encodeURIComponent(variant)}` : "";
|
|
77
|
+
return `${PD_ROUTE_PREFIX.source}${id}/rendered${q}`;
|
|
78
|
+
}
|
|
79
|
+
function dataSourceUrl(id, variant) {
|
|
80
|
+
const q = variant ? `?variant=${encodeURIComponent(variant)}` : "";
|
|
81
|
+
return `${PD_ROUTE_PREFIX.source}${id}/data${q}`;
|
|
82
|
+
}
|
|
83
|
+
function overviewUrl(path) {
|
|
84
|
+
return `${PD_ROUTE_PREFIX.overview}${path}`;
|
|
85
|
+
}
|
|
86
|
+
var PD_ROUTES = {
|
|
87
|
+
/** GET → ComponentTree */
|
|
88
|
+
tree: "/api/tree",
|
|
89
|
+
/** GET → AuditResponse (500 → ApiError) */
|
|
90
|
+
audit: "/api/audit",
|
|
91
|
+
/** GET → ThemeSummary[] */
|
|
92
|
+
themes: "/api/themes",
|
|
93
|
+
/** GET → VersionInfo */
|
|
94
|
+
version: "/api/version",
|
|
95
|
+
/** GET → ShellClientConfig */
|
|
96
|
+
config: "/api/config",
|
|
97
|
+
/** The shell bundle, stylesheet and vendored assets. */
|
|
98
|
+
mainJs: "/_pd/main.js",
|
|
99
|
+
mainCss: "/_pd/main.css",
|
|
100
|
+
axeCore: "/_pd/axe-core.js",
|
|
101
|
+
/** GET → RuleTiers */
|
|
102
|
+
ruleTiers: "/_pd/rule-tiers.json",
|
|
103
|
+
shikiCss: "/_pd/shiki.css",
|
|
104
|
+
/** Document-base layer (reset + font-face). */
|
|
105
|
+
baseCss: "/_pd/base.css"
|
|
106
|
+
};
|
|
107
|
+
var PD_ROUTE_PREFIX = {
|
|
108
|
+
component: "/api/component/",
|
|
109
|
+
overview: "/api/overview/",
|
|
110
|
+
preview: "/preview/",
|
|
111
|
+
source: "/source/",
|
|
112
|
+
componentCss: "/_pd/components/",
|
|
113
|
+
themeCss: "/_pd/themes/",
|
|
114
|
+
script: "/_pd/scripts/",
|
|
115
|
+
assets: "/assets/"
|
|
116
|
+
};
|
|
117
|
+
function componentUrl(id, variant) {
|
|
118
|
+
const q = variant ? `?variant=${encodeURIComponent(variant)}` : "";
|
|
119
|
+
return `${PD_ROUTE_PREFIX.component}${id}${q}`;
|
|
120
|
+
}
|
|
121
|
+
function themeCssUrl(name) {
|
|
122
|
+
return `${PD_ROUTE_PREFIX.themeCss}${encodeURIComponent(name)}.css`;
|
|
123
|
+
}
|
|
124
|
+
function normalizeBasePath(basePath2) {
|
|
125
|
+
const trimmed = (basePath2 ?? "").trim().replace(/^\/+|\/+$/g, "");
|
|
126
|
+
return trimmed ? `/${trimmed}/` : "/";
|
|
127
|
+
}
|
|
128
|
+
function staticPathFor(liveUrl) {
|
|
129
|
+
const url = new URL(liveUrl, "http://pd.invalid");
|
|
130
|
+
const path = url.pathname;
|
|
131
|
+
const variant = url.searchParams.get("variant") || void 0;
|
|
132
|
+
const suffix = (ext) => variant ? `~${variant}${ext}` : `index${ext}`;
|
|
133
|
+
const under = (prefix) => path.slice(prefix.length);
|
|
134
|
+
if (path.startsWith(PD_ROUTE_PREFIX.component)) return `api/component/${under(PD_ROUTE_PREFIX.component)}/${suffix(".json")}`;
|
|
135
|
+
if (path.startsWith(PD_ROUTE_PREFIX.overview)) return `api/overview/${under(PD_ROUTE_PREFIX.overview)}/index.json`;
|
|
136
|
+
if (path.startsWith("/api/")) return `${path.slice(1)}.json`;
|
|
137
|
+
if (path.startsWith(PD_ROUTE_PREFIX.preview)) return `preview/${under(PD_ROUTE_PREFIX.preview)}/${suffix(".html")}`;
|
|
138
|
+
if (path.startsWith(PD_ROUTE_PREFIX.source)) {
|
|
139
|
+
const rest = under(PD_ROUTE_PREFIX.source);
|
|
140
|
+
const facet = rest.endsWith("/rendered") ? "rendered" : rest.endsWith("/data") ? "data" : null;
|
|
141
|
+
if (facet) {
|
|
142
|
+
const id = rest.slice(0, -(facet.length + 1));
|
|
143
|
+
return `source/${id}/${facet}${variant ? `~${variant}` : ""}.html`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (path.startsWith("/_pd/") || path.startsWith(PD_ROUTE_PREFIX.assets)) return path.slice(1);
|
|
147
|
+
const route = path.replace(/^\/+|\/+$/g, "");
|
|
148
|
+
return route ? `${route}/index.html` : "index.html";
|
|
149
|
+
}
|
|
150
|
+
var PD_PROTOCOL_VERSION = 1;
|
|
151
|
+
|
|
152
|
+
// src/core/dom.ts
|
|
153
|
+
var dom = {};
|
|
154
|
+
function initDom(app) {
|
|
155
|
+
dom.iframe = document.getElementById("preview-frame");
|
|
156
|
+
dom.singlePreview = document.getElementById("single-preview");
|
|
157
|
+
dom.categoryOverview = document.getElementById("category-overview");
|
|
158
|
+
dom.mainEl = app.querySelector(".main");
|
|
159
|
+
dom.stageView = app.querySelector(".stage-view");
|
|
160
|
+
dom.variantBar = app.querySelector(".variant-bar");
|
|
161
|
+
dom.panel = app.querySelector(".panel");
|
|
162
|
+
dom.previewCaption = app.querySelector(".preview-caption");
|
|
163
|
+
dom.sidebarTree = app.querySelector(".sidebar-tree");
|
|
164
|
+
dom.sidebarEl = app.querySelector(".sidebar");
|
|
165
|
+
dom.railBrandEl = app.querySelector(".rail-brand");
|
|
166
|
+
dom.searchInput = app.querySelector(".sidebar-search input");
|
|
167
|
+
dom.showInternalsCheck = app.querySelector(".show-internals-check");
|
|
168
|
+
dom.hidePackagesCheck = app.querySelector(".hide-packages-check");
|
|
169
|
+
dom.hidePackagesLabel = app.querySelector(".hide-packages-label");
|
|
170
|
+
dom.variantSelect = app.querySelector(".variant-select");
|
|
171
|
+
dom.themePickerList = app.querySelector(".theme-picker-list");
|
|
172
|
+
dom.themePickerCurrentName = app.querySelector(".theme-picker-current-name");
|
|
173
|
+
dom.themePickerSwatch = app.querySelector(".theme-picker-swatch");
|
|
174
|
+
dom.themePickerReadout = app.querySelector(".theme-picker-readout");
|
|
175
|
+
dom.panelToggleBtn = app.querySelector(".panel-toggle-btn");
|
|
176
|
+
dom.railToggleClusterEl = app.querySelector(".rail-toggle-cluster");
|
|
177
|
+
dom.railToggleLabelEl = app.querySelector(".rail-toggle-label");
|
|
178
|
+
dom.panelContent = app.querySelector(".panel-content");
|
|
179
|
+
dom.railStatus = app.querySelector(".rail-status");
|
|
180
|
+
dom.railStatusLabel = dom.railStatus.querySelector(".rail-status-label");
|
|
181
|
+
dom.dockEl = app.querySelector(".dock");
|
|
182
|
+
dom.dockToggleBtn = app.querySelector(".dock-toggle-btn");
|
|
183
|
+
dom.dockTabBtns = Array.from(app.querySelectorAll(".dock-tab"));
|
|
184
|
+
dom.dockHbsPanel = document.getElementById("dock-panel-hbs");
|
|
185
|
+
dom.dockHtmlPanel = document.getElementById("dock-panel-html");
|
|
186
|
+
dom.dockConsolePanel = document.getElementById("dock-panel-console");
|
|
187
|
+
dom.dockDiagnosticsPanel = document.getElementById("dock-panel-diagnostics");
|
|
188
|
+
dom.hbsBody = dom.dockHbsPanel.querySelector(".source-body");
|
|
189
|
+
dom.htmlBody = dom.dockHtmlPanel.querySelector(".source-body");
|
|
190
|
+
dom.consoleEl = dom.dockConsolePanel.querySelector(".console");
|
|
191
|
+
dom.diagnosticsEl = dom.dockDiagnosticsPanel.querySelector(".diagnostics");
|
|
192
|
+
dom.dockCopyBtn = app.querySelector(".dock-copy-btn");
|
|
193
|
+
dom.dockFacetsEl = app.querySelector(".dock-facets");
|
|
194
|
+
dom.dockFacetBtns = Array.from(app.querySelectorAll(".dock-facet"));
|
|
195
|
+
dom.dockFacetDataBtn = document.getElementById("dock-facet-data");
|
|
196
|
+
dom.dockFacetSchemaBtn = document.getElementById("dock-facet-schema");
|
|
197
|
+
dom.sourceFacetsEl = app.querySelector(".source-facets");
|
|
198
|
+
dom.dockDataPanel = document.getElementById("dock-panel-data");
|
|
199
|
+
dom.dockSchemaPanel = document.getElementById("dock-panel-schema");
|
|
200
|
+
dom.dataBody = dom.dockDataPanel.querySelector(".source-body");
|
|
201
|
+
dom.schemaBody = dom.dockSchemaPanel.querySelector(".source-body");
|
|
202
|
+
dom.variantsGalleryEl = document.getElementById("variants-gallery");
|
|
203
|
+
dom.viewportToolbar = app.querySelector(".rail-vp-cluster");
|
|
204
|
+
dom.vpReadout = app.querySelector(".vp-readout");
|
|
205
|
+
dom.vpModeLabelEl = app.querySelector(".vp-mode-label");
|
|
206
|
+
dom.vpDiscoBtn = app.querySelector(".vp-disco-btn");
|
|
207
|
+
dom.cvdFeMatrix = document.getElementById("cvd-matrix-vals");
|
|
208
|
+
dom.a11yDetails = app.querySelector(".a11y-picker");
|
|
209
|
+
dom.cvdTypeGroup = app.querySelector(".cvd-type-group");
|
|
210
|
+
dom.cvdSeveritySlider = app.querySelector(".cvd-severity-slider");
|
|
211
|
+
dom.fullpageDialog = document.getElementById("fullpage-dialog");
|
|
212
|
+
dom.fullpageFrame = document.getElementById("fullpage-frame");
|
|
213
|
+
dom.fullpageCloseBtn = dom.fullpageDialog.querySelector(".fullpage-close");
|
|
214
|
+
dom.markOverlay = document.createElement("div");
|
|
215
|
+
dom.markOverlay.className = "pd-mark-overlay";
|
|
216
|
+
dom.markOverlay.setAttribute("aria-hidden", "true");
|
|
217
|
+
dom.markOverlay.innerHTML = '<span class="pd-mark pd-mark-tl"></span><span class="pd-mark pd-mark-tr"></span><span class="pd-mark pd-mark-bl"></span><span class="pd-mark pd-mark-br"></span>';
|
|
218
|
+
dom.markOverlay.hidden = true;
|
|
219
|
+
dom.stageView.appendChild(dom.markOverlay);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/core/session.ts
|
|
223
|
+
var session = {
|
|
224
|
+
tree: { roots: [] },
|
|
225
|
+
componentRegistry: /* @__PURE__ */ new Map(),
|
|
226
|
+
navCategoryOrder: /* @__PURE__ */ new Map(),
|
|
227
|
+
themes: [],
|
|
228
|
+
themeSwitch: "attribute",
|
|
229
|
+
serverDefaultTheme: "",
|
|
230
|
+
needsRenderingRules: [],
|
|
231
|
+
current: null,
|
|
232
|
+
currentVariant: null,
|
|
233
|
+
stageMode: "single",
|
|
234
|
+
searchQuery: "",
|
|
235
|
+
renderedHtmlCache: null,
|
|
236
|
+
rawRenderedHtml: null,
|
|
237
|
+
dataCache: null,
|
|
238
|
+
dataRaw: null,
|
|
239
|
+
renderedA11y: [],
|
|
240
|
+
scanPending: false,
|
|
241
|
+
lastPdWidth: null
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/core/runtime.ts
|
|
245
|
+
var runtime = typeof window !== "undefined" && window.__pd || { mode: "live", wsPort: 4001 };
|
|
246
|
+
var isStatic = runtime.mode === "static";
|
|
247
|
+
var auditAvailable = runtime.mode === "live" || runtime.audit !== false;
|
|
248
|
+
var basePath = runtime.mode === "static" ? normalizeBasePath(runtime.basePath) : "/";
|
|
249
|
+
function resourceUrl(liveUrl) {
|
|
250
|
+
return isStatic && liveUrl.startsWith("/") ? `${basePath}${staticPathFor(liveUrl)}` : liveUrl;
|
|
251
|
+
}
|
|
252
|
+
function routeHref(route) {
|
|
253
|
+
return basePath === "/" ? route : `${basePath}${route.replace(/^\/+/, "")}`;
|
|
254
|
+
}
|
|
255
|
+
function routeFromPathname(pathname) {
|
|
256
|
+
if (basePath === "/" || !pathname.startsWith(basePath)) return pathname;
|
|
257
|
+
return `/${pathname.slice(basePath.length)}`;
|
|
258
|
+
}
|
|
259
|
+
function installStaticFetch() {
|
|
260
|
+
if (!isStatic || typeof window === "undefined") return;
|
|
261
|
+
const original = window.fetch.bind(window);
|
|
262
|
+
window.fetch = (input, init2) => original(typeof input === "string" ? resourceUrl(input) : input, init2);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/core/store.ts
|
|
266
|
+
var KNOWN_VP_MODES = /* @__PURE__ */ new Set(["fit", "content", "mobile", "tablet", "desktop", "fullscreen"]);
|
|
267
|
+
var KNOWN_CVD_TYPES = /* @__PURE__ */ new Set(
|
|
268
|
+
["none", "protanopia", "deuteranopia", "tritanopia", "achromatopsia"]
|
|
269
|
+
);
|
|
270
|
+
var DEFAULT_CVD_STATE = { type: "none", severity: 0 };
|
|
271
|
+
var DEFAULT_URL_STATE = {
|
|
272
|
+
pattern: null,
|
|
273
|
+
variant: null,
|
|
274
|
+
viewport: "fit",
|
|
275
|
+
cvd: DEFAULT_CVD_STATE
|
|
276
|
+
};
|
|
277
|
+
var DEFAULT_LOCAL_STATE = {
|
|
278
|
+
theme: "",
|
|
279
|
+
dims: { sidebarW: "", panelW: "", dockH: "" },
|
|
280
|
+
dockVisibility: { rightPanelOpen: false, consoleDockOpen: true },
|
|
281
|
+
panelTab: "docs",
|
|
282
|
+
dock: { dockTab: "source", sourceFacet: "hbs" },
|
|
283
|
+
navOpenPaths: /* @__PURE__ */ new Set(),
|
|
284
|
+
navFilters: { showPrivate: false, hidePackages: false }
|
|
285
|
+
};
|
|
286
|
+
var STORAGE_KEY = "pd-view-state";
|
|
287
|
+
function toJSON(local) {
|
|
288
|
+
return JSON.stringify({
|
|
289
|
+
theme: local.theme,
|
|
290
|
+
dims: local.dims,
|
|
291
|
+
dockVisibility: local.dockVisibility,
|
|
292
|
+
panelTab: local.panelTab,
|
|
293
|
+
dock: local.dock,
|
|
294
|
+
navOpenPaths: [...local.navOpenPaths],
|
|
295
|
+
// Set → Array (sole conversion point)
|
|
296
|
+
navFilters: local.navFilters
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function deserializeLocalState(raw) {
|
|
300
|
+
const theme = typeof raw.theme === "string" ? raw.theme : DEFAULT_LOCAL_STATE.theme;
|
|
301
|
+
const rawDims = raw.dims;
|
|
302
|
+
const dims = {
|
|
303
|
+
sidebarW: typeof rawDims?.sidebarW === "string" ? rawDims.sidebarW : DEFAULT_LOCAL_STATE.dims.sidebarW,
|
|
304
|
+
panelW: typeof rawDims?.panelW === "string" ? rawDims.panelW : DEFAULT_LOCAL_STATE.dims.panelW,
|
|
305
|
+
dockH: typeof rawDims?.dockH === "string" ? rawDims.dockH : DEFAULT_LOCAL_STATE.dims.dockH
|
|
306
|
+
};
|
|
307
|
+
const rawVis = raw.dockVisibility;
|
|
308
|
+
const dockVisibility = {
|
|
309
|
+
rightPanelOpen: typeof rawVis?.rightPanelOpen === "boolean" ? rawVis.rightPanelOpen : DEFAULT_LOCAL_STATE.dockVisibility.rightPanelOpen,
|
|
310
|
+
consoleDockOpen: typeof rawVis?.consoleDockOpen === "boolean" ? rawVis.consoleDockOpen : DEFAULT_LOCAL_STATE.dockVisibility.consoleDockOpen
|
|
311
|
+
};
|
|
312
|
+
const panelTab = raw.panelTab === "docs" || raw.panelTab === "relationships" || raw.panelTab === "audit" ? raw.panelTab : DEFAULT_LOCAL_STATE.panelTab;
|
|
313
|
+
const rawDock = raw.dock;
|
|
314
|
+
let dock;
|
|
315
|
+
if (rawDock?.dockTab === "source") {
|
|
316
|
+
const sf = rawDock.sourceFacet;
|
|
317
|
+
dock = {
|
|
318
|
+
dockTab: "source",
|
|
319
|
+
sourceFacet: sf === "hbs" || sf === "data" || sf === "schema" ? sf : "hbs"
|
|
320
|
+
};
|
|
321
|
+
} else if (rawDock?.dockTab === "html" || rawDock?.dockTab === "diagnostics" || rawDock?.dockTab === "console") {
|
|
322
|
+
dock = { dockTab: rawDock.dockTab };
|
|
323
|
+
} else {
|
|
324
|
+
dock = DEFAULT_LOCAL_STATE.dock;
|
|
325
|
+
}
|
|
326
|
+
const rawPaths = raw.navOpenPaths;
|
|
327
|
+
const navOpenPaths = Array.isArray(rawPaths) ? new Set(rawPaths.filter((p) => typeof p === "string")) : /* @__PURE__ */ new Set();
|
|
328
|
+
const rawFilters = raw.navFilters;
|
|
329
|
+
const navFilters = {
|
|
330
|
+
showPrivate: typeof rawFilters?.showPrivate === "boolean" ? rawFilters.showPrivate : DEFAULT_LOCAL_STATE.navFilters.showPrivate,
|
|
331
|
+
hidePackages: typeof rawFilters?.hidePackages === "boolean" ? rawFilters.hidePackages : DEFAULT_LOCAL_STATE.navFilters.hidePackages
|
|
332
|
+
};
|
|
333
|
+
return { theme, dims, dockVisibility, panelTab, dock, navOpenPaths, navFilters };
|
|
334
|
+
}
|
|
335
|
+
function fromJSON(json) {
|
|
336
|
+
try {
|
|
337
|
+
return deserializeLocalState(JSON.parse(json));
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function parseCvd(raw) {
|
|
343
|
+
if (!raw) return DEFAULT_CVD_STATE;
|
|
344
|
+
const colon = raw.indexOf(":");
|
|
345
|
+
const typeRaw = colon === -1 ? raw : raw.slice(0, colon);
|
|
346
|
+
const sevRaw = colon === -1 ? "" : raw.slice(colon + 1);
|
|
347
|
+
const type = KNOWN_CVD_TYPES.has(typeRaw) ? typeRaw : "none";
|
|
348
|
+
const severity = Math.min(1, Math.max(0, parseFloat(sevRaw) || 0));
|
|
349
|
+
return { type, severity };
|
|
350
|
+
}
|
|
351
|
+
function parseUrlState(location) {
|
|
352
|
+
const raw = routeFromPathname(location.pathname).slice(1);
|
|
353
|
+
const pattern = raw || null;
|
|
354
|
+
const hash = location.hash.slice(1);
|
|
355
|
+
const variant = hash || null;
|
|
356
|
+
const params = new URLSearchParams(location.search);
|
|
357
|
+
const vpRaw = params.get("vp");
|
|
358
|
+
const viewport = vpRaw !== null && KNOWN_VP_MODES.has(vpRaw) ? vpRaw : "fit";
|
|
359
|
+
const cvd = parseCvd(params.get("cvd"));
|
|
360
|
+
return { pattern, variant, viewport, cvd };
|
|
361
|
+
}
|
|
362
|
+
function reconcile(url, local, ctx, themeOverride = null) {
|
|
363
|
+
let variant = url.variant;
|
|
364
|
+
if (variant !== null && ctx.availableVariants !== null) {
|
|
365
|
+
if (!ctx.availableVariants.includes(variant)) variant = null;
|
|
366
|
+
}
|
|
367
|
+
let viewport = KNOWN_VP_MODES.has(url.viewport) ? url.viewport : "fit";
|
|
368
|
+
if (viewport === "fullscreen" && url.pattern === null) viewport = "fit";
|
|
369
|
+
const navOpenPaths = new Set(
|
|
370
|
+
[...local.navOpenPaths].filter((p) => ctx.knownFolderPaths.has(p))
|
|
371
|
+
);
|
|
372
|
+
let theme = local.theme;
|
|
373
|
+
let stripThemeParam = false;
|
|
374
|
+
if (themeOverride !== null && ctx.knownThemes.has(themeOverride)) {
|
|
375
|
+
theme = themeOverride;
|
|
376
|
+
stripThemeParam = true;
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
url: { ...url, variant, viewport },
|
|
380
|
+
local: { ...local, theme, navOpenPaths },
|
|
381
|
+
stripThemeParam
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function createStore() {
|
|
385
|
+
let local = { ...DEFAULT_LOCAL_STATE, navOpenPaths: /* @__PURE__ */ new Set() };
|
|
386
|
+
let url = { ...DEFAULT_URL_STATE };
|
|
387
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
388
|
+
function notify() {
|
|
389
|
+
for (const fn of listeners) fn();
|
|
390
|
+
}
|
|
391
|
+
function persist(state) {
|
|
392
|
+
try {
|
|
393
|
+
localStorage.setItem(STORAGE_KEY, toJSON(state));
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const storeObj = {
|
|
398
|
+
get local() {
|
|
399
|
+
return local;
|
|
400
|
+
},
|
|
401
|
+
get url() {
|
|
402
|
+
return url;
|
|
403
|
+
},
|
|
404
|
+
setLocal(patch) {
|
|
405
|
+
local = { ...local, ...patch };
|
|
406
|
+
persist(local);
|
|
407
|
+
notify();
|
|
408
|
+
},
|
|
409
|
+
setUrl(patch) {
|
|
410
|
+
url = { ...url, ...patch };
|
|
411
|
+
notify();
|
|
412
|
+
},
|
|
413
|
+
subscribe(fn) {
|
|
414
|
+
listeners.add(fn);
|
|
415
|
+
return () => {
|
|
416
|
+
listeners.delete(fn);
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
hydrate(ctx) {
|
|
420
|
+
const parsedUrl = parseUrlState(window.location);
|
|
421
|
+
const themeOverride = new URLSearchParams(window.location.search).get("theme");
|
|
422
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
423
|
+
const storedLocal = stored !== null ? fromJSON(stored) ?? { ...DEFAULT_LOCAL_STATE, navOpenPaths: /* @__PURE__ */ new Set() } : { ...DEFAULT_LOCAL_STATE, navOpenPaths: /* @__PURE__ */ new Set() };
|
|
424
|
+
const { url: reconciledUrl, local: reconciledLocal, stripThemeParam } = reconcile(parsedUrl, storedLocal, ctx, themeOverride);
|
|
425
|
+
url = reconciledUrl;
|
|
426
|
+
local = reconciledLocal;
|
|
427
|
+
persist(local);
|
|
428
|
+
notify();
|
|
429
|
+
return stripThemeParam;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return storeObj;
|
|
433
|
+
}
|
|
434
|
+
var store = createStore();
|
|
435
|
+
|
|
436
|
+
// src/dock/axe-map.ts
|
|
437
|
+
function mapAxeResults(results) {
|
|
438
|
+
const out = [];
|
|
439
|
+
function mapNodes(v, needsReview) {
|
|
440
|
+
const impact = v["impact"];
|
|
441
|
+
const severity = impact === "critical" || impact === "serious" ? "error" : "warning";
|
|
442
|
+
for (const n of v["nodes"] ?? []) {
|
|
443
|
+
const target = (n["target"] ?? []).join(" > ");
|
|
444
|
+
const any0 = n["any"]?.[0];
|
|
445
|
+
const message = needsReview ? any0?.["message"] ?? n["failureSummary"] ?? v["description"] : n["failureSummary"] ?? v["description"];
|
|
446
|
+
const d = {
|
|
447
|
+
kind: "A11Y",
|
|
448
|
+
source: "axe",
|
|
449
|
+
severity,
|
|
450
|
+
message,
|
|
451
|
+
ruleId: v["id"],
|
|
452
|
+
target,
|
|
453
|
+
nodeHtml: n["html"] ?? "",
|
|
454
|
+
help: v["help"],
|
|
455
|
+
helpUrl: v["helpUrl"],
|
|
456
|
+
...needsReview ? { needsReview: true } : {}
|
|
457
|
+
};
|
|
458
|
+
if (v["id"] === "color-contrast") {
|
|
459
|
+
const data = any0?.["data"];
|
|
460
|
+
if (data?.["fgColor"]) {
|
|
461
|
+
d.contrast = {
|
|
462
|
+
fg: data["fgColor"],
|
|
463
|
+
bg: data["bgColor"],
|
|
464
|
+
ratio: data["contrastRatio"],
|
|
465
|
+
expected: parseFloat(data["expectedContrastRatio"])
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
out.push(d);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const v of results.violations ?? []) {
|
|
473
|
+
mapNodes(v, false);
|
|
474
|
+
}
|
|
475
|
+
for (const v of results.incomplete ?? []) {
|
|
476
|
+
mapNodes(v, true);
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/icons/index.ts
|
|
482
|
+
var ICONS = {
|
|
483
|
+
"warning": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/></svg>`,
|
|
484
|
+
"x": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>`,
|
|
485
|
+
"panel": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm-64,80h64v16H136Zm0-16V88h64v16Zm0,48h64v16H136Zm64-80H136V56h64ZM56,56h64V200H56ZM200,200H136V184h64v16Z"/></svg>`,
|
|
486
|
+
"panel-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40ZM56,56h72V200H56Z"/></svg>`,
|
|
487
|
+
"dock": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,16v64H56V56Zm-96,80v64H88V136Zm16,0h16v64H120Zm32,0h16v64H152Zm-96,0H72v64H56Zm144,64H184V136h16v64Z"/></svg>`,
|
|
488
|
+
"dock-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,16v72H56V56Z"/></svg>`,
|
|
489
|
+
// ---- Viewport cluster ----
|
|
490
|
+
"vp-fit": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M152,96V48a8,8,0,0,1,16,0V88h40a8,8,0,0,1,0,16H160A8,8,0,0,1,152,96ZM96,152H48a8,8,0,0,0,0,16H88v40a8,8,0,0,0,16,0V160A8,8,0,0,0,96,152Zm112,0H160a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V168h40a8,8,0,0,0,0-16ZM96,40a8,8,0,0,0-8,8V88H48a8,8,0,0,0,0,16H96a8,8,0,0,0,8-8V48A8,8,0,0,0,96,40Z"/></svg>`,
|
|
491
|
+
"vp-fit-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M152,96V48a8,8,0,0,1,13.66-5.66l48,48A8,8,0,0,1,208,104H160A8,8,0,0,1,152,96ZM96,152H48a8,8,0,0,0-5.66,13.66l48,48A8,8,0,0,0,104,208V160A8,8,0,0,0,96,152ZM99.06,40.61a8,8,0,0,0-8.72,1.73l-48,48A8,8,0,0,0,48,104H96a8,8,0,0,0,8-8V48A8,8,0,0,0,99.06,40.61ZM208,152H160a8,8,0,0,0-8,8v48a8,8,0,0,0,13.66,5.66l48-48A8,8,0,0,0,208,152Z"/></svg>`,
|
|
492
|
+
// FrameCorners — bracket marks at four corners; the empty center represents the
|
|
493
|
+
// content that the frame sizes itself to fit. Regular = inactive, fill = active.
|
|
494
|
+
"vp-content": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M96,48H48V96a8,8,0,0,1-16,0V48A16,16,0,0,1,48,32H96a8,8,0,0,1,0,16ZM216,32H168a8,8,0,0,0,0,16h48V96a8,8,0,0,0,16,0V48A16,16,0,0,0,216,32Zm8,112a8,8,0,0,0-8,8v48H168a8,8,0,0,0,0,16h48a16,16,0,0,0,16-16V152A8,8,0,0,0,224,144ZM96,208H48V160a8,8,0,0,0-16,0v48a16,16,0,0,0,16,16H96a8,8,0,0,0,0-16Z"/></svg>`,
|
|
495
|
+
"vp-content-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M96,40H48A24,24,0,0,0,24,64v48a8,8,0,0,0,16,0V64a8,8,0,0,1,8-8H96a8,8,0,0,0,0-16ZM232,152a8,8,0,0,0-8,8v32H192a8,8,0,0,0,0,16h32a24,24,0,0,0,24-24V160A8,8,0,0,0,232,152ZM208,40H160a8,8,0,0,0,0,16h32v32a8,8,0,0,0,16,0V64A24,24,0,0,0,208,40ZM64,160a8,8,0,0,0-16,0v32a24,24,0,0,0,24,24H96a8,8,0,0,0,0-16H72Z"/></svg>`,
|
|
496
|
+
"vp-mobile": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M176,16H80A24,24,0,0,0,56,40V216a24,24,0,0,0,24,24h96a24,24,0,0,0,24-24V40A24,24,0,0,0,176,16ZM72,64H184V192H72Zm8-32h96a8,8,0,0,1,8,8v8H72V40A8,8,0,0,1,80,32Zm96,192H80a8,8,0,0,1-8-8v-8H184v8A8,8,0,0,1,176,224Z"/></svg>`,
|
|
497
|
+
"vp-mobile-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M176,16H80A24,24,0,0,0,56,40V216a24,24,0,0,0,24,24h96a24,24,0,0,0,24-24V40A24,24,0,0,0,176,16ZM80,32h96a8,8,0,0,1,8,8v8H72V40A8,8,0,0,1,80,32Zm96,192H80a8,8,0,0,1-8-8v-8H184v8A8,8,0,0,1,176,224Z"/></svg>`,
|
|
498
|
+
"vp-tablet": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M192,24H64A24,24,0,0,0,40,48V208a24,24,0,0,0,24,24H192a24,24,0,0,0,24-24V48A24,24,0,0,0,192,24ZM56,72H200V184H56Zm8-32H192a8,8,0,0,1,8,8v8H56V48A8,8,0,0,1,64,40ZM192,216H64a8,8,0,0,1-8-8v-8H200v8A8,8,0,0,1,192,216Z"/></svg>`,
|
|
499
|
+
"vp-tablet-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M192,24H64A24,24,0,0,0,40,48V208a24,24,0,0,0,24,24H192a24,24,0,0,0,24-24V48A24,24,0,0,0,192,24ZM64,40H192a8,8,0,0,1,8,8v8H56V48A8,8,0,0,1,64,40ZM192,216H64a8,8,0,0,1-8-8v-8H200v8A8,8,0,0,1,192,216Z"/></svg>`,
|
|
500
|
+
"vp-desktop": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M208,40H48A24,24,0,0,0,24,64V176a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V64A24,24,0,0,0,208,40Zm8,136a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H208a8,8,0,0,1,8,8Zm-48,48a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,224Z"/></svg>`,
|
|
501
|
+
"vp-desktop-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M232,64V176a24,24,0,0,1-24,24H48a24,24,0,0,1-24-24V64A24,24,0,0,1,48,40H208A24,24,0,0,1,232,64ZM160,216H96a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Z"/></svg>`,
|
|
502
|
+
"vp-disco": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M120,64.37V16a8,8,0,0,0-16,0V64.37a88,88,0,1,0,16,0ZM183.54,144H151.77c-1.51-28.36-10.79-48.36-19.44-61.06A72.16,72.16,0,0,1,183.54,144Zm-95.3,16h47.52c-2,33.52-16.13,52.95-23.76,61.08C104.36,212.93,90.23,193.51,88.24,160Zm0-16c2-33.52,16.13-52.95,23.76-61.08,7.64,8.15,21.77,27.57,23.76,61.08Zm3.43-61.06C83,95.64,73.74,115.64,72.23,144H40.46A72.16,72.16,0,0,1,91.67,82.94ZM40.46,160H72.23c1.51,28.36,10.79,48.36,19.44,61.06A72.16,72.16,0,0,1,40.46,160Zm91.87,61.06c8.65-12.7,17.93-32.7,19.44-61.06h31.77A72.16,72.16,0,0,1,132.33,221.06ZM256,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,256,88ZM152,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H192V64a8,8,0,0,1-16,0V48H160A8,8,0,0,1,152,40Z"/></svg>`,
|
|
503
|
+
"vp-disco-fill": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M120,64.37V16a8,8,0,0,0-16,0V64.37a88,88,0,1,0,16,0ZM183.54,144H151.77c-1.51-28.36-10.79-48.36-19.44-61.06A72.16,72.16,0,0,1,183.54,144Zm-47.78,16c-2,33.52-16.13,52.95-23.76,61.08-7.64-8.15-21.77-27.57-23.76-61.08ZM91.67,82.94C83,95.64,73.74,115.64,72.23,144H40.46A72.16,72.16,0,0,1,91.67,82.94ZM40.46,160H72.23c1.51,28.36,10.79,48.36,19.44,61.06A72.16,72.16,0,0,1,40.46,160ZM256,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,256,88ZM152,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H192V64a8,8,0,0,1-16,0V48H160A8,8,0,0,1,152,40Z"/></svg>`,
|
|
504
|
+
"vp-fullpage": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M216,48V88a8,8,0,0,1-16,0V56H168a8,8,0,0,1,0-16h40A8,8,0,0,1,216,48ZM88,200H56V168a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H88a8,8,0,0,0,0-16Zm120-40a8,8,0,0,0-8,8v32H168a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V168A8,8,0,0,0,208,160ZM88,40H48a8,8,0,0,0-8,8V88a8,8,0,0,0,16,0V56H88a8,8,0,0,0,0-16Z"/></svg>`,
|
|
505
|
+
// ---- A11y CVD picker ----
|
|
506
|
+
"a11y": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="1em" height="1em" aria-hidden="true"><path d="M160,40a32,32,0,1,0-32,32A32,32,0,0,0,160,40ZM128,56a16,16,0,1,1,16-16A16,16,0,0,1,128,56ZM231.5,87.71A19.62,19.62,0,0,0,212,72H44a20,20,0,0,0-8.38,38.16l.13,0,50.75,22.35-21,79.72A20,20,0,0,0,102,228.8l26-44.87,26,44.87a20,20,0,0,0,36.4-16.52l-21-79.72,50.75-22.35.13,0A19.64,19.64,0,0,0,231.5,87.71Zm-17.8,7.9-56.93,25.06a8,8,0,0,0-4.51,9.36L175.13,217a7,7,0,0,0,.49,1.35,4,4,0,0,1-5,5.45,4,4,0,0,1-2.25-2.07,6.31,6.31,0,0,0-.34-.63L134.92,164a8,8,0,0,0-13.84,0L88,221.05a6.31,6.31,0,0,0-.34.63,4,4,0,0,1-2.25,2.07,4,4,0,0,1-5-5.45,7,7,0,0,0,.49-1.35L103.74,130a8,8,0,0,0-4.51-9.36L42.3,95.61A4,4,0,0,1,44,88H212a4,4,0,0,1,1.73,7.61Z"/></svg>`,
|
|
507
|
+
// ---- Custom (non-Phosphor) — stroke-based, viewBox 0 0 16 16 ----
|
|
508
|
+
"chevron": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="1em" height="1em" aria-hidden="true" shape-rendering="geometricPrecision"><path d="M6 3.5 L10.5 8 L6 12.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
|
|
509
|
+
// Box-arrow "open in new / external link". Same custom-stroke idiom as chevron: viewBox 0 0 16 16,
|
|
510
|
+
// fill none, currentColor, round caps/joins. Box (open at its top-right) + an arrow escaping to the
|
|
511
|
+
// top-right corner. Slightly lighter stroke (1.5) than the chevron since it carries more detail.
|
|
512
|
+
"external-link": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" width="1em" height="1em" aria-hidden="true" shape-rendering="geometricPrecision"><path d="M12 8.7 V12.5 A1.3 1.3 0 0 1 10.7 13.8 H3.5 A1.3 1.3 0 0 1 2.2 12.5 V5.3 A1.3 1.3 0 0 1 3.5 4 H7.3 M9.8 2.2 H13.8 V6.2 M13.8 2.2 L7.4 8.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`
|
|
513
|
+
};
|
|
514
|
+
function svgIcon(name) {
|
|
515
|
+
return ICONS[name];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/dock/diag-format.ts
|
|
519
|
+
var SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
|
|
520
|
+
var CATEGORY_ORDER = ["Render/Template", "Schema/Data", "HTML validation", "A11y", "Other"];
|
|
521
|
+
function diagSevLabel(severity) {
|
|
522
|
+
const s = document.createElement("span");
|
|
523
|
+
s.className = "diag-sev";
|
|
524
|
+
s.textContent = severity.charAt(0).toUpperCase() + severity.slice(1);
|
|
525
|
+
return s;
|
|
526
|
+
}
|
|
527
|
+
function diagSep() {
|
|
528
|
+
const s = document.createElement("span");
|
|
529
|
+
s.className = "diag-sep";
|
|
530
|
+
s.textContent = " \xB7 ";
|
|
531
|
+
return s;
|
|
532
|
+
}
|
|
533
|
+
function diagDocsLink(helpUrl) {
|
|
534
|
+
const link = document.createElement("a");
|
|
535
|
+
link.className = "diag-docs";
|
|
536
|
+
link.href = helpUrl;
|
|
537
|
+
link.target = "_blank";
|
|
538
|
+
link.rel = "noopener";
|
|
539
|
+
link.innerHTML = `${svgIcon("external-link")}<span>See docs</span>`;
|
|
540
|
+
return link;
|
|
541
|
+
}
|
|
542
|
+
function categoryFromSource(source) {
|
|
543
|
+
if (source === "render" || source === "highlight") return "Render/Template";
|
|
544
|
+
if (source === "schema" || source === "cascade") return "Schema/Data";
|
|
545
|
+
if (source === "html") return "HTML validation";
|
|
546
|
+
if (source === "axe") return "A11y";
|
|
547
|
+
return "Other";
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/dock/severity.ts
|
|
551
|
+
function severityGlyph(severity) {
|
|
552
|
+
if (severity === "error" || severity === "warning") {
|
|
553
|
+
return `<span class="pd-sev-glyph" data-severity="${severity}" aria-hidden="true"></span>`;
|
|
554
|
+
}
|
|
555
|
+
return "";
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// src/dock/diagnostics.ts
|
|
559
|
+
function renderDiagnostics(diagnostics) {
|
|
560
|
+
dom.diagnosticsEl.innerHTML = "";
|
|
561
|
+
if (diagnostics.length === 0) {
|
|
562
|
+
const p = document.createElement("p");
|
|
563
|
+
p.textContent = "No issues found";
|
|
564
|
+
dom.diagnosticsEl.appendChild(p);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const groups = /* @__PURE__ */ new Map();
|
|
568
|
+
for (const d of diagnostics) {
|
|
569
|
+
const cat = categoryFromSource(d.source);
|
|
570
|
+
const list = groups.get(cat) ?? [];
|
|
571
|
+
list.push(d);
|
|
572
|
+
groups.set(cat, list);
|
|
573
|
+
}
|
|
574
|
+
function buildA11yLi(d) {
|
|
575
|
+
const li = document.createElement("li");
|
|
576
|
+
li.dataset.severity = d.severity;
|
|
577
|
+
li.dataset.kind = d.kind;
|
|
578
|
+
li.dataset.ruleId = d.ruleId;
|
|
579
|
+
if (d.needsReview) li.dataset.needsReview = "true";
|
|
580
|
+
if (d.needsReview) {
|
|
581
|
+
li.appendChild(document.createTextNode(d.message));
|
|
582
|
+
} else {
|
|
583
|
+
li.appendChild(diagSevLabel(d.severity));
|
|
584
|
+
li.appendChild(diagSep());
|
|
585
|
+
li.appendChild(document.createTextNode(d.message));
|
|
586
|
+
}
|
|
587
|
+
const ruleSpan = document.createElement("span");
|
|
588
|
+
ruleSpan.className = "pd-a11y-rule";
|
|
589
|
+
ruleSpan.textContent = ` \u2014 ${d.ruleId}`;
|
|
590
|
+
li.appendChild(ruleSpan);
|
|
591
|
+
if (d.target) {
|
|
592
|
+
const targetSpan = document.createElement("span");
|
|
593
|
+
targetSpan.className = "pd-a11y-target";
|
|
594
|
+
targetSpan.textContent = ` (${d.target})`;
|
|
595
|
+
li.appendChild(targetSpan);
|
|
596
|
+
}
|
|
597
|
+
li.appendChild(diagDocsLink(d.helpUrl));
|
|
598
|
+
if (d.contrast) {
|
|
599
|
+
const c = d.contrast;
|
|
600
|
+
const swatches = document.createElement("span");
|
|
601
|
+
swatches.className = "pd-contrast-swatches";
|
|
602
|
+
const fgSwatch = document.createElement("span");
|
|
603
|
+
fgSwatch.className = "pd-swatch";
|
|
604
|
+
fgSwatch.style.setProperty("--swatch-color", c.fg);
|
|
605
|
+
fgSwatch.title = c.fg;
|
|
606
|
+
const bgSwatch = document.createElement("span");
|
|
607
|
+
bgSwatch.className = "pd-swatch";
|
|
608
|
+
bgSwatch.style.setProperty("--swatch-color", c.bg);
|
|
609
|
+
bgSwatch.title = c.bg;
|
|
610
|
+
const ratioSpan = document.createElement("span");
|
|
611
|
+
ratioSpan.className = "pd-contrast-ratio";
|
|
612
|
+
ratioSpan.textContent = ` ${c.ratio.toFixed(2)}:1 (needs ${c.expected}:1)`;
|
|
613
|
+
swatches.append(fgSwatch, bgSwatch, ratioSpan);
|
|
614
|
+
li.appendChild(swatches);
|
|
615
|
+
}
|
|
616
|
+
if (!d.needsReview) li.insertAdjacentHTML("afterbegin", severityGlyph(d.severity));
|
|
617
|
+
return li;
|
|
618
|
+
}
|
|
619
|
+
const sortBySeverity = (arr) => [...arr].sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 9) - (SEVERITY_ORDER[b.severity] ?? 9));
|
|
620
|
+
const catRank = (c) => {
|
|
621
|
+
const i = CATEGORY_ORDER.indexOf(c);
|
|
622
|
+
return i === -1 ? CATEGORY_ORDER.length : i;
|
|
623
|
+
};
|
|
624
|
+
const ordered = [...groups.entries()].filter(([, items]) => items.length > 0).map(([cat, items]) => {
|
|
625
|
+
const isA11y = cat === "A11y";
|
|
626
|
+
const needsReview = (d) => d.kind === "A11Y" && d.needsReview === true;
|
|
627
|
+
const confirmed = isA11y ? items.filter((d) => !needsReview(d)) : items;
|
|
628
|
+
const review = isA11y ? items.filter(needsReview) : [];
|
|
629
|
+
const errorCount = confirmed.filter((d) => d.severity === "error").length;
|
|
630
|
+
const warningCount = confirmed.filter((d) => d.severity === "warning").length;
|
|
631
|
+
const worstRank = confirmed.reduce((m, d) => Math.min(m, SEVERITY_ORDER[d.severity] ?? 9), 9);
|
|
632
|
+
return { cat, items, isA11y, confirmed, review, errorCount, warningCount, worstRank };
|
|
633
|
+
}).sort((a, b) => a.worstRank - b.worstRank || catRank(a.cat) - catRank(b.cat));
|
|
634
|
+
for (const { cat, isA11y, confirmed, review, errorCount, warningCount } of ordered) {
|
|
635
|
+
const mixed = errorCount > 0 && warningCount > 0;
|
|
636
|
+
const details = document.createElement("details");
|
|
637
|
+
details.name = "pd-diagnostics";
|
|
638
|
+
if (mixed) details.dataset.mixed = "true";
|
|
639
|
+
const summary = document.createElement("summary");
|
|
640
|
+
summary.appendChild(document.createTextNode(cat));
|
|
641
|
+
const headSeg = (sev, n) => `<span class="pd-diag-head-seg" data-severity="${sev}">${severityGlyph(sev)}<span class="pd-diag-head-count">${n}</span></span>`;
|
|
642
|
+
let markerHtml = "";
|
|
643
|
+
if (errorCount > 0) markerHtml += headSeg("error", errorCount);
|
|
644
|
+
if (warningCount > 0) markerHtml += headSeg("warning", warningCount);
|
|
645
|
+
if (markerHtml) summary.insertAdjacentHTML("beforeend", markerHtml);
|
|
646
|
+
if (isA11y && review.length > 0) {
|
|
647
|
+
const rev = document.createElement("span");
|
|
648
|
+
rev.className = "pd-diag-head-review";
|
|
649
|
+
rev.textContent = confirmed.length === 0 ? ` 0 \xB7 ${review.length} to review` : ` \xB7 ${review.length} to review`;
|
|
650
|
+
summary.appendChild(rev);
|
|
651
|
+
}
|
|
652
|
+
const diagCaret = document.createElement("span");
|
|
653
|
+
diagCaret.className = "disclosure-caret";
|
|
654
|
+
diagCaret.setAttribute("aria-hidden", "true");
|
|
655
|
+
diagCaret.innerHTML = svgIcon("chevron");
|
|
656
|
+
summary.prepend(diagCaret);
|
|
657
|
+
details.appendChild(summary);
|
|
658
|
+
const ul = document.createElement("ul");
|
|
659
|
+
for (const d of sortBySeverity(confirmed)) {
|
|
660
|
+
if (d.kind === "A11Y") {
|
|
661
|
+
ul.appendChild(buildA11yLi(d));
|
|
662
|
+
} else {
|
|
663
|
+
const li = document.createElement("li");
|
|
664
|
+
li.dataset.severity = d.severity;
|
|
665
|
+
li.dataset.kind = d.kind;
|
|
666
|
+
li.appendChild(diagSevLabel(d.severity));
|
|
667
|
+
li.appendChild(diagSep());
|
|
668
|
+
let text = d.message;
|
|
669
|
+
if (d.path) text += ` (path: ${d.path})`;
|
|
670
|
+
if (d.location) {
|
|
671
|
+
const loc = [d.location.file, d.location.line, d.location.column].filter((v) => v !== void 0).join(":");
|
|
672
|
+
if (loc) text += ` (at ${loc})`;
|
|
673
|
+
}
|
|
674
|
+
li.appendChild(document.createTextNode(text));
|
|
675
|
+
li.insertAdjacentHTML("afterbegin", severityGlyph(d.severity));
|
|
676
|
+
ul.appendChild(li);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
details.appendChild(ul);
|
|
680
|
+
if (review.length > 0) {
|
|
681
|
+
const reviewDetails = document.createElement("details");
|
|
682
|
+
const reviewSummary = document.createElement("summary");
|
|
683
|
+
reviewSummary.className = "pd-a11y-review-header";
|
|
684
|
+
reviewSummary.textContent = `Needs review (${review.length})`;
|
|
685
|
+
const reviewCaret = document.createElement("span");
|
|
686
|
+
reviewCaret.className = "disclosure-caret";
|
|
687
|
+
reviewCaret.setAttribute("aria-hidden", "true");
|
|
688
|
+
reviewCaret.innerHTML = svgIcon("chevron");
|
|
689
|
+
reviewSummary.prepend(reviewCaret);
|
|
690
|
+
reviewDetails.appendChild(reviewSummary);
|
|
691
|
+
const reviewUl = document.createElement("ul");
|
|
692
|
+
for (const d of sortBySeverity(review)) {
|
|
693
|
+
if (d.kind === "A11Y") reviewUl.appendChild(buildA11yLi(d));
|
|
694
|
+
}
|
|
695
|
+
reviewDetails.appendChild(reviewUl);
|
|
696
|
+
if (confirmed.length === 0) reviewDetails.open = true;
|
|
697
|
+
details.appendChild(reviewDetails);
|
|
698
|
+
}
|
|
699
|
+
dom.diagnosticsEl.appendChild(details);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function refreshDiagnostics() {
|
|
703
|
+
if (session.scanPending) {
|
|
704
|
+
const structural = session.current?.diagnostics ?? [];
|
|
705
|
+
if (structural.length > 0) {
|
|
706
|
+
renderDiagnostics(structural);
|
|
707
|
+
const p = document.createElement("p");
|
|
708
|
+
p.className = "pd-a11y-pending";
|
|
709
|
+
p.textContent = "running rendered checks\u2026";
|
|
710
|
+
dom.diagnosticsEl.appendChild(p);
|
|
711
|
+
} else {
|
|
712
|
+
dom.diagnosticsEl.innerHTML = "";
|
|
713
|
+
const p = document.createElement("p");
|
|
714
|
+
p.className = "pd-a11y-pending";
|
|
715
|
+
p.textContent = "running rendered checks\u2026";
|
|
716
|
+
dom.diagnosticsEl.appendChild(p);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
renderDiagnostics([...session.current?.diagnostics ?? [], ...session.renderedA11y]);
|
|
720
|
+
}
|
|
721
|
+
updateTabMarkers();
|
|
722
|
+
}
|
|
723
|
+
function updateTabMarkers() {
|
|
724
|
+
const tab = document.getElementById("dock-tab-diagnostics");
|
|
725
|
+
if (!tab) return;
|
|
726
|
+
const merged = [...session.current?.diagnostics ?? [], ...session.renderedA11y];
|
|
727
|
+
let errors = 0;
|
|
728
|
+
let warnings = 0;
|
|
729
|
+
for (const d of merged) {
|
|
730
|
+
if (d.kind === "A11Y" && d.needsReview === true) continue;
|
|
731
|
+
if (d.severity === "error") errors++;
|
|
732
|
+
else if (d.severity === "warning") warnings++;
|
|
733
|
+
}
|
|
734
|
+
const marker = tab.querySelector(".tab-marker");
|
|
735
|
+
const countEl = tab.querySelector(".tab-marker-count");
|
|
736
|
+
marker?.querySelector(".pd-sev-glyph")?.remove();
|
|
737
|
+
if (errors > 0) {
|
|
738
|
+
tab.dataset.severity = "error";
|
|
739
|
+
tab.dataset.count = String(errors);
|
|
740
|
+
if (countEl) countEl.textContent = String(errors);
|
|
741
|
+
marker?.insertAdjacentHTML("afterbegin", severityGlyph("error"));
|
|
742
|
+
} else if (warnings > 0) {
|
|
743
|
+
tab.dataset.severity = "warning";
|
|
744
|
+
tab.dataset.count = String(warnings);
|
|
745
|
+
if (countEl) countEl.textContent = String(warnings);
|
|
746
|
+
marker?.insertAdjacentHTML("afterbegin", severityGlyph("warning"));
|
|
747
|
+
} else {
|
|
748
|
+
delete tab.dataset.severity;
|
|
749
|
+
delete tab.dataset.count;
|
|
750
|
+
if (countEl) countEl.textContent = "";
|
|
751
|
+
}
|
|
752
|
+
const clauses = [];
|
|
753
|
+
if (errors > 0) clauses.push(`${errors} ${errors === 1 ? "error" : "errors"}`);
|
|
754
|
+
if (warnings > 0) clauses.push(`${warnings} ${warnings === 1 ? "warning" : "warnings"}`);
|
|
755
|
+
if (clauses.length > 0) {
|
|
756
|
+
tab.setAttribute("aria-label", `Diagnostics, ${clauses.join(", ")}`);
|
|
757
|
+
} else {
|
|
758
|
+
tab.removeAttribute("aria-label");
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/dock/a11y-scan.ts
|
|
763
|
+
var scanGen = 0;
|
|
764
|
+
async function injectAxe() {
|
|
765
|
+
const script = dom.iframe.contentDocument.createElement("script");
|
|
766
|
+
script.src = resourceUrl(PD_ROUTES.axeCore);
|
|
767
|
+
await new Promise((res, rej) => {
|
|
768
|
+
script.onload = () => res();
|
|
769
|
+
script.onerror = rej;
|
|
770
|
+
dom.iframe.contentDocument.head.appendChild(script);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
async function runRenderedA11yScan() {
|
|
774
|
+
if (!session.current || !dom.iframe.contentDocument) return;
|
|
775
|
+
const gen = ++scanGen;
|
|
776
|
+
if (session.needsRenderingRules.length === 0) {
|
|
777
|
+
await new Promise((res) => setTimeout(res, 200));
|
|
778
|
+
if (session.needsRenderingRules.length === 0) {
|
|
779
|
+
if (gen === scanGen) {
|
|
780
|
+
session.scanPending = false;
|
|
781
|
+
refreshDiagnostics();
|
|
782
|
+
}
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
if (dom.iframe.contentDocument?.readyState !== "complete") {
|
|
788
|
+
await new Promise((res) => dom.iframe.addEventListener("load", () => res(), { once: true }));
|
|
789
|
+
}
|
|
790
|
+
if (!dom.iframe.contentDocument) {
|
|
791
|
+
if (gen === scanGen) {
|
|
792
|
+
session.scanPending = false;
|
|
793
|
+
refreshDiagnostics();
|
|
794
|
+
}
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const axeWindow = dom.iframe.contentWindow;
|
|
798
|
+
if (!axeWindow.axe) await injectAxe();
|
|
799
|
+
const root = dom.iframe.contentDocument.querySelector("main") ?? dom.iframe.contentDocument.body;
|
|
800
|
+
const results = await axeWindow.axe.run(root, { runOnly: session.needsRenderingRules });
|
|
801
|
+
if (gen !== scanGen) return;
|
|
802
|
+
session.renderedA11y = mapAxeResults(results);
|
|
803
|
+
} catch (err) {
|
|
804
|
+
if (gen !== scanGen) return;
|
|
805
|
+
session.renderedA11y = [];
|
|
806
|
+
console.error("[pd-a11y] scan error:", err);
|
|
807
|
+
} finally {
|
|
808
|
+
if (gen === scanGen) {
|
|
809
|
+
session.scanPending = false;
|
|
810
|
+
refreshDiagnostics();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/core/html-util.ts
|
|
816
|
+
function extractHighlightedPre(containerHtml) {
|
|
817
|
+
const tmp = document.createElement("div");
|
|
818
|
+
tmp.innerHTML = containerHtml;
|
|
819
|
+
const pre = tmp.querySelector("pre");
|
|
820
|
+
return pre ? pre.outerHTML : containerHtml;
|
|
821
|
+
}
|
|
822
|
+
function escapeAttr(s) {
|
|
823
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/dock/tabs.ts
|
|
827
|
+
function applySourceFacetDom(facet) {
|
|
828
|
+
document.getElementById("dock-tab-source")?.setAttribute("aria-controls", `dock-panel-${facet}`);
|
|
829
|
+
for (const btn of dom.dockFacetBtns) {
|
|
830
|
+
btn.setAttribute("aria-selected", String(btn.dataset.facet === facet));
|
|
831
|
+
}
|
|
832
|
+
dom.dockHbsPanel.hidden = facet !== "hbs";
|
|
833
|
+
dom.dockDataPanel.hidden = facet !== "data";
|
|
834
|
+
dom.dockSchemaPanel.hidden = facet !== "schema";
|
|
835
|
+
if (facet === "data" && session.current && session.dataCache === null) void fetchAndShowData();
|
|
836
|
+
updateCopyLabel();
|
|
837
|
+
}
|
|
838
|
+
function switchDockTab(tab, forceFacet) {
|
|
839
|
+
if (tab === "diagnostics" && session.current) {
|
|
840
|
+
session.renderedA11y = [];
|
|
841
|
+
session.scanPending = true;
|
|
842
|
+
}
|
|
843
|
+
const prevDock = store.local.dock;
|
|
844
|
+
const dock = tab === "source" ? { dockTab: "source", sourceFacet: forceFacet ?? (prevDock.dockTab === "source" ? prevDock.sourceFacet : "hbs") } : { dockTab: tab };
|
|
845
|
+
store.setLocal({ dock });
|
|
846
|
+
for (const btn of dom.dockTabBtns) {
|
|
847
|
+
btn.setAttribute("aria-selected", String(btn.dataset.tab === tab));
|
|
848
|
+
}
|
|
849
|
+
const isSource = tab === "source";
|
|
850
|
+
const isHtml = tab === "html";
|
|
851
|
+
dom.dockFacetsEl.hidden = tab === "console" || tab === "diagnostics";
|
|
852
|
+
dom.sourceFacetsEl.hidden = !isSource;
|
|
853
|
+
dom.dockHtmlPanel.hidden = !isHtml;
|
|
854
|
+
dom.dockDiagnosticsPanel.hidden = tab !== "diagnostics";
|
|
855
|
+
dom.dockConsolePanel.hidden = tab !== "console";
|
|
856
|
+
if (!isSource) {
|
|
857
|
+
dom.dockHbsPanel.hidden = true;
|
|
858
|
+
dom.dockDataPanel.hidden = true;
|
|
859
|
+
dom.dockSchemaPanel.hidden = true;
|
|
860
|
+
} else {
|
|
861
|
+
const d = store.local.dock;
|
|
862
|
+
if (d.dockTab === "source") applySourceFacetDom(d.sourceFacet);
|
|
863
|
+
}
|
|
864
|
+
if (isHtml && session.current && session.renderedHtmlCache === null) {
|
|
865
|
+
void fetchAndShowHtml();
|
|
866
|
+
}
|
|
867
|
+
if (tab === "diagnostics" && session.current) {
|
|
868
|
+
refreshDiagnostics();
|
|
869
|
+
void runRenderedA11yScan();
|
|
870
|
+
}
|
|
871
|
+
updateCopyLabel();
|
|
872
|
+
}
|
|
873
|
+
function switchSourceFacet(facet) {
|
|
874
|
+
store.setLocal({ dock: { dockTab: "source", sourceFacet: facet } });
|
|
875
|
+
applySourceFacetDom(facet);
|
|
876
|
+
}
|
|
877
|
+
async function copyDockSource() {
|
|
878
|
+
const dock = store.local.dock;
|
|
879
|
+
if (!session.current || dock.dockTab === "console") return;
|
|
880
|
+
let raw;
|
|
881
|
+
if (dock.dockTab === "html") {
|
|
882
|
+
raw = session.rawRenderedHtml ?? "";
|
|
883
|
+
} else if (dock.dockTab !== "source") {
|
|
884
|
+
raw = session.current.source.schema ?? "";
|
|
885
|
+
} else if (dock.sourceFacet === "hbs") {
|
|
886
|
+
raw = session.current.source.hbs;
|
|
887
|
+
} else if (dock.sourceFacet === "data") {
|
|
888
|
+
raw = session.dataRaw ?? session.current.source.json ?? "";
|
|
889
|
+
} else {
|
|
890
|
+
raw = session.current.source.schema ?? "";
|
|
891
|
+
}
|
|
892
|
+
const prevLabel = dom.dockCopyBtn.textContent ?? "";
|
|
893
|
+
try {
|
|
894
|
+
await navigator.clipboard.writeText(raw);
|
|
895
|
+
dom.dockCopyBtn.textContent = "Copied!";
|
|
896
|
+
setTimeout(() => {
|
|
897
|
+
dom.dockCopyBtn.textContent = prevLabel;
|
|
898
|
+
}, 1500);
|
|
899
|
+
} catch {
|
|
900
|
+
dom.dockCopyBtn.textContent = "Failed";
|
|
901
|
+
setTimeout(() => {
|
|
902
|
+
dom.dockCopyBtn.textContent = prevLabel;
|
|
903
|
+
}, 1500);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function updateCopyLabel() {
|
|
907
|
+
const dock = store.local.dock;
|
|
908
|
+
let label;
|
|
909
|
+
if (dock.dockTab === "html") {
|
|
910
|
+
label = "Copy HTML";
|
|
911
|
+
} else if (dock.dockTab !== "source" || dock.sourceFacet === "hbs") {
|
|
912
|
+
label = "Copy Handlebars";
|
|
913
|
+
} else if (dock.sourceFacet === "data") {
|
|
914
|
+
label = "Copy data";
|
|
915
|
+
} else {
|
|
916
|
+
label = "Copy schema";
|
|
917
|
+
}
|
|
918
|
+
dom.dockCopyBtn.setAttribute("aria-label", label);
|
|
919
|
+
dom.dockCopyBtn.textContent = label;
|
|
920
|
+
}
|
|
921
|
+
async function fetchAndShowHtml() {
|
|
922
|
+
if (!session.current) return;
|
|
923
|
+
if (session.renderedHtmlCache !== null) {
|
|
924
|
+
dom.htmlBody.innerHTML = session.renderedHtmlCache;
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
dom.htmlBody.innerHTML = "<p>Loading\u2026</p>";
|
|
928
|
+
try {
|
|
929
|
+
const url = renderedSourceUrl(session.current.id, session.currentVariant ?? void 0);
|
|
930
|
+
const res = await fetch(url);
|
|
931
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
932
|
+
const fullHtml = await res.text();
|
|
933
|
+
const tmp = document.createElement("div");
|
|
934
|
+
tmp.innerHTML = fullHtml;
|
|
935
|
+
session.rawRenderedHtml = tmp.querySelector(".shiki-container")?.dataset.raw ?? fullHtml;
|
|
936
|
+
const pre = tmp.querySelector("pre");
|
|
937
|
+
session.renderedHtmlCache = pre ? pre.outerHTML : fullHtml;
|
|
938
|
+
dom.htmlBody.innerHTML = session.renderedHtmlCache;
|
|
939
|
+
} catch (e) {
|
|
940
|
+
dom.htmlBody.innerHTML = `<p>Error: ${String(e)}</p>`;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
async function fetchAndShowData() {
|
|
944
|
+
if (!session.current) return;
|
|
945
|
+
if (session.dataCache !== null) {
|
|
946
|
+
dom.dataBody.innerHTML = extractHighlightedPre(session.dataCache);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
dom.dataBody.innerHTML = "<p>Loading\u2026</p>";
|
|
950
|
+
try {
|
|
951
|
+
const url = dataSourceUrl(session.current.id, session.currentVariant ?? void 0);
|
|
952
|
+
const res = await fetch(url);
|
|
953
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
954
|
+
const fullHtml = await res.text();
|
|
955
|
+
const tmp = document.createElement("div");
|
|
956
|
+
tmp.innerHTML = fullHtml;
|
|
957
|
+
session.dataRaw = tmp.querySelector(".shiki-container")?.dataset.raw ?? fullHtml;
|
|
958
|
+
const pre = tmp.querySelector("pre");
|
|
959
|
+
session.dataCache = pre ? pre.outerHTML : fullHtml;
|
|
960
|
+
dom.dataBody.innerHTML = session.dataCache;
|
|
961
|
+
} catch (e) {
|
|
962
|
+
dom.dataBody.innerHTML = `<p>Error: ${String(e)}</p>`;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function renderConsole(message, level = "info") {
|
|
966
|
+
const p = document.createElement("p");
|
|
967
|
+
p.textContent = message;
|
|
968
|
+
if (level !== "info") p.dataset.level = level;
|
|
969
|
+
dom.consoleEl.appendChild(p);
|
|
970
|
+
dom.consoleEl.scrollTop = dom.consoleEl.scrollHeight;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/inspector/audit-view.ts
|
|
974
|
+
async function fetchAuditRollup() {
|
|
975
|
+
const res = await fetch(PD_ROUTES.audit);
|
|
976
|
+
if (!res.ok) throw new Error(`audit fetch failed: ${res.status}`);
|
|
977
|
+
const graph = await res.json();
|
|
978
|
+
return buildAuditRollup(graph, graph.subjectSha ? { subjectSha: graph.subjectSha } : {});
|
|
979
|
+
}
|
|
980
|
+
var el = (tag, cls, text) => {
|
|
981
|
+
const n = document.createElement(tag);
|
|
982
|
+
if (cls) n.className = cls;
|
|
983
|
+
if (text !== void 0) n.textContent = text;
|
|
984
|
+
return n;
|
|
985
|
+
};
|
|
986
|
+
function tagBadge(tag) {
|
|
987
|
+
const s = el("span", "audit-tag", `[${tag}]`);
|
|
988
|
+
s.dataset.tag = tag;
|
|
989
|
+
return s;
|
|
990
|
+
}
|
|
991
|
+
function componentLink(id) {
|
|
992
|
+
const a = el("a", "audit-link", id);
|
|
993
|
+
a.href = routeHref(`/${id.split("/").map(encodeURIComponent).join("/")}`);
|
|
994
|
+
return a;
|
|
995
|
+
}
|
|
996
|
+
function renderAuditView(rollup) {
|
|
997
|
+
const section = el("section", "audit-panel");
|
|
998
|
+
section.setAttribute("aria-label", "Corpus diagnostics audit");
|
|
999
|
+
const head = el("header", "audit-head");
|
|
1000
|
+
head.appendChild(el("p", "audit-headline", rollup.headline));
|
|
1001
|
+
if (rollup.subjectSha) head.appendChild(el("p", "audit-sha", rollup.subjectSha));
|
|
1002
|
+
if (runtime.mode === "static") {
|
|
1003
|
+
head.appendChild(el("p", "audit-sha", `Snapshot from ${new Date(runtime.builtAt).toLocaleString()}`));
|
|
1004
|
+
}
|
|
1005
|
+
section.appendChild(head);
|
|
1006
|
+
const t = rollup.totals, ro = rollup.renderOutcomes;
|
|
1007
|
+
const totals = el("dl", "audit-totals");
|
|
1008
|
+
const stat = (label, value) => {
|
|
1009
|
+
totals.appendChild(el("dt", "audit-stat-label", label));
|
|
1010
|
+
totals.appendChild(el("dd", "audit-stat-value", value));
|
|
1011
|
+
};
|
|
1012
|
+
stat("Components", String(t.components));
|
|
1013
|
+
stat("Affected", String(t.affected));
|
|
1014
|
+
stat("Clean", `${t.cleanPct}%`);
|
|
1015
|
+
stat("Defects", String(t.defects));
|
|
1016
|
+
stat("Render", `${ro.ok} ok \xB7 ${ro.blank} blank \xB7 ${ro.error} error`);
|
|
1017
|
+
section.appendChild(totals);
|
|
1018
|
+
section.appendChild(el("h3", "audit-h", "Rules by blast radius"));
|
|
1019
|
+
if (rollup.rulesByBlast.length === 0) {
|
|
1020
|
+
section.appendChild(el("p", "audit-empty", "No rule findings."));
|
|
1021
|
+
} else {
|
|
1022
|
+
const ul = el("ul", "audit-rules");
|
|
1023
|
+
for (const r of rollup.rulesByBlast) {
|
|
1024
|
+
const li = el("li", "audit-rule");
|
|
1025
|
+
li.appendChild(el("code", "audit-rule-id", `${r.engine}:${r.ruleId}`));
|
|
1026
|
+
li.appendChild(el("span", "audit-rule-count", `${r.components} component${r.components === 1 ? "" : "s"} \xB7 ${r.occurrences} occ`));
|
|
1027
|
+
for (const tag of r.tags) li.appendChild(tagBadge(tag));
|
|
1028
|
+
ul.appendChild(li);
|
|
1029
|
+
}
|
|
1030
|
+
section.appendChild(ul);
|
|
1031
|
+
}
|
|
1032
|
+
section.appendChild(el("h3", "audit-h", "Per component"));
|
|
1033
|
+
if (rollup.perComponent.length === 0) {
|
|
1034
|
+
section.appendChild(el("p", "audit-empty", "No components with findings or a non-ok render."));
|
|
1035
|
+
} else {
|
|
1036
|
+
const ul = el("ul", "audit-components");
|
|
1037
|
+
for (const c of rollup.perComponent) {
|
|
1038
|
+
const li = el("li", "audit-component");
|
|
1039
|
+
const cHead = el("div", "audit-component-head");
|
|
1040
|
+
cHead.appendChild(componentLink(c.id));
|
|
1041
|
+
if (c.render !== "ok") {
|
|
1042
|
+
const r = el("span", "audit-render", c.render);
|
|
1043
|
+
r.dataset.render = c.render;
|
|
1044
|
+
cHead.appendChild(r);
|
|
1045
|
+
}
|
|
1046
|
+
li.appendChild(cHead);
|
|
1047
|
+
const findings = el("ul", "audit-findings");
|
|
1048
|
+
for (const o of c.occurrences) {
|
|
1049
|
+
const f = el("li", "audit-finding");
|
|
1050
|
+
f.appendChild(el("code", "audit-finding-rule", `${o.engine}:${o.ruleId}`));
|
|
1051
|
+
f.appendChild(tagBadge(o.tag));
|
|
1052
|
+
f.appendChild(el("span", "audit-finding-msg", o.message));
|
|
1053
|
+
if (o.domPath) f.appendChild(el("code", "audit-finding-path", o.domPath));
|
|
1054
|
+
findings.appendChild(f);
|
|
1055
|
+
}
|
|
1056
|
+
li.appendChild(findings);
|
|
1057
|
+
ul.appendChild(li);
|
|
1058
|
+
}
|
|
1059
|
+
section.appendChild(ul);
|
|
1060
|
+
}
|
|
1061
|
+
const scope = el("details", "audit-scope");
|
|
1062
|
+
scope.open = true;
|
|
1063
|
+
scope.appendChild(el("summary", "audit-scope-summary", "Scope \u2014 what this count does and does not cover"));
|
|
1064
|
+
scope.appendChild(el("pre", "audit-scope-body", rollup.scope));
|
|
1065
|
+
section.appendChild(scope);
|
|
1066
|
+
return section;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/inspector/audit.ts
|
|
1070
|
+
var auditState = "idle";
|
|
1071
|
+
var auditRollup = null;
|
|
1072
|
+
async function loadAudit() {
|
|
1073
|
+
auditState = "loading";
|
|
1074
|
+
if (store.local.panelTab === "audit") renderAuditFacet();
|
|
1075
|
+
try {
|
|
1076
|
+
auditRollup = await fetchAuditRollup();
|
|
1077
|
+
auditState = "loaded";
|
|
1078
|
+
} catch {
|
|
1079
|
+
auditRollup = null;
|
|
1080
|
+
auditState = "error";
|
|
1081
|
+
}
|
|
1082
|
+
if (store.local.panelTab === "audit") renderAuditFacet();
|
|
1083
|
+
}
|
|
1084
|
+
function refreshAudit() {
|
|
1085
|
+
void loadAudit();
|
|
1086
|
+
}
|
|
1087
|
+
function clearAuditError() {
|
|
1088
|
+
if (auditState === "error") auditState = "idle";
|
|
1089
|
+
}
|
|
1090
|
+
function renderAuditFacet() {
|
|
1091
|
+
if (auditState === "idle") {
|
|
1092
|
+
void loadAudit();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (auditState === "loaded" && auditRollup) {
|
|
1096
|
+
dom.panelContent.replaceChildren(renderAuditView(auditRollup));
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
dom.panelContent.innerHTML = "<section class='audit-panel' aria-label='Corpus diagnostics audit'>" + (auditState === "loading" ? "<p class='audit-loading'>Loading corpus audit\u2026</p>" : "<p class='audit-error'>Corpus audit unavailable. Reopen the tab to retry.</p>") + "</section>";
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/inspector/panel.ts
|
|
1103
|
+
async function renderPanel() {
|
|
1104
|
+
if (store.local.panelTab === "audit") {
|
|
1105
|
+
await renderAuditFacet();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (!session.current) {
|
|
1109
|
+
dom.panelContent.innerHTML = "<p>Select a component to view docs.</p>";
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (store.local.panelTab === "docs") {
|
|
1113
|
+
dom.panelContent.innerHTML = session.current.doc ? session.current.doc : '<p class="doc-empty">No Documentation found</p>';
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (store.local.panelTab === "relationships") {
|
|
1117
|
+
let appendComponentGroup2 = function(label, ids, container, headingTag = "h3") {
|
|
1118
|
+
const h = document.createElement(headingTag);
|
|
1119
|
+
h.className = "rel-group-heading";
|
|
1120
|
+
h.textContent = label;
|
|
1121
|
+
container.appendChild(h);
|
|
1122
|
+
if (ids.length === 0) {
|
|
1123
|
+
const empty = document.createElement("p");
|
|
1124
|
+
empty.className = "rel-empty";
|
|
1125
|
+
empty.textContent = "none";
|
|
1126
|
+
container.appendChild(empty);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
1130
|
+
for (const id of ids) {
|
|
1131
|
+
const cat = session.componentRegistry.get(id)?.category ?? id.split("/")[0];
|
|
1132
|
+
const bucket = byCategory.get(cat) ?? [];
|
|
1133
|
+
bucket.push(id);
|
|
1134
|
+
byCategory.set(cat, bucket);
|
|
1135
|
+
}
|
|
1136
|
+
const sortedCategories = [...byCategory.keys()].sort(
|
|
1137
|
+
(a, b) => (session.navCategoryOrder.get(a) ?? 999) - (session.navCategoryOrder.get(b) ?? 999)
|
|
1138
|
+
);
|
|
1139
|
+
const list = document.createElement("ul");
|
|
1140
|
+
list.className = "rel-list";
|
|
1141
|
+
for (const cat of sortedCategories) {
|
|
1142
|
+
const catIds = byCategory.get(cat);
|
|
1143
|
+
const catLi = document.createElement("li");
|
|
1144
|
+
catLi.className = "rel-category";
|
|
1145
|
+
const catSpan = document.createElement("span");
|
|
1146
|
+
catSpan.className = "rel-category-heading";
|
|
1147
|
+
catSpan.textContent = cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
1148
|
+
catLi.appendChild(catSpan);
|
|
1149
|
+
catIds.sort((a, b) => {
|
|
1150
|
+
const ta = session.componentRegistry.get(a)?.title ?? a;
|
|
1151
|
+
const tb = session.componentRegistry.get(b)?.title ?? b;
|
|
1152
|
+
return ta.localeCompare(tb);
|
|
1153
|
+
});
|
|
1154
|
+
const catList = document.createElement("ul");
|
|
1155
|
+
catList.className = "rel-category-list";
|
|
1156
|
+
for (const id of catIds) {
|
|
1157
|
+
const meta = session.componentRegistry.get(id);
|
|
1158
|
+
const isPrivate = meta?.visibility === "private";
|
|
1159
|
+
const li = document.createElement("li");
|
|
1160
|
+
li.className = "rel-item";
|
|
1161
|
+
const a = document.createElement("a");
|
|
1162
|
+
a.className = "rel-link";
|
|
1163
|
+
a.href = routeHref(`/${id}`);
|
|
1164
|
+
a.textContent = meta?.title ?? id;
|
|
1165
|
+
li.appendChild(a);
|
|
1166
|
+
if (isPrivate) {
|
|
1167
|
+
const badge = document.createElement("span");
|
|
1168
|
+
badge.className = "rel-private-badge";
|
|
1169
|
+
badge.textContent = " private";
|
|
1170
|
+
li.appendChild(badge);
|
|
1171
|
+
}
|
|
1172
|
+
catList.appendChild(li);
|
|
1173
|
+
}
|
|
1174
|
+
catLi.appendChild(catList);
|
|
1175
|
+
list.appendChild(catLi);
|
|
1176
|
+
}
|
|
1177
|
+
container.appendChild(list);
|
|
1178
|
+
}, appendPlainGroup2 = function(label, ids, container, headingTag = "h3") {
|
|
1179
|
+
const h = document.createElement(headingTag);
|
|
1180
|
+
h.className = "rel-group-heading";
|
|
1181
|
+
h.textContent = label;
|
|
1182
|
+
container.appendChild(h);
|
|
1183
|
+
if (ids.length === 0) {
|
|
1184
|
+
const empty = document.createElement("p");
|
|
1185
|
+
empty.className = "rel-empty";
|
|
1186
|
+
empty.textContent = "none";
|
|
1187
|
+
container.appendChild(empty);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const list = document.createElement("ul");
|
|
1191
|
+
list.className = "rel-list";
|
|
1192
|
+
for (const id of ids) {
|
|
1193
|
+
const li = document.createElement("li");
|
|
1194
|
+
li.className = "rel-item";
|
|
1195
|
+
li.textContent = id;
|
|
1196
|
+
list.appendChild(li);
|
|
1197
|
+
}
|
|
1198
|
+
container.appendChild(list);
|
|
1199
|
+
};
|
|
1200
|
+
var appendComponentGroup = appendComponentGroup2, appendPlainGroup = appendPlainGroup2;
|
|
1201
|
+
const relAvail = session.current.relationships;
|
|
1202
|
+
dom.panelContent.innerHTML = "";
|
|
1203
|
+
const section = document.createElement("section");
|
|
1204
|
+
section.className = "rel-panel";
|
|
1205
|
+
section.setAttribute("aria-label", "Component relationships");
|
|
1206
|
+
appendComponentGroup2("Uses", relAvail.uses, section);
|
|
1207
|
+
appendComponentGroup2("Used by", relAvail.usedBy, section);
|
|
1208
|
+
const styleUmbrella = document.createElement("div");
|
|
1209
|
+
styleUmbrella.className = "rel-style-umbrella";
|
|
1210
|
+
const styleH = document.createElement("h3");
|
|
1211
|
+
styleH.className = "rel-group-heading rel-umbrella-heading";
|
|
1212
|
+
styleH.textContent = "Style";
|
|
1213
|
+
styleUmbrella.appendChild(styleH);
|
|
1214
|
+
appendComponentGroup2("Style uses", relAvail.styleUses, styleUmbrella, "h4");
|
|
1215
|
+
const styleModulesAll = [.../* @__PURE__ */ new Set([
|
|
1216
|
+
...relAvail.styleUsesModules ?? [],
|
|
1217
|
+
...relAvail.styleModules ?? []
|
|
1218
|
+
])].sort();
|
|
1219
|
+
appendPlainGroup2("Style modules", styleModulesAll, styleUmbrella, "h4");
|
|
1220
|
+
section.appendChild(styleUmbrella);
|
|
1221
|
+
const hasChips = relAvail.inlineStyles || relAvail.utilityClasses || (relAvail.unattributedClasses?.length ?? 0) > 0;
|
|
1222
|
+
if (hasChips) {
|
|
1223
|
+
const chips = document.createElement("div");
|
|
1224
|
+
chips.className = "rel-chips";
|
|
1225
|
+
if (relAvail.inlineStyles) {
|
|
1226
|
+
const chip = document.createElement("span");
|
|
1227
|
+
chip.className = "rel-chip rel-chip--inline-styles";
|
|
1228
|
+
chip.textContent = "Inline styles";
|
|
1229
|
+
chips.appendChild(chip);
|
|
1230
|
+
}
|
|
1231
|
+
if (relAvail.utilityClasses) {
|
|
1232
|
+
const chip = document.createElement("span");
|
|
1233
|
+
chip.className = "rel-chip rel-chip--utility-classes";
|
|
1234
|
+
chip.textContent = "Utility classes";
|
|
1235
|
+
chips.appendChild(chip);
|
|
1236
|
+
}
|
|
1237
|
+
for (const cls of relAvail.unattributedClasses ?? []) {
|
|
1238
|
+
const chip = document.createElement("span");
|
|
1239
|
+
chip.className = "rel-chip rel-chip--unattributed";
|
|
1240
|
+
chip.textContent = cls;
|
|
1241
|
+
chips.appendChild(chip);
|
|
1242
|
+
}
|
|
1243
|
+
section.appendChild(chips);
|
|
1244
|
+
}
|
|
1245
|
+
dom.panelContent.appendChild(section);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/core/cvd-math.ts
|
|
1251
|
+
var CVD_IDENTITY = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
1252
|
+
function cvdLinearTable(full) {
|
|
1253
|
+
return Array.from({ length: 11 }, (_, i) => {
|
|
1254
|
+
const t = i / 10;
|
|
1255
|
+
return CVD_IDENTITY.map((v, j) => v + (full[j] - v) * t);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
var MACHADO = {
|
|
1259
|
+
// Protanopia (L-cone loss) — Machado 2009, exact physiological values.
|
|
1260
|
+
protanopia: [
|
|
1261
|
+
[...CVD_IDENTITY],
|
|
1262
|
+
[0.856167, 0.182038, -0.038205, 0.029342, 0.955115, 0.015544, -2176e-6, -0.025934, 1.02811],
|
|
1263
|
+
[0.734766, 0.334872, -0.069637, 0.05784, 0.903969, 0.038191, -4285e-6, -0.051898, 1.056183],
|
|
1264
|
+
[0.630323, 0.465641, -0.095964, 0.086397, 0.8471, 0.066503, -6294e-6, -0.075916, 1.08221],
|
|
1265
|
+
[0.539009, 0.579343, -0.118352, 0.11482, 0.788267, 0.096913, -82e-4, -0.098948, 1.107148],
|
|
1266
|
+
[0.458064, 0.679578, -0.137642, 0.140729, 0.736319, 0.122953, -9962e-6, -0.121111, 1.131073],
|
|
1267
|
+
[0.38545, 0.769005, -0.154455, 0.164416, 0.69109, 0.144494, -0.011561, -0.142143, 1.153704],
|
|
1268
|
+
[0.319627, 0.849633, -0.169261, 0.186197, 0.651799, 0.162004, -0.013136, -0.16262, 1.175756],
|
|
1269
|
+
[0.259411, 0.923008, -0.182419, 0.205816, 0.613938, 0.180247, -0.014678, -0.182078, 1.196756],
|
|
1270
|
+
[0.203876, 0.990338, -0.194214, 0.223825, 0.579316, 0.19686, -0.016169, -0.200382, 1.216551],
|
|
1271
|
+
[0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -3882e-6, -0.048116, 1.051998]
|
|
1272
|
+
],
|
|
1273
|
+
// Deuteranopia (M-cone loss) — Machado 2009, exact physiological values.
|
|
1274
|
+
deuteranopia: [
|
|
1275
|
+
[...CVD_IDENTITY],
|
|
1276
|
+
[0.866435, 0.177704, -0.044139, 0.049567, 0.939063, 0.01137, -3453e-6, 7233e-6, 0.99622],
|
|
1277
|
+
[0.760729, 0.319078, -0.079807, 0.090568, 0.865985, 0.043447, -6027e-6, 0.013626, 0.992401],
|
|
1278
|
+
[0.675425, 0.43385, -0.109275, 0.125303, 0.781209, 0.093488, -795e-5, 0.026799, 0.981151],
|
|
1279
|
+
[0.605511, 0.52856, -0.134071, 0.155318, 0.687938, 0.156744, -9376e-6, 0.04331, 0.966066],
|
|
1280
|
+
[0.547494, 0.60724, -0.154734, 0.181692, 0.589028, 0.22928, -0.01041, 0.06081, 0.9496],
|
|
1281
|
+
[0.498864, 0.674188, -0.173052, 0.205199, 0.484799, 0.310002, -0.011131, 0.078156, 0.932975],
|
|
1282
|
+
[0.457771, 0.731899, -0.18967, 0.225977, 0.374653, 0.39937, -0.011595, 0.095399, 0.916196],
|
|
1283
|
+
[0.422116, 0.781437, -0.203553, 0.24434, 0.260728, 0.494932, -0.011841, 0.112064, 0.899777],
|
|
1284
|
+
[0.394719, 0.821042, -0.215761, 0.262213, 0.466615, 0.271172, -0.01183, 0.077502, 0.934329],
|
|
1285
|
+
[0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182, 0.04294, 0.968881]
|
|
1286
|
+
],
|
|
1287
|
+
// Tritanopia (S-cone loss) — Machado 2009, exact physiological values.
|
|
1288
|
+
// NOT a linear ramp: R→R dips to 0.896 at sev 0.2, then rises past 1 (peaks ~1.279 at sev 0.9).
|
|
1289
|
+
tritanopia: [
|
|
1290
|
+
[...CVD_IDENTITY],
|
|
1291
|
+
[0.92667, 0.092514, -0.019184, 0.021191, 0.964503, 0.014306, 8437e-6, 0.054813, 0.93675],
|
|
1292
|
+
[0.89572, 0.13333, -0.02905, 0.029997, 0.9454, 0.024603, 0.013027, 0.104707, 0.882266],
|
|
1293
|
+
[0.905871, 0.127791, -0.033662, 0.026856, 0.941251, 0.031893, 0.01341, 0.148296, 0.838294],
|
|
1294
|
+
[0.948035, 0.08949, -0.037526, 0.014364, 0.946792, 0.038844, 0.010853, 0.193991, 0.795156],
|
|
1295
|
+
[1.017277, 0.027029, -0.044306, -6113e-6, 0.958479, 0.047634, 6379e-6, 0.248708, 0.744913],
|
|
1296
|
+
[1.104996, -0.046633, -0.058363, -0.032137, 0.971635, 0.060503, 1336e-6, 0.317922, 0.680742],
|
|
1297
|
+
[1.193214, -0.109812, -0.083402, -0.058496, 0.97941, 0.079086, -2346e-6, 0.403492, 0.598854],
|
|
1298
|
+
[1.257728, -0.139648, -0.118081, -0.078003, 0.975409, 0.102594, -3316e-6, 0.501214, 0.502102],
|
|
1299
|
+
[1.278864, -0.125333, -0.153531, -0.084748, 0.957674, 0.127074, -989e-6, 0.601151, 0.399838],
|
|
1300
|
+
[1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 4733e-6, 0.691367, 0.3039]
|
|
1301
|
+
],
|
|
1302
|
+
// Achromatopsia — Rec. 709 luminance-weighted greyscale ramp (identity → full greyscale).
|
|
1303
|
+
// Not a Machado table: the physiological model doesn't cover monochromacy.
|
|
1304
|
+
achromatopsia: cvdLinearTable(
|
|
1305
|
+
[0.2126, 0.7152, 0.0722, 0.2126, 0.7152, 0.0722, 0.2126, 0.7152, 0.0722]
|
|
1306
|
+
)
|
|
1307
|
+
};
|
|
1308
|
+
function computeCvdMatrix(type, severity) {
|
|
1309
|
+
const table = MACHADO[type];
|
|
1310
|
+
if (severity <= 0) return table[0];
|
|
1311
|
+
if (severity >= 1) return table[10];
|
|
1312
|
+
const lo = Math.floor(severity * 10);
|
|
1313
|
+
const t = severity * 10 - lo;
|
|
1314
|
+
const a = table[lo];
|
|
1315
|
+
const b = table[lo + 1];
|
|
1316
|
+
return a.map((v, i) => v + (b[i] - v) * t);
|
|
1317
|
+
}
|
|
1318
|
+
function cvdMatrixToValues(m) {
|
|
1319
|
+
return `${m[0]} ${m[1]} ${m[2]} 0 0 ${m[3]} ${m[4]} ${m[5]} 0 0 ${m[6]} ${m[7]} ${m[8]} 0 0 0 0 0 1 0`;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/core/radiogroup.ts
|
|
1323
|
+
function setupRadiogroupKeyboard(group) {
|
|
1324
|
+
group.addEventListener("keydown", (e) => {
|
|
1325
|
+
const radios = [...group.querySelectorAll("[role='radio']")];
|
|
1326
|
+
const focused = radios.indexOf(document.activeElement);
|
|
1327
|
+
if (focused === -1) return;
|
|
1328
|
+
let next = -1;
|
|
1329
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
1330
|
+
next = (focused + 1) % radios.length;
|
|
1331
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
1332
|
+
next = (focused - 1 + radios.length) % radios.length;
|
|
1333
|
+
} else if (e.key === " " || e.key === "Enter") {
|
|
1334
|
+
radios[focused].click();
|
|
1335
|
+
e.preventDefault();
|
|
1336
|
+
return;
|
|
1337
|
+
} else return;
|
|
1338
|
+
e.preventDefault();
|
|
1339
|
+
radios[next].focus();
|
|
1340
|
+
radios[next].click();
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/rail/a11y-control.ts
|
|
1345
|
+
function applyCvd() {
|
|
1346
|
+
const { type, severity } = store.url.cvd;
|
|
1347
|
+
if (type === "none" || !dom.cvdFeMatrix) {
|
|
1348
|
+
dom.iframe.style.removeProperty("filter");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
dom.cvdFeMatrix.setAttribute("values", cvdMatrixToValues(computeCvdMatrix(type, severity)));
|
|
1352
|
+
dom.iframe.style.setProperty("filter", "url(#cvd-filter)");
|
|
1353
|
+
}
|
|
1354
|
+
function applyType() {
|
|
1355
|
+
const type = store.url.cvd.type;
|
|
1356
|
+
dom.cvdTypeGroup.querySelectorAll("[role='radio']").forEach((btn) => {
|
|
1357
|
+
const active = btn.dataset.cvdType === type;
|
|
1358
|
+
btn.setAttribute("aria-checked", String(active));
|
|
1359
|
+
btn.tabIndex = active ? 0 : -1;
|
|
1360
|
+
});
|
|
1361
|
+
dom.cvdSeveritySlider.disabled = type === "none";
|
|
1362
|
+
updateSeverityFill();
|
|
1363
|
+
updateSignal();
|
|
1364
|
+
}
|
|
1365
|
+
function updateSeverityFill() {
|
|
1366
|
+
const min = parseFloat(dom.cvdSeveritySlider.min || "0");
|
|
1367
|
+
const max = parseFloat(dom.cvdSeveritySlider.max || "1");
|
|
1368
|
+
const v = parseFloat(dom.cvdSeveritySlider.value);
|
|
1369
|
+
const pct = max > min ? (v - min) / (max - min) * 100 : 0;
|
|
1370
|
+
dom.cvdSeveritySlider.style.setProperty("--fill", `${pct}%`);
|
|
1371
|
+
}
|
|
1372
|
+
function updateSignal() {
|
|
1373
|
+
const { type, severity } = store.url.cvd;
|
|
1374
|
+
const active = type !== "none" && severity > 0;
|
|
1375
|
+
const summary = dom.a11yDetails.querySelector(".a11y-picker-summary");
|
|
1376
|
+
const existing = summary.querySelector(".a11y-signal");
|
|
1377
|
+
if (!active) {
|
|
1378
|
+
existing?.remove();
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const el2 = existing ?? (() => {
|
|
1382
|
+
const s = document.createElement("span");
|
|
1383
|
+
s.className = "a11y-signal";
|
|
1384
|
+
s.setAttribute("aria-hidden", "true");
|
|
1385
|
+
summary.appendChild(s);
|
|
1386
|
+
return s;
|
|
1387
|
+
})();
|
|
1388
|
+
const LABELS = {
|
|
1389
|
+
protanopia: "Protanopia",
|
|
1390
|
+
deuteranopia: "Deuteranopia",
|
|
1391
|
+
tritanopia: "Tritanopia",
|
|
1392
|
+
achromatopsia: "Achrom."
|
|
1393
|
+
};
|
|
1394
|
+
el2.textContent = `${LABELS[type] ?? type} \xB7 ${severity.toFixed(2)}`;
|
|
1395
|
+
}
|
|
1396
|
+
function initA11yControl() {
|
|
1397
|
+
if (store.url.cvd.type !== "none") {
|
|
1398
|
+
dom.cvdSeveritySlider.value = String(store.url.cvd.severity);
|
|
1399
|
+
}
|
|
1400
|
+
applyType();
|
|
1401
|
+
setupRadiogroupKeyboard(dom.cvdTypeGroup);
|
|
1402
|
+
dom.cvdTypeGroup.querySelectorAll("[role='radio']").forEach((btn) => {
|
|
1403
|
+
btn.addEventListener("click", () => {
|
|
1404
|
+
const type = btn.dataset.cvdType;
|
|
1405
|
+
const severity = type === "none" ? 0 : parseFloat(dom.cvdSeveritySlider.value);
|
|
1406
|
+
store.setUrl({ cvd: { type, severity } });
|
|
1407
|
+
const u = new URL(window.location.href);
|
|
1408
|
+
if (type === "none") u.searchParams.delete("cvd");
|
|
1409
|
+
else u.searchParams.set("cvd", `${type}:${severity}`);
|
|
1410
|
+
history.replaceState(null, "", u.pathname + (u.search || "") + u.hash);
|
|
1411
|
+
applyType();
|
|
1412
|
+
applyCvd();
|
|
1413
|
+
});
|
|
1414
|
+
});
|
|
1415
|
+
dom.cvdSeveritySlider.addEventListener("input", () => {
|
|
1416
|
+
const type = store.url.cvd.type;
|
|
1417
|
+
if (type === "none") return;
|
|
1418
|
+
const severityStr = dom.cvdSeveritySlider.value;
|
|
1419
|
+
const severity = parseFloat(severityStr);
|
|
1420
|
+
store.setUrl({ cvd: { type, severity } });
|
|
1421
|
+
const u = new URL(window.location.href);
|
|
1422
|
+
u.searchParams.set("cvd", `${type}:${severityStr}`);
|
|
1423
|
+
history.replaceState(null, "", u.pathname + (u.search || "") + u.hash);
|
|
1424
|
+
updateSeverityFill();
|
|
1425
|
+
applyCvd();
|
|
1426
|
+
updateSignal();
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// src/rail/brand.ts
|
|
1431
|
+
function renderBrand(brand) {
|
|
1432
|
+
dom.railBrandEl.innerHTML = "";
|
|
1433
|
+
let inner;
|
|
1434
|
+
if (typeof brand?.logo === "string") {
|
|
1435
|
+
const img = document.createElement("img");
|
|
1436
|
+
img.src = brand.logo;
|
|
1437
|
+
img.alt = brand.logoAlt ?? "";
|
|
1438
|
+
img.className = "rail-brand-logo";
|
|
1439
|
+
inner = img;
|
|
1440
|
+
} else if (brand?.logo && typeof brand.logo === "object") {
|
|
1441
|
+
const picture = document.createElement("picture");
|
|
1442
|
+
const source = document.createElement("source");
|
|
1443
|
+
source.media = "(prefers-color-scheme: dark)";
|
|
1444
|
+
source.srcset = brand.logo.dark;
|
|
1445
|
+
const img = document.createElement("img");
|
|
1446
|
+
img.src = brand.logo.light;
|
|
1447
|
+
img.alt = brand.logoAlt ?? "";
|
|
1448
|
+
picture.appendChild(source);
|
|
1449
|
+
picture.appendChild(img);
|
|
1450
|
+
inner = picture;
|
|
1451
|
+
} else {
|
|
1452
|
+
const span = document.createElement("span");
|
|
1453
|
+
span.className = "rail-brand-title";
|
|
1454
|
+
span.textContent = brand?.title ?? "Pattern Dump";
|
|
1455
|
+
inner = span;
|
|
1456
|
+
}
|
|
1457
|
+
if (brand?.href) {
|
|
1458
|
+
const a = document.createElement("a");
|
|
1459
|
+
a.href = brand.href;
|
|
1460
|
+
a.className = "rail-brand-link";
|
|
1461
|
+
a.appendChild(inner);
|
|
1462
|
+
dom.railBrandEl.appendChild(a);
|
|
1463
|
+
} else {
|
|
1464
|
+
dom.railBrandEl.appendChild(inner);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// src/rail/theme.ts
|
|
1469
|
+
function buildSwatchStrip(tokens, slots) {
|
|
1470
|
+
const strip = document.createElement("span");
|
|
1471
|
+
strip.className = "theme-swatch-strip";
|
|
1472
|
+
strip.setAttribute("aria-hidden", "true");
|
|
1473
|
+
if (!tokens || slots.length === 0) return strip;
|
|
1474
|
+
for (const slot of slots) {
|
|
1475
|
+
const band = document.createElement("span");
|
|
1476
|
+
const value = tokens[slot];
|
|
1477
|
+
if (value !== null && value !== void 0) {
|
|
1478
|
+
band.className = "theme-swatch-band";
|
|
1479
|
+
band.style.background = value;
|
|
1480
|
+
} else {
|
|
1481
|
+
band.className = "theme-swatch-band theme-swatch-undefined";
|
|
1482
|
+
}
|
|
1483
|
+
strip.appendChild(band);
|
|
1484
|
+
}
|
|
1485
|
+
return strip;
|
|
1486
|
+
}
|
|
1487
|
+
function activeThemeName() {
|
|
1488
|
+
return store.local.theme || session.serverDefaultTheme;
|
|
1489
|
+
}
|
|
1490
|
+
function populateThemePicker(fetchedThemes, onThemeApplied) {
|
|
1491
|
+
const currentTheme = activeThemeName();
|
|
1492
|
+
const defaultSlots = ["--bodyBackground", "--themePrimary", "--accent"];
|
|
1493
|
+
const slotsOf = (t) => t?.tokens ? Object.keys(t.tokens) : defaultSlots;
|
|
1494
|
+
const activeTheme = fetchedThemes.find((t) => t.name === currentTheme);
|
|
1495
|
+
dom.themePickerSwatch.innerHTML = "";
|
|
1496
|
+
dom.themePickerSwatch.appendChild(buildSwatchStrip(activeTheme?.tokens, slotsOf(activeTheme)));
|
|
1497
|
+
dom.themePickerCurrentName.textContent = currentTheme || "\u2014 no theme \u2014";
|
|
1498
|
+
dom.themePickerReadout.textContent = currentTheme || "";
|
|
1499
|
+
dom.themePickerList.innerHTML = "";
|
|
1500
|
+
for (const t of fetchedThemes) {
|
|
1501
|
+
const li = document.createElement("li");
|
|
1502
|
+
li.className = "theme-swatch-item";
|
|
1503
|
+
const radio = document.createElement("input");
|
|
1504
|
+
radio.type = "radio";
|
|
1505
|
+
radio.name = "pd-theme";
|
|
1506
|
+
radio.id = `pd-theme-${t.name}`;
|
|
1507
|
+
radio.className = "theme-radio";
|
|
1508
|
+
radio.value = t.name;
|
|
1509
|
+
radio.checked = t.name === currentTheme;
|
|
1510
|
+
const label = document.createElement("label");
|
|
1511
|
+
label.htmlFor = radio.id;
|
|
1512
|
+
label.className = "theme-swatch-chip";
|
|
1513
|
+
label.title = t.name;
|
|
1514
|
+
label.setAttribute("aria-label", t.name);
|
|
1515
|
+
label.appendChild(buildSwatchStrip(t.tokens, slotsOf(t)));
|
|
1516
|
+
radio.addEventListener("change", () => {
|
|
1517
|
+
store.setLocal({ theme: t.name });
|
|
1518
|
+
applyTheme(t.name);
|
|
1519
|
+
dom.themePickerCurrentName.textContent = t.name;
|
|
1520
|
+
dom.themePickerReadout.textContent = t.name;
|
|
1521
|
+
dom.themePickerSwatch.innerHTML = "";
|
|
1522
|
+
dom.themePickerSwatch.appendChild(buildSwatchStrip(t.tokens, slotsOf(t)));
|
|
1523
|
+
onThemeApplied();
|
|
1524
|
+
});
|
|
1525
|
+
label.addEventListener("mouseover", () => {
|
|
1526
|
+
dom.themePickerReadout.textContent = t.name;
|
|
1527
|
+
});
|
|
1528
|
+
label.addEventListener("focus", () => {
|
|
1529
|
+
dom.themePickerReadout.textContent = t.name;
|
|
1530
|
+
});
|
|
1531
|
+
label.addEventListener("mouseleave", () => {
|
|
1532
|
+
dom.themePickerReadout.textContent = activeThemeName();
|
|
1533
|
+
});
|
|
1534
|
+
label.addEventListener("blur", () => {
|
|
1535
|
+
dom.themePickerReadout.textContent = activeThemeName();
|
|
1536
|
+
});
|
|
1537
|
+
li.appendChild(radio);
|
|
1538
|
+
li.appendChild(label);
|
|
1539
|
+
dom.themePickerList.appendChild(li);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function applyTheme(theme) {
|
|
1543
|
+
applyThemeToIframe(dom.iframe, theme);
|
|
1544
|
+
document.querySelectorAll(".gallery-item iframe").forEach((f) => {
|
|
1545
|
+
applyThemeToIframe(f, theme);
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
function applyThemeToIframe(f, theme) {
|
|
1549
|
+
const doc = f.contentDocument;
|
|
1550
|
+
const body = doc?.body;
|
|
1551
|
+
if (!doc || !body) return;
|
|
1552
|
+
if (session.themeSwitch === "stylesheet") {
|
|
1553
|
+
const link = doc.querySelector("link[data-pd-theme]");
|
|
1554
|
+
if (link && theme) {
|
|
1555
|
+
link.setAttribute("href", resourceUrl(themeCssUrl(theme)));
|
|
1556
|
+
link.setAttribute("data-pd-theme", theme);
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (theme) body.setAttribute("data-theme", theme);
|
|
1561
|
+
else body.removeAttribute("data-theme");
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/rail/tree-util.ts
|
|
1565
|
+
function extractFolderPaths(nodes) {
|
|
1566
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1567
|
+
for (const n of nodes) {
|
|
1568
|
+
if (n.children && n.children.length > 0) {
|
|
1569
|
+
paths.add(n.path);
|
|
1570
|
+
for (const p of extractFolderPaths(n.children)) paths.add(p);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return paths;
|
|
1574
|
+
}
|
|
1575
|
+
function buildSidebarHtml(nodes, depth) {
|
|
1576
|
+
return nodes.map((n) => buildNodeHtml(n, depth)).join("");
|
|
1577
|
+
}
|
|
1578
|
+
function isEffectivelyPrivate(node) {
|
|
1579
|
+
if (node.visibility === "private") return true;
|
|
1580
|
+
if (node.kind === "component") return false;
|
|
1581
|
+
if (!node.children || node.children.length === 0) return false;
|
|
1582
|
+
return node.children.every(isEffectivelyPrivate);
|
|
1583
|
+
}
|
|
1584
|
+
function isEffectivelyPackage(node) {
|
|
1585
|
+
if (node.kind === "component") return node.source !== void 0;
|
|
1586
|
+
if (!node.children || node.children.length === 0) return false;
|
|
1587
|
+
return node.children.every(isEffectivelyPackage);
|
|
1588
|
+
}
|
|
1589
|
+
function hasPackageComponents(nodes) {
|
|
1590
|
+
return nodes.some((n) => n.kind === "component" && n.source !== void 0 || hasPackageComponents(n.children ?? []));
|
|
1591
|
+
}
|
|
1592
|
+
function buildNodeHtml(node, depth) {
|
|
1593
|
+
const isPrivate = isEffectivelyPrivate(node);
|
|
1594
|
+
const privateAttr = (isPrivate ? " data-private" : "") + (isEffectivelyPackage(node) ? " data-package" : "");
|
|
1595
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
1596
|
+
if (hasChildren) {
|
|
1597
|
+
const titleEscaped = escapeAttr(node.title);
|
|
1598
|
+
const pathEscaped = escapeAttr(node.path);
|
|
1599
|
+
const showAllLink = node.hasOverview ? `<a href="${routeHref(`/${pathEscaped}`)}" class="tree-show-all" aria-label="Show all ${titleEscaped}">All</a>` : "";
|
|
1600
|
+
let firstItem = "";
|
|
1601
|
+
if (!node.hasOverview && node.kind === "component") {
|
|
1602
|
+
firstItem = `<li class="tree-node"><a href="${routeHref(`/${pathEscaped}`)}" class="tree-link">${titleEscaped}</a></li>`;
|
|
1603
|
+
}
|
|
1604
|
+
const childrenHtml = buildSidebarHtml(node.children, depth + 1);
|
|
1605
|
+
return `<li class="tree-node"${privateAttr} data-path="${pathEscaped}">
|
|
1606
|
+
<div class="tree-folder-row" data-depth="${depth}"><details class="tree-details" data-depth="${depth}"><summary class="tree-folder"><span class="disclosure-caret" aria-hidden="true">${svgIcon("chevron")}</span><span class="tree-folder-label">${titleEscaped}</span></summary></details>${showAllLink}</div>
|
|
1607
|
+
<ul role="list">${firstItem}${childrenHtml}</ul>
|
|
1608
|
+
</li>`;
|
|
1609
|
+
}
|
|
1610
|
+
const privateBadge = isPrivate ? ` <span class="private-badge">private</span>` : "";
|
|
1611
|
+
return `<li class="tree-node"${privateAttr} data-path="${escapeAttr(node.path)}"><a href="${routeHref(`/${escapeAttr(node.path)}`)}" class="tree-link">${escapeAttr(node.title)}${privateBadge}</a></li>`;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// src/rail/tree.ts
|
|
1615
|
+
function restoreNavOpenState(paths) {
|
|
1616
|
+
for (const path of paths) {
|
|
1617
|
+
const li = dom.sidebarTree.querySelector(`.tree-node[data-path="${CSS.escape(path)}"]`);
|
|
1618
|
+
const det = li?.querySelector(":scope > .tree-folder-row > .tree-details");
|
|
1619
|
+
if (det) det.open = true;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function mountSidebar() {
|
|
1623
|
+
dom.sidebarTree.innerHTML = buildSidebarHtml(session.tree.roots, 0);
|
|
1624
|
+
dom.sidebarTree.querySelectorAll("details.tree-details > summary").forEach((summary) => {
|
|
1625
|
+
summary.addEventListener("click", () => {
|
|
1626
|
+
const det = summary.closest("details.tree-details");
|
|
1627
|
+
if (!det) return;
|
|
1628
|
+
const li = det.closest(".tree-node[data-path]");
|
|
1629
|
+
if (!li?.dataset.path) return;
|
|
1630
|
+
const path = li.dataset.path;
|
|
1631
|
+
const paths = new Set(store.local.navOpenPaths);
|
|
1632
|
+
const willBeOpen = !det.open;
|
|
1633
|
+
if (willBeOpen) paths.add(path);
|
|
1634
|
+
else paths.delete(path);
|
|
1635
|
+
store.setLocal({ navOpenPaths: paths });
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
function setActiveTreeItem(path) {
|
|
1640
|
+
dom.sidebarTree.querySelectorAll("[aria-current]").forEach((el3) => el3.removeAttribute("aria-current"));
|
|
1641
|
+
const link = dom.sidebarTree.querySelector(`a.tree-link[href="${CSS.escape(routeHref(`/${path}`))}"]`) ?? dom.sidebarTree.querySelector(`a.tree-show-all[href="${CSS.escape(routeHref(`/${path}`))}"]`);
|
|
1642
|
+
if (!link) return;
|
|
1643
|
+
link.setAttribute("aria-current", "page");
|
|
1644
|
+
let el2 = link.parentElement;
|
|
1645
|
+
while (el2 && el2 !== dom.sidebarTree) {
|
|
1646
|
+
if (el2.classList.contains("tree-node")) {
|
|
1647
|
+
const det = el2.querySelector(":scope > .tree-folder-row > .tree-details");
|
|
1648
|
+
if (det) det.open = true;
|
|
1649
|
+
}
|
|
1650
|
+
el2 = el2.parentElement;
|
|
1651
|
+
}
|
|
1652
|
+
link.scrollIntoView({ block: "nearest" });
|
|
1653
|
+
}
|
|
1654
|
+
function filterTree(query) {
|
|
1655
|
+
const q = query.toLowerCase().trim();
|
|
1656
|
+
if (!q) {
|
|
1657
|
+
dom.sidebarTree.querySelectorAll(".tree-node[data-hidden-search]").forEach((el2) => {
|
|
1658
|
+
el2.hidden = false;
|
|
1659
|
+
el2.removeAttribute("data-hidden-search");
|
|
1660
|
+
});
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
dom.sidebarTree.querySelectorAll(".tree-node").forEach((li) => {
|
|
1664
|
+
const path = li.dataset.path ?? "";
|
|
1665
|
+
const linkText = (li.querySelector("a.tree-link")?.textContent ?? li.querySelector(".tree-folder-label")?.textContent ?? "").toLowerCase();
|
|
1666
|
+
const matches = linkText.includes(q) || path.toLowerCase().includes(q);
|
|
1667
|
+
li.hidden = !matches;
|
|
1668
|
+
if (!matches) li.dataset.hiddenSearch = "1";
|
|
1669
|
+
else li.removeAttribute("data-hidden-search");
|
|
1670
|
+
});
|
|
1671
|
+
const allDetails = Array.from(dom.sidebarTree.querySelectorAll("details.tree-details")).reverse();
|
|
1672
|
+
for (const det of allDetails) {
|
|
1673
|
+
const li = det.closest(".tree-node");
|
|
1674
|
+
if (!li) continue;
|
|
1675
|
+
const ul = li.querySelector(":scope > ul");
|
|
1676
|
+
if (!ul) continue;
|
|
1677
|
+
const hasVisible = Array.from(ul.querySelectorAll(":scope > .tree-node")).some(
|
|
1678
|
+
(child) => !child.hidden
|
|
1679
|
+
);
|
|
1680
|
+
li.hidden = !hasVisible;
|
|
1681
|
+
if (!hasVisible) li.dataset.hiddenSearch = "1";
|
|
1682
|
+
else {
|
|
1683
|
+
li.removeAttribute("data-hidden-search");
|
|
1684
|
+
if (q) det.open = true;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
function applyPackageFilter(hide) {
|
|
1689
|
+
dom.hidePackagesCheck.checked = hide;
|
|
1690
|
+
dom.mainEl.classList.toggle("hide-packages", hide);
|
|
1691
|
+
}
|
|
1692
|
+
function syncPackageControl(tree) {
|
|
1693
|
+
dom.hidePackagesLabel.hidden = !hasPackageComponents(tree.roots);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/stage/fullpage-forwarder.ts
|
|
1697
|
+
var FORWARDER = `<script>(function(){
|
|
1698
|
+
document.addEventListener('keydown',function(e){
|
|
1699
|
+
if(e.key==='Escape'){e.preventDefault();window.parent.postMessage({type:'pd-fp',action:'exit'},'*');}
|
|
1700
|
+
});
|
|
1701
|
+
var _l=0;
|
|
1702
|
+
function _a(){var n=Date.now();if(n-_l<100)return;_l=n;window.parent.postMessage({type:'pd-fp',action:'active'},'*');}
|
|
1703
|
+
document.addEventListener('pointermove',_a);
|
|
1704
|
+
document.addEventListener('pointerdown',_a);
|
|
1705
|
+
document.addEventListener('touchstart',_a,{passive:true});
|
|
1706
|
+
})();<\/script>`;
|
|
1707
|
+
function withForwarder(doc) {
|
|
1708
|
+
return doc.includes("</body>") ? doc.replace("</body>", FORWARDER + "</body>") : doc + FORWARDER;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/stage/viewport.ts
|
|
1712
|
+
var PRESETS = {
|
|
1713
|
+
mobile: { w: 375, h: 667 },
|
|
1714
|
+
tablet: { w: 768, h: 1024 },
|
|
1715
|
+
desktop: { w: 1280, h: 800 }
|
|
1716
|
+
};
|
|
1717
|
+
var vpDisco = false;
|
|
1718
|
+
var vpDiscoRaf = 0;
|
|
1719
|
+
var vpDiscoX = 320;
|
|
1720
|
+
var vpDiscoDir = 1;
|
|
1721
|
+
function updateVpModeLabel() {
|
|
1722
|
+
if (dom.vpDiscoBtn.getAttribute("aria-pressed") === "true") {
|
|
1723
|
+
dom.vpModeLabelEl.textContent = "Disco";
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
const active = dom.viewportToolbar.querySelector('[data-vp][aria-checked="true"]');
|
|
1727
|
+
dom.vpModeLabelEl.textContent = active?.getAttribute("aria-label") ?? "";
|
|
1728
|
+
}
|
|
1729
|
+
function updateReadout() {
|
|
1730
|
+
dom.iframe.title = session.current ? session.currentVariant ? `Preview: ${session.current.id}~${session.currentVariant}` : `Preview: ${session.current.id}` : "Component preview";
|
|
1731
|
+
try {
|
|
1732
|
+
const cw = dom.iframe.contentWindow;
|
|
1733
|
+
if (!cw) {
|
|
1734
|
+
dom.vpReadout.textContent = "\u2014 \xD7 \u2014";
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const w = cw.innerWidth;
|
|
1738
|
+
const h = cw.innerHeight;
|
|
1739
|
+
dom.vpReadout.textContent = `${w} \xD7 ${h}`;
|
|
1740
|
+
dom.vpReadout.setAttribute("aria-label", `Viewport ${w} by ${h} pixels`);
|
|
1741
|
+
} catch {
|
|
1742
|
+
dom.vpReadout.textContent = "\u2014 \xD7 \u2014";
|
|
1743
|
+
}
|
|
1744
|
+
syncMarks();
|
|
1745
|
+
}
|
|
1746
|
+
function syncMarks() {
|
|
1747
|
+
if (dom.singlePreview.hidden) {
|
|
1748
|
+
dom.markOverlay.hidden = true;
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
const fr = dom.iframe.getBoundingClientRect();
|
|
1752
|
+
if (fr.width < 1 || fr.height < 1) {
|
|
1753
|
+
dom.markOverlay.hidden = true;
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const sr = dom.stageView.getBoundingClientRect();
|
|
1757
|
+
const EPSILON = 2;
|
|
1758
|
+
if (fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
|
|
1759
|
+
dom.markOverlay.hidden = true;
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
dom.markOverlay.style.left = `${fr.left - sr.left}px`;
|
|
1763
|
+
dom.markOverlay.style.top = `${fr.top - sr.top}px`;
|
|
1764
|
+
dom.markOverlay.style.width = `${fr.width}px`;
|
|
1765
|
+
dom.markOverlay.style.height = `${fr.height}px`;
|
|
1766
|
+
dom.markOverlay.hidden = false;
|
|
1767
|
+
}
|
|
1768
|
+
function syncFrameFocusListeners() {
|
|
1769
|
+
try {
|
|
1770
|
+
const doc = dom.iframe.contentDocument;
|
|
1771
|
+
if (!doc) return;
|
|
1772
|
+
doc.addEventListener("focusin", () => dom.iframe.classList.add("frame-focused"), { capture: true });
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function currentVpParam() {
|
|
1777
|
+
const vp = store.url.viewport;
|
|
1778
|
+
return vp !== "fit" && vp !== "fullscreen" ? `?vp=${encodeURIComponent(vp)}` : "";
|
|
1779
|
+
}
|
|
1780
|
+
function applyViewport() {
|
|
1781
|
+
const vp = store.url.viewport;
|
|
1782
|
+
if (vp === "fit" || vp === "fullscreen") {
|
|
1783
|
+
dom.iframe.style.removeProperty("--vp-w");
|
|
1784
|
+
dom.iframe.classList.remove("vp-sized");
|
|
1785
|
+
dom.iframe.classList.remove("vp-content");
|
|
1786
|
+
dom.iframe.classList.add("vp-fit");
|
|
1787
|
+
} else if (vp === "content") {
|
|
1788
|
+
const layout = dom.stageView.getAttribute("data-layout") ?? "centered";
|
|
1789
|
+
if (session.lastPdWidth !== null) {
|
|
1790
|
+
dom.iframe.style.setProperty("--vp-w", `${session.lastPdWidth}px`);
|
|
1791
|
+
} else if (layout === "full") {
|
|
1792
|
+
dom.iframe.style.removeProperty("--vp-w");
|
|
1793
|
+
} else {
|
|
1794
|
+
dom.iframe.style.setProperty("--vp-w", "max-content");
|
|
1795
|
+
}
|
|
1796
|
+
dom.iframe.classList.remove("vp-sized");
|
|
1797
|
+
dom.iframe.classList.remove("vp-fit");
|
|
1798
|
+
dom.iframe.classList.add("vp-content");
|
|
1799
|
+
} else {
|
|
1800
|
+
const p = PRESETS[vp];
|
|
1801
|
+
dom.iframe.style.setProperty("--vp-w", `${p.w}px`);
|
|
1802
|
+
dom.iframe.classList.add("vp-sized");
|
|
1803
|
+
dom.iframe.classList.remove("vp-content");
|
|
1804
|
+
dom.iframe.classList.remove("vp-fit");
|
|
1805
|
+
}
|
|
1806
|
+
dom.viewportToolbar.querySelectorAll("[data-vp]").forEach((btn) => {
|
|
1807
|
+
const key = btn.dataset.vp;
|
|
1808
|
+
const active = key === vp;
|
|
1809
|
+
btn.setAttribute("aria-checked", String(active));
|
|
1810
|
+
btn.innerHTML = active ? svgIcon(`vp-${key}-fill`) : svgIcon(`vp-${key}`);
|
|
1811
|
+
});
|
|
1812
|
+
const radios = [...dom.viewportToolbar.querySelectorAll("[role='radio']")];
|
|
1813
|
+
const checkedIdx = radios.findIndex((b) => b.dataset.vp === vp && vp !== "fullscreen");
|
|
1814
|
+
radios.forEach((b, i) => {
|
|
1815
|
+
b.tabIndex = (checkedIdx === -1 ? i === 0 : i === checkedIdx) ? 0 : -1;
|
|
1816
|
+
});
|
|
1817
|
+
requestAnimationFrame(updateReadout);
|
|
1818
|
+
updateVpModeLabel();
|
|
1819
|
+
}
|
|
1820
|
+
function isDiscoRunning() {
|
|
1821
|
+
return vpDisco;
|
|
1822
|
+
}
|
|
1823
|
+
function stopDisco() {
|
|
1824
|
+
vpDisco = false;
|
|
1825
|
+
cancelAnimationFrame(vpDiscoRaf);
|
|
1826
|
+
dom.viewportToolbar.removeAttribute("data-disco-active");
|
|
1827
|
+
dom.vpDiscoBtn.setAttribute("aria-pressed", "false");
|
|
1828
|
+
dom.vpDiscoBtn.innerHTML = svgIcon("vp-disco");
|
|
1829
|
+
delete dom.mainEl.dataset.noTransition;
|
|
1830
|
+
applyViewport();
|
|
1831
|
+
}
|
|
1832
|
+
function startDisco() {
|
|
1833
|
+
if (vpDisco) return;
|
|
1834
|
+
vpDisco = true;
|
|
1835
|
+
dom.viewportToolbar.setAttribute("data-disco-active", "");
|
|
1836
|
+
dom.vpDiscoBtn.setAttribute("aria-pressed", "true");
|
|
1837
|
+
dom.vpDiscoBtn.innerHTML = svgIcon("vp-disco-fill");
|
|
1838
|
+
dom.mainEl.dataset.noTransition = "";
|
|
1839
|
+
dom.iframe.classList.add("vp-sized");
|
|
1840
|
+
vpDiscoX = Math.max(320, Math.round(dom.iframe.getBoundingClientRect().width) || 320);
|
|
1841
|
+
vpDiscoDir = 1;
|
|
1842
|
+
function tick() {
|
|
1843
|
+
if (!vpDisco) return;
|
|
1844
|
+
const stageW = dom.stageView.clientWidth;
|
|
1845
|
+
vpDiscoX += vpDiscoDir * 4;
|
|
1846
|
+
if (vpDiscoX >= stageW) {
|
|
1847
|
+
vpDiscoX = stageW;
|
|
1848
|
+
vpDiscoDir = -1;
|
|
1849
|
+
}
|
|
1850
|
+
if (vpDiscoX <= 320) {
|
|
1851
|
+
vpDiscoX = 320;
|
|
1852
|
+
vpDiscoDir = 1;
|
|
1853
|
+
}
|
|
1854
|
+
dom.iframe.style.setProperty("--vp-w", `${vpDiscoX}px`);
|
|
1855
|
+
updateReadout();
|
|
1856
|
+
vpDiscoRaf = requestAnimationFrame(tick);
|
|
1857
|
+
}
|
|
1858
|
+
vpDiscoRaf = requestAnimationFrame(tick);
|
|
1859
|
+
}
|
|
1860
|
+
function initViewport(onFullscreen) {
|
|
1861
|
+
applyViewport();
|
|
1862
|
+
dom.viewportToolbar.querySelectorAll("[data-vp]").forEach((btn) => {
|
|
1863
|
+
btn.addEventListener("click", () => {
|
|
1864
|
+
if (vpDisco) stopDisco();
|
|
1865
|
+
const key = btn.dataset.vp;
|
|
1866
|
+
if (key === "fullscreen") {
|
|
1867
|
+
onFullscreen();
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
store.setUrl({ viewport: key });
|
|
1871
|
+
const u = new URL(window.location.href);
|
|
1872
|
+
if (key === "fit") u.searchParams.delete("vp");
|
|
1873
|
+
else u.searchParams.set("vp", key);
|
|
1874
|
+
history.replaceState(null, "", u.pathname + (u.search || "") + u.hash);
|
|
1875
|
+
applyViewport();
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
dom.vpDiscoBtn.addEventListener("click", () => {
|
|
1879
|
+
if (vpDisco) stopDisco();
|
|
1880
|
+
else startDisco();
|
|
1881
|
+
updateVpModeLabel();
|
|
1882
|
+
});
|
|
1883
|
+
let vpPointerInside = false;
|
|
1884
|
+
let vpFocusInside = false;
|
|
1885
|
+
const showVpLabel = (e) => {
|
|
1886
|
+
const btn = e.target.closest(".rail-vp-btn");
|
|
1887
|
+
if (!btn) return;
|
|
1888
|
+
if (btn.dataset.labelOff !== void 0) {
|
|
1889
|
+
const on = btn.getAttribute("aria-pressed") === "true";
|
|
1890
|
+
dom.vpModeLabelEl.textContent = on ? btn.dataset.labelOn ?? "" : btn.dataset.labelOff ?? "";
|
|
1891
|
+
} else {
|
|
1892
|
+
dom.vpModeLabelEl.textContent = btn.getAttribute("aria-label") ?? "";
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
const tryRevertVpLabel = () => {
|
|
1896
|
+
if (!vpPointerInside && !vpFocusInside) updateVpModeLabel();
|
|
1897
|
+
};
|
|
1898
|
+
dom.viewportToolbar.addEventListener("pointerenter", (e) => {
|
|
1899
|
+
vpPointerInside = true;
|
|
1900
|
+
showVpLabel(e);
|
|
1901
|
+
}, true);
|
|
1902
|
+
dom.viewportToolbar.addEventListener("pointerleave", () => {
|
|
1903
|
+
vpPointerInside = false;
|
|
1904
|
+
tryRevertVpLabel();
|
|
1905
|
+
});
|
|
1906
|
+
dom.viewportToolbar.addEventListener("focusin", (e) => {
|
|
1907
|
+
vpFocusInside = true;
|
|
1908
|
+
showVpLabel(e);
|
|
1909
|
+
});
|
|
1910
|
+
dom.viewportToolbar.addEventListener("focusout", (e) => {
|
|
1911
|
+
if (!e.relatedTarget || !dom.viewportToolbar.contains(e.relatedTarget)) {
|
|
1912
|
+
vpFocusInside = false;
|
|
1913
|
+
tryRevertVpLabel();
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
setupRadiogroupKeyboard(dom.viewportToolbar.querySelector("[role='radiogroup']"));
|
|
1917
|
+
const iframeObserver = new ResizeObserver(() => requestAnimationFrame(updateReadout));
|
|
1918
|
+
iframeObserver.observe(dom.iframe);
|
|
1919
|
+
iframeObserver.observe(dom.stageView);
|
|
1920
|
+
dom.iframe.addEventListener("load", () => {
|
|
1921
|
+
updateReadout();
|
|
1922
|
+
syncFrameFocusListeners();
|
|
1923
|
+
});
|
|
1924
|
+
document.addEventListener("focusin", () => dom.iframe.classList.remove("frame-focused"), { capture: true });
|
|
1925
|
+
}
|
|
1926
|
+
function initDprTracking() {
|
|
1927
|
+
const setDpr = () => {
|
|
1928
|
+
document.documentElement.style.setProperty("--dpr", String(window.devicePixelRatio));
|
|
1929
|
+
};
|
|
1930
|
+
setDpr();
|
|
1931
|
+
const arm = () => {
|
|
1932
|
+
const mq = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
1933
|
+
mq.addEventListener("change", () => {
|
|
1934
|
+
setDpr();
|
|
1935
|
+
arm();
|
|
1936
|
+
}, { once: true });
|
|
1937
|
+
};
|
|
1938
|
+
arm();
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// src/stage/fullpage.ts
|
|
1942
|
+
var fpIdleTimer = null;
|
|
1943
|
+
var fullpageUnsubscribe = null;
|
|
1944
|
+
function showFullpageChrome() {
|
|
1945
|
+
dom.fullpageDialog.classList.add("fp-chrome-visible");
|
|
1946
|
+
if (fpIdleTimer !== null) clearTimeout(fpIdleTimer);
|
|
1947
|
+
fpIdleTimer = setTimeout(() => {
|
|
1948
|
+
dom.fullpageDialog.classList.remove("fp-chrome-visible");
|
|
1949
|
+
fpIdleTimer = null;
|
|
1950
|
+
}, 2e3);
|
|
1951
|
+
}
|
|
1952
|
+
async function refreshFullpageContent() {
|
|
1953
|
+
if (!session.current) return;
|
|
1954
|
+
try {
|
|
1955
|
+
const doc = await fetch(previewUrl(session.current.id, session.currentVariant ?? void 0, store.local.theme || void 0)).then((r) => r.text());
|
|
1956
|
+
dom.fullpageFrame.srcdoc = withForwarder(doc);
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
async function openFullpage() {
|
|
1961
|
+
if (!session.current) return;
|
|
1962
|
+
store.setUrl({ viewport: "fullscreen" });
|
|
1963
|
+
const u = new URL(window.location.href);
|
|
1964
|
+
u.searchParams.set("vp", "fullscreen");
|
|
1965
|
+
history.replaceState(null, "", u.pathname + (u.search || "") + u.hash);
|
|
1966
|
+
applyViewport();
|
|
1967
|
+
await refreshFullpageContent();
|
|
1968
|
+
dom.fullpageDialog.showModal();
|
|
1969
|
+
if (fullpageUnsubscribe) fullpageUnsubscribe();
|
|
1970
|
+
fullpageUnsubscribe = store.subscribe(() => {
|
|
1971
|
+
if (dom.fullpageDialog.open) void refreshFullpageContent();
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
function initFullpage() {
|
|
1975
|
+
dom.fullpageCloseBtn.addEventListener("click", () => dom.fullpageDialog.close());
|
|
1976
|
+
dom.fullpageDialog.addEventListener("close", () => {
|
|
1977
|
+
dom.fullpageFrame.srcdoc = "";
|
|
1978
|
+
if (fpIdleTimer !== null) {
|
|
1979
|
+
clearTimeout(fpIdleTimer);
|
|
1980
|
+
fpIdleTimer = null;
|
|
1981
|
+
}
|
|
1982
|
+
dom.fullpageDialog.classList.remove("fp-chrome-visible");
|
|
1983
|
+
if (fullpageUnsubscribe) {
|
|
1984
|
+
fullpageUnsubscribe();
|
|
1985
|
+
fullpageUnsubscribe = null;
|
|
1986
|
+
}
|
|
1987
|
+
if (store.url.viewport === "fullscreen") {
|
|
1988
|
+
store.setUrl({ viewport: "fit" });
|
|
1989
|
+
const u = new URL(window.location.href);
|
|
1990
|
+
u.searchParams.delete("vp");
|
|
1991
|
+
history.replaceState(null, "", u.pathname + (u.search || "") + u.hash);
|
|
1992
|
+
applyViewport();
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
dom.viewportToolbar.querySelector(".fullpage-btn").addEventListener("click", () => {
|
|
1996
|
+
void openFullpage();
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/stage/splitters.ts
|
|
2001
|
+
function initSplitters() {
|
|
2002
|
+
const dims = store.local.dims;
|
|
2003
|
+
if (dims.sidebarW) dom.mainEl.style.setProperty("--pd-sidebar-w", dims.sidebarW);
|
|
2004
|
+
if (dims.panelW) dom.mainEl.style.setProperty("--pd-panel-w", dims.panelW);
|
|
2005
|
+
if (dims.dockH) dom.mainEl.style.setProperty("--pd-dock-h", dims.dockH);
|
|
2006
|
+
requestAnimationFrame(() => {
|
|
2007
|
+
const vw = window.innerWidth, vh = window.innerHeight;
|
|
2008
|
+
const renderedSidebar = dom.sidebarEl.getBoundingClientRect().width;
|
|
2009
|
+
const renderedPanel = "panelOpen" in dom.mainEl.dataset && !dom.panel.hidden ? dom.panel.getBoundingClientRect().width : null;
|
|
2010
|
+
const renderedDock = !dom.dockEl.hidden ? dom.dockEl.getBoundingClientRect().height : null;
|
|
2011
|
+
function pxToVw(px) {
|
|
2012
|
+
return `${(px / vw * 100).toFixed(2)}vw`;
|
|
2013
|
+
}
|
|
2014
|
+
function pxToVh(px) {
|
|
2015
|
+
return `${(px / vh * 100).toFixed(2)}vh`;
|
|
2016
|
+
}
|
|
2017
|
+
const cur = store.local.dims;
|
|
2018
|
+
const storedSidebarPx = cur.sidebarW ? parseFloat(cur.sidebarW) / 100 * vw : null;
|
|
2019
|
+
const storedPanelPx = cur.panelW ? parseFloat(cur.panelW) / 100 * vw : null;
|
|
2020
|
+
const storedDockPx = cur.dockH ? parseFloat(cur.dockH) / 100 * vh : null;
|
|
2021
|
+
const EPS = 2;
|
|
2022
|
+
let next = cur;
|
|
2023
|
+
let changed = false;
|
|
2024
|
+
if (storedSidebarPx !== null && Math.abs(storedSidebarPx - renderedSidebar) > EPS) {
|
|
2025
|
+
next = { ...next, sidebarW: pxToVw(renderedSidebar) };
|
|
2026
|
+
dom.mainEl.style.setProperty("--pd-sidebar-w", next.sidebarW);
|
|
2027
|
+
changed = true;
|
|
2028
|
+
}
|
|
2029
|
+
if (renderedPanel !== null && storedPanelPx !== null && Math.abs(storedPanelPx - renderedPanel) > EPS) {
|
|
2030
|
+
next = { ...next, panelW: pxToVw(renderedPanel) };
|
|
2031
|
+
dom.mainEl.style.setProperty("--pd-panel-w", next.panelW);
|
|
2032
|
+
changed = true;
|
|
2033
|
+
}
|
|
2034
|
+
if (renderedDock !== null && storedDockPx !== null && Math.abs(storedDockPx - renderedDock) > EPS) {
|
|
2035
|
+
next = { ...next, dockH: pxToVh(renderedDock) };
|
|
2036
|
+
dom.mainEl.style.setProperty("--pd-dock-h", next.dockH);
|
|
2037
|
+
changed = true;
|
|
2038
|
+
}
|
|
2039
|
+
if (changed) store.setLocal({ dims: next });
|
|
2040
|
+
});
|
|
2041
|
+
function probeWidth(cssExpr, contextEl) {
|
|
2042
|
+
const el2 = document.createElement("div");
|
|
2043
|
+
el2.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;width:${cssExpr}`;
|
|
2044
|
+
contextEl.appendChild(el2);
|
|
2045
|
+
const px = Math.round(el2.getBoundingClientRect().width);
|
|
2046
|
+
el2.remove();
|
|
2047
|
+
return px;
|
|
2048
|
+
}
|
|
2049
|
+
const STAGE_MIN_PX = 320;
|
|
2050
|
+
const DOCK_MIN_PX = 80;
|
|
2051
|
+
const panelFloorPx = 320;
|
|
2052
|
+
const sidebarFloorPx = probeWidth("var(--pd-rail-min)", dom.mainEl);
|
|
2053
|
+
dom.mainEl.querySelectorAll("[data-resize]").forEach((el2) => {
|
|
2054
|
+
const handle = el2;
|
|
2055
|
+
const key = handle.dataset.resize;
|
|
2056
|
+
handle.addEventListener("pointerdown", (e) => {
|
|
2057
|
+
const pe = e;
|
|
2058
|
+
pe.preventDefault();
|
|
2059
|
+
handle.setPointerCapture(pe.pointerId);
|
|
2060
|
+
dom.mainEl.dataset.noTransition = "";
|
|
2061
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2062
|
+
f.style.pointerEvents = "none";
|
|
2063
|
+
});
|
|
2064
|
+
const startX = pe.clientX;
|
|
2065
|
+
const startY = pe.clientY;
|
|
2066
|
+
let startPx;
|
|
2067
|
+
if (key === "sidebar-w") startPx = dom.sidebarEl.getBoundingClientRect().width;
|
|
2068
|
+
else if (key === "panel-w") startPx = dom.panel.getBoundingClientRect().width;
|
|
2069
|
+
else startPx = dom.dockEl.getBoundingClientRect().height;
|
|
2070
|
+
function computeVal(ce) {
|
|
2071
|
+
let newPx;
|
|
2072
|
+
if (key === "sidebar-w") {
|
|
2073
|
+
newPx = startPx + (ce.clientX - startX);
|
|
2074
|
+
const reservedForPanel = "panelOpen" in dom.mainEl.dataset ? panelFloorPx : 0;
|
|
2075
|
+
newPx = Math.min(newPx, window.innerWidth - reservedForPanel - STAGE_MIN_PX);
|
|
2076
|
+
newPx = Math.max(newPx, sidebarFloorPx);
|
|
2077
|
+
} else if (key === "panel-w") {
|
|
2078
|
+
newPx = startPx - (ce.clientX - startX);
|
|
2079
|
+
newPx = Math.min(newPx, window.innerWidth - sidebarFloorPx - STAGE_MIN_PX);
|
|
2080
|
+
newPx = Math.max(newPx, panelFloorPx);
|
|
2081
|
+
} else {
|
|
2082
|
+
newPx = startPx - (ce.clientY - startY);
|
|
2083
|
+
newPx = Math.max(newPx, DOCK_MIN_PX);
|
|
2084
|
+
newPx = Math.min(newPx, window.innerHeight * 0.8);
|
|
2085
|
+
}
|
|
2086
|
+
return `${Math.round(newPx)}px`;
|
|
2087
|
+
}
|
|
2088
|
+
const prop = `--pd-${key}`;
|
|
2089
|
+
function onMove(me) {
|
|
2090
|
+
dom.mainEl.style.setProperty(prop, computeVal(me));
|
|
2091
|
+
}
|
|
2092
|
+
function endDrag() {
|
|
2093
|
+
delete dom.mainEl.dataset.noTransition;
|
|
2094
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2095
|
+
f.style.pointerEvents = "";
|
|
2096
|
+
});
|
|
2097
|
+
const actualPx = key === "sidebar-w" ? dom.sidebarEl.getBoundingClientRect().width : key === "panel-w" ? dom.panel.getBoundingClientRect().width : dom.dockEl.getBoundingClientRect().height;
|
|
2098
|
+
const val = key === "dock-h" ? `${(actualPx / window.innerHeight * 100).toFixed(2)}vh` : `${(actualPx / window.innerWidth * 100).toFixed(2)}vw`;
|
|
2099
|
+
dom.mainEl.style.setProperty(prop, val);
|
|
2100
|
+
const cur = store.local.dims;
|
|
2101
|
+
if (key === "sidebar-w") store.setLocal({ dims: { ...cur, sidebarW: val } });
|
|
2102
|
+
else if (key === "panel-w") store.setLocal({ dims: { ...cur, panelW: val } });
|
|
2103
|
+
else store.setLocal({ dims: { ...cur, dockH: val } });
|
|
2104
|
+
document.removeEventListener("pointermove", onMove, true);
|
|
2105
|
+
document.removeEventListener("pointerup", endDrag, true);
|
|
2106
|
+
document.removeEventListener("pointercancel", endDrag, true);
|
|
2107
|
+
}
|
|
2108
|
+
document.addEventListener("pointermove", onMove, true);
|
|
2109
|
+
document.addEventListener("pointerup", endDrag, true);
|
|
2110
|
+
document.addEventListener("pointercancel", endDrag, true);
|
|
2111
|
+
});
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/core/pd-links.ts
|
|
2116
|
+
function reconcilePdLinks(doc, orderedHrefs) {
|
|
2117
|
+
if (!doc) return;
|
|
2118
|
+
const desired = orderedHrefs.flatMap((href) => {
|
|
2119
|
+
const m = href.match(/\/_pd\/components\/(.+?)\.css/);
|
|
2120
|
+
return m ? [{ id: m[1], href }] : [];
|
|
2121
|
+
});
|
|
2122
|
+
const desiredIds = new Set(desired.map((d) => d.id));
|
|
2123
|
+
const existing = Array.from(doc.querySelectorAll("link[data-pd-comp]"));
|
|
2124
|
+
const existingMap = new Map(existing.map((l) => [l.dataset.pdComp, l]));
|
|
2125
|
+
for (const link of existing) {
|
|
2126
|
+
if (!desiredIds.has(link.dataset.pdComp)) link.remove();
|
|
2127
|
+
}
|
|
2128
|
+
for (let i = 0; i < desired.length; i++) {
|
|
2129
|
+
const { id, href } = desired[i];
|
|
2130
|
+
const existingLink = existingMap.get(id);
|
|
2131
|
+
if (existingLink) {
|
|
2132
|
+
existingLink.href = href;
|
|
2133
|
+
} else {
|
|
2134
|
+
const link = doc.createElement("link");
|
|
2135
|
+
link.rel = "stylesheet";
|
|
2136
|
+
link.href = href;
|
|
2137
|
+
link.dataset.pdComp = id;
|
|
2138
|
+
let anchor = null;
|
|
2139
|
+
for (let j = i + 1; j < desired.length; j++) {
|
|
2140
|
+
const follower = existingMap.get(desired[j].id);
|
|
2141
|
+
if (follower?.parentNode === doc.head) {
|
|
2142
|
+
anchor = follower;
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
doc.head.insertBefore(link, anchor);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/core/protocol.ts
|
|
2152
|
+
function protocolMismatch(serverProtocol, shellProtocol = PD_PROTOCOL_VERSION) {
|
|
2153
|
+
if (serverProtocol === shellProtocol) return null;
|
|
2154
|
+
const server = typeof serverProtocol === "number" ? `v${serverProtocol}` : "an unknown version";
|
|
2155
|
+
return `Protocol mismatch \u2014 the server speaks ${server}, this shell expects v${shellProtocol}. Rebuild and restart the dev server, then reload.`;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// src/app/galleries.ts
|
|
2159
|
+
async function buildVariantsGallery() {
|
|
2160
|
+
if (!session.current) return;
|
|
2161
|
+
dom.variantsGalleryEl.innerHTML = "";
|
|
2162
|
+
const gallery = document.createElement("div");
|
|
2163
|
+
gallery.className = "gallery";
|
|
2164
|
+
const currentTheme = store.local.theme;
|
|
2165
|
+
const allVariants = [
|
|
2166
|
+
{ name: "default", variantParam: void 0 },
|
|
2167
|
+
...session.current.variants.map((v) => ({ name: v.name, variantParam: v.name }))
|
|
2168
|
+
];
|
|
2169
|
+
const docs = await Promise.all(
|
|
2170
|
+
allVariants.map((v) => fetch(previewUrl(session.current.id, v.variantParam, currentTheme || void 0)).then((r) => r.text()))
|
|
2171
|
+
);
|
|
2172
|
+
for (let i = 0; i < allVariants.length; i++) {
|
|
2173
|
+
const v = allVariants[i];
|
|
2174
|
+
const fig = document.createElement("figure");
|
|
2175
|
+
fig.className = "preview gallery-item";
|
|
2176
|
+
const isDefault = v.variantParam === void 0;
|
|
2177
|
+
const labelText = isDefault ? session.current.title : `${session.current.title} \u2014 ${v.name}`;
|
|
2178
|
+
const labelHref = isDefault ? `/${session.current.id}${currentVpParam()}` : `/${session.current.id}${currentVpParam()}#${encodeURIComponent(v.name)}`;
|
|
2179
|
+
const h2 = document.createElement("h2");
|
|
2180
|
+
h2.className = "gallery-label";
|
|
2181
|
+
const lnk = document.createElement("a");
|
|
2182
|
+
lnk.className = "gallery-label-link";
|
|
2183
|
+
lnk.href = labelHref;
|
|
2184
|
+
lnk.textContent = labelText;
|
|
2185
|
+
h2.appendChild(lnk);
|
|
2186
|
+
const f = document.createElement("iframe");
|
|
2187
|
+
f.title = labelText;
|
|
2188
|
+
f.srcdoc = docs[i];
|
|
2189
|
+
f.dataset.pdVariant = v.variantParam ?? "";
|
|
2190
|
+
f.addEventListener("load", () => {
|
|
2191
|
+
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2192
|
+
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2193
|
+
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2194
|
+
});
|
|
2195
|
+
fig.appendChild(h2);
|
|
2196
|
+
fig.appendChild(f);
|
|
2197
|
+
gallery.appendChild(fig);
|
|
2198
|
+
}
|
|
2199
|
+
dom.variantsGalleryEl.appendChild(gallery);
|
|
2200
|
+
}
|
|
2201
|
+
function renderOverview(overview) {
|
|
2202
|
+
dom.categoryOverview.innerHTML = "";
|
|
2203
|
+
if (overview.doc) {
|
|
2204
|
+
const docDiv = document.createElement("div");
|
|
2205
|
+
docDiv.className = "overview-doc";
|
|
2206
|
+
docDiv.innerHTML = overview.doc;
|
|
2207
|
+
dom.categoryOverview.appendChild(docDiv);
|
|
2208
|
+
}
|
|
2209
|
+
if (overview.components.length === 0) {
|
|
2210
|
+
const p = document.createElement("p");
|
|
2211
|
+
p.textContent = "No components found.";
|
|
2212
|
+
dom.categoryOverview.appendChild(p);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
const gallery = document.createElement("div");
|
|
2216
|
+
gallery.className = "gallery";
|
|
2217
|
+
for (const item of overview.components) {
|
|
2218
|
+
const fig = document.createElement("figure");
|
|
2219
|
+
fig.className = "preview gallery-item";
|
|
2220
|
+
if (item.source) fig.dataset.package = item.source;
|
|
2221
|
+
const h2 = document.createElement("h2");
|
|
2222
|
+
h2.className = "gallery-label";
|
|
2223
|
+
const lnk = document.createElement("a");
|
|
2224
|
+
lnk.className = "gallery-label-link";
|
|
2225
|
+
lnk.href = routeHref(`/${item.id}`);
|
|
2226
|
+
lnk.textContent = item.title;
|
|
2227
|
+
h2.appendChild(lnk);
|
|
2228
|
+
const f = document.createElement("iframe");
|
|
2229
|
+
f.title = item.title;
|
|
2230
|
+
f.addEventListener("load", () => {
|
|
2231
|
+
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2232
|
+
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2233
|
+
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2234
|
+
});
|
|
2235
|
+
f.src = resourceUrl(previewUrl(item.id, void 0, store.local.theme || void 0));
|
|
2236
|
+
fig.appendChild(h2);
|
|
2237
|
+
fig.appendChild(f);
|
|
2238
|
+
gallery.appendChild(fig);
|
|
2239
|
+
}
|
|
2240
|
+
dom.categoryOverview.appendChild(gallery);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// src/app/navigation.ts
|
|
2244
|
+
function swapWithTransition(fn) {
|
|
2245
|
+
if (document.startViewTransition && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
2246
|
+
document.startViewTransition(fn);
|
|
2247
|
+
} else {
|
|
2248
|
+
fn();
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
async function applyComponent(detail, variant) {
|
|
2252
|
+
session.current = detail;
|
|
2253
|
+
session.currentVariant = variant ?? null;
|
|
2254
|
+
session.renderedHtmlCache = null;
|
|
2255
|
+
session.rawRenderedHtml = null;
|
|
2256
|
+
session.stageMode = "single";
|
|
2257
|
+
dom.panel.hidden = false;
|
|
2258
|
+
dom.singlePreview.hidden = false;
|
|
2259
|
+
dom.categoryOverview.hidden = true;
|
|
2260
|
+
dom.variantsGalleryEl.hidden = true;
|
|
2261
|
+
dom.stageView.dataset.layout = detail.layout;
|
|
2262
|
+
dom.previewCaption.textContent = detail.id;
|
|
2263
|
+
populateVariantSelect(detail.variants);
|
|
2264
|
+
const isKnownVariant = !variant || detail.variants.some((v) => v.name === variant);
|
|
2265
|
+
if (!isKnownVariant) {
|
|
2266
|
+
session.currentVariant = null;
|
|
2267
|
+
variant = void 0;
|
|
2268
|
+
history.replaceState(null, "", window.location.pathname + (window.location.search || ""));
|
|
2269
|
+
}
|
|
2270
|
+
dom.variantSelect.value = variant ?? "";
|
|
2271
|
+
dom.variantBar.hidden = detail.variants.length === 0;
|
|
2272
|
+
if (session.componentRegistry.get(detail.id)?.visibility === "private" && !dom.showInternalsCheck.checked) {
|
|
2273
|
+
dom.showInternalsCheck.checked = true;
|
|
2274
|
+
dom.showInternalsCheck.dispatchEvent(new Event("change"));
|
|
2275
|
+
}
|
|
2276
|
+
if (session.componentRegistry.get(detail.id)?.source && dom.hidePackagesCheck.checked) {
|
|
2277
|
+
dom.hidePackagesCheck.checked = false;
|
|
2278
|
+
dom.hidePackagesCheck.dispatchEvent(new Event("change"));
|
|
2279
|
+
}
|
|
2280
|
+
const previewDoc = variant && isKnownVariant ? await fetch(previewUrl(detail.id, variant, store.local.theme || void 0)).then((r) => r.text()) : detail.document;
|
|
2281
|
+
const _html = previewDoc ?? "";
|
|
2282
|
+
swapWithTransition(() => {
|
|
2283
|
+
dom.iframe.srcdoc = _html;
|
|
2284
|
+
});
|
|
2285
|
+
dom.iframe.style.removeProperty("--vp-h");
|
|
2286
|
+
session.lastPdWidth = null;
|
|
2287
|
+
if (isDiscoRunning()) stopDisco();
|
|
2288
|
+
applyViewport();
|
|
2289
|
+
delete dom.dockEl.dataset.mode;
|
|
2290
|
+
dom.hbsBody.innerHTML = extractHighlightedPre(detail.source.hbsHighlighted);
|
|
2291
|
+
dom.htmlBody.innerHTML = "";
|
|
2292
|
+
const hasData = detail.source.json !== void 0;
|
|
2293
|
+
dom.dockFacetDataBtn.hidden = !hasData;
|
|
2294
|
+
if (hasData) {
|
|
2295
|
+
session.dataCache = detail.source.jsonHighlighted ?? null;
|
|
2296
|
+
session.dataRaw = detail.source.json ?? null;
|
|
2297
|
+
dom.dataBody.innerHTML = session.dataCache ? extractHighlightedPre(session.dataCache) : "";
|
|
2298
|
+
} else {
|
|
2299
|
+
session.dataCache = null;
|
|
2300
|
+
session.dataRaw = null;
|
|
2301
|
+
dom.dataBody.innerHTML = "";
|
|
2302
|
+
}
|
|
2303
|
+
const hasSchema = detail.source.schema !== void 0;
|
|
2304
|
+
dom.dockFacetSchemaBtn.hidden = !hasSchema;
|
|
2305
|
+
dom.schemaBody.innerHTML = hasSchema ? extractHighlightedPre(detail.source.schemaHighlighted ?? "") : "";
|
|
2306
|
+
session.renderedA11y = [];
|
|
2307
|
+
session.scanPending = false;
|
|
2308
|
+
refreshDiagnostics();
|
|
2309
|
+
switchDockTab("source", "hbs");
|
|
2310
|
+
setActiveTreeItem(detail.id);
|
|
2311
|
+
await renderPanel();
|
|
2312
|
+
}
|
|
2313
|
+
async function swapVariant(componentId, variant) {
|
|
2314
|
+
if (!session.current || session.current.id !== componentId) return;
|
|
2315
|
+
session.currentVariant = variant;
|
|
2316
|
+
session.renderedHtmlCache = null;
|
|
2317
|
+
session.rawRenderedHtml = null;
|
|
2318
|
+
session.dataCache = null;
|
|
2319
|
+
session.dataRaw = null;
|
|
2320
|
+
dom.htmlBody.innerHTML = "";
|
|
2321
|
+
dom.dataBody.innerHTML = "";
|
|
2322
|
+
dom.variantSelect.value = variant ?? "";
|
|
2323
|
+
const previewDoc = await fetch(previewUrl(componentId, variant ?? void 0, store.local.theme || void 0)).then((r) => r.text());
|
|
2324
|
+
swapWithTransition(() => {
|
|
2325
|
+
dom.iframe.srcdoc = previewDoc;
|
|
2326
|
+
});
|
|
2327
|
+
const dock = store.local.dock;
|
|
2328
|
+
if (dock.dockTab === "html") await fetchAndShowHtml();
|
|
2329
|
+
if (dock.dockTab === "source" && dock.sourceFacet === "data") await fetchAndShowData();
|
|
2330
|
+
void fetch(componentUrl(componentId, variant ?? void 0)).then((r) => r.ok ? r.json() : Promise.reject()).then((d) => {
|
|
2331
|
+
session.current = d;
|
|
2332
|
+
session.renderedA11y = [];
|
|
2333
|
+
session.scanPending = store.local.dock.dockTab === "diagnostics";
|
|
2334
|
+
refreshDiagnostics();
|
|
2335
|
+
if (store.local.dock.dockTab === "diagnostics") {
|
|
2336
|
+
dom.iframe.addEventListener("load", () => void runRenderedA11yScan(), { once: true });
|
|
2337
|
+
}
|
|
2338
|
+
}).catch(() => {
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
async function navigate(componentId, variant) {
|
|
2342
|
+
try {
|
|
2343
|
+
const res = await fetch(componentUrl(componentId, variant));
|
|
2344
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2345
|
+
const detail = await res.json();
|
|
2346
|
+
await applyComponent(detail, variant);
|
|
2347
|
+
} catch (e) {
|
|
2348
|
+
console.error("[pd] Navigate error:", e);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
async function navigateOverview(folderPath) {
|
|
2352
|
+
try {
|
|
2353
|
+
const res = await fetch(overviewUrl(folderPath));
|
|
2354
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2355
|
+
const overview = await res.json();
|
|
2356
|
+
session.current = null;
|
|
2357
|
+
session.currentVariant = null;
|
|
2358
|
+
session.renderedHtmlCache = null;
|
|
2359
|
+
session.rawRenderedHtml = null;
|
|
2360
|
+
session.dataCache = null;
|
|
2361
|
+
session.dataRaw = null;
|
|
2362
|
+
session.stageMode = "single";
|
|
2363
|
+
dom.variantBar.hidden = true;
|
|
2364
|
+
dom.panel.hidden = true;
|
|
2365
|
+
dom.singlePreview.hidden = true;
|
|
2366
|
+
dom.categoryOverview.hidden = false;
|
|
2367
|
+
dom.variantsGalleryEl.hidden = true;
|
|
2368
|
+
dom.stageView.removeAttribute("data-layout");
|
|
2369
|
+
dom.dockEl.dataset.mode = "overview";
|
|
2370
|
+
switchDockTab("console");
|
|
2371
|
+
setActiveTreeItem(folderPath);
|
|
2372
|
+
renderOverview(overview);
|
|
2373
|
+
await renderPanel();
|
|
2374
|
+
} catch (e) {
|
|
2375
|
+
console.error("[pd] Overview error:", e);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
async function navigateVariants(componentId) {
|
|
2379
|
+
try {
|
|
2380
|
+
if (!session.current || session.current.id !== componentId) {
|
|
2381
|
+
const res = await fetch(componentUrl(componentId));
|
|
2382
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2383
|
+
session.current = await res.json();
|
|
2384
|
+
}
|
|
2385
|
+
session.currentVariant = null;
|
|
2386
|
+
session.renderedHtmlCache = null;
|
|
2387
|
+
session.rawRenderedHtml = null;
|
|
2388
|
+
session.dataCache = null;
|
|
2389
|
+
session.dataRaw = null;
|
|
2390
|
+
session.stageMode = "variants";
|
|
2391
|
+
dom.panel.hidden = false;
|
|
2392
|
+
dom.singlePreview.hidden = true;
|
|
2393
|
+
dom.categoryOverview.hidden = true;
|
|
2394
|
+
dom.variantsGalleryEl.hidden = false;
|
|
2395
|
+
dom.stageView.dataset.layout = session.current.layout;
|
|
2396
|
+
populateVariantSelect(session.current.variants);
|
|
2397
|
+
dom.variantSelect.value = "__all__";
|
|
2398
|
+
dom.variantBar.hidden = false;
|
|
2399
|
+
void buildVariantsGallery();
|
|
2400
|
+
delete dom.dockEl.dataset.mode;
|
|
2401
|
+
dom.hbsBody.innerHTML = extractHighlightedPre(session.current.source.hbsHighlighted);
|
|
2402
|
+
dom.htmlBody.innerHTML = "";
|
|
2403
|
+
const hasData = session.current.source.json !== void 0;
|
|
2404
|
+
dom.dockFacetDataBtn.hidden = !hasData;
|
|
2405
|
+
if (hasData) {
|
|
2406
|
+
session.dataCache = session.current.source.jsonHighlighted ?? null;
|
|
2407
|
+
session.dataRaw = session.current.source.json ?? null;
|
|
2408
|
+
dom.dataBody.innerHTML = session.dataCache ? extractHighlightedPre(session.dataCache) : "";
|
|
2409
|
+
} else {
|
|
2410
|
+
session.dataCache = null;
|
|
2411
|
+
session.dataRaw = null;
|
|
2412
|
+
dom.dataBody.innerHTML = "";
|
|
2413
|
+
}
|
|
2414
|
+
const hasSchema = session.current.source.schema !== void 0;
|
|
2415
|
+
dom.dockFacetSchemaBtn.hidden = !hasSchema;
|
|
2416
|
+
dom.schemaBody.innerHTML = hasSchema ? extractHighlightedPre(session.current.source.schemaHighlighted ?? "") : "";
|
|
2417
|
+
switchDockTab("source", "hbs");
|
|
2418
|
+
setActiveTreeItem(componentId);
|
|
2419
|
+
await renderPanel();
|
|
2420
|
+
} catch (e) {
|
|
2421
|
+
console.error("[pd] Variants navigate error:", e);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
function populateVariantSelect(variants) {
|
|
2425
|
+
dom.variantSelect.innerHTML = '<option value="">default</option>';
|
|
2426
|
+
for (const v of variants) {
|
|
2427
|
+
const opt = document.createElement("option");
|
|
2428
|
+
opt.value = v.name;
|
|
2429
|
+
opt.textContent = v.name;
|
|
2430
|
+
dom.variantSelect.appendChild(opt);
|
|
2431
|
+
}
|
|
2432
|
+
if (variants.length > 0) {
|
|
2433
|
+
dom.variantSelect.appendChild(document.createElement("hr"));
|
|
2434
|
+
const allOpt = document.createElement("option");
|
|
2435
|
+
allOpt.value = "__all__";
|
|
2436
|
+
allOpt.textContent = "All variants";
|
|
2437
|
+
dom.variantSelect.appendChild(allOpt);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/app/hmr.ts
|
|
2442
|
+
function connectHmr() {
|
|
2443
|
+
if (runtime.mode !== "live") return;
|
|
2444
|
+
const ws = new WebSocket(`ws://localhost:${runtime.wsPort}`);
|
|
2445
|
+
ws.addEventListener("open", () => {
|
|
2446
|
+
dom.railStatus.dataset.state = "connected";
|
|
2447
|
+
dom.railStatusLabel.textContent = "Connected";
|
|
2448
|
+
renderConsole("HMR connected");
|
|
2449
|
+
});
|
|
2450
|
+
ws.addEventListener("close", () => {
|
|
2451
|
+
dom.railStatus.dataset.state = "disconnected";
|
|
2452
|
+
dom.railStatusLabel.textContent = "disconnected";
|
|
2453
|
+
renderConsole("HMR disconnected \u2014 retrying\u2026");
|
|
2454
|
+
setTimeout(() => connectHmr(), 1e3);
|
|
2455
|
+
});
|
|
2456
|
+
ws.addEventListener("message", async (e) => {
|
|
2457
|
+
let msg;
|
|
2458
|
+
try {
|
|
2459
|
+
msg = JSON.parse(e.data);
|
|
2460
|
+
} catch {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
if ((msg.type === "content-swap" || msg.type === "error") && store.local.panelTab === "audit") {
|
|
2464
|
+
refreshAudit();
|
|
2465
|
+
}
|
|
2466
|
+
if (msg.type === "content-swap") {
|
|
2467
|
+
const componentId = msg.componentId;
|
|
2468
|
+
if (componentId === session.current?.id) {
|
|
2469
|
+
session.renderedHtmlCache = null;
|
|
2470
|
+
session.rawRenderedHtml = null;
|
|
2471
|
+
session.dataCache = null;
|
|
2472
|
+
session.dataRaw = null;
|
|
2473
|
+
dom.htmlBody.innerHTML = "";
|
|
2474
|
+
dom.dataBody.innerHTML = "";
|
|
2475
|
+
if (session.stageMode === "variants") {
|
|
2476
|
+
const tiles = Array.from(
|
|
2477
|
+
dom.variantsGalleryEl.querySelectorAll(".gallery-item iframe")
|
|
2478
|
+
);
|
|
2479
|
+
await Promise.all(tiles.map(async (f) => {
|
|
2480
|
+
const vp = f.dataset.pdVariant || void 0;
|
|
2481
|
+
const doc = await fetch(previewUrl(componentId, vp, store.local.theme || void 0)).then((r) => r.text());
|
|
2482
|
+
f.srcdoc = doc;
|
|
2483
|
+
console.log(
|
|
2484
|
+
"%c[pd] srcdoc reloaded",
|
|
2485
|
+
"color:#7ed321;font-weight:bold",
|
|
2486
|
+
`\u2014 ${componentId} (${vp ?? "default"})`
|
|
2487
|
+
);
|
|
2488
|
+
}));
|
|
2489
|
+
} else {
|
|
2490
|
+
const previewDoc = await fetch(previewUrl(componentId, session.currentVariant ?? void 0, store.local.theme || void 0)).then((r) => r.text());
|
|
2491
|
+
dom.iframe.srcdoc = previewDoc;
|
|
2492
|
+
console.log("%c[pd] srcdoc reloaded", "color:#7ed321;font-weight:bold", `\u2014 ${componentId}`);
|
|
2493
|
+
}
|
|
2494
|
+
{
|
|
2495
|
+
const d = store.local.dock;
|
|
2496
|
+
if (d.dockTab === "source" && d.sourceFacet === "hbs") {
|
|
2497
|
+
dom.hbsBody.innerHTML = extractHighlightedPre(session.current.source.hbsHighlighted);
|
|
2498
|
+
} else if (d.dockTab === "html") {
|
|
2499
|
+
void fetchAndShowHtml();
|
|
2500
|
+
} else if (d.dockTab === "source" && d.sourceFacet === "data") {
|
|
2501
|
+
void fetchAndShowData();
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
void fetch(componentUrl(componentId, session.currentVariant ?? void 0)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
|
|
2505
|
+
session.current = d;
|
|
2506
|
+
session.renderedA11y = [];
|
|
2507
|
+
session.scanPending = store.local.dock.dockTab === "diagnostics";
|
|
2508
|
+
refreshDiagnostics();
|
|
2509
|
+
if (store.local.dock.dockTab === "diagnostics") {
|
|
2510
|
+
dom.iframe.addEventListener("load", () => void runRenderedA11yScan(), { once: true });
|
|
2511
|
+
}
|
|
2512
|
+
{
|
|
2513
|
+
const d2 = store.local.dock;
|
|
2514
|
+
if (d2.dockTab === "source" && d2.sourceFacet === "hbs") {
|
|
2515
|
+
dom.hbsBody.innerHTML = extractHighlightedPre(session.current.source.hbsHighlighted);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (store.local.panelTab === "relationships" || store.local.panelTab === "docs") void renderPanel();
|
|
2519
|
+
}).catch(() => {
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
} else if (msg.type === "hello") {
|
|
2523
|
+
const mismatch = protocolMismatch(msg.protocol);
|
|
2524
|
+
if (mismatch) {
|
|
2525
|
+
dom.railStatus.dataset.state = "disconnected";
|
|
2526
|
+
dom.railStatusLabel.textContent = "Protocol mismatch";
|
|
2527
|
+
renderConsole(mismatch, "error");
|
|
2528
|
+
}
|
|
2529
|
+
} else if (msg.type === "css-link-swap") {
|
|
2530
|
+
const componentId = msg.componentId;
|
|
2531
|
+
const href = msg.href;
|
|
2532
|
+
const orderedHrefs = msg.orderedHrefs;
|
|
2533
|
+
if (orderedHrefs !== void 0) {
|
|
2534
|
+
const previewFrame = document.getElementById("preview-frame");
|
|
2535
|
+
if (previewFrame) reconcilePdLinks(previewFrame.contentDocument, orderedHrefs);
|
|
2536
|
+
document.querySelectorAll("#variants-gallery .gallery-item iframe").forEach((f) => reconcilePdLinks(f.contentDocument, orderedHrefs));
|
|
2537
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2538
|
+
if (f.id === "preview-frame") return;
|
|
2539
|
+
if (f.closest("#variants-gallery")) return;
|
|
2540
|
+
const link = f.contentDocument?.querySelector(`link[data-pd-comp="${componentId}"]`);
|
|
2541
|
+
if (link) link.href = href;
|
|
2542
|
+
});
|
|
2543
|
+
} else {
|
|
2544
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2545
|
+
const link = f.contentDocument?.querySelector(`link[data-pd-comp="${componentId}"]`);
|
|
2546
|
+
if (link) link.href = href;
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
if (store.local.panelTab === "relationships" && session.current) {
|
|
2550
|
+
void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
|
|
2551
|
+
session.current = d;
|
|
2552
|
+
return renderPanel();
|
|
2553
|
+
}).catch(() => {
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
} else if (msg.type === "error") {
|
|
2557
|
+
renderConsole(`${msg.scope === "build" ? "Build" : "Render"} error \u2014 ${msg.message}`, "error");
|
|
2558
|
+
const errId = msg.componentId;
|
|
2559
|
+
if (errId && errId === session.current?.id) {
|
|
2560
|
+
void fetch(componentUrl(errId)).then((r) => r.ok ? r.json() : Promise.reject()).then((d) => {
|
|
2561
|
+
session.current = d;
|
|
2562
|
+
session.renderedA11y = [];
|
|
2563
|
+
session.scanPending = store.local.dock.dockTab === "diagnostics";
|
|
2564
|
+
refreshDiagnostics();
|
|
2565
|
+
if (store.local.dock.dockTab === "diagnostics") void runRenderedA11yScan();
|
|
2566
|
+
}).catch(() => {
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
} else if (msg.type === "tree-changed") {
|
|
2570
|
+
void fetch(PD_ROUTES.tree).then((r) => r.ok ? r.json() : Promise.reject()).then((freshTree) => {
|
|
2571
|
+
session.tree = freshTree;
|
|
2572
|
+
session.componentRegistry.clear();
|
|
2573
|
+
(function populate(nodes) {
|
|
2574
|
+
for (const n of nodes) {
|
|
2575
|
+
if (n.kind === "component") session.componentRegistry.set(n.path, { title: n.title, category: n.path.split("/")[0], visibility: n.visibility, ...n.source ? { source: n.source } : {} });
|
|
2576
|
+
if (n.children) populate(n.children);
|
|
2577
|
+
}
|
|
2578
|
+
})(session.tree.roots);
|
|
2579
|
+
session.navCategoryOrder = new Map(session.tree.roots.map((r, i) => [r.path, i]));
|
|
2580
|
+
const newFolderPaths = extractFolderPaths(session.tree.roots);
|
|
2581
|
+
const reconciledPaths = new Set([...store.local.navOpenPaths].filter((p) => newFolderPaths.has(p)));
|
|
2582
|
+
store.setLocal({ navOpenPaths: reconciledPaths });
|
|
2583
|
+
mountSidebar();
|
|
2584
|
+
syncPackageControl(session.tree);
|
|
2585
|
+
restoreNavOpenState(store.local.navOpenPaths);
|
|
2586
|
+
if (session.current?.id) {
|
|
2587
|
+
if (session.componentRegistry.has(session.current.id)) {
|
|
2588
|
+
setActiveTreeItem(session.current.id);
|
|
2589
|
+
} else {
|
|
2590
|
+
session.current = null;
|
|
2591
|
+
dom.iframe.srcdoc = "";
|
|
2592
|
+
dom.singlePreview.hidden = true;
|
|
2593
|
+
dom.panel.hidden = true;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (session.searchQuery) filterTree(session.searchQuery);
|
|
2597
|
+
if (!dom.categoryOverview.hidden && !session.current) {
|
|
2598
|
+
const folderPath = routeFromPathname(window.location.pathname).slice(1);
|
|
2599
|
+
if (folderPath) void navigateOverview(folderPath);
|
|
2600
|
+
}
|
|
2601
|
+
if (session.current?.id && session.componentRegistry.has(session.current.id) && store.local.panelTab === "relationships") {
|
|
2602
|
+
void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
|
|
2603
|
+
session.current = d;
|
|
2604
|
+
return renderPanel();
|
|
2605
|
+
}).catch(() => {
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2608
|
+
}).catch(() => {
|
|
2609
|
+
});
|
|
2610
|
+
} else if (msg.type === "collision") {
|
|
2611
|
+
renderConsole(
|
|
2612
|
+
`Handle collision \u2014 "${msg.handle}" is held by ${msg.incumbentPath}; ${msg.quarantinedPath} is quarantined until one is renamed`,
|
|
2613
|
+
"error"
|
|
2614
|
+
);
|
|
2615
|
+
} else {
|
|
2616
|
+
const unhandled = msg;
|
|
2617
|
+
void unhandled;
|
|
2618
|
+
}
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// src/app/router.ts
|
|
2623
|
+
function resolvePathKind(path) {
|
|
2624
|
+
function findNode(nodes) {
|
|
2625
|
+
for (const n of nodes) {
|
|
2626
|
+
if (n.path === path) return n;
|
|
2627
|
+
if (n.children) {
|
|
2628
|
+
const found = findNode(n.children);
|
|
2629
|
+
if (found) return found;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
const node = findNode(session.tree.roots);
|
|
2635
|
+
return node ? node.kind : null;
|
|
2636
|
+
}
|
|
2637
|
+
async function routeFromCurrentLocation() {
|
|
2638
|
+
const parsedUrl = parseUrlState(window.location);
|
|
2639
|
+
if (parsedUrl.viewport !== store.url.viewport) {
|
|
2640
|
+
store.setUrl({ viewport: parsedUrl.viewport });
|
|
2641
|
+
applyViewport();
|
|
2642
|
+
}
|
|
2643
|
+
const parsedCvd = parsedUrl.cvd;
|
|
2644
|
+
if (parsedCvd.type !== store.url.cvd.type || parsedCvd.severity !== store.url.cvd.severity) {
|
|
2645
|
+
store.setUrl({ cvd: parsedCvd });
|
|
2646
|
+
applyCvd();
|
|
2647
|
+
}
|
|
2648
|
+
const path = routeFromPathname(window.location.pathname).slice(1);
|
|
2649
|
+
const variant = window.location.hash.slice(1) || void 0;
|
|
2650
|
+
if (!path) return;
|
|
2651
|
+
if (path.endsWith("/variants")) {
|
|
2652
|
+
const componentId = path.slice(0, -"/variants".length);
|
|
2653
|
+
if (resolvePathKind(componentId) === "component") {
|
|
2654
|
+
await navigateVariants(componentId);
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
const kind = resolvePathKind(path);
|
|
2659
|
+
if (kind === "component") {
|
|
2660
|
+
if (session.current && session.current.id === path && session.stageMode !== "variants") {
|
|
2661
|
+
await swapVariant(path, variant ?? null);
|
|
2662
|
+
} else {
|
|
2663
|
+
await navigate(path, variant);
|
|
2664
|
+
}
|
|
2665
|
+
} else {
|
|
2666
|
+
await navigateOverview(path);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// src/stage/preview-sizing.ts
|
|
2671
|
+
function setVpProp(el2, prop, value) {
|
|
2672
|
+
const cur = parseFloat(el2.style.getPropertyValue(prop)) || 0;
|
|
2673
|
+
if (Math.abs(value - cur) > 1) {
|
|
2674
|
+
el2.style.setProperty(prop, `${value}px`);
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// src/stage/preview-messages.ts
|
|
2679
|
+
function listenForPreviewMessages() {
|
|
2680
|
+
window.addEventListener("message", (e) => {
|
|
2681
|
+
const data = e.data;
|
|
2682
|
+
if (!data) return;
|
|
2683
|
+
if (data.type === "pd-height" && typeof data.height === "number") {
|
|
2684
|
+
const height = data.height;
|
|
2685
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2686
|
+
if (f.contentWindow === e.source) {
|
|
2687
|
+
const el2 = f;
|
|
2688
|
+
if (el2.closest('[data-layout="full"]')) return;
|
|
2689
|
+
setVpProp(el2, "--vp-h", height);
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
} else if (data.type === "pd-width" && typeof data.width === "number") {
|
|
2693
|
+
session.lastPdWidth = data.width;
|
|
2694
|
+
if (store.url.viewport === "content") {
|
|
2695
|
+
const width = data.width;
|
|
2696
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2697
|
+
if (f.contentWindow === e.source) {
|
|
2698
|
+
setVpProp(f, "--vp-w", width);
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
requestAnimationFrame(updateReadout);
|
|
2702
|
+
}
|
|
2703
|
+
} else if (data.type === "pd-fp") {
|
|
2704
|
+
if (data.action === "exit") dom.fullpageDialog.close();
|
|
2705
|
+
else if (data.action === "active") showFullpageChrome();
|
|
2706
|
+
}
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// src/app/shell.html
|
|
2711
|
+
var shell_default = '<div class="app">\n <div class="main">\n <nav class="sidebar" aria-label="Components">\n <div class="rail-brand">\n <span class="rail-brand-title">Pattern Dump</span>\n </div>\n <details class="theme-picker">\n <summary class="theme-picker-summary">\n <span class="disclosure-caret" aria-hidden="true"><!--pd-icon:chevron--></span>\n <span class="theme-picker-swatch" aria-hidden="true"></span>\n <span class="theme-picker-current-name"></span>\n </summary>\n <ul class="theme-picker-list" role="radiogroup" aria-label="Component theme"></ul>\n <p class="theme-picker-readout" aria-live="polite"></p>\n </details>\n <details class="a11y-picker">\n <summary class="a11y-picker-summary" aria-label="Accessibility">\n <span class="disclosure-caret" aria-hidden="true"><!--pd-icon:chevron--></span>\n <span class="a11y-icon" aria-hidden="true"><!--pd-icon:a11y--></span>\n <span class="a11y-label">\n <span class="a11y-label-full" aria-hidden="true">Accessibility</span>\n <span class="a11y-label-short" aria-hidden="true">A11y</span>\n </span>\n </summary>\n <div class="a11y-cvd-panel">\n <div role="radiogroup" aria-label="Deficiency type" class="cvd-type-group">\n <button class="cvd-type-btn" role="radio" data-cvd-type="none"\n aria-checked="true" tabindex="0" >None</button>\n <button class="cvd-type-btn" role="radio" data-cvd-type="protanopia"\n aria-checked="false" tabindex="-1">Protanopia</button>\n <button class="cvd-type-btn" role="radio" data-cvd-type="deuteranopia"\n aria-checked="false" tabindex="-1">Deuteranopia</button>\n <button class="cvd-type-btn" role="radio" data-cvd-type="tritanopia"\n aria-checked="false" tabindex="-1">Tritanopia</button>\n <button class="cvd-type-btn" role="radio" data-cvd-type="achromatopsia"\n aria-checked="false" tabindex="-1">Achromatopsia</button>\n </div>\n <label class="cvd-severity-label">\n <span>Severity</span>\n <input type="range" class="cvd-severity-slider"\n min="0" max="1" step="0.01" value="1"\n aria-label="Severity" disabled>\n </label>\n </div>\n </details>\n <div class="rail-toggle-cluster" role="group" aria-label="Layout controls">\n <button class="rail-toggle panel-toggle-btn"\n aria-label="Docs panel"\n aria-expanded="false" aria-controls="app-panel"\n data-label-open="Hide docs panel"\n data-label-closed="Show docs panel">\n <!--pd-icon:panel-->\n </button>\n <button class="rail-toggle dock-toggle-btn"\n aria-label="Source dock"\n aria-expanded="true" aria-controls="app-dock"\n data-label-open="Hide source dock"\n data-label-closed="Show source dock">\n <!--pd-icon:dock-fill-->\n </button>\n <span class="rail-toggle-label" aria-hidden="true"></span>\n </div>\n <div class="rail-vp-cluster" role="group" aria-label="Viewport">\n <output class="vp-readout">\u2014 \xD7 \u2014</output>\n <div role="radiogroup" aria-label="Viewport size">\n <button class="rail-vp-btn" data-vp="fit"\n role="radio" aria-checked="true" tabindex="0"\n aria-label="Fit"><!--pd-icon:vp-fit-fill--></button>\n <button class="rail-vp-btn" data-vp="content"\n role="radio" aria-checked="false" tabindex="-1"\n aria-label="Fit content"><!--pd-icon:vp-content--></button>\n <button class="rail-vp-btn" data-vp="mobile"\n role="radio" aria-checked="false" tabindex="-1"\n aria-label="Mobile"><!--pd-icon:vp-mobile--></button>\n <button class="rail-vp-btn" data-vp="tablet"\n role="radio" aria-checked="false" tabindex="-1"\n aria-label="Tablet"><!--pd-icon:vp-tablet--></button>\n <button class="rail-vp-btn" data-vp="desktop"\n role="radio" aria-checked="false" tabindex="-1"\n aria-label="Desktop"><!--pd-icon:vp-desktop--></button>\n </div>\n <button class="rail-vp-btn vp-disco-btn"\n aria-pressed="false"\n aria-label="Disco mode"\n data-label-off="Start disco"\n data-label-on="Stop disco"><!--pd-icon:vp-disco--></button>\n <button class="rail-vp-btn fullpage-btn"\n aria-haspopup="dialog"\n aria-label="Full-page preview"><!--pd-icon:vp-fullpage--></button>\n <span class="vp-mode-label" aria-hidden="true"></span>\n </div>\n <div class="sidebar-search">\n <input type="search" placeholder="Search components\u2026" aria-label="Search components">\n </div>\n <div class="sidebar-controls">\n <label class="internals-label"><input type="checkbox" class="show-internals-check"> Show private</label>\n <label class="internals-label hide-packages-label" hidden><input type="checkbox" class="hide-packages-check"> Hide packages</label>\n </div>\n <ul class="sidebar-tree" role="list"></ul>\n <div class="rail-status" role="status" aria-label="Server connection status"\n data-state="connected">\n <span class="rail-status-icon" aria-hidden="true"><!--pd-icon:warning--></span>\n <span class="rail-status-label">Connected</span>\n </div>\n <div data-resize="sidebar-w" aria-hidden="true"></div>\n </nav>\n <main class="stage">\n <div class="variant-bar" hidden>\n <label>Variant: <select class="variant-select"><option value="">default</option></select></label>\n </div>\n <div class="stage-view">\n <figure class="preview single-preview" id="single-preview" hidden>\n <iframe id="preview-frame" title="Component preview"></iframe>\n\n <figcaption class="preview-caption"></figcaption>\n </figure>\n <div class="overview" id="category-overview" tabindex="0" hidden></div>\n <div class="variants-gallery" id="variants-gallery" hidden></div>\n </div>\n </main>\n <section class="dock" id="app-dock" aria-label="Console">\n <div data-resize="dock-h" aria-hidden="true"></div>\n <div class="dock-header">\n <div class="dock-tabs" role="tablist" aria-label="Source and console">\n <button role="tab" id="dock-tab-source" class="dock-tab" data-tab="source" aria-selected="true" aria-controls="dock-panel-hbs"><span class="tab-content"><span class="tab-label">Source</span></span></button>\n <button role="tab" id="dock-tab-html" class="dock-tab" data-tab="html" aria-selected="false" aria-controls="dock-panel-html"><span class="tab-content"><span class="tab-label">HTML</span></span></button>\n <button role="tab" id="dock-tab-diagnostics" class="dock-tab" data-tab="diagnostics" aria-selected="false" aria-controls="dock-panel-diagnostics"><span class="tab-content"><span class="tab-label">Diagnostics</span><span class="tab-marker" aria-hidden="true"><span class="tab-marker-count"></span></span></span></button>\n <button role="tab" id="dock-tab-console" class="dock-tab" data-tab="console" aria-selected="false" aria-controls="dock-panel-console"><span class="tab-content"><span class="tab-label">Console</span></span></button>\n </div>\n </div>\n <div class="dock-facets">\n <div class="source-facets" role="tablist" aria-label="Source format">\n <button role="tab" id="dock-facet-hbs" class="dock-facet" data-facet="hbs" aria-selected="true" aria-controls="dock-panel-hbs"><span class="tab-content"><span class="tab-label">Handlebars</span></span></button>\n <button role="tab" id="dock-facet-data" class="dock-facet" data-facet="data" aria-selected="false" aria-controls="dock-panel-data" hidden><span class="tab-content"><span class="tab-label">Data</span></span></button>\n <button role="tab" id="dock-facet-schema" class="dock-facet" data-facet="schema" aria-selected="false" aria-controls="dock-panel-schema" hidden><span class="tab-content"><span class="tab-label">Schema</span></span></button>\n </div>\n <button class="dock-copy-btn" aria-label="Copy Handlebars">Copy Handlebars</button>\n </div>\n <div id="dock-panel-hbs" class="dock-panel" role="tabpanel" aria-labelledby="dock-facet-hbs">\n <div class="source-body"></div>\n </div>\n <div id="dock-panel-data" class="dock-panel" role="tabpanel" aria-labelledby="dock-facet-data" hidden>\n <div class="source-body"></div>\n </div>\n <div id="dock-panel-schema" class="dock-panel" role="tabpanel" aria-labelledby="dock-facet-schema" hidden>\n <div class="source-body"></div>\n </div>\n <div id="dock-panel-html" class="dock-panel" role="tabpanel" aria-labelledby="dock-tab-html" hidden>\n <div class="source-body"></div>\n </div>\n <div id="dock-panel-diagnostics" class="dock-panel dock-panel-diagnostics" role="tabpanel" aria-labelledby="dock-tab-diagnostics" hidden>\n <div class="pd-a11y-controls">\n <button class="pd-run-rendered" type="button">Run rendered checks</button>\n </div>\n <div class="diagnostics" role="region" aria-label="Diagnostics"></div>\n </div>\n <div id="dock-panel-console" class="dock-panel" role="tabpanel" aria-labelledby="dock-tab-console" hidden>\n <div class="console" role="log" aria-live="polite"></div>\n </div>\n </section>\n <aside class="panel" id="app-panel" aria-label="Inspector">\n <div data-resize="panel-w" aria-hidden="true"></div>\n <div class="panel-tabs" role="tablist" aria-label="Panel tabs">\n <button role="tab" data-tab="docs" aria-selected="true"><span class="tab-content"><span class="tab-label">Docs</span></span></button>\n <button role="tab" data-tab="relationships"><span class="tab-content"><span class="tab-label">Relationships</span></span></button>\n <button role="tab" data-tab="audit"><span class="tab-content"><span class="tab-label">Audit</span></span></button>\n </div>\n <div class="panel-content" role="tabpanel"></div>\n </aside>\n </div>\n <dialog id="fullpage-dialog" class="fullpage" aria-label="Full-page preview">\n <button class="fullpage-close" aria-label="Close full-page preview"><!--pd-icon:x--></button>\n <iframe id="fullpage-frame" title="Full-page preview"></iframe>\n </dialog>\n <svg aria-hidden="true" focusable="false"\n style="position:absolute;width:0;height:0;overflow:hidden">\n <defs>\n <filter id="cvd-filter" color-interpolation-filters="linearRGB">\n <feColorMatrix id="cvd-matrix-vals" type="matrix"\n values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>\n </filter>\n </defs>\n </svg>\n</div>\n';
|
|
2712
|
+
|
|
2713
|
+
// src/app/shell.ts
|
|
2714
|
+
function renderShellHtml() {
|
|
2715
|
+
const expanded = shell_default.replace(
|
|
2716
|
+
/<!--pd-icon:([a-z0-9-]+)-->/g,
|
|
2717
|
+
(_match, name) => svgIcon(name)
|
|
2718
|
+
);
|
|
2719
|
+
return "\n" + expanded.replace(/\n+$/, "");
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// src/app/wiring.ts
|
|
2723
|
+
function applyRightPanelDom(open) {
|
|
2724
|
+
if (open) dom.mainEl.dataset.panelOpen = "";
|
|
2725
|
+
else delete dom.mainEl.dataset.panelOpen;
|
|
2726
|
+
dom.panelToggleBtn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
2727
|
+
dom.panelToggleBtn.innerHTML = svgIcon(open ? "panel-fill" : "panel");
|
|
2728
|
+
}
|
|
2729
|
+
function applyConsoleDockDom(open) {
|
|
2730
|
+
dom.dockEl.hidden = !open;
|
|
2731
|
+
if (open) delete dom.mainEl.dataset.dockHidden;
|
|
2732
|
+
else dom.mainEl.dataset.dockHidden = "";
|
|
2733
|
+
dom.dockToggleBtn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
2734
|
+
dom.dockToggleBtn.innerHTML = svgIcon(open ? "dock-fill" : "dock");
|
|
2735
|
+
}
|
|
2736
|
+
function mount() {
|
|
2737
|
+
const app = document.getElementById("app");
|
|
2738
|
+
if (!app) return;
|
|
2739
|
+
app.innerHTML = renderShellHtml();
|
|
2740
|
+
initDom(app);
|
|
2741
|
+
listenForPreviewMessages();
|
|
2742
|
+
dom.searchInput.addEventListener("input", () => {
|
|
2743
|
+
session.searchQuery = dom.searchInput.value;
|
|
2744
|
+
filterTree(session.searchQuery);
|
|
2745
|
+
});
|
|
2746
|
+
dom.showInternalsCheck.addEventListener("change", () => {
|
|
2747
|
+
const checked = dom.showInternalsCheck.checked;
|
|
2748
|
+
store.setLocal({ navFilters: { ...store.local.navFilters, showPrivate: checked } });
|
|
2749
|
+
dom.sidebarTree.classList.toggle("show-internals", checked);
|
|
2750
|
+
});
|
|
2751
|
+
dom.hidePackagesCheck.addEventListener("change", () => {
|
|
2752
|
+
const hide = dom.hidePackagesCheck.checked;
|
|
2753
|
+
store.setLocal({ navFilters: { ...store.local.navFilters, hidePackages: hide } });
|
|
2754
|
+
applyPackageFilter(hide);
|
|
2755
|
+
});
|
|
2756
|
+
dom.variantSelect.addEventListener("change", async () => {
|
|
2757
|
+
if (!session.current) return;
|
|
2758
|
+
const value = dom.variantSelect.value;
|
|
2759
|
+
if (value === "__all__") {
|
|
2760
|
+
history.pushState({}, "", routeHref(`/${session.current.id}/variants`) + currentVpParam());
|
|
2761
|
+
void navigateVariants(session.current.id);
|
|
2762
|
+
return;
|
|
2763
|
+
}
|
|
2764
|
+
if (session.stageMode === "variants") {
|
|
2765
|
+
session.stageMode = "single";
|
|
2766
|
+
dom.singlePreview.hidden = false;
|
|
2767
|
+
dom.variantsGalleryEl.hidden = true;
|
|
2768
|
+
}
|
|
2769
|
+
const variantParam = value || void 0;
|
|
2770
|
+
const hash = variantParam ? `#${encodeURIComponent(variantParam)}` : "";
|
|
2771
|
+
history.pushState({}, "", routeHref(`/${session.current.id}`) + `${currentVpParam()}${hash}`);
|
|
2772
|
+
await swapVariant(session.current.id, variantParam ?? null);
|
|
2773
|
+
});
|
|
2774
|
+
dom.iframe.addEventListener("load", () => {
|
|
2775
|
+
const theme = store.local.theme;
|
|
2776
|
+
if (theme) applyThemeToIframe(dom.iframe, theme);
|
|
2777
|
+
});
|
|
2778
|
+
dom.sidebarTree.addEventListener("click", (e) => {
|
|
2779
|
+
const target = e.target;
|
|
2780
|
+
const showAll = target.closest("a.tree-show-all");
|
|
2781
|
+
if (showAll) {
|
|
2782
|
+
e.preventDefault();
|
|
2783
|
+
const href = showAll.getAttribute("href") ?? "/";
|
|
2784
|
+
history.pushState({}, "", href + currentVpParam());
|
|
2785
|
+
void routeFromCurrentLocation();
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
const link = target.closest("a.tree-link");
|
|
2789
|
+
if (link) {
|
|
2790
|
+
e.preventDefault();
|
|
2791
|
+
const href = link.getAttribute("href") ?? "/";
|
|
2792
|
+
history.pushState({}, "", href + currentVpParam());
|
|
2793
|
+
void routeFromCurrentLocation();
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
app.querySelector(".dock-tabs")?.addEventListener("click", (e) => {
|
|
2797
|
+
const btn = e.target.closest("[role='tab'][data-tab]");
|
|
2798
|
+
if (btn) {
|
|
2799
|
+
const tab = btn.dataset.tab;
|
|
2800
|
+
switchDockTab(tab);
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
app.querySelector(".source-facets")?.addEventListener("click", (e) => {
|
|
2804
|
+
const btn = e.target.closest("[role='tab'][data-facet]");
|
|
2805
|
+
if (btn) {
|
|
2806
|
+
const facet = btn.dataset.facet;
|
|
2807
|
+
switchSourceFacet(facet);
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
dom.dockCopyBtn.addEventListener("click", () => {
|
|
2811
|
+
void copyDockSource();
|
|
2812
|
+
});
|
|
2813
|
+
app.querySelector(".pd-run-rendered")?.addEventListener("click", () => {
|
|
2814
|
+
session.renderedA11y = [];
|
|
2815
|
+
session.scanPending = true;
|
|
2816
|
+
refreshDiagnostics();
|
|
2817
|
+
void runRenderedA11yScan();
|
|
2818
|
+
});
|
|
2819
|
+
const panelTabsEl = app.querySelector(".panel-tabs");
|
|
2820
|
+
panelTabsEl.addEventListener("click", (e) => {
|
|
2821
|
+
const btn = e.target.closest("button[data-tab]");
|
|
2822
|
+
if (!btn) return;
|
|
2823
|
+
const tab = btn.dataset.tab;
|
|
2824
|
+
if (tab === "audit") clearAuditError();
|
|
2825
|
+
store.setLocal({ panelTab: tab });
|
|
2826
|
+
panelTabsEl.querySelectorAll("button[role='tab']").forEach((b) => {
|
|
2827
|
+
b.setAttribute("aria-selected", String(b.dataset.tab === tab));
|
|
2828
|
+
});
|
|
2829
|
+
void renderPanel();
|
|
2830
|
+
});
|
|
2831
|
+
dom.panelContent.addEventListener("click", async (e) => {
|
|
2832
|
+
const btn = e.target.closest(".copy-btn");
|
|
2833
|
+
if (!btn) return;
|
|
2834
|
+
const raw = btn.closest("[data-raw]")?.getAttribute("data-raw") ?? "";
|
|
2835
|
+
try {
|
|
2836
|
+
await navigator.clipboard.writeText(raw);
|
|
2837
|
+
btn.textContent = "Copied!";
|
|
2838
|
+
setTimeout(() => {
|
|
2839
|
+
btn.textContent = "Copy";
|
|
2840
|
+
}, 1500);
|
|
2841
|
+
} catch {
|
|
2842
|
+
btn.textContent = "Failed";
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
dom.panelContent.addEventListener("click", (e) => {
|
|
2846
|
+
const a = e.target.closest("a.rel-link");
|
|
2847
|
+
if (!a) return;
|
|
2848
|
+
e.preventDefault();
|
|
2849
|
+
const href = a.getAttribute("href") ?? "/";
|
|
2850
|
+
history.pushState({}, "", href + currentVpParam());
|
|
2851
|
+
void routeFromCurrentLocation();
|
|
2852
|
+
});
|
|
2853
|
+
dom.panelContent.addEventListener("click", (e) => {
|
|
2854
|
+
const a = e.target.closest("a.audit-link");
|
|
2855
|
+
if (!a) return;
|
|
2856
|
+
e.preventDefault();
|
|
2857
|
+
const href = a.getAttribute("href") ?? "/";
|
|
2858
|
+
history.pushState({}, "", href + currentVpParam());
|
|
2859
|
+
void routeFromCurrentLocation();
|
|
2860
|
+
});
|
|
2861
|
+
dom.panelContent.addEventListener("click", (e) => {
|
|
2862
|
+
const target = e.target;
|
|
2863
|
+
if (target.closest("a.pattern-link--unresolved")) {
|
|
2864
|
+
e.preventDefault();
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
const a = target.closest("a.pattern-link");
|
|
2868
|
+
if (!a) return;
|
|
2869
|
+
e.preventDefault();
|
|
2870
|
+
const compId = a.dataset["patternId"];
|
|
2871
|
+
const variant = a.dataset["variant"];
|
|
2872
|
+
if (!compId) return;
|
|
2873
|
+
const url = routeHref(`/${compId}`) + `${currentVpParam()}${variant ? "#" + variant : ""}`;
|
|
2874
|
+
history.pushState({}, "", url);
|
|
2875
|
+
void routeFromCurrentLocation();
|
|
2876
|
+
});
|
|
2877
|
+
dom.panelToggleBtn.addEventListener("click", () => {
|
|
2878
|
+
const vis = store.local.dockVisibility;
|
|
2879
|
+
const open = !vis.rightPanelOpen;
|
|
2880
|
+
store.setLocal({ dockVisibility: { ...vis, rightPanelOpen: open } });
|
|
2881
|
+
applyRightPanelDom(open);
|
|
2882
|
+
});
|
|
2883
|
+
dom.dockToggleBtn.addEventListener("click", () => {
|
|
2884
|
+
const vis = store.local.dockVisibility;
|
|
2885
|
+
const open = !vis.consoleDockOpen;
|
|
2886
|
+
store.setLocal({ dockVisibility: { ...vis, consoleDockOpen: open } });
|
|
2887
|
+
applyConsoleDockDom(open);
|
|
2888
|
+
});
|
|
2889
|
+
const showToggleLabel = (e) => {
|
|
2890
|
+
const btn = e.target.closest(".rail-toggle");
|
|
2891
|
+
if (!btn) return;
|
|
2892
|
+
const open = btn.getAttribute("aria-expanded") === "true";
|
|
2893
|
+
dom.railToggleLabelEl.textContent = open ? btn.dataset.labelOpen ?? "" : btn.dataset.labelClosed ?? "";
|
|
2894
|
+
};
|
|
2895
|
+
const clearToggleLabel = (e) => {
|
|
2896
|
+
if (!e.relatedTarget || !dom.railToggleClusterEl.contains(e.relatedTarget)) {
|
|
2897
|
+
dom.railToggleLabelEl.textContent = "";
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
dom.railToggleClusterEl.addEventListener("pointerenter", showToggleLabel, true);
|
|
2901
|
+
dom.railToggleClusterEl.addEventListener("pointerleave", () => {
|
|
2902
|
+
dom.railToggleLabelEl.textContent = "";
|
|
2903
|
+
});
|
|
2904
|
+
dom.railToggleClusterEl.addEventListener("focusin", showToggleLabel);
|
|
2905
|
+
dom.railToggleClusterEl.addEventListener("focusout", clearToggleLabel);
|
|
2906
|
+
window.addEventListener("popstate", () => {
|
|
2907
|
+
void routeFromCurrentLocation();
|
|
2908
|
+
});
|
|
2909
|
+
window.addEventListener("hashchange", () => {
|
|
2910
|
+
if (!session.current) return;
|
|
2911
|
+
const newVariant = window.location.hash.slice(1) || null;
|
|
2912
|
+
void swapVariant(session.current.id, newVariant);
|
|
2913
|
+
});
|
|
2914
|
+
app.querySelector(".stage")?.addEventListener("click", (e) => {
|
|
2915
|
+
const link = e.target.closest("a.gallery-label-link");
|
|
2916
|
+
if (!link) return;
|
|
2917
|
+
e.preventDefault();
|
|
2918
|
+
const href = link.getAttribute("href") ?? "/";
|
|
2919
|
+
const url = new URL(href, window.location.href);
|
|
2920
|
+
history.pushState({}, "", url.pathname + (url.search || currentVpParam()) + url.hash);
|
|
2921
|
+
void routeFromCurrentLocation();
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
// src/app/bootstrap.ts
|
|
2926
|
+
async function init() {
|
|
2927
|
+
installStaticFetch();
|
|
2928
|
+
mount();
|
|
2929
|
+
if (!auditAvailable) document.querySelector(".panel-tabs button[data-tab='audit']")?.remove();
|
|
2930
|
+
dom.mainEl.dataset.noTransition = "";
|
|
2931
|
+
requestAnimationFrame(() => {
|
|
2932
|
+
requestAnimationFrame(() => {
|
|
2933
|
+
delete dom.mainEl.dataset.noTransition;
|
|
2934
|
+
});
|
|
2935
|
+
});
|
|
2936
|
+
const isFirstLoad = localStorage.getItem(STORAGE_KEY) === null;
|
|
2937
|
+
if (isFirstLoad && window.innerWidth > 900) {
|
|
2938
|
+
dom.mainEl.dataset.panelOpen = "";
|
|
2939
|
+
}
|
|
2940
|
+
if (!isFirstLoad) {
|
|
2941
|
+
try {
|
|
2942
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
2943
|
+
if (raw) {
|
|
2944
|
+
const stored = JSON.parse(raw);
|
|
2945
|
+
if (stored.dockVisibility?.rightPanelOpen === true) applyRightPanelDom(true);
|
|
2946
|
+
if (stored.dockVisibility?.consoleDockOpen === false) applyConsoleDockDom(false);
|
|
2947
|
+
}
|
|
2948
|
+
} catch {
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
const [treeRes, themeRes, configRes, tiersRes] = await Promise.all([
|
|
2952
|
+
fetch(PD_ROUTES.tree),
|
|
2953
|
+
fetch(PD_ROUTES.themes),
|
|
2954
|
+
fetch(PD_ROUTES.config),
|
|
2955
|
+
fetch(PD_ROUTES.ruleTiers).catch(() => null)
|
|
2956
|
+
]);
|
|
2957
|
+
session.tree = await treeRes.json();
|
|
2958
|
+
session.themes = await themeRes.json();
|
|
2959
|
+
const shellConfig = await configRes.json();
|
|
2960
|
+
session.themeSwitch = shellConfig.themeSwitch;
|
|
2961
|
+
session.serverDefaultTheme = shellConfig.defaultTheme;
|
|
2962
|
+
renderBrand(shellConfig.shell?.brand);
|
|
2963
|
+
if (tiersRes) {
|
|
2964
|
+
const tiers = await tiersRes.json();
|
|
2965
|
+
session.needsRenderingRules = tiers["needsRendering"] ?? [];
|
|
2966
|
+
}
|
|
2967
|
+
function walkTree(nodes) {
|
|
2968
|
+
for (const node of nodes) {
|
|
2969
|
+
if (node.kind === "component") {
|
|
2970
|
+
session.componentRegistry.set(node.path, {
|
|
2971
|
+
title: node.title,
|
|
2972
|
+
category: node.path.split("/")[0],
|
|
2973
|
+
visibility: node.visibility,
|
|
2974
|
+
...node.source ? { source: node.source } : {}
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
if (node.children) walkTree(node.children);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
walkTree(session.tree.roots);
|
|
2981
|
+
session.navCategoryOrder = new Map(session.tree.roots.map((r, i) => [r.path, i]));
|
|
2982
|
+
const knownThemes = new Set(session.themes.map((t) => t.name));
|
|
2983
|
+
const stripThemeParam = store.hydrate({ availableVariants: null, knownFolderPaths: extractFolderPaths(session.tree.roots), knownThemes });
|
|
2984
|
+
if (!auditAvailable && store.local.panelTab === "audit") store.setLocal({ panelTab: "docs" });
|
|
2985
|
+
if (stripThemeParam) {
|
|
2986
|
+
const stripped = new URL(window.location.href);
|
|
2987
|
+
stripped.searchParams.delete("theme");
|
|
2988
|
+
history.replaceState(null, "", stripped.pathname + (stripped.search || "") + stripped.hash);
|
|
2989
|
+
}
|
|
2990
|
+
populateThemePicker(session.themes, () => {
|
|
2991
|
+
if (store.local.dock.dockTab === "diagnostics") {
|
|
2992
|
+
session.renderedA11y = [];
|
|
2993
|
+
session.scanPending = true;
|
|
2994
|
+
refreshDiagnostics();
|
|
2995
|
+
requestAnimationFrame(() => void runRenderedA11yScan());
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
if (isFirstLoad && window.innerWidth > 900) {
|
|
2999
|
+
store.setLocal({ dockVisibility: { ...store.local.dockVisibility, rightPanelOpen: true } });
|
|
3000
|
+
}
|
|
3001
|
+
dom.mainEl.dataset.noTransition = "";
|
|
3002
|
+
applyRightPanelDom(store.local.dockVisibility.rightPanelOpen);
|
|
3003
|
+
applyConsoleDockDom(store.local.dockVisibility.consoleDockOpen);
|
|
3004
|
+
requestAnimationFrame(() => {
|
|
3005
|
+
delete dom.mainEl.dataset.noTransition;
|
|
3006
|
+
});
|
|
3007
|
+
if (store.local.navFilters.showPrivate) {
|
|
3008
|
+
dom.showInternalsCheck.checked = true;
|
|
3009
|
+
dom.sidebarTree.classList.add("show-internals");
|
|
3010
|
+
}
|
|
3011
|
+
syncPackageControl(session.tree);
|
|
3012
|
+
applyPackageFilter(store.local.navFilters.hidePackages);
|
|
3013
|
+
const storedDockForInit = store.local.dock;
|
|
3014
|
+
mountSidebar();
|
|
3015
|
+
restoreNavOpenState(store.local.navOpenPaths);
|
|
3016
|
+
initDprTracking();
|
|
3017
|
+
initSplitters();
|
|
3018
|
+
initViewport(() => {
|
|
3019
|
+
void openFullpage();
|
|
3020
|
+
});
|
|
3021
|
+
if (store.url.cvd.type !== "none" && store.url.cvd.severity > 0) {
|
|
3022
|
+
dom.a11yDetails.open = true;
|
|
3023
|
+
}
|
|
3024
|
+
applyCvd();
|
|
3025
|
+
initA11yControl();
|
|
3026
|
+
initFullpage();
|
|
3027
|
+
await routeFromCurrentLocation();
|
|
3028
|
+
const storedPanelTab = store.local.panelTab;
|
|
3029
|
+
document.querySelectorAll(".panel-tabs button[role='tab']").forEach((b) => {
|
|
3030
|
+
b.setAttribute("aria-selected", String(b.dataset.tab === storedPanelTab));
|
|
3031
|
+
});
|
|
3032
|
+
if (storedPanelTab !== "docs") void renderPanel();
|
|
3033
|
+
if (store.url.viewport === "fullscreen" && session.current !== null) {
|
|
3034
|
+
void openFullpage();
|
|
3035
|
+
}
|
|
3036
|
+
if (session.current !== null && (storedDockForInit.dockTab !== "source" || storedDockForInit.sourceFacet !== "hbs")) {
|
|
3037
|
+
if (storedDockForInit.dockTab !== "source") {
|
|
3038
|
+
switchDockTab(storedDockForInit.dockTab);
|
|
3039
|
+
} else {
|
|
3040
|
+
switchDockTab("source", storedDockForInit.sourceFacet);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
if (runtime.mode === "static") {
|
|
3044
|
+
const built = new Date(runtime.builtAt).toLocaleString();
|
|
3045
|
+
dom.railStatus.dataset.state = "static";
|
|
3046
|
+
dom.railStatusLabel.textContent = `Static build \xB7 ${built}`;
|
|
3047
|
+
renderConsole(`Static build of ${runtime.builtAt} (pattern-dump ${runtime.pdVersion}) \u2014 read-only snapshot, no live reload.`);
|
|
3048
|
+
} else {
|
|
3049
|
+
connectHmr();
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// src/main.ts
|
|
3054
|
+
void init();
|