Members
(static, constant) ElementBase
HTMLElement when there is one, a plain class when there is not (e.g. Node under
test), so custom element modules stay importable outside the browser.
- Source:
Example
class MyElement extends ElementBase {}
Methods
(static) announcer(host, optionsopt) → {Announcer}
A live region, and the one way of putting something in it that a screen reader reads.
Two things go wrong with live regions, both silently, which is what makes them worth a helper rather than four lines at each call site.
A region announces text that arrives in one already in the document. Creating the element and filling it in the same breath announces nothing at all - there was no live region in the accessibility tree yet for the change to happen in. So this is called when there is nothing to say, and the saying comes later.
A region announces a change. Setting the same sentence twice is not one, so the second copy, the second failed save and the second press of a toggle are all silent. Clearing first and setting in a later task is what makes the second one a change.
delay is how much later. 0 - the next task - is enough for two mutations to be recorded
where one would otherwise be, and is what this has always used. It is an option rather than
a constant because live region behaviour is, in ARIA's own words, a strong suggestion that
may be overridden by the browser, the assistive technology or the user: a page that finds
its own pairing needs longer can say so without patching this. The number has not been
measured against NVDA, JAWS or VoiceOver, and is not presented as tuned.
The node is a <span> the caller names, because where it sits and how it is hidden are the
caller's business. Hide it by clipping - display: none and visibility: hidden both take
it out of the accessibility tree, and a region nothing can read is the whole point undone.
- Source:
Parameters:
| Name | Type | Attributes | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
host |
Element
|
Element the region is appended to. One already there under the same class is adopted rather than a second one added, so calling this twice is safe. |
|||||||||||||||||||||
options |
object
|
<optional> |
|
Returns:
- Type:
-
Announcer
Example
const status = announcer(el, { className: 'copy-status' })
status.say('Copied') // and again later, and it is read again
status.destroy() // when the element goes
(static) define(tag, ctor)
Registers a custom element - in the browser only, and only once, so the module is safe to import twice or to import under Node.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
tag |
string
|
|
ctor |
function
|
Example
define('my-element', MyElement)
define('my-element', MyElement) // second call is a no-op
(static) fits(at, size, limit) → {boolean}
Whether a box of size starting at at is inside a viewport of limit.
Both ends, because a panel that runs off the top is as unreachable as one that runs off the bottom.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
at |
number
|
Where the box starts, in viewport coordinates |
size |
number
|
|
limit |
number
|
The viewport's extent on the same axis |
Returns:
- Type:
-
boolean
Example
fits(700, 300, 800) // => false, runs off the bottom
fits(100, 300, 800) // => true
(static) nextIndex(current, key, length) → {number|null}
Where an arrow, Home or End key moves focus in a wrapping list of widgets - the key map the APG patterns with a roving tabindex (menu, tablist, accordion headers) share.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
current |
number
|
Index of the currently focused item, |
key |
string
|
KeyboardEvent.key value |
length |
number
|
Number of items in the list |
Returns:
- Type:
-
number|null
Target index, or null if the key is unhandled
Example
nextIndex(0, 'ArrowDown', 3) // => 1
nextIndex(2, 'ArrowDown', 3) // => 0, wraps
nextIndex(0, 'End', 3) // => 2
nextIndex(0, 'Tab', 3) // => null
(static) placeFlyout(trigger, panel, viewport, rtl, centredopt) → {Object}
Where a floating panel goes relative to its trigger: under it, or over it when there is no room under; and running from the trigger's inline start, or back the other way when that would take it off the edge.
The preferred placement wins ties and wins when neither fits, because a panel with nowhere good to go should at least land where the reader expects it.
centred asks for the panel to sit on the trigger's middle - what a tooltip wants, where
an edge-aligned bubble points at nothing. It is a preference and not an instruction: a
trigger near the edge cannot be centred on without the panel hanging off it, so the
answer falls back to the edge that fits. Off by default, because align is spent as a
CSS keyword and a caller whose stylesheet answers only start and end must not be
handed a third value it has no rule for.
Only the inline axis: which side of the trigger the panel goes on is a separate question, and this does not change its answer.
- Source:
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
trigger |
DOMRect
|
object
|
Rect of the trigger, in viewport coordinates |
||
panel |
Object
|
Size of the panel |
||
viewport |
Object
|
|||
rtl |
boolean
|
Whether the layout runs right to left |
||
centred |
boolean
|
<optional> |
false |
Prefer the trigger's middle over either of its edges |
Returns:
- Type:
-
Object
side is block-end/block-start,
align is start/end, or center when centred was asked for and there was room -
the CSS spelling, since that is where the value is spent
Examples
const viewport = { width: 1000, height: 800 }
placeFlyout(button.getBoundingClientRect(), { width: 200, height: 300 }, viewport, false)
// => { side: 'block-end', align: 'start' } when there is room below
placeFlyout(rect, { width: 200, height: 300 }, viewport, false, true)
// => { side: 'block-end', align: 'center' } when the middle has room for it
(static) placeSubmenu(item, panel, viewport, rtl) → {Object}
Where a nested panel goes: beside the item that opens it, on the inline end unless the edge is there, and running down from the item unless the bottom is.
Which is how a submenu near the bottom right corner ends up opening up and to the left - one decision per axis rather than a list of corners.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
item |
DOMRect
|
object
|
Rect of the item that opens it, in viewport coordinates |
panel |
Object
|
|
viewport |
Object
|
|
rtl |
boolean
|
Whether the layout runs right to left |
Returns:
- Type:
-
Object
side is inline-end/inline-start,
align is start/end
Example
placeSubmenu(item.getBoundingClientRect(), { width: 200, height: 300 }, viewport, false)
// => { side: 'inline-end', align: 'start' } when there is room beside it
(static) stepIndex(current, key, length) → {number|null}
Where an arrow, Home or End key moves focus in a list with ends - the non-wrapping
counterpart of nextIndex, for sets where running off one end is how you get back to
the rest of the page (the APG disclosure navigation, a toolbar).
Answers to both axes, so a horizontal bar and a vertical stack share it.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
current |
number
|
Index of the focused item, |
key |
string
|
KeyboardEvent.key value |
length |
number
|
Number of items in the set |
Returns:
- Type:
-
number|null
Target index, or null if the key is unhandled or there is nowhere to go
Example
stepIndex(0, 'ArrowRight', 4) // => 1
stepIndex(3, 'ArrowRight', 4) // => null, the ends do not wrap
stepIndex(2, 'Home', 4) // => 0
(static) typeAheadIndex(labels, current, buffer) → {number|null}
Where a type-ahead lands in a list, given what has been typed so far.
Two rules that look like edge cases and are not. Holding or repeating one letter
cycles the items starting with it - aaa is someone pressing a three times looking
for the next "Archive", not an item named "aaa". And a search that is one character
long starts after the focused item, so pressing that letter again moves on, while a
buffer still being typed starts at it, so the match narrows onto the item the
reader is already on instead of skipping past it.
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
labels |
Array.<string>
|
The items' text, in list order |
current |
number
|
Index of the focused item, |
buffer |
string
|
What has been typed inside the type-ahead window |
Returns:
- Type:
-
number|null
Target index, or null if nothing matches
Example
const labels = ['Profile', 'Preferences', 'Archive']
typeAheadIndex(labels, 0, 'a') // => 2
typeAheadIndex(labels, 0, 'p') // => 1, starts after the focused item
typeAheadIndex(labels, 1, 'pre') // => 1, a buffer narrows onto it
Type Definitions
Announcer
Properties:
| Name | Type | Description |
|---|---|---|
node |
Element
|
The live region itself |
say |
AnnouncerSay
|
Announces, and announces again for a repeated message |
clear |
AnnouncerAction
|
Empties the region at once |
destroy |
AnnouncerAction
|
Drops a message still in the air and removes the node |
- Source:
Type:
-
object
AnnouncerSay(message) → {void}
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
message |
string
|
What is read out |
Returns:
- Type:
-
void