@primer/live-region-element
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -18,3 +18,3 @@ import './define'; | ||
*/ | ||
export declare function announce(message: string, options?: GlobalAnnounceOptions): void; | ||
export declare function announce(message: string, options?: GlobalAnnounceOptions): () => void; | ||
/** | ||
@@ -24,3 +24,3 @@ * Announce a message using the text content of an element using a live region | ||
*/ | ||
export declare function announceFromElement(element: HTMLElement, options?: GlobalAnnounceOptions): void; | ||
export declare function announceFromElement(element: HTMLElement, options?: GlobalAnnounceOptions): () => void; | ||
export { LiveRegionElement, templateContent }; |
@@ -0,4 +1,198 @@ | ||
const Ordering = { | ||
Less: "less", | ||
Equal: "equal", | ||
Greater: "greater" | ||
}; | ||
var __accessCheck$1 = (obj, member, msg) => { | ||
if (!member.has(obj)) | ||
throw TypeError("Cannot " + msg); | ||
}; | ||
var __privateGet$1 = (obj, member, getter) => { | ||
__accessCheck$1(obj, member, "read from private field"); | ||
return getter ? getter.call(obj) : member.get(obj); | ||
}; | ||
var __privateAdd$1 = (obj, member, value) => { | ||
if (member.has(obj)) | ||
throw TypeError("Cannot add the same private member more than once"); | ||
member instanceof WeakSet ? member.add(obj) : member.set(obj, value); | ||
}; | ||
var __privateSet$1 = (obj, member, value, setter) => { | ||
__accessCheck$1(obj, member, "write to private field"); | ||
setter ? setter.call(obj, value) : member.set(obj, value); | ||
return value; | ||
}; | ||
var __privateMethod = (obj, member, method) => { | ||
__accessCheck$1(obj, member, "access private method"); | ||
return method; | ||
}; | ||
var _compareFn, _heap, _heapifyDown, heapifyDown_fn, _heapifyUp, heapifyUp_fn; | ||
class MinHeap { | ||
constructor({ compareFn }) { | ||
__privateAdd$1(this, _heapifyDown); | ||
__privateAdd$1(this, _heapifyUp); | ||
__privateAdd$1(this, _compareFn, void 0); | ||
__privateAdd$1(this, _heap, void 0); | ||
__privateSet$1(this, _compareFn, compareFn); | ||
__privateSet$1(this, _heap, []); | ||
} | ||
insert(value) { | ||
__privateGet$1(this, _heap).push(value); | ||
__privateMethod(this, _heapifyUp, heapifyUp_fn).call(this); | ||
} | ||
pop() { | ||
const item = __privateGet$1(this, _heap)[0]; | ||
if (__privateGet$1(this, _heap)[__privateGet$1(this, _heap).length - 1]) { | ||
__privateGet$1(this, _heap)[0] = __privateGet$1(this, _heap)[__privateGet$1(this, _heap).length - 1]; | ||
__privateGet$1(this, _heap).pop(); | ||
} | ||
__privateMethod(this, _heapifyDown, heapifyDown_fn).call(this); | ||
return item; | ||
} | ||
peek() { | ||
return __privateGet$1(this, _heap)[0]; | ||
} | ||
delete(value) { | ||
const index = __privateGet$1(this, _heap).indexOf(value); | ||
if (index === -1) { | ||
return; | ||
} | ||
swap(__privateGet$1(this, _heap), index, __privateGet$1(this, _heap).length - 1); | ||
__privateGet$1(this, _heap).pop(); | ||
__privateMethod(this, _heapifyDown, heapifyDown_fn).call(this); | ||
} | ||
clear() { | ||
__privateSet$1(this, _heap, []); | ||
} | ||
get size() { | ||
return __privateGet$1(this, _heap).length; | ||
} | ||
} | ||
_compareFn = new WeakMap(); | ||
_heap = new WeakMap(); | ||
_heapifyDown = new WeakSet(); | ||
heapifyDown_fn = function() { | ||
let index = 0; | ||
while (hasLeftChild(index, __privateGet$1(this, _heap).length)) { | ||
let smallerChildIndex = getLeftChildIndex(index); | ||
if (hasRightChild(index, __privateGet$1(this, _heap).length) && __privateGet$1(this, _compareFn).call(this, rightChild(__privateGet$1(this, _heap), index), leftChild(__privateGet$1(this, _heap), index)) === Ordering.Less) { | ||
smallerChildIndex = getRightChildIndex(index); | ||
} | ||
if (__privateGet$1(this, _compareFn).call(this, __privateGet$1(this, _heap)[index], __privateGet$1(this, _heap)[smallerChildIndex]) === Ordering.Less) { | ||
break; | ||
} else { | ||
swap(__privateGet$1(this, _heap), index, smallerChildIndex); | ||
} | ||
index = smallerChildIndex; | ||
} | ||
}; | ||
_heapifyUp = new WeakSet(); | ||
heapifyUp_fn = function() { | ||
let index = __privateGet$1(this, _heap).length - 1; | ||
while (hasParent(index) && __privateGet$1(this, _compareFn).call(this, __privateGet$1(this, _heap)[index], parent(__privateGet$1(this, _heap), index)) === Ordering.Less) { | ||
swap(__privateGet$1(this, _heap), index, getParentIndex(index)); | ||
index = getParentIndex(index); | ||
} | ||
}; | ||
function getLeftChildIndex(index) { | ||
return 2 * index + 1; | ||
} | ||
function getRightChildIndex(index) { | ||
return 2 * index + 2; | ||
} | ||
function getParentIndex(index) { | ||
return Math.floor((index - 1) / 2); | ||
} | ||
function hasLeftChild(index, size) { | ||
return getLeftChildIndex(index) < size; | ||
} | ||
function hasRightChild(index, size) { | ||
return getRightChildIndex(index) < size; | ||
} | ||
function hasParent(index) { | ||
return index > 0; | ||
} | ||
function leftChild(heap, index) { | ||
return heap[getLeftChildIndex(index)]; | ||
} | ||
function rightChild(heap, index) { | ||
return heap[getRightChildIndex(index)]; | ||
} | ||
function parent(heap, index) { | ||
return heap[getParentIndex(index)]; | ||
} | ||
function swap(heap, a, b) { | ||
const tmp = heap[a]; | ||
heap[a] = heap[b]; | ||
heap[b] = tmp; | ||
} | ||
function throttle(fn, wait) { | ||
const queue = []; | ||
let timeoutId = null; | ||
function processQueue() { | ||
if (timeoutId !== null) { | ||
return; | ||
} | ||
if (queue.length === 0) { | ||
return; | ||
} | ||
const args = queue.shift(); | ||
if (args === void 0) { | ||
return; | ||
} | ||
fn(...args); | ||
timeoutId = window.setTimeout(() => { | ||
timeoutId = null; | ||
processQueue(); | ||
}, wait); | ||
} | ||
function throttled(...args) { | ||
queue.push(args); | ||
if (timeoutId === null) { | ||
processQueue(); | ||
} | ||
} | ||
throttled.cancel = () => { | ||
if (timeoutId !== null) { | ||
clearTimeout(timeoutId); | ||
timeoutId = null; | ||
} | ||
queue.length = 0; | ||
}; | ||
return throttled; | ||
} | ||
var __defProp = Object.defineProperty; | ||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
var __publicField = (obj, key, value) => { | ||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
return value; | ||
}; | ||
var __accessCheck = (obj, member, msg) => { | ||
if (!member.has(obj)) | ||
throw TypeError("Cannot " + msg); | ||
}; | ||
var __privateGet = (obj, member, getter) => { | ||
__accessCheck(obj, member, "read from private field"); | ||
return getter ? getter.call(obj) : member.get(obj); | ||
}; | ||
var __privateAdd = (obj, member, value) => { | ||
if (member.has(obj)) | ||
throw TypeError("Cannot add the same private member more than once"); | ||
member instanceof WeakSet ? member.add(obj) : member.set(obj, value); | ||
}; | ||
var __privateSet = (obj, member, value, setter) => { | ||
__accessCheck(obj, member, "write to private field"); | ||
setter ? setter.call(obj, value) : member.set(obj, value); | ||
return value; | ||
}; | ||
var _queue, _timeoutId; | ||
const DEFAULT_THROTTLE_DELAY_MS = 500; | ||
class LiveRegionElement extends HTMLElement { | ||
constructor() { | ||
constructor({ waitMs } = {}) { | ||
super(); | ||
__privateAdd(this, _queue, void 0); | ||
__privateAdd(this, _timeoutId, void 0); | ||
__publicField(this, "updateContainerWithMessage"); | ||
if (!this.shadowRoot) { | ||
@@ -9,2 +203,18 @@ const template2 = getTemplate(); | ||
} | ||
__privateSet(this, _timeoutId, null); | ||
__privateSet(this, _queue, new MinHeap({ | ||
compareFn: compareMessages | ||
})); | ||
this.updateContainerWithMessage = throttle((message) => { | ||
const { contents, politeness } = message; | ||
const container = this.shadowRoot?.getElementById(politeness); | ||
if (!container) { | ||
throw new Error(`Unable to find container for message. Expected a container with id="${politeness}"`); | ||
} | ||
if (container.textContent === contents) { | ||
container.textContent = `${contents}\xA0`; | ||
} else { | ||
container.textContent = contents; | ||
} | ||
}, waitMs ?? DEFAULT_THROTTLE_DELAY_MS); | ||
} | ||
@@ -14,21 +224,15 @@ /** | ||
* level. | ||
* | ||
* Note: a politeness level of `assertive` should only be used for | ||
* time-sensistive or critical notifications that absolutely require the | ||
* user's immediate attention | ||
* | ||
* @see https://www.w3.org/TR/wai-aria/#aria-live | ||
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions | ||
*/ | ||
announce(message, options = {}) { | ||
const { politeness = "polite" } = options; | ||
const container = this.shadowRoot?.getElementById(politeness); | ||
if (!container) { | ||
throw new Error("Unable to find container for message"); | ||
} | ||
if (container.textContent === message) { | ||
container.textContent = `${message}\xA0`; | ||
} else { | ||
container.textContent = message; | ||
} | ||
const { delayMs, politeness = "polite" } = options; | ||
const item = { | ||
politeness, | ||
contents: message, | ||
scheduled: delayMs !== void 0 ? Date.now() + delayMs : "immediate" | ||
}; | ||
__privateGet(this, _queue).insert(item); | ||
this.performWork(); | ||
return () => { | ||
__privateGet(this, _queue).delete(item); | ||
}; | ||
} | ||
@@ -42,5 +246,38 @@ /** | ||
if (textContent !== "") { | ||
this.announce(textContent, options); | ||
return this.announce(textContent, options); | ||
} | ||
return noop; | ||
} | ||
performWork() { | ||
let message = __privateGet(this, _queue).peek(); | ||
if (!message) { | ||
return; | ||
} | ||
if (__privateGet(this, _timeoutId) !== null) { | ||
clearTimeout(__privateGet(this, _timeoutId)); | ||
__privateSet(this, _timeoutId, null); | ||
} | ||
if (message.scheduled === "immediate") { | ||
message = __privateGet(this, _queue).pop(); | ||
if (message) { | ||
this.updateContainerWithMessage(message); | ||
} | ||
this.performWork(); | ||
return; | ||
} | ||
const now = Date.now(); | ||
if (message.scheduled <= now) { | ||
message = __privateGet(this, _queue).pop(); | ||
if (message) { | ||
this.updateContainerWithMessage(message); | ||
} | ||
this.performWork(); | ||
return; | ||
} | ||
const timeout = message.scheduled > now ? message.scheduled - now : 0; | ||
__privateSet(this, _timeoutId, window.setTimeout(() => { | ||
__privateSet(this, _timeoutId, null); | ||
this.performWork(); | ||
}, timeout)); | ||
} | ||
getMessage(politeness = "polite") { | ||
@@ -53,3 +290,16 @@ const container = this.shadowRoot?.getElementById(politeness); | ||
} | ||
/** | ||
* Stop any pending messages from being announced by the live region | ||
*/ | ||
clear() { | ||
if (__privateGet(this, _timeoutId) !== null) { | ||
clearTimeout(__privateGet(this, _timeoutId)); | ||
__privateSet(this, _timeoutId, null); | ||
} | ||
this.updateContainerWithMessage.cancel(); | ||
__privateGet(this, _queue).clear(); | ||
} | ||
} | ||
_queue = new WeakMap(); | ||
_timeoutId = new WeakMap(); | ||
function getTextContent(element) { | ||
@@ -89,2 +339,19 @@ let value = ""; | ||
} | ||
function compareMessages(a, b) { | ||
if (a.scheduled === b.scheduled) { | ||
return Ordering.Equal; | ||
} | ||
if (a.scheduled === "immediate" && b.scheduled !== "immediate") { | ||
return Ordering.Less; | ||
} | ||
if (a.scheduled !== "immediate" && b.scheduled === "immediate") { | ||
return Ordering.Greater; | ||
} | ||
if (a.scheduled < b.scheduled) { | ||
return Ordering.Less; | ||
} | ||
return Ordering.Greater; | ||
} | ||
function noop() { | ||
} | ||
@@ -91,0 +358,0 @@ if (!customElements.get("live-region")) { |
@@ -0,9 +1,10 @@ | ||
import { type Throttle } from './throttle'; | ||
type Politeness = 'polite' | 'assertive'; | ||
type AnnounceOptions = { | ||
politeness?: 'polite' | 'assertive'; | ||
}; | ||
declare class LiveRegionElement extends HTMLElement { | ||
constructor(); | ||
/** | ||
* Announce a message using a live region with a corresponding politeness | ||
* level. | ||
* A delay in milliseconds to wait before announcing a message. | ||
*/ | ||
delayMs?: number; | ||
/** | ||
* The politeness level for a message. | ||
* | ||
@@ -17,9 +18,35 @@ * Note: a politeness level of `assertive` should only be used for | ||
*/ | ||
announce(message: string, options?: AnnounceOptions): void; | ||
politeness?: Politeness; | ||
}; | ||
type Message = { | ||
contents: string; | ||
politeness: Politeness; | ||
scheduled: number | 'immediate'; | ||
}; | ||
/** | ||
* A function to cancel a scheduled message. | ||
*/ | ||
type Cancel = () => void; | ||
declare class LiveRegionElement extends HTMLElement { | ||
#private; | ||
updateContainerWithMessage: Throttle<(message: Message) => void>; | ||
constructor({ waitMs }?: { | ||
waitMs?: number; | ||
}); | ||
/** | ||
* Announce a message using a live region with a corresponding politeness | ||
* level. | ||
*/ | ||
announce(message: string, options?: AnnounceOptions): Cancel; | ||
/** | ||
* Announce a message using the text content of an element with a | ||
* corresponding politeness level | ||
*/ | ||
announceFromElement(element: HTMLElement, options?: AnnounceOptions): void; | ||
announceFromElement(element: HTMLElement, options?: AnnounceOptions): Cancel; | ||
performWork(): void; | ||
getMessage(politeness?: AnnounceOptions['politeness']): string | null; | ||
/** | ||
* Stop any pending messages from being announced by the live region | ||
*/ | ||
clear(): void; | ||
} | ||
@@ -26,0 +53,0 @@ declare const templateContent = "\n<style>\n:host {\n clip-path: inset(50%);\n height: 1px;\n overflow: hidden;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n</style>\n<div id=\"polite\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n<div id=\"assertive\" aria-live=\"assertive\" aria-atomic=\"true\"></div>\n"; |
import { HTMLElement, customElements } from '@lit-labs/ssr-dom-shim'; | ||
const Ordering = { | ||
Less: "less", | ||
Equal: "equal", | ||
Greater: "greater" | ||
}; | ||
var __accessCheck$1 = (obj, member, msg) => { | ||
if (!member.has(obj)) | ||
throw TypeError("Cannot " + msg); | ||
}; | ||
var __privateGet$1 = (obj, member, getter) => { | ||
__accessCheck$1(obj, member, "read from private field"); | ||
return getter ? getter.call(obj) : member.get(obj); | ||
}; | ||
var __privateAdd$1 = (obj, member, value) => { | ||
if (member.has(obj)) | ||
throw TypeError("Cannot add the same private member more than once"); | ||
member instanceof WeakSet ? member.add(obj) : member.set(obj, value); | ||
}; | ||
var __privateSet$1 = (obj, member, value, setter) => { | ||
__accessCheck$1(obj, member, "write to private field"); | ||
setter ? setter.call(obj, value) : member.set(obj, value); | ||
return value; | ||
}; | ||
var __privateMethod = (obj, member, method) => { | ||
__accessCheck$1(obj, member, "access private method"); | ||
return method; | ||
}; | ||
var _compareFn, _heap, _heapifyDown, heapifyDown_fn, _heapifyUp, heapifyUp_fn; | ||
class MinHeap { | ||
constructor({ compareFn }) { | ||
__privateAdd$1(this, _heapifyDown); | ||
__privateAdd$1(this, _heapifyUp); | ||
__privateAdd$1(this, _compareFn, void 0); | ||
__privateAdd$1(this, _heap, void 0); | ||
__privateSet$1(this, _compareFn, compareFn); | ||
__privateSet$1(this, _heap, []); | ||
} | ||
insert(value) { | ||
__privateGet$1(this, _heap).push(value); | ||
__privateMethod(this, _heapifyUp, heapifyUp_fn).call(this); | ||
} | ||
pop() { | ||
const item = __privateGet$1(this, _heap)[0]; | ||
if (__privateGet$1(this, _heap)[__privateGet$1(this, _heap).length - 1]) { | ||
__privateGet$1(this, _heap)[0] = __privateGet$1(this, _heap)[__privateGet$1(this, _heap).length - 1]; | ||
__privateGet$1(this, _heap).pop(); | ||
} | ||
__privateMethod(this, _heapifyDown, heapifyDown_fn).call(this); | ||
return item; | ||
} | ||
peek() { | ||
return __privateGet$1(this, _heap)[0]; | ||
} | ||
delete(value) { | ||
const index = __privateGet$1(this, _heap).indexOf(value); | ||
if (index === -1) { | ||
return; | ||
} | ||
swap(__privateGet$1(this, _heap), index, __privateGet$1(this, _heap).length - 1); | ||
__privateGet$1(this, _heap).pop(); | ||
__privateMethod(this, _heapifyDown, heapifyDown_fn).call(this); | ||
} | ||
clear() { | ||
__privateSet$1(this, _heap, []); | ||
} | ||
get size() { | ||
return __privateGet$1(this, _heap).length; | ||
} | ||
} | ||
_compareFn = new WeakMap(); | ||
_heap = new WeakMap(); | ||
_heapifyDown = new WeakSet(); | ||
heapifyDown_fn = function() { | ||
let index = 0; | ||
while (hasLeftChild(index, __privateGet$1(this, _heap).length)) { | ||
let smallerChildIndex = getLeftChildIndex(index); | ||
if (hasRightChild(index, __privateGet$1(this, _heap).length) && __privateGet$1(this, _compareFn).call(this, rightChild(__privateGet$1(this, _heap), index), leftChild(__privateGet$1(this, _heap), index)) === Ordering.Less) { | ||
smallerChildIndex = getRightChildIndex(index); | ||
} | ||
if (__privateGet$1(this, _compareFn).call(this, __privateGet$1(this, _heap)[index], __privateGet$1(this, _heap)[smallerChildIndex]) === Ordering.Less) { | ||
break; | ||
} else { | ||
swap(__privateGet$1(this, _heap), index, smallerChildIndex); | ||
} | ||
index = smallerChildIndex; | ||
} | ||
}; | ||
_heapifyUp = new WeakSet(); | ||
heapifyUp_fn = function() { | ||
let index = __privateGet$1(this, _heap).length - 1; | ||
while (hasParent(index) && __privateGet$1(this, _compareFn).call(this, __privateGet$1(this, _heap)[index], parent(__privateGet$1(this, _heap), index)) === Ordering.Less) { | ||
swap(__privateGet$1(this, _heap), index, getParentIndex(index)); | ||
index = getParentIndex(index); | ||
} | ||
}; | ||
function getLeftChildIndex(index) { | ||
return 2 * index + 1; | ||
} | ||
function getRightChildIndex(index) { | ||
return 2 * index + 2; | ||
} | ||
function getParentIndex(index) { | ||
return Math.floor((index - 1) / 2); | ||
} | ||
function hasLeftChild(index, size) { | ||
return getLeftChildIndex(index) < size; | ||
} | ||
function hasRightChild(index, size) { | ||
return getRightChildIndex(index) < size; | ||
} | ||
function hasParent(index) { | ||
return index > 0; | ||
} | ||
function leftChild(heap, index) { | ||
return heap[getLeftChildIndex(index)]; | ||
} | ||
function rightChild(heap, index) { | ||
return heap[getRightChildIndex(index)]; | ||
} | ||
function parent(heap, index) { | ||
return heap[getParentIndex(index)]; | ||
} | ||
function swap(heap, a, b) { | ||
const tmp = heap[a]; | ||
heap[a] = heap[b]; | ||
heap[b] = tmp; | ||
} | ||
function throttle(fn, wait) { | ||
const queue = []; | ||
let timeoutId = null; | ||
function processQueue() { | ||
if (timeoutId !== null) { | ||
return; | ||
} | ||
if (queue.length === 0) { | ||
return; | ||
} | ||
const args = queue.shift(); | ||
if (args === void 0) { | ||
return; | ||
} | ||
fn(...args); | ||
timeoutId = window.setTimeout(() => { | ||
timeoutId = null; | ||
processQueue(); | ||
}, wait); | ||
} | ||
function throttled(...args) { | ||
queue.push(args); | ||
if (timeoutId === null) { | ||
processQueue(); | ||
} | ||
} | ||
throttled.cancel = () => { | ||
if (timeoutId !== null) { | ||
clearTimeout(timeoutId); | ||
timeoutId = null; | ||
} | ||
queue.length = 0; | ||
}; | ||
return throttled; | ||
} | ||
var __defProp = Object.defineProperty; | ||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
var __publicField = (obj, key, value) => { | ||
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
return value; | ||
}; | ||
var __accessCheck = (obj, member, msg) => { | ||
if (!member.has(obj)) | ||
throw TypeError("Cannot " + msg); | ||
}; | ||
var __privateGet = (obj, member, getter) => { | ||
__accessCheck(obj, member, "read from private field"); | ||
return getter ? getter.call(obj) : member.get(obj); | ||
}; | ||
var __privateAdd = (obj, member, value) => { | ||
if (member.has(obj)) | ||
throw TypeError("Cannot add the same private member more than once"); | ||
member instanceof WeakSet ? member.add(obj) : member.set(obj, value); | ||
}; | ||
var __privateSet = (obj, member, value, setter) => { | ||
__accessCheck(obj, member, "write to private field"); | ||
setter ? setter.call(obj, value) : member.set(obj, value); | ||
return value; | ||
}; | ||
var _queue, _timeoutId; | ||
const DEFAULT_THROTTLE_DELAY_MS = 500; | ||
class LiveRegionElement extends (globalThis.HTMLElement ?? HTMLElement) { | ||
constructor() { | ||
constructor({ waitMs } = {}) { | ||
super(); | ||
__privateAdd(this, _queue, void 0); | ||
__privateAdd(this, _timeoutId, void 0); | ||
__publicField(this, "updateContainerWithMessage"); | ||
if (!this.shadowRoot) { | ||
@@ -11,2 +205,18 @@ const template2 = getTemplate(); | ||
} | ||
__privateSet(this, _timeoutId, null); | ||
__privateSet(this, _queue, new MinHeap({ | ||
compareFn: compareMessages | ||
})); | ||
this.updateContainerWithMessage = throttle((message) => { | ||
const { contents, politeness } = message; | ||
const container = this.shadowRoot?.getElementById(politeness); | ||
if (!container) { | ||
throw new Error(`Unable to find container for message. Expected a container with id="${politeness}"`); | ||
} | ||
if (container.textContent === contents) { | ||
container.textContent = `${contents}\xA0`; | ||
} else { | ||
container.textContent = contents; | ||
} | ||
}, waitMs ?? DEFAULT_THROTTLE_DELAY_MS); | ||
} | ||
@@ -16,21 +226,15 @@ /** | ||
* level. | ||
* | ||
* Note: a politeness level of `assertive` should only be used for | ||
* time-sensistive or critical notifications that absolutely require the | ||
* user's immediate attention | ||
* | ||
* @see https://www.w3.org/TR/wai-aria/#aria-live | ||
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions | ||
*/ | ||
announce(message, options = {}) { | ||
const { politeness = "polite" } = options; | ||
const container = this.shadowRoot?.getElementById(politeness); | ||
if (!container) { | ||
throw new Error("Unable to find container for message"); | ||
} | ||
if (container.textContent === message) { | ||
container.textContent = `${message}\xA0`; | ||
} else { | ||
container.textContent = message; | ||
} | ||
const { delayMs, politeness = "polite" } = options; | ||
const item = { | ||
politeness, | ||
contents: message, | ||
scheduled: delayMs !== void 0 ? Date.now() + delayMs : "immediate" | ||
}; | ||
__privateGet(this, _queue).insert(item); | ||
this.performWork(); | ||
return () => { | ||
__privateGet(this, _queue).delete(item); | ||
}; | ||
} | ||
@@ -44,5 +248,38 @@ /** | ||
if (textContent !== "") { | ||
this.announce(textContent, options); | ||
return this.announce(textContent, options); | ||
} | ||
return noop; | ||
} | ||
performWork() { | ||
let message = __privateGet(this, _queue).peek(); | ||
if (!message) { | ||
return; | ||
} | ||
if (__privateGet(this, _timeoutId) !== null) { | ||
clearTimeout(__privateGet(this, _timeoutId)); | ||
__privateSet(this, _timeoutId, null); | ||
} | ||
if (message.scheduled === "immediate") { | ||
message = __privateGet(this, _queue).pop(); | ||
if (message) { | ||
this.updateContainerWithMessage(message); | ||
} | ||
this.performWork(); | ||
return; | ||
} | ||
const now = Date.now(); | ||
if (message.scheduled <= now) { | ||
message = __privateGet(this, _queue).pop(); | ||
if (message) { | ||
this.updateContainerWithMessage(message); | ||
} | ||
this.performWork(); | ||
return; | ||
} | ||
const timeout = message.scheduled > now ? message.scheduled - now : 0; | ||
__privateSet(this, _timeoutId, window.setTimeout(() => { | ||
__privateSet(this, _timeoutId, null); | ||
this.performWork(); | ||
}, timeout)); | ||
} | ||
getMessage(politeness = "polite") { | ||
@@ -55,3 +292,16 @@ const container = this.shadowRoot?.getElementById(politeness); | ||
} | ||
/** | ||
* Stop any pending messages from being announced by the live region | ||
*/ | ||
clear() { | ||
if (__privateGet(this, _timeoutId) !== null) { | ||
clearTimeout(__privateGet(this, _timeoutId)); | ||
__privateSet(this, _timeoutId, null); | ||
} | ||
this.updateContainerWithMessage.cancel(); | ||
__privateGet(this, _queue).clear(); | ||
} | ||
} | ||
_queue = new WeakMap(); | ||
_timeoutId = new WeakMap(); | ||
function getTextContent(element) { | ||
@@ -91,2 +341,19 @@ let value = ""; | ||
} | ||
function compareMessages(a, b) { | ||
if (a.scheduled === b.scheduled) { | ||
return Ordering.Equal; | ||
} | ||
if (a.scheduled === "immediate" && b.scheduled !== "immediate") { | ||
return Ordering.Less; | ||
} | ||
if (a.scheduled !== "immediate" && b.scheduled === "immediate") { | ||
return Ordering.Greater; | ||
} | ||
if (a.scheduled < b.scheduled) { | ||
return Ordering.Less; | ||
} | ||
return Ordering.Greater; | ||
} | ||
function noop() { | ||
} | ||
@@ -93,0 +360,0 @@ if (!customElements.get("live-region")) { |
{ | ||
"name": "@primer/live-region-element", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"type": "module", | ||
@@ -47,3 +47,3 @@ "main": "./dist/esm/index.js", | ||
"devDependencies": { | ||
"@custom-elements-manifest/analyzer": "^0.9.2", | ||
"@custom-elements-manifest/analyzer": "^0.9.3", | ||
"@rollup/plugin-inject": "^5.0.5", | ||
@@ -56,3 +56,3 @@ "@rollup/plugin-replace": "^5.0.5", | ||
"rollup-plugin-typescript2": "^0.36.0", | ||
"typescript": "^5.3.3" | ||
"typescript": "^5.4.3" | ||
}, | ||
@@ -59,0 +59,0 @@ "sideEffects": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
61874
18
1688