@motion-proto/live-tokens 0.98.2 → 0.99.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/CHANGELOG.md +67 -0
- package/bin/contractRunner.mjs +2 -2
- package/bin/rules/a11y.mjs +1 -1
- package/dist-plugin/index.cjs +71 -15
- package/dist-plugin/index.js +71 -15
- package/package.json +1 -1
- package/src/editor/bootstrap.ts +3 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +9 -10
- package/src/editor/core/a11y/a11yReport.ts +85 -22
- package/src/editor/core/a11y/a11yReportTypes.ts +11 -3
- package/src/editor/core/a11y/contrastFixes.ts +41 -0
- package/src/editor/core/palettes/contrastPairs.ts +4 -1
- package/src/editor/docs/content/accessibility.md +67 -18
- package/src/editor/docs/content/editing-tokens.md +4 -0
- package/src/editor/docs/content.generated.ts +2 -2
- package/src/editor/pages/ComponentEditorPage.svelte +4 -3
- package/src/editor/pages/EditorShell.svelte +10 -8
- package/src/editor/styles/ui-editor.css +2 -0
- package/src/editor/ui/a11y/A11yByEye.svelte +264 -0
- package/src/editor/ui/a11y/A11yComponentIssues.svelte +34 -16
- package/src/editor/ui/a11y/A11yFindingRow.svelte +66 -4
- package/src/editor/ui/a11y/A11yIssueRow.svelte +55 -24
- package/src/editor/ui/a11y/A11yOverview.svelte +72 -68
- package/src/editor/ui/a11y/A11yPanel.svelte +29 -4
- package/src/editor/ui/a11y/A11yPropertyMark.svelte +8 -8
- package/src/editor/ui/a11y/A11yRailMark.svelte +11 -4
- package/src/editor/ui/a11y/AccessibilityTab.svelte +22 -35
- package/src/editor/ui/a11y/a11yView.ts +50 -11
- package/src/live-tokens/data/colors-and-type/halloween.json +1 -1
- package/src/live-tokens/data/themes/autumn.json +16 -16
- package/src/live-tokens/data/themes/halloween.json +17 -17
- package/src/live-tokens/data/themes/midnight-study.json +16 -16
- package/src/live-tokens/data/themes/ocean.json +16 -16
- package/src/live-tokens/data/themes/royal-velvet.json +16 -16
- package/src/live-tokens/data/themes/sketchy.json +16 -16
- package/src/live-tokens/data/themes/spring-meadow.json +16 -16
- package/src/live-tokens/data/themes/sunset.json +16 -16
- package/src/system/components/Button.svelte +3 -3
- package/src/system/components/InlineEditActions.svelte +1 -1
- package/src/system/components/Input.svelte +1 -1
- package/src/system/components/MenuSelect.svelte +1 -1
- package/src/system/components/ProgressBar.svelte +1 -1
- package/src/system/components/SlidePager.svelte +1 -1
- package/src/system/components/Slider.svelte +6 -6
- package/src/system/components/TabBar.svelte +1 -1
- package/src/system/components/Toggle.svelte +1 -1
- package/src/testing-js/{chunk-RWH46IU5.js → chunk-7QXUJRSO.js} +2 -2
- package/src/testing-js/chunk-GZKUYLDW.js +193 -0
- package/src/testing-js/chunk-GZKUYLDW.js.map +1 -0
- package/src/testing-js/{chunk-SHLPDNAQ.js → chunk-RTZQLW3F.js} +172 -181
- package/src/testing-js/chunk-RTZQLW3F.js.map +1 -0
- package/src/testing-js/{chunk-5HNZHIHG.js → chunk-RWOT2XDB.js} +2 -2
- package/src/testing-js/{chunk-5HNZHIHG.js.map → chunk-RWOT2XDB.js.map} +1 -1
- package/src/testing-js/{chunk-R54I7YL6.js → chunk-THWGMSRH.js} +2 -2
- package/src/testing-js/component-a11y.contract.js +53 -23
- package/src/testing-js/component-a11y.contract.js.map +1 -1
- package/src/testing-js/component-behavior.contract.js +1 -1
- package/src/testing-js/component-editor.contract.js +3 -3
- package/src/testing-js/component-render.contract.js +3 -3
- package/src/testing-js/index.js +4 -4
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +3 -3
- package/src/testing-js/vitest.js +2 -2
- package/template/_gitignore +3 -0
- package/src/testing-js/chunk-EWSGE27V.js +0 -168
- package/src/testing-js/chunk-EWSGE27V.js.map +0 -1
- package/src/testing-js/chunk-SHLPDNAQ.js.map +0 -1
- /package/src/testing-js/{chunk-RWH46IU5.js.map → chunk-7QXUJRSO.js.map} +0 -0
- /package/src/testing-js/{chunk-R54I7YL6.js.map → chunk-THWGMSRH.js.map} +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describeTagged
|
|
3
|
+
} from "./chunk-RTZQLW3F.js";
|
|
4
|
+
|
|
5
|
+
// src/testing/support/textSpacingAndMotion.ts
|
|
6
|
+
var TEXT_SPACING_URL = "https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html";
|
|
7
|
+
var MOTION_MS = 200;
|
|
8
|
+
var MOTION_URL = "https://www.w3.org/WAI/WCAG22/Techniques/css/C39";
|
|
9
|
+
var TEXT_SPACING_RULE = "text-spacing";
|
|
10
|
+
var REDUCED_MOTION_RULE = "prefers-reduced-motion";
|
|
11
|
+
var SPACING_ATTR = "data-lt-spacing-probe";
|
|
12
|
+
var MOTION_ATTR = "data-lt-motion-probe";
|
|
13
|
+
var STILL_ID = "lt-spacing-probe-still";
|
|
14
|
+
var TOLERANCE = 1;
|
|
15
|
+
function textSpacingCss(scope) {
|
|
16
|
+
const every = scope ? `${scope}, ${scope} *` : "*";
|
|
17
|
+
const paragraphs = scope ? `${scope} p` : "p";
|
|
18
|
+
return `
|
|
19
|
+
${every} { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; }
|
|
20
|
+
${paragraphs} { margin-bottom: 2em !important; }
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
async function newUnderTextSpacing(page, observe, key, scope) {
|
|
24
|
+
const before = await observe();
|
|
25
|
+
const style = await page.addStyleTag({ content: textSpacingCss(scope) });
|
|
26
|
+
let after;
|
|
27
|
+
try {
|
|
28
|
+
after = await observe();
|
|
29
|
+
} finally {
|
|
30
|
+
await style.evaluate((node) => node.remove());
|
|
31
|
+
}
|
|
32
|
+
const known = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const failure of before) known.set(key(failure), (known.get(key(failure)) ?? 0) + 1);
|
|
34
|
+
return after.filter((failure) => {
|
|
35
|
+
const left = known.get(key(failure)) ?? 0;
|
|
36
|
+
if (left > 0) known.set(key(failure), left - 1);
|
|
37
|
+
return left === 0;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function tagInScope(page, scope, exclude, own, attr) {
|
|
41
|
+
return page.evaluate(({ scope: scope2, exclude: exclude2, own: own2, attr: attr2 }) => {
|
|
42
|
+
const found = [...document.querySelectorAll(scope2)].flatMap((root) => [root, ...root.querySelectorAll("*")]).filter((el, i, all) => all.indexOf(el) === i && el.closest(own2) && !exclude2.some((selector) => el.closest(selector)));
|
|
43
|
+
found.forEach((el, i) => el.setAttribute(attr2, String(i)));
|
|
44
|
+
return found.length;
|
|
45
|
+
}, { scope, exclude, own: own.join(", "), attr });
|
|
46
|
+
}
|
|
47
|
+
function untag(page, attr, styleId) {
|
|
48
|
+
return page.evaluate(({ attr: attr2, styleId: styleId2 }) => {
|
|
49
|
+
if (styleId2) document.getElementById(styleId2)?.remove();
|
|
50
|
+
for (const el of document.querySelectorAll(`[${attr2}]`)) el.removeAttribute(attr2);
|
|
51
|
+
}, { attr, styleId });
|
|
52
|
+
}
|
|
53
|
+
function observeOverflow(page) {
|
|
54
|
+
return page.evaluate(({ attr, tolerance }) => [...document.querySelectorAll(`[${attr}]`)].flatMap((el) => {
|
|
55
|
+
const style = getComputedStyle(el);
|
|
56
|
+
if (style.display === "none" || style.display === "contents") return [];
|
|
57
|
+
if (el.clientWidth === 0 && el.clientHeight === 0) return [];
|
|
58
|
+
if (!el.textContent?.trim()) return [];
|
|
59
|
+
const index = el.getAttribute(attr);
|
|
60
|
+
const tag = el.tagName.toLowerCase();
|
|
61
|
+
const found = [];
|
|
62
|
+
const axes = [
|
|
63
|
+
["x", el.scrollWidth, el.clientWidth, style.overflowX],
|
|
64
|
+
["y", el.scrollHeight, el.clientHeight, style.overflowY]
|
|
65
|
+
];
|
|
66
|
+
for (const [axis, content, box, overflow] of axes) {
|
|
67
|
+
if (overflow === "auto" || overflow === "scroll" || content <= box + tolerance) continue;
|
|
68
|
+
found.push({ index, axis, tag, content, box, overflow });
|
|
69
|
+
}
|
|
70
|
+
return found;
|
|
71
|
+
}), { attr: SPACING_ATTR, tolerance: TOLERANCE });
|
|
72
|
+
}
|
|
73
|
+
function groupBy(items, key) {
|
|
74
|
+
const groups = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const item of items) groups.set(key(item), [...groups.get(key(item)) ?? [], item]);
|
|
76
|
+
return groups;
|
|
77
|
+
}
|
|
78
|
+
async function scanTextSpacing(page, scope, exclude, own) {
|
|
79
|
+
await page.evaluate(({ id, scope: scope2 }) => {
|
|
80
|
+
const style = document.createElement("style");
|
|
81
|
+
style.id = id;
|
|
82
|
+
style.textContent = `${scope2} *, ${scope2} *::before, ${scope2} *::after { transition: none !important; }`;
|
|
83
|
+
document.head.append(style);
|
|
84
|
+
}, { id: STILL_ID, scope });
|
|
85
|
+
try {
|
|
86
|
+
if (await tagInScope(page, scope, exclude, own, SPACING_ATTR) === 0) return [];
|
|
87
|
+
const found = await newUnderTextSpacing(page, () => observeOverflow(page), (o) => `${o.index}|${o.axis}`, scope);
|
|
88
|
+
if (found.length === 0) return [];
|
|
89
|
+
const byElement = groupBy(found, (o) => o.index);
|
|
90
|
+
const described = await describeTagged(page, scope, exclude, SPACING_ATTR, [...byElement.keys()]);
|
|
91
|
+
return [...byElement.values()].map((overflows, i) => ({
|
|
92
|
+
rule: "a11y-text-spacing",
|
|
93
|
+
engineRule: TEXT_SPACING_RULE,
|
|
94
|
+
impact: null,
|
|
95
|
+
target: described[i].target,
|
|
96
|
+
html: described[i].html,
|
|
97
|
+
help: `Under the text spacing override, ${overflows.map((o) => `<${o.tag}> holds ${o.content}px of content in ${o.box}px of ${o.axis === "x" ? "width" : "height"}, and its overflow-${o.axis} is ${o.overflow}`).join("; ")}`,
|
|
98
|
+
helpUrl: TEXT_SPACING_URL
|
|
99
|
+
}));
|
|
100
|
+
} finally {
|
|
101
|
+
await untag(page, SPACING_ATTR, STILL_ID);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function observeMotion(page) {
|
|
105
|
+
return page.evaluate(({ attr, limit }) => {
|
|
106
|
+
const ms = (time) => parseFloat(time) * (time.trim().endsWith("ms") ? 1 : 1e3) || 0;
|
|
107
|
+
const list = (value) => value.split(/,(?![^(]*\))/).map((part) => part.trim());
|
|
108
|
+
const at = (values, i) => values[i % values.length];
|
|
109
|
+
const found = [];
|
|
110
|
+
const tagged = [...document.querySelectorAll(`[${attr}]`)].filter((el) => el.checkVisibility());
|
|
111
|
+
for (const el of tagged) {
|
|
112
|
+
const index = el.getAttribute(attr);
|
|
113
|
+
for (const pseudo of ["", "::before", "::after"]) {
|
|
114
|
+
const style = getComputedStyle(el, pseudo || null);
|
|
115
|
+
if (pseudo && (style.content === "none" || style.content === "normal" || style.display === "none")) continue;
|
|
116
|
+
const durations = list(style.animationDuration);
|
|
117
|
+
const counts = list(style.animationIterationCount);
|
|
118
|
+
const delays = list(style.animationDelay);
|
|
119
|
+
list(style.animationName).forEach((name, i) => {
|
|
120
|
+
if (name === "none") return;
|
|
121
|
+
const count = at(counts, i);
|
|
122
|
+
const end = ms(at(delays, i)) + (count === "infinite" ? Infinity : ms(at(durations, i)) * parseFloat(count));
|
|
123
|
+
if (end > limit) found.push({ index, pseudo, kind: "animation", name: name.replace(/^svelte-[a-z0-9]+-/, ""), end });
|
|
124
|
+
});
|
|
125
|
+
const transitionDurations = list(style.transitionDuration);
|
|
126
|
+
const transitionDelays = list(style.transitionDelay);
|
|
127
|
+
list(style.transitionProperty).forEach((name, i) => {
|
|
128
|
+
const end = ms(at(transitionDurations, i)) + ms(at(transitionDelays, i));
|
|
129
|
+
if (name !== "none" && end > limit) found.push({ index, pseudo, kind: "transition", name, end });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const animation of document.getAnimations()) {
|
|
134
|
+
if (animation instanceof CSSAnimation || animation instanceof CSSTransition || animation.playState !== "running") continue;
|
|
135
|
+
const effect = animation.effect;
|
|
136
|
+
if (!(effect instanceof KeyframeEffect) || !effect.target?.hasAttribute(attr)) continue;
|
|
137
|
+
const end = Number(effect.getComputedTiming().endTime ?? 0);
|
|
138
|
+
if (end > limit) {
|
|
139
|
+
found.push({ index: effect.target.getAttribute(attr), pseudo: effect.pseudoElement ?? "", kind: "script", name: animation.id, end });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return found;
|
|
143
|
+
}, { attr: MOTION_ATTR, limit: MOTION_MS });
|
|
144
|
+
}
|
|
145
|
+
var capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
146
|
+
function describeMotion({ kind, name, end }) {
|
|
147
|
+
const what = kind === "animation" ? `the animation ${name}` : kind === "transition" ? `a transition of ${name}` : `a script animation${name ? ` ${name}` : ""}`;
|
|
148
|
+
return `${what} ${Number.isFinite(end) ? `lasts ${Math.round(end)} ms` : "runs without end"}`;
|
|
149
|
+
}
|
|
150
|
+
async function scanReducedMotion(page, scope, exclude, own) {
|
|
151
|
+
try {
|
|
152
|
+
if (await tagInScope(page, scope, exclude, own, MOTION_ATTR) === 0) return [];
|
|
153
|
+
if ((await observeMotion(page)).length === 0) return [];
|
|
154
|
+
let reduced;
|
|
155
|
+
await page.emulateMedia({ reducedMotion: "reduce" });
|
|
156
|
+
try {
|
|
157
|
+
await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
|
|
158
|
+
reduced = await observeMotion(page);
|
|
159
|
+
} finally {
|
|
160
|
+
await page.emulateMedia({ reducedMotion: null });
|
|
161
|
+
}
|
|
162
|
+
if (reduced.length === 0) return [];
|
|
163
|
+
const byTarget = groupBy(reduced, (m) => `${m.index}
|
|
164
|
+
${m.pseudo}`);
|
|
165
|
+
const groups = [...byTarget.values()];
|
|
166
|
+
const described = await describeTagged(page, scope, exclude, MOTION_ATTR, groups.map((motions) => motions[0].index));
|
|
167
|
+
return groups.map((motions, i) => ({
|
|
168
|
+
result: {
|
|
169
|
+
rule: "a11y-reduced-motion",
|
|
170
|
+
engineRule: REDUCED_MOTION_RULE,
|
|
171
|
+
impact: null,
|
|
172
|
+
target: `${described[i].target}${motions[0].pseudo}`,
|
|
173
|
+
html: described[i].html,
|
|
174
|
+
help: capitalize(`${motions.map(describeMotion).join("; ")} under prefers-reduced-motion: reduce`),
|
|
175
|
+
helpUrl: MOTION_URL
|
|
176
|
+
},
|
|
177
|
+
trace: { selector: described[i].target }
|
|
178
|
+
}));
|
|
179
|
+
} finally {
|
|
180
|
+
await untag(page, MOTION_ATTR);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
TEXT_SPACING_URL,
|
|
186
|
+
MOTION_MS,
|
|
187
|
+
MOTION_URL,
|
|
188
|
+
TEXT_SPACING_RULE,
|
|
189
|
+
newUnderTextSpacing,
|
|
190
|
+
scanTextSpacing,
|
|
191
|
+
scanReducedMotion
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=chunk-GZKUYLDW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../testing/support/textSpacingAndMotion.ts"],"sourcesContent":["import type { Page } from '@playwright/test';\nimport type { A11yResult } from './a11y';\nimport { describeTagged, type CheckedResult } from './nonTextContrast';\n\nexport const TEXT_SPACING_URL = 'https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html';\n\n/** An animation that ends within this many milliseconds of its start reads as\n * a fade or a state change, which reduced motion leaves alone. */\nexport const MOTION_MS = 200;\nexport const MOTION_URL = 'https://www.w3.org/WAI/WCAG22/Techniques/css/C39';\n\nexport const TEXT_SPACING_RULE = 'text-spacing';\nexport const REDUCED_MOTION_RULE = 'prefers-reduced-motion';\n\nconst SPACING_ATTR = 'data-lt-spacing-probe';\nconst MOTION_ATTR = 'data-lt-motion-probe';\nconst STILL_ID = 'lt-spacing-probe-still';\nconst TOLERANCE = 1;\n\n/** The override SC 1.4.12 names, as the W3C's own text spacing bookmarklet\n * applies it, over the whole page or inside `scope`. */\nexport function textSpacingCss(scope?: string): string {\n const every = scope ? `${scope}, ${scope} *` : '*';\n const paragraphs = scope ? `${scope} p` : 'p';\n return `\n${every} { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; }\n${paragraphs} { margin-bottom: 2em !important; }\n`;\n}\n\n/**\n * What `observe` finds under the SC 1.4.12 override and not without it. The\n * pixel counts in a failure move under the override, so `key` names a failure\n * by what fails and where; a key found n times without the override excuses n\n * under it.\n */\nexport async function newUnderTextSpacing<T>(\n page: Page,\n observe: () => Promise<T[]>,\n key: (failure: T) => string,\n scope?: string,\n): Promise<T[]> {\n const before = await observe();\n const style = await page.addStyleTag({ content: textSpacingCss(scope) });\n let after: T[];\n try {\n after = await observe();\n } finally {\n await style.evaluate((node) => (node as Element).remove());\n }\n const known = new Map<string, number>();\n for (const failure of before) known.set(key(failure), (known.get(key(failure)) ?? 0) + 1);\n return after.filter((failure) => {\n const left = known.get(key(failure)) ?? 0;\n if (left > 0) known.set(key(failure), left - 1);\n return left === 0;\n });\n}\n\n/** Tags with its index under `attr` each element inside `scope`, outside\n * `exclude`, that is or sits inside one of `own`, and returns how many it\n * tagged. `own` names the component's root and portaled parts, so the\n * editor's scaffolding around the preview stays out. */\nfunction tagInScope(page: Page, scope: string, exclude: string[], own: string[], attr: string): Promise<number> {\n return page.evaluate(({ scope, exclude, own, attr }) => {\n const found = [...document.querySelectorAll(scope)].flatMap((root) => [root, ...root.querySelectorAll('*')])\n .filter((el, i, all) => all.indexOf(el) === i && el.closest(own) && !exclude.some((selector) => el.closest(selector)));\n found.forEach((el, i) => el.setAttribute(attr, String(i)));\n return found.length;\n }, { scope, exclude, own: own.join(', '), attr });\n}\n\nfunction untag(page: Page, attr: string, styleId?: string): Promise<void> {\n return page.evaluate(({ attr, styleId }) => {\n if (styleId) document.getElementById(styleId)?.remove();\n for (const el of document.querySelectorAll(`[${attr}]`)) el.removeAttribute(attr);\n }, { attr, styleId });\n}\n\ninterface Overflow {\n index: string;\n axis: 'x' | 'y';\n tag: string;\n content: number;\n box: number;\n overflow: string;\n}\n\n/** Each tagged element whose content runs past its box on an axis it does not\n * scroll. */\nfunction observeOverflow(page: Page): Promise<Overflow[]> {\n return page.evaluate(({ attr, tolerance }) => [...document.querySelectorAll(`[${attr}]`)].flatMap((el) => {\n const style = getComputedStyle(el);\n if (style.display === 'none' || style.display === 'contents') return [];\n // An inline box reports 0 for both, so it would read as overflowing its own zero size.\n if (el.clientWidth === 0 && el.clientHeight === 0) return [];\n // A box with no text under it holds icon glyphs, which letter spacing pads with nothing visible.\n if (!el.textContent?.trim()) return [];\n const index = el.getAttribute(attr)!;\n const tag = el.tagName.toLowerCase();\n const found: Overflow[] = [];\n const axes = [\n ['x', el.scrollWidth, el.clientWidth, style.overflowX],\n ['y', el.scrollHeight, el.clientHeight, style.overflowY],\n ] as const;\n for (const [axis, content, box, overflow] of axes) {\n if (overflow === 'auto' || overflow === 'scroll' || content <= box + tolerance) continue;\n found.push({ index, axis, tag, content, box, overflow });\n }\n return found;\n }), { attr: SPACING_ATTR, tolerance: TOLERANCE });\n}\n\nfunction groupBy<T>(items: T[], key: (item: T) => string): Map<string, T[]> {\n const groups = new Map<string, T[]>();\n for (const item of items) groups.set(key(item), [...(groups.get(key(item)) ?? []), item]);\n return groups;\n}\n\n/**\n * Decision 10: each element of the component in `scope` that overflows or\n * clips under the SC 1.4.12 override, applied inside `scope`, and did not\n * without it. A transition would leave the box at its start size, so\n * transitions stay off while the check reads.\n */\nexport async function scanTextSpacing(page: Page, scope: string, exclude: string[], own: string[]): Promise<A11yResult[]> {\n await page.evaluate(({ id, scope }) => {\n const style = document.createElement('style');\n style.id = id;\n style.textContent = `${scope} *, ${scope} *::before, ${scope} *::after { transition: none !important; }`;\n document.head.append(style);\n }, { id: STILL_ID, scope });\n try {\n if (await tagInScope(page, scope, exclude, own, SPACING_ATTR) === 0) return [];\n const found = await newUnderTextSpacing(page, () => observeOverflow(page), (o) => `${o.index}|${o.axis}`, scope);\n if (found.length === 0) return [];\n const byElement = groupBy(found, (o) => o.index);\n const described = await describeTagged(page, scope, exclude, SPACING_ATTR, [...byElement.keys()]);\n return [...byElement.values()].map((overflows, i) => ({\n rule: 'a11y-text-spacing',\n engineRule: TEXT_SPACING_RULE,\n impact: null,\n target: described[i].target,\n html: described[i].html,\n help: `Under the text spacing override, ${overflows.map((o) =>\n `<${o.tag}> holds ${o.content}px of content in ${o.box}px of ${o.axis === 'x' ? 'width' : 'height'}, `\n + `and its overflow-${o.axis} is ${o.overflow}`).join('; ')}`,\n helpUrl: TEXT_SPACING_URL,\n }));\n } finally {\n await untag(page, SPACING_ATTR, STILL_ID);\n }\n}\n\ninterface Motion {\n index: string;\n pseudo: string;\n kind: 'animation' | 'transition' | 'script';\n name: string;\n end: number;\n}\n\n/** Each tagged element's animations and transitions, and its pseudo-elements',\n * that last past `MOTION_MS`. The harness finishes every finite animation\n * when it selects a view, so the read takes each declared one at its length,\n * run or not. A script animation counts while it runs. */\nfunction observeMotion(page: Page): Promise<Motion[]> {\n return page.evaluate(({ attr, limit }) => {\n const ms = (time: string) => parseFloat(time) * (time.trim().endsWith('ms') ? 1 : 1000) || 0;\n const list = (value: string) => value.split(/,(?![^(]*\\))/).map((part) => part.trim());\n const at = (values: string[], i: number) => values[i % values.length];\n const found: Motion[] = [];\n const tagged = [...document.querySelectorAll(`[${attr}]`)].filter((el) => el.checkVisibility());\n for (const el of tagged) {\n const index = el.getAttribute(attr)!;\n for (const pseudo of ['', '::before', '::after']) {\n const style = getComputedStyle(el, pseudo || null);\n if (pseudo && (style.content === 'none' || style.content === 'normal' || style.display === 'none')) continue;\n const durations = list(style.animationDuration);\n const counts = list(style.animationIterationCount);\n const delays = list(style.animationDelay);\n list(style.animationName).forEach((name, i) => {\n if (name === 'none') return;\n const count = at(counts, i);\n const end = ms(at(delays, i)) + (count === 'infinite' ? Infinity : ms(at(durations, i)) * parseFloat(count));\n // Svelte scopes a component's keyframes with a hash its source never spells.\n if (end > limit) found.push({ index, pseudo, kind: 'animation', name: name.replace(/^svelte-[a-z0-9]+-/, ''), end });\n });\n const transitionDurations = list(style.transitionDuration);\n const transitionDelays = list(style.transitionDelay);\n list(style.transitionProperty).forEach((name, i) => {\n const end = ms(at(transitionDurations, i)) + ms(at(transitionDelays, i));\n if (name !== 'none' && end > limit) found.push({ index, pseudo, kind: 'transition', name, end });\n });\n }\n }\n for (const animation of document.getAnimations()) {\n if (animation instanceof CSSAnimation || animation instanceof CSSTransition || animation.playState !== 'running') continue;\n const effect = animation.effect;\n if (!(effect instanceof KeyframeEffect) || !effect.target?.hasAttribute(attr)) continue;\n const end = Number(effect.getComputedTiming().endTime ?? 0);\n if (end > limit) {\n found.push({ index: effect.target.getAttribute(attr)!, pseudo: effect.pseudoElement ?? '', kind: 'script', name: animation.id, end });\n }\n }\n return found;\n }, { attr: MOTION_ATTR, limit: MOTION_MS });\n}\n\nconst capitalize = (text: string) => text.charAt(0).toUpperCase() + text.slice(1);\n\nfunction describeMotion({ kind, name, end }: Motion): string {\n const what = kind === 'animation' ? `the animation ${name}`\n : kind === 'transition' ? `a transition of ${name}` : `a script animation${name ? ` ${name}` : ''}`;\n return `${what} ${Number.isFinite(end) ? `lasts ${Math.round(end)} ms` : 'runs without end'}`;\n}\n\n/**\n * Decision 11: the animations and transitions of the component in `scope`\n * that last past `MOTION_MS`, read again under `prefers-reduced-motion:\n * reduce`. Each one still that long reports. A view with none skips the\n * second read, and the emulation lifts before the check returns.\n */\nexport async function scanReducedMotion(page: Page, scope: string, exclude: string[], own: string[]): Promise<CheckedResult[]> {\n try {\n if (await tagInScope(page, scope, exclude, own, MOTION_ATTR) === 0) return [];\n if ((await observeMotion(page)).length === 0) return [];\n let reduced: Motion[];\n await page.emulateMedia({ reducedMotion: 'reduce' });\n try {\n // A script that listens for the preference reacts on a later frame.\n await page.evaluate(() => new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));\n reduced = await observeMotion(page);\n } finally {\n await page.emulateMedia({ reducedMotion: null });\n }\n if (reduced.length === 0) return [];\n const byTarget = groupBy(reduced, (m) => `${m.index}\\n${m.pseudo}`);\n const groups = [...byTarget.values()];\n const described = await describeTagged(page, scope, exclude, MOTION_ATTR, groups.map((motions) => motions[0].index));\n return groups.map((motions, i) => ({\n result: {\n rule: 'a11y-reduced-motion',\n engineRule: REDUCED_MOTION_RULE,\n impact: null,\n target: `${described[i].target}${motions[0].pseudo}`,\n html: described[i].html,\n help: capitalize(`${motions.map(describeMotion).join('; ')} under prefers-reduced-motion: reduce`),\n helpUrl: MOTION_URL,\n },\n trace: { selector: described[i].target },\n }));\n } finally {\n await untag(page, MOTION_ATTR);\n }\n}\n"],"mappings":"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","names":["scope","exclude","own","attr","styleId"]}
|