Skip to content

Commit a8a5fd9

Browse files
authored
Merge pull request #132 from akrherz/gh131_side_drawer
🐛 Address sidedrawer ally issues
2 parents 02ff9e1 + b95ca6e commit a8a5fd9

5 files changed

Lines changed: 103 additions & 17 deletions

File tree

index.html

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ <h4 class="modal-title" id="myModalLabel">Search for Watershed by Name or ID</h4
161161
<div class="col-2 col-md-1">
162162
<button id="btnq1" data-bs-toggle="offcanvas"
163163
class="btn btn-sq-sm btn-danger" title="Show Menu."
164-
data-bs-target="#sidebar" aria-controls="sidebar">
164+
data-bs-target="#sidebar" aria-controls="sidebar"
165+
aria-label="Open menu" aria-expanded="false">
165166
<i class="bi-list"></i></button>
166167
</div>
167168
<div class="col-2 col-md-1">
@@ -214,9 +215,10 @@ <h4 class="modal-title" id="myModalLabel">Search for Watershed by Name or ID</h4
214215

215216
<div id="map" style="width: 100%; height: 100%; position:fixed;"></div>
216217
<div class="offcanvas offcanvas-start" id="sidebar"
217-
data-bs-scroll="true" data-bs-backdrop="false">
218+
data-bs-scroll="false" data-bs-backdrop="true"
219+
aria-labelledby="sidebar-title">
218220
<div class="offcanvas-header">
219-
<h5 class="offcanvas-title">Daily Erosion Project</h5>
221+
<h5 class="offcanvas-title" id="sidebar-title">Daily Erosion Project</h5>
220222
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
221223
</div>
222224
<div class="offcanvas-body" style="overflow-y: scroll;">
@@ -375,13 +377,14 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
375377
autocomplete="off" value="multi">
376378
<label class="btn btn-outline-success btn-sm" for="multi">Multi</label>
377379
</div>
380+
<label class="form-label visually-hidden" for="datepicker">Date</label>
378381
<div class="input-group input-group-sm">
379382
<button id="minus1d" class="btn btn-secondary" type="button" aria-label="Previous day"><i class="bi-arrow-left"></i></button>
380383
<input type="date" name="date" id="datepicker" class="form-control" style="font-weight: bolder;">
381384
<button id="plus1d" class="btn btn-secondary" type="button" aria-label="Next day"><i class="bi-arrow-right"></i></button>
382385
</div>
383386
<div style="display: none;" id="dp2" class="mt-2">
384-
<label class="small text-muted">To Date:</label>
387+
<label class="small text-muted" for="datepicker2">To Date:</label>
385388
<div class="input-group input-group-sm">
386389
<input type="date" name="date2" id="datepicker2" class="form-control" style="font-weight: bolder;" />
387390
</div>
@@ -399,8 +402,8 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
399402
<div class="card-header py-1 d-flex justify-content-between align-items-center">
400403
<span><i class="bi-layers me-2"></i> Available Data Layers</span>
401404
<span>
402-
<button data-action="decrease-opacity" class="btn btn-outline-secondary btn-sm py-0 px-1" type="button" title="Decrease Opacity"><i class="bi-brightness-high"></i></button>
403-
<button data-action="increase-opacity" class="btn btn-outline-secondary btn-sm py-0 px-1" type="button" title="Increase Opacity"><i class="bi-brightness-low"></i></button>
405+
<button data-action="decrease-opacity" class="btn btn-outline-secondary btn-sm py-0 px-1" type="button" title="Decrease Opacity" aria-label="Decrease opacity"><i class="bi-brightness-high"></i></button>
406+
<button data-action="increase-opacity" class="btn btn-outline-secondary btn-sm py-0 px-1" type="button" title="Increase Opacity" aria-label="Increase opacity"><i class="bi-brightness-low"></i></button>
404407
</span>
405408
</div>
406409
<div class="card-body py-2">
@@ -437,11 +440,11 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
437440
<li data-category="metadata">
438441
<input type="radio" id="dt_opt" name="whichlayer" value="dt"
439442
data-description="Dominant Tillage Practice is the most common DEP tillage code within a HUC12. See info button for more details.">
440-
<label for="dt_opt">Dominant Tillage Practice
441-
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal"
442-
data-bs-target="#dtModal" style="padding: 0.1rem 0.4rem; font-size: 0.85em;">
443-
<i class="bi-info-circle-fill"></i> info</button>
444-
</label>
443+
<label for="dt_opt">Dominant Tillage Practice</label>
444+
<button type="button" class="btn btn-outline-primary btn-sm ms-2" data-bs-toggle="modal"
445+
data-bs-target="#dtModal" style="padding: 0.1rem 0.4rem; font-size: 0.85em;"
446+
aria-label="More information about dominant tillage practice">
447+
<i class="bi-info-circle-fill"></i> info</button>
445448
</li>
446449
</ul>
447450
<p id="layer-description" class="text-muted small mb-0"></p>

src/style.css

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ body {
4343
z-index: 1003;
4444
}
4545

46-
.btn:focus,
47-
.btn:active:focus,
48-
.btn.active:focus,
49-
.btn.focus,
50-
.btn:active.focus,
51-
.btn.active.focus {
46+
.btn:focus-visible,
47+
.btn:active:focus-visible,
48+
.btn.active:focus-visible,
49+
.btn.focus-visible,
50+
.btn:active.focus-visible,
51+
.btn.active.focus-visible {
52+
outline: 3px solid #111;
53+
outline-offset: 2px;
54+
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.85);
55+
}
56+
57+
.btn:focus:not(:focus-visible) {
5258
outline: none;
59+
box-shadow: none;
5360
}
5461

5562
#colorbar {

src/uiManager.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@ import { requireElement } from 'iemjs/domUtils';
66

77
export function setupSidebarEvents() {
88
const sidebarElement = requireElement('sidebar');
9+
const sidebarToggle = requireElement('btnq1');
10+
const primarySidebarAction = sidebarElement.querySelector('[data-bs-toggle="modal"]');
11+
12+
sidebarToggle.setAttribute('aria-expanded', 'false');
13+
914
sidebarElement.addEventListener('shown.bs.offcanvas', () => {
15+
sidebarToggle.setAttribute('aria-expanded', 'true');
1016
setState(StateKeys.SIDEBAR_OPEN, true);
17+
18+
if (primarySidebarAction instanceof HTMLElement) {
19+
primarySidebarAction.focus();
20+
}
1121
});
1222
sidebarElement.addEventListener('hidden.bs.offcanvas', () => {
23+
sidebarToggle.setAttribute('aria-expanded', 'false');
1324
setState(StateKeys.SIDEBAR_OPEN, false);
25+
26+
sidebarToggle.focus();
1427
});
1528
}
1629

tests/drawerAccessibility.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { readFileSync } from 'node:fs';
2+
import path from 'node:path';
3+
import { describe, expect, it } from 'vitest';
4+
5+
const htmlPath = path.join(process.cwd(), 'index.html');
6+
const html = readFileSync(htmlPath, 'utf8');
7+
const documentFragment = new DOMParser().parseFromString(html, 'text/html');
8+
9+
describe('drawer accessibility markup', () => {
10+
it('provides accessible naming for the menu trigger and drawer', () => {
11+
const sidebarToggle = documentFragment.getElementById('btnq1');
12+
const sidebar = documentFragment.getElementById('sidebar');
13+
const sidebarTitle = documentFragment.getElementById('sidebar-title');
14+
15+
expect(sidebarToggle?.getAttribute('aria-label')).toBe('Open menu');
16+
expect(sidebarToggle?.getAttribute('aria-expanded')).toBe('false');
17+
expect(sidebar?.getAttribute('aria-labelledby')).toBe('sidebar-title');
18+
expect(sidebarTitle?.textContent).toContain('Daily Erosion Project');
19+
});
20+
21+
it('keeps drawer controls programmatically labeled', () => {
22+
const dateLabel = documentFragment.querySelector('label[for="datepicker"]');
23+
const dateRangeLabel = documentFragment.querySelector('label[for="datepicker2"]');
24+
const decreaseOpacity = documentFragment.querySelector('[data-action="decrease-opacity"]');
25+
const increaseOpacity = documentFragment.querySelector('[data-action="increase-opacity"]');
26+
const tillageInfo = documentFragment.querySelector('[data-bs-target="#dtModal"]');
27+
28+
expect(dateLabel?.textContent?.trim()).toBe('Date');
29+
expect(dateRangeLabel?.textContent?.trim()).toBe('To Date:');
30+
expect(decreaseOpacity?.getAttribute('aria-label')).toBe('Decrease opacity');
31+
expect(increaseOpacity?.getAttribute('aria-label')).toBe('Increase opacity');
32+
expect(tillageInfo?.getAttribute('aria-label')).toBe('More information about dominant tillage practice');
33+
});
34+
});

tests/uiManager.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
22
import * as uiManager from '../src/uiManager.js';
33
import { initializeMap } from '../src/mapManager.js';
44
import { requireElement } from 'iemjs/domUtils';
5+
import { getState, setState, StateKeys } from '../src/state.js';
56

67
function setupDOM() {
78
// Containers for layer switcher
@@ -58,4 +59,32 @@ describe('uiManager', () => {
5859
expect(second.checked).toBe(true);
5960
expect(first.checked).toBe(false);
6061
});
62+
63+
it('updates drawer accessibility state and focus on offcanvas events', () => {
64+
const sidebarToggle = document.createElement('button');
65+
sidebarToggle.id = 'btnq1';
66+
document.body.appendChild(sidebarToggle);
67+
68+
const sidebar = document.createElement('div');
69+
sidebar.id = 'sidebar';
70+
71+
const searchButton = document.createElement('button');
72+
searchButton.type = 'button';
73+
searchButton.setAttribute('data-bs-toggle', 'modal');
74+
sidebar.appendChild(searchButton);
75+
document.body.appendChild(sidebar);
76+
77+
setState(StateKeys.SIDEBAR_OPEN, false);
78+
uiManager.setupSidebarEvents();
79+
80+
sidebar.dispatchEvent(new Event('shown.bs.offcanvas'));
81+
expect(sidebarToggle.getAttribute('aria-expanded')).toBe('true');
82+
expect(getState(StateKeys.SIDEBAR_OPEN)).toBe(true);
83+
expect(document.activeElement).toBe(searchButton);
84+
85+
sidebar.dispatchEvent(new Event('hidden.bs.offcanvas'));
86+
expect(sidebarToggle.getAttribute('aria-expanded')).toBe('false');
87+
expect(getState(StateKeys.SIDEBAR_OPEN)).toBe(false);
88+
expect(document.activeElement).toBe(sidebarToggle);
89+
});
6190
});

0 commit comments

Comments
 (0)