pattern-dump 0.1.0-alpha.0 → 0.1.0-alpha.2
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 +60 -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 +339 -80
- 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
|
|
@@ -388,9 +380,9 @@ function createStore() {
|
|
|
388
380
|
function notify() {
|
|
389
381
|
for (const fn of listeners) fn();
|
|
390
382
|
}
|
|
391
|
-
function persist(
|
|
383
|
+
function persist(state2) {
|
|
392
384
|
try {
|
|
393
|
-
localStorage.setItem(STORAGE_KEY, toJSON(
|
|
385
|
+
localStorage.setItem(STORAGE_KEY, toJSON(state2));
|
|
394
386
|
} catch {
|
|
395
387
|
}
|
|
396
388
|
}
|
|
@@ -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,175 @@ 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-height.ts
|
|
1732
|
+
var GROWTH_FACTOR = 1.5;
|
|
1733
|
+
var GROWTH_RUN = 2;
|
|
1734
|
+
var state = /* @__PURE__ */ new WeakMap();
|
|
1735
|
+
var circular = /* @__PURE__ */ new WeakSet();
|
|
1736
|
+
var report = null;
|
|
1737
|
+
function isCircularGrowth(previous, measured) {
|
|
1738
|
+
if (previous === void 0) return false;
|
|
1739
|
+
return measured > previous.last * GROWTH_FACTOR && previous.run + 1 >= GROWTH_RUN;
|
|
1740
|
+
}
|
|
1741
|
+
function fitsHeight(frame) {
|
|
1742
|
+
return frame.closest('[data-layout="full"]') === null;
|
|
1743
|
+
}
|
|
1744
|
+
function acceptContentHeight(frame, height) {
|
|
1745
|
+
if (!fitsHeight(frame) || circular.has(frame)) return false;
|
|
1746
|
+
const previous = state.get(frame);
|
|
1747
|
+
if (isCircularGrowth(previous, height)) {
|
|
1748
|
+
circular.add(frame);
|
|
1749
|
+
state.delete(frame);
|
|
1750
|
+
frame.dataset.pdHeight = "circular";
|
|
1751
|
+
setVpProp(frame, "--vp-h", previous.steady);
|
|
1752
|
+
report?.(frame);
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1755
|
+
const grew = previous !== void 0 && height > previous.last * GROWTH_FACTOR;
|
|
1756
|
+
state.set(frame, {
|
|
1757
|
+
last: height,
|
|
1758
|
+
steady: grew ? previous.steady : height,
|
|
1759
|
+
run: grew ? previous.run + 1 : 0
|
|
1760
|
+
});
|
|
1761
|
+
setVpProp(frame, "--vp-h", height);
|
|
1762
|
+
return true;
|
|
1763
|
+
}
|
|
1764
|
+
function resetContentHeight(frame) {
|
|
1765
|
+
state.delete(frame);
|
|
1766
|
+
circular.delete(frame);
|
|
1767
|
+
delete frame.dataset.pdHeight;
|
|
1768
|
+
}
|
|
1769
|
+
function initContentHeight(onCircular) {
|
|
1770
|
+
report = onCircular;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/stage/content-width.ts
|
|
1774
|
+
var measuring = /* @__PURE__ */ new WeakMap();
|
|
1775
|
+
var fitted = /* @__PURE__ */ new WeakMap();
|
|
1776
|
+
function fitsContent(frame) {
|
|
1777
|
+
if (frame === dom.iframe) {
|
|
1778
|
+
const vp = store.url.viewport;
|
|
1779
|
+
return (vp === "fit" || vp === "content") && dom.stageView.dataset.layout !== "full";
|
|
1780
|
+
}
|
|
1781
|
+
return frame.closest(".gallery-item") !== null;
|
|
1782
|
+
}
|
|
1783
|
+
function measureContentWidth(frame) {
|
|
1784
|
+
if (!fitsContent(frame)) {
|
|
1785
|
+
stopMeasuring(frame);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
frame.classList.add("vp-measuring");
|
|
1789
|
+
frame.style.removeProperty("--vp-w");
|
|
1790
|
+
fitted.delete(frame);
|
|
1791
|
+
measuring.set(frame, frame.clientWidth);
|
|
1792
|
+
}
|
|
1793
|
+
function stopMeasuring(frame) {
|
|
1794
|
+
measuring.delete(frame);
|
|
1795
|
+
fitted.delete(frame);
|
|
1796
|
+
frame.classList.remove("vp-measuring");
|
|
1797
|
+
}
|
|
1798
|
+
function remeasureAll() {
|
|
1799
|
+
for (const frame of document.querySelectorAll("#preview-frame, .gallery-item iframe")) {
|
|
1800
|
+
measureContentWidth(frame);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function widthThresholds(condition) {
|
|
1804
|
+
const out = [];
|
|
1805
|
+
const px = (n, unit) => parseFloat(n) * (unit.toLowerCase() === "px" ? 1 : 16);
|
|
1806
|
+
for (const query of condition.split(",")) {
|
|
1807
|
+
if (/^\s*(only\s+)?print\b/i.test(query)) continue;
|
|
1808
|
+
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]));
|
|
1809
|
+
for (const m of query.matchAll(/(?:width|inline-size)\s*[<>]=?\s*(-?[\d.]+)(px|r?em)\b/gi)) out.push(px(m[1], m[2]));
|
|
1810
|
+
for (const m of query.matchAll(/(-?[\d.]+)(px|r?em)\s*[<>]=?\s*(?:width|inline-size)\b/gi)) out.push(px(m[1], m[2]));
|
|
1811
|
+
}
|
|
1812
|
+
return out;
|
|
1813
|
+
}
|
|
1814
|
+
function widthConditionsChange(frame, narrow, wide) {
|
|
1815
|
+
const doc = frame.contentDocument;
|
|
1816
|
+
if (!doc) return false;
|
|
1817
|
+
const between = (t) => t >= narrow && t <= wide;
|
|
1818
|
+
const matches = (selector) => {
|
|
1819
|
+
try {
|
|
1820
|
+
return doc.querySelector(selector) !== null;
|
|
1821
|
+
} catch {
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
const stylesThePreview = (rules) => {
|
|
1826
|
+
for (const rule of rules ?? []) {
|
|
1827
|
+
const r = rule;
|
|
1828
|
+
if (r.selectorText !== void 0 && matches(r.selectorText)) return true;
|
|
1829
|
+
if (r.cssRules !== void 0 && stylesThePreview(r.cssRules)) return true;
|
|
1830
|
+
}
|
|
1831
|
+
return false;
|
|
1832
|
+
};
|
|
1833
|
+
const visit = (rules) => {
|
|
1834
|
+
for (const rule of rules ?? []) {
|
|
1835
|
+
const r = rule;
|
|
1836
|
+
const condition = r.styleSheet === void 0 && r.media !== void 0 ? r.media.mediaText : r.containerName !== void 0 ? r.conditionText ?? null : null;
|
|
1837
|
+
if (condition !== null && widthThresholds(condition).some(between) && stylesThePreview(r.cssRules)) return true;
|
|
1838
|
+
if (r.styleSheet !== void 0) {
|
|
1839
|
+
const imported = sheetRules(r.styleSheet);
|
|
1840
|
+
if (r.media && widthThresholds(r.media.mediaText).some(between) && stylesThePreview(imported)) return true;
|
|
1841
|
+
if (visit(imported)) return true;
|
|
1842
|
+
} else if (r.cssRules !== void 0 && visit(r.cssRules)) return true;
|
|
1843
|
+
}
|
|
1844
|
+
return false;
|
|
1845
|
+
};
|
|
1846
|
+
return [...doc.styleSheets].some((sheet) => visit(sheetRules(sheet)));
|
|
1847
|
+
}
|
|
1848
|
+
function sheetRules(sheet) {
|
|
1849
|
+
try {
|
|
1850
|
+
return sheet?.cssRules;
|
|
1851
|
+
} catch {
|
|
1852
|
+
return void 0;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function setWidthNow(frame, width) {
|
|
1856
|
+
frame.classList.add("vp-measuring");
|
|
1857
|
+
if (width === null) frame.style.removeProperty("--vp-w");
|
|
1858
|
+
else setVpProp(frame, "--vp-w", width);
|
|
1859
|
+
requestAnimationFrame(() => requestAnimationFrame(() => frame.classList.remove("vp-measuring")));
|
|
1860
|
+
}
|
|
1861
|
+
function acceptContentWidth(frame, width, at, scroll) {
|
|
1862
|
+
const full = measuring.get(frame);
|
|
1863
|
+
if (full === void 0) {
|
|
1864
|
+
const state2 = fitted.get(frame);
|
|
1865
|
+
if (state2 === void 0 || scroll <= at + 1) return false;
|
|
1866
|
+
const parentWidth = frame.parentElement?.clientWidth ?? Infinity;
|
|
1867
|
+
if (state2 === "measured" && scroll < parentWidth) {
|
|
1868
|
+
fitted.set(frame, "widened");
|
|
1869
|
+
setWidthNow(frame, scroll);
|
|
1870
|
+
} else {
|
|
1871
|
+
fitted.delete(frame);
|
|
1872
|
+
setWidthNow(frame, null);
|
|
1873
|
+
}
|
|
1874
|
+
return true;
|
|
1875
|
+
}
|
|
1876
|
+
if (Math.abs(at - full) > 1) return false;
|
|
1877
|
+
measuring.delete(frame);
|
|
1878
|
+
const narrower = width < full - 1 && !widthConditionsChange(frame, width, full);
|
|
1879
|
+
if (narrower) fitted.set(frame, "measured");
|
|
1880
|
+
setWidthNow(frame, narrower ? width : null);
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
function initContentWidth() {
|
|
1884
|
+
let theme = store.local.theme;
|
|
1885
|
+
store.subscribe(() => {
|
|
1886
|
+
if (store.local.theme === theme) return;
|
|
1887
|
+
theme = store.local.theme;
|
|
1888
|
+
remeasureAll();
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1711
1892
|
// src/stage/viewport.ts
|
|
1712
1893
|
var PRESETS = {
|
|
1713
1894
|
mobile: { w: 375, h: 667 },
|
|
@@ -1755,7 +1936,7 @@ function syncMarks() {
|
|
|
1755
1936
|
}
|
|
1756
1937
|
const sr = dom.stageView.getBoundingClientRect();
|
|
1757
1938
|
const EPSILON = 2;
|
|
1758
|
-
if (fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
|
|
1939
|
+
if (dom.iframe.classList.contains("vp-fit") || fr.width >= sr.width - EPSILON || fr.height >= sr.height - EPSILON) {
|
|
1759
1940
|
dom.markOverlay.hidden = true;
|
|
1760
1941
|
return;
|
|
1761
1942
|
}
|
|
@@ -1784,19 +1965,14 @@ function applyViewport() {
|
|
|
1784
1965
|
dom.iframe.classList.remove("vp-sized");
|
|
1785
1966
|
dom.iframe.classList.remove("vp-content");
|
|
1786
1967
|
dom.iframe.classList.add("vp-fit");
|
|
1968
|
+
measureContentWidth(dom.iframe);
|
|
1787
1969
|
} 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
1970
|
dom.iframe.classList.remove("vp-sized");
|
|
1797
1971
|
dom.iframe.classList.remove("vp-fit");
|
|
1798
1972
|
dom.iframe.classList.add("vp-content");
|
|
1973
|
+
measureContentWidth(dom.iframe);
|
|
1799
1974
|
} else {
|
|
1975
|
+
stopMeasuring(dom.iframe);
|
|
1800
1976
|
const p = PRESETS[vp];
|
|
1801
1977
|
dom.iframe.style.setProperty("--vp-w", `${p.w}px`);
|
|
1802
1978
|
dom.iframe.classList.add("vp-sized");
|
|
@@ -1832,6 +2008,7 @@ function stopDisco() {
|
|
|
1832
2008
|
function startDisco() {
|
|
1833
2009
|
if (vpDisco) return;
|
|
1834
2010
|
vpDisco = true;
|
|
2011
|
+
stopMeasuring(dom.iframe);
|
|
1835
2012
|
dom.viewportToolbar.setAttribute("data-disco-active", "");
|
|
1836
2013
|
dom.vpDiscoBtn.setAttribute("aria-pressed", "true");
|
|
1837
2014
|
dom.vpDiscoBtn.innerHTML = svgIcon("vp-disco-fill");
|
|
@@ -1918,6 +2095,8 @@ function initViewport(onFullscreen) {
|
|
|
1918
2095
|
iframeObserver.observe(dom.iframe);
|
|
1919
2096
|
iframeObserver.observe(dom.stageView);
|
|
1920
2097
|
dom.iframe.addEventListener("load", () => {
|
|
2098
|
+
resetContentHeight(dom.iframe);
|
|
2099
|
+
measureContentWidth(dom.iframe);
|
|
1921
2100
|
updateReadout();
|
|
1922
2101
|
syncFrameFocusListeners();
|
|
1923
2102
|
});
|
|
@@ -2190,7 +2369,9 @@ async function buildVariantsGallery() {
|
|
|
2190
2369
|
f.addEventListener("load", () => {
|
|
2191
2370
|
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2192
2371
|
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2372
|
+
resetContentHeight(f);
|
|
2193
2373
|
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2374
|
+
measureContentWidth(f);
|
|
2194
2375
|
});
|
|
2195
2376
|
fig.appendChild(h2);
|
|
2196
2377
|
fig.appendChild(f);
|
|
@@ -2230,7 +2411,9 @@ function renderOverview(overview) {
|
|
|
2230
2411
|
f.addEventListener("load", () => {
|
|
2231
2412
|
if (store.local.theme) applyThemeToIframe(f, store.local.theme);
|
|
2232
2413
|
const h = f.contentDocument?.documentElement?.scrollHeight;
|
|
2414
|
+
resetContentHeight(f);
|
|
2233
2415
|
if (typeof h === "number" && h > 0) f.style.setProperty("--vp-h", `${h}px`);
|
|
2416
|
+
measureContentWidth(f);
|
|
2234
2417
|
});
|
|
2235
2418
|
f.src = resourceUrl(previewUrl(item.id, void 0, store.local.theme || void 0));
|
|
2236
2419
|
fig.appendChild(h2);
|
|
@@ -2283,7 +2466,6 @@ async function applyComponent(detail, variant) {
|
|
|
2283
2466
|
dom.iframe.srcdoc = _html;
|
|
2284
2467
|
});
|
|
2285
2468
|
dom.iframe.style.removeProperty("--vp-h");
|
|
2286
|
-
session.lastPdWidth = null;
|
|
2287
2469
|
if (isDiscoRunning()) stopDisco();
|
|
2288
2470
|
applyViewport();
|
|
2289
2471
|
delete dom.dockEl.dataset.mode;
|
|
@@ -2346,33 +2528,98 @@ async function navigate(componentId, variant) {
|
|
|
2346
2528
|
await applyComponent(detail, variant);
|
|
2347
2529
|
} catch (e) {
|
|
2348
2530
|
console.error("[pd] Navigate error:", e);
|
|
2531
|
+
showStageMessage(`Could not load \u201C${componentId}\u201D: ${e instanceof Error ? e.message : String(e)}`);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
async function navigateUnknown(path, variant) {
|
|
2535
|
+
const res = await fetch(componentUrl(path, variant)).catch(() => null);
|
|
2536
|
+
if (res?.ok) {
|
|
2537
|
+
await applyComponent(await res.json(), variant);
|
|
2538
|
+
return;
|
|
2349
2539
|
}
|
|
2540
|
+
showStageMessage(`No component or folder \u201C${path}\u201D.`, closestPaths(path));
|
|
2541
|
+
}
|
|
2542
|
+
function closestPaths(path) {
|
|
2543
|
+
const all = [];
|
|
2544
|
+
const walk = (nodes) => {
|
|
2545
|
+
for (const n of nodes) {
|
|
2546
|
+
all.push(n.path);
|
|
2547
|
+
if (n.children) walk(n.children);
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
walk(session.tree.roots);
|
|
2551
|
+
const name = path.split("/").pop() ?? path;
|
|
2552
|
+
const sameName = all.filter((p) => p.split("/").pop() === name);
|
|
2553
|
+
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);
|
|
2554
|
+
return [...sameName, ...near].slice(0, 3);
|
|
2555
|
+
}
|
|
2556
|
+
function editDistance(a, b) {
|
|
2557
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
2558
|
+
for (let i = 1; i <= a.length; i++) {
|
|
2559
|
+
let prev = row[0];
|
|
2560
|
+
row[0] = i;
|
|
2561
|
+
for (let j = 1; j <= b.length; j++) {
|
|
2562
|
+
const cur = row[j];
|
|
2563
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
2564
|
+
prev = cur;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
return row[b.length];
|
|
2568
|
+
}
|
|
2569
|
+
function showFolderArea(activePath) {
|
|
2570
|
+
session.current = null;
|
|
2571
|
+
session.currentVariant = null;
|
|
2572
|
+
session.renderedHtmlCache = null;
|
|
2573
|
+
session.rawRenderedHtml = null;
|
|
2574
|
+
session.dataCache = null;
|
|
2575
|
+
session.dataRaw = null;
|
|
2576
|
+
session.stageMode = "single";
|
|
2577
|
+
dom.variantBar.hidden = true;
|
|
2578
|
+
dom.panel.hidden = true;
|
|
2579
|
+
dom.singlePreview.hidden = true;
|
|
2580
|
+
dom.categoryOverview.hidden = false;
|
|
2581
|
+
dom.variantsGalleryEl.hidden = true;
|
|
2582
|
+
dom.stageView.removeAttribute("data-layout");
|
|
2583
|
+
dom.dockEl.dataset.mode = "overview";
|
|
2584
|
+
switchDockTab("console");
|
|
2585
|
+
setActiveTreeItem(activePath);
|
|
2586
|
+
}
|
|
2587
|
+
function showStageMessage(message, suggestions = []) {
|
|
2588
|
+
showFolderArea("");
|
|
2589
|
+
dom.categoryOverview.innerHTML = "";
|
|
2590
|
+
const box = document.createElement("div");
|
|
2591
|
+
box.className = "stage-message";
|
|
2592
|
+
box.setAttribute("role", "status");
|
|
2593
|
+
const p = document.createElement("p");
|
|
2594
|
+
p.textContent = message;
|
|
2595
|
+
box.appendChild(p);
|
|
2596
|
+
if (suggestions.length > 0) {
|
|
2597
|
+
const intro = document.createElement("p");
|
|
2598
|
+
intro.textContent = "Did you mean:";
|
|
2599
|
+
const list = document.createElement("ul");
|
|
2600
|
+
for (const s of suggestions) {
|
|
2601
|
+
const li = document.createElement("li");
|
|
2602
|
+
const a = document.createElement("a");
|
|
2603
|
+
a.href = routeHref(`/${s}`);
|
|
2604
|
+
a.textContent = s;
|
|
2605
|
+
li.appendChild(a);
|
|
2606
|
+
list.appendChild(li);
|
|
2607
|
+
}
|
|
2608
|
+
box.append(intro, list);
|
|
2609
|
+
}
|
|
2610
|
+
dom.categoryOverview.appendChild(box);
|
|
2350
2611
|
}
|
|
2351
2612
|
async function navigateOverview(folderPath) {
|
|
2352
2613
|
try {
|
|
2353
2614
|
const res = await fetch(overviewUrl(folderPath));
|
|
2354
2615
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2355
2616
|
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);
|
|
2617
|
+
showFolderArea(folderPath);
|
|
2372
2618
|
renderOverview(overview);
|
|
2373
2619
|
await renderPanel();
|
|
2374
2620
|
} catch (e) {
|
|
2375
2621
|
console.error("[pd] Overview error:", e);
|
|
2622
|
+
showStageMessage(`Could not load \u201C${folderPath}\u201D: ${e instanceof Error ? e.message : String(e)}`);
|
|
2376
2623
|
}
|
|
2377
2624
|
}
|
|
2378
2625
|
async function navigateVariants(componentId) {
|
|
@@ -2546,6 +2793,10 @@ function connectHmr() {
|
|
|
2546
2793
|
if (link) link.href = href;
|
|
2547
2794
|
});
|
|
2548
2795
|
}
|
|
2796
|
+
void pdLinksLoaded().then(() => {
|
|
2797
|
+
document.querySelectorAll("#preview-frame, .gallery-item iframe").forEach(resetContentHeight);
|
|
2798
|
+
remeasureAll();
|
|
2799
|
+
});
|
|
2549
2800
|
if (store.local.panelTab === "relationships" && session.current) {
|
|
2550
2801
|
void fetch(componentUrl(session.current.id)).then((r) => r.ok ? r.json() : Promise.reject(r.status)).then((d) => {
|
|
2551
2802
|
session.current = d;
|
|
@@ -2618,6 +2869,14 @@ function connectHmr() {
|
|
|
2618
2869
|
}
|
|
2619
2870
|
});
|
|
2620
2871
|
}
|
|
2872
|
+
function pdLinksLoaded() {
|
|
2873
|
+
const links = [...document.querySelectorAll("iframe")].flatMap((f) => [...f.contentDocument?.querySelectorAll("link[data-pd-comp]") ?? []]);
|
|
2874
|
+
return Promise.all(links.map((link) => new Promise((resolve) => {
|
|
2875
|
+
link.addEventListener("load", () => resolve(), { once: true });
|
|
2876
|
+
link.addEventListener("error", () => resolve(), { once: true });
|
|
2877
|
+
setTimeout(resolve, 1e3);
|
|
2878
|
+
}))).then(() => void 0);
|
|
2879
|
+
}
|
|
2621
2880
|
|
|
2622
2881
|
// src/app/router.ts
|
|
2623
2882
|
function resolvePathKind(path) {
|
|
@@ -2646,6 +2905,10 @@ async function routeFromCurrentLocation() {
|
|
|
2646
2905
|
applyCvd();
|
|
2647
2906
|
}
|
|
2648
2907
|
const path = routeFromPathname(window.location.pathname).slice(1);
|
|
2908
|
+
if (!path && window.location.hash.startsWith("#/")) {
|
|
2909
|
+
history.replaceState(history.state, "", routeHref(window.location.hash.slice(1)) + window.location.search);
|
|
2910
|
+
return routeFromCurrentLocation();
|
|
2911
|
+
}
|
|
2649
2912
|
const variant = window.location.hash.slice(1) || void 0;
|
|
2650
2913
|
if (!path) return;
|
|
2651
2914
|
if (path.endsWith("/variants")) {
|
|
@@ -2662,16 +2925,10 @@ async function routeFromCurrentLocation() {
|
|
|
2662
2925
|
} else {
|
|
2663
2926
|
await navigate(path, variant);
|
|
2664
2927
|
}
|
|
2665
|
-
} else {
|
|
2928
|
+
} else if (kind === "group") {
|
|
2666
2929
|
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`);
|
|
2930
|
+
} else {
|
|
2931
|
+
await navigateUnknown(path, variant);
|
|
2675
2932
|
}
|
|
2676
2933
|
}
|
|
2677
2934
|
|
|
@@ -2683,23 +2940,20 @@ function listenForPreviewMessages() {
|
|
|
2683
2940
|
if (data.type === "pd-height" && typeof data.height === "number") {
|
|
2684
2941
|
const height = data.height;
|
|
2685
2942
|
document.querySelectorAll("iframe").forEach((f) => {
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2943
|
+
const frame = f;
|
|
2944
|
+
if (frame.contentWindow !== e.source) return;
|
|
2945
|
+
acceptContentHeight(frame, height);
|
|
2946
|
+
});
|
|
2947
|
+
} else if (data.type === "pd-width" && typeof data.width === "number" && typeof data.at === "number") {
|
|
2948
|
+
const { width, at } = data;
|
|
2949
|
+
const scroll = typeof data.scroll === "number" ? data.scroll : at;
|
|
2950
|
+
document.querySelectorAll("iframe").forEach((f) => {
|
|
2951
|
+
const frame = f;
|
|
2952
|
+
if (frame.contentWindow !== e.source) return;
|
|
2953
|
+
if (acceptContentWidth(frame, width, at, scroll) && frame === dom.iframe) {
|
|
2954
|
+
requestAnimationFrame(updateReadout);
|
|
2690
2955
|
}
|
|
2691
2956
|
});
|
|
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
2957
|
} else if (data.type === "pd-fp") {
|
|
2704
2958
|
if (data.action === "exit") dom.fullpageDialog.close();
|
|
2705
2959
|
else if (data.action === "active") showFullpageChrome();
|
|
@@ -2739,6 +2993,11 @@ function mount() {
|
|
|
2739
2993
|
app.innerHTML = renderShellHtml();
|
|
2740
2994
|
initDom(app);
|
|
2741
2995
|
listenForPreviewMessages();
|
|
2996
|
+
initContentWidth();
|
|
2997
|
+
initContentHeight((frame) => {
|
|
2998
|
+
const id = frame.contentDocument?.body?.dataset.componentId ?? session.current?.id ?? "preview";
|
|
2999
|
+
renderConsole(`${id}: height depends on the preview's own height (a viewport-relative rule such as height: 100vh), so the frame keeps its last steady height and the content scrolls inside it.`);
|
|
3000
|
+
});
|
|
2742
3001
|
dom.searchInput.addEventListener("input", () => {
|
|
2743
3002
|
session.searchQuery = dom.searchInput.value;
|
|
2744
3003
|
filterTree(session.searchQuery);
|
package/dist/index.js
CHANGED