pattern-dump 0.1.0-alpha.0 → 0.1.0-alpha.1
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/CHANGELOG.md +55 -0
- package/README.md +14 -1
- package/dist/chunks/chunk-3WZHFYHZ.js +160 -0
- package/dist/dev-ui/main.css +38 -7
- package/dist/dev-ui/main.js +283 -74
- package/dist/index.js +3 -4
- package/dist/pd.js +588 -381
- package/dist/types/config/index.d.ts +19 -12
- package/dist/types/config/validate.d.ts +10 -0
- package/dist/types/contracts/dev-protocol.d.ts +26 -13
- package/package.json +3 -2
package/dist/dev-ui/main.js
CHANGED
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
// ../../packages/contracts/src/dev-protocol.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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".`;
|
|
2
|
+
var AUDIT_SCOPE = {
|
|
3
|
+
covered: [
|
|
4
|
+
{ name: "HTML content model", detail: "invalid nesting and content that isn't permitted where it appears (html-validate)." },
|
|
5
|
+
{ name: "Structural accessibility", detail: "labels, accessible names, ARIA attributes, roles and image alternatives (the axe-core rules that don't need layout)." },
|
|
6
|
+
{ name: "Schema", detail: "missing required fields and values of the wrong type, checked against each component's `<name>.schema.json`." },
|
|
7
|
+
{ name: "Render outcome", detail: "whether each component rendered, rendered empty or failed. It is reported with the component, not counted as a defect." }
|
|
8
|
+
],
|
|
9
|
+
notCovered: [
|
|
10
|
+
{ name: "Checks that need a rendered page", detail: "colour contrast, focusable content hidden with aria-hidden, and duplicate ids referenced by ARIA. They need real browser layout; the Console's Diagnostics tab runs them for the component on screen." },
|
|
11
|
+
{ name: "Interaction", detail: "focus traps, keyboard flow and Escape handling. The audit looks at a single static render." },
|
|
12
|
+
{ name: "Structure", detail: "unused components, shared bases and near-duplicate copies. The Relationships tab and `pd export` cover those." },
|
|
13
|
+
{ name: "Components that need data", detail: "a component that renders empty without data can't show the problems it would have with data. Its findings carry the render outcome." }
|
|
14
|
+
],
|
|
15
|
+
note: "No findings means no static, machine-detectable defects, not that a component is fully accessible or correct."
|
|
16
|
+
};
|
|
24
17
|
function buildAuditRollup(graph, opts = {}) {
|
|
25
18
|
const total = graph.components.length;
|
|
26
19
|
const affected = new Set(graph.occurrences.map((o) => o.component));
|
|
@@ -62,7 +55,7 @@ function buildAuditRollup(graph, opts = {}) {
|
|
|
62
55
|
renderOutcomes: { ok: outcomes["ok"] ?? 0, blank: outcomes["blank"] ?? 0, error: outcomes["error"] ?? 0 },
|
|
63
56
|
rulesByBlast,
|
|
64
57
|
perComponent,
|
|
65
|
-
scope:
|
|
58
|
+
scope: AUDIT_SCOPE
|
|
66
59
|
};
|
|
67
60
|
}
|
|
68
61
|
function previewUrl(id, variant, theme) {
|
|
@@ -237,8 +230,7 @@ var session = {
|
|
|
237
230
|
dataCache: null,
|
|
238
231
|
dataRaw: null,
|
|
239
232
|
renderedA11y: [],
|
|
240
|
-
scanPending: false
|
|
241
|
-
lastPdWidth: null
|
|
233
|
+
scanPending: false
|
|
242
234
|
};
|
|
243
235
|
|
|
244
236
|
// src/core/runtime.ts
|
|
@@ -796,7 +788,7 @@ async function runRenderedA11yScan() {
|
|
|
796
788
|
}
|
|
797
789
|
const axeWindow = dom.iframe.contentWindow;
|
|
798
790
|
if (!axeWindow.axe) await injectAxe();
|
|
799
|
-
const root = dom.iframe.contentDocument.
|
|
791
|
+
const root = dom.iframe.contentDocument.body;
|
|
800
792
|
const results = await axeWindow.axe.run(root, { runOnly: session.needsRenderingRules });
|
|
801
793
|
if (gen !== scanGen) return;
|
|
802
794
|
session.renderedA11y = mapAxeResults(results);
|
|
@@ -983,6 +975,12 @@ var el = (tag, cls, text) => {
|
|
|
983
975
|
if (text !== void 0) n.textContent = text;
|
|
984
976
|
return n;
|
|
985
977
|
};
|
|
978
|
+
function appendWithCode(parent, text) {
|
|
979
|
+
text.split("`").forEach((part, i) => {
|
|
980
|
+
if (part === "") return;
|
|
981
|
+
parent.appendChild(i % 2 === 1 ? el("code", "audit-scope-code", part) : document.createTextNode(part));
|
|
982
|
+
});
|
|
983
|
+
}
|
|
986
984
|
function tagBadge(tag) {
|
|
987
985
|
const s = el("span", "audit-tag", `[${tag}]`);
|
|
988
986
|
s.dataset.tag = tag;
|
|
@@ -1060,8 +1058,22 @@ function renderAuditView(rollup) {
|
|
|
1060
1058
|
}
|
|
1061
1059
|
const scope = el("details", "audit-scope");
|
|
1062
1060
|
scope.open = true;
|
|
1063
|
-
scope.appendChild(el("summary", "audit-scope-summary", "Scope
|
|
1064
|
-
|
|
1061
|
+
scope.appendChild(el("summary", "audit-scope-summary", "Scope: what this count does and does not cover"));
|
|
1062
|
+
const scopeList = (label, items, kind) => {
|
|
1063
|
+
scope.appendChild(el("p", "audit-scope-label", label));
|
|
1064
|
+
const list = el("ul", "audit-scope-list");
|
|
1065
|
+
list.dataset.scope = kind;
|
|
1066
|
+
for (const item of items) {
|
|
1067
|
+
const li = el("li", "audit-scope-item");
|
|
1068
|
+
li.appendChild(el("strong", "audit-scope-name", `${item.name}: `));
|
|
1069
|
+
appendWithCode(li, item.detail);
|
|
1070
|
+
list.appendChild(li);
|
|
1071
|
+
}
|
|
1072
|
+
scope.appendChild(list);
|
|
1073
|
+
};
|
|
1074
|
+
scopeList("Covered: static checks on a single render, without browser layout.", rollup.scope.covered, "covered");
|
|
1075
|
+
scopeList("Not covered:", rollup.scope.notCovered, "not-covered");
|
|
1076
|
+
scope.appendChild(el("p", "audit-scope-note", rollup.scope.note));
|
|
1065
1077
|
section.appendChild(scope);
|
|
1066
1078
|
return section;
|
|
1067
1079
|
}
|
|
@@ -1708,6 +1720,133 @@ function withForwarder(doc) {
|
|
|
1708
1720
|
return doc.includes("</body>") ? doc.replace("</body>", FORWARDER + "</body>") : doc + FORWARDER;
|
|
1709
1721
|
}
|
|
1710
1722
|
|
|
1723
|
+
// src/stage/preview-sizing.ts
|
|
1724
|
+
function setVpProp(el2, prop, value) {
|
|
1725
|
+
const cur = parseFloat(el2.style.getPropertyValue(prop)) || 0;
|
|
1726
|
+
if (Math.abs(value - cur) > 1) {
|
|
1727
|
+
el2.style.setProperty(prop, `${value}px`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/stage/content-width.ts
|
|
1732
|
+
var measuring = /* @__PURE__ */ new WeakMap();
|
|
1733
|
+
var fitted = /* @__PURE__ */ new WeakMap();
|
|
1734
|
+
function fitsContent(frame) {
|
|
1735
|
+
if (frame === dom.iframe) {
|
|
1736
|
+
const vp = store.url.viewport;
|
|
1737
|
+
return (vp === "fit" || vp === "content") && dom.stageView.dataset.layout !== "full";
|
|
1738
|
+
}
|
|
1739
|
+
return frame.closest(".gallery-item") !== null;
|
|
1740
|
+
}
|
|
1741
|
+
function measureContentWidth(frame) {
|
|
1742
|
+
if (!fitsContent(frame)) {
|
|
1743
|
+
stopMeasuring(frame);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
frame.classList.add("vp-measuring");
|
|
1747
|
+
frame.style.removeProperty("--vp-w");
|
|
1748
|
+
fitted.delete(frame);
|
|
1749
|
+
measuring.set(frame, frame.clientWidth);
|
|
1750
|
+
}
|
|
1751
|
+
function stopMeasuring(frame) {
|
|
1752
|
+
measuring.delete(frame);
|
|
1753
|
+
fitted.delete(frame);
|
|
1754
|
+
frame.classList.remove("vp-measuring");
|
|
1755
|
+
}
|
|
1756
|
+
function remeasureAll() {
|
|
1757
|
+
for (const frame of document.querySelectorAll("#preview-frame, .gallery-item iframe")) {
|
|
1758
|
+
measureContentWidth(frame);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function widthThresholds(condition) {
|
|
1762
|
+
const out = [];
|
|
1763
|
+
const px = (n, unit) => parseFloat(n) * (unit.toLowerCase() === "px" ? 1 : 16);
|
|
1764
|
+
for (const query of condition.split(",")) {
|
|
1765
|
+
if (/^\s*(only\s+)?print\b/i.test(query)) continue;
|
|
1766
|
+
for (const m of query.matchAll(/(?:min|max)-(?:width|inline-size)\s*:\s*(-?[\d.]+)(px|r?em)\b/gi)) out.push(px(m[1], m[2]));
|
|
1767
|
+
for (const m of query.matchAll(/(?:width|inline-size)\s*[<>]=?\s*(-?[\d.]+)(px|r?em)\b/gi)) out.push(px(m[1], m[2]));
|
|
1768
|
+
for (const m of query.matchAll(/(-?[\d.]+)(px|r?em)\s*[<>]=?\s*(?:width|inline-size)\b/gi)) out.push(px(m[1], m[2]));
|
|
1769
|
+
}
|
|
1770
|
+
return out;
|
|
1771
|
+
}
|
|
1772
|
+
function widthConditionsChange(frame, narrow, wide) {
|
|
1773
|
+
const doc = frame.contentDocument;
|
|
1774
|
+
if (!doc) return false;
|
|
1775
|
+
const between = (t) => t >= narrow && t <= wide;
|
|
1776
|
+
const matches = (selector) => {
|
|
1777
|
+
try {
|
|
1778
|
+
return doc.querySelector(selector) !== null;
|
|
1779
|
+
} catch {
|
|
1780
|
+
return true;
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
const stylesThePreview = (rules) => {
|
|
1784
|
+
for (const rule of rules ?? []) {
|
|
1785
|
+
const r = rule;
|
|
1786
|
+
if (r.selectorText !== void 0 && matches(r.selectorText)) return true;
|
|
1787
|
+
if (r.cssRules !== void 0 && stylesThePreview(r.cssRules)) return true;
|
|
1788
|
+
}
|
|
1789
|
+
return false;
|
|
1790
|
+
};
|
|
1791
|
+
const visit = (rules) => {
|
|
1792
|
+
for (const rule of rules ?? []) {
|
|
1793
|
+
const r = rule;
|
|
1794
|
+
const condition = r.styleSheet === void 0 && r.media !== void 0 ? r.media.mediaText : r.containerName !== void 0 ? r.conditionText ?? null : null;
|
|
1795
|
+
if (condition !== null && widthThresholds(condition).some(between) && stylesThePreview(r.cssRules)) return true;
|
|
1796
|
+
if (r.styleSheet !== void 0) {
|
|
1797
|
+
const imported = sheetRules(r.styleSheet);
|
|
1798
|
+
if (r.media && widthThresholds(r.media.mediaText).some(between) && stylesThePreview(imported)) return true;
|
|
1799
|
+
if (visit(imported)) return true;
|
|
1800
|
+
} else if (r.cssRules !== void 0 && visit(r.cssRules)) return true;
|
|
1801
|
+
}
|
|
1802
|
+
return false;
|
|
1803
|
+
};
|
|
1804
|
+
return [...doc.styleSheets].some((sheet) => visit(sheetRules(sheet)));
|
|
1805
|
+
}
|
|
1806
|
+
function sheetRules(sheet) {
|
|
1807
|
+
try {
|
|
1808
|
+
return sheet?.cssRules;
|
|
1809
|
+
} catch {
|
|
1810
|
+
return void 0;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function setWidthNow(frame, width) {
|
|
1814
|
+
frame.classList.add("vp-measuring");
|
|
1815
|
+
if (width === null) frame.style.removeProperty("--vp-w");
|
|
1816
|
+
else setVpProp(frame, "--vp-w", width);
|
|
1817
|
+
requestAnimationFrame(() => requestAnimationFrame(() => frame.classList.remove("vp-measuring")));
|
|
1818
|
+
}
|
|
1819
|
+
function acceptContentWidth(frame, width, at, scroll) {
|
|
1820
|
+
const full = measuring.get(frame);
|
|
1821
|
+
if (full === void 0) {
|
|
1822
|
+
const state = fitted.get(frame);
|
|
1823
|
+
if (state === void 0 || scroll <= at + 1) return false;
|
|
1824
|
+
const parentWidth = frame.parentElement?.clientWidth ?? Infinity;
|
|
1825
|
+
if (state === "measured" && scroll < parentWidth) {
|
|
1826
|
+
fitted.set(frame, "widened");
|
|
1827
|
+
setWidthNow(frame, scroll);
|
|
1828
|
+
} else {
|
|
1829
|
+
fitted.delete(frame);
|
|
1830
|
+
setWidthNow(frame, null);
|
|
1831
|
+
}
|
|
1832
|
+
return true;
|
|
1833
|
+
}
|
|
1834
|
+
if (Math.abs(at - full) > 1) return false;
|
|
1835
|
+
measuring.delete(frame);
|
|
1836
|
+
const narrower = width < full - 1 && !widthConditionsChange(frame, width, full);
|
|
1837
|
+
if (narrower) fitted.set(frame, "measured");
|
|
1838
|
+
setWidthNow(frame, narrower ? width : null);
|
|
1839
|
+
return true;
|
|
1840
|
+
}
|
|
1841
|
+
function initContentWidth() {
|
|
1842
|
+
let theme = store.local.theme;
|
|
1843
|
+
store.subscribe(() => {
|
|
1844
|
+
if (store.local.theme === theme) return;
|
|
1845
|
+
theme = store.local.theme;
|
|
1846
|
+
remeasureAll();
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1711
1850
|
// src/stage/viewport.ts
|
|
1712
1851
|
var PRESETS = {
|
|
1713
1852
|
mobile: { w: 375, h: 667 },
|
|
@@ -1755,7 +1894,7 @@ function syncMarks() {
|
|
|
1755
1894
|
}
|
|
1756
1895
|
const sr = dom.stageView.getBoundingClientRect();
|
|
1757
1896
|
const EPSILON = 2;
|
|
1758
|
-
if (fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
|
|
1897
|
+
if (dom.iframe.classList.contains("vp-fit") || fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
|
|
1759
1898
|
dom.markOverlay.hidden = true;
|
|
1760
1899
|
return;
|
|
1761
1900
|
}
|
|
@@ -1784,19 +1923,14 @@ function applyViewport() {
|
|
|
1784
1923
|
dom.iframe.classList.remove("vp-sized");
|
|
1785
1924
|
dom.iframe.classList.remove("vp-content");
|
|
1786
1925
|
dom.iframe.classList.add("vp-fit");
|
|
1926
|
+
measureContentWidth(dom.iframe);
|
|
1787
1927
|
} 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
1928
|
dom.iframe.classList.remove("vp-sized");
|
|
1797
1929
|
dom.iframe.classList.remove("vp-fit");
|
|
1798
1930
|
dom.iframe.classList.add("vp-content");
|
|
1931
|
+
measureContentWidth(dom.iframe);
|
|
1799
1932
|
} else {
|
|
1933
|
+
stopMeasuring(dom.iframe);
|
|
1800
1934
|
const p = PRESETS[vp];
|
|
1801
1935
|
dom.iframe.style.setProperty("--vp-w", `${p.w}px`);
|
|
1802
1936
|
dom.iframe.classList.add("vp-sized");
|
|
@@ -1832,6 +1966,7 @@ function stopDisco() {
|
|
|
1832
1966
|
function startDisco() {
|
|
1833
1967
|
if (vpDisco) return;
|
|
1834
1968
|
vpDisco = true;
|
|
1969
|
+
stopMeasuring(dom.iframe);
|
|
1835
1970
|
dom.viewportToolbar.setAttribute("data-disco-active", "");
|
|
1836
1971
|
dom.vpDiscoBtn.setAttribute("aria-pressed", "true");
|
|
1837
1972
|
dom.vpDiscoBtn.innerHTML = svgIcon("vp-disco-fill");
|
|
@@ -1918,6 +2053,7 @@ function initViewport(onFullscreen) {
|
|
|
1918
2053
|
iframeObserver.observe(dom.iframe);
|
|
1919
2054
|
iframeObserver.observe(dom.stageView);
|
|
1920
2055
|
dom.iframe.addEventListener("load", () => {
|
|
2056
|
+
measureContentWidth(dom.iframe);
|
|
1921
2057
|
updateReadout();
|
|
1922
2058
|
syncFrameFocusListeners();
|
|
1923
2059
|
});
|
|
@@ -2191,6 +2327,7 @@ async function buildVariantsGallery() {
|
|
|
2191
2327
|
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2192
2328
|
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2193
2329
|
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2330
|
+
measureContentWidth(f);
|
|
2194
2331
|
});
|
|
2195
2332
|
fig.appendChild(h2);
|
|
2196
2333
|
fig.appendChild(f);
|
|
@@ -2231,6 +2368,7 @@ function renderOverview(overview) {
|
|
|
2231
2368
|
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2232
2369
|
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2233
2370
|
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2371
|
+
measureContentWidth(f);
|
|
2234
2372
|
});
|
|
2235
2373
|
f.src = resourceUrl(previewUrl(item.id, void 0, store.local.theme || void 0));
|
|
2236
2374
|
fig.appendChild(h2);
|
|
@@ -2283,7 +2421,6 @@ async function applyComponent(detail, variant) {
|
|
|
2283
2421
|
dom.iframe.srcdoc = _html;
|
|
2284
2422
|
});
|
|
2285
2423
|
dom.iframe.style.removeProperty("--vp-h");
|
|
2286
|
-
session.lastPdWidth = null;
|
|
2287
2424
|
if (isDiscoRunning()) stopDisco();
|
|
2288
2425
|
applyViewport();
|
|
2289
2426
|
delete dom.dockEl.dataset.mode;
|
|
@@ -2346,33 +2483,98 @@ async function navigate(componentId, variant) {
|
|
|
2346
2483
|
await applyComponent(detail, variant);
|
|
2347
2484
|
} catch (e) {
|
|
2348
2485
|
console.error("[pd] Navigate error:", e);
|
|
2486
|
+
showStageMessage(`Could not load \u201C${componentId}\u201D: ${e instanceof Error ? e.message : String(e)}`);
|
|
2349
2487
|
}
|
|
2350
2488
|
}
|
|
2489
|
+
async function navigateUnknown(path, variant) {
|
|
2490
|
+
const res = await fetch(componentUrl(path, variant)).catch(() => null);
|
|
2491
|
+
if (res?.ok) {
|
|
2492
|
+
await applyComponent(await res.json(), variant);
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
showStageMessage(`No component or folder \u201C${path}\u201D.`, closestPaths(path));
|
|
2496
|
+
}
|
|
2497
|
+
function closestPaths(path) {
|
|
2498
|
+
const all = [];
|
|
2499
|
+
const walk = (nodes) => {
|
|
2500
|
+
for (const n of nodes) {
|
|
2501
|
+
all.push(n.path);
|
|
2502
|
+
if (n.children) walk(n.children);
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
walk(session.tree.roots);
|
|
2506
|
+
const name = path.split("/").pop() ?? path;
|
|
2507
|
+
const sameName = all.filter((p) => p.split("/").pop() === name);
|
|
2508
|
+
const near = all.map((p) => ({ p, d: editDistance(p, path) })).filter(({ p, d }) => d <= Math.max(2, Math.floor(path.length / 5)) && !sameName.includes(p)).sort((a, b) => a.d - b.d).map(({ p }) => p);
|
|
2509
|
+
return [...sameName, ...near].slice(0, 3);
|
|
2510
|
+
}
|
|
2511
|
+
function editDistance(a, b) {
|
|
2512
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
2513
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2514
|
+
let prev = row[0];
|
|
2515
|
+
row[0] = i;
|
|
2516
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2517
|
+
const cur = row[j];
|
|
2518
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
2519
|
+
prev = cur;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return row[b.length];
|
|
2523
|
+
}
|
|
2524
|
+
function showFolderArea(activePath) {
|
|
2525
|
+
session.current = null;
|
|
2526
|
+
session.currentVariant = null;
|
|
2527
|
+
session.renderedHtmlCache = null;
|
|
2528
|
+
session.rawRenderedHtml = null;
|
|
2529
|
+
session.dataCache = null;
|
|
2530
|
+
session.dataRaw = null;
|
|
2531
|
+
session.stageMode = "single";
|
|
2532
|
+
dom.variantBar.hidden = true;
|
|
2533
|
+
dom.panel.hidden = true;
|
|
2534
|
+
dom.singlePreview.hidden = true;
|
|
2535
|
+
dom.categoryOverview.hidden = false;
|
|
2536
|
+
dom.variantsGalleryEl.hidden = true;
|
|
2537
|
+
dom.stageView.removeAttribute("data-layout");
|
|
2538
|
+
dom.dockEl.dataset.mode = "overview";
|
|
2539
|
+
switchDockTab("console");
|
|
2540
|
+
setActiveTreeItem(activePath);
|
|
2541
|
+
}
|
|
2542
|
+
function showStageMessage(message, suggestions = []) {
|
|
2543
|
+
showFolderArea("");
|
|
2544
|
+
dom.categoryOverview.innerHTML = "";
|
|
2545
|
+
const box = document.createElement("div");
|
|
2546
|
+
box.className = "stage-message";
|
|
2547
|
+
box.setAttribute("role", "status");
|
|
2548
|
+
const p = document.createElement("p");
|
|
2549
|
+
p.textContent = message;
|
|
2550
|
+
box.appendChild(p);
|
|
2551
|
+
if (suggestions.length > 0) {
|
|
2552
|
+
const intro = document.createElement("p");
|
|
2553
|
+
intro.textContent = "Did you mean:";
|
|
2554
|
+
const list = document.createElement("ul");
|
|
2555
|
+
for (const s of suggestions) {
|
|
2556
|
+
const li = document.createElement("li");
|
|
2557
|
+
const a = document.createElement("a");
|
|
2558
|
+
a.href = routeHref(`/${s}`);
|
|
2559
|
+
a.textContent = s;
|
|
2560
|
+
li.appendChild(a);
|
|
2561
|
+
list.appendChild(li);
|
|
2562
|
+
}
|
|
2563
|
+
box.append(intro, list);
|
|
2564
|
+
}
|
|
2565
|
+
dom.categoryOverview.appendChild(box);
|
|
2566
|
+
}
|
|
2351
2567
|
async function navigateOverview(folderPath) {
|
|
2352
2568
|
try {
|
|
2353
2569
|
const res = await fetch(overviewUrl(folderPath));
|
|
2354
2570
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2355
2571
|
const overview = await res.json();
|
|
2356
|
-
|
|
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);
|
|
2572
|
+
showFolderArea(folderPath);
|
|
2372
2573
|
renderOverview(overview);
|
|
2373
2574
|
await renderPanel();
|
|
2374
2575
|
} catch (e) {
|
|
2375
2576
|
console.error("[pd] Overview error:", e);
|
|
2577
|
+
showStageMessage(`Could not load \u201C${folderPath}\u201D: ${e instanceof Error ? e.message : String(e)}`);
|
|
2376
2578
|
}
|
|
2377
2579
|
}
|
|
2378
2580
|
async function navigateVariants(componentId) {
|
|
@@ -2546,6 +2748,7 @@ function connectHmr() {
|
|
|
2546
2748
|
if (link) link.href = href;
|
|
2547
2749
|
});
|
|
2548
2750
|
}
|
|
2751
|
+
void pdLinksLoaded().then(remeasureAll);
|
|
2549
2752
|
if (store.local.panelTab === "relationships" && session.current) {
|
|
2550
2753
|
void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
|
|
2551
2754
|
session.current = d;
|
|
@@ -2618,6 +2821,14 @@ function connectHmr() {
|
|
|
2618
2821
|
}
|
|
2619
2822
|
});
|
|
2620
2823
|
}
|
|
2824
|
+
function pdLinksLoaded() {
|
|
2825
|
+
const links = [...document.querySelectorAll("iframe")].flatMap((f) => [...f.contentDocument?.querySelectorAll("link[data-pd-comp]") ?? []]);
|
|
2826
|
+
return Promise.all(links.map((link) => new Promise((resolve) => {
|
|
2827
|
+
link.addEventListener("load", () => resolve(), { once: true });
|
|
2828
|
+
link.addEventListener("error", () => resolve(), { once: true });
|
|
2829
|
+
setTimeout(resolve, 1e3);
|
|
2830
|
+
}))).then(() => void 0);
|
|
2831
|
+
}
|
|
2621
2832
|
|
|
2622
2833
|
// src/app/router.ts
|
|
2623
2834
|
function resolvePathKind(path) {
|
|
@@ -2646,6 +2857,10 @@ async function routeFromCurrentLocation() {
|
|
|
2646
2857
|
applyCvd();
|
|
2647
2858
|
}
|
|
2648
2859
|
const path = routeFromPathname(window.location.pathname).slice(1);
|
|
2860
|
+
if (!path && window.location.hash.startsWith("#/")) {
|
|
2861
|
+
history.replaceState(history.state, "", routeHref(window.location.hash.slice(1)) + window.location.search);
|
|
2862
|
+
return routeFromCurrentLocation();
|
|
2863
|
+
}
|
|
2649
2864
|
const variant = window.location.hash.slice(1) || void 0;
|
|
2650
2865
|
if (!path) return;
|
|
2651
2866
|
if (path.endsWith("/variants")) {
|
|
@@ -2662,16 +2877,10 @@ async function routeFromCurrentLocation() {
|
|
|
2662
2877
|
} else {
|
|
2663
2878
|
await navigate(path, variant);
|
|
2664
2879
|
}
|
|
2665
|
-
} else {
|
|
2880
|
+
} else if (kind === "group") {
|
|
2666
2881
|
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`);
|
|
2882
|
+
} else {
|
|
2883
|
+
await navigateUnknown(path, variant);
|
|
2675
2884
|
}
|
|
2676
2885
|
}
|
|
2677
2886
|
|
|
@@ -2689,17 +2898,16 @@ function listenForPreviewMessages() {
|
|
|
2689
2898
|
setVpProp(el2, "--vp-h", height);
|
|
2690
2899
|
}
|
|
2691
2900
|
});
|
|
2692
|
-
} else if (data.type === "pd-width" && typeof data.width === "number") {
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
}
|
|
2901
|
+
} else if (data.type === "pd-width" && typeof data.width === "number" && typeof data.at === "number") {
|
|
2902
|
+
const { width, at } = data;
|
|
2903
|
+
const scroll = typeof data.scroll === "number" ? data.scroll : at;
|
|
2904
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2905
|
+
const frame = f;
|
|
2906
|
+
if (frame.contentWindow !== e.source) return;
|
|
2907
|
+
if (acceptContentWidth(frame, width, at, scroll) && frame === dom.iframe) {
|
|
2908
|
+
requestAnimationFrame(updateReadout);
|
|
2909
|
+
}
|
|
2910
|
+
});
|
|
2703
2911
|
} else if (data.type === "pd-fp") {
|
|
2704
2912
|
if (data.action === "exit") dom.fullpageDialog.close();
|
|
2705
2913
|
else if (data.action === "active") showFullpageChrome();
|
|
@@ -2739,6 +2947,7 @@ function mount() {
|
|
|
2739
2947
|
app.innerHTML = renderShellHtml();
|
|
2740
2948
|
initDom(app);
|
|
2741
2949
|
listenForPreviewMessages();
|
|
2950
|
+
initContentWidth();
|
|
2742
2951
|
dom.searchInput.addEventListener("input", () => {
|
|
2743
2952
|
session.searchQuery = dom.searchInput.value;
|
|
2744
2953
|
filterTree(session.searchQuery);
|
package/dist/index.js
CHANGED