Skip to content

Commit cee4351

Browse files
authored
Fixes from accessibility audit. (#344)
* Improve accessibility of app. * remove user-scalable=no from html head * add aria-label to builds page elements * hide build table columns to prevent horizontal scrolling * display error when map URL can't be loaded in viewer * add accessible labels to theme and language select elements * stateful text display on show JSON button * bump maplibre-gl-inspect to 4.7.1 for accessible toggle button
1 parent a65c962 commit cee4351

File tree

7 files changed

+93
-59
lines changed

7 files changed

+93
-59
lines changed

app/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>Protomaps Basemaps</title>
77
<link rel="preconnect" href="https://demo-bucket.protomaps.com"/>
88
</head>

app/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"format": "biome format --write src test --javascript-formatter-indent-style=space --json-formatter-indent-style=space"
1313
},
1414
"dependencies": {
15-
"@maplibre/maplibre-gl-inspect": "^1.7.0",
15+
"@maplibre/maplibre-gl-inspect": "^1.7.1",
1616
"maplibre-gl": "5.0.0",
1717
"pixelmatch": "^5.3.0",
1818
"pmtiles": "^4.1.0",

app/src/Builds.tsx

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ interface Build {
1212
version: string;
1313
}
1414

15-
function isMonday(dateStr: string): boolean {
15+
function toDate(dateStr: string): Date {
1616
const year = Number.parseInt(dateStr.substring(0, 4), 10);
1717
const month = Number.parseInt(dateStr.substring(4, 6), 10) - 1; // Subtract 1 because months are 0-indexed in JavaScript dates
1818
const day = Number.parseInt(dateStr.substring(6, 8), 10);
19-
const date = new Date(year, month, day);
20-
return date.getDay() === 1;
19+
return new Date(year, month, day);
2120
}
2221

2322
function formatBytes(bytes: number, decimals = 2) {
@@ -48,9 +47,10 @@ function BuildComponent(props: {
4847
setCmpB: (i: number) => void;
4948
}) {
5049
const build = props.build;
50+
const dateStr = build.key.substr(0, 8);
51+
const date = toDate(dateStr);
5152
const link = `https://build.protomaps.com/${build.key}`;
52-
const date = build.key.substr(0, 8);
53-
const statsLink = `https://build.protomaps.com/${date}.layerstats.parquet`;
53+
const statsLink = `https://build.protomaps.com/${dateStr}.layerstats.parquet`;
5454
const idx = props.idx;
5555

5656
const onChangeA = () => {
@@ -62,31 +62,28 @@ function BuildComponent(props: {
6262
};
6363

6464
return (
65-
<tr style={{ color: isMonday(date) ? "black" : "#aaa" }}>
65+
<tr style={{ color: date.getDay() === 1 ? "black" : "#aaa" }}>
6666
<td>
67-
<span style={{ display: "inline-block", width: "20px" }}>
68-
{idx > props.cmpB && (
69-
<input
70-
type="radio"
71-
onChange={onChangeA}
72-
checked={idx === props.cmpA}
73-
/>
74-
)}
75-
</span>
76-
<span style={{ display: "inline-block", width: "20px" }}>
77-
{idx < props.cmpA && (
78-
<input
79-
type="radio"
80-
onChange={onChangeB}
81-
checked={idx === props.cmpB}
82-
/>
83-
)}
84-
</span>
67+
<input
68+
disabled={idx <= props.cmpB}
69+
type="radio"
70+
onChange={onChangeA}
71+
checked={idx === props.cmpA}
72+
aria-label={`compare earlier build ${date.toDateString()}`}
73+
/>
74+
<input
75+
class="ml-2"
76+
disabled={idx >= props.cmpA}
77+
type="radio"
78+
onChange={onChangeB}
79+
checked={idx === props.cmpB}
80+
aria-label={`compare later build ${date.toDateString()}`}
81+
/>
8582
</td>
8683
<td>{build.key}</td>
8784
<td>{build.version}</td>
88-
<td>{formatBytes(build.size)}</td>
89-
<td>{build.uploaded}</td>
85+
<td class="hidden lg:table-cell">{formatBytes(build.size)}</td>
86+
<td class="hidden lg:table-cell">{build.uploaded}</td>
9087
<td>
9188
<a class="underline" href={`/#tiles=${link}`}>
9289
map
@@ -100,13 +97,13 @@ function BuildComponent(props: {
10097
xray
10198
</a>
10299
</td>
103-
<td>
100+
<td class="hidden lg:table-cell">
104101
<a class="underline" href={link}>
105102
download
106103
</a>
107104
</td>
108-
<td>
109-
{date >= "20231228" ? (
105+
<td class="hidden lg:table-cell">
106+
{dateStr >= "20231228" ? (
110107
<a class="underline" href={statsLink}>
111108
stats
112109
</a>
@@ -193,15 +190,15 @@ function Builds() {
193190
<p>Only Monday builds (black) are kept indefinitely.</p>
194191
<div class="space-x-2 my-2">
195192
<button class="btn-primary" type="button" onClick={openVisualTests}>
196-
Compare visual tests
193+
Compare selected versions
197194
</button>
198195
{latestStyle() ? (
199196
<button class="btn-primary" type="button" onClick={openMaperture}>
200-
Compare in Maperture (style {latestStyle()})
197+
Compare in Maperture
201198
</button>
202199
) : null}
203200
</div>
204-
<table class="table-auto border-separate border-spacing-4 font-mono">
201+
<table class="table-auto border-separate text-xs lg:text-base border-spacing-2 lg:border-spacing-4 font-mono">
205202
<tbody>
206203
<For each={builds()}>
207204
{(build, idx) => (

app/src/MapView.tsx

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ function MapLibreView(props: {
188188
let protocolRef: Protocol | undefined;
189189
let hiddenRef: HTMLDivElement | undefined;
190190

191+
const [error, setError] = createSignal<string | undefined>();
192+
191193
onMount(() => {
192194
if (getRTLTextPluginStatus() === "unavailable") {
193195
setRTLTextPlugin(
@@ -241,6 +243,14 @@ function MapLibreView(props: {
241243
maxWidth: "none",
242244
});
243245

246+
map.on("error", (e) => {
247+
setError(e.error.message);
248+
});
249+
250+
map.on("idle", () => {
251+
setError(undefined);
252+
});
253+
244254
map.on("contextmenu", (e) => {
245255
const features = map.queryRenderedFeatures(e.point);
246256
if (hiddenRef && features.length) {
@@ -318,6 +328,11 @@ function MapLibreView(props: {
318328
<>
319329
<div class="hidden" ref={hiddenRef} />
320330
<div ref={mapContainer} class="h-100 w-full flex" />
331+
<Show when={error()}>
332+
<div class="absolute h-20 w-full flex justify-center items-center bg-white bg-opacity-50 font-mono text-red">
333+
{error()}
334+
</div>
335+
</Show>
321336
</>
322337
);
323338
}
@@ -421,33 +436,54 @@ function MapView() {
421436
<div class="max-w-[1500px] mx-auto">
422437
<form onSubmit={loadTiles} class="flex">
423438
<input
424-
class="border-2 border-gray p-1 flex-1 mr-2"
439+
class="border-2 border-gray p-1 flex-1 mr-2 text-xs lg:text-base"
425440
type="text"
426441
name="tiles"
427442
value={tiles()}
428443
style={{ width: "50%" }}
444+
autocomplete="off"
429445
/>
430446
<button class="btn-primary" type="submit">
431447
load
432448
</button>
433449
</form>
434-
<div class="my-2 space-x-2">
435-
<select onChange={(e) => setTheme(e.target.value)} value={theme()}>
436-
<option value="light">light</option>
437-
<option value="dark">dark</option>
438-
<option value="white">data viz (white)</option>
439-
<option value="grayscale">data viz (grayscale)</option>
440-
<option value="black">data viz (black)</option>
441-
</select>
442-
<select onChange={(e) => setLang(e.target.value)} value={lang()}>
443-
<For each={language_script_pairs}>
444-
{(pair) => (
445-
<option value={pair.lang}>
446-
{pair.lang} ({pair.full_name})
447-
</option>
448-
)}
449-
</For>
450-
</select>
450+
<div class="flex my-2 space-y-2 lg:space-y-0 space-x-2 flex-col lg:flex-row items-center">
451+
<div class="flex items-center">
452+
<label for="theme" class="text-xs mr-1">
453+
theme
454+
</label>
455+
<select
456+
id="theme"
457+
onChange={(e) => setTheme(e.target.value)}
458+
value={theme()}
459+
autocomplete="on"
460+
>
461+
<option value="light">light</option>
462+
<option value="dark">dark</option>
463+
<option value="white">data viz (white)</option>
464+
<option value="grayscale">data viz (grayscale)</option>
465+
<option value="black">data viz (black)</option>
466+
</select>
467+
</div>
468+
<div class="flex items-center">
469+
<label for="lang" class="text-xs mr-1">
470+
language
471+
</label>
472+
<select
473+
id="lang"
474+
onChange={(e) => setLang(e.target.value)}
475+
value={lang()}
476+
autocomplete="on"
477+
>
478+
<For each={language_script_pairs}>
479+
{(pair) => (
480+
<option value={pair.lang}>
481+
{pair.lang} ({pair.full_name})
482+
</option>
483+
)}
484+
</For>
485+
</select>
486+
</div>
451487
<div class="hidden lg:inline">
452488
<input
453489
id="localSprites"
@@ -497,7 +533,7 @@ function MapView() {
497533
class="btn-primary hidden lg:inline"
498534
onClick={() => setShowStyleJson(!showStyleJson())}
499535
>
500-
get style JSON
536+
{showStyleJson() ? "Close style JSON" : "Get style JSON"}
501537
</button>
502538
</div>
503539
</div>

app/src/VisualTests.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ function VisualTests() {
383383
return (
384384
<div class="flex flex-col h-screen w-full">
385385
<Nav page={2} />
386-
<div class="w-[1500px] mx-auto">
386+
<div class="w-[1500px] mx-auto p-2">
387387
<h1 class="my-8 text-4xl">Visual Tests</h1>
388388
<div class="inline-block w-[500px] font-mono text-xs">
389389
leftTiles={displayInfo().leftTiles}

app/tailwind.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export default {
66
purple: "#3131DC",
77
white: "#FFFFFF",
88
black: "#000000",
9-
gray: "#E7E7F9"
9+
gray: "#E7E7F9",
10+
red: "#FF0000"
1011
},
1112
},
1213
plugins: [],

0 commit comments

Comments
 (0)