Skip to main content

Observed Element

About

Observed Element lets you find and focus elements by name, including ones that aren't mounted in the DOM yet — Tabster waits for them to appear.

Mark an element as observed by giving it one or more names:

<button data-tabster='{"observed": {"names": ["myButton"]}}'>Observed</button>

Setup

Call getObservedElement() once to enable the observed data-tabster key and get the API:

import { createTabster, getObservedElement } from "tabster";

const tabster = createTabster(window);
const observedElement = getObservedElement(tabster);

observedElement.requestFocus("myButton", 3000);

Properties

interface ObservedElementProps {
names: string[];
details?: unknown;
}

names is the list of names this element can be looked up by; details is an arbitrary application-defined payload retrievable through getAllObservedElements()/onObservedElementChange below.

Methods

getElement()

Returns the observed element currently in the DOM for a name, or null.

const element = observedElement.getElement("myButton");

waitElement()

Waits (up to timeout ms) for an element with the given name to appear.

import { ObservedElementAccessibilities } from "tabster";

const wait = observedElement.waitElement(
"myButton",
3000,
ObservedElementAccessibilities.Focusable // Only resolve once focusable.
);

wait.result.then((element) => {
console.log("Observed element:", element);
});

// Cancel the wait if it's no longer needed.
wait.cancel();

requestFocus()

Waits for the observed element to appear, then focuses it. A subsequent requestFocus() call, or the user manually moving focus, cancels the pending request automatically.

const focus = observedElement.requestFocus("myButton", 3000);

focus.result.then((focused: boolean) => {
console.log("Focused:", focused);
});

focus.cancel();

waitElement()/requestFocus() both return an ObservedElementAsyncRequest, whose status is one of the ObservedElementRequestStatuses (Waiting/Succeeded/Canceled/TimedOut), and whose diagnostics field carries details useful for debugging a failed/timed-out request:

interface ObservedElementAsyncRequestDiagnostics {
reason?: ObservedElementFailureReason; // set when Canceled or TimedOut
waitForElementDuration?: number; // ms actually spent waiting
targetState?: {
inDOM: boolean;
isAccessible?: boolean;
isFocusable?: boolean;
};
getCancelTriggeringElement?: () => HTMLElement | null;
}

reason is one of the ObservedElementFailureReasonsTimeoutElementNotInDOM, TimeoutElementNotAccessible, TimeoutElementNotFocusable, TimeoutElementNotReady, CanceledFocusChange, SupersededByNewRequest, or FocusCallFailed.

getAllObservedElements()

Returns every currently registered observed element, grouped by name:

const all = observedElement.getAllObservedElements();
// Map<string, Array<{ element: HTMLElement; names: string[] }>>

onObservedElementChange

An optional callback you can assign to be notified whenever an observed element is added, removed, or has its names updated:

observedElement.onObservedElementChange = (change) => {
// change.type: "added" | "removed" | "updated"
// change.element, change.names, change.addedNames?, change.removedNames?
console.log(change.type, change.element, change.names);
};

Cross-origin lookups

Observed Element names are also reachable across <iframe> boundaries once Cross-Origin support is set up — see crossOrigin.observedElement.requestFocus() on that page.

Examples

See Observed Element examples in Storybook, including a live demo of getAllObservedElements()/onObservedElementChange and a cross-iframe focus request.