# DOM helpers The queries the behaviors share, exported for a template's own key and pointer handling: which elements take focus, whether one is disabled or visible, how to focus without scrolling, and the text a typeahead matches. Every function touches the DOM only when called, so importing them on the server is safe. ```ts Signatures function tabbables(container: Element): HTMLElement[]; function isDisabled(el: Element): boolean; function isVisible(el: Element): boolean; function hiddenWithRoot(el: Element | null | undefined, root: Element): boolean; function focusElement(el: HTMLElement | null | undefined, opts?: { select?: boolean }): boolean; function focusFirst(candidates: HTMLElement[], opts?: { select?: boolean }): HTMLElement | null; function clickPointer(e: MouseEvent, on?: Element | null): string; function isInside(target: EventTarget | null, roots: (Element | null | undefined)[]): boolean; function directionOf(el: Element): "ltr" | "rtl"; function textValue(el: Element): string; ``` | Function | What it returns | |---|---| | `tabbables(container)` | Every element Tab can reach in container, in DOM order: focusable, not disabled (a disabled fieldset included), `tabindex >= 0`, visible, not inert. Radio groups are not collapsed to one stop. | | `isDisabled(el)` | True for an element the user cannot act on: `disabled` (a disabled fieldset included), `aria-disabled="true"` or `data-disabled`. | | `isVisible(el)` | True when the element is in the document, renders a box and is not `visibility: hidden`. | | `hiddenWithRoot(el, root)` | True when el is inside root and root itself renders no box (a closed Collapsible around a group). Such an element is hidden, not gone, so a focus stop on it is kept. | | `focusElement(el, { select })` | Focuses el without scrolling the page, selects a text input's content when asked, and returns whether el took focus. Null is a false, not an error. | | `focusFirst(candidates, { select })` | Focuses the first candidate that accepts focus and returns it, or null. | | `clickPointer(e, on)` | The pointer behind a click: `"mouse"`, `"touch"`, `"pen"`, or `""` for a click from the keyboard, a label or code. When the click event does not say, the press that came before it decides, if it landed inside `on` (the element that took the click, by default). | | `isInside(target, roots)` | True when target is inside any of the roots. Null entries are ignored. | | `directionOf(el)` | The writing direction in effect on el. | | `textValue(el)` | The text a typeahead matches: `data-text-value`, else the trimmed text content. | ## In a template of your own ```ehtml app/shared/templates/link-card/index.ehtml import { tabbables, isInside } from "@elements/ui/behaviors"; function onCardClick(e: MouseEvent, href: string) { let card = e.currentTarget as HTMLElement; if (isInside(e.target, tabbables(card))) { return; } location.assign(href); } ``` A click anywhere on the card follows the link, and a click on a control inside it (the link itself, a button in the slot) is left to that control. ## Used by `tabbables`: [Dialog](../templates/dialog), [Drawer](../templates/drawer), [NavigationMenu](../templates/navigation-menu), [Popover](../templates/popover), [Resizable](../templates/resizable), [VirtualList](../templates/virtual-list). `isDisabled`: [Combobox](../templates/combobox), [Command](../templates/command), the menus, [Select](../templates/select). `focusElement`: the menus, [NavigationMenu](../templates/navigation-menu), [Popover](../templates/popover), [Resizable](../templates/resizable), [Tree](../templates/tree). `clickPointer`: [Chart](../templates/chart), [Dialog](../templates/dialog), the menus, [Select](../templates/select). `directionOf`: every template with a reading direction, from [Accordion](../templates/accordion) to [Slider](../templates/slider). `textValue`: [Combobox](../templates/combobox), [Kanban](../templates/kanban). `isInside`: [TagsInput](../templates/tags-input).