@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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { issueTotals,
|
|
2
|
+
import { issueTotals, type A11yComponentRow, type A11yIssueTotals } from './a11yView';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
rows: A11yComponentRow[];
|
|
6
6
|
onjump: (component: string) => void;
|
|
7
|
+
/** Scrolls to the By eye list. */
|
|
8
|
+
onreview?: () => void;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
let { rows, onjump }: Props = $props();
|
|
11
|
+
let { rows, onjump, onreview }: Props = $props();
|
|
10
12
|
|
|
11
13
|
const KINDS: { key: keyof A11yIssueTotals; label: string; column: string }[] = [
|
|
12
14
|
{ key: 'property', label: 'Fix with a property', column: 'Property' },
|
|
@@ -14,15 +16,25 @@
|
|
|
14
16
|
{ key: 'review', label: 'Check by eye', column: 'By eye' },
|
|
15
17
|
];
|
|
16
18
|
|
|
17
|
-
let tallies = $derived(
|
|
19
|
+
let tallies = $derived(
|
|
20
|
+
rows
|
|
21
|
+
.map((row) => ({ row, totals: issueTotals(row.issues) }))
|
|
22
|
+
.sort(
|
|
23
|
+
(a, b) =>
|
|
24
|
+
b.totals.property - a.totals.property ||
|
|
25
|
+
b.totals.code - a.totals.code ||
|
|
26
|
+
a.row.label.localeCompare(b.row.label),
|
|
27
|
+
),
|
|
28
|
+
);
|
|
18
29
|
let sums = $derived(
|
|
19
30
|
tallies.reduce(
|
|
20
31
|
(acc, { totals }) => ({ property: acc.property + totals.property, code: acc.code + totals.code, review: acc.review + totals.review }),
|
|
21
32
|
{ property: 0, code: 0, review: 0 },
|
|
22
33
|
),
|
|
23
34
|
);
|
|
24
|
-
let total = $derived(sums.property + sums.code
|
|
25
|
-
let
|
|
35
|
+
let total = $derived(sums.property + sums.code);
|
|
36
|
+
let withIssues = $derived(rows.filter((row) => row.count > 0).length);
|
|
37
|
+
let fixed = $derived(rows.reduce((sum, row) => sum + row.issues.fixed.length, 0));
|
|
26
38
|
</script>
|
|
27
39
|
|
|
28
40
|
<section class="editor-section-card a11y-overview" aria-label="Overview">
|
|
@@ -30,17 +42,30 @@
|
|
|
30
42
|
<div class="a11y-stat a11y-stat-total">
|
|
31
43
|
<span class="a11y-stat-value">{total}</span>
|
|
32
44
|
<span class="a11y-stat-label">
|
|
33
|
-
{total === 1 ? 'issue' : 'issues'} in {
|
|
45
|
+
{total === 1 ? 'issue' : 'issues'} in {withIssues} {withIssues === 1 ? 'component' : 'components'}
|
|
34
46
|
</span>
|
|
35
47
|
</div>
|
|
36
48
|
{#each KINDS as kind (kind.key)}
|
|
37
|
-
|
|
38
|
-
<
|
|
49
|
+
{#if kind.key === 'review' && onreview}
|
|
50
|
+
<button type="button" class="a11y-stat a11y-stat-jump" onclick={onreview}>
|
|
51
|
+
<span class="a11y-stat-value" class:zero={sums[kind.key] === 0}>{sums[kind.key]}</span>
|
|
52
|
+
<span class="a11y-stat-label">{kind.label} <i class="fas fa-arrow-down" aria-hidden="true"></i></span>
|
|
53
|
+
</button>
|
|
54
|
+
{:else}
|
|
55
|
+
<div class="a11y-stat">
|
|
56
|
+
<span class="a11y-stat-value" class:zero={sums[kind.key] === 0}>{sums[kind.key]}</span>
|
|
57
|
+
<span class="a11y-stat-label">{kind.label}</span>
|
|
58
|
+
</div>
|
|
59
|
+
{/if}
|
|
60
|
+
{/each}
|
|
61
|
+
{#if fixed > 0}
|
|
62
|
+
<div class="a11y-stat a11y-stat-fixed">
|
|
63
|
+
<span class="a11y-stat-value">{fixed}</span>
|
|
39
64
|
<span class="a11y-stat-label">
|
|
40
|
-
<
|
|
65
|
+
<i class="fa-solid fa-check" aria-hidden="true"></i>Fixed since the run
|
|
41
66
|
</span>
|
|
42
67
|
</div>
|
|
43
|
-
{/
|
|
68
|
+
{/if}
|
|
44
69
|
</div>
|
|
45
70
|
|
|
46
71
|
<table class="a11y-table">
|
|
@@ -50,7 +75,9 @@
|
|
|
50
75
|
{#each KINDS as kind (kind.key)}
|
|
51
76
|
<th scope="col" class="num">{kind.column}</th>
|
|
52
77
|
{/each}
|
|
53
|
-
|
|
78
|
+
{#if fixed > 0}
|
|
79
|
+
<th scope="col" class="num">Fixed</th>
|
|
80
|
+
{/if}
|
|
54
81
|
</tr>
|
|
55
82
|
</thead>
|
|
56
83
|
<tbody>
|
|
@@ -64,15 +91,9 @@
|
|
|
64
91
|
{#each KINDS as kind (kind.key)}
|
|
65
92
|
<td class="num" class:zero={totals[kind.key] === 0}>{totals[kind.key]}</td>
|
|
66
93
|
{/each}
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
{#if totals[kind.key] > 0}
|
|
71
|
-
<span class="a11y-bar-part {kind.key}" style:flex-grow={totals[kind.key]}></span>
|
|
72
|
-
{/if}
|
|
73
|
-
{/each}
|
|
74
|
-
</span>
|
|
75
|
-
</td>
|
|
94
|
+
{#if fixed > 0}
|
|
95
|
+
<td class="num fixed" class:zero={row.issues.fixed.length === 0}>{row.issues.fixed.length}</td>
|
|
96
|
+
{/if}
|
|
76
97
|
</tr>
|
|
77
98
|
{/each}
|
|
78
99
|
</tbody>
|
|
@@ -98,6 +119,33 @@
|
|
|
98
119
|
border-left: 1px solid var(--ui-border-low);
|
|
99
120
|
}
|
|
100
121
|
|
|
122
|
+
.a11y-stat-jump {
|
|
123
|
+
margin: 0;
|
|
124
|
+
background: none;
|
|
125
|
+
border-top: none;
|
|
126
|
+
border-right: none;
|
|
127
|
+
border-bottom: none;
|
|
128
|
+
font: inherit;
|
|
129
|
+
text-align: left;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.a11y-stat-jump i {
|
|
134
|
+
font-size: var(--ui-font-size-xs);
|
|
135
|
+
opacity: 0;
|
|
136
|
+
transition: opacity var(--ui-transition-fast);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.a11y-stat-jump:hover .a11y-stat-label,
|
|
140
|
+
.a11y-stat-jump:focus-visible .a11y-stat-label {
|
|
141
|
+
color: var(--ui-text-primary);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.a11y-stat-jump:hover i,
|
|
145
|
+
.a11y-stat-jump:focus-visible i {
|
|
146
|
+
opacity: 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
101
149
|
.a11y-stat-total {
|
|
102
150
|
padding-left: 0;
|
|
103
151
|
border-left: none;
|
|
@@ -127,23 +175,10 @@
|
|
|
127
175
|
color: var(--ui-text-muted);
|
|
128
176
|
}
|
|
129
177
|
|
|
130
|
-
.a11y-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
border-radius: var(--ui-radius-full);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.property {
|
|
138
|
-
background: var(--ui-text-primary);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.code {
|
|
142
|
-
background: var(--ui-border-high);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.review {
|
|
146
|
-
border: 1px solid var(--ui-border-high);
|
|
178
|
+
.a11y-stat-fixed .a11y-stat-value,
|
|
179
|
+
.a11y-stat-fixed i,
|
|
180
|
+
.a11y-table td.fixed:not(.zero) {
|
|
181
|
+
color: var(--ui-fixed);
|
|
147
182
|
}
|
|
148
183
|
|
|
149
184
|
.a11y-table {
|
|
@@ -185,10 +220,6 @@
|
|
|
185
220
|
color: var(--ui-text-muted);
|
|
186
221
|
}
|
|
187
222
|
|
|
188
|
-
.bar-col {
|
|
189
|
-
width: 40%;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
223
|
.a11y-jump {
|
|
193
224
|
display: inline-flex;
|
|
194
225
|
align-items: center;
|
|
@@ -212,31 +243,4 @@
|
|
|
212
243
|
text-decoration: underline;
|
|
213
244
|
text-underline-offset: var(--ui-space-2);
|
|
214
245
|
}
|
|
215
|
-
|
|
216
|
-
.a11y-bar {
|
|
217
|
-
display: flex;
|
|
218
|
-
gap: var(--ui-space-2);
|
|
219
|
-
height: var(--ui-space-8);
|
|
220
|
-
min-width: var(--ui-space-8);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.a11y-bar-part {
|
|
224
|
-
flex-basis: 0;
|
|
225
|
-
border-radius: var(--ui-radius-full);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
.a11y-hidden {
|
|
229
|
-
position: absolute;
|
|
230
|
-
width: 1px;
|
|
231
|
-
height: 1px;
|
|
232
|
-
overflow: hidden;
|
|
233
|
-
clip-path: inset(50%);
|
|
234
|
-
white-space: nowrap;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
@media (max-width: 600px) {
|
|
238
|
-
.bar-col {
|
|
239
|
-
display: none;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
246
|
</style>
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
import { FLASH_DURATION_MS } from '../../core/flashStatus';
|
|
5
5
|
import { getEditorContext, type RevealRequest } from '../../component-editor/scaffolding/editorContext';
|
|
6
6
|
import A11yComponentIssues from './A11yComponentIssues.svelte';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
accessibilityIssuesLabel,
|
|
9
|
+
fixedLabel,
|
|
10
|
+
formatRunTime,
|
|
11
|
+
issueCount,
|
|
12
|
+
pendingA11yReveal,
|
|
13
|
+
reviewLabel,
|
|
14
|
+
} from './a11yView';
|
|
8
15
|
|
|
9
16
|
let { component }: { component: string } = $props();
|
|
10
17
|
const titleId = $props.id();
|
|
@@ -14,6 +21,8 @@
|
|
|
14
21
|
let panel: HTMLElement | undefined = $state();
|
|
15
22
|
let issues = $derived($a11yIssues[component]);
|
|
16
23
|
let count = $derived(issues ? issueCount(issues) : 0);
|
|
24
|
+
let fixedCount = $derived(issues?.fixed.length ?? 0);
|
|
25
|
+
let reviewCount = $derived(issues?.review.length ?? 0);
|
|
17
26
|
|
|
18
27
|
/** Rendered rows for `variable`. The linked block can repeat a variant's row, so the variant's own row comes first. */
|
|
19
28
|
function renderedRows(variable: string): HTMLElement[] {
|
|
@@ -70,11 +79,19 @@
|
|
|
70
79
|
});
|
|
71
80
|
</script>
|
|
72
81
|
|
|
73
|
-
{#if issues && count > 0 && $a11yFinishedReport}
|
|
82
|
+
{#if issues && count + fixedCount + reviewCount > 0 && $a11yFinishedReport}
|
|
74
83
|
<section class="editor-section-card a11y-panel" aria-labelledby={titleId} bind:this={panel}>
|
|
75
84
|
<header class="a11y-panel-head">
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
{#if count > 0}
|
|
86
|
+
<i class="fa-solid fa-triangle-exclamation a11y-panel-mark" aria-hidden="true"></i>
|
|
87
|
+
<h3 id={titleId} class="a11y-panel-title">{accessibilityIssuesLabel(count)}</h3>
|
|
88
|
+
{:else if reviewCount > 0}
|
|
89
|
+
<i class="fa-solid fa-eye a11y-panel-mark review" aria-hidden="true"></i>
|
|
90
|
+
<h3 id={titleId} class="a11y-panel-title">{reviewLabel(reviewCount)}</h3>
|
|
91
|
+
{:else}
|
|
92
|
+
<i class="fa-solid fa-check a11y-panel-mark fixed" aria-hidden="true"></i>
|
|
93
|
+
<h3 id={titleId} class="a11y-panel-title">{fixedLabel(fixedCount)}</h3>
|
|
94
|
+
{/if}
|
|
78
95
|
<span class="a11y-panel-run">Last run {formatRunTime($a11yFinishedReport.finishedAt)}</span>
|
|
79
96
|
</header>
|
|
80
97
|
<A11yComponentIssues {component} {issues} withReview onreveal={reveal} />
|
|
@@ -113,6 +130,14 @@
|
|
|
113
130
|
color: var(--ui-highlight);
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
.a11y-panel-mark.fixed {
|
|
134
|
+
color: var(--ui-fixed);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.a11y-panel-mark.review {
|
|
138
|
+
color: var(--ui-text-tertiary);
|
|
139
|
+
}
|
|
140
|
+
|
|
116
141
|
.a11y-panel-title {
|
|
117
142
|
margin: 0;
|
|
118
143
|
font-size: var(--ui-font-size-lg);
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
let { variables, placement = 'inline' }: Props = $props();
|
|
14
14
|
|
|
15
15
|
let findings = $derived([...new Set(variables.flatMap((variable) => $a11yPropertyFindings.get(variable) ?? []))]);
|
|
16
|
-
let
|
|
17
|
-
let
|
|
16
|
+
let reviewCount = $derived(findings.filter((finding) => finding.rule === 'a11y-review').length);
|
|
17
|
+
let review = $derived(reviewCount === findings.length);
|
|
18
|
+
let label = $derived([
|
|
19
|
+
review ? '' : accessibilityIssuesLabel(findings.length - reviewCount),
|
|
20
|
+
reviewCount > 0 ? reviewLabel(reviewCount) : '',
|
|
21
|
+
].filter(Boolean).join(', '));
|
|
18
22
|
let rows = $derived(findingRows([
|
|
19
23
|
...findings.filter((finding) => finding.rule !== 'a11y-review'),
|
|
20
24
|
...findings.filter((finding) => finding.rule === 'a11y-review'),
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
<UIInfoPopover title={label} ariaLabel={label}>
|
|
27
31
|
{#snippet trigger()}
|
|
28
32
|
{#if review}
|
|
29
|
-
<
|
|
33
|
+
<i class="fa-solid fa-eye a11y-property-mark-glyph review" aria-hidden="true"></i>
|
|
30
34
|
{:else}
|
|
31
35
|
<i class="fa-solid fa-triangle-exclamation a11y-property-mark-glyph" aria-hidden="true"></i>
|
|
32
36
|
{/if}
|
|
@@ -63,12 +67,8 @@
|
|
|
63
67
|
font-size: var(--ui-font-size-xs);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
.a11y-property-mark-glyph.review::after {
|
|
68
|
-
content: '?';
|
|
70
|
+
.a11y-property-mark-glyph.review {
|
|
69
71
|
color: var(--ui-text-tertiary);
|
|
70
|
-
font-size: var(--ui-font-size-sm);
|
|
71
|
-
font-weight: var(--ui-font-weight-bold);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
.a11y-property-findings {
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
interface Props {
|
|
3
3
|
/** `corner` overlaps the icon of a condensed rail, where the label and its inline mark are hidden. */
|
|
4
4
|
placement?: 'inline' | 'corner';
|
|
5
|
+
/** Draws the eye of a component whose only results are to check by eye. */
|
|
6
|
+
review?: boolean;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
let { placement = 'inline' }: Props = $props();
|
|
8
|
-
|
|
9
|
+
let { placement = 'inline', review = false }: Props = $props();
|
|
10
|
+
let label = $derived(review ? 'Results to check by eye' : 'Accessibility issues');
|
|
11
|
+
let glyph = $derived(review ? 'fa-eye' : 'fa-triangle-exclamation');
|
|
9
12
|
</script>
|
|
10
13
|
|
|
11
14
|
{#if placement === 'inline'}
|
|
12
|
-
<i class="fa-solid
|
|
15
|
+
<i class="fa-solid {glyph} a11y-rail-mark" class:review role="img" aria-label={label} title={label}></i>
|
|
13
16
|
{:else}
|
|
14
|
-
<i class="fa-solid
|
|
17
|
+
<i class="fa-solid {glyph} a11y-rail-mark corner" class:review aria-hidden="true"></i>
|
|
15
18
|
{/if}
|
|
16
19
|
|
|
17
20
|
<style>
|
|
@@ -21,6 +24,10 @@
|
|
|
21
24
|
font-size: var(--ui-font-size-sm);
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
.review {
|
|
28
|
+
color: var(--ui-text-tertiary);
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
/* Bottom-right of the icon, clear of the unsaved dot at its top-right. */
|
|
25
32
|
.corner {
|
|
26
33
|
position: absolute;
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
import { unsavedEdits } from '../../core/store/unsavedEdits';
|
|
7
7
|
import { listThemes } from '../../core/themes/themeService';
|
|
8
8
|
import UIPillButton from '../UIPillButton.svelte';
|
|
9
|
+
import A11yByEye from './A11yByEye.svelte';
|
|
9
10
|
import A11yComponentIssues from './A11yComponentIssues.svelte';
|
|
10
|
-
import A11yFindingRow from './A11yFindingRow.svelte';
|
|
11
11
|
import A11yOverview from './A11yOverview.svelte';
|
|
12
12
|
import {
|
|
13
13
|
A11Y_REVIEW_SECTION,
|
|
14
14
|
A11Y_RUN_SECTION,
|
|
15
15
|
a11ySectionId,
|
|
16
16
|
codeSpans,
|
|
17
|
-
|
|
17
|
+
componentSections,
|
|
18
|
+
componentsInReport,
|
|
19
|
+
fixedLabel,
|
|
18
20
|
formatElapsed,
|
|
19
21
|
formatRunTime,
|
|
20
22
|
getA11yViewLink,
|
|
@@ -26,9 +28,9 @@
|
|
|
26
28
|
|
|
27
29
|
const openView = getA11yViewLink();
|
|
28
30
|
|
|
29
|
-
let
|
|
31
|
+
let tableRows = $derived(componentsInReport($a11yIssues));
|
|
32
|
+
let sectionRows = $derived(componentSections($a11yIssues));
|
|
30
33
|
let review = $derived(reviewResults($a11yIssues));
|
|
31
|
-
let reviewTotal = $derived(rows.reduce((sum, row) => sum + row.issues.review.length, 0));
|
|
32
34
|
let running = $derived($a11yReport?.state === 'running' ? $a11yReport : null);
|
|
33
35
|
let failed = $derived($a11yReport?.state === 'failed' ? $a11yReport : null);
|
|
34
36
|
let finished = $derived($a11yFinishedReport);
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
</div>
|
|
129
131
|
{/if}
|
|
130
132
|
{/if}
|
|
131
|
-
{#if !notInstalled &&
|
|
133
|
+
{#if !notInstalled && sectionRows.length === 0}
|
|
132
134
|
<p class="a11y-summary">Every component passes the automated checks.</p>
|
|
133
135
|
{/if}
|
|
134
136
|
{:else if !running && !failed}
|
|
@@ -137,21 +139,28 @@
|
|
|
137
139
|
Run test checks every component in the open theme as saved. It scans each one with axe and Alfa,
|
|
138
140
|
presses the keys each one answers, and measures the contrast of every color pair.
|
|
139
141
|
</p>
|
|
140
|
-
<p>A run takes about three minutes.
|
|
142
|
+
<p>A run takes about three minutes. Each theme keeps its report until its next run.</p>
|
|
141
143
|
</div>
|
|
142
144
|
{/if}
|
|
143
145
|
</section>
|
|
144
146
|
|
|
145
|
-
{#if
|
|
146
|
-
<A11yOverview
|
|
147
|
+
{#if tableRows.length > 0}
|
|
148
|
+
<A11yOverview
|
|
149
|
+
rows={tableRows}
|
|
150
|
+
onjump={(component) => openView?.(a11ySectionId(component))}
|
|
151
|
+
onreview={() => openView?.(A11Y_REVIEW_SECTION)}
|
|
152
|
+
/>
|
|
147
153
|
{/if}
|
|
148
154
|
|
|
149
|
-
{#each
|
|
155
|
+
{#each sectionRows as row (row.component)}
|
|
150
156
|
<section id={a11ySectionId(row.component)} class="editor-section-card a11y-component">
|
|
151
157
|
<header class="a11y-component-head">
|
|
152
158
|
<i class={row.icon} aria-hidden="true"></i>
|
|
153
159
|
<h3 class="editor-subsection-title">{row.label}</h3>
|
|
154
|
-
<span class="a11y-count">{
|
|
160
|
+
<span class="a11y-count">{[
|
|
161
|
+
row.count > 0 ? issuesLabel(row.count) : '',
|
|
162
|
+
row.issues.fixed.length > 0 ? fixedLabel(row.issues.fixed.length) : '',
|
|
163
|
+
].filter(Boolean).join(', ')}</span>
|
|
155
164
|
<UIPillButton
|
|
156
165
|
size="compact"
|
|
157
166
|
variant="outline"
|
|
@@ -166,32 +175,16 @@
|
|
|
166
175
|
onreveal={(variable) => openComponentAt(row.component, variable)}
|
|
167
176
|
onopen={() => openComponentAt(row.component)}
|
|
168
177
|
/>
|
|
169
|
-
{#if row.
|
|
178
|
+
{#if row.review > 0}
|
|
170
179
|
<button type="button" class="a11y-review-link" onclick={() => openView?.(A11Y_REVIEW_SECTION)}>
|
|
171
|
-
{reviewLabel(row.
|
|
180
|
+
{reviewLabel(row.review)}
|
|
172
181
|
</button>
|
|
173
182
|
{/if}
|
|
174
183
|
</section>
|
|
175
184
|
{/each}
|
|
176
185
|
|
|
177
186
|
{#if review.length > 0}
|
|
178
|
-
<
|
|
179
|
-
<details class="a11y-disclosure">
|
|
180
|
-
<summary>{reviewLabel(reviewTotal)}</summary>
|
|
181
|
-
<ul class="a11y-review-list">
|
|
182
|
-
{#each review as { component, label, row }, i (i)}
|
|
183
|
-
<A11yFindingRow
|
|
184
|
-
{component}
|
|
185
|
-
{row}
|
|
186
|
-
review
|
|
187
|
-
prefix={label}
|
|
188
|
-
onreveal={(variable) => openComponentAt(component, variable)}
|
|
189
|
-
onopen={() => openComponentAt(component)}
|
|
190
|
-
/>
|
|
191
|
-
{/each}
|
|
192
|
-
</ul>
|
|
193
|
-
</details>
|
|
194
|
-
</section>
|
|
187
|
+
<A11yByEye />
|
|
195
188
|
{/if}
|
|
196
189
|
</div>
|
|
197
190
|
|
|
@@ -321,12 +314,6 @@
|
|
|
321
314
|
color: var(--ui-text-primary);
|
|
322
315
|
}
|
|
323
316
|
|
|
324
|
-
.a11y-review-list {
|
|
325
|
-
margin: var(--ui-space-8) 0 0;
|
|
326
|
-
padding: 0;
|
|
327
|
-
list-style: none;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
317
|
.a11y-tab code {
|
|
331
318
|
font-family: var(--ui-font-mono);
|
|
332
319
|
font-size: var(--ui-font-size-sm);
|
|
@@ -84,18 +84,24 @@ export function accessibilityIssuesLabel(count: number): string {
|
|
|
84
84
|
return `${count} accessibility ${count === 1 ? 'issue' : 'issues'}`;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
export function fixedLabel(count: number): string {
|
|
88
|
+
return `${count} ${count === 1 ? 'issue' : 'issues'} fixed`;
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
export function reviewLabel(count: number): string {
|
|
88
92
|
return `${count} ${count === 1 ? 'result' : 'results'} to check by eye`;
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
/**
|
|
95
|
+
/** The findings a fix clears. A result to check by eye counts apart, since only a person can settle it. */
|
|
92
96
|
export function issueCount(issues: ComponentA11yIssues): number {
|
|
93
|
-
return issues.contrast.length + issues.property.length + issues.code.length
|
|
97
|
+
return issues.contrast.length + issues.property.length + issues.code.length;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
/** One panel row: the findings that share an engine rule, a help text and the properties they trace to. */
|
|
97
101
|
export interface A11yFindingRow {
|
|
98
102
|
finding: A11yFinding;
|
|
103
|
+
/** Every finding the row merges, `finding` first. */
|
|
104
|
+
findings: A11yFinding[];
|
|
99
105
|
targets: string[];
|
|
100
106
|
views: string;
|
|
101
107
|
variables: string[];
|
|
@@ -131,6 +137,7 @@ export function findingRows(findings: A11yFinding[]): A11yFindingRow[] {
|
|
|
131
137
|
}
|
|
132
138
|
return [...rows.values()].map((group) => ({
|
|
133
139
|
finding: group[0],
|
|
140
|
+
findings: group,
|
|
134
141
|
targets: [...new Set(group.flatMap((f) => (f.details ? [f.details.target] : [])))],
|
|
135
142
|
views: group.length === 1 ? (group[0].details?.view ?? '') : mergeViews(group.map((f) => f.details?.view)),
|
|
136
143
|
variables: group[0].details?.variables ?? [],
|
|
@@ -155,24 +162,56 @@ export interface A11yComponentRow {
|
|
|
155
162
|
component: string;
|
|
156
163
|
label: string;
|
|
157
164
|
icon: string;
|
|
165
|
+
/** `issueCount`: the findings a fix clears. */
|
|
158
166
|
count: number;
|
|
167
|
+
/** The results to check by eye no one has marked. */
|
|
168
|
+
review: number;
|
|
159
169
|
issues: ComponentA11yIssues;
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
|
|
163
|
-
|
|
172
|
+
function componentRows(
|
|
173
|
+
all: Record<string, ComponentA11yIssues>,
|
|
174
|
+
listed: (row: A11yComponentRow) => boolean,
|
|
175
|
+
): A11yComponentRow[] {
|
|
164
176
|
return getComponentRegistryEntries().flatMap(({ id, label, icon }) => {
|
|
165
177
|
const issues = all[id];
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
if (!issues) return [];
|
|
179
|
+
const row = { component: id, label, icon, count: issueCount(issues), review: issues.review.length, issues };
|
|
180
|
+
return listed(row) ? [row] : [];
|
|
168
181
|
});
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
/** Every result to check by eye,
|
|
172
|
-
export function
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
/** Every registered component with an issue or an unmarked result to check by eye, in the component rail's order. */
|
|
185
|
+
export function componentsWithIssues(all: Record<string, ComponentA11yIssues>): A11yComponentRow[] {
|
|
186
|
+
return componentRows(all, (row) => row.count > 0 || row.review > 0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Every registered component the overview table lists: an issue, a fix, or an unmarked result to check by eye.
|
|
190
|
+
* A result marked as looking right is acknowledged, so it alone lists nothing. */
|
|
191
|
+
export function componentsInReport(all: Record<string, ComponentA11yIssues>): A11yComponentRow[] {
|
|
192
|
+
return componentRows(all, (row) => row.count > 0 || row.review > 0 || row.issues.fixed.length > 0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Every component with its own section in the Accessibility view: an issue or a fix. The By eye list holds the rest. */
|
|
196
|
+
export function componentSections(all: Record<string, ComponentA11yIssues>): A11yComponentRow[] {
|
|
197
|
+
return componentRows(all, (row) => row.count > 0 || row.issues.fixed.length > 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface A11yReviewResult {
|
|
201
|
+
component: string;
|
|
202
|
+
label: string;
|
|
203
|
+
row: A11yFindingRow;
|
|
204
|
+
confirmed: boolean;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Every result to check by eye, merged into rows per component in the rail's order, the unmarked ones first. */
|
|
208
|
+
export function reviewResults(all: Record<string, ComponentA11yIssues>): A11yReviewResult[] {
|
|
209
|
+
const entries = getComponentRegistryEntries();
|
|
210
|
+
const results = (confirmed: boolean) =>
|
|
211
|
+
entries.flatMap(({ id, label }) =>
|
|
212
|
+
findingRows((confirmed ? all[id]?.confirmed : all[id]?.review) ?? []).map((row) => ({ component: id, label, row, confirmed })),
|
|
213
|
+
);
|
|
214
|
+
return [...results(false), ...results(true)];
|
|
176
215
|
}
|
|
177
216
|
|
|
178
217
|
/** The words of `variable` after the component's prefix that `label` lacks,
|