Deloser 
About
Ideally, the application should always have a focused element while
navigating with the keyboard. In practice, closing a modal dialog or removing
a focused item from a list often leaves focus nowhere — "focus goes to
<body>" — which is especially confusing for screen reader users.
This gets harder because applications are usually built from independent components that don't know about each other: the component that closes a dialog shouldn't need to know which button opened it in order to restore focus there.
Deloser tracks focus history and, when focus is lost, restores it to the most recent element from that history that's still available (from the dialog example, likely the button that opened it).
Delosers can be nested. A Deloser on a list container keeps its own focus history for that list, without polluting the application-level Deloser (typically on the root element) — nested Delosers are recorded as a single entry from their parent's point of view.
Setup
Call getDeloser() once to enable the deloser data-tabster key:
import { createTabster, getDeloser } from "tabster";
const tabster = createTabster(window);
getDeloser(tabster);
<div data-tabster='{"deloser": {}}'>
<button>Button1</button>
<button>Button2</button>
<button>Button3</button>
</div>
If Button2 above is focused and then removed from the DOM, Deloser
restores focus to Button3 (or Button1, depending on history) instead of
leaving focus on <body>.
Properties
interface DeloserProps {
restoreFocusOrder?: RestoreFocusOrder;
noSelectorCheck?: boolean;
strategy?: DeloserStrategy;
}
restoreFocusOrder
History | DeloserDefault | RootDefault | DeloserFirst | RootFirst
History(default) — walk backwards through previously-focused elements.DeloserDefault— focus the element markedfocusable: { isDefault: true }(see Focusable element properties) within this Deloser's own container.RootDefault— same, but search the whole application root instead of just this Deloser's container.DeloserFirst— focus the first focusable element in this Deloser's container.RootFirst— focus the first focusable element in the whole application.
noSelectorCheck
Deloser keeps weak references to DOM elements, plus the CSS selector that
located them. In apps using a virtual DOM (React, etc.), part of the tree
can be re-rendered — visually unchanged, but with new element instances —
which would make the stored element reference stale. By default, Deloser
falls back to re-locating the element via its stored selector when the
direct reference is gone; set noSelectorCheck: true to disable that
fallback.
strategy
Auto | Manual
Auto (default) restores focus automatically using the available history,
as described above. With Manual, focus is not restored automatically
when lost from this Deloser instance — the application must call it itself
by dispatching a DeloserRestoreFocusEvent (see
Events) at the point it wants the restore to
happen:
import { DeloserRestoreFocusEvent } from "tabster";
deloserElement.dispatchEvent(new DeloserRestoreFocusEvent());
Note that even with Manual strategy on this Deloser, its history can still
be used to find an element to focus when the focus is lost from a
different Deloser instance.
Examples
import { createTabster, getDeloser, getTabsterAttribute } from "tabster";
const tabster = createTabster(window);
getDeloser(tabster);
<div {...getTabsterAttribute({ root: {}, deloser: {} })}>
<button>Button1</button>
<button>Button2</button>
<button>Button3</button>
<button>Button4</button>
</div>;
Focus Button2, remove it from the DOM, and focus automatically moves to
Button3.