From c31cef20b5a07e07cdec5cdf770679313cece6a5 Mon Sep 17 00:00:00 2001 From: sahilSingh-publicisSapient <112582465+sahilSingh-publicisSapient@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:49:43 -0500 Subject: [PATCH 001/274] Initial commit, Added input masking test, Added tests for masking functionality --- js/test/input-mask.test.js | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 js/test/input-mask.test.js diff --git a/js/test/input-mask.test.js b/js/test/input-mask.test.js new file mode 100644 index 00000000..e3674d87 --- /dev/null +++ b/js/test/input-mask.test.js @@ -0,0 +1,58 @@ +"use strict"; + +import config from "./test-config.js"; +import testHelpers from "./test-helpers.js"; +let mobileBrowser, desktopBrowser; + +describe("Input mask test suite", () => { + beforeAll(async () => { + mobileBrowser = await testHelpers.getMobileBrowser(); + desktopBrowser = await testHelpers.getDesktopBrowser(); + }); + afterAll(async () => { + await testHelpers.pause(); + mobileBrowser.close(); + desktopBrowser.close(); + }); + + it("Testing the masking functionality", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`); + await page.type("#tel", "1234567890"); + const maskedTelephone = await page.$eval( + "span.enable-input-mask__mask-pre-val", + (span) => span.textContent + ); + expect(maskedTelephone).toBe("123-456-7890"); + }); + it("should render winkey masked correctly", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`); + await page.type("#winkey", "abcdefghijklmnopqrstuvwxy"); + const maskedWinKey = await page.$$eval( + "span.enable-input-mask__mask-pre-val", + (spans) => spans[1].textContent + ); + expect(maskedWinKey).toBe("ABCDE-FGHIJ-KLMNO-PQRST-UVWXY"); + }); + it("should render cc masked correctly (non Amex)", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`); + await page.type("#cc", "5555555555555555"); + const maskedWinKey = await page.$$eval( + "span.enable-input-mask__mask-pre-val", + (spans) => spans[2].textContent + ); + expect(maskedWinKey).toBe("5555 5555 5555 5555"); + }); + it("should render cc masked correctly (Amex)", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`); + await page.type("#cc", "3455555555555555"); + const maskedWinKey = await page.$$eval( + "span.enable-input-mask__mask-pre-val", + (spans) => spans[2].textContent + ); + expect(maskedWinKey).toBe("3455 555555 55555"); + }); +}); From 11dc9e43af373cd6332e5f3570f0b4573fdaa909 Mon Sep 17 00:00:00 2001 From: sahilSingh-publicisSapient <112582465+sahilSingh-publicisSapient@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:00:32 -0500 Subject: [PATCH 002/274] Updated input-masking tests. Added keyboard interaction test --- js/test/input-mask.test.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/js/test/input-mask.test.js b/js/test/input-mask.test.js index e3674d87..900e0a19 100644 --- a/js/test/input-mask.test.js +++ b/js/test/input-mask.test.js @@ -2,6 +2,7 @@ import config from "./test-config.js"; import testHelpers from "./test-helpers.js"; +import puppeteer from "puppeteer"; let mobileBrowser, desktopBrowser; describe("Input mask test suite", () => { @@ -14,7 +15,7 @@ describe("Input mask test suite", () => { mobileBrowser.close(); desktopBrowser.close(); }); - + // Masking functionality tests it("Testing the masking functionality", async () => { const page = await desktopBrowser.newPage(); await page.goto(`${config.BASE_URL}/input-mask.php`); @@ -55,4 +56,24 @@ describe("Input mask test suite", () => { ); expect(maskedWinKey).toBe("3455 555555 55555"); }); + //Simulate keyboard interaction + + it("should render formatted value correctly post keyboard interaction", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`, { + waitUntil: "domcontentloaded", + }); + + const telInput = "#tel"; + await page.focus(telInput); + await page.keyboard.type("1234567890"); + await page.keyboard.press("Backspace"); + await page.keyboard.press("ArrowLeft"); + await page.keyboard.type("1"); + const maskedTelephone = await page.$eval( + "span.enable-input-mask__mask-pre-val", + (span) => span.textContent + ); + expect(maskedTelephone).toBe("123-456-781"); + }); }); From cd93146264093995956ec6ea0cb4e92209032667 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 13 Mar 2024 16:45:27 -0400 Subject: [PATCH 003/274] Generate the TOC content for all pages but home and faq --- js/global.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/js/global.js b/js/global.js index a532789f..f64c074b 100644 --- a/js/global.js +++ b/js/global.js @@ -64,6 +64,16 @@ function initEnable() { el.innerHTML = breakpointWidth; }); + // Table of Contents container setup + const toc = document.createElement('aside'); + toc.setAttribute('id', 'toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Page Overview'; + const tocList = document.createElement('ol'); + tocList.setAttribute('class', 'toc-level-1'); + let prevHeadingLevel = 0; + let tocNode = tocList; + pauseAnimControl.init(); @@ -98,10 +108,57 @@ function initEnable() { if (el.nodeName !== 'H1' && el.getAttribute('role') !== 'heading') { el.innerHTML = `${el.innerHTML}` } + + /** + * Generates a table of contents for your document based on the headings + * present. Anchors are injected into the document and the + * entries in the table of contents are linked to them. The table of + * contents will be generated inside of the first element with the id `toc`. + */ + const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); + if (headingLevel === 1) { + prevHeadingLevel = headingLevel; + return; + } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { + const subList = document.createElement('ul'); + subList.setAttribute('class', `toc-level-${headingLevel - 1}`); + tocNode.appendChild(subList); + tocNode = subList; + } + + tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); + + const tocItem = document.createElement('li'); + tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); + + const tocLink = el.id ? document.createElement('a') : document.createElement('p'); + el.id && tocLink.setAttribute('href', `#${el.id}`); + tocLink.textContent = el.textContent; + + tocItem.appendChild(tocLink); + tocNode.appendChild(tocItem); + tocNode = tocItem; + prevHeadingLevel = headingLevel; + return; } }) } + // Add the Table of Contents to the top of pages + if (location.href.indexOf('index.php') === -1 && location.href.indexOf('faq.php') === -1) { + toc.appendChild(tocHeading); + toc.appendChild(tocList); + const h1 = document.getElementsByTagName('h1')?.[0]; + const main = document.getElementsByTagName('main')?.[0]; + + // Insert the TOC after the H1, or just inside the main element + if (h1) { + h1.after(toc); + } else { + main.insertAdjacentElement('afterbegin', toc); + } + } + focusDeepLink(); } From 8f4de1f0bef1c9cb0843993c2610c991fb21cdf7 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 13 Mar 2024 16:46:13 -0400 Subject: [PATCH 004/274] Style the TOC --- css/shared/all.css | 34 +++++++++++++++++++++++++++++++ css/shared/landmarks.css | 34 +++++++++++++++++++++++++++++++ less/shared/landmarks.less | 41 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/css/shared/all.css b/css/shared/all.css index 8d04395b..2751f38a 100644 --- a/css/shared/all.css +++ b/css/shared/all.css @@ -773,3 +773,37 @@ footer a, [role="contentinfo"] a { color: #ccccff; } +#toc { + padding: 1em; + background: #f0f8ff; +} +@media only screen and (min-width: 720px) { + #toc { + float: right; + max-width: 50%; + margin-left: 2em; + } +} +#toc h2 { + font-weight: normal; +} +#toc h2::before { + content: none; +} +#toc ol, +#toc ul { + margin-left: 0.5em; +} +#toc ~ h2 { + clear: right; +} +@media only screen and (min-width: 720px) { + #toc:has(+ h2) { + float: left; + max-width: 100%; + margin-left: 0; + } +} +#toc:has(+ h2) ~ h2 { + clear: left; +} diff --git a/css/shared/landmarks.css b/css/shared/landmarks.css index 8d04395b..2751f38a 100644 --- a/css/shared/landmarks.css +++ b/css/shared/landmarks.css @@ -773,3 +773,37 @@ footer a, [role="contentinfo"] a { color: #ccccff; } +#toc { + padding: 1em; + background: #f0f8ff; +} +@media only screen and (min-width: 720px) { + #toc { + float: right; + max-width: 50%; + margin-left: 2em; + } +} +#toc h2 { + font-weight: normal; +} +#toc h2::before { + content: none; +} +#toc ol, +#toc ul { + margin-left: 0.5em; +} +#toc ~ h2 { + clear: right; +} +@media only screen and (min-width: 720px) { + #toc:has(+ h2) { + float: left; + max-width: 100%; + margin-left: 0; + } +} +#toc:has(+ h2) ~ h2 { + clear: left; +} diff --git a/less/shared/landmarks.less b/less/shared/landmarks.less index e59f762a..69ec3d21 100755 --- a/less/shared/landmarks.less +++ b/less/shared/landmarks.less @@ -220,3 +220,44 @@ footer, color: #ccccff; } } + +#toc { + padding: 1em; + background: #f0f8ff; + + @media @tablet-up { + float: right; + max-width: 50%; + margin-left: 2em; + } + + h2 { + font-weight: normal; + + &::before { + content: none; + } + } + + ol, + ul { + margin-left: 0.5em; + } + + & ~ h2 { + clear: right; + } + + // If the first element after the TOC is an h2, have the TOC be the full width + &:has(+ h2) { + @media @tablet-up { + float: left; + max-width: 100%; + margin-left: 0; + } + + & ~ h2 { + clear: left; + } + } +} \ No newline at end of file From 9d478e1985bbd42f683332343f8a559d14af0548 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 13 Mar 2024 16:53:03 -0400 Subject: [PATCH 005/274] Improve TOC code comments --- js/global.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/global.js b/js/global.js index f64c074b..3125c996 100644 --- a/js/global.js +++ b/js/global.js @@ -112,8 +112,7 @@ function initEnable() { /** * Generates a table of contents for your document based on the headings * present. Anchors are injected into the document and the - * entries in the table of contents are linked to them. The table of - * contents will be generated inside of the first element with the id `toc`. + * entries in the table of contents are linked to them. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); if (headingLevel === 1) { @@ -144,7 +143,7 @@ function initEnable() { }) } - // Add the Table of Contents to the top of pages + // Add the generated Table of Contents to the top of pages if (location.href.indexOf('index.php') === -1 && location.href.indexOf('faq.php') === -1) { toc.appendChild(tocHeading); toc.appendChild(tocList); From e759c995c089fe799c2c802a2d29cdc4482a5831 Mon Sep 17 00:00:00 2001 From: sahilSingh-publicisSapient <112582465+sahilSingh-publicisSapient@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:44:22 -0400 Subject: [PATCH 006/274] Added test cases for keyboard and mouse interactions & input validation, Added package RobotJS --- js/test/input-mask.test.js | 130 +++++++- js/test/install-helper.js | 86 ++++++ package-lock.json | 594 ++++++++++++++++++++++++++++++++++++- package.json | 1 + 4 files changed, 804 insertions(+), 7 deletions(-) create mode 100644 js/test/install-helper.js diff --git a/js/test/input-mask.test.js b/js/test/input-mask.test.js index 900e0a19..218eb402 100644 --- a/js/test/input-mask.test.js +++ b/js/test/input-mask.test.js @@ -2,17 +2,23 @@ import config from "./test-config.js"; import testHelpers from "./test-helpers.js"; -import puppeteer from "puppeteer"; -let mobileBrowser, desktopBrowser; +const { installMouseHelper } = require("./install-helper.js"); + +let desktopBrowser; + +const robot = require("robotjs"); + +async function captureCursorPosition() { + const { x, y } = robot.getMousePos(); + return { x, y }; +} describe("Input mask test suite", () => { beforeAll(async () => { - mobileBrowser = await testHelpers.getMobileBrowser(); desktopBrowser = await testHelpers.getDesktopBrowser(); }); afterAll(async () => { await testHelpers.pause(); - mobileBrowser.close(); desktopBrowser.close(); }); // Masking functionality tests @@ -56,8 +62,8 @@ describe("Input mask test suite", () => { ); expect(maskedWinKey).toBe("3455 555555 55555"); }); - //Simulate keyboard interaction + //Test keyboard interaction it("should render formatted value correctly post keyboard interaction", async () => { const page = await desktopBrowser.newPage(); await page.goto(`${config.BASE_URL}/input-mask.php`, { @@ -76,4 +82,118 @@ describe("Input mask test suite", () => { ); expect(maskedTelephone).toBe("123-456-781"); }); + + // Test mouse interaction + it("should correctly render visual cursor and the input cursor on mouse Interactions", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`, { + waitUntil: "domcontentloaded", + }); + + const telephoneInput = "#tel"; + + // bounding box of input + const inputBoundingBox = await page.$eval(telephoneInput, (input) => { + const { x, y, width, height } = input.getBoundingClientRect(); + return { x, y, width, height }; + }); + + // Calculate the middle of the span + const middleX = inputBoundingBox.x + inputBoundingBox.width / 2; + const middleY = inputBoundingBox.y + inputBoundingBox.height / 2; + + //Initial cursor position, before mouse Interaction + const initialCursorPosition = await captureCursorPosition(); + + //explicitly move the mouse and click + await page.mouse.move(middleX, middleY); + await page.mouse.click(middleX, middleY); + + // get the cursor position ofter click, the assumption is after mouse move, an onclick the cursor doesn't Move + const cursorPosition = await captureCursorPosition(); + + expect(cursorPosition.x).toBeCloseTo(initialCursorPosition.x, 2); + expect(cursorPosition.y).toBeCloseTo(initialCursorPosition.y, 2); + }); + + // Mouse Selection + it("should select characters in the Tel Input field - Mouse", async () => { + const page = await desktopBrowser.newPage(); + await installMouseHelper(page); + await page.goto(`${config.BASE_URL}/input-mask.php`, { + waitUntil: "domcontentloaded", + }); + const telephoneInput = "#tel"; + const expectedSelection = "12345"; + await page.type(telephoneInput, "1234567890"); + // bounding box of input + const inputBoundingBox = await page.$eval(telephoneInput, (input) => { + const { x, y, width, height } = input.getBoundingClientRect(); + return { x, y, width, height }; + }); + //Click the input and move to the start of the field + await page.click(telephoneInput); + await page.mouse.move(inputBoundingBox.x, inputBoundingBox.y); + // Select using + await page.mouse.down(); + await page.mouse.move(inputBoundingBox.x + 50, inputBoundingBox.y); + await page.mouse.up(); + //get the selection + const selectedText = await page.$eval( + "span.enable-input-mask__mask-pre-val", + (span) => { + return window.getSelection().toString(); + } + ); + // check screenshot for the selection, should match up - "123-45" + await page.screenshot({ path: "mouseSelection.png" }); + expect(expectedSelection).toBe(selectedText); + }); + + // Keyboard Selection + it("should select characters in the Tel Input field - Keyboard", async () => { + const page = await desktopBrowser.newPage(); + await installMouseHelper(page); + await page.goto(`${config.BASE_URL}/input-mask.php`, { + waitUntil: "domcontentloaded", + }); + const telephoneInput = "#tel"; + const expectedSelection = "67890"; + //Focus on the input and type + await page.focus(telephoneInput); + await page.type(telephoneInput, "1234567890"); + await page.keyboard.down("Shift"); + // Select using keyboard + await page.keyboard.press("ArrowLeft", { shift: true }); + await page.keyboard.press("ArrowLeft", { shift: true }); + await page.keyboard.press("ArrowLeft", { shift: true }); + await page.keyboard.press("ArrowLeft", { shift: true }); + await page.keyboard.press("ArrowLeft", { shift: true }); + await page.keyboard.up("Shift"); + //get the selection + const selectedText = await page.$eval( + "span.enable-input-mask__mask-pre-val", + (span) => { + return window.getSelection().toString(); + } + ); + // check screenshot for the selection, should match up - "6-7890" + await page.screenshot({ path: "keyBoardSelection.png" }); + expect(expectedSelection).toBe(selectedText); + }); + + // Input Validation + it("should not render letters in telephone input", async () => { + const page = await desktopBrowser.newPage(); + await page.goto(`${config.BASE_URL}/input-mask.php`, { + waitUntil: "domcontentloaded", + }); + + await page.type("#tel", "abscded"); + const maskedTelephone = await page.$eval( + "span.enable-input-mask__mask-pre-val", + (span) => span.textContent + ); + expect(maskedTelephone).toBe(""); + }); }); diff --git a/js/test/install-helper.js b/js/test/install-helper.js new file mode 100644 index 00000000..d358b0fc --- /dev/null +++ b/js/test/install-helper.js @@ -0,0 +1,86 @@ +// This injects a box into the page that moves with the mouse; +// Useful for debugging +async function installMouseHelper(page) { + await page.evaluateOnNewDocument(() => { + // Install mouse helper only for top-level frame. + if (window !== window.parent) return; + window.addEventListener( + "DOMContentLoaded", + () => { + const box = document.createElement("puppeteer-mouse-pointer"); + const styleElement = document.createElement("style"); + styleElement.innerHTML = ` + puppeteer-mouse-pointer { + pointer-events: none; + position: absolute; + top: 0; + z-index: 10000; + left: 0; + width: 20px; + height: 20px; + background: rgba(0,0,0,.4); + border: 1px solid white; + border-radius: 10px; + margin: -10px 0 0 -10px; + padding: 0; + transition: background .2s, border-radius .2s, border-color .2s; + } + puppeteer-mouse-pointer.button-1 { + transition: none; + background: rgba(0,0,0,0.9); + } + puppeteer-mouse-pointer.button-2 { + transition: none; + border-color: rgba(0,0,255,0.9); + } + puppeteer-mouse-pointer.button-3 { + transition: none; + border-radius: 4px; + } + puppeteer-mouse-pointer.button-4 { + transition: none; + border-color: rgba(255,0,0,0.9); + } + puppeteer-mouse-pointer.button-5 { + transition: none; + border-color: rgba(0,255,0,0.9); + } + `; + document.head.appendChild(styleElement); + document.body.appendChild(box); + document.addEventListener( + "mousemove", + (event) => { + box.style.left = event.pageX + "px"; + box.style.top = event.pageY + "px"; + updateButtons(event.buttons); + }, + true + ); + document.addEventListener( + "mousedown", + (event) => { + updateButtons(event.buttons); + box.classList.add("button-" + event.which); + }, + true + ); + document.addEventListener( + "mouseup", + (event) => { + updateButtons(event.buttons); + box.classList.remove("button-" + event.which); + }, + true + ); + function updateButtons(buttons) { + for (let i = 0; i < 5; i++) + box.classList.toggle("button-" + i, buttons & (1 << i)); + } + }, + false + ); + }); +} + +module.exports = { installMouseHelper }; diff --git a/package-lock.json b/package-lock.json index 2c446a3e..fb8eb4bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "np": "^10.0.0", "npm-watch": "^0.11.0", "php-express": "^0.0.3", + "robotjs": "^0.6.0", "swiper": "^11.0.7", "text-zoom-event": "^1.7.0", "wicg-inert": "^3.1.2" @@ -3679,6 +3680,11 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, "node_modules/archive-type": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-2.1.0.tgz", @@ -3740,6 +3746,15 @@ "semver": "bin/semver" } }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5855,6 +5870,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -6411,6 +6431,11 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -7705,6 +7730,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7722,6 +7752,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9495,6 +9536,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/expand-tilde": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", @@ -10220,6 +10269,64 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gaze": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", @@ -10347,6 +10454,11 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/github-url-from-git": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", @@ -10873,6 +10985,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -15635,6 +15752,11 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15645,6 +15767,16 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "node_modules/native-request": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz", @@ -15722,6 +15854,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "dependencies": { + "semver": "^5.4.1" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/node-fetch": { "version": "2.6.13", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", @@ -15829,6 +15977,11 @@ "semver": "bin/semver" } }, + "node_modules/noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==" + }, "node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -16352,6 +16505,17 @@ "npm-watch": "cli.js" } }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -17801,6 +17965,83 @@ "node": ">= 0.4" } }, + "node_modules/prebuild-install": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz", + "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==", + "dependencies": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prebuild-install/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/prebuild-install/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -17982,7 +18223,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -19012,6 +19252,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robotjs": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/robotjs/-/robotjs-0.6.0.tgz", + "integrity": "sha512-6pRWI3d+CBZqCXT/rsJfabbZoELua+jTeXilG27F8Jvix/J2BYZ0O7Tly2WCmXyqw5xYdCvOwvCeLRHEtXkt4w==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0", + "node-abi": "^2.13.0", + "prebuild-install": "^5.3.3" + } + }, "node_modules/robust-predicates": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", @@ -19346,6 +19597,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -19477,6 +19733,57 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/simple-update-notifier": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", @@ -21918,6 +22225,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "engines": { + "node": ">=4" + } + }, "node_modules/which-typed-array": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", @@ -21942,6 +22257,14 @@ "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz", "integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang==" }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", @@ -24919,6 +25242,11 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, "archive-type": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-2.1.0.tgz", @@ -24970,6 +25298,15 @@ } } }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -26681,6 +27018,11 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -27087,6 +27429,11 @@ } } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -28033,6 +28380,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -28043,6 +28395,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -29347,6 +29704,11 @@ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-4.0.0.tgz", "integrity": "sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==" }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, "expand-tilde": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", @@ -29908,6 +30270,54 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gaze": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", @@ -29996,6 +30406,11 @@ "assert-plus": "^1.0.0" } }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "github-url-from-git": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz", @@ -30390,6 +30805,11 @@ "has-symbols": "^1.0.3" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -33871,6 +34291,11 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -33881,6 +34306,16 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "nan": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "native-request": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.0.tgz", @@ -33936,6 +34371,21 @@ } } }, + "node-abi": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", + "requires": { + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + } + } + }, "node-fetch": { "version": "2.6.13", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", @@ -34017,6 +34467,11 @@ } } }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==" + }, "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -34360,6 +34815,17 @@ "through2": "^4.0.2" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -35415,6 +35881,73 @@ "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true }, + "prebuild-install": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz", + "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -35562,7 +36095,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -36322,6 +36854,16 @@ "glob": "^7.1.3" } }, + "robotjs": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/robotjs/-/robotjs-0.6.0.tgz", + "integrity": "sha512-6pRWI3d+CBZqCXT/rsJfabbZoELua+jTeXilG27F8Jvix/J2BYZ0O7Tly2WCmXyqw5xYdCvOwvCeLRHEtXkt4w==", + "requires": { + "nan": "^2.14.0", + "node-abi": "^2.13.0", + "prebuild-install": "^5.3.3" + } + }, "robust-predicates": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", @@ -36574,6 +37116,11 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "set-function-length": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", @@ -36675,6 +37222,36 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + } + } + }, "simple-update-notifier": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", @@ -38547,6 +39124,11 @@ "is-symbol": "^1.0.3" } }, + "which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==" + }, "which-typed-array": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", @@ -38565,6 +39147,14 @@ "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz", "integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang==" }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "widest-line": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", diff --git a/package.json b/package.json index 4b327ee1..8ba91cf3 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "np": "^10.0.0", "npm-watch": "^0.11.0", "php-express": "^0.0.3", + "robotjs": "^0.6.0", "swiper": "^11.0.7", "text-zoom-event": "^1.7.0", "wicg-inert": "^3.1.2" From 3a64603b507d7aac95da43348de4590a57efcedf Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Tue, 28 May 2024 15:30:07 -0400 Subject: [PATCH 007/274] Move the TOC code into a separate module file --- js/global.js | 55 ++---------------------- js/modules/enable-toc.js | 83 ++++++++++++++++++++++++++++++++++++ js/modules/es4/enable-toc.js | 82 +++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 52 deletions(-) create mode 100644 js/modules/enable-toc.js create mode 100644 js/modules/es4/enable-toc.js diff --git a/js/global.js b/js/global.js index 54036c9c..92a25f67 100644 --- a/js/global.js +++ b/js/global.js @@ -16,6 +16,7 @@ import EnableFlyout from './modules/enable-flyout.js'; import enableVisibleOnFocus from './modules/enable-visible-on-focus.js'; import offscreenObserver from './modules/offscreen-observer.js'; import textZoom from './demos/hero-image-text-resize.js'; +import tableOfContents from './modules/enable-toc.js'; function scrollToEl(el) { /* @@ -80,15 +81,6 @@ function initEnable() { }, ); - // Table of Contents container setup - const toc = document.createElement('aside'); - toc.setAttribute('id', 'toc'); - const tocHeading = document.createElement('h2'); - tocHeading.textContent = 'Page Overview'; - const tocList = document.createElement('ol'); - tocList.setAttribute('class', 'toc-level-1'); - let prevHeadingLevel = 0; - let tocNode = tocList; pauseAnimControl.init(); @@ -149,55 +141,14 @@ function initEnable() { el.innerHTML = `${el.innerHTML}` } - /** - * Generates a table of contents for your document based on the headings - * present. Anchors are injected into the document and the - * entries in the table of contents are linked to them. - */ - const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); - if (headingLevel === 1) { - prevHeadingLevel = headingLevel; - return; - } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { - const subList = document.createElement('ul'); - subList.setAttribute('class', `toc-level-${headingLevel - 1}`); - tocNode.appendChild(subList); - tocNode = subList; - } - - tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); - - const tocItem = document.createElement('li'); - tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); - - const tocLink = el.id ? document.createElement('a') : document.createElement('p'); - el.id && tocLink.setAttribute('href', `#${el.id}`); - tocLink.textContent = el.textContent; - - tocItem.appendChild(tocLink); - tocNode.appendChild(tocItem); - tocNode = tocItem; - prevHeadingLevel = headingLevel; return; }) }; - // Add the generated Table of Contents to the top of pages - if (location.href.indexOf('index.php') === -1 && location.href.indexOf('faq.php') === -1) { - toc.appendChild(tocHeading); - toc.appendChild(tocList); - const h1 = document.getElementsByTagName('h1')?.[0]; - const main = document.getElementsByTagName('main')?.[0]; - - // Insert the TOC after the H1, or just inside the main element - if (h1) { - h1.after(toc); - } else { - main.insertAdjacentElement('afterbegin', toc); - } - } focusDeepLink(); + + tableOfContents.init(['/index.php', '/faq.php']); } initEnable(); diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js new file mode 100644 index 00000000..eaf91f8c --- /dev/null +++ b/js/modules/enable-toc.js @@ -0,0 +1,83 @@ +'use strict' + +/******************************************************************************* + * enable-toc.js - UI for the Table of Contents + * + * Written by Alison Hall + * Part of the Enable accessible component library. + * Version 1.0 released June 2024 + * + * More information about this script available at: + * https://www.useragentman.com/enable/ + * + * Released under the MIT License. + ******************************************************************************/ + +const tableOfContents = new function() { + this.init = (skipPages = []) => { + // Table of Contents container setup + const toc = document.createElement('aside'); + toc.setAttribute('id', 'toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Page Overview'; + const tocList = document.createElement('ol'); + tocList.setAttribute('class', 'toc-level-1'); + let prevHeadingLevel = 0; + let tocNode = tocList; + + // Skip the Table of Contents on certain pages + if (skipPages.includes(location.pathname)) { + return; + } + + document + .querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]') + .forEach((el) => { + /** + * Generates a table of contents for your document based on the headings + * present. Anchors are injected into the document and the + * entries in the table of contents are linked to them. + */ + const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); + if (headingLevel === 1) { + prevHeadingLevel = headingLevel; + return; + } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { + const subList = document.createElement('ul'); + subList.setAttribute('class', `toc-level-${headingLevel - 1}`); + tocNode.appendChild(subList); + tocNode = subList; + } + + tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); + + const tocItem = document.createElement('li'); + tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); + + const tocLink = el.id ? document.createElement('a') : document.createElement('p'); + el.id && tocLink.setAttribute('href', `#${el.id}`); + tocLink.textContent = el.textContent; + + tocItem.appendChild(tocLink); + tocNode.appendChild(tocItem); + tocNode = tocItem; + prevHeadingLevel = headingLevel; + return; + }) + + // Add the generated Table of Contents to the top of pages + toc.appendChild(tocHeading); + toc.appendChild(tocList); + const h1 = document.getElementsByTagName('h1')?.[0]; + const main = document.getElementsByTagName('main')?.[0]; + + // Insert the TOC after the H1, or just inside the main element + if (h1) { + h1.after(toc); + } else { + main.insertAdjacentElement('afterbegin', toc); + } + } +} + +export default tableOfContents; diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js new file mode 100644 index 00000000..7dbbb724 --- /dev/null +++ b/js/modules/es4/enable-toc.js @@ -0,0 +1,82 @@ +'use strict' + +/******************************************************************************* + * enable-toc.js - UI for the Table of Contents + * + * Written by Alison Hall + * Part of the Enable accessible component library. + * Version 1.0 released June 2024 + * + * More information about this script available at: + * https://www.useragentman.com/enable/ + * + * Released under the MIT License. + ******************************************************************************/ + +const tableOfContents = new function() { + this.init = (skipPages = []) => { + // Table of Contents container setup + const toc = document.createElement('aside'); + toc.setAttribute('id', 'toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Page Overview'; + const tocList = document.createElement('ol'); + tocList.setAttribute('class', 'toc-level-1'); + let prevHeadingLevel = 0; + let tocNode = tocList; + + // Skip the Table of Contents on certain pages + if (skipPages.includes(location.pathname)) { + return; + } + + document + .querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]') + .forEach((el) => { + /** + * Generates a table of contents for your document based on the headings + * present. Anchors are injected into the document and the + * entries in the table of contents are linked to them. + */ + const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); + if (headingLevel === 1) { + prevHeadingLevel = headingLevel; + return; + } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { + const subList = document.createElement('ul'); + subList.setAttribute('class', `toc-level-${headingLevel - 1}`); + tocNode.appendChild(subList); + tocNode = subList; + } + + tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); + + const tocItem = document.createElement('li'); + tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); + + const tocLink = el.id ? document.createElement('a') : document.createElement('p'); + el.id && tocLink.setAttribute('href', `#${el.id}`); + tocLink.textContent = el.textContent; + + tocItem.appendChild(tocLink); + tocNode.appendChild(tocItem); + tocNode = tocItem; + prevHeadingLevel = headingLevel; + return; + }) + + // Add the generated Table of Contents to the top of pages + toc.appendChild(tocHeading); + toc.appendChild(tocList); + const h1 = document.getElementsByTagName('h1')?.[0]; + const main = document.getElementsByTagName('main')?.[0]; + + // Insert the TOC after the H1, or just inside the main element + if (h1) { + h1.after(toc); + } else { + main.insertAdjacentElement('afterbegin', toc); + } + } +} + From 819906bc3f408b450253e3e11aabd1fa7d601dc9 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Tue, 28 May 2024 15:51:22 -0400 Subject: [PATCH 008/274] Handle scenario when h1 is not first heading --- js/modules/enable-toc.js | 2 +- js/modules/es4/enable-toc.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index eaf91f8c..b6812cc8 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -39,7 +39,7 @@ const tableOfContents = new function() { * entries in the table of contents are linked to them. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); - if (headingLevel === 1) { + if (headingLevel === 1 || prevHeadingLevel === 0) { prevHeadingLevel = headingLevel; return; } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index 7dbbb724..9ee61a7c 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -39,7 +39,7 @@ const tableOfContents = new function() { * entries in the table of contents are linked to them. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); - if (headingLevel === 1) { + if (headingLevel === 1 || prevHeadingLevel === 0) { prevHeadingLevel = headingLevel; return; } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { From c51ee17d33cf908536170f18774de1655786c795 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 29 May 2024 17:43:47 -0400 Subject: [PATCH 009/274] Improve TOC component and styling --- images/icons/toc.svg | 4 + js/global.js | 2 +- js/modules/enable-toc.js | 181 +++++++++++++++++++++++++++++++---- js/modules/es4/enable-toc.js | 181 +++++++++++++++++++++++++++++++---- less/shared/all.less | 1 + less/shared/enable-toc.less | 131 +++++++++++++++++++++++++ less/shared/landmarks.less | 41 -------- 7 files changed, 459 insertions(+), 82 deletions(-) create mode 100644 images/icons/toc.svg create mode 100644 less/shared/enable-toc.less diff --git a/images/icons/toc.svg b/images/icons/toc.svg new file mode 100644 index 00000000..b2e6eb64 --- /dev/null +++ b/images/icons/toc.svg @@ -0,0 +1,4 @@ + + + bullet list + diff --git a/js/global.js b/js/global.js index 92a25f67..c2fc55bb 100644 --- a/js/global.js +++ b/js/global.js @@ -148,7 +148,7 @@ function initEnable() { focusDeepLink(); - tableOfContents.init(['/index.php', '/faq.php']); + tableOfContents.init(['/index.php', '/faq.php'], true, true); } initEnable(); diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index b6812cc8..b3b1fcdf 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -13,30 +13,45 @@ * Released under the MIT License. ******************************************************************************/ +function splitCookies() { + const list = {}; + document?.cookie?.split(';')?.forEach((cookie) => { + const parts = cookie.split('='); + list[parts.shift().trim()] = decodeURI(parts.join('=')); + }); + return list; +} + +function getCookie(name) { + return splitCookies()[name]; +} + +function setCookie(name, value) { + document.cookie = `${name}=${value}; path=/;`; +} + +function deleteCookie(name) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; +} + const tableOfContents = new function() { - this.init = (skipPages = []) => { + let toc; + + this.createContent = (numberFirstLevelHeadings) => { // Table of Contents container setup - const toc = document.createElement('aside'); - toc.setAttribute('id', 'toc'); - const tocHeading = document.createElement('h2'); - tocHeading.textContent = 'Page Overview'; - const tocList = document.createElement('ol'); + const tocList = document.createElement(numberFirstLevelHeadings ? 'ol' : 'ul'); tocList.setAttribute('class', 'toc-level-1'); + let prevHeadingLevel = 0; let tocNode = tocList; - // Skip the Table of Contents on certain pages - if (skipPages.includes(location.pathname)) { - return; - } - document .querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]') .forEach((el) => { /** * Generates a table of contents for your document based on the headings - * present. Anchors are injected into the document and the - * entries in the table of contents are linked to them. + * present. The + * entries in the table of contents are linked to the headings. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); if (headingLevel === 1 || prevHeadingLevel === 0) { @@ -65,17 +80,143 @@ const tableOfContents = new function() { return; }) - // Add the generated Table of Contents to the top of pages - toc.appendChild(tocHeading); - toc.appendChild(tocList); - const h1 = document.getElementsByTagName('h1')?.[0]; + return tocList; + } + + /* Action when clicking the toggle TOC button */ + this.toggleTOC = () => { + const toc = document.getElementById('toggle-toc'); + const toggleButton = document.querySelector('.toggle-toc-button'); + const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; + if (isExpanded) { + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + } else { + toggleButton?.setAttribute('aria-expanded', 'true'); + toc.style.display = 'grid'; + } + } + + /* Initial code to add the TOC as a sidebar */ + this.appendAsSidebar = () => { + // Create the nav and heading elements + const nav = document.createElement('nav'); + nav.setAttribute('id', 'sidebar-toc'); + nav.setAttribute('class', 'toc sidebar-toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Contents'; + + // Create the button to hide the TOC and move it to the toggle button + const hideSidebarButton = document.createElement('button'); + hideSidebarButton.textContent = 'Hide'; + hideSidebarButton.setAttribute('class', 'hide-sidebar-button'); + hideSidebarButton.setAttribute('aria-label', 'Hide Table of Contents sidebar'); + hideSidebarButton.addEventListener('click', this.moveToToggleButton); + + // Append the elements to the nav + nav.appendChild(tocHeading); + nav.appendChild(hideSidebarButton); + nav.appendChild(toc); + + // Insert the nav and button elements const main = document.getElementsByTagName('main')?.[0]; + main?.insertAdjacentElement('beforebegin', nav); + } - // Insert the TOC after the H1, or just inside the main element + /* Initial code to add the TOC as a toggle button beside the header */ + this.appendAsToggleButton = () => { + // Create the nav and heading elements + const nav = document.createElement('nav'); + nav.setAttribute('id', 'toggle-toc'); + nav.setAttribute('class', 'toc toggle-toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Contents'; + + // Create the button to move the TOC to the sidebar + const moveToSidebarButton = document.createElement('button'); + moveToSidebarButton.textContent = 'Move to Sidebar'; + moveToSidebarButton.setAttribute('class', 'move-to-sidebar-button'); + moveToSidebarButton.setAttribute('aria-label', 'Move Table of Contents to Sidebar'); + moveToSidebarButton.addEventListener('click', this.moveToSidebar); + + // Append the elements to the nav + nav.appendChild(tocHeading); + nav.appendChild(moveToSidebarButton); + const clonedToc = toc.cloneNode(true); + nav.appendChild(clonedToc); + + // Create the button to toggle the TOC + const toggleButton = document.createElement('button'); + toggleButton.setAttribute('class', 'toggle-toc-button'); + toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); + toggleButton.setAttribute('aria-controls', 'toggle-toc'); + toggleButton.setAttribute('aria-haspopup', 'true'); + toggleButton.setAttribute('aria-expanded', 'false'); + toggleButton.innerHTML = ''; + toggleButton.addEventListener('click', this.toggleTOC); + + // Insert the nav and button elements + const h1 = document.getElementsByTagName('h1')?.[0]; if (h1) { - h1.after(toc); + h1.before(toggleButton); + h1.after(nav); } else { - main.insertAdjacentElement('afterbegin', toc); + const main = document.getElementsByTagName('main')?.[0]; + main.insertAdjacentElement('beforebegin', toggleButton); + main.insertAdjacentElement('beforebegin', nav); + } + } + + this.moveToSidebar = () => { + // Update the body class to show the TOC as a sidebar + document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + + // Hide the TOC toggle button and content + const toc = document.getElementById('toggle-toc'); + const toggleButton = document.querySelector('.toggle-toc-button'); + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + + // Set the cookie to remember the sidebar state + setCookie('tocAsSidebar', 'true'); + + // Focus on the Hide button + document.querySelector('.hide-sidebar-button').focus(); + } + + this.moveToToggleButton = () => { + // Update the body class to not show the TOC as a sidebar + document.getElementsByTagName('body')[0].classList.remove('toc-as-sidebar'); + + // Set the cookie to remember the sidebar state + setCookie('tocAsSidebar', 'false'); + + // Focus on the TOC toggle button + document.querySelector('.toggle-toc-button').focus(); + } + + this.init = (skipPages = [], showAsSidebarDefault = true, numberFirstLevelHeadings = true) => { + // Skip the Table of Contents on certain pages + if (skipPages.includes(location.pathname)) { + return; + } + + // Create the Table of Contents + toc = this.createContent(numberFirstLevelHeadings); + + // Insert the TOC beside the main content and/or beside the H1 + this.appendAsSidebar(); + this.appendAsToggleButton(); + + // Check if the TOC should be shown as a sidebar, and update the body class + const sidebarCookieValue = getCookie('tocAsSidebar'); + if (sidebarCookieValue === 'true' || (!sidebarCookieValue && showAsSidebarDefault)) { + document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + } + + // Set the default tocAsSidebar cookie if it doesn't exist + if (!sidebarCookieValue) { + setCookie('tocAsSidebar', `${showAsSidebarDefault}`); } } } diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index 9ee61a7c..52263d99 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -13,30 +13,45 @@ * Released under the MIT License. ******************************************************************************/ +function splitCookies() { + const list = {}; + document?.cookie?.split(';')?.forEach((cookie) => { + const parts = cookie.split('='); + list[parts.shift().trim()] = decodeURI(parts.join('=')); + }); + return list; +} + +function getCookie(name) { + return splitCookies()[name]; +} + +function setCookie(name, value) { + document.cookie = `${name}=${value}; path=/;`; +} + +function deleteCookie(name) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; +} + const tableOfContents = new function() { - this.init = (skipPages = []) => { + let toc; + + this.createContent = (numberFirstLevelHeadings) => { // Table of Contents container setup - const toc = document.createElement('aside'); - toc.setAttribute('id', 'toc'); - const tocHeading = document.createElement('h2'); - tocHeading.textContent = 'Page Overview'; - const tocList = document.createElement('ol'); + const tocList = document.createElement(numberFirstLevelHeadings ? 'ol' : 'ul'); tocList.setAttribute('class', 'toc-level-1'); + let prevHeadingLevel = 0; let tocNode = tocList; - // Skip the Table of Contents on certain pages - if (skipPages.includes(location.pathname)) { - return; - } - document .querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]') .forEach((el) => { /** * Generates a table of contents for your document based on the headings - * present. Anchors are injected into the document and the - * entries in the table of contents are linked to them. + * present. The + * entries in the table of contents are linked to the headings. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); if (headingLevel === 1 || prevHeadingLevel === 0) { @@ -65,17 +80,143 @@ const tableOfContents = new function() { return; }) - // Add the generated Table of Contents to the top of pages - toc.appendChild(tocHeading); - toc.appendChild(tocList); - const h1 = document.getElementsByTagName('h1')?.[0]; + return tocList; + } + + /* Action when clicking the toggle TOC button */ + this.toggleTOC = () => { + const toc = document.getElementById('toggle-toc'); + const toggleButton = document.querySelector('.toggle-toc-button'); + const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; + if (isExpanded) { + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + } else { + toggleButton?.setAttribute('aria-expanded', 'true'); + toc.style.display = 'grid'; + } + } + + /* Initial code to add the TOC as a sidebar */ + this.appendAsSidebar = () => { + // Create the nav and heading elements + const nav = document.createElement('nav'); + nav.setAttribute('id', 'sidebar-toc'); + nav.setAttribute('class', 'toc sidebar-toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Contents'; + + // Create the button to hide the TOC and move it to the toggle button + const hideSidebarButton = document.createElement('button'); + hideSidebarButton.textContent = 'Hide'; + hideSidebarButton.setAttribute('class', 'hide-sidebar-button'); + hideSidebarButton.setAttribute('aria-label', 'Hide Table of Contents sidebar'); + hideSidebarButton.addEventListener('click', this.moveToToggleButton); + + // Append the elements to the nav + nav.appendChild(tocHeading); + nav.appendChild(hideSidebarButton); + nav.appendChild(toc); + + // Insert the nav and button elements const main = document.getElementsByTagName('main')?.[0]; + main?.insertAdjacentElement('beforebegin', nav); + } - // Insert the TOC after the H1, or just inside the main element + /* Initial code to add the TOC as a toggle button beside the header */ + this.appendAsToggleButton = () => { + // Create the nav and heading elements + const nav = document.createElement('nav'); + nav.setAttribute('id', 'toggle-toc'); + nav.setAttribute('class', 'toc toggle-toc'); + const tocHeading = document.createElement('h2'); + tocHeading.textContent = 'Contents'; + + // Create the button to move the TOC to the sidebar + const moveToSidebarButton = document.createElement('button'); + moveToSidebarButton.textContent = 'Move to Sidebar'; + moveToSidebarButton.setAttribute('class', 'move-to-sidebar-button'); + moveToSidebarButton.setAttribute('aria-label', 'Move Table of Contents to Sidebar'); + moveToSidebarButton.addEventListener('click', this.moveToSidebar); + + // Append the elements to the nav + nav.appendChild(tocHeading); + nav.appendChild(moveToSidebarButton); + const clonedToc = toc.cloneNode(true); + nav.appendChild(clonedToc); + + // Create the button to toggle the TOC + const toggleButton = document.createElement('button'); + toggleButton.setAttribute('class', 'toggle-toc-button'); + toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); + toggleButton.setAttribute('aria-controls', 'toggle-toc'); + toggleButton.setAttribute('aria-haspopup', 'true'); + toggleButton.setAttribute('aria-expanded', 'false'); + toggleButton.innerHTML = ''; + toggleButton.addEventListener('click', this.toggleTOC); + + // Insert the nav and button elements + const h1 = document.getElementsByTagName('h1')?.[0]; if (h1) { - h1.after(toc); + h1.before(toggleButton); + h1.after(nav); } else { - main.insertAdjacentElement('afterbegin', toc); + const main = document.getElementsByTagName('main')?.[0]; + main.insertAdjacentElement('beforebegin', toggleButton); + main.insertAdjacentElement('beforebegin', nav); + } + } + + this.moveToSidebar = () => { + // Update the body class to show the TOC as a sidebar + document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + + // Hide the TOC toggle button and content + const toc = document.getElementById('toggle-toc'); + const toggleButton = document.querySelector('.toggle-toc-button'); + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + + // Set the cookie to remember the sidebar state + setCookie('tocAsSidebar', 'true'); + + // Focus on the Hide button + document.querySelector('.hide-sidebar-button').focus(); + } + + this.moveToToggleButton = () => { + // Update the body class to not show the TOC as a sidebar + document.getElementsByTagName('body')[0].classList.remove('toc-as-sidebar'); + + // Set the cookie to remember the sidebar state + setCookie('tocAsSidebar', 'false'); + + // Focus on the TOC toggle button + document.querySelector('.toggle-toc-button').focus(); + } + + this.init = (skipPages = [], showAsSidebarDefault = true, numberFirstLevelHeadings = true) => { + // Skip the Table of Contents on certain pages + if (skipPages.includes(location.pathname)) { + return; + } + + // Create the Table of Contents + toc = this.createContent(numberFirstLevelHeadings); + + // Insert the TOC beside the main content and/or beside the H1 + this.appendAsSidebar(); + this.appendAsToggleButton(); + + // Check if the TOC should be shown as a sidebar, and update the body class + const sidebarCookieValue = getCookie('tocAsSidebar'); + if (sidebarCookieValue === 'true' || (!sidebarCookieValue && showAsSidebarDefault)) { + document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + } + + // Set the default tocAsSidebar cookie if it doesn't exist + if (!sidebarCookieValue) { + setCookie('tocAsSidebar', `${showAsSidebarDefault}`); } } } diff --git a/less/shared/all.less b/less/shared/all.less index 9cd77c2a..8bf590e5 100755 --- a/less/shared/all.less +++ b/less/shared/all.less @@ -3,6 +3,7 @@ @import "defaults"; @import "a11y"; @import "landmarks"; +@import "enable-toc"; @import "../../fonts/specify/stylesheet.css"; @import "../../fonts/modeseven/stylesheet.css"; @import "../../fonts/monice/stylesheet.css"; diff --git a/less/shared/enable-toc.less b/less/shared/enable-toc.less new file mode 100644 index 00000000..a3b1570e --- /dev/null +++ b/less/shared/enable-toc.less @@ -0,0 +1,131 @@ +@import "mixins-and-vars"; + +.toc-as-sidebar { + main#main { + @media @tablet-up { + margin-left: 25%; + } + } +} + +nav.toc { + background-color: white; + grid-template-columns: 1fr auto; + align-items: end; + overflow-y: auto; + padding: 0.5em 0.8em 0.5em 0.5em; + + h2 { + font-weight: normal; + border-bottom: none; + margin-left: 8px; + line-height: 1em; + + &::before { + content: none; + } + } + + ol, + ul { + margin-left: 0.5em; + } + + li { + margin-bottom: 8px; + + a { + text-decoration: none; + } + } + + .move-to-sidebar-button, + .hide-sidebar-button { + padding: 5px 10px; + border: none; + margin-bottom: 2px; + + &:hover { + background-color: #ccc; + } + } + + .toc-level-1 { + grid-column-start: 1; + grid-column-end: -1; + max-width: 100%; + border-top: 1px solid gray; + padding-top: 20px; + } +} + +nav.sidebar-toc { + display: none; + position: sticky; + float: left; + top: 2.5rem; + right: 0; + bottom: 0; + width: 25%; + max-width: 300px; + + h2 { + margin-top: 20px; + } + + .toc-as-sidebar & { + @media @tablet-up { + display: grid; + } + } +} + +nav.toggle-toc { + display: none; + position: absolute; + z-index: 1; + border-radius: 5px; + box-shadow: gray 0 0 5px; + max-width: 500px; + z-index: 2; + + .move-to-sidebar-button { + display: none; + + @media @tablet-up { + display: grid; + } + } + + h2 { + scroll-margin-top: 0; + margin-top: 8px; + } + + .toc-as-sidebar & { + @media @tablet-up { + display: none; + } + } +} + +.toggle-toc-button { + position: relative; + top: -2px; + display: inline-block; + padding: 5px 10px; + margin-left: -10px; + border: none; + background: none; + cursor: pointer; + + .toc-as-sidebar & { + @media @tablet-up { + display: none; + } + } + + & ~ h1 { + display: inline-block; + } +} diff --git a/less/shared/landmarks.less b/less/shared/landmarks.less index d9c41c08..707c6679 100755 --- a/less/shared/landmarks.less +++ b/less/shared/landmarks.less @@ -228,44 +228,3 @@ footer, color: #ccccff; } } - -#toc { - padding: 1em; - background: #f0f8ff; - - @media @tablet-up { - float: right; - max-width: 50%; - margin-left: 2em; - } - - h2 { - font-weight: normal; - - &::before { - content: none; - } - } - - ol, - ul { - margin-left: 0.5em; - } - - & ~ h2 { - clear: right; - } - - // If the first element after the TOC is an h2, have the TOC be the full width - &:has(+ h2) { - @media @tablet-up { - float: left; - max-width: 100%; - margin-left: 0; - } - - & ~ h2 { - clear: left; - } - } -} \ No newline at end of file From 6256d0a1c13ebe22cf3f84e1a3b18baf007a1bb5 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 29 May 2024 17:45:28 -0400 Subject: [PATCH 010/274] Fix site nav styling selector --- less/shared/landmarks.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/less/shared/landmarks.less b/less/shared/landmarks.less index 707c6679..6c38c889 100755 --- a/less/shared/landmarks.less +++ b/less/shared/landmarks.less @@ -53,7 +53,7 @@ header, } } -nav { +nav.site__nav { position: static; background: #000000; padding: 0.4em; From c3276d935a1428bf988d66095e3a73456503984e Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Wed, 29 May 2024 17:46:10 -0400 Subject: [PATCH 011/274] Fix HTML example button listener to only work on example --- js/demos/html-button-example.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/demos/html-button-example.js b/js/demos/html-button-example.js index 0cbd8b20..38eb1b04 100644 --- a/js/demos/html-button-example.js +++ b/js/demos/html-button-example.js @@ -4,6 +4,7 @@ var htmlButtonExample = new (function () { if ( target.tagName === 'BUTTON' && target.closest('main') && + target.closest('.enable-example') && target.id !== 'aria-js-disabled-button' ) { alert('this HTML button has been triggered'); From 956569526beb2f00cd0b42a377730de2ada1c4db Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 14:33:31 -0400 Subject: [PATCH 012/274] Update the location of the enable-toc stylesheet, update generated css --- css/enable-toc.css | 117 ++++++++++++++++++++++++++++++ css/shared/all.css | 40 +--------- css/shared/landmarks.css | 40 +--------- less/{shared => }/enable-toc.less | 2 +- 4 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 css/enable-toc.css rename less/{shared => }/enable-toc.less (98%) diff --git a/css/enable-toc.css b/css/enable-toc.css new file mode 100644 index 00000000..9e969468 --- /dev/null +++ b/css/enable-toc.css @@ -0,0 +1,117 @@ +.font-heading-3 { + font-family: "OpenSans", "Helvetica", "Arial", sans-serif; + font-weight: bold; + padding-top: 20px; + font-size: 1.25rem; + border-bottom: double 3px #000; +} +@media only screen and (min-width: 720px) { + .toc-as-sidebar main#main { + margin-left: 25%; + } +} +nav.toc { + background-color: white; + grid-template-columns: 1fr auto; + align-items: end; + overflow-y: auto; + padding: 0.5em 0.8em 0.5em 0.5em; +} +nav.toc h2 { + font-weight: normal; + border-bottom: none; + margin-left: 8px; + line-height: 1em; +} +nav.toc h2::before { + content: none; +} +nav.toc ol, +nav.toc ul { + margin-left: 0.5em; +} +nav.toc li { + margin-bottom: 8px; +} +nav.toc li a { + text-decoration: none; +} +nav.toc .move-to-sidebar-button, +nav.toc .hide-sidebar-button { + padding: 5px 10px; + border: none; + margin-bottom: 2px; +} +nav.toc .move-to-sidebar-button:hover, +nav.toc .hide-sidebar-button:hover { + background-color: #ccc; +} +nav.toc .toc-level-1 { + grid-column-start: 1; + grid-column-end: -1; + max-width: 100%; + border-top: 1px solid gray; + padding-top: 20px; +} +nav.sidebar-toc { + display: none; + position: sticky; + float: left; + top: 2.5rem; + right: 0; + bottom: 0; + width: 25%; + max-width: 300px; +} +nav.sidebar-toc h2 { + margin-top: 20px; +} +@media only screen and (min-width: 720px) { + .toc-as-sidebar nav.sidebar-toc { + display: grid; + } +} +nav.toggle-toc { + display: none; + position: absolute; + z-index: 1; + border-radius: 5px; + box-shadow: gray 0 0 5px; + max-width: 500px; + z-index: 2; +} +nav.toggle-toc .move-to-sidebar-button { + display: none; +} +@media only screen and (min-width: 720px) { + nav.toggle-toc .move-to-sidebar-button { + display: grid; + } +} +nav.toggle-toc h2 { + scroll-margin-top: 0; + margin-top: 8px; +} +@media only screen and (min-width: 720px) { + .toc-as-sidebar nav.toggle-toc { + display: none; + } +} +.toggle-toc-button { + position: relative; + top: -2px; + display: inline-block; + padding: 5px 10px; + margin-left: -10px; + border: none; + background: none; + cursor: pointer; +} +@media only screen and (min-width: 720px) { + .toc-as-sidebar .toggle-toc-button { + display: none; + } +} +.toggle-toc-button ~ h1 { + display: inline-block; +} diff --git a/css/shared/all.css b/css/shared/all.css index 6a2699b1..aa6e268d 100644 --- a/css/shared/all.css +++ b/css/shared/all.css @@ -630,17 +630,17 @@ header .search-form, top: 1em; right: 1em; } -nav { +nav.site__nav { position: static; background: #000000; padding: 0.4em; line-height: 1.6; } -nav ul li { +nav.site__nav ul li { display: inline-block; margin-right: 1em; } -nav ul li a { +nav.site__nav ul li a { color: white; text-decoration: none; } @@ -773,37 +773,3 @@ footer a, [role="contentinfo"] a { color: #ccccff; } -#toc { - padding: 1em; - background: #f0f8ff; -} -@media only screen and (min-width: 720px) { - #toc { - float: right; - max-width: 50%; - margin-left: 2em; - } -} -#toc h2 { - font-weight: normal; -} -#toc h2::before { - content: none; -} -#toc ol, -#toc ul { - margin-left: 0.5em; -} -#toc ~ h2 { - clear: right; -} -@media only screen and (min-width: 720px) { - #toc:has(+ h2) { - float: left; - max-width: 100%; - margin-left: 0; - } -} -#toc:has(+ h2) ~ h2 { - clear: left; -} diff --git a/css/shared/landmarks.css b/css/shared/landmarks.css index 6a2699b1..aa6e268d 100644 --- a/css/shared/landmarks.css +++ b/css/shared/landmarks.css @@ -630,17 +630,17 @@ header .search-form, top: 1em; right: 1em; } -nav { +nav.site__nav { position: static; background: #000000; padding: 0.4em; line-height: 1.6; } -nav ul li { +nav.site__nav ul li { display: inline-block; margin-right: 1em; } -nav ul li a { +nav.site__nav ul li a { color: white; text-decoration: none; } @@ -773,37 +773,3 @@ footer a, [role="contentinfo"] a { color: #ccccff; } -#toc { - padding: 1em; - background: #f0f8ff; -} -@media only screen and (min-width: 720px) { - #toc { - float: right; - max-width: 50%; - margin-left: 2em; - } -} -#toc h2 { - font-weight: normal; -} -#toc h2::before { - content: none; -} -#toc ol, -#toc ul { - margin-left: 0.5em; -} -#toc ~ h2 { - clear: right; -} -@media only screen and (min-width: 720px) { - #toc:has(+ h2) { - float: left; - max-width: 100%; - margin-left: 0; - } -} -#toc:has(+ h2) ~ h2 { - clear: left; -} diff --git a/less/shared/enable-toc.less b/less/enable-toc.less similarity index 98% rename from less/shared/enable-toc.less rename to less/enable-toc.less index a3b1570e..27aad0c4 100644 --- a/less/shared/enable-toc.less +++ b/less/enable-toc.less @@ -1,4 +1,4 @@ -@import "mixins-and-vars"; +@import "shared/mixins-and-vars"; .toc-as-sidebar { main#main { From 23f08b8cadfb8dfb1213742dca19f5304c56b5ad Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 14:34:20 -0400 Subject: [PATCH 013/274] Load the enable-toc stylesheet on all pages --- less/shared/all.less | 1 - templates/includes/common-head-tags.php | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/less/shared/all.less b/less/shared/all.less index 8bf590e5..9cd77c2a 100755 --- a/less/shared/all.less +++ b/less/shared/all.less @@ -3,7 +3,6 @@ @import "defaults"; @import "a11y"; @import "landmarks"; -@import "enable-toc"; @import "../../fonts/specify/stylesheet.css"; @import "../../fonts/modeseven/stylesheet.css"; @import "../../fonts/monice/stylesheet.css"; diff --git a/templates/includes/common-head-tags.php b/templates/includes/common-head-tags.php index de4850eb..c9b0b435 100755 --- a/templates/includes/common-head-tags.php +++ b/templates/includes/common-head-tags.php @@ -40,7 +40,11 @@ rel="stylesheet" href="css/pause-animations-demo.css" /> - + + From 0b25bf71665b82292132de685f3fb5ce8107b34d Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 15:38:43 -0400 Subject: [PATCH 014/274] Use BEM for enable-toc classes and IDs --- css/enable-toc.css | 90 ++++++++++++------------ js/modules/enable-toc.js | 53 +++++++------- js/modules/es4/enable-toc.js | 53 +++++++------- less/enable-toc.less | 132 +++++++++++++++++------------------ 4 files changed, 163 insertions(+), 165 deletions(-) diff --git a/css/enable-toc.css b/css/enable-toc.css index 9e969468..7d9385f6 100644 --- a/css/enable-toc.css +++ b/css/enable-toc.css @@ -6,54 +6,80 @@ border-bottom: double 3px #000; } @media only screen and (min-width: 720px) { - .toc-as-sidebar main#main { + .enable-toc-as-sidebar main#main { margin-left: 25%; } } -nav.toc { +.enable-toc { background-color: white; grid-template-columns: 1fr auto; align-items: end; overflow-y: auto; padding: 0.5em 0.8em 0.5em 0.5em; } -nav.toc h2 { +.enable-toc h2 { font-weight: normal; border-bottom: none; margin-left: 8px; line-height: 1em; } -nav.toc h2::before { +.enable-toc h2::before { content: none; } -nav.toc ol, -nav.toc ul { +.enable-toc ol, +.enable-toc ul { margin-left: 0.5em; } -nav.toc li { +.enable-toc li { margin-bottom: 8px; } -nav.toc li a { +.enable-toc li a { text-decoration: none; } -nav.toc .move-to-sidebar-button, -nav.toc .hide-sidebar-button { +.enable-toc__move-to-sidebar-button, +.enable-toc__hide-sidebar-button { padding: 5px 10px; border: none; margin-bottom: 2px; } -nav.toc .move-to-sidebar-button:hover, -nav.toc .hide-sidebar-button:hover { +.enable-toc__move-to-sidebar-button:hover, +.enable-toc__hide-sidebar-button:hover { background-color: #ccc; } -nav.toc .toc-level-1 { +.enable-toc__move-to-sidebar-button { + display: none; +} +@media only screen and (min-width: 720px) { + .enable-toc__move-to-sidebar-button { + display: grid; + } +} +.enable-toc__level-1-content { grid-column-start: 1; grid-column-end: -1; max-width: 100%; border-top: 1px solid gray; padding-top: 20px; } -nav.sidebar-toc { +.enable-toc__toggle-button { + position: relative; + top: -2px; + display: inline-block; + padding: 5px 10px; + margin-left: -10px; + border: none; + background: none; + cursor: pointer; +} +@media only screen and (min-width: 720px) { + .enable-toc-as-sidebar .enable-toc__toggle-button { + display: none; + } +} +.enable-toc__toggle-button ~ h1 { + display: inline-block; +} +.enable-toc--sidebar { display: none; position: sticky; float: left; @@ -63,15 +89,15 @@ nav.sidebar-toc { width: 25%; max-width: 300px; } -nav.sidebar-toc h2 { +.enable-toc--sidebar h2 { margin-top: 20px; } @media only screen and (min-width: 720px) { - .toc-as-sidebar nav.sidebar-toc { + .enable-toc-as-sidebar .enable-toc--sidebar { display: grid; } } -nav.toggle-toc { +.enable-toc--toggle { display: none; position: absolute; z-index: 1; @@ -80,38 +106,12 @@ nav.toggle-toc { max-width: 500px; z-index: 2; } -nav.toggle-toc .move-to-sidebar-button { - display: none; -} -@media only screen and (min-width: 720px) { - nav.toggle-toc .move-to-sidebar-button { - display: grid; - } -} -nav.toggle-toc h2 { +.enable-toc--toggle h2 { scroll-margin-top: 0; margin-top: 8px; } @media only screen and (min-width: 720px) { - .toc-as-sidebar nav.toggle-toc { + .enable-toc-as-sidebar .enable-toc--toggle { display: none; } } -.toggle-toc-button { - position: relative; - top: -2px; - display: inline-block; - padding: 5px 10px; - margin-left: -10px; - border: none; - background: none; - cursor: pointer; -} -@media only screen and (min-width: 720px) { - .toc-as-sidebar .toggle-toc-button { - display: none; - } -} -.toggle-toc-button ~ h1 { - display: inline-block; -} diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index b3b1fcdf..2fc00bfc 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -40,7 +40,7 @@ const tableOfContents = new function() { this.createContent = (numberFirstLevelHeadings) => { // Table of Contents container setup const tocList = document.createElement(numberFirstLevelHeadings ? 'ol' : 'ul'); - tocList.setAttribute('class', 'toc-level-1'); + tocList.setAttribute('class', 'enable-toc__level-1-content'); let prevHeadingLevel = 0; let tocNode = tocList; @@ -59,15 +59,15 @@ const tableOfContents = new function() { return; } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { const subList = document.createElement('ul'); - subList.setAttribute('class', `toc-level-${headingLevel - 1}`); + subList.setAttribute('class', `enable-toc__level-${headingLevel - 1}-content`); tocNode.appendChild(subList); tocNode = subList; } - tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); + tocNode = tocNode.closest(`.enable-toc__level-${headingLevel - 1}-content`); const tocItem = document.createElement('li'); - tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); + tocItem.setAttribute('class', `enable-toc__item-${el.tagName.toLowerCase()}`); const tocLink = el.id ? document.createElement('a') : document.createElement('p'); el.id && tocLink.setAttribute('href', `#${el.id}`); @@ -85,8 +85,8 @@ const tableOfContents = new function() { /* Action when clicking the toggle TOC button */ this.toggleTOC = () => { - const toc = document.getElementById('toggle-toc'); - const toggleButton = document.querySelector('.toggle-toc-button'); + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { toggleButton.setAttribute('aria-expanded', 'false'); @@ -101,15 +101,15 @@ const tableOfContents = new function() { this.appendAsSidebar = () => { // Create the nav and heading elements const nav = document.createElement('nav'); - nav.setAttribute('id', 'sidebar-toc'); - nav.setAttribute('class', 'toc sidebar-toc'); + nav.setAttribute('id', 'enable-toc--sidebar'); + nav.setAttribute('class', 'enable-toc enable-toc--sidebar'); const tocHeading = document.createElement('h2'); tocHeading.textContent = 'Contents'; // Create the button to hide the TOC and move it to the toggle button const hideSidebarButton = document.createElement('button'); hideSidebarButton.textContent = 'Hide'; - hideSidebarButton.setAttribute('class', 'hide-sidebar-button'); + hideSidebarButton.setAttribute('class', 'enable-toc__hide-sidebar-button'); hideSidebarButton.setAttribute('aria-label', 'Hide Table of Contents sidebar'); hideSidebarButton.addEventListener('click', this.moveToToggleButton); @@ -127,15 +127,15 @@ const tableOfContents = new function() { this.appendAsToggleButton = () => { // Create the nav and heading elements const nav = document.createElement('nav'); - nav.setAttribute('id', 'toggle-toc'); - nav.setAttribute('class', 'toc toggle-toc'); + nav.setAttribute('id', 'enable-toc--toggle'); + nav.setAttribute('class', 'enable-toc enable-toc--toggle'); const tocHeading = document.createElement('h2'); tocHeading.textContent = 'Contents'; // Create the button to move the TOC to the sidebar const moveToSidebarButton = document.createElement('button'); moveToSidebarButton.textContent = 'Move to Sidebar'; - moveToSidebarButton.setAttribute('class', 'move-to-sidebar-button'); + moveToSidebarButton.setAttribute('class', 'enable-toc__move-to-sidebar-button'); moveToSidebarButton.setAttribute('aria-label', 'Move Table of Contents to Sidebar'); moveToSidebarButton.addEventListener('click', this.moveToSidebar); @@ -147,10 +147,9 @@ const tableOfContents = new function() { // Create the button to toggle the TOC const toggleButton = document.createElement('button'); - toggleButton.setAttribute('class', 'toggle-toc-button'); + toggleButton.setAttribute('class', 'enable-toc__toggle-button'); toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); - toggleButton.setAttribute('aria-controls', 'toggle-toc'); - toggleButton.setAttribute('aria-haspopup', 'true'); + toggleButton.setAttribute('aria-controls', 'enable-toc--toggle'); toggleButton.setAttribute('aria-expanded', 'false'); toggleButton.innerHTML = ''; toggleButton.addEventListener('click', this.toggleTOC); @@ -169,30 +168,30 @@ const tableOfContents = new function() { this.moveToSidebar = () => { // Update the body class to show the TOC as a sidebar - document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.add('enable-toc-as-sidebar'); // Hide the TOC toggle button and content - const toc = document.getElementById('toggle-toc'); - const toggleButton = document.querySelector('.toggle-toc-button'); + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); toggleButton.setAttribute('aria-expanded', 'false'); toc.style.display = 'none'; // Set the cookie to remember the sidebar state - setCookie('tocAsSidebar', 'true'); + setCookie('enable-toc-as-sidebar', 'true'); // Focus on the Hide button - document.querySelector('.hide-sidebar-button').focus(); + document.querySelector('.enable-toc__hide-sidebar-button').focus(); } this.moveToToggleButton = () => { // Update the body class to not show the TOC as a sidebar - document.getElementsByTagName('body')[0].classList.remove('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.remove('enable-toc-as-sidebar'); // Set the cookie to remember the sidebar state - setCookie('tocAsSidebar', 'false'); + setCookie('enable-toc-as-sidebar', 'false'); // Focus on the TOC toggle button - document.querySelector('.toggle-toc-button').focus(); + document.querySelector('.enable-toc__toggle-button').focus(); } this.init = (skipPages = [], showAsSidebarDefault = true, numberFirstLevelHeadings = true) => { @@ -209,14 +208,14 @@ const tableOfContents = new function() { this.appendAsToggleButton(); // Check if the TOC should be shown as a sidebar, and update the body class - const sidebarCookieValue = getCookie('tocAsSidebar'); + const sidebarCookieValue = getCookie('enable-toc-as-sidebar'); if (sidebarCookieValue === 'true' || (!sidebarCookieValue && showAsSidebarDefault)) { - document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.add('enable-toc-as-sidebar'); } - // Set the default tocAsSidebar cookie if it doesn't exist + // Set the default enable-toc-as-sidebar cookie if it doesn't exist if (!sidebarCookieValue) { - setCookie('tocAsSidebar', `${showAsSidebarDefault}`); + setCookie('enable-toc-as-sidebar', `${showAsSidebarDefault}`); } } } diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index 52263d99..881c8eab 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -40,7 +40,7 @@ const tableOfContents = new function() { this.createContent = (numberFirstLevelHeadings) => { // Table of Contents container setup const tocList = document.createElement(numberFirstLevelHeadings ? 'ol' : 'ul'); - tocList.setAttribute('class', 'toc-level-1'); + tocList.setAttribute('class', 'enable-toc__level-1-content'); let prevHeadingLevel = 0; let tocNode = tocList; @@ -59,15 +59,15 @@ const tableOfContents = new function() { return; } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { const subList = document.createElement('ul'); - subList.setAttribute('class', `toc-level-${headingLevel - 1}`); + subList.setAttribute('class', `enable-toc__level-${headingLevel - 1}-content`); tocNode.appendChild(subList); tocNode = subList; } - tocNode = tocNode.closest(`.toc-level-${headingLevel - 1}`); + tocNode = tocNode.closest(`.enable-toc__level-${headingLevel - 1}-content`); const tocItem = document.createElement('li'); - tocItem.setAttribute('class', `toc-item-${el.tagName.toLowerCase()}`); + tocItem.setAttribute('class', `enable-toc__item-${el.tagName.toLowerCase()}`); const tocLink = el.id ? document.createElement('a') : document.createElement('p'); el.id && tocLink.setAttribute('href', `#${el.id}`); @@ -85,8 +85,8 @@ const tableOfContents = new function() { /* Action when clicking the toggle TOC button */ this.toggleTOC = () => { - const toc = document.getElementById('toggle-toc'); - const toggleButton = document.querySelector('.toggle-toc-button'); + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { toggleButton.setAttribute('aria-expanded', 'false'); @@ -101,15 +101,15 @@ const tableOfContents = new function() { this.appendAsSidebar = () => { // Create the nav and heading elements const nav = document.createElement('nav'); - nav.setAttribute('id', 'sidebar-toc'); - nav.setAttribute('class', 'toc sidebar-toc'); + nav.setAttribute('id', 'enable-toc--sidebar'); + nav.setAttribute('class', 'enable-toc enable-toc--sidebar'); const tocHeading = document.createElement('h2'); tocHeading.textContent = 'Contents'; // Create the button to hide the TOC and move it to the toggle button const hideSidebarButton = document.createElement('button'); hideSidebarButton.textContent = 'Hide'; - hideSidebarButton.setAttribute('class', 'hide-sidebar-button'); + hideSidebarButton.setAttribute('class', 'enable-toc__hide-sidebar-button'); hideSidebarButton.setAttribute('aria-label', 'Hide Table of Contents sidebar'); hideSidebarButton.addEventListener('click', this.moveToToggleButton); @@ -127,15 +127,15 @@ const tableOfContents = new function() { this.appendAsToggleButton = () => { // Create the nav and heading elements const nav = document.createElement('nav'); - nav.setAttribute('id', 'toggle-toc'); - nav.setAttribute('class', 'toc toggle-toc'); + nav.setAttribute('id', 'enable-toc--toggle'); + nav.setAttribute('class', 'enable-toc enable-toc--toggle'); const tocHeading = document.createElement('h2'); tocHeading.textContent = 'Contents'; // Create the button to move the TOC to the sidebar const moveToSidebarButton = document.createElement('button'); moveToSidebarButton.textContent = 'Move to Sidebar'; - moveToSidebarButton.setAttribute('class', 'move-to-sidebar-button'); + moveToSidebarButton.setAttribute('class', 'enable-toc__move-to-sidebar-button'); moveToSidebarButton.setAttribute('aria-label', 'Move Table of Contents to Sidebar'); moveToSidebarButton.addEventListener('click', this.moveToSidebar); @@ -147,10 +147,9 @@ const tableOfContents = new function() { // Create the button to toggle the TOC const toggleButton = document.createElement('button'); - toggleButton.setAttribute('class', 'toggle-toc-button'); + toggleButton.setAttribute('class', 'enable-toc__toggle-button'); toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); - toggleButton.setAttribute('aria-controls', 'toggle-toc'); - toggleButton.setAttribute('aria-haspopup', 'true'); + toggleButton.setAttribute('aria-controls', 'enable-toc--toggle'); toggleButton.setAttribute('aria-expanded', 'false'); toggleButton.innerHTML = ''; toggleButton.addEventListener('click', this.toggleTOC); @@ -169,30 +168,30 @@ const tableOfContents = new function() { this.moveToSidebar = () => { // Update the body class to show the TOC as a sidebar - document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.add('enable-toc-as-sidebar'); // Hide the TOC toggle button and content - const toc = document.getElementById('toggle-toc'); - const toggleButton = document.querySelector('.toggle-toc-button'); + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); toggleButton.setAttribute('aria-expanded', 'false'); toc.style.display = 'none'; // Set the cookie to remember the sidebar state - setCookie('tocAsSidebar', 'true'); + setCookie('enable-toc-as-sidebar', 'true'); // Focus on the Hide button - document.querySelector('.hide-sidebar-button').focus(); + document.querySelector('.enable-toc__hide-sidebar-button').focus(); } this.moveToToggleButton = () => { // Update the body class to not show the TOC as a sidebar - document.getElementsByTagName('body')[0].classList.remove('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.remove('enable-toc-as-sidebar'); // Set the cookie to remember the sidebar state - setCookie('tocAsSidebar', 'false'); + setCookie('enable-toc-as-sidebar', 'false'); // Focus on the TOC toggle button - document.querySelector('.toggle-toc-button').focus(); + document.querySelector('.enable-toc__toggle-button').focus(); } this.init = (skipPages = [], showAsSidebarDefault = true, numberFirstLevelHeadings = true) => { @@ -209,14 +208,14 @@ const tableOfContents = new function() { this.appendAsToggleButton(); // Check if the TOC should be shown as a sidebar, and update the body class - const sidebarCookieValue = getCookie('tocAsSidebar'); + const sidebarCookieValue = getCookie('enable-toc-as-sidebar'); if (sidebarCookieValue === 'true' || (!sidebarCookieValue && showAsSidebarDefault)) { - document.getElementsByTagName('body')[0].classList.add('toc-as-sidebar'); + document.getElementsByTagName('body')[0].classList.add('enable-toc-as-sidebar'); } - // Set the default tocAsSidebar cookie if it doesn't exist + // Set the default enable-toc-as-sidebar cookie if it doesn't exist if (!sidebarCookieValue) { - setCookie('tocAsSidebar', `${showAsSidebarDefault}`); + setCookie('enable-toc-as-sidebar', `${showAsSidebarDefault}`); } } } diff --git a/less/enable-toc.less b/less/enable-toc.less index 27aad0c4..cc92ce61 100644 --- a/less/enable-toc.less +++ b/less/enable-toc.less @@ -1,6 +1,6 @@ @import "shared/mixins-and-vars"; -.toc-as-sidebar { +.enable-toc-as-sidebar { main#main { @media @tablet-up { margin-left: 25%; @@ -8,7 +8,7 @@ } } -nav.toc { +.enable-toc { background-color: white; grid-template-columns: 1fr auto; align-items: end; @@ -39,8 +39,8 @@ nav.toc { } } - .move-to-sidebar-button, - .hide-sidebar-button { + &__move-to-sidebar-button, + &__hide-sidebar-button { padding: 5px 10px; border: none; margin-bottom: 2px; @@ -50,82 +50,82 @@ nav.toc { } } - .toc-level-1 { + &__move-to-sidebar-button { + display: none; + + @media @tablet-up { + display: grid; + } + } + + &__level-1-content { grid-column-start: 1; grid-column-end: -1; max-width: 100%; border-top: 1px solid gray; padding-top: 20px; } -} - -nav.sidebar-toc { - display: none; - position: sticky; - float: left; - top: 2.5rem; - right: 0; - bottom: 0; - width: 25%; - max-width: 300px; - - h2 { - margin-top: 20px; - } - - .toc-as-sidebar & { - @media @tablet-up { - display: grid; + + &__toggle-button { + position: relative; + top: -2px; + display: inline-block; + padding: 5px 10px; + margin-left: -10px; + border: none; + background: none; + cursor: pointer; + + .enable-toc-as-sidebar & { + @media @tablet-up { + display: none; + } + } + + & ~ h1 { + display: inline-block; } } -} -nav.toggle-toc { - display: none; - position: absolute; - z-index: 1; - border-radius: 5px; - box-shadow: gray 0 0 5px; - max-width: 500px; - z-index: 2; - - .move-to-sidebar-button { + &--sidebar { display: none; - - @media @tablet-up { - display: grid; + position: sticky; + float: left; + top: 2.5rem; + right: 0; + bottom: 0; + width: 25%; + max-width: 300px; + + h2 { + margin-top: 20px; } - } - - h2 { - scroll-margin-top: 0; - margin-top: 8px; - } - - .toc-as-sidebar & { - @media @tablet-up { - display: none; + + .enable-toc-as-sidebar & { + @media @tablet-up { + display: grid; + } } } -} - -.toggle-toc-button { - position: relative; - top: -2px; - display: inline-block; - padding: 5px 10px; - margin-left: -10px; - border: none; - background: none; - cursor: pointer; - .toc-as-sidebar & { - @media @tablet-up { - display: none; + &--toggle { + display: none; + position: absolute; + z-index: 1; + border-radius: 5px; + box-shadow: gray 0 0 5px; + max-width: 500px; + z-index: 2; + + h2 { + scroll-margin-top: 0; + margin-top: 8px; + } + + .enable-toc-as-sidebar & { + @media @tablet-up { + display: none; + } } - } - - & ~ h1 { - display: inline-block; } } From bb89fba794a7e94099cbc137d1fda977125b9069 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 15:41:08 -0400 Subject: [PATCH 015/274] Fix conflicting specificity of nav styles --- css/shared/all.css | 7 +++---- css/shared/landmarks.css | 7 +++---- less/shared/landmarks.less | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/css/shared/all.css b/css/shared/all.css index aa6e268d..81c85c34 100644 --- a/css/shared/all.css +++ b/css/shared/all.css @@ -630,17 +630,16 @@ header .search-form, top: 1em; right: 1em; } -nav.site__nav { +.site__nav { position: static; background: #000000; - padding: 0.4em; line-height: 1.6; } -nav.site__nav ul li { +.site__nav ul li { display: inline-block; margin-right: 1em; } -nav.site__nav ul li a { +.site__nav ul li a { color: white; text-decoration: none; } diff --git a/css/shared/landmarks.css b/css/shared/landmarks.css index aa6e268d..81c85c34 100644 --- a/css/shared/landmarks.css +++ b/css/shared/landmarks.css @@ -630,17 +630,16 @@ header .search-form, top: 1em; right: 1em; } -nav.site__nav { +.site__nav { position: static; background: #000000; - padding: 0.4em; line-height: 1.6; } -nav.site__nav ul li { +.site__nav ul li { display: inline-block; margin-right: 1em; } -nav.site__nav ul li a { +.site__nav ul li a { color: white; text-decoration: none; } diff --git a/less/shared/landmarks.less b/less/shared/landmarks.less index 6c38c889..62ca4d58 100755 --- a/less/shared/landmarks.less +++ b/less/shared/landmarks.less @@ -53,10 +53,9 @@ header, } } -nav.site__nav { +.site__nav { position: static; background: #000000; - padding: 0.4em; line-height: 1.6; ul li { From feef5e2b2d1406e99de3064a0a8050e00beb5c83 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 15:41:21 -0400 Subject: [PATCH 016/274] Remove extra lines --- js/global.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/global.js b/js/global.js index c2fc55bb..c1b1113f 100644 --- a/js/global.js +++ b/js/global.js @@ -81,8 +81,6 @@ function initEnable() { }, ); - - pauseAnimControl.init(); // So screen reader users, like VoiceOver users, can navigate via heading and have focus From 83ba36078dc19b265365b0b035adde09a8b908a9 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 15:57:32 -0400 Subject: [PATCH 017/274] Add tooltip content to explaing the toggle TOC button --- js/modules/enable-toc.js | 6 ++++++ js/modules/es4/enable-toc.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index 2fc00bfc..a50ccab9 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -87,6 +87,7 @@ const tableOfContents = new function() { this.toggleTOC = () => { const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { toggleButton.setAttribute('aria-expanded', 'false'); @@ -151,6 +152,7 @@ const tableOfContents = new function() { toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); toggleButton.setAttribute('aria-controls', 'enable-toc--toggle'); toggleButton.setAttribute('aria-expanded', 'false'); + toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); toggleButton.innerHTML = ''; toggleButton.addEventListener('click', this.toggleTOC); @@ -187,6 +189,10 @@ const tableOfContents = new function() { // Update the body class to not show the TOC as a sidebar document.getElementsByTagName('body')[0].classList.remove('enable-toc-as-sidebar'); + // Update the toggle button tooltip + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('data-tooltip', 'The Table of Contents has moved here. Click to open or close.'); + // Set the cookie to remember the sidebar state setCookie('enable-toc-as-sidebar', 'false'); diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index 881c8eab..b2b78884 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -87,6 +87,7 @@ const tableOfContents = new function() { this.toggleTOC = () => { const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { toggleButton.setAttribute('aria-expanded', 'false'); @@ -151,6 +152,7 @@ const tableOfContents = new function() { toggleButton.setAttribute('aria-label', 'Toggle the Table of Contents'); toggleButton.setAttribute('aria-controls', 'enable-toc--toggle'); toggleButton.setAttribute('aria-expanded', 'false'); + toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); toggleButton.innerHTML = ''; toggleButton.addEventListener('click', this.toggleTOC); @@ -187,6 +189,10 @@ const tableOfContents = new function() { // Update the body class to not show the TOC as a sidebar document.getElementsByTagName('body')[0].classList.remove('enable-toc-as-sidebar'); + // Update the toggle button tooltip + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('data-tooltip', 'The Table of Contents has moved here. Click to open or close.'); + // Set the cookie to remember the sidebar state setCookie('enable-toc-as-sidebar', 'false'); From e67f5022a85e4cda2509968ece2ed4e2e92710ad Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 17:18:30 -0400 Subject: [PATCH 018/274] Close toggled TOC when clicking outside --- js/modules/enable-toc.js | 33 ++++++++++++++++++++++++++++----- js/modules/es4/enable-toc.js | 33 ++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index a50ccab9..a51fdd30 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -83,18 +83,41 @@ const tableOfContents = new function() { return tocList; } + this.openToggleTOC = () => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton?.setAttribute('aria-expanded', 'true'); + toc.style.display = 'grid'; + toggleButton.focus(); + window.addEventListener('click', this.toggleOnOutsideClick); + } + + this.closeToggleTOC = () => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + toggleButton.focus(); + window.removeEventListener('click', this.toggleOnOutsideClick); + } + + this.toggleOnOutsideClick = (event) => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + if (!toggleButton.contains(event.target) && !toc.contains(event.target)) { + this.closeToggleTOC(); + } + } + /* Action when clicking the toggle TOC button */ this.toggleTOC = () => { - const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { - toggleButton.setAttribute('aria-expanded', 'false'); - toc.style.display = 'none'; + this.closeToggleTOC(); } else { - toggleButton?.setAttribute('aria-expanded', 'true'); - toc.style.display = 'grid'; + this.openToggleTOC(); } } diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index b2b78884..a9ba3d93 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -83,18 +83,41 @@ const tableOfContents = new function() { return tocList; } + this.openToggleTOC = () => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton?.setAttribute('aria-expanded', 'true'); + toc.style.display = 'grid'; + toggleButton.focus(); + window.addEventListener('click', this.toggleOnOutsideClick); + } + + this.closeToggleTOC = () => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + toggleButton.setAttribute('aria-expanded', 'false'); + toc.style.display = 'none'; + toggleButton.focus(); + window.removeEventListener('click', this.toggleOnOutsideClick); + } + + this.toggleOnOutsideClick = (event) => { + const toc = document.getElementById('enable-toc--toggle'); + const toggleButton = document.querySelector('.enable-toc__toggle-button'); + if (!toggleButton.contains(event.target) && !toc.contains(event.target)) { + this.closeToggleTOC(); + } + } + /* Action when clicking the toggle TOC button */ this.toggleTOC = () => { - const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { - toggleButton.setAttribute('aria-expanded', 'false'); - toc.style.display = 'none'; + this.closeToggleTOC(); } else { - toggleButton?.setAttribute('aria-expanded', 'true'); - toc.style.display = 'grid'; + this.openToggleTOC(); } } From 1c0268b8ad55f1484f40cb0736dfa3fc26f17d74 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Thu, 30 May 2024 17:32:45 -0400 Subject: [PATCH 019/274] Close toggled TOC on escape key or focusing outside TOC --- js/modules/enable-toc.js | 10 ++++++---- js/modules/es4/enable-toc.js | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index a51fdd30..153a35d0 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -89,7 +89,8 @@ const tableOfContents = new function() { toggleButton?.setAttribute('aria-expanded', 'true'); toc.style.display = 'grid'; toggleButton.focus(); - window.addEventListener('click', this.toggleOnOutsideClick); + window.addEventListener('click', this.closeToggleTOCOnEvent); + window.addEventListener('keyup', this.closeToggleTOCOnEvent); } this.closeToggleTOC = () => { @@ -98,13 +99,14 @@ const tableOfContents = new function() { toggleButton.setAttribute('aria-expanded', 'false'); toc.style.display = 'none'; toggleButton.focus(); - window.removeEventListener('click', this.toggleOnOutsideClick); + window.removeEventListener('click', this.closeToggleTOCOnEvent); + window.removeEventListener('keyup', this.closeToggleTOCOnEvent); } - this.toggleOnOutsideClick = (event) => { + this.closeToggleTOCOnEvent = (event) => { const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); - if (!toggleButton.contains(event.target) && !toc.contains(event.target)) { + if ((event.type === 'keyup' && event.key === 'Escape') || (!toggleButton.contains(event.target) && !toc.contains(event.target))) { this.closeToggleTOC(); } } diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index a9ba3d93..820cedc4 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -89,7 +89,8 @@ const tableOfContents = new function() { toggleButton?.setAttribute('aria-expanded', 'true'); toc.style.display = 'grid'; toggleButton.focus(); - window.addEventListener('click', this.toggleOnOutsideClick); + window.addEventListener('click', this.closeToggleTOCOnEvent); + window.addEventListener('keyup', this.closeToggleTOCOnEvent); } this.closeToggleTOC = () => { @@ -98,13 +99,14 @@ const tableOfContents = new function() { toggleButton.setAttribute('aria-expanded', 'false'); toc.style.display = 'none'; toggleButton.focus(); - window.removeEventListener('click', this.toggleOnOutsideClick); + window.removeEventListener('click', this.closeToggleTOCOnEvent); + window.removeEventListener('keyup', this.closeToggleTOCOnEvent); } - this.toggleOnOutsideClick = (event) => { + this.closeToggleTOCOnEvent = (event) => { const toc = document.getElementById('enable-toc--toggle'); const toggleButton = document.querySelector('.enable-toc__toggle-button'); - if (!toggleButton.contains(event.target) && !toc.contains(event.target)) { + if ((event.type === 'keyup' && event.key === 'Escape') || (!toggleButton.contains(event.target) && !toc.contains(event.target))) { this.closeToggleTOC(); } } From 69cc56ebe174b7f81bfad32fb5e6d3cf10f83718 Mon Sep 17 00:00:00 2001 From: Jessie Cai Date: Tue, 4 Jun 2024 13:36:24 -0400 Subject: [PATCH 020/274] Fixed PHP binPath for windows users --- bin/php-express/lib/PHPExpress/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/php-express/lib/PHPExpress/index.js b/bin/php-express/lib/PHPExpress/index.js index ace85011..91f76126 100644 --- a/bin/php-express/lib/PHPExpress/index.js +++ b/bin/php-express/lib/PHPExpress/index.js @@ -13,7 +13,10 @@ var PHPExpress = function (opts) { console.log(`Error was: ${error || stderr}`) process.exit(1); } else { - this.binPath = stdout.trim(); + const stdoutVal = stdout.trim(); + if (stdoutVal !== '') { + this.binPath = stdoutVal; + } console.log(`PHP found at ${this.binPath}`); } }); From 2ec0b8e13f61454104f3aafd19f581f14b907faf Mon Sep 17 00:00:00 2001 From: "josiah.williams" Date: Wed, 5 Jun 2024 10:03:37 -0400 Subject: [PATCH 021/274] test commit for readme file --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ac07a84d..4bd64245 100755 --- a/README.md +++ b/README.md @@ -165,3 +165,5 @@ When testing using screen readers, these resources may be of help to you: - [Desktop Screen Readers Survival Guide - Basic Keyboard Shortcuts](https://dequeuniversity.com/screenreaders/survival-guide) - [NVDA Users Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html) + +test commit \ No newline at end of file From 6e5640216f99b593864ee4e44a619111b7378694 Mon Sep 17 00:00:00 2001 From: "josiah.williams" Date: Wed, 5 Jun 2024 10:12:38 -0400 Subject: [PATCH 022/274] test commit for new github userid --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bd64245..c066d866 100755 --- a/README.md +++ b/README.md @@ -166,4 +166,4 @@ When testing using screen readers, these resources may be of help to you: - [Desktop Screen Readers Survival Guide - Basic Keyboard Shortcuts](https://dequeuniversity.com/screenreaders/survival-guide) - [NVDA Users Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html) -test commit \ No newline at end of file +test commit for github userid \ No newline at end of file From f0275bf6e9d3d048c6981ee712ec25aaedc2463e Mon Sep 17 00:00:00 2001 From: "josiah.williams" Date: Wed, 5 Jun 2024 10:18:43 -0400 Subject: [PATCH 023/274] Adding additional instructions for Java install --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c066d866..a76b66c9 100755 --- a/README.md +++ b/README.md @@ -28,6 +28,28 @@ A place to learn and share with developers what makes web work accessible. This - You can find the path to the installed PHP using `which php`. Add that path to your terminal profile's PATH environment value and/or VS Code settings. - Java: required in order to use the v.Nu checker during automation and unit testing + - Install options: + - ```bash + brew install java + ``` + - ```bash + brew install openjdk + ``` + - Additional tasks + + - ```bash + java -version + ``` + + If you get "Unable to locate a Java Runtime", proceed to next step. + + - Ensure your symlink is correctly mapped (these instructions will also appear after you brew install java) + + - ```bash + sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk \ + /Library/Java/JavaVirtualMachines/openjdk.jdk + ``` + > Note: Any changes to these prerequisites will need to be reflected in the GitHub Actions in order to run the CI/CD checks. #### Optional installations (since `npm run server` will run an express server) @@ -165,5 +187,3 @@ When testing using screen readers, these resources may be of help to you: - [Desktop Screen Readers Survival Guide - Basic Keyboard Shortcuts](https://dequeuniversity.com/screenreaders/survival-guide) - [NVDA Users Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html) - -test commit for github userid \ No newline at end of file From 92b697a1f1d9426889ce2041e68e67725ec732e9 Mon Sep 17 00:00:00 2001 From: akr3081 Date: Wed, 5 Jun 2024 16:41:41 +0200 Subject: [PATCH 024/274] Set up initial page for meter component, added images/link in header --- content/body/meter.php | 56 ++++++++++++++++++++ content/head/meter.php | 1 + css-list.txt | 1 + css/meter.css | 8 +++ images/main-menu/meter.png | Bin 0 -> 17658 bytes images/main-menu/meter.webp | Bin 0 -> 17658 bytes templates/data/meta-info.json | 4 ++ templates/includes/documentation-header.php | 7 +++ 8 files changed, 77 insertions(+) create mode 100644 content/body/meter.php create mode 100644 content/head/meter.php create mode 100644 css/meter.css create mode 100644 images/main-menu/meter.png create mode 100644 images/main-menu/meter.webp diff --git a/content/body/meter.php b/content/body/meter.php new file mode 100644 index 00000000..d846b6a8 --- /dev/null +++ b/content/body/meter.php @@ -0,0 +1,56 @@ +

+ The meter component is used to show a scalar measurement within a known range or a fractional value. + For example, meter could be used to display a device's current battery or a car's remaining fuel level. + In cases where there is no fixed maxium, it is preferable to utilize the progress bar component. +

+ +

+ This page provides examples of both HTML and aria based solutions for creating accessible meter components. +

+ +

HTML5 meter component

+ true]); ?> + +

+ This progress bar uses aria-live regions to update the status of the progress bar. It works in for all + screen + readers. It is the most bulletproof way to implement a progress bar if you need to ensure that screen reader + users are updated as soon as the progress bar value changes. Be mindful of how often the ARIA live region updates, so it + doesn't cause unnecessary noise for screen readers users. +

+ +
+
+ + + + + + + + +
+
+ + + \ No newline at end of file diff --git a/content/head/meter.php b/content/head/meter.php new file mode 100644 index 00000000..513dfdde --- /dev/null +++ b/content/head/meter.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/css-list.txt b/css-list.txt index 3d9fc523..c80c5aa5 100644 --- a/css-list.txt +++ b/css-list.txt @@ -28,6 +28,7 @@ css/log.css css/marquee.css css/math.css css/menubar.css +css/meter.css css/paginate.css css/pausable-animated-gif.css css/pause-animations-demo.css diff --git a/css/meter.css b/css/meter.css new file mode 100644 index 00000000..3a44639e --- /dev/null +++ b/css/meter.css @@ -0,0 +1,8 @@ +.meter-container { + align-items: center; + column-gap: 10px; + display: grid; + grid-template-columns: 1fr auto; + justify-content: space-between; + width: min-content; +} \ No newline at end of file diff --git a/images/main-menu/meter.png b/images/main-menu/meter.png new file mode 100644 index 0000000000000000000000000000000000000000..c9de683d0494ed4f82558c770d9ccd8e08e649fe GIT binary patch literal 17658 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 literal 0 HcmV?d00001 diff --git a/images/main-menu/meter.webp b/images/main-menu/meter.webp new file mode 100644 index 0000000000000000000000000000000000000000..c9de683d0494ed4f82558c770d9ccd8e08e649fe GIT binary patch literal 17658 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 literal 0 HcmV?d00001 diff --git a/templates/data/meta-info.json b/templates/data/meta-info.json index 8f5e42ea..06754d13 100644 --- a/templates/data/meta-info.json +++ b/templates/data/meta-info.json @@ -127,6 +127,10 @@ "desc": "Math equations can be made accessible to math students and scholars with MathML.", "wip": true }, + "meter.php": { + "title": "Meter", + "desc": "Meter component which is supported by non html5 browsers." + }, "multi-level-hamburger-menu.php": { "title": "Accessible Flyout/Hamburger Hybrid Menus", "desc": "A hard thing to code from scratch, many developers will use an existing library to create hybrid flyout/hamburger menus. Most of them aren't accessible. Let's show you how they can be.", diff --git a/templates/includes/documentation-header.php b/templates/includes/documentation-header.php index 35b13269..9f13350a 100755 --- a/templates/includes/documentation-header.php +++ b/templates/includes/documentation-header.php @@ -499,6 +499,13 @@ class="enable-flyout enable-flyout__level enable-flyout__dropdown"> "url-slug": "reflow", "alt": "" } + }, { + "id": "flyout__link", + "props": { + "label": "Meter", + "url-slug": "meter", + "alt": "" + } } ] From dd8b70647f2b6aad6928201ea0e036c9465a87fc Mon Sep 17 00:00:00 2001 From: akr3081 Date: Wed, 5 Jun 2024 16:42:27 +0200 Subject: [PATCH 025/274] Adding images From e3fe8b884e268a796a34bc4152e6f74b02f18f67 Mon Sep 17 00:00:00 2001 From: "josiah.williams" Date: Wed, 5 Jun 2024 10:49:11 -0400 Subject: [PATCH 026/274] Updates info for testing --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a76b66c9..47ff611a 100755 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ A place to learn and share with developers what makes web work accessible. This > See the [Chromedriver issues](#chromedriver-issues) section if you encounter an error related to Chromedriver. + > Make sure your server is running in another terminal before running `npm run test`. + ## Tests ### Tools used for testing From 384353020eb6df000a95462d5a62b8e93e3ebe38 Mon Sep 17 00:00:00 2001 From: "josiah.williams" Date: Wed, 5 Jun 2024 14:50:01 -0400 Subject: [PATCH 027/274] Updates Java install instructions. Clarifies local server for tests --- README.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 47ff611a..c3e77062 100755 --- a/README.md +++ b/README.md @@ -29,26 +29,33 @@ A place to learn and share with developers what makes web work accessible. This - Java: required in order to use the v.Nu checker during automation and unit testing - Install options: - - ```bash - brew install java - ``` - - ```bash - brew install openjdk - ``` - - Additional tasks - - ```bash - java -version - ``` - - If you get "Unable to locate a Java Runtime", proceed to next step. - - - Ensure your symlink is correctly mapped (these instructions will also appear after you brew install java) + - Brew Install Options: - ```bash - sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk \ - /Library/Java/JavaVirtualMachines/openjdk.jdk + brew install java ``` + - ```bash + brew install openjdk + ``` + - Additional tasks + + - ```bash + java -version + ``` + + If you get "Unable to locate a Java Runtime", proceed to next step. + + - Ensure your symlink is correctly mapped (these instructions will also appear after you brew install java) + + - ```bash + sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk \ + /Library/Java/JavaVirtualMachines/openjdk.jdk + ``` + + - Manual Install Options: + - Mac Install + - Windows Install > Note: Any changes to these prerequisites will need to be reflected in the GitHub Actions in order to run the CI/CD checks. @@ -81,7 +88,7 @@ A place to learn and share with developers what makes web work accessible. This > See the [Chromedriver issues](#chromedriver-issues) section if you encounter an error related to Chromedriver. - > Make sure your server is running in another terminal before running `npm run test`. + > Make sure your local server is running in another terminal before running `npm run test`. ## Tests From fbae215b277e748bd8e16a4ea3a4950c73309d60 Mon Sep 17 00:00:00 2001 From: Neha-Singla Date: Wed, 5 Jun 2024 15:48:39 -0400 Subject: [PATCH 028/274] removing : and space between asterik --- content/body/form-error-checking.php | 18 +++++++++--------- js/demos/custom-form-example.js | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/content/body/form-error-checking.php b/content/body/form-error-checking.php index 31b220c8..a8bf9fca 100644 --- a/content/body/form-error-checking.php +++ b/content/body/form-error-checking.php @@ -38,21 +38,21 @@
Contact Information -

* denotes a required field.

+

*denotes a required field.

- +
- +
- + -

* denotes a required field.

+

*denotes a required field.

- +
- +
- +
- + +
@@ -249,6 +254,6 @@ true, + "noCSS" => true, ]) ?> diff --git a/js/modules/enable-character-count.js b/js/modules/enable-character-count.js index 3ad58d83..c3f9abca 100644 --- a/js/modules/enable-character-count.js +++ b/js/modules/enable-character-count.js @@ -1,23 +1,18 @@ import { interpolate } from "./interpolate.js"; const enableCharacterCount = new function() { - let charCountInitEl, - globalVisualTemplate, - charCountTemplate, - globalWarningThreshold, + let globalWarningThreshold, + readCountKey, announcementTimeout; let idIndex = 0; this.init = () => { const charCountEls = document.querySelectorAll('[data-has-character-count]'); - charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; - const charCountTemplateEl = document.getElementById('enable-character-count__template'); + const charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; const dataset = charCountInitEl ? charCountInitEl.dataset : {}; - charCountTemplate = charCountTemplateEl ? charCountTemplateEl.innerHTML : ''; - - globalVisualTemplate = dataset.visualText || '${numChars}/${maxLength}'; + readCountKey = 'Escape'; globalWarningThreshold = dataset.warningThreshold || 20; charCountEls.forEach((target) => { @@ -25,7 +20,7 @@ const enableCharacterCount = new function() { setIdIfNullFor(target); setUpAriaDescribedByFor(target); addLiveRegion(target); - createCounterFor(target); + createCounterContainerFor(target); writeCharCount(target); }); } @@ -42,25 +37,34 @@ const enableCharacterCount = new function() { } function setUpAriaDescribedByFor(target) { - const maxCharacterCount = target.maxLength; - const readCharacterCountKey = target.dataset.readCharacterCountWithKey; - const description = `In edit text area with a ${maxCharacterCount} character limit.`; - const instructions = `Press ${readCharacterCountKey} to find out how many more characters are allowed.`; + const describedByContent = `${getScreenReaderDescription(target)} ${getScreenReaderInstructions(target)}`; const ariaDescribedByElement = document.createElement('p'); ariaDescribedByElement.id = `${target.id}-aria-describedby`; ariaDescribedByElement.className="sr-only"; - ariaDescribedByElement.textContent = `${description} ${instructions}`; + ariaDescribedByElement.textContent = describedByContent; target.insertAdjacentElement('afterend', ariaDescribedByElement); target.setAttribute('aria-describedby', ariaDescribedByElement.id); } - function createCounterFor(target) { - const counterEl = document.createElement('output'); - counterEl.id = `${target.id}__counter`; - counterEl.className = "enable-character-count"; - counterEl.setAttribute('aria-live', 'off') - target.setAttribute('data-character-count-label', counterEl.id); - target.insertAdjacentElement('afterend', counterEl); + function getScreenReaderDescription(target) { + const { maxLength, dataset } = target; + const description = dataset.description ?? 'In edit text area with a ${maxLength} character limit.'; + return interpolate(description, { maxLength }); + } + + function getScreenReaderInstructions(target) { + const { readCountKey, instructions } = target.dataset; + const defaultInstructions = 'Press ${readCountKey} to find out how many more characters are allowed.'; + const instructionsToInterpolate = instructions ?? defaultInstructions; + return interpolate(instructionsToInterpolate, { readCountKey }); + } + + function createCounterContainerFor(target) { + const container= document.createElement('div'); + container.id = `${target.id}-counter-container`; + container.className = "enable-character-count"; + target.setAttribute('data-character-count-container', container.id); + target.insertAdjacentElement('afterend', container); } function addLiveRegion(target) { @@ -79,6 +83,26 @@ const enableCharacterCount = new function() { return result; } + function writeCharCount(target) { + const { dataset } = target; + const { characterCountContainer } = dataset; + const container = document.getElementById(characterCountContainer); + if (container) { + container.innerHTML = getCounterHTML(target); + } + } + + function getCounterHTML(target) { + const { maxLength, dataset, value } = target; + const numChars = value.length; + const charsRemaining = maxLength - numChars; + const visualTextTemplate = dataset.visualText ?? '${numChars}/${maxLength}'; + const visualText = interpolate(visualTextTemplate, { numChars, maxLength, charsRemaining }); + const counterElementTemplate = document.getElementById('enable-character-count__template'); + const counterElement = counterElementTemplate ?? '' + return interpolate(counterElement, { visualText }); + } + function wasArrowPressed(key) { switch(key) { case 'ArrowUp': @@ -130,23 +154,8 @@ const enableCharacterCount = new function() { announceCharacterCount(target); } - const writeCharCount = (target) => { - const { maxLength, dataset, value } = target; - const { characterCountLabel } = dataset; - const characterCountLabelEl = document.getElementById(characterCountLabel); - if (characterCountLabelEl) { - characterCountLabelEl.innerHTML = getCounterHTML(value.length, parseInt(maxLength)); - } - } - - function getCounterHTML(numChars, maxLength) { - const charsRemaining = maxLength - numChars; - const visualText = interpolate(globalVisualTemplate, { numChars, maxLength, charsRemaining }); - return interpolate(charCountTemplate, { visualText }); - } - function announceCharacterCount(target) { - announceCharacterCountWithDelay(target, 150) + announceCharacterCountWithDelay(target, 250) } function announceCharacterCountWithDelay(target, delay) { @@ -160,7 +169,7 @@ const enableCharacterCount = new function() { announcementTimeout = setTimeout(() => { const maxLength = target.maxLength; const numChars = target.value.length; - const charsRemaining = parseInt(maxLength) - numChars; + const charsRemaining = maxLength - numChars; const screenReaderText = target.dataset.screenReaderText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' counterForScreenReader.textContent = interpolate(screenReaderText, { numChars, maxLength, charsRemaining }); }, delay); diff --git a/js/modules/es4/enable-character-count.js b/js/modules/es4/enable-character-count.js index 40f27163..b0131e6a 100644 --- a/js/modules/es4/enable-character-count.js +++ b/js/modules/es4/enable-character-count.js @@ -1,22 +1,17 @@ const enableCharacterCount = new function() { - let charCountInitEl, - globalVisualTemplate, - charCountTemplate, - globalWarningThreshold, + let globalWarningThreshold, + readCountKey, announcementTimeout; let idIndex = 0; this.init = () => { const charCountEls = document.querySelectorAll('[data-has-character-count]'); - charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; - const charCountTemplateEl = document.getElementById('enable-character-count__template'); + const charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; const dataset = charCountInitEl ? charCountInitEl.dataset : {}; - charCountTemplate = charCountTemplateEl ? charCountTemplateEl.innerHTML : ''; - - globalVisualTemplate = dataset.visualText || '${numChars}/${maxLength}'; + readCountKey = 'Escape'; globalWarningThreshold = dataset.warningThreshold || 20; charCountEls.forEach((target) => { @@ -24,7 +19,7 @@ const enableCharacterCount = new function() { setIdIfNullFor(target); setUpAriaDescribedByFor(target); addLiveRegion(target); - createCounterFor(target); + createCounterContainerFor(target); writeCharCount(target); }); } @@ -41,25 +36,34 @@ const enableCharacterCount = new function() { } function setUpAriaDescribedByFor(target) { - const maxCharacterCount = target.maxLength; - const readCharacterCountKey = target.dataset.readCharacterCountWithKey; - const description = `In edit text area with a ${maxCharacterCount} character limit.`; - const instructions = `Press ${readCharacterCountKey} to find out how many more characters are allowed.`; + const describedByContent = `${getScreenReaderDescription(target)} ${getScreenReaderInstructions(target)}`; const ariaDescribedByElement = document.createElement('p'); ariaDescribedByElement.id = `${target.id}-aria-describedby`; ariaDescribedByElement.className="sr-only"; - ariaDescribedByElement.textContent = `${description} ${instructions}`; + ariaDescribedByElement.textContent = describedByContent; target.insertAdjacentElement('afterend', ariaDescribedByElement); target.setAttribute('aria-describedby', ariaDescribedByElement.id); } - function createCounterFor(target) { - const counterEl = document.createElement('output'); - counterEl.id = `${target.id}__counter`; - counterEl.className = "enable-character-count"; - counterEl.setAttribute('aria-live', 'off') - target.setAttribute('data-character-count-label', counterEl.id); - target.insertAdjacentElement('afterend', counterEl); + function getScreenReaderDescription(target) { + const { maxLength, dataset } = target; + const description = dataset.description ?? 'In edit text area with a ${maxLength} character limit.'; + return interpolate(description, { maxLength }); + } + + function getScreenReaderInstructions(target) { + const { readCountKey, instructions } = target.dataset; + const defaultInstructions = 'Press ${readCountKey} to find out how many more characters are allowed.'; + const instructionsToInterpolate = instructions ?? defaultInstructions; + return interpolate(instructionsToInterpolate, { readCountKey }); + } + + function createCounterContainerFor(target) { + const container= document.createElement('div'); + container.id = `${target.id}-counter-container`; + container.className = "enable-character-count"; + target.setAttribute('data-character-count-container', container.id); + target.insertAdjacentElement('afterend', container); } function addLiveRegion(target) { @@ -78,6 +82,26 @@ const enableCharacterCount = new function() { return result; } + function writeCharCount(target) { + const { dataset } = target; + const { characterCountContainer } = dataset; + const container = document.getElementById(characterCountContainer); + if (container) { + container.innerHTML = getCounterHTML(target); + } + } + + function getCounterHTML(target) { + const { maxLength, dataset, value } = target; + const numChars = value.length; + const charsRemaining = maxLength - numChars; + const visualTextTemplate = dataset.visualText ?? '${numChars}/${maxLength}'; + const visualText = interpolate(visualTextTemplate, { numChars, maxLength, charsRemaining }); + const counterElementTemplate = document.getElementById('enable-character-count__template'); + const counterElement = counterElementTemplate ?? '' + return interpolate(counterElement, { visualText }); + } + function wasArrowPressed(key) { switch(key) { case 'ArrowUp': @@ -129,23 +153,8 @@ const enableCharacterCount = new function() { announceCharacterCount(target); } - const writeCharCount = (target) => { - const { maxLength, dataset, value } = target; - const { characterCountLabel } = dataset; - const characterCountLabelEl = document.getElementById(characterCountLabel); - if (characterCountLabelEl) { - characterCountLabelEl.innerHTML = getCounterHTML(value.length, parseInt(maxLength)); - } - } - - function getCounterHTML(numChars, maxLength) { - const charsRemaining = maxLength - numChars; - const visualText = interpolate(globalVisualTemplate, { numChars, maxLength, charsRemaining }); - return interpolate(charCountTemplate, { visualText }); - } - function announceCharacterCount(target) { - announceCharacterCountWithDelay(target, 150) + announceCharacterCountWithDelay(target, 250) } function announceCharacterCountWithDelay(target, delay) { @@ -159,7 +168,7 @@ const enableCharacterCount = new function() { announcementTimeout = setTimeout(() => { const maxLength = target.maxLength; const numChars = target.value.length; - const charsRemaining = parseInt(maxLength) - numChars; + const charsRemaining = maxLength - numChars; const screenReaderText = target.dataset.screenReaderText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' counterForScreenReader.textContent = interpolate(screenReaderText, { numChars, maxLength, charsRemaining }); }, delay); From c2a85b3b711a2e635df500e09ad478abcdc07e5d Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Mon, 17 Jun 2024 09:48:07 -0400 Subject: [PATCH 112/274] fix[Issue# 49] - fix the suggested PR review changes of content --- content/body/animated-gif-with-pause-button.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/animated-gif-with-pause-button.php b/content/body/animated-gif-with-pause-button.php index 121f33aa..1de858d9 100644 --- a/content/body/animated-gif-with-pause-button.php +++ b/content/body/animated-gif-with-pause-button.php @@ -1,7 +1,7 @@ true, "comment" => - "All the examples here are useful for new and existing work where you want to pause animated GIFs most straightforwardly", + "All the examples here are useful for new and existing work where you want to pause animated GIFs in the most straightforward way.", ]); ?>
From 7af0f44e9669b1a28ae17037f5019e08898e32ab Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Mon, 17 Jun 2024 09:48:34 -0400 Subject: [PATCH 113/274] Use extracted helper functions in the global init --- js/global.js | 84 ++++++---------------------------------------------- 1 file changed, 9 insertions(+), 75 deletions(-) diff --git a/js/global.js b/js/global.js index 30f5ee95..212fe6a9 100644 --- a/js/global.js +++ b/js/global.js @@ -17,19 +17,10 @@ import enableVisibleOnFocus from './modules/enable-visible-on-focus.js'; import offscreenObserver from './modules/offscreen-observer.js'; import textZoom from './demos/hero-image-text-resize.js'; import tableOfContents from './modules/enable-toc.js'; -import { scrollToEl, addMissingIDToHeading } from './modules/helpers.js'; - -function focusDeepLink() { - const { hash } = window.location; - - if (hash !== '') { - const elToFocus = document.querySelector(hash); - if (elToFocus) { - elToFocus.focus(); - scrollToEl(elToFocus); - } - } -} +import { + focusDeepLink, + createPermalinksForHeading, +} from './modules/helpers.js'; function buildFlyoutMenuHTML() { // This is the DOM element where the hamburger menu will be inserted into. @@ -46,36 +37,6 @@ function buildFlyoutMenuHTML() { EnableFlyout.init(); } -function findImagesNextToHeading(headingId, className) { - const heading = document.getElementById(headingId); - let images = ''; - - if (heading) { - let nextElement = heading.nextElementSibling; - - // Iterate over siblings with the specified class name - while (nextElement && nextElement.classList.contains(className)) { - const imgElements = nextElement.querySelectorAll('img'); - - // Append attributes of img elements to the images string - imgElements.forEach((img) => { - const attributes = Array.from(img.attributes) - .map((attr) => { - return attr.name !== 'class' - ? `${attr.name}="${attr.value}"` - : ((attr.value += ' enable-stats__heading-icon'), - `${attr.name}="${attr.value}"`); - }) - .join(' '); - images += ``; - }); - // Move to the next sibling - nextElement = nextElement.nextElementSibling; - } - } - return images; -} - function initEnable() { offscreenObserver.init(document.querySelector('[role="banner"]')); @@ -95,8 +56,7 @@ function initEnable() { pauseAnimControl.init(); - // So screen reader users, like VoiceOver users, can navigate via heading and have focus - // applied to the heading. + // Add permalinks on all pages except home, so screen reader users, like VoiceOver users, can navigate via heading and have focus applied to the heading. let headingIndex = 0; if (location.href.indexOf('index.php') === -1) { @@ -104,42 +64,16 @@ function initEnable() { .querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]') .forEach((el) => { // Only do this if: - // 1) the heading doesn't have an ID - // 2) it is not part of an example - // 3) it has an ancestor with class no-permalink-headings. + // 1) it is not part of an example + // 2) it does not have an ancestor with class no-permalink-headings if ( el.closest('.enable-example') === null && el.closest('.no-permalink-headings') === null ) { - addMissingIDToHeading(el, headingIndex); - - if (el.getAttribute('tabIndex') === null) { - el.setAttribute('tabIndex', '-1'); - } - - // now, let's put a link tag inside the heading so we can deeplink to it easily - if ( - el.nodeName !== 'H1' && - el.getAttribute('role') !== 'heading' - ) { - // now, let's put a link tag inside the heading so we can deeplink to it easily - el.innerHTML = `${el.innerHTML}`; - - // add icons next to the heading if the content below the heading has any images - if ( - el.nextElementSibling?.classList.contains( - 'enable-stats', - ) - ) { - const images = findImagesNextToHeading( - el.id, - 'enable-stats', - ); - el.innerHTML = images + el.innerHTML; - } - } + createPermalinksForHeading(el, headingIndex, true); } + // If the heading doesn't have a tabindex, add one. if (el.getAttribute('tabIndex') === null) { el.setAttribute('tabIndex', '-1'); } From 1a6770325f154b6c568b9368cbb8f16e575add2c Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Mon, 17 Jun 2024 09:49:59 -0400 Subject: [PATCH 114/274] Fix TOC childNode links --- js/modules/enable-toc.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/js/modules/enable-toc.js b/js/modules/enable-toc.js index f1ce2172..bbeac16e 100644 --- a/js/modules/enable-toc.js +++ b/js/modules/enable-toc.js @@ -45,6 +45,7 @@ const tableOfContents = new function() { * entries in the table of contents are linked to the headings. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); + addMissingIDToHeading(el); // Skip headings that are within the selector or deeper than the specified level if (selectorToSkipHeadingsWithin && el.closest(selectorToSkipHeadingsWithin) !== null || ignoreHeadersDeeperThan && headingLevel > ignoreHeadersDeeperThan) { @@ -65,17 +66,12 @@ const tableOfContents = new function() { const tocItem = document.createElement('li'); tocItem.setAttribute('class', `enable-toc__item-${el.tagName.toLowerCase()}`); - el.childNodes.forEach((child) => { - if (child.nodeName === 'A') { - const clonedLink = child.cloneNode(true); - clonedLink.setAttribute('class', 'enable-toc__link'); - tocItem.appendChild(clonedLink); - } else if (child.nodeName === 'IMG') { + el.childNodes && el.childNodes.forEach((child) => { + if (child.nodeName === 'IMG') { const clonedImage = child.cloneNode(true); clonedImage.setAttribute('class', 'enable-toc__image'); tocItem.appendChild(clonedImage); } else if (el.textContent) { - addMissingIDToHeading(el); const tocLink = document.createElement('a'); tocLink.setAttribute('href', `#${el.id}`); tocLink.textContent = el.textContent; From 8768573421b65cf3af00726f2d9e69df0e6399d7 Mon Sep 17 00:00:00 2001 From: Alison Hall Date: Mon, 17 Jun 2024 09:50:59 -0400 Subject: [PATCH 115/274] Update the auto-compiled code --- css/enable-toc.css | 35 +++++----- js/modules/es4/enable-toc.js | 129 ++++++++++++++++++++--------------- 2 files changed, 93 insertions(+), 71 deletions(-) diff --git a/css/enable-toc.css b/css/enable-toc.css index 7d9385f6..786c5419 100644 --- a/css/enable-toc.css +++ b/css/enable-toc.css @@ -20,7 +20,7 @@ .enable-toc h2 { font-weight: normal; border-bottom: none; - margin-left: 8px; + margin-left: 0.5rem; line-height: 1em; } .enable-toc h2::before { @@ -31,16 +31,16 @@ margin-left: 0.5em; } .enable-toc li { - margin-bottom: 8px; + margin-bottom: 0.5rem; } .enable-toc li a { text-decoration: none; } .enable-toc__move-to-sidebar-button, .enable-toc__hide-sidebar-button { - padding: 5px 10px; + padding: 0.3125rem 0.625rem; border: none; - margin-bottom: 2px; + margin-bottom: 0.125rem; } .enable-toc__move-to-sidebar-button:hover, .enable-toc__hide-sidebar-button:hover { @@ -58,15 +58,15 @@ grid-column-start: 1; grid-column-end: -1; max-width: 100%; - border-top: 1px solid gray; - padding-top: 20px; + border-top: 0.0625rem solid gray; + padding-top: 1.25rem; } .enable-toc__toggle-button { position: relative; - top: -2px; + top: -0.125rem; display: inline-block; - padding: 5px 10px; - margin-left: -10px; + padding: 0.3125rem 0.625rem; + margin-left: -0.625rem; border: none; background: none; cursor: pointer; @@ -79,18 +79,21 @@ .enable-toc__toggle-button ~ h1 { display: inline-block; } +.enable-toc__image { + height: 1rem; + margin: 0 0.4rem 0 0; +} .enable-toc--sidebar { display: none; position: sticky; float: left; top: 2.5rem; right: 0; - bottom: 0; width: 25%; - max-width: 300px; + max-width: 18.75rem; } .enable-toc--sidebar h2 { - margin-top: 20px; + margin-top: 1.25rem; } @media only screen and (min-width: 720px) { .enable-toc-as-sidebar .enable-toc--sidebar { @@ -101,14 +104,14 @@ display: none; position: absolute; z-index: 1; - border-radius: 5px; - box-shadow: gray 0 0 5px; - max-width: 500px; + border-radius: 0.3125rem; + box-shadow: gray 0 0 0.3125rem; + max-width: 31.25rem; z-index: 2; } .enable-toc--toggle h2 { scroll-margin-top: 0; - margin-top: 8px; + margin-top: 0.5rem; } @media only screen and (min-width: 720px) { .enable-toc-as-sidebar .enable-toc--toggle { diff --git a/js/modules/es4/enable-toc.js b/js/modules/es4/enable-toc.js index 820cedc4..8c1bd6dd 100644 --- a/js/modules/es4/enable-toc.js +++ b/js/modules/es4/enable-toc.js @@ -13,31 +13,20 @@ * Released under the MIT License. ******************************************************************************/ -function splitCookies() { - const list = {}; - document?.cookie?.split(';')?.forEach((cookie) => { - const parts = cookie.split('='); - list[parts.shift().trim()] = decodeURI(parts.join('=')); - }); - return list; -} - -function getCookie(name) { - return splitCookies()[name]; -} - -function setCookie(name, value) { - document.cookie = `${name}=${value}; path=/;`; -} -function deleteCookie(name) { - document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; -} const tableOfContents = new function() { - let toc; + this.toc; - this.createContent = (numberFirstLevelHeadings) => { + const commonSelectors = () => { + return { + sidebarTOCSelector: document.getElementById('enable-toc--sidebar'), + toggleTOCSelector: document.getElementById('enable-toc--toggle'), + toggleButtonSelector: document.querySelector('.enable-toc__toggle-button'), + } + } + + this.createContent = (numberFirstLevelHeadings, selectorToSkipHeadingsWithin, ignoreHeadersDeeperThan) => { // Table of Contents container setup const tocList = document.createElement(numberFirstLevelHeadings ? 'ol' : 'ul'); tocList.setAttribute('class', 'enable-toc__level-1-content'); @@ -54,7 +43,13 @@ const tableOfContents = new function() { * entries in the table of contents are linked to the headings. */ const headingLevel = Number(el.nodeName?.toLowerCase()?.split('h')?.[1] || 0); - if (headingLevel === 1 || prevHeadingLevel === 0) { + addMissingIDToHeading(el); + + // Skip headings that are within the selector or deeper than the specified level + if (selectorToSkipHeadingsWithin && el.closest(selectorToSkipHeadingsWithin) !== null || ignoreHeadersDeeperThan && headingLevel > ignoreHeadersDeeperThan) { + // Do not add headings to the TOC + return; + } else if (headingLevel === 1 || prevHeadingLevel === 0) { prevHeadingLevel = headingLevel; return; } else if (headingLevel > prevHeadingLevel && prevHeadingLevel !== 1) { @@ -69,11 +64,20 @@ const tableOfContents = new function() { const tocItem = document.createElement('li'); tocItem.setAttribute('class', `enable-toc__item-${el.tagName.toLowerCase()}`); - const tocLink = el.id ? document.createElement('a') : document.createElement('p'); - el.id && tocLink.setAttribute('href', `#${el.id}`); - tocLink.textContent = el.textContent; - - tocItem.appendChild(tocLink); + el.childNodes && el.childNodes.forEach((child) => { + if (child.nodeName === 'IMG') { + const clonedImage = child.cloneNode(true); + clonedImage.setAttribute('class', 'enable-toc__image'); + tocItem.appendChild(clonedImage); + } else if (el.textContent) { + const tocLink = document.createElement('a'); + tocLink.setAttribute('href', `#${el.id}`); + tocLink.textContent = el.textContent; + tocLink.setAttribute('class', 'enable-toc__link'); + tocItem.appendChild(tocLink); + } + }); + tocNode.appendChild(tocItem); tocNode = tocItem; prevHeadingLevel = headingLevel; @@ -84,38 +88,43 @@ const tableOfContents = new function() { } this.openToggleTOC = () => { - const toc = document.getElementById('enable-toc--toggle'); - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - toggleButton?.setAttribute('aria-expanded', 'true'); - toc.style.display = 'grid'; - toggleButton.focus(); + const { toggleButtonSelector, toggleTOCSelector } = commonSelectors(); + toggleButtonSelector?.setAttribute('aria-expanded', 'true'); + if (toggleTOCSelector) { + toggleTOCSelector.style.display = 'grid'; + } + toggleButtonSelector?.focus(); window.addEventListener('click', this.closeToggleTOCOnEvent); window.addEventListener('keyup', this.closeToggleTOCOnEvent); } this.closeToggleTOC = () => { - const toc = document.getElementById('enable-toc--toggle'); - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - toggleButton.setAttribute('aria-expanded', 'false'); - toc.style.display = 'none'; - toggleButton.focus(); + const { toggleButtonSelector, toggleTOCSelector } = commonSelectors(); + toggleButtonSelector?.setAttribute('aria-expanded', 'false'); + if (toggleTOCSelector) { + toggleTOCSelector.style.display = 'none'; + } + toggleButtonSelector?.focus(); window.removeEventListener('click', this.closeToggleTOCOnEvent); window.removeEventListener('keyup', this.closeToggleTOCOnEvent); } this.closeToggleTOCOnEvent = (event) => { - const toc = document.getElementById('enable-toc--toggle'); - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - if ((event.type === 'keyup' && event.key === 'Escape') || (!toggleButton.contains(event.target) && !toc.contains(event.target))) { + const { toggleButtonSelector, toggleTOCSelector } = commonSelectors(); + if ( + (event.type === 'keyup' && event.key === 'Escape') || + (!toggleButtonSelector?.contains(event.target) && !toggleTOCSelector?.contains(event.target)) + ) { + event.preventDefault(); this.closeToggleTOC(); } } /* Action when clicking the toggle TOC button */ this.toggleTOC = () => { - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - toggleButton.setAttribute('data-tooltip', 'Open or close the Table of Contents'); - const isExpanded = toggleButton?.getAttribute('aria-expanded') === 'true'; + const { toggleButtonSelector } = commonSelectors(); + toggleButtonSelector?.setAttribute('data-tooltip', 'Open or close the Table of Contents'); + const isExpanded = toggleButtonSelector?.getAttribute('aria-expanded') === 'true'; if (isExpanded) { this.closeToggleTOC(); } else { @@ -142,7 +151,7 @@ const tableOfContents = new function() { // Append the elements to the nav nav.appendChild(tocHeading); nav.appendChild(hideSidebarButton); - nav.appendChild(toc); + nav.appendChild(this.toc); // Insert the nav and button elements const main = document.getElementsByTagName('main')?.[0]; @@ -168,7 +177,7 @@ const tableOfContents = new function() { // Append the elements to the nav nav.appendChild(tocHeading); nav.appendChild(moveToSidebarButton); - const clonedToc = toc.cloneNode(true); + const clonedToc = this.toc.cloneNode(true); nav.appendChild(clonedToc); // Create the button to toggle the TOC @@ -198,10 +207,11 @@ const tableOfContents = new function() { document.getElementsByTagName('body')[0].classList.add('enable-toc-as-sidebar'); // Hide the TOC toggle button and content - const toc = document.getElementById('enable-toc--toggle'); - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - toggleButton.setAttribute('aria-expanded', 'false'); - toc.style.display = 'none'; + const { toggleButtonSelector, toggleTOCSelector } = commonSelectors(); + toggleButtonSelector?.setAttribute('aria-expanded', 'false'); + if (toggleTOCSelector) { + toggleTOCSelector.style.display = 'none'; + } // Set the cookie to remember the sidebar state setCookie('enable-toc-as-sidebar', 'true'); @@ -215,26 +225,32 @@ const tableOfContents = new function() { document.getElementsByTagName('body')[0].classList.remove('enable-toc-as-sidebar'); // Update the toggle button tooltip - const toggleButton = document.querySelector('.enable-toc__toggle-button'); - toggleButton.setAttribute('data-tooltip', 'The Table of Contents has moved here. Click to open or close.'); + const { toggleButtonSelector } = commonSelectors(); + toggleButtonSelector?.setAttribute('data-tooltip', 'The Table of Contents has moved here. Click to open or close.'); // Set the cookie to remember the sidebar state setCookie('enable-toc-as-sidebar', 'false'); // Focus on the TOC toggle button - document.querySelector('.enable-toc__toggle-button').focus(); + toggleButtonSelector.focus(); } - this.init = (skipPages = [], showAsSidebarDefault = true, numberFirstLevelHeadings = true) => { + this.init = ({ + skipPages = [], + showAsSidebarDefault = true, + numberFirstLevelHeadings = true, + selectorToSkipHeadingsWithin, + ignoreHeadersDeeperThan, + }) => { // Skip the Table of Contents on certain pages if (skipPages.includes(location.pathname)) { return; } // Create the Table of Contents - toc = this.createContent(numberFirstLevelHeadings); + this.toc = this.createContent(numberFirstLevelHeadings, selectorToSkipHeadingsWithin, ignoreHeadersDeeperThan); - // Insert the TOC beside the main content and/or beside the H1 + // Insert the TOC beside the main content and beside the H1 this.appendAsSidebar(); this.appendAsToggleButton(); @@ -248,6 +264,9 @@ const tableOfContents = new function() { if (!sidebarCookieValue) { setCookie('enable-toc-as-sidebar', `${showAsSidebarDefault}`); } + + // Add the tooltip component + tooltip.init(); // Tooltip module currently not working } } From 878252501adcde0286a65ea9d636640ce27ef6af Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Mon, 17 Jun 2024 11:40:31 -0400 Subject: [PATCH 116/274] fix[Issue#49] - fix the grammar / spelling issues on Controls Section pages --- content/body/carousel.php | 6 ++--- content/body/dialog.php | 6 ++--- content/body/dropdown.php | 18 ++++++------- content/body/form-error-checking.php | 12 ++++----- content/body/link.php | 8 +++--- content/body/multi-level-hamburger-menu.php | 28 ++++++++++----------- content/body/skip-link.php | 15 ++++++----- content/body/switch.php | 6 ++--- content/body/tabs.php | 14 +++++------ content/body/tooltip.php | 12 ++++----- content/body/video-content.php | 2 +- content/body/video-player.php | 12 ++++----- templates/includes/npm.php | 14 +++++------ templates/includes/showcode-template.php | 4 +-- 14 files changed, 78 insertions(+), 79 deletions(-) diff --git a/content/body/carousel.php b/content/body/carousel.php index 6afce50d..1b651384 100644 --- a/content/body/carousel.php +++ b/content/body/carousel.php @@ -1,6 +1,6 @@

- Carousels are a list of content panels that mouse user can cycle through using arrow controls and panel indicator. - Usually carousels show only one panel at a time, and they usually (but not always) have at least one CTA in them. + Carousels are a list of content panels that the mouse user can cycle through using arrow controls and panel indicators. + Usually, carousels show only one panel at a time, and they usually (but not always) have at least one CTA in them. They exist to cram as much content in the valuable "Above The Fold" real estate on websites. Although it is @@ -20,7 +20,7 @@

- That said, there are times when as a web developer you are asked upon implementing an accessible one. On this page are two ways of implementing accessible carousels: one solution is good when you know there will be at least one interactive control in each panel, and the other is good when you cannot make that guarantee. + That said, there are times when as a web developer you are asked to implement an accessible one. On this page are two ways of implementing accessible carousels: one solution is good when you know there will be at least one interactive control in each panel, and the other is good when you cannot make that guarantee. Note that all the carousels on this page use Glider.js, but the code walkthrough below will contain information developers need to implement accessible carousels regardless of the diff --git a/content/body/dialog.php b/content/body/dialog.php index b34cbed7..5e1ea065 100644 --- a/content/body/dialog.php +++ b/content/body/dialog.php @@ -2,10 +2,10 @@

Modals are pieces of stand-alone content that pop up inside of the main web page document. If that content is not - interactive (i.e. just formatted text), then the modal has a role of alertdialog. Modals with interactive content inside have a role of dialog. This instructions on this page cover the dialog role. + href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/dialog_role">dialog. The instructions on this page cover the dialog role.

HTML5 Modal Dialog

@@ -24,7 +24,7 @@ role="dialog". Making modal dialogs accessible for the first time can be tricky. Full notes on how the accessibility features of this example can be - found on my blog post, + found in my blog post, Creating Accessible HTML5 Modal Dialogs For Desktop and Mobile

diff --git a/content/body/dropdown.php b/content/body/dropdown.php index b8934af3..85e980f4 100644 --- a/content/body/dropdown.php +++ b/content/body/dropdown.php @@ -28,7 +28,7 @@ ]); ?>

- This should be the ideal solution, since it is a native HTML5 control that doesn't require + This should be the ideal solution since it is a native HTML5 control that doesn't require JavaScript. However, there are some issues:

@@ -39,7 +39,7 @@ drawer is opened (most of the time it does, but I have noticed it enough to make mention of it here).
  • - Chrome with Voiceover report "Disclosure triangle" as the role, which is a quite odd and misleading. + Chrome with Voiceover report "Disclosure triangle" as the role, which is quite odd and misleading.
  • Chromevox doesn't indicate that the summary is expandable when it gains keyboard focus @@ -50,7 +50,7 @@ These issues have been around for a while (see Graham Armfield's article about the details tag from 2019). I hope that browser manufacturers can fix their accessibility APIs so - this can work correctly in all platforms. + this can work correctly on all platforms.

    @@ -178,16 +178,16 @@ true, - "comment" => "This is best solution for both new and existing work.", + "comment" => "This is the best solution for both new and existing work.", ]); ?> true, ]); ?>

    - Even though this is not native, it is pretty easy to set up. There is really one HTML attribute that you have to + Even though this is not native, it is pretty easy to set up. There is one HTML attribute that you have to change with JavaScript when the drawer opens or closes: aria-expanded. I just use CSS to style it - depending what that attribute is set to, and everything just works. + depending on what that attribute is set to, and everything just works.

    @@ -262,15 +262,15 @@ true, "comment" => - "This is a better solution that using multi-select <select> boxes, in my opinion.", + "This is a better solution than using multi-select <select> boxes, in my opinion.", ]); ?>

    Frequently, there is a requirement to create a "multi-select selectbox". It is possible to do this with the - <select> tag, but many users (sighted, partially sighted and non-sighted) + <select> tag, but many users (sighted, partially sighted, and non-sighted) have difficulty using them and don't even know they are multi-selectable (the comments in Juan Manuel Ramallo's blog post, Does anyone else - think HTML5 multiple select sucks? agrees, and the comments in the post show a lot of developers agree). I have + think HTML5 multiple select sucks? agrees, and the comments in the post, show a lot of developers agree). I have found making dropdowns with checkboxes inside is a better solution, and this can be done without a lot of effort.

    diff --git a/content/body/form-error-checking.php b/content/body/form-error-checking.php index 4af5de63..87dce91f 100644 --- a/content/body/form-error-checking.php +++ b/content/body/form-error-checking.php @@ -3,11 +3,11 @@

    - There are two ways to make accessible forms: the native HTML5 way, or using JavaScript. You would think + There are two ways to make accessible forms: the native HTML5 way or using JavaScript. You would think it would be a no-brainer to just code things the HTML5 way and call it a day instead of creating custom - code. However, many designers don't like the design of native HTML5 error message "bubbles", and want/demand + code. However, many designers don't like the design of the native HTML5 error message "bubbles", and want or demand that flexibility. Since there doesn't seem to be any easy cross-browser workaround for this, I have labelled - both of the solution below good for new and existing work: the HTML5 one is good if you want to implement + both of the solutions below as good for new and existing work: the HTML5 one is good if you want to implement validation quickly, and the custom JavaScript implementation is good if you want design flexibility.

    @@ -24,9 +24,9 @@

    You can use just required and - pattern attributes on HTML forms to do client side - validation without JavaScript. However, in order to make the messaging - more accessible, we have added a tiny bit of JavaScript code (inspired by this accessible HTML5 forms code demo by Paul J Adam) in order to ensure the error messages + pattern attributes on HTML forms to do client-side + validation without JavaScript. However, to make the messaging + more accessible, we have added a tiny bit of JavaScript code (inspired by this accessible HTML5 forms code demo by Paul J Adam) to ensure the error messages themselves are more accessible to screen reader users (see the last step in the code walkthrough for details)

    diff --git a/content/body/link.php b/content/body/link.php index 23c8a18a..358adbd6 100644 --- a/content/body/link.php +++ b/content/body/link.php @@ -66,10 +66,10 @@

    Links That "Look Like Buttons"

    - There are many times where you want a CTA to stick out from the rest of the text, maybe even covering a more prominent + There are many times when you want a CTA to stick out from the rest of the text, maybe even covering a more prominent and bigger area on the screen. If you make the CTA look like a button, you are being dishonest: it looks like - a button, but its not since it has a URL associated with it. In order to distinguish this from other buttons, we - should make a small change to it, like adding a right pointing chevron to the CTA like the example below: + a button, but it's not since it has a URL associated with it. To distinguish this from other buttons, we + should make a small change to it, like adding a right-pointing chevron to the CTA like the example below:

    @@ -101,7 +101,7 @@

    Breadcrumbs

    - Breadcrumbs are usually at the top of the page after the main nav. Users can use them to navigate the hierarchy that + Breadcrumbs are usually at the top of the page after the main nav. Users can use them to navigate the hierarchy where the current page resides.

    diff --git a/content/body/multi-level-hamburger-menu.php b/content/body/multi-level-hamburger-menu.php index 1fc7541f..aedaf56d 100644 --- a/content/body/multi-level-hamburger-menu.php +++ b/content/body/multi-level-hamburger-menu.php @@ -17,16 +17,16 @@

    This is the component that the most development and testing time was spent on. On many sites I have done accessibility - audits on, there is a main navigation that appears as a traditional flyout menu on the desktop breakpoint, and a + audits on, there is a main navigation that appears as a traditional flyout menu on the desktop breakpoint and a mobile hamburger menu on the tablet and mobile breakpoints. More often than not, this component would have several accessibility issues:

      -
    1. On desktop, when the user opened a menu flyout and tabbed through the flyout to the next flyout button, the flyout +
    2. On the desktop, when the user opened a menu flyout and tabbed through the flyout to the next flyout button, the flyout wouldn't close.
    3. On mobile, there wouldn't be a focus loop around the hamburger menu when opened.
    4. -
    5. On mobile, when opening up a submenu, focus wouldn't go the the back button/close button of the new submenu.
    6. +
    7. On mobile, when opening up a submenu, the focus wouldn't go the the back button/close button of the new submenu.
    8. Links and collapsable buttons were not marked up correctly.
    @@ -54,7 +54,7 @@ "Screenshot of the banner on the top of this page in the mobile breakpoint", ); ?> -
    Figure 1. The hamburger menu icon appears on the upper right hand side of the page. It is +
    Figure 1. The hamburger menu icon appears on the upper right-hand side of the page. It is denoted by three horizontal lines that has become the standard.
    @@ -75,14 +75,14 @@
    Figure 2. When the hamburger menu icon is clicked, the black menu above appears. - It has a close button (that gains keyboard focus when first opened) and few CTAs + It has a close button (that gains keyboard focus when first opened) and a few CTAs stacked on top of each other.

    The user can choose any item inside that menu with either a mouse or keyboard. Menu subcategories are visually indicated - by a right pointing chevron, and to assistive technologies as + by a right-pointing chevron, and assistive technologies as collapsible/expandable buttons. Clicking on these subcategory buttons will show the subcategory menu appearing, with keyboard focus being applied to the back button that will take users back @@ -91,7 +91,7 @@

    - Keyboard users experiencing a focus loop + Keyboard users experience a focus loop that keeps the current menu panel until the menu is closed. If the user either uses a mouse to click outside the menu or hits the Escape key, the menu will close. @@ -101,8 +101,8 @@

    If you are in the desktop breakpoint (i.e. a viewport width greater - then or equal to ), then a mega menu - will appear in across to top of the page underneath the Enable logo in the + than or equal to ), then a mega menu + will appear across to top of the page underneath the Enable logo in the global header.

    @@ -115,14 +115,14 @@
    Figure 3. The mega menu is a horizontal bar with the - top level CTAs appearing inside it next to one another. + top-level CTAs appearing inside it next to one another.

    Users can either click the CTAs in the menu with either a mouse or keyboard. Menu subcategories are visually indicated - by a downwards pointing chevron, and to assistive technologies as + by a downwards pointing chevron, and assistive technologies as collapsible/expandable buttons. Clicking on these subcategory buttons will show the subcategory menu appearing below the button. Keyboard users can then tab immediately into the subcategory menu with @@ -153,13 +153,13 @@

    -

    How can I use this script in my own site

    +

    How can I use this script on my site?

      -
    1. Follow the instructions below in order to learn how to download the hamburger menu +
    2. Follow the instructions below to learn how to download the hamburger menu library.
    3. -
    4. Use the following code walkthrough below to create your own menu navigation.
    5. +
    6. Use the following code walkthrough below to create your menu navigation.
    diff --git a/content/body/skip-link.php b/content/body/skip-link.php index ada44655..d859752b 100644 --- a/content/body/skip-link.php +++ b/content/body/skip-link.php @@ -3,7 +3,7 @@

    When keyboard users encounter components with a lot of interactive elements in them (e.g. a website's main - navigation), they may want to tab (or on a mobile device, swipe) 100 times through those elements in order to get to + navigation), they may want to tab (or on a mobile device, swipe) 100 times through those elements to get to the CTAs in the main content. Skip links fix this issue. @@ -11,7 +11,7 @@

    Traditionally, a skip link is a keyboard-only link that keyboard users - can use to skip blocks of interactive elements. They are usually visible only when focused into and mouse + can use to skip blocks of interactive elements. They are usually visible only when focused on and mouse users will never see them.

    @@ -28,11 +28,11 @@ "Please review our mobile friendly solution before you decide to choose this one.", ]); ?> -

    This is an variation of a traditional skip link seen on many websites today. They appear only when keyboard focus is applied to them. Note that while it works well on desktop, it fails on mobile, due to screen readers not passing - focus events to the mobile browser. Screen reader users can focus into them, but partially sighted users may be initially confused when they cannot see what they are focusing into.

    +

    This is a variation of a traditional skip link seen on many websites today. They appear only when keyboard focus is applied to them. Note that while it works well on desktop, it fails on mobile, due to screen readers not passing + focus events to the mobile browser. Screen reader users can focus on them, but partially sighted users may be initially confused when they cannot see what they are focusing on.

    -

    Unlike a lot of implementations, this example has, in my opinion, one really super helpful feature: we have two skip links pointing to each other. If a keyboard user triggers the skip link by accident, they can undo - their mistakes by pressing they ENTER key again, which will return focus back to where they came from. This is useful for people who have hand tremors. This feature was thought up by my colleague Alison Hall during an accessibility hackathon.

    +

    Unlike a lot of implementations, this example has, in my opinion, one super helpful feature: we have two skip links pointing to each other. If a keyboard user triggers the skip link by accident, they can undo + their mistakes by pressing the ENTER key again, which will return the focus to where they came from. This is useful for people who have hand tremors. This feature was thought up by my colleague Alison Hall during an accessibility hackathon.

    @@ -309,8 +309,7 @@ true]); ?>

    These skip links work on a different principle than the ones above. They use a little bit of JavaScript to - work, - and work really well for mobile screen reader users. Due to technical limitations, once focused on, + work and work well for mobile screen reader users. Due to technical limitations, once focused on, the skip links will remain visible unless they are clicked. This seems like a reasonable tradeoff (and can arguably be better for accessibility).

    diff --git a/content/body/switch.php b/content/body/switch.php index e47da879..416aeb99 100644 --- a/content/body/switch.php +++ b/content/body/switch.php @@ -14,9 +14,9 @@

    - My father, who is partially sighted, has fallen in this trap on a website once on his tablet. He was afraid of - submitting an order form because he felt that the screen reader was lying to him, and he afraid of making a mistake - because he didn't know what the control really did (he wasn't sure what it was, but it certainly didn't look like a + My father, who is partially sighted, has fallen into this trap on a website once on his tablet. He was afraid of + submitting an order form because he felt that the screen reader was lying to him, and he was afraid of making a mistake + because he didn't know what the control did (he wasn't sure what it was, but it certainly didn't look like a checkbox). He tried to explain to me this issue over the phone, and after quite a few minutes not understanding what the trouble was, I went over to his house to see what he was talking about. After looking at his tablet, I learned a valuable lesson: developers shouldn't be dishonest to users to make things easier for themselves. diff --git a/content/body/tabs.php b/content/body/tabs.php index d2108a4d..f1c074a5 100644 --- a/content/body/tabs.php +++ b/content/body/tabs.php @@ -33,7 +33,7 @@ true]); ?>

    - In order to make a tablist accessible, there are a few complications: + To make a tablist accessible, there are a few complications:

      @@ -41,22 +41,22 @@ href="radiogroup.php">how users navigate a group of radio buttons)
    1. Keyboard users may not know how this interaction works, and when they try to navigate through the tablist with a Tab key, they will be a bit confused when they skip over the whole list with one key press.
    2. -
    3. While you can give screen reader user verbal instructions about how to interact with a tablist, keyboard users - that don't use a screen reader won't hear them.
    4. +
    5. While you can give screen reader users verbal instructions about how to interact with a tablist, keyboard users + who don't use a screen reader won't hear them.

    - In order to fix this UX issue, I show the instructions visually to keyboard users only. These instructions + To fix this UX issue, I show the instructions visually to keyboard users only. These instructions don't appear for mouse users. They also don't appear for mobile screen reader users who don't use a - keyboard. Our implementation "borrows" their visual design, while adding our own code to conform to the W3C's recommended UX for a tablist (their implementation, unfortunately, doesn't seem to work with a keyboard + keyboard. Our implementation "borrows" their visual design, while adding our code to conform to the W3C's recommended UX for a tablist (their implementation, unfortunately, doesn't seem to work with a keyboard with some screen reader/browser combinations, like VoiceOver for Safari on OSX).

    - This issue has been handled in differently in Danger! + This issue has been handled differently in Danger! ARIA tabs, written by Jeff Smith (TL;DR: He decided to not code them - using ARIA tabs, but as a list of links that anchor to the tabpanels). + using ARIA tabs but as a list of links that anchor to the tabpanels).

    diff --git a/content/body/tooltip.php b/content/body/tooltip.php index d3d8da99..d0298f57 100644 --- a/content/body/tooltip.php +++ b/content/body/tooltip.php @@ -1,10 +1,10 @@

    A "tooltip" is a non-modal (or non-blocking) overlay containing text-only content that provides supplemental - information about an existing UI control. It is hidden by default, and becomes available on hover or focus of the + information about an existing UI control. It is hidden by default and becomes available on hover or focus of the control it describes. Sarah M. Higley came up with this definition for what a tooltip is in her article Tooltips in the time of WCAG - 2.1, and its better than anything I could write, so I hope she doesn't mind me stealing it. + 2.1, and it's better than anything I could write, so I hope she doesn't mind me stealing it.

    @@ -19,7 +19,7 @@ ]); ?>

    - This solution can be styled exactly the want, appears on focus, and uses the maximum value of a z-index in the document. It will disappear when keyboard users press the Escape key. It doesn't work in mobile, which while consistent with other tooltip solutions, is something that I am still looking to fix. If anyone has any ideas, please feel to reach out to me on Twitter. + This solution can be styled exactly the want, appears on focus, and uses the maximum value of a z-index in the document. It will disappear when keyboard users press the Escape key. It doesn't work in mobile, which while consistent with other tooltip solutions, is something that I am still looking to fix. If anyone has any ideas, please feel free to reach out to me on Twitter.

    @@ -87,7 +87,7 @@ browser decides looks good.
  • There is a small delay between the time the user hovers the item with the tooltip and when the tooltip appears. There isn't a way to adjust this delay.
  • -
  • The tooltip inherits the z-index of element being hovered. If there are elements close by that +
  • The tooltip inherits the z-index of the element being hovered. If there are elements close by that have a higher stacking order, it will not appear as intended.
  • @@ -97,12 +97,12 @@

    - A really good round up of how the title attribute works, its history, and where it is appropriate to + A really good round-up of how title attribute works, its history, and where it is appropriate to use it is in The Trials and Tribulations of the Title Attribute by Scott O'Hara diff --git a/content/body/video-content.php b/content/body/video-content.php index 71e5078b..d8a377cd 100644 --- a/content/body/video-content.php +++ b/content/body/video-content.php @@ -50,7 +50,7 @@

    What makes a video accessible is widely misunderstood. Many web professionals know about closed captions. - What many don't know is that they absolutely need audio descriptions in order to be WCAG AA compliant.

    + What many don't know is that they need audio descriptions to be WCAG AA compliant.

    diff --git a/content/body/video-player.php b/content/body/video-player.php index d9302b9a..2062250e 100644 --- a/content/body/video-player.php +++ b/content/body/video-player.php @@ -1,15 +1,15 @@

    What makes a video accessible is widely misunderstood. Many web professionals know about closed captions. - What many don't know is that they absolutely need audio descriptions in order to be WCAG AA compliant.

    + What many don't know is that they need audio descriptions to be WCAG AA compliant.

    - For a lot of companies and organizations, re-cutting a alternative cut of each video on their website is - cost prohibitive: + For a lot of companies and organizations, re-cutting an alternative cut of each video on their website is + cost-prohibitive:

      -
    1. It requires a voice-actor to recite the audio descriptions.
    2. +
    3. It requires a voice actor to recite the audio descriptions.
    4. It requires a video editor to re-edit the video.
    5. Sometimes, other footage needs to be shot to accommodate the amount of time needed to insert the audio descriptions into the video.
    6. @@ -17,7 +17,7 @@

      Because of this, I have recommended using our custom build of Able Player - and have the browser insert the audio descriptions. We have added some extra code to our build to ensure that Able Player + and having the browser insert the audio descriptions. We have added some extra code to our build to ensure that Able Player plays the audio descriptions correctly on all devices (the official build has some issues in iOS — we will be submitting a PR to the official AblePlayer code so they can be incorporated in the official build). Able Player requires a separate caption file (in WebVTT format) so the player knows at what time the captions should be read out. In many instances, I also set the player @@ -26,7 +26,7 @@

      - One of the great side-effects is that if you implement audio-descriptions this way, the caption file + the audio + One of the great side effects is that if you implement audio descriptions this way, the caption file + the audio descriptions file will produce a transcript for free, so your video player will meet a WCAG AAA guideline.

      diff --git a/templates/includes/npm.php b/templates/includes/npm.php index 9e082cdd..d14ed9e1 100644 --- a/templates/includes/npm.php +++ b/templates/includes/npm.php @@ -1,6 +1,6 @@

      Installation Instructions

      -

      You can load this JavaScript library into your application in serveral ways: +

      You can load this JavaScript library into your application in several ways:

      • as an ES6 module using Webpack.
      • @@ -16,14 +16,14 @@

        Note: Unlike most of the other Enable JavaScript modules, you cannot load this one as an old-school ES4 JavaScript library. - This is because it tests for browser features (in this case, the <dialog> tag) and if the browser + This is because it tests for browser features (in this case, the <dialog> tag), and if the browser doesn't support it, load the polyfill using the ES6 import() function.

        -

        If you haven't done so already, choosing which you should use is obviously a major architectural decision. +

        If you haven't done so already, choosing which you should use is a major architectural decision. Here are a few articles that will help you decide:

        @@ -45,7 +45,7 @@

        Important Note On The CSS Classes Used In This Module:

        -

        This module requires specific CSS class names to be used in order it to work correctly. +

        This module requires specific CSS class names to be used for it to work correctly. These CSS classes begin with __. Please see the documentation above to see where these CSS classes are inserted. @@ -190,8 +190,8 @@

        1. Grab the source by either using NPM, grabbing a ZIP file or cloning the enable source code from github. + href="https://github.com/PublicisSapient/enable-a11y/archive/refs/heads/master.zip">grabbing a ZIP file, or cloning the enable source code from GitHub.
        2. If you want to load the module as a native ES6 module, copy js/modules/.js @@ -211,7 +211,7 @@
        3. - Load the CSS in the head of you document: + Load the CSS in the head of your document: <html> diff --git a/templates/includes/showcode-template.php b/templates/includes/showcode-template.php index c910db79..a012543e 100755 --- a/templates/includes/showcode-template.php +++ b/templates/includes/showcode-template.php @@ -15,8 +15,8 @@ class="showcode__heading">Code Walkthrough of the Above Example>

          - Below is the HTML of the above example. Use the dropdown - to highlight each of the individual steps that makes the + Below is the HTML of the above example. Use the drop-down + to highlight each of the individual steps that make the example accessible.

          From 2e4a8d70460a858fb4a458064ea1e1fabe146ec4 Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:41:05 -0700 Subject: [PATCH 117/274] Remove the usage of globals and values from the first element with data-has-character-count. Respect internationalization of strings. --- js/modules/enable-character-count.js | 72 ++++++++++-------------- js/modules/es4/enable-character-count.js | 72 ++++++++++-------------- 2 files changed, 60 insertions(+), 84 deletions(-) diff --git a/js/modules/enable-character-count.js b/js/modules/enable-character-count.js index c3f9abca..438ade25 100644 --- a/js/modules/enable-character-count.js +++ b/js/modules/enable-character-count.js @@ -1,20 +1,12 @@ import { interpolate } from "./interpolate.js"; const enableCharacterCount = new function() { - let globalWarningThreshold, - readCountKey, - announcementTimeout; - + const defaultReadCharacterCountKey = 'Escape'; + let announcementTimeout; let idIndex = 0; this.init = () => { const charCountEls = document.querySelectorAll('[data-has-character-count]'); - const charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; - const dataset = charCountInitEl ? charCountInitEl.dataset : {}; - - readCountKey = 'Escape'; - globalWarningThreshold = dataset.warningThreshold || 20; - charCountEls.forEach((target) => { setUpEventsFor(target); setIdIfNullFor(target); @@ -32,8 +24,9 @@ const enableCharacterCount = new function() { } function setIdIfNullFor(target) { - if (target.id == null) + if (target.id == null) { target.id = `enable-character-counter-${idIndex++}`; + } } function setUpAriaDescribedByFor(target) { @@ -54,9 +47,10 @@ const enableCharacterCount = new function() { function getScreenReaderInstructions(target) { const { readCountKey, instructions } = target.dataset; - const defaultInstructions = 'Press ${readCountKey} to find out how many more characters are allowed.'; + const keyToPress = readCountKey ?? defaultReadCharacterCountKey; + const defaultInstructions = 'Press ${keyToPress} to find out how many more characters are allowed.'; const instructionsToInterpolate = instructions ?? defaultInstructions; - return interpolate(instructionsToInterpolate, { readCountKey }); + return interpolate(instructionsToInterpolate, { keyToPress }); } function createCounterContainerFor(target) { @@ -117,47 +111,41 @@ const enableCharacterCount = new function() { function onKeyUp(event) { const { target, key } = event; - const { dataset } = target; - - if (dataset.hasCharacterCount) { - const inputLength = target.value.length; - const { maxLength } = target; - - writeCharCount(target); - - if (inputLength > maxLength - globalWarningThreshold && !wasArrowPressed(key)) { - announceCharacterCountWithDelay(target, 1000); - } + const { dataset, maxLength } = target; + writeCharCount(target); + const inputLength = target.value.length; + const warningThreshold = dataset.warningThreshold ?? 20; + const isWithinWarningThreshold = inputLength > (maxLength - warningThreshold); + if (isWithinWarningThreshold && !wasArrowPressed(key)) { + announceCharacterCountWithDelay(target, 1000); } } function onKeyDown(event) { - const keyPressed = event.key; - if (isReadCharacterCountKeyPressed(event, keyPressed) || isReadCharacterCountCtrlAndKeyPressed(event, keyPressed)) - announceCharacterCount(event.target); - } - - function isReadCharacterCountKeyPressed(event, keyPressed) { - const readCharacterCountWithKey = event.target.dataset.readCharacterCountWithKey; - return readCharacterCountWithKey && keyPressed === readCharacterCountWithKey; + const { target, key } = event; + const { dataset } = target; + if (isReadCharacterCountKeyPressed(key, dataset.readCountKey)) { + announceCharacterCount(target); + } } - function isReadCharacterCountCtrlAndKeyPressed(event, keyPressed) { - const readCharacterCountWithCtrlAndKey = event.target.dataset.readCharacterCountWithCtrlAndKey; - return readCharacterCountWithCtrlAndKey && event.ctrlKey && keyPressed === readCharacterCountWithCtrlAndKey; + function isReadCharacterCountKeyPressed(keyPressed, readCountKey) { + if (readCountKey) { + return keyPressed === readCountKey; + } else { + return keyPressed === defaultReadCharacterCountKey; + } } function onFocus(event) { const { target } = event; - const { dataset } = target; - if (dataset.hasCharacterCount) - announceCharacterCount(target); + announceCharacterCount(target); } function announceCharacterCount(target) { - announceCharacterCountWithDelay(target, 250) + announceCharacterCountWithDelay(target, 200); } - + function announceCharacterCountWithDelay(target, delay) { const counterForScreenReader = getScreenReaderCharacterCount(target); @@ -170,8 +158,8 @@ const enableCharacterCount = new function() { const maxLength = target.maxLength; const numChars = target.value.length; const charsRemaining = maxLength - numChars; - const screenReaderText = target.dataset.screenReaderText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' - counterForScreenReader.textContent = interpolate(screenReaderText, { numChars, maxLength, charsRemaining }); + const characterCountText = target.dataset.characterCountText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' + counterForScreenReader.textContent = interpolate(characterCountText, { numChars, maxLength, charsRemaining }); }, delay); } diff --git a/js/modules/es4/enable-character-count.js b/js/modules/es4/enable-character-count.js index b0131e6a..7a03ff71 100644 --- a/js/modules/es4/enable-character-count.js +++ b/js/modules/es4/enable-character-count.js @@ -1,19 +1,11 @@ const enableCharacterCount = new function() { - let globalWarningThreshold, - readCountKey, - announcementTimeout; - + const defaultReadCharacterCountKey = 'Escape'; + let announcementTimeout; let idIndex = 0; this.init = () => { const charCountEls = document.querySelectorAll('[data-has-character-count]'); - const charCountInitEl = charCountEls.length > 0 ? charCountEls[0] : null; - const dataset = charCountInitEl ? charCountInitEl.dataset : {}; - - readCountKey = 'Escape'; - globalWarningThreshold = dataset.warningThreshold || 20; - charCountEls.forEach((target) => { setUpEventsFor(target); setIdIfNullFor(target); @@ -31,8 +23,9 @@ const enableCharacterCount = new function() { } function setIdIfNullFor(target) { - if (target.id == null) + if (target.id == null) { target.id = `enable-character-counter-${idIndex++}`; + } } function setUpAriaDescribedByFor(target) { @@ -53,9 +46,10 @@ const enableCharacterCount = new function() { function getScreenReaderInstructions(target) { const { readCountKey, instructions } = target.dataset; - const defaultInstructions = 'Press ${readCountKey} to find out how many more characters are allowed.'; + const keyToPress = readCountKey ?? defaultReadCharacterCountKey; + const defaultInstructions = 'Press ${keyToPress} to find out how many more characters are allowed.'; const instructionsToInterpolate = instructions ?? defaultInstructions; - return interpolate(instructionsToInterpolate, { readCountKey }); + return interpolate(instructionsToInterpolate, { keyToPress }); } function createCounterContainerFor(target) { @@ -116,47 +110,41 @@ const enableCharacterCount = new function() { function onKeyUp(event) { const { target, key } = event; - const { dataset } = target; - - if (dataset.hasCharacterCount) { - const inputLength = target.value.length; - const { maxLength } = target; - - writeCharCount(target); - - if (inputLength > maxLength - globalWarningThreshold && !wasArrowPressed(key)) { - announceCharacterCountWithDelay(target, 1000); - } + const { dataset, maxLength } = target; + writeCharCount(target); + const inputLength = target.value.length; + const warningThreshold = dataset.warningThreshold ?? 20; + const isWithinWarningThreshold = inputLength > (maxLength - warningThreshold); + if (isWithinWarningThreshold && !wasArrowPressed(key)) { + announceCharacterCountWithDelay(target, 1000); } } function onKeyDown(event) { - const keyPressed = event.key; - if (isReadCharacterCountKeyPressed(event, keyPressed) || isReadCharacterCountCtrlAndKeyPressed(event, keyPressed)) - announceCharacterCount(event.target); - } - - function isReadCharacterCountKeyPressed(event, keyPressed) { - const readCharacterCountWithKey = event.target.dataset.readCharacterCountWithKey; - return readCharacterCountWithKey && keyPressed === readCharacterCountWithKey; + const { target, key } = event; + const { dataset } = target; + if (isReadCharacterCountKeyPressed(key, dataset.readCountKey)) { + announceCharacterCount(target); + } } - function isReadCharacterCountCtrlAndKeyPressed(event, keyPressed) { - const readCharacterCountWithCtrlAndKey = event.target.dataset.readCharacterCountWithCtrlAndKey; - return readCharacterCountWithCtrlAndKey && event.ctrlKey && keyPressed === readCharacterCountWithCtrlAndKey; + function isReadCharacterCountKeyPressed(keyPressed, readCountKey) { + if (readCountKey) { + return keyPressed === readCountKey; + } else { + return keyPressed === defaultReadCharacterCountKey; + } } function onFocus(event) { const { target } = event; - const { dataset } = target; - if (dataset.hasCharacterCount) - announceCharacterCount(target); + announceCharacterCount(target); } function announceCharacterCount(target) { - announceCharacterCountWithDelay(target, 250) + announceCharacterCountWithDelay(target, 200); } - + function announceCharacterCountWithDelay(target, delay) { const counterForScreenReader = getScreenReaderCharacterCount(target); @@ -169,8 +157,8 @@ const enableCharacterCount = new function() { const maxLength = target.maxLength; const numChars = target.value.length; const charsRemaining = maxLength - numChars; - const screenReaderText = target.dataset.screenReaderText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' - counterForScreenReader.textContent = interpolate(screenReaderText, { numChars, maxLength, charsRemaining }); + const characterCountText = target.dataset.characterCountText ?? 'Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.' + counterForScreenReader.textContent = interpolate(characterCountText, { numChars, maxLength, charsRemaining }); }, delay); } From 46dca43e70f0472035bf71f8d01238b91ba8bef5 Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:34:08 -0700 Subject: [PATCH 118/274] Update the instructions for using the textarea character counter. --- content/body/textbox.php | 51 +++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 30c42ce9..f8167d55 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -205,10 +205,15 @@ + data-warning-threshold="20" + data-visual-text="${numChars}/${maxLength}" + data-description="In edit text area with a ${maxLength} character limit." + data-instructions="Press ${keyToPress} to find out how many more characters are allowed." + data-character-count-text="Character Count: ${numChars} out of ${maxLength}. ${charsRemaining} characters remaining.">
    @@ -232,20 +237,44 @@ { "replaceHtmlRules": {}, "steps": [ - { - "label": "Place an aria-describedby for instructions", - "highlight": "%INLINE%charcount-example ||| aria-describedby" + "label": "Set the 'maxlength' attribute of the textarea element.", + "highlight": "%INLINE%charcount-example ||| maxlength" }, { - "label": "Code the instructions for this component.", - "highlight": "%INLINE%enable-character-count__global ||| id=\"character-count__desc\"", - "notes": "This is the target of the aria-describedby in the previous step." + "label": "Set hasCharacterCount for the textarea element.", + "highlight": "%INLINE%charcount-example ||| data-has-character-count", + "notes": "There's no need to explicitly provide a value of \"true\"." }, { - "label": "Have an ARIA live region to announce when user starts approaching character count limit", - "highlight": "%INLINE%enable-character-count__global ||| %OPENCLOSECONTENTTAG%output", - "notes": "" + "label": "Optional: Provide a key for the user to press to read out the character count.", + "highlight": "%INLINE%charcount-example ||| data-read-count-key", + "notes": "The default is the 'Escape' key. For information regarding which keypress events are recognized visit https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values." + }, + { + "label": "Optional: Change the amount of available characters left before the screen reader starts announcing after each keypress.", + "highlight": "%INLINE%charcount-example ||| data-warning-threshold", + "notes": "The default is 20 characters before the limit is reached." + }, + { + "label": "Optional: Visually change how the character count looks.", + "highlight": "%INLINE%charcount-example ||| data-visual-text", + "notes": "${numChars} and ${maxLength} are used to string interpolate the values. Make sure they're present. The default is ${numChars}/${maxLength} which looks like: 11/100." + }, + { + "label": "Optional—Internationalization: Change the description.", + "highlight": "%INLINE%charcount-example ||| data-description", + "notes": "${maxLength} is required to string interpolate the value set in Step #1 so that the screen reader can let the user know the maximum character limit." + }, + { + "label": "Optional—Internationalization: Change the keypress instructions.", + "highlight": "%INLINE%charcount-example ||| data-instructions", + "notes": "${keyToPress} is required to string interpolate the value set in Step #3 so that the screen reader can let the user know which key to press." + }, + { + "label": "Optional—Internationalization: Change the announcement for character count.", + "highlight": "%INLINE%charcount-example ||| data-character-count-text", + "notes": "Provide both ${numChars} and ${maxLength} together to provide an informative announcement. ${charsRemaining} may or may not be beneficial and can be omitted." } ] } From 0747509af63a6ee09191ceb5c17f06646c3d2d72 Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:39:03 -0700 Subject: [PATCH 119/274] Fix wording in Steps. --- content/body/textbox.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index f8167d55..cc87e63a 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -264,17 +264,17 @@ { "label": "Optional—Internationalization: Change the description.", "highlight": "%INLINE%charcount-example ||| data-description", - "notes": "${maxLength} is required to string interpolate the value set in Step #1 so that the screen reader can let the user know the maximum character limit." + "notes": "${maxLength} is required to string interpolate the value set in Step #1 so that the screen reader can let the user know the maximum character limit. The value highlighted below is the default." }, { "label": "Optional—Internationalization: Change the keypress instructions.", "highlight": "%INLINE%charcount-example ||| data-instructions", - "notes": "${keyToPress} is required to string interpolate the value set in Step #3 so that the screen reader can let the user know which key to press." + "notes": "${keyToPress} is required to string interpolate the value set in Step #3 so that the screen reader can let the user know which key to press. The value highlighted below is the default." }, { "label": "Optional—Internationalization: Change the announcement for character count.", "highlight": "%INLINE%charcount-example ||| data-character-count-text", - "notes": "Provide both ${numChars} and ${maxLength} together to provide an informative announcement. ${charsRemaining} may or may not be beneficial and can be omitted." + "notes": "Provide both ${numChars} and ${maxLength} together to provide an informative announcement. ${charsRemaining} may or may not be beneficial and can be omitted. The value highlighted below is the default." } ] } From a06628f220956af880173fc19dd5ab6414bdb6ea Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:40:33 -0700 Subject: [PATCH 120/274] Fix wording one more time. --- content/body/textbox.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index cc87e63a..5ff665ac 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -264,17 +264,17 @@ { "label": "Optional—Internationalization: Change the description.", "highlight": "%INLINE%charcount-example ||| data-description", - "notes": "${maxLength} is required to string interpolate the value set in Step #1 so that the screen reader can let the user know the maximum character limit. The value highlighted below is the default." + "notes": "${maxLength} is required to string interpolate the value set in Step #1 so that the screen reader can let the user know the maximum character limit. The description text highlighted below is the default." }, { "label": "Optional—Internationalization: Change the keypress instructions.", "highlight": "%INLINE%charcount-example ||| data-instructions", - "notes": "${keyToPress} is required to string interpolate the value set in Step #3 so that the screen reader can let the user know which key to press. The value highlighted below is the default." + "notes": "${keyToPress} is required to string interpolate the value set in Step #3 so that the screen reader can let the user know which key to press. The instructions text highlighted below is the default." }, { "label": "Optional—Internationalization: Change the announcement for character count.", "highlight": "%INLINE%charcount-example ||| data-character-count-text", - "notes": "Provide both ${numChars} and ${maxLength} together to provide an informative announcement. ${charsRemaining} may or may not be beneficial and can be omitted. The value highlighted below is the default." + "notes": "Provide both ${numChars} and ${maxLength} together to provide an informative announcement. ${charsRemaining} may or may not be beneficial and can be omitted. The text value highlighted below is the default." } ] } From 35fd75af895a71c293b3d4280d3b6a2d868de6dc Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Thu, 20 Jun 2024 10:57:39 -0400 Subject: [PATCH 121/274] fix[Issue#49] - fix the grammar / spelling issue in Form Elements section --- content/body/button.php | 12 +++++------ content/body/checkbox.php | 20 +++++++++--------- content/body/listbox.php | 17 +++++++--------- content/body/radiogroup.php | 6 +++--- content/body/slider.php | 23 ++++++++++----------- content/body/spinner.php | 10 ++++----- content/body/textbox.php | 16 +++++++-------- templates/includes/npm.php | 26 ++++++++++++------------ templates/includes/showcode-template.php | 4 ++-- 9 files changed, 65 insertions(+), 69 deletions(-) diff --git a/content/body/button.php b/content/body/button.php index 0823433b..1dc0f6f7 100644 --- a/content/body/button.php +++ b/content/body/button.php @@ -25,13 +25,13 @@ -->

    - A Button is an interactive element that triggers an action on a webpage or submits a form. + A button is an interactive element that triggers an action on a webpage or submits a form. The <button> tag has been available for a long time and developers should utilize it for this specific purpose.

    This may seem obvious to many developers. - Unfortunately there is a lot of code out there that uses <a> tags + Unfortunately, there is a lot of code out there that uses <a> tags to do the work of a <button>. This is problematic from an accessibility standpoint because those relying on screen readers may mistakenly assume that clicking it will redirect them to another page. When I review code like this, it makes me sad. If you do this in a new project, @@ -91,7 +91,7 @@

    I can't tell you how many times I have seen buttons incorrectly marked up as links on a project. When dealing with older projects where it would be time-consuming to refactor the existing functionality and convert these "pseudo-buttons" into proper <button> tags, it might be more practical to add the ARIA role="button" to the existing <a> tags. If you decide to do this, you should first review all the steps below - and see what would entail more work: refactoring the code to use actual HTML buttons, or adding the extra + and see what would entail more work: refactoring the code to use actual HTML buttons or adding the extra JavaScript to the codebase to ensure the "imitation buttons" are accessible.

    @@ -145,7 +145,7 @@

    This hurts my brain. It goes against the ideas of semantic HTML and it makes Tim Berners-Lee cry. - Do you really want to make the Father of the Web cry? What kind of monster are you? + Do you want to make the Father of the Web cry? What kind of monster are you?

    @@ -208,13 +208,13 @@

  • Use the aria-disabled="true" attribute. This doesn't remove the button - from the keyboard tabbing order. It also doesn't prevents click events + from the keyboard tabbing order. It also doesn't prevent click events from being fired except for Chrome on Google Android with Talkback on. (Thanks to Noel Tibbles for pointing this out).
  • -

    The ideal solution would be to use the aria-disabled="true" attribute, while using JavaScript to prevent the click event from performing an action. This allows the button to be accessible by screen reader users, while still notifying them that it is disabled.

    +

    The ideal solution would be to use the aria-disabled="true" attribute while using JavaScript to prevent the click event from performing an action. This allows the button to be accessible by screen reader users, while still notifying them that it is disabled.

    The following button is disabled with the disabled attribute diff --git a/content/body/checkbox.php b/content/body/checkbox.php index 07f3fe8d..949a5682 100644 --- a/content/body/checkbox.php +++ b/content/body/checkbox.php @@ -5,7 +5,7 @@ true]); ?> -You can style an HTML5 checkbox using CSS easily. You don't need to make faux checkbox +You can style an HTML5 checkbox using CSS easily. You don't need to make a faux checkbox using <div> tags.

    @@ -43,7 +43,7 @@

    If you come across a <div> in existing code that is marked up like a checkbox, - you can fix it this way. It is preferable to use the HTML5 version instead, if you can implement + you can fix it this way. It is preferable to use the HTML5 version instead if you can implement it quickly.

    @@ -186,11 +186,11 @@

    We usually think of checkboxes being either checked or unchecked. There is a third - possible state: indeterminate. The most common use-case for this, as outlined in indeterminate. The most common use case for this, as outlined in the CSS Tricks article about Indeterminate Checkboxes, is for nested checkboxes. Consider you have a group of related attributes, like toppings in an ice-cream cone, that you can select with a group of checkboxes. It would be great to be able to have a "select all" - checkbox that can choose all of them — because who doesn't want all the things on their ice-cream cone?!?! The + checkbox that can choose all of them — because who doesn't want all the things on their ice cream cone?!?! The problem is, what is this "select all" checkbox set to when some of the ingredients are checked but not all of them? While I have seen select all checkboxes not checked in this case, it can be argued that we give the indeterminate state for the checkbox. @@ -222,8 +222,8 @@

    Note the last bit in the code above where we set the .checked property to false. This is done for progressive enhancement. While MDN reports that indeterminate is supported in all major browsers, we should still consider that not all - browser/screen reader pairs (e.g. Firefox/NVDA, Firefox/Voiceover at the time of this writing) will announce the indeterminate - state. For this reason, we should set the checked attribute to something that makes sense for screen reader users who + browser/screen-reader pairs (e.g. Firefox/NVDA, Firefox/Voiceover at the time of this writing) will announce the indeterminate + state. For this reason, we should set the checked attribute to something that makes sense for screen-reader users who use those browser/screen-reader pairs. Setting the checkbox to unchecked does make the most sense in this "Select All" scenario.

    @@ -269,7 +269,7 @@

    This example uses a library we developed to set up the hierarchical structure for the select all button to work. Below are the developer notes on how the library does it. If you are interested in using the library, please read the instructions on how to use the library in your own projects.

    + href="#npm-instructions">read the instructions on how to use the library in your projects.

    @@ -294,7 +294,7 @@

    Indeterminate Checkboxes Using ARIA

    - Just like two-state checkboxes, we can use ARIA to create faux-checkboxes. At the time of this writing, there is one advantage of doing so: Firefox using Voiceover and NVDA will report an indeterminate checkbox's state as mixed using ARIA. + Just like two-state checkboxes, we can use ARIA to create faux checkboxes. At the time of this writing, there is one advantage of doing so: Firefox using Voiceover and NVDA will report an indeterminate checkbox's state as mixed using ARIA.

    @@ -361,7 +361,7 @@

    This example uses the same library we used in the native HTML5 example to set up the hierarchical structure for the select all button to work. As you compare the developer notes below to that of the HTML5 example, you will see the way to implement is similar. Please read the instructions on how to use the library in your own projects.

    + href="#npm-instructions">read the instructions on how to use the library in your projects.

    @@ -382,7 +382,7 @@ } -

    How to Install the Hierarchical Checkbox library Into Your Projects

    +

    How to Install the Hierarchical Checkbox Library into Your Projects

    true, diff --git a/content/body/listbox.php b/content/body/listbox.php index 4fc1c58d..a711c60b 100644 --- a/content/body/listbox.php +++ b/content/body/listbox.php @@ -81,15 +81,14 @@

    Although native HTML5 select boxes were difficult to style in the past, it is possible to style the default (i.e. closed) state completely using CSS. We have used Scott - Jehl's cross - browser CSS demo to style our demo below.

    + Jehl's cross-browser CSS demo to style our demo below.

    The fact that we still can't style the options within a select box is a feature, not a bug. - The gut reaction from a lot of designers is to change their appearance, since they understandably want to control - ever aspect of the design of the user interface consistently across browsers and devices. However, mobile browser + The gut reaction from a lot of designers is to change their appearance since they understandably want to control + every aspect of the design of the user interface consistently across browsers and devices. However, mobile browser manufacturers have optimized the HTML5 select box UI to use the strengths of the platform they run on. - Take a look at how the options are displayed when the activates the control: + Take a look at how the options are displayed when the user activates the control:

    @@ -133,10 +132,8 @@ -

    What follows is an excellent customly styled native HTML5 select box. It uses code from Scott Jehl's cross - browser CSS demo that you can download via - NPM. +

    What follows is an excellent custom-styled native HTML5 select box. It uses code from Scott Jehl's cross-browser CSS demo that you can download via NPM. Instead of putting my usual notes as an explanation, visit their blog post Styling a Select Like It's 2019. @@ -265,7 +262,7 @@ ]); ?>

    - This listbox I made is accessible, and I have used in a few projects in the past. It works well, a developer can + This listbox I made is accessible, and I have used it in a few projects in the past. It works well, a developer can ensure it looks the same in all browsers, and I am happy with the accessibility features in it. However, I strongly recommend you use the <select> box example instead. Using this library means that:

    diff --git a/content/body/radiogroup.php b/content/body/radiogroup.php index c251a1d8..85500e15 100644 --- a/content/body/radiogroup.php +++ b/content/body/radiogroup.php @@ -12,7 +12,7 @@ -->

    - Radio buttons are the easiest way to get users to chose one of a small set of choices. Many developers forget that + Radio buttons are the easiest way to get users to choose one of a small set of choices. Many developers forget that a radio button has two labels: one unique to each radio button, and one for the entire group.

    @@ -97,7 +97,7 @@ ]); ?>

    - Radio buttons can be styled using a bit of careful CSS-fu. I styled these ones by refactoring + Radio buttons can be styled using a bit of careful CSS-fu. I styled these by refactoring the basic CSS from the Custom Radio Button CSS Only Codepen by Mana. I added focus states as well ensuring @@ -184,7 +184,7 @@ false]); ?>

    - Use when some developer before you decided making <div> tags look like radio buttons was a good use of time. + Use when some developer before you decided to make <div> tags look like radio buttons was a good use of time. Even though it wasn't. Bad, developer! Bad!

    diff --git a/content/body/slider.php b/content/body/slider.php index 8d71ea98..7ce71686 100644 --- a/content/body/slider.php +++ b/content/body/slider.php @@ -16,9 +16,9 @@

    This page shows two examples of a slider: an HTML5 one implemented with <input type="range"> and - an ARIA one using the slider role and fair amount of JavaScript. While the latter solution + an ARIA one using the slider role and a fair amount of JavaScript. While the latter solution is accessible on both desktop and mobile, it works so differently than the native one in mobile devices due - to JavaScript limitations, and is a great example of "just because you can so something, it doesn't + to JavaScript limitations, and is a great example of "just because you can do something, it doesn't mean you should".

    @@ -27,7 +27,7 @@ true]); ?>

    - This is by the preferred method of implementing a slider. + This is the preferred method of implementing a slider. It "just works". Note that the UI for mobile screen reader users is very different between the two major operating systems:

    @@ -71,13 +71,13 @@ '

    - Although we give basic information cover how to style HTML5 Sliders, we do gloss over some minor + Although we give basic information covering how to style HTML5 Sliders, we do gloss over some minor cross-browser styling issues. More information on making them look super pretty can be found here:

      -
    • Style Input Range on-line generator tool can +
    • Style Input Range online generator tool can get you up and running quickly.
    • A Sliding Nightmare: Understanding the Range Input @@ -148,7 +148,7 @@

      Sometimes, the need comes up to have a slider with minimum and maximum values. Even though one single HTML5 range element - can't do this, it is possible to combine two of them, with a little bit of CSS and surprisingly tiny amount of JavaScript, to + can't do this, it is possible to combine two of them, with a little bit of CSS and a surprisingly tiny amount of JavaScript, to achieve this effect.

      @@ -156,9 +156,9 @@

      I cannot claim credit for this solution -- it's the work of the hugely talented Ana Tudor. Anyone interested - in bleeding edge CSS and animation work should definitely check out Ana's Codepen - and/or donating to help fund her research and mad scientist + and/or donate to help fund her research and mad scientist inclinations.

      @@ -313,7 +313,7 @@
    • I also refactored it to work with mobile. I couldn't use the same UI as the native <input type="range">, - since using either iOS's vertical swiping and Android's hardware volume buttons to control the slider is impossible + since using either iOS's vertical swiping or Android's hardware volume buttons to control the slider is impossible today.
    • @@ -322,11 +322,10 @@

    - I honestly struggled as to whether it was a Good Idea to share this component to the outside world. In the end, I am + I honestly struggled as to whether it was a Good Idea to share this component with the outside world. In the end, I am posting this here as a great example of The First Rule of ARIA. I do think, however, that it shows an interesting use case for the mobile skip links as - mobile - only buttons, which could be used in something else in the future. + mobile-only buttons, which could be used in something else in the future.

    A note on all ARIA sliders on this page:

    diff --git a/content/body/spinner.php b/content/body/spinner.php index 8bbf7837..c5d47b3b 100644 --- a/content/body/spinner.php +++ b/content/body/spinner.php @@ -40,7 +40,7 @@

    Numeric form fields fall into two categories: ones that are supposed to measure a quantity (e.g. items in a shopping cart, number of dependents in your family), and ones that don't (e.g. a zip code, a social insurance number, etc). - On mobile devices for both of these fields you will want a virtual numeric keyboard to appear. However, it doesn't + On mobile devices, for both of these fields, you will want a virtual numeric keyboard to appear. However, it doesn't make sense for increase/decrease controls to appear for either mouse or keyboard users for those that don't represent a quantity.

    @@ -104,7 +104,7 @@ pattern="[0-9]*">. This is currently what the recommendation - of the UK government when dealing with numeric + of the UK government is when dealing with numeric information that isn't a quantity.

    @@ -144,7 +144,7 @@ false, "comment" => - 'I also don\'t see any reason why you wouldn\'t want to modify existing code to use this, unless you used <div> tags instead of <input> tags for form fields (in which case, you may really want to question some of your other life choices)', + 'I also don\'t see any reason why you wouldn\'t want to modify existing code to use this, unless you used <div> tags instead of <input> tags for form fields (in which case, you may want to question some of your other life choices)', ]); ?> true, @@ -156,9 +156,9 @@ - Spinbutton using IMG elements for buttons by the Open Ajax Alliance (now - currently offline). I refactored the code and released it as an NPM module for your convenience. It was created before + offline). I refactored the code and released it as an NPM module for your convenience. It was created before <input type="number"> was supported on all browsers. - I would recommend to just use that instead, but if you have existing code you need to fix, use the instructions below + I would recommend just using that instead, but if you have existing code you need to fix, use the instructions below to make it work.

    diff --git a/content/body/textbox.php b/content/body/textbox.php index a4f7bec0..63501f6c 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -33,11 +33,11 @@
  • Maybe because you can't use ::before or ::after pseudo-elements to style form elements, although there are other ways around this without using ARIA.
  • -
  • If you wanted to create a WYSIWYG editor then you would have to do this, since form elements don't allow the +
  • If you wanted to create a WYSIWYG editor, then you would have to do this, since form elements don't allow the editing of formatted text.
  • -

    This last use case we do not cover, since creating an accessible WYSIWYG editor would involve quite a bit of +

    This last use case we do not cover since creating an accessible WYSIWYG editor would involve quite a bit of JavaScript (I will be adding a page in Enable about WYSIWYG editors in the future).

    @@ -100,12 +100,12 @@ false, "comment" => - "Recommended only if you needed to create a JavaScript WYSIWYG editor.", + "It is recommended only if you need to create a JavaScript WYSIWYG editor.", ]); ?>

    - Keep in mind that if you use this in a form, none of the nice free form functionality (e.g.: HTML5 validation, - inclusion of data when submitting a form in an HTTP request, etc), won't work. These example do, however, show up in + Keep in mind that if you use this in a form, none of the nice free-form functionality (e.g., HTML5 validation, + inclusion of data when submitting a form in an HTTP request, etc.) won't work. These examples do, however, show up in Voiceover's Rotor and NVDA's Element Dialogue.

    @@ -184,12 +184,12 @@ -

    Textbox With Character Counter

    +

    Textbox with Character Counter

    The character counter is visible at all times. It is announced to screen reader users when:

      -
    1. They use the keyboard to access the textbox (e.g. using the TAB key).
    2. +
    3. They use the keyboard to access the textbox (e.g., using the TAB key).
    4. When there are n characters left before the textbox is filled, where n is either 20 (the default value) or the value used in the textbox's data-warning-threshold attribute.
    @@ -219,7 +219,7 @@
    -

    The character counter uses a JavaScript library to implement it. Below is the HTML markup needed for it to work, as well as instructions on how to load the library in your own projects.

    +

    The character counter uses a JavaScript library to implement it. Below is the HTML markup needed for it to work, as well as instructions on how to load the library in your projects.

    diff --git a/templates/includes/npm.php b/templates/includes/npm.php index 9e082cdd..a5fcc5a0 100644 --- a/templates/includes/npm.php +++ b/templates/includes/npm.php @@ -1,6 +1,6 @@

    Installation Instructions

    -

    You can load this JavaScript library into your application in serveral ways: +

    You can load this JavaScript library into your application in several ways:

    • as an ES6 module using Webpack.
    • @@ -23,29 +23,29 @@

      -

      If you haven't done so already, choosing which you should use is obviously a major architectural decision. +

      If you haven't done so already, choosing which you should use is a major architectural decision. Here are a few articles that will help you decide:

      -

      Important Note On The CSS Classes Used In This Module:

      +

      Important Note on the CSS Classes Used in This Module:

      -

      This module requires specific CSS class names to be used in order it to work correctly. +

      This module requires specific CSS class names to be used in order for it to work correctly. These CSS classes begin with __. Please see the documentation above to see where these CSS classes are inserted. @@ -184,14 +184,14 @@

      Using ES6 modules natively.

      - This is the method that this page you are reading now loads the scripts. + This is the method by which this page you are reading now loads the scripts.

      1. Grab the source by either using NPM, grabbing a ZIP file or cloning the enable source code from github. + href="https://github.com/PublicisSapient/enable-a11y/archive/refs/heads/master.zip">grabbing a ZIP file, or cloning the enable source code from GitHub.
      2. If you want to load the module as a native ES6 module, copy js/modules/.js @@ -211,7 +211,7 @@
      3. - Load the CSS in the head of you document: + Load the CSS in the head of your document: <html> @@ -231,7 +231,7 @@
      4. - Load your scripts using the follwing code (NOTE: you must use <script type="module">): + Load your scripts using the following code (NOTE: you must use <script type="module">): <script type="module"> diff --git a/templates/includes/showcode-template.php b/templates/includes/showcode-template.php index c910db79..a012543e 100755 --- a/templates/includes/showcode-template.php +++ b/templates/includes/showcode-template.php @@ -15,8 +15,8 @@ class="showcode__heading">Code Walkthrough of the Above Example>

        - Below is the HTML of the above example. Use the dropdown - to highlight each of the individual steps that makes the + Below is the HTML of the above example. Use the drop-down + to highlight each of the individual steps that make the example accessible.

        From 032ca6f74d8f4a96829f5619f2833a648c65ba4f Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 08:59:16 -0700 Subject: [PATCH 122/274] Ensure character counter is only added to textarea when data-has-character-count is true. --- js/modules/enable-character-count.js | 2 +- js/modules/es4/enable-character-count.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/modules/enable-character-count.js b/js/modules/enable-character-count.js index 438ade25..487b9b3d 100644 --- a/js/modules/enable-character-count.js +++ b/js/modules/enable-character-count.js @@ -6,7 +6,7 @@ const enableCharacterCount = new function() { let idIndex = 0; this.init = () => { - const charCountEls = document.querySelectorAll('[data-has-character-count]'); + const charCountEls = document.querySelectorAll("[data-has-character-count='true']"); charCountEls.forEach((target) => { setUpEventsFor(target); setIdIfNullFor(target); diff --git a/js/modules/es4/enable-character-count.js b/js/modules/es4/enable-character-count.js index 7a03ff71..a798cbc6 100644 --- a/js/modules/es4/enable-character-count.js +++ b/js/modules/es4/enable-character-count.js @@ -5,7 +5,7 @@ const enableCharacterCount = new function() { let idIndex = 0; this.init = () => { - const charCountEls = document.querySelectorAll('[data-has-character-count]'); + const charCountEls = document.querySelectorAll("[data-has-character-count='true']"); charCountEls.forEach((target) => { setUpEventsFor(target); setIdIfNullFor(target); From e84bf8e710d242314ad4d08e30d7cdd454c554aa Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:04:49 -0700 Subject: [PATCH 123/274] Edit instructions that claim the character counter uses a Javascript library. --- content/body/textbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 5ff665ac..67cb3fce 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -229,7 +229,7 @@
    -

    The character counter uses a JavaScript library to implement it. Below is the HTML markup needed for it to work, as well as instructions on how to load the library in your own projects.

    +

    Below is the HTML markup needed to add the character counter to a textarea element, as well as instructions on how to use it in your own projects.

    From 2e86109daf80c4a5259c2170375a15e4f256e65b Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:06:34 -0700 Subject: [PATCH 124/274] Step #2 instructions change: explicitly state data-has-character-count attribute instead of hasCharacterCount. --- content/body/textbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 67cb3fce..11d7198f 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -242,7 +242,7 @@ "highlight": "%INLINE%charcount-example ||| maxlength" }, { - "label": "Set hasCharacterCount for the textarea element.", + "label": "Set the custom data-has-character-count attribute for the textarea element.", "highlight": "%INLINE%charcount-example ||| data-has-character-count", "notes": "There's no need to explicitly provide a value of \"true\"." }, From ed488871536c7b39a0fefff683a71163442b0f97 Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:07:57 -0700 Subject: [PATCH 125/274] Remove the note saying data-has-character-count doesn't need to be explicitly set to 'true'. --- content/body/textbox.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 11d7198f..4bb48662 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -243,8 +243,7 @@ }, { "label": "Set the custom data-has-character-count attribute for the textarea element.", - "highlight": "%INLINE%charcount-example ||| data-has-character-count", - "notes": "There's no need to explicitly provide a value of \"true\"." + "highlight": "%INLINE%charcount-example ||| data-has-character-count" }, { "label": "Optional: Provide a key for the user to press to read out the character count.", From 896562b9f52576a6ea06226d66f61556df92b59c Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:15:21 -0700 Subject: [PATCH 126/274] Use an element to link to the MDN docs for Step #3. --- content/body/textbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 4bb48662..5ab4ed78 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -248,7 +248,7 @@ { "label": "Optional: Provide a key for the user to press to read out the character count.", "highlight": "%INLINE%charcount-example ||| data-read-count-key", - "notes": "The default is the 'Escape' key. For information regarding which keypress events are recognized visit https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values." + "notes": "The default is the Escape key. For information regarding which keypress events are recognized visit MDN docs." }, { "label": "Optional: Change the amount of available characters left before the screen reader starts announcing after each keypress.", From 6cec7c8d0a6ee850db15368b571df4baf65920df Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:20:02 -0700 Subject: [PATCH 127/274] Shorten the instructions for Step #4. --- content/body/textbox.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/textbox.php b/content/body/textbox.php index 5ab4ed78..4e17e995 100644 --- a/content/body/textbox.php +++ b/content/body/textbox.php @@ -251,7 +251,7 @@ "notes": "The default is the Escape key. For information regarding which keypress events are recognized visit MDN docs." }, { - "label": "Optional: Change the amount of available characters left before the screen reader starts announcing after each keypress.", + "label": "Optional: Change how many characters are left before announcing after each keypress.", "highlight": "%INLINE%charcount-example ||| data-warning-threshold", "notes": "The default is 20 characters before the limit is reached." }, From 81ccb28a0fc12352dd29232b3608c021408250bf Mon Sep 17 00:00:00 2001 From: Jadan Ou <17204721+ocjadan@users.noreply.github.com> Date: Thu, 20 Jun 2024 09:50:33 -0700 Subject: [PATCH 128/274] Code clean up. --- js/modules/enable-character-count.js | 9 +++------ js/modules/es4/enable-character-count.js | 9 +++------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/js/modules/enable-character-count.js b/js/modules/enable-character-count.js index 487b9b3d..86aae0f9 100644 --- a/js/modules/enable-character-count.js +++ b/js/modules/enable-character-count.js @@ -48,8 +48,7 @@ const enableCharacterCount = new function() { function getScreenReaderInstructions(target) { const { readCountKey, instructions } = target.dataset; const keyToPress = readCountKey ?? defaultReadCharacterCountKey; - const defaultInstructions = 'Press ${keyToPress} to find out how many more characters are allowed.'; - const instructionsToInterpolate = instructions ?? defaultInstructions; + const instructionsToInterpolate = instructions ?? 'Press ${keyToPress} to find out how many more characters are allowed.'; return interpolate(instructionsToInterpolate, { keyToPress }); } @@ -130,11 +129,9 @@ const enableCharacterCount = new function() { } function isReadCharacterCountKeyPressed(keyPressed, readCountKey) { - if (readCountKey) { + if (readCountKey) return keyPressed === readCountKey; - } else { - return keyPressed === defaultReadCharacterCountKey; - } + return keyPressed === defaultReadCharacterCountKey; } function onFocus(event) { diff --git a/js/modules/es4/enable-character-count.js b/js/modules/es4/enable-character-count.js index a798cbc6..41dc310c 100644 --- a/js/modules/es4/enable-character-count.js +++ b/js/modules/es4/enable-character-count.js @@ -47,8 +47,7 @@ const enableCharacterCount = new function() { function getScreenReaderInstructions(target) { const { readCountKey, instructions } = target.dataset; const keyToPress = readCountKey ?? defaultReadCharacterCountKey; - const defaultInstructions = 'Press ${keyToPress} to find out how many more characters are allowed.'; - const instructionsToInterpolate = instructions ?? defaultInstructions; + const instructionsToInterpolate = instructions ?? 'Press ${keyToPress} to find out how many more characters are allowed.'; return interpolate(instructionsToInterpolate, { keyToPress }); } @@ -129,11 +128,9 @@ const enableCharacterCount = new function() { } function isReadCharacterCountKeyPressed(keyPressed, readCountKey) { - if (readCountKey) { + if (readCountKey) return keyPressed === readCountKey; - } else { - return keyPressed === defaultReadCharacterCountKey; - } + return keyPressed === defaultReadCharacterCountKey; } function onFocus(event) { From 507df8d70108ae21ba3525dd96d830dce7b01a65 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Thu, 20 Jun 2024 14:04:24 -0400 Subject: [PATCH 129/274] fix[Issue#49] - fix the grammar / spelling check on form UX pattern section --- content/body/form-error-checking.php | 12 ++--- content/body/form.php | 6 +-- content/body/input-mask.php | 74 +++++++++++++--------------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/content/body/form-error-checking.php b/content/body/form-error-checking.php index 4af5de63..59aaec50 100644 --- a/content/body/form-error-checking.php +++ b/content/body/form-error-checking.php @@ -7,7 +7,7 @@ it would be a no-brainer to just code things the HTML5 way and call it a day instead of creating custom code. However, many designers don't like the design of native HTML5 error message "bubbles", and want/demand that flexibility. Since there doesn't seem to be any easy cross-browser workaround for this, I have labelled - both of the solution below good for new and existing work: the HTML5 one is good if you want to implement + both of the solutions below good for new and existing work: the HTML5 one is good if you want to implement validation quickly, and the custom JavaScript implementation is good if you want design flexibility.

    @@ -24,9 +24,9 @@

    You can use just required and - pattern attributes on HTML forms to do client side - validation without JavaScript. However, in order to make the messaging - more accessible, we have added a tiny bit of JavaScript code (inspired by this accessible HTML5 forms code demo by Paul J Adam) in order to ensure the error messages + pattern attributes on HTML forms to do client-side + validation without JavaScript. However, to make the messaging + more accessible, we have added a tiny bit of JavaScript code (inspired by this accessible HTML5 forms code demo by Paul J Adam) to ensure the error messages themselves are more accessible to screen reader users (see the last step in the code walkthrough for details)

    @@ -139,7 +139,7 @@

    You can do the custom validation as well, but you have to ensure that - when the form submits and there is an error, the first input value with + when the form is submitted and there is an error, the first input value with an error receives focus so that keyboard and/or screen reader users can correct mistakes easily. You also have to ensure that form errors are marked up as <label> tags for the form fields they are associated with. @@ -148,7 +148,7 @@

    The following example used jQuery.validate() which is - not accessible. We do not recommend to use this library for new projects + not accessible. We do not recommend using this library for new projects ... it is just used as an example of how we can take existing code and make it accessible. If you want information about how to make forms accessible with JavaScript in the general sense, please read diff --git a/content/body/form.php b/content/body/form.php index f4b845dc..feb33b3f 100644 --- a/content/body/form.php +++ b/content/body/form.php @@ -27,7 +27,7 @@

    - Forms must be marked up correctly in order for screen reader users to be able to use them correctly. Please ensure all + Forms must be marked up correctly for screen reader users to be able to use them correctly. Please ensure all your forms are marked up like in the following examples.

    @@ -97,7 +97,7 @@

    Unfortunately, there will be times when you will come across a bit of code that is supposed to be a form but is not - marked up correctly and unusable to screen reader users. Recoding it with fieldset and + marked up correctly and is unusable to screen reader users. Recoding it with fieldset and legend tags may be prohibitive due to:

    @@ -111,7 +111,7 @@

    - While I would still endeavour to advise to code forms correctly, the following code should reduce the amount of coding + While I would still endeavor to advise to code forms correctly, the following code should reduce the amount of coding time on existing work to fix accessibility issues for screen reader users.

    diff --git a/content/body/input-mask.php b/content/body/input-mask.php index 79a0f847..cee6418c 100644 --- a/content/body/input-mask.php +++ b/content/body/input-mask.php @@ -37,7 +37,7 @@ class="enable-table enable-table--centered-data enable-table--with-borders">

    While we want users to be able to input just the numbers into a form, it would be nice to be able to format the input with dashes as users type so they can keep track of which digits have already been entered. This is especially - nice when users are given an even larger set of characters to enter, such as a credit card, a Windows Activation + nice when users are given an even larger set of characters to enter, such as a credit card, or a Windows Activation License key:

    @@ -50,7 +50,7 @@ class="enable-table enable-table--centered-data enable-table--with-borders">

    - In order to deal with this problem, there exist many input masking JavaScript libraries that will mask input as the + To deal with this problem, there are many input masking JavaScript libraries exist that will mask input as the user types. The problem is that a lot of them have quirks that make them hard for all users, especially those with disabilities. I have spent a lot of time playing with input masking, and I have found that in order for an input mask to be truly accessible, it should have the following features: @@ -61,9 +61,9 @@ class="enable-table enable-table--centered-data enable-table--with-borders"> example, if spaces appear in the masked data, it's just for presentational purposes; the data submitted to the server in the end should not have the spaces in it.

  • Flexible input of data: If the input field has data in it, the user should be able to move the - cursor inside the input field with a keyboard or mouse and edit the data anywhere the cursor can move (i.e. not + cursor inside the input field with a keyboard or mouse and edit the data anywhere the cursor can move (i.e., not just at the end of the data). They should also be able to paste data anywhere into the field as well as select - multiple characters that can be replaced or erased. It should be the same behavior as an unmasked input + multiple characters that can be replaced or erased. It should have the same behavior as an unmasked input field.
  • Keyboard friendly: Keyboard users should be able to access the masked field with the TAB key, just like an unmasked input field. @@ -73,13 +73,13 @@ class="enable-table enable-table--centered-data enable-table--with-borders">
  • Screen reader alerts: If the user pauses while typing the data, screen readers will announce all the characters in the input field individually instead of reading the data as a word. This - is because the data used in masking (e.g. phone numbers, credit cards, product keys, etc) are not words, and it + is because the data used in masking (e.g., phone numbers, credit cards, product keys, etc.) are not words, and it is better UX to have the data read out character by character.
  • - When I searched for "Accessible input mask" in Google in Oct 2023, the following three libraries were the most + When I searched for "Accessible input mask" in Google in October 2023, the following three libraries were the most commonly cited, so I tested for these features:

    @@ -133,7 +133,7 @@ class="enable-table enable-table--centered-data enable-table--with-borders"> -

    Since none of them really fit the bill (and I do think that these features are 100% needed to be truly accessible) I +

    Since none of them really fit the bill (and I do think that these features are 100% needed to be truly accessible), I created Enable's Input Making library. You can test it out with a screen reader and keyboard yourself.

    Example 1: Static Input Masking

    @@ -213,17 +213,17 @@ class="enable-table enable-table--centered-data enable-table--with-borders"> } -

    How to Set the data-mask Attribute

    +

    How to set the data-mask attribute

    For the phone number field, you will note it is 999-999-9999. The 9 characters are what we call input characters and represent where inputted data (in this case digits) should appear. The dash characters are what we call format characters and will be automatically put in the visual - field as the user types the numbers in. Users don't need to add them manually.

    -

    Note that spaces, dashes and round brackets (i.e. " ", "-", "(" and + field as the user types in the numbers. Users don't need to add them manually.

    +

    Note that spaces, dashes, and round brackets (i.e., " ", "-", "(" and ")")can be used as format characters. Possible input characters are:

    "_" (underscore)
    -
    Represents any character that isn't a format character
    +
    Represents any character that isn't a format character.
    "U"
    Any non-numeric character that we want to change to uppercase, if possible.
    @@ -238,7 +238,7 @@ class="enable-table enable-table--centered-data enable-table--with-borders">
    Any number
    -

    How Does the Library Work?

    +

    How does the library work?

    If you just want to implement input masking and don't care how it works, just skip this section. If you are @@ -251,12 +251,12 @@ class="enable-table enable-table--centered-data enable-table--with-borders">

    -

    How the DOM and CSS is Set Up.

    +

    How the DOM and CSS are set up.

    We don't change the data inside the input field. Instead, we create an absolutely positioned HTML block (which we call a facade) that, using a higher z-index than the input field, sits on top of it. This contains the formatted input field data and covers the input field so it is no longer visible to the user (We also - make the input field's text transparent and ensure the facade and the input field are the same pixel-size). + make the input field's text transparent and ensure the facade and the input field are the same pixel size).

    @@ -267,37 +267,29 @@ class="enable-table enable-table--centered-data enable-table--with-borders">
    A 3D representation of the DOM of the input mask component.
    -

    The diagram above shows that the input field is stacked underneath a facade that contains the visually formatted - input with the data mask applied. The input field contains the phone number 212-312-1231, without dashes, in it - and the numbers 3121, which is in the middle of the phone number, is selected (presumably because the user wants - to cut, copy or erase it). The facade has the same phone number as the input field, but is formatted with dashes - in the standard places for a North American phone number. The mask's visual data is divided into the three - areas: the text before the selected area (212), the selected text (-3121) and the text after the selected area - (231). This was done so we can mimic the input field's blinking cursor as well as show what data has been - selected by a mouse (more on this below).

    +

    The diagram above shows that the input field is stacked underneath a facade that contains the visually formatted input with the data mask applied. + The input field contains the phone number 212-312-1231, without dashes, and the number 3121, which is in the middle of the phone number, is selected (presumably because the user wants to cut, copy, or erase it). + The facade has the same phone number as the input field but is formatted with dashes in the standard places for a North American phone number. + The mask's visual data is divided into three areas: the text before the selected area (212), the selected text (-3121), and the text after the selected area (231). + This was done so we can mimic the input field's blinking cursor as well as show what data has been selected by a mouse (more on this below).

    Keyboard UX

    -

    The input field is keyboard accessible, and keyboard users can type in data just as they normally would. Keyboard - focus, when applied to the input field, is visible since the input field and the facade are the same size. When - the user types into the input field, JavaScript updates the facade with the same data, except it has format - information. The user can even select text (via the usual SHIFT+arrow keys) and the equivalent text is selected - in the input field underneath. Data can also be cut, copied and pasted from the input field, and the facade will - be appropriately updated.

    +

    The input field is keyboard accessible, and keyboard users can type in data just as they normally would. + Keyboard focus, when applied to the input field, is visible since the input field and the facade are the same size. + When the user types into the input field, JavaScript updates the facade with the same data, except it has format information. + The user can even select text (via the usual SHIFT+arrow keys), and the equivalent text is selected in the input field underneath. + Data can also be cut, copied, and pasted from the input field, and the facade will be appropriately updated.

    Mouse UX

    -

    For mouse users, when the click on what they think is the input field, they are actually clicking on the facade - stacked on top. JavaScript figures out where in the input data they are clicking and ensure the cursor in the - input field stacked underneath is placed in the right area. Because all mouse events are basically passed on to - the input field underneath, the user can select text with a mouse and the appropriate text is selected in the - input field so that is updated correctly.

    +

    For mouse users, when they click on what they think is the input field, they are actually clicking on the facade stacked on top. + JavaScript figures out where in the input data they are clicking and ensures the cursor in the input field stacked underneath is placed in the right area. + Because all mouse events are basically passed on to the input field underneath, the user can select text with a mouse, and the appropriate text is selected in the input field, so that is updated correctly.

    Screen-reader UX

    -

    If the user stops typing for a while, the "formatted value" of the input field is announced (i.e. the input - field's value announced character by character). This is done via an ARIA live region which is described in the - code walkthrough above. So, instead of the screen reader reading the input field as a large integer (in this - case "two billion one hundred twenty three million one hundred twenty one thousand two hundred thirty one"), it - will read it as the phone number one digit at a time (i.e. two one two three one two one two three one). This - makes it easy for screen reader users to know what they just typed in.

    +

    If the user stops typing for a while, the "formatted value" of the input field is announced (i.e., the input field's value is announced character by character). + This is done via an ARIA live region, which is described in the code walkthrough above. + So, instead of the screen reader reading the input field as a large integer (in this case, "two billion one hundred twenty-three million one hundred twenty-one thousand two hundred thirty-one"), + it will read it as the phone number one digit at a time (i.e., two one two three one two one two three one). This makes it easy for screen reader users to know what they just typed in.

    @@ -305,8 +297,8 @@ class="enable-table enable-table--centered-data enable-table--with-borders">

    - Credit card fields are a little different. At the time of this writing, the format characters (i.e. the spaces) are - put into different places depending if it's an American Express (a.k.a. AMEX) Card or another credit card type: + The credit card fields are a little different. At the time of this writing, the format characters (i.e., the spaces) are + put in different places depending on whether it's an American Express (a.k.a. AMEX) card or another credit card type:

    From 378b76fb43faa64859872b1d15a59d6d9989e7d8 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 09:02:27 -0400 Subject: [PATCH 130/274] Update content/body/marquee.php Co-authored-by: Alison Hall Signed-off-by: AKSHAY PANCHAL --- content/body/marquee.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/marquee.php b/content/body/marquee.php index fee5806a..4ff7e5dc 100644 --- a/content/body/marquee.php +++ b/content/body/marquee.php @@ -1,6 +1,6 @@

    Marquees are meant for content that scrolls or updates consistently, like a stock ticker or a news feed. - Although they have a default aria-live value of off, you can use aria-live="polite" to let users hear the information within a marquee in almost real-time. Do this carefully. It would be a bad user experience to have a screen reader update too often. The rest of your application will become unusable to screen reader users due to too much screen reader noise caused by too many alerts. + Although they have a default aria-live value of off, you can use aria-live="polite" to let users hear the information within a marquee in almost real-time. Do this carefully since it would be a bad user experience to have a screen reader update too often. The rest of your application will become unusable to screen reader users due to too much screen reader noise when there are too many alerts.

    From e2bb7cd359d7c0b7fc3ee5bd69fa80dd7a4dae80 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 09:03:51 -0400 Subject: [PATCH 131/274] Update content/body/status.php Co-authored-by: Alison Hall Signed-off-by: AKSHAY PANCHAL --- content/body/status.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/status.php b/content/body/status.php index 064a520d..d7ea5e22 100644 --- a/content/body/status.php +++ b/content/body/status.php @@ -1,5 +1,5 @@

    - Status messages allow screen readers and other assistive technology to tell users that content has changed on the page. It does this without interrupting what the user is doing and changing which interactive element has focus. + Status messages allow screen readers and other assistive technology to tell users that content has changed on the page. It does this without interrupting what the user is doing by changing which interactive element has focus. A perfect example of this use-case is in a search component like in the example below.

    From 0cc14d25583d41c50618746bf49b1b3fda0946aa Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 09:04:19 -0400 Subject: [PATCH 132/274] Update content/body/text-resize.php Co-authored-by: Alison Hall Signed-off-by: AKSHAY PANCHAL --- content/body/text-resize.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/text-resize.php b/content/body/text-resize.php index 67d8b8c2..37adc0a2 100644 --- a/content/body/text-resize.php +++ b/content/body/text-resize.php @@ -228,7 +228,7 @@ functionality
  • Desktop:
    • At the top right, click More and then Settings.
    • -
    • Under "Appearance," next" to "Font size," click the Down arrow . +
    • Under "Appearance", next to "Font size", click the Down arrow . Then select the font size you want (you have a choice of very small, small, medium, large, and very large). You can have a little bit more granular control by clicking "Customize fonts" and moving the "Font Size" range widget.
    From c6bdec8ef61f4dd6b2114ff4ebb2c561a30a99b3 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 10:08:44 -0400 Subject: [PATCH 133/274] fix[Issue#49] - fix the suggested content changes by Alison on PR --- content/body/dropdown.php | 4 ++-- content/body/form-error-checking.php | 2 +- content/body/multi-level-hamburger-menu.php | 6 +++--- content/body/skip-link.php | 2 +- content/body/tooltip.php | 4 ++-- content/body/video-content.php | 2 +- content/body/video-player.php | 2 +- templates/includes/showcode-template.php | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/content/body/dropdown.php b/content/body/dropdown.php index 85e980f4..175e392c 100644 --- a/content/body/dropdown.php +++ b/content/body/dropdown.php @@ -39,7 +39,7 @@ drawer is opened (most of the time it does, but I have noticed it enough to make mention of it here).
  • - Chrome with Voiceover report "Disclosure triangle" as the role, which is quite odd and misleading. + Chrome with Voiceover reports "Disclosure triangle" as the role, which is quite odd and misleading.
  • Chromevox doesn't indicate that the summary is expandable when it gains keyboard focus @@ -270,7 +270,7 @@ have difficulty using them and don't even know they are multi-selectable (the comments in Juan Manuel Ramallo's blog post, Does anyone else - think HTML5 multiple select sucks? agrees, and the comments in the post, show a lot of developers agree). I have + think HTML5 multiple select sucks? agrees, and the comments in the post show a lot of developers agree). I have found making dropdowns with checkboxes inside is a better solution, and this can be done without a lot of effort.

    diff --git a/content/body/form-error-checking.php b/content/body/form-error-checking.php index 87dce91f..addeb863 100644 --- a/content/body/form-error-checking.php +++ b/content/body/form-error-checking.php @@ -6,7 +6,7 @@ There are two ways to make accessible forms: the native HTML5 way or using JavaScript. You would think it would be a no-brainer to just code things the HTML5 way and call it a day instead of creating custom code. However, many designers don't like the design of the native HTML5 error message "bubbles", and want or demand - that flexibility. Since there doesn't seem to be any easy cross-browser workaround for this, I have labelled + that flexibility. Since there doesn't seem to be any easy cross-browser workaround for this, I have labeled both of the solutions below as good for new and existing work: the HTML5 one is good if you want to implement validation quickly, and the custom JavaScript implementation is good if you want design flexibility.

    diff --git a/content/body/multi-level-hamburger-menu.php b/content/body/multi-level-hamburger-menu.php index aedaf56d..a350b69c 100644 --- a/content/body/multi-level-hamburger-menu.php +++ b/content/body/multi-level-hamburger-menu.php @@ -82,7 +82,7 @@

    The user can choose any item inside that menu with either a mouse or keyboard. Menu subcategories are visually indicated - by a right-pointing chevron, and assistive technologies as + by a right-pointing chevron, and to assistive technologies as collapsible/expandable buttons. Clicking on these subcategory buttons will show the subcategory menu appearing, with keyboard focus being applied to the back button that will take users back @@ -102,7 +102,7 @@

    If you are in the desktop breakpoint (i.e. a viewport width greater than or equal to ), then a mega menu - will appear across to top of the page underneath the Enable logo in the + will appear across the top of the page underneath the Enable logo in the global header.

    @@ -122,7 +122,7 @@

    Users can either click the CTAs in the menu with either a mouse or keyboard. Menu subcategories are visually indicated - by a downwards pointing chevron, and assistive technologies as + by a downwards pointing chevron, and to assistive technologies as collapsible/expandable buttons. Clicking on these subcategory buttons will show the subcategory menu appearing below the button. Keyboard users can then tab immediately into the subcategory menu with diff --git a/content/body/skip-link.php b/content/body/skip-link.php index d859752b..af9c13d9 100644 --- a/content/body/skip-link.php +++ b/content/body/skip-link.php @@ -32,7 +32,7 @@ focus events to the mobile browser. Screen reader users can focus on them, but partially sighted users may be initially confused when they cannot see what they are focusing on.

    Unlike a lot of implementations, this example has, in my opinion, one super helpful feature: we have two skip links pointing to each other. If a keyboard user triggers the skip link by accident, they can undo - their mistakes by pressing the ENTER key again, which will return the focus to where they came from. This is useful for people who have hand tremors. This feature was thought up by my colleague Alison Hall during an accessibility hackathon.

    + their mistakes by pressing the ENTER key again, which will return the focus back to where they came from. This is useful for people who have hand tremors. This feature was thought up by my colleague Alison Hall during an accessibility hackathon.

    diff --git a/content/body/tooltip.php b/content/body/tooltip.php index d0298f57..f3d84ed7 100644 --- a/content/body/tooltip.php +++ b/content/body/tooltip.php @@ -19,7 +19,7 @@ ]); ?>

    - This solution can be styled exactly the want, appears on focus, and uses the maximum value of a z-index in the document. It will disappear when keyboard users press the Escape key. It doesn't work in mobile, which while consistent with other tooltip solutions, is something that I am still looking to fix. If anyone has any ideas, please feel free to reach out to me on Twitter. + This solution can be styled exactly as wanted, appears on focus, and uses the maximum value of a z-index in the document. It will disappear when keyboard users press the Escape key. It doesn't work in mobile, which while consistent with other tooltip solutions, is something that I am still looking to fix. If anyone has any ideas, please feel free to reach out to me on Twitter.

    @@ -102,7 +102,7 @@

    - A really good round-up of how title attribute works, its history, and where it is appropriate to + A really good round-up of how the title attribute works, its history, and where it is appropriate to use it is in The Trials and Tribulations of the Title Attribute by Scott O'Hara diff --git a/content/body/video-content.php b/content/body/video-content.php index d8a377cd..187b60b7 100644 --- a/content/body/video-content.php +++ b/content/body/video-content.php @@ -50,7 +50,7 @@

    What makes a video accessible is widely misunderstood. Many web professionals know about closed captions. - What many don't know is that they need audio descriptions to be WCAG AA compliant.

    + What many don't know is that they need audio descriptions in order to be WCAG AA compliant.

    diff --git a/content/body/video-player.php b/content/body/video-player.php index 2062250e..b2683ae0 100644 --- a/content/body/video-player.php +++ b/content/body/video-player.php @@ -1,5 +1,5 @@

    What makes a video accessible is widely misunderstood. Many web professionals know about closed captions. - What many don't know is that they need audio descriptions to be WCAG AA compliant.

    + What many don't know is that they need audio descriptions in order to be WCAG AA compliant.

    diff --git a/templates/includes/showcode-template.php b/templates/includes/showcode-template.php index a012543e..75770767 100755 --- a/templates/includes/showcode-template.php +++ b/templates/includes/showcode-template.php @@ -15,7 +15,7 @@ class="showcode__heading">Code Walkthrough of the Above Example>

    - Below is the HTML of the above example. Use the drop-down + Below is the HTML of the above example. Use the dropdown to highlight each of the individual steps that make the example accessible.

    From 57c1fe2c2360a68c6020d4640a4a9b891fd0cd76 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 11:15:16 -0400 Subject: [PATCH 134/274] fix[Issue# 49] - fix the suggested code changes by Alison on PR --- content/body/heading.php | 2 +- content/body/img.php | 2 +- content/body/progress.php | 2 +- content/body/reflow.php | 2 +- content/body/screen-reader-only-text.php | 6 +++--- content/body/table.php | 4 ++-- content/body/video-content.php | 14 +++++++------- templates/includes/npm.php | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/content/body/heading.php b/content/body/heading.php index b18f55f2..13520e92 100644 --- a/content/body/heading.php +++ b/content/body/heading.php @@ -16,7 +16,7 @@

    - Screen-reader users also use headings to skim through a web page quickly and are used by screen-reader users as a table of contents. For example, using NVDA, this is done via the Elements List, and for Voiceover, this is done via The Rotor. The following video shows how screen reader users use these tools. + Screen reader users also use headings to skim through a web page quickly and are used by screen reader users as a table of contents. For example, using NVDA, this is done via the Elements List, and for Voiceover, this is done via The Rotor. The following video shows how screen reader users use these tools.

    diff --git a/content/body/img.php b/content/body/img.php index 55be3570..01641ddb 100644 --- a/content/body/img.php +++ b/content/body/img.php @@ -183,7 +183,7 @@

    Using SVG Sprites

    - Many developers use SVG Sprites so they can put all their SVG icons in one place and have their svg tags point to a shape in the sprite inside their HTML code. This can make your webpage more efficient when there are a lot of icons on a page, as described in the article Which SVG technique performs best for way too many icons? by Tyler Sticka. Here, we should you how to embed them in an accessible way like the other SVG examples on this page. + Many developers use SVG Sprites so they can put all their SVG icons in one place and have their svg tags point to a shape in the sprite inside their HTML code. This can make your webpage more efficient when there are a lot of icons on a page, as described in the article Which SVG technique performs best for way too many icons? by Tyler Sticka. Here, we show you how to embed them in an accessible way like the other SVG examples on this page.

    diff --git a/content/body/progress.php b/content/body/progress.php index e9a473a6..dd31006d 100644 --- a/content/body/progress.php +++ b/content/body/progress.php @@ -62,7 +62,7 @@ how long it will take to upload a movie file to a web server), then you will want that information updated to the user in real-time as it happens. In the latter case, the developer and UX designer should think about how immediately this information should be given to screen reader users since this information causes - a bit of noise and sets the aria-live level appropriately. If the user is not going to be doing anything + a bit of noise, and set the aria-live level appropriately. If the user is not going to be doing anything else on the screen while the action is happening and needs immediate updates, use aria-live="assertive". If the user is going to be doing other things on the page while the progress bar is updating, use "polite" instead. diff --git a/content/body/reflow.php b/content/body/reflow.php index f0db2849..36564866 100644 --- a/content/body/reflow.php +++ b/content/body/reflow.php @@ -119,7 +119,7 @@

    Common Reflow Problem #2: Filters on Product Listing Pages

    - A common design problem on many e-commerce sites is PLPs (product listing pages). Consider the screenshot below + A common design problem on many e-commerce sites is on PLPs (product listing pages). Consider the screenshot below that shows a desktop PLP with a list of product tiles that follow a list of filters:

    diff --git a/content/body/screen-reader-only-text.php b/content/body/screen-reader-only-text.php index d9fa1fa8..980ac47a 100644 --- a/content/body/screen-reader-only-text.php +++ b/content/body/screen-reader-only-text.php @@ -32,7 +32,7 @@ visually hidden headings to complete a page's outline from Accessibility Developer Guide talks about this in depth.
  • -
  • Text read in your screen reader's reading mode (i.e. using it to read the page, not for reading out the interactive elements that have focus), screen reader only text is guaranteed to work. ARIA labels might not be read in your screen reader reading mode.
  • +
  • Text read in your screen reader's reading mode (i.e. using it to read the page, not for reading out the interactive elements that have focus), screen reader only text is guaranteed to work. ARIA labels might not be read in the reading mode of your screen reader.
  • @@ -86,8 +86,8 @@ class="enable-example"

    What To Remember When Using Screen Reader Only Text Or Aria-Labels In Production

      -
    1. If you are using a content management system (CMS), you should remember that you must make screen reader only text and any aria-labels authorable. I advise always having a default value for these items in case someone forgets to author them, or better still, make them mandatory for authors to fill out in the CMS (since screen reader only text and aria-labels are sometimes forgotten by content authors). This is really important when you have a multilingual website, since hard-coding screen reader-only text and aria-labels will result in that text may not be understood by users who don't know the language that hard-coded text is written in.
    2. -
    3. If you are using a third party like MotionPoint to do your translation, you will want to make sure their service logs the translation of screen reader only text, aria-labels, and image alt text.
    4. +
    5. If you are using a content management system (CMS), you should remember that you must make screen reader only text and any aria-labels authorable. I advise always having a default value for these items in case someone forgets to author them, or better still, make them mandatory for authors to fill out in the CMS (since screen reader-only text and aria-labels are sometimes forgotten by content authors). This is really important when you have a multilingual website, since hard-coding screen reader-only text and aria-labels will result in that text may not be understood by users who don't know the language that hard-coded text is written in.
    6. +
    7. If you are using a third-party like MotionPoint to do your translation, you will want to make sure their service logs the translation of screen reader only text, aria-labels, and image alt text.
    8. You should always use screen-reader only text and aria-labels as a last resort. If there is any visual text that can be used instead, use aria-labelledby to point to that content instead of using an aria-label. This is to ensure the screen reader user experience is as close to the visual experience as much as possible.
    diff --git a/content/body/table.php b/content/body/table.php index 74d6d86e..667de0a0 100644 --- a/content/body/table.php +++ b/content/body/table.php @@ -6,7 +6,7 @@ -

    Many web developers who use <table> markup, however, don't know there are some additional codes they need to make them accessible (even the CSS tricks so called "Complete" Guide to the Table Element doesn't cover these items adequately, in my opinion):

    +

    Many web developers who use <table> markup, however, don't know there is some additional code they need to make them accessible (even the CSS Tricks' so called "Complete" Guide to the Table Element doesn't cover these items adequately, in my opinion):

    • th tags must have a scope attribute set to row if it is a heading for a @@ -522,7 +522,7 @@ class="sr-only">Distances If you are not worried about large hits to a backend database for generating the data or a ridiculously huge HTTP payload, I would suggest considering the use of a table with a fixed table header. It has a - way less complicated UI, and if marked up correctly, is accessible via screen readers and keyboard. + way less complicated UI, and if marked up correctly, is still accessible via screen readers and keyboard.

      (Note: Data from this table was taken from World Population Review).

      diff --git a/content/body/video-content.php b/content/body/video-content.php index d517d9bd..23423717 100644 --- a/content/body/video-content.php +++ b/content/body/video-content.php @@ -36,10 +36,10 @@
      Transcripts:
      -
      A transcript is used by users who can neither hear audio nor see video, like deaf/blind users. It is like the - script the actors in the video used to know what is going on in the story that they are about to act in. A transcript - also should include descriptions of audio information in the video (like laughing) and visual information (such as - the creak of a door opening). +
      A transcript is used by users who can neither hear audio nor see video, like deaf or blind users. It is like the + script, the actors in the video used to know what was going on in the story that they were about to act in. A transcript + also should include descriptions of audio information in the video (like laughing) and visual information (such as + the creak of a door opening).
    @@ -50,7 +50,7 @@

    What makes a video accessible is widely misunderstood. Many web professionals know about closed captions. - What many don't know is that they need audio descriptions to be WCAG AA compliant.

    + What many don't know is that they need audio descriptions in order to be WCAG AA compliant.

    @@ -62,7 +62,7 @@ -

    Should I use auto-generated captions For My Videos?

    +

    Should I Use Auto-Generated Captions For My Videos?

    Some video services like YouTube will generate captions automatically using AI that converts audio to text. While this @@ -149,7 +149,7 @@

    Now compare this with the AblePlayer video in the first example above. The AblePlayer video does have a proper transcript; it contains descriptions of what is happening visually that are not given by the spoken text in the - video). + video.

    diff --git a/templates/includes/npm.php b/templates/includes/npm.php index 720e135b..f054ab41 100644 --- a/templates/includes/npm.php +++ b/templates/includes/npm.php @@ -161,7 +161,7 @@ Install the enable-a11y NPM project.
  • - You can import the module using requirements like this: + You can import the module using require like this: var = require('enable-a11y/').default; From 84d54efdaac29e8c6aea3f0061e9039940ed4f0b Mon Sep 17 00:00:00 2001 From: Umasankar430 Date: Fri, 21 Jun 2024 12:01:03 -0400 Subject: [PATCH 135/274] Add additional terms for components --- content/body/switch.php | 12 ++++++------ templates/includes/documentation-header.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/content/body/switch.php b/content/body/switch.php index e47da879..207547a7 100644 --- a/content/body/switch.php +++ b/content/body/switch.php @@ -2,13 +2,13 @@ A switch is like a checkbox, in that is designed to be an input control that has a binary value (either checked and unchecked or on or off, depending on the screen reader). - Like tablists.php, switches do not have a native HTML5 tag, so we implement custom code using + Like tablists.php, switches/Toggles do not have a native HTML5 tag, so we implement custom code using the - switch role in JavaScript. + switch/toggle role in JavaScript.

    - Many developers will implement switches using the <input type="checkbox">, since the native HTML5 + Many developers will implement switches/toggles using the <input type="checkbox">, since the native HTML5 checkbox control is already accessible. While you could do this, I would argue this is semantically dishonest: partially sighted users who use a screen reader hear that the control is a checkbox, but there is no checkmark involved.

    @@ -23,7 +23,7 @@

    -

    A simple switch coded with ARIA.

    +

    A simple switch/toggle coded with ARIA.

    true]); ?> This code is based on information from the MDN article on Using - the switch role. The switch reports the checked state as "on" or "off" in VoiceOver - and "checked" or "unchecked" in NVDA and ChromeVox. In order to make some consistency among user agents, an aria-describedby on the switch can state the "on/off" state to all screen readers. This description is also given visually, to make it obvious what the state is for sighted users. Developers could hide this text with the sr-only class, and put "off" and "on" labels on sides of the right and left sides of the component if they wish instead. + the switch/toggle role. The switch/toggle reports the checked state as "on" or "off" in VoiceOver + and "checked" or "unchecked" in NVDA and ChromeVox. In order to make some consistency among user agents, an aria-describedby on the switch/toggle can state the "on/off" state to all screen readers. This description is also given visually, to make it obvious what the state is for sighted users. Developers could hide this text with the sr-only class, and put "off" and "on" labels on sides of the right and left sides of the component if they wish instead.

    diff --git a/templates/includes/documentation-header.php b/templates/includes/documentation-header.php index 35b13269..e1dfb0ee 100755 --- a/templates/includes/documentation-header.php +++ b/templates/includes/documentation-header.php @@ -369,7 +369,7 @@ class="enable-flyout enable-flyout__level enable-flyout__dropdown"> { "id": "flyout__link", "props": { - "label": "Switch", + "label": "Switch / Toggle", "url-slug": "switch", "alt": "" } From 9c66feab18b8b0c585a1b203ec6445c1eae93c51 Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Fri, 21 Jun 2024 12:02:17 -0400 Subject: [PATCH 136/274] fix[Issue# 49] - fix the spelling issue of screen reader, third party and labeled --- content/body/button.php | 2 +- content/body/checkbox.php | 6 +++--- content/body/combobox.php | 2 +- content/body/dialog.php | 4 ++-- content/body/dropdown.php | 2 +- content/body/exposing-style-info-to-screen-readers.php | 4 ++-- content/body/input-mask.php | 2 +- content/body/npm.php | 2 +- content/body/progress.php | 2 +- content/body/screen-reader-only-text.php | 4 ++-- content/body/skip-link.php | 2 +- content/body/table.php | 2 +- dialog-html5-transcript | 4 ++-- vtt/dialog-document__html5--desc.vtt | 2 +- vtt/dialog-document__html5.vtt | 4 ++-- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/content/body/button.php b/content/body/button.php index 1dc0f6f7..95812a81 100644 --- a/content/body/button.php +++ b/content/body/button.php @@ -17,7 +17,7 @@ difference, from a UX point of view, is that links go to another web page, while buttons cause an action on a page (or submit a form). Keep this in mind when you are coding your CTAs. We should be marking up these controls correctly - so screen-reader users know in advance what will happen when they interact with them. + so screen reader users know in advance what will happen when they interact with them.
  • diff --git a/content/body/checkbox.php b/content/body/checkbox.php index 949a5682..86de5ed9 100644 --- a/content/body/checkbox.php +++ b/content/body/checkbox.php @@ -222,9 +222,9 @@

    Note the last bit in the code above where we set the .checked property to false. This is done for progressive enhancement. While MDN reports that indeterminate is supported in all major browsers, we should still consider that not all - browser/screen-reader pairs (e.g. Firefox/NVDA, Firefox/Voiceover at the time of this writing) will announce the indeterminate - state. For this reason, we should set the checked attribute to something that makes sense for screen-reader users who - use those browser/screen-reader pairs. Setting the checkbox to unchecked does make the most sense in this "Select + browser/screen reader pairs (e.g. Firefox/NVDA, Firefox/Voiceover at the time of this writing) will announce the indeterminate + state. For this reason, we should set the checked attribute to something that makes sense for screen reader users who + use those browser/screen reader pairs. Setting the checkbox to unchecked does make the most sense in this "Select All" scenario.

    diff --git a/content/body/combobox.php b/content/body/combobox.php index 39ee62c2..d8e7887a 100644 --- a/content/body/combobox.php +++ b/content/body/combobox.php @@ -304,7 +304,7 @@ { "label": "Code label to be associated with input", "highlight": "for", - "notes": "Ensure the label is properly labelled" + "notes": "Ensure the label is properly labeled" }, { "label": "Component instructions for the component using aria-describedby", diff --git a/content/body/dialog.php b/content/body/dialog.php index 5e1ea065..c1b18130 100644 --- a/content/body/dialog.php +++ b/content/body/dialog.php @@ -93,12 +93,12 @@ { "label": "Use aria-labelledby to point to the title of the modal", "highlight": "aria-labelledby", - "notes": "If there is no visible label in the dialog, use aria-label to set a screen-reader only label that describes the purpose of the modal. This will be read by the screen reader when the modal is first opened" + "notes": "If there is no visible label in the dialog, use aria-label to set a screen reader only label that describes the purpose of the modal. This will be read by the screen reader when the modal is first opened" }, { "label": "Use aria-describedby to point to a summary description of the modal", "highlight": "aria-describedby", - "notes": "This will give screen-reader users supplementary information about this modal. In this case, the user must login in order to continue." + "notes": "This will give screen reader users supplementary information about this modal. In this case, the user must login in order to continue." }, { "label": "Use proper roles inside the modal", diff --git a/content/body/dropdown.php b/content/body/dropdown.php index 175e392c..cb9c0720 100644 --- a/content/body/dropdown.php +++ b/content/body/dropdown.php @@ -1,7 +1,7 @@ 00:00:23.549 -(A web page appears inside the OSX Safari web browser. The web page has a picture of a woman and a gnome looking at an HTML button labelled "Log in to our website") +(A web page appears inside the OSX Safari web browser. The web page has a picture of a woman and a gnome looking at an HTML button labeled "Log in to our website") 00:00:38.500 --> 00:00:40.509 (As the user uses his keyboard to navigate to the "Log in to our website" button, the VoiceOver screen reader reports what items are focused.) diff --git a/vtt/dialog-document__html5.vtt b/vtt/dialog-document__html5.vtt index 5e5f1d3c..8f99cc1b 100644 --- a/vtt/dialog-document__html5.vtt +++ b/vtt/dialog-document__html5.vtt @@ -29,7 +29,7 @@ Safari using VoiceOver, which is built into OSX. 6 00:27.146 --> 00:33.143 Right now, keyboard focus is on the link just -before the button labelled "Log in to our website", +before the button labeled "Log in to our website", 7 00:33.144 --> 00:35.395 @@ -65,7 +65,7 @@ said "close this dialog, button". 14 00:58.143 --> 01:03.394 This tells the screen reader user that focus -is on a button labelled "close this dialog". +is on a button labeled "close this dialog". 15 01:04.500 --> 01:10.528 From 266dbc9f3a3a700ab04be46fb596e0f61905248e Mon Sep 17 00:00:00 2001 From: Umasankar430 Date: Fri, 21 Jun 2024 12:05:39 -0400 Subject: [PATCH 137/274] Add additional terms for components --- templates/data/meta-info.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/data/meta-info.json b/templates/data/meta-info.json index 8f5e42ea..558baca3 100644 --- a/templates/data/meta-info.json +++ b/templates/data/meta-info.json @@ -67,7 +67,7 @@ "isNPM": "true" }, "dropdown.php": { - "title": "Accessible Dropdowns/Drawers/Expando", + "title": "Accessible Drawers", "desc": "No matter what you can them, dropdowns, drawers and expandos can be easily made accessible with just a little bit of markup.", "isNPM": "true" }, @@ -119,7 +119,7 @@ "desc": "When you want up-to-date inbound information, like chat history or error logs, to be consumed by users of assistive technology, use the log role." }, "marquee.php": { - "title": "The Marquee Role", + "title": "The Marquee Content Role", "desc": "Non essential information that updates frequently should be coded so screen reader users know it's there, but that it doesn't annoy them." }, "math.php": { @@ -156,7 +156,7 @@ "desc": "This page is a perfect example of why the First Rule of ARIA (i.e. don't use ARIA) is so relevant, especially for mobile screen reader users." }, "spinner.php": { - "title": "Accessible Numeric Fields", + "title": "Number Input Spinner", "desc": "Many developers think form fields with numeric values must always be coded as . There are exceptions." }, "status.php": { @@ -164,8 +164,8 @@ "desc": "If part of a page is updated (e.g. because of an AJAX call), users of screen readers and other assistive tech need to be informed. Enter the ARIA role \"status\"" }, "switch.php": { - "title": "The ARIA Switch Role", - "desc": "A control with no native HTML5 equivalent, switches have become popular recently in web and mobile applications, but many are inaccessible to screen reader and keyboard-only users. Here's how to code them correctly." + "title": "The ARIA Switch/Toggle Role", + "desc": "A control with no native HTML5 equivalent, switches/Toggles have become popular recently in web and mobile applications, but many are inaccessible to screen reader and keyboard-only users. Here's how to code them correctly." }, "table.php": { "title": "Accessible Tables", From cbfded0d310966c26d2b6836902bb675fdf7648b Mon Sep 17 00:00:00 2001 From: Umasankar430 Date: Fri, 21 Jun 2024 15:11:39 -0400 Subject: [PATCH 138/274] switch --- content/body/code-quality.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/body/code-quality.php b/content/body/code-quality.php index 2e376ff1..14550481 100644 --- a/content/body/code-quality.php +++ b/content/body/code-quality.php @@ -201,7 +201,7 @@ } -

    A Simple Interactive Example: Switches

    +

    A Simple Interactive Example: Switches/Toggles

    This example is used to test Enable's switch component to ensure that it is keyboard accessible and that the HTML structure includes all the necessary accessibility features (e.g. the role="switch", a valid aria-checked attribute set, a proper label, etc.). Please go through the code walkthrough below for more details. From ad74ed0fccbe949882bae3a364259d7740c166ab Mon Sep 17 00:00:00 2001 From: AKSHAY PANCHAL Date: Mon, 24 Jun 2024 11:55:29 -0400 Subject: [PATCH 139/274] fix[Issue# 49] - check for the grammar / spelling issues on FAQ, ACQ, Credits and bookmark section --- content/body/acknowledgements.php | 6 +++--- content/body/code-quality.php | 26 +++++++++++++------------- content/body/faq.php | 8 ++++---- content/body/focus-styling.php | 2 +- content/body/index.php | 10 +++++----- content/body/radiogroup.php | 2 +- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/content/body/acknowledgements.php b/content/body/acknowledgements.php index 733cead3..6cd24cdb 100644 --- a/content/body/acknowledgements.php +++ b/content/body/acknowledgements.php @@ -1,13 +1,13 @@

    Originally, Enable started out as a small personal website to help me show other developers how accessible code is structured. - Some of the solutions are my own, some I have borrowed from others (because why reinvent the wheel, especially when - you have learned from the best already?) + Some of the solutions are my own, and some I have borrowed from others (because why reinvent the wheel, especially when + you have already learned from the best?)

    What follows are not just acknowledgements to existing accessible code examples used in Enable, but also to other code I have - built on that I have accessibility features to. + built on that I have accessibility features for.

    Code Used By Enable

    diff --git a/content/body/code-quality.php b/content/body/code-quality.php index 2e376ff1..7ee4d3d1 100644 --- a/content/body/code-quality.php +++ b/content/body/code-quality.php @@ -35,7 +35,7 @@

      -
    1. Generating all the HTML of all the PHP pages on the site.
    2. +
    3. Generating all the HTML for all the PHP pages on the site.
    4. Separating pages that initialize instantly (let's call this group "A") with ones that need a bit more processing time due to JavaScript use (let's call this group "B").
    5. Parsing the group A pages with v.Nu, using one call to v.Nu (since each call to the v.Nu command line tool would @@ -44,9 +44,9 @@

    - Note that v.Nu requires Java in order to run. If this is a concern on your project, + Note that v.Nu requires Java in order to run. If this is a concern for your project, you may want to - try using this Node based HTML validator instead (I have not used this + try using this Node based HTML validator instead (I have not used it yet, so your mileage may vary).

    @@ -74,9 +74,9 @@

    - Both tools go through all the Enable pages to check to see if colour contrast is right, alt attributes are set, ARIA + Both tools go through all the Enable pages to check to see if color contrast is right, alt attributes are set, ARIA is marked up correctly, and so on. As axe-core explicitly states after execution, automated testing can only catch - from 20% to 50% of accessibility issues. Is there any way to improve upon that? + 20% to 50% of accessibility issues. Is there any way to improve upon that?

    @@ -87,7 +87,7 @@ accessibility feature you have just implemented stays within the project. For example, if you create a custom accessible listbox dropdown, you want to make sure that when - keyboard users tab into the component and use the arrow keys, then they can change the selected listbox value. + keyboard users tab into the component and use the arrow keys, they can change the selected listbox value.

    @@ -145,12 +145,12 @@

    A Simple Example: Having Screen Readers Read Strikethrough Text

    -

    Let's look at a simple example that just involves just steps 1 through 3. If you look at the page for Exposing Style Information To Screen Readers, we use +

    Let's look at a simple example that just involves steps 1 through 3. If you look at the page for Exposing Style Information to Screen Readers, we use visually-hidden content inside of mark tags. We want to ensure that a new developer that contributes code to Enable never removes this screen reader only text by accident, so we create a jest test file, exposing-style-info-to-screen-readers.test.js, to ensure we can test that this CSS is in these - example. Let's walk through this file to show how it works. + examples. Let's walk through this file to show how it works.