Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ <h4 class="modal-title" id="myModalLabel">Search for Watershed by Name or ID</h4
<div class="col-2 col-md-1">
<button id="btnq1" data-bs-toggle="offcanvas"
class="btn btn-sq-sm btn-danger" title="Show Menu."
data-bs-target="#sidebar" aria-controls="sidebar">
data-bs-target="#sidebar" aria-controls="sidebar"
aria-label="Open menu" aria-expanded="false">
<i class="bi-list"></i></button>
</div>
<div class="col-2 col-md-1">
Expand Down Expand Up @@ -214,9 +215,10 @@ <h4 class="modal-title" id="myModalLabel">Search for Watershed by Name or ID</h4

<div id="map" style="width: 100%; height: 100%; position:fixed;"></div>
<div class="offcanvas offcanvas-start" id="sidebar"
data-bs-scroll="true" data-bs-backdrop="false">
data-bs-scroll="false" data-bs-backdrop="true"
aria-labelledby="sidebar-title">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Daily Erosion Project</h5>
<h5 class="offcanvas-title" id="sidebar-title">Daily Erosion Project</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" style="overflow-y: scroll;">
Expand Down Expand Up @@ -375,13 +377,14 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
autocomplete="off" value="multi">
<label class="btn btn-outline-success btn-sm" for="multi">Multi</label>
</div>
<label class="form-label visually-hidden" for="datepicker">Date</label>
<div class="input-group input-group-sm">
<button id="minus1d" class="btn btn-secondary" type="button" aria-label="Previous day"><i class="bi-arrow-left"></i></button>
<input type="date" name="date" id="datepicker" class="form-control" style="font-weight: bolder;">
<button id="plus1d" class="btn btn-secondary" type="button" aria-label="Next day"><i class="bi-arrow-right"></i></button>
</div>
<div style="display: none;" id="dp2" class="mt-2">
<label class="small text-muted">To Date:</label>
<label class="small text-muted" for="datepicker2">To Date:</label>
<div class="input-group input-group-sm">
<input type="date" name="date2" id="datepicker2" class="form-control" style="font-weight: bolder;" />
</div>
Expand All @@ -399,8 +402,8 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
<div class="card-header py-1 d-flex justify-content-between align-items-center">
<span><i class="bi-layers me-2"></i> Available Data Layers</span>
<span>
<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>
<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>
<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>
<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>
</span>
</div>
<div class="card-body py-2">
Expand Down Expand Up @@ -437,11 +440,11 @@ <h6 class="text-muted small mb-1">Top 10 Soil Detachment Days</h6>
<li data-category="metadata">
<input type="radio" id="dt_opt" name="whichlayer" value="dt"
data-description="Dominant Tillage Practice is the most common DEP tillage code within a HUC12. See info button for more details.">
<label for="dt_opt">Dominant Tillage Practice
<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal"
data-bs-target="#dtModal" style="padding: 0.1rem 0.4rem; font-size: 0.85em;">
<i class="bi-info-circle-fill"></i> info</button>
</label>
<label for="dt_opt">Dominant Tillage Practice</label>
<button type="button" class="btn btn-outline-primary btn-sm ms-2" data-bs-toggle="modal"
data-bs-target="#dtModal" style="padding: 0.1rem 0.4rem; font-size: 0.85em;"
aria-label="More information about dominant tillage practice">
<i class="bi-info-circle-fill"></i> info</button>
</li>
</ul>
<p id="layer-description" class="text-muted small mb-0"></p>
Expand Down
19 changes: 13 additions & 6 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,20 @@ body {
z-index: 1003;
}

.btn:focus,
.btn:active:focus,
.btn.active:focus,
.btn.focus,
.btn:active.focus,
.btn.active.focus {
.btn:focus-visible,
.btn:active:focus-visible,
.btn.active:focus-visible,
.btn.focus-visible,
.btn:active.focus-visible,
.btn.active.focus-visible {
outline: 3px solid #111;
outline-offset: 2px;
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.85);
}

.btn:focus:not(:focus-visible) {
outline: none;
box-shadow: none;
}

#colorbar {
Expand Down
13 changes: 13 additions & 0 deletions src/uiManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@ import { requireElement } from 'iemjs/domUtils';

export function setupSidebarEvents() {
const sidebarElement = requireElement('sidebar');
const sidebarToggle = requireElement('btnq1');
const primarySidebarAction = sidebarElement.querySelector('[data-bs-toggle="modal"]');

sidebarToggle.setAttribute('aria-expanded', 'false');

sidebarElement.addEventListener('shown.bs.offcanvas', () => {
sidebarToggle.setAttribute('aria-expanded', 'true');
setState(StateKeys.SIDEBAR_OPEN, true);

if (primarySidebarAction instanceof HTMLElement) {
primarySidebarAction.focus();
}
});
sidebarElement.addEventListener('hidden.bs.offcanvas', () => {
sidebarToggle.setAttribute('aria-expanded', 'false');
setState(StateKeys.SIDEBAR_OPEN, false);

sidebarToggle.focus();
});
}

Expand Down
34 changes: 34 additions & 0 deletions tests/drawerAccessibility.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';

const htmlPath = path.join(process.cwd(), 'index.html');
const html = readFileSync(htmlPath, 'utf8');
const documentFragment = new DOMParser().parseFromString(html, 'text/html');

describe('drawer accessibility markup', () => {
it('provides accessible naming for the menu trigger and drawer', () => {
const sidebarToggle = documentFragment.getElementById('btnq1');
const sidebar = documentFragment.getElementById('sidebar');
const sidebarTitle = documentFragment.getElementById('sidebar-title');

expect(sidebarToggle?.getAttribute('aria-label')).toBe('Open menu');
expect(sidebarToggle?.getAttribute('aria-expanded')).toBe('false');
expect(sidebar?.getAttribute('aria-labelledby')).toBe('sidebar-title');
expect(sidebarTitle?.textContent).toContain('Daily Erosion Project');
});

it('keeps drawer controls programmatically labeled', () => {
const dateLabel = documentFragment.querySelector('label[for="datepicker"]');
const dateRangeLabel = documentFragment.querySelector('label[for="datepicker2"]');
const decreaseOpacity = documentFragment.querySelector('[data-action="decrease-opacity"]');
const increaseOpacity = documentFragment.querySelector('[data-action="increase-opacity"]');
const tillageInfo = documentFragment.querySelector('[data-bs-target="#dtModal"]');

expect(dateLabel?.textContent?.trim()).toBe('Date');
expect(dateRangeLabel?.textContent?.trim()).toBe('To Date:');
expect(decreaseOpacity?.getAttribute('aria-label')).toBe('Decrease opacity');
expect(increaseOpacity?.getAttribute('aria-label')).toBe('Increase opacity');
expect(tillageInfo?.getAttribute('aria-label')).toBe('More information about dominant tillage practice');
});
});
29 changes: 29 additions & 0 deletions tests/uiManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import * as uiManager from '../src/uiManager.js';
import { initializeMap } from '../src/mapManager.js';
import { requireElement } from 'iemjs/domUtils';
import { getState, setState, StateKeys } from '../src/state.js';

function setupDOM() {
// Containers for layer switcher
Expand Down Expand Up @@ -58,4 +59,32 @@ describe('uiManager', () => {
expect(second.checked).toBe(true);
expect(first.checked).toBe(false);
});

it('updates drawer accessibility state and focus on offcanvas events', () => {
const sidebarToggle = document.createElement('button');
sidebarToggle.id = 'btnq1';
document.body.appendChild(sidebarToggle);

const sidebar = document.createElement('div');
sidebar.id = 'sidebar';

const searchButton = document.createElement('button');
searchButton.type = 'button';
searchButton.setAttribute('data-bs-toggle', 'modal');
sidebar.appendChild(searchButton);
document.body.appendChild(sidebar);

setState(StateKeys.SIDEBAR_OPEN, false);
uiManager.setupSidebarEvents();

sidebar.dispatchEvent(new Event('shown.bs.offcanvas'));
expect(sidebarToggle.getAttribute('aria-expanded')).toBe('true');
expect(getState(StateKeys.SIDEBAR_OPEN)).toBe(true);
expect(document.activeElement).toBe(searchButton);

sidebar.dispatchEvent(new Event('hidden.bs.offcanvas'));
expect(sidebarToggle.getAttribute('aria-expanded')).toBe('false');
expect(getState(StateKeys.SIDEBAR_OPEN)).toBe(false);
expect(document.activeElement).toBe(sidebarToggle);
});
});
Loading