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) 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