From 19a1381ad3baf0e7ad462de8c8c9d9f273c5e620 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 22 Apr 2026 12:33:09 -0400 Subject: [PATCH 1/4] Map priority to live region politeness Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- arianotify-polyfill.js | 45 +++++++++--- .../arianotify-polyfill.test.js | 70 ++++++++++++++++--- 2 files changed, 97 insertions(+), 18 deletions(-) diff --git a/arianotify-polyfill.js b/arianotify-polyfill.js index b3b079e..aeb7fd7 100644 --- a/arianotify-polyfill.js +++ b/arianotify-polyfill.js @@ -21,7 +21,10 @@ if ( const passkey = Symbol(); /** @type {string} */ - const liveRegionCustomElementName = `live-region-${uniqueId}`; + const politeLiveRegionCustomElementName = `polite-live-region-${uniqueId}`; + + /** @type {string} */ + const assertiveLiveRegionCustomElementName = `assertive-live-region-${uniqueId}`; /** * @param {number} ms @@ -79,16 +82,19 @@ if ( } // Get root element - let root = /** @type {Element} */ ( + let root = /** @type {Element | ShadowRoot | Document} */ ( this.element.closest("dialog") || this.element.closest("[role='dialog']") || this.element.getRootNode() ); if (!root || root instanceof Document) root = document.body; - // Get 'live-region', if it already exists - /** @type {LiveRegionCustomElement | null} */ - let liveRegion = root.querySelector(liveRegionCustomElementName); + const liveRegionCustomElementName = + this.priority === "high" + ? assertiveLiveRegionCustomElementName + : politeLiveRegionCustomElementName; + let liveRegion = /** @type {LiveRegionCustomElement | null} */ ( + root.querySelector(liveRegionCustomElementName) + ); - // Create (or recreate) 'live-region', if it doesn’t exist if (!liveRegion) { liveRegion = /** @type {LiveRegionCustomElement} */ ( document.createElement(liveRegionCustomElementName) @@ -145,7 +151,6 @@ if ( #shadowRoot = this.attachShadow({ mode: "closed" }); connectedCallback() { - this.ariaLive = "polite"; this.ariaAtomic = "true"; this.style.marginLeft = "-1px"; this.style.marginTop = "-1px"; @@ -171,7 +176,29 @@ if ( this.#shadowRoot.textContent = message; } } - customElements.define(liveRegionCustomElementName, LiveRegionCustomElement); + + class PoliteLiveRegionCustomElement extends LiveRegionCustomElement { + connectedCallback() { + this.ariaLive = "polite"; + super.connectedCallback(); + } + } + + class AssertiveLiveRegionCustomElement extends LiveRegionCustomElement { + connectedCallback() { + this.ariaLive = "assertive"; + super.connectedCallback(); + } + } + + customElements.define( + politeLiveRegionCustomElementName, + PoliteLiveRegionCustomElement + ); + customElements.define( + assertiveLiveRegionCustomElementName, + AssertiveLiveRegionCustomElement + ); if (!("ariaNotify" in Element.prototype)) { /** @@ -200,4 +227,4 @@ if ( queue.enqueue(new Message({ element: this.documentElement, message, priority })); }; } -} \ No newline at end of file +} diff --git a/tests/web-test-runner/arianotify-polyfill.test.js b/tests/web-test-runner/arianotify-polyfill.test.js index 649f7c1..b6bb055 100644 --- a/tests/web-test-runner/arianotify-polyfill.test.js +++ b/tests/web-test-runner/arianotify-polyfill.test.js @@ -1,16 +1,68 @@ import { expect } from "@esm-bundle/chai"; +function spyOn(object, methodName) { + const calls = []; + const method = object[methodName]; + + object[methodName] = function (...args) { + calls.push(args); + return method.call(this, ...args); + }; + + return calls; +} + export async function tests() { describe("ariaNotify polyfill", () => { - it(" placement", () => { - let count = 0; - for (const container of document.querySelectorAll("[data-should-contain-live-region]")) { - container.ariaNotify("Hello, world!"); - const liveRegion = Array.from(container.childNodes).find((node) => node.nodeType === Node.ELEMENT_NODE && node.tagName.match(/^live-region/i)); - expect(liveRegion).to.not.be.undefined; - count++; + let container; + + beforeEach(() => { + container = document.querySelector("[data-should-contain-live-region]"); + if (!container) { + throw new Error("Expected a live-region test container"); } - expect(count).to.be.above(0); + + for (const liveRegion of Array.from(container.children).filter((node) => + node.tagName.match(/-live-region/i) + )) { + liveRegion.remove(); + } + }); + + it("routes polite messages to the polite live region", async () => { + container.ariaNotify("Hello, world!"); + const liveRegions = Array.from(container.children).filter((node) => + node.tagName.match(/-live-region/i) + ); + expect(liveRegions).to.have.length(1); + + const liveRegion = liveRegions[0]; + expect(liveRegion.tagName.match(/^polite-live-region/i)).to.not.equal(null); + expect(liveRegion.ariaLive).to.equal("polite"); + + const calls = spyOn(liveRegion, "handleMessage"); + + await new Promise((resolve) => setTimeout(resolve, 300)); + expect(calls).to.have.length(1); + expect(calls[0][1]).to.equal("Hello, world!"); + }); + + it("routes assertive messages to the assertive live region", async () => { + container.ariaNotify("Emergency!", { priority: "high" }); + const liveRegions = Array.from(container.children).filter((node) => + node.tagName.match(/-live-region/i) + ); + expect(liveRegions).to.have.length(1); + + const liveRegion = liveRegions[0]; + expect(liveRegion.tagName.match(/^assertive-live-region/i)).to.not.equal(null); + expect(liveRegion.ariaLive).to.equal("assertive"); + + const calls = spyOn(liveRegion, "handleMessage"); + + await new Promise((resolve) => setTimeout(resolve, 300)); + expect(calls).to.have.length(1); + expect(calls[0][1]).to.equal("Emergency!"); }); }); -} \ No newline at end of file +} From c1fc38cf898d26b141a77f13345b89f2cf277c83 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 22 Apr 2026 12:45:56 -0400 Subject: [PATCH 2/4] Use calmer assertive test message Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/web-test-runner/arianotify-polyfill.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/web-test-runner/arianotify-polyfill.test.js b/tests/web-test-runner/arianotify-polyfill.test.js index b6bb055..a476ed6 100644 --- a/tests/web-test-runner/arianotify-polyfill.test.js +++ b/tests/web-test-runner/arianotify-polyfill.test.js @@ -48,7 +48,7 @@ export async function tests() { }); it("routes assertive messages to the assertive live region", async () => { - container.ariaNotify("Emergency!", { priority: "high" }); + container.ariaNotify("Update available", { priority: "high" }); const liveRegions = Array.from(container.children).filter((node) => node.tagName.match(/-live-region/i) ); @@ -62,7 +62,7 @@ export async function tests() { await new Promise((resolve) => setTimeout(resolve, 300)); expect(calls).to.have.length(1); - expect(calls[0][1]).to.equal("Emergency!"); + expect(calls[0][1]).to.equal("Update available"); }); }); } From 59b7c4f8368725b949833ba8e5ace32672663580 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 22 Apr 2026 12:49:26 -0400 Subject: [PATCH 3/4] nit: Adjust wording used in test messages Co-authored-by: Clay Miller --- tests/web-test-runner/arianotify-polyfill.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/web-test-runner/arianotify-polyfill.test.js b/tests/web-test-runner/arianotify-polyfill.test.js index a476ed6..6a4ff4d 100644 --- a/tests/web-test-runner/arianotify-polyfill.test.js +++ b/tests/web-test-runner/arianotify-polyfill.test.js @@ -30,7 +30,7 @@ export async function tests() { }); it("routes polite messages to the polite live region", async () => { - container.ariaNotify("Hello, world!"); + container.ariaNotify("Normal-priority message"); const liveRegions = Array.from(container.children).filter((node) => node.tagName.match(/-live-region/i) ); @@ -44,11 +44,11 @@ export async function tests() { await new Promise((resolve) => setTimeout(resolve, 300)); expect(calls).to.have.length(1); - expect(calls[0][1]).to.equal("Hello, world!"); + expect(calls[0][1]).to.equal("Normal-priority message"); }); it("routes assertive messages to the assertive live region", async () => { - container.ariaNotify("Update available", { priority: "high" }); + container.ariaNotify("High-priority message", { priority: "high" }); const liveRegions = Array.from(container.children).filter((node) => node.tagName.match(/-live-region/i) ); @@ -62,7 +62,7 @@ export async function tests() { await new Promise((resolve) => setTimeout(resolve, 300)); expect(calls).to.have.length(1); - expect(calls[0][1]).to.equal("Update available"); + expect(calls[0][1]).to.equal("High-priority message"); }); }); } From f82b525cff034300bec8c7854e7a618182c9c597 Mon Sep 17 00:00:00 2001 From: Clay Miller Date: Wed, 22 Apr 2026 12:51:22 -0400 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20Extend=20timeouts=20(to=20double=20t?= =?UTF-8?q?he=20polyfill=E2=80=99s=20own=20250ms=20wait)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clay Miller --- tests/web-test-runner/arianotify-polyfill.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/web-test-runner/arianotify-polyfill.test.js b/tests/web-test-runner/arianotify-polyfill.test.js index 6a4ff4d..69a81d7 100644 --- a/tests/web-test-runner/arianotify-polyfill.test.js +++ b/tests/web-test-runner/arianotify-polyfill.test.js @@ -42,7 +42,7 @@ export async function tests() { const calls = spyOn(liveRegion, "handleMessage"); - await new Promise((resolve) => setTimeout(resolve, 300)); + await new Promise((resolve) => setTimeout(resolve, 500)); expect(calls).to.have.length(1); expect(calls[0][1]).to.equal("Normal-priority message"); }); @@ -60,7 +60,7 @@ export async function tests() { const calls = spyOn(liveRegion, "handleMessage"); - await new Promise((resolve) => setTimeout(resolve, 300)); + await new Promise((resolve) => setTimeout(resolve, 500)); expect(calls).to.have.length(1); expect(calls[0][1]).to.equal("High-priority message"); });