Appearance
@pixelscriptio/tessera/behavior
Headless interaction primitives, such as press. See Behaviors.
attachCombobox
function
Combobox: the keyboard of an editable combobox with a listbox popup (the ARIA 1.2 pattern), on the text input.
It is the keys and the attributes, nothing else. The component owns the text, the options, which one is active and whether the list is open, and binds aria-expanded and aria-activedescendant on the input. The behavior reads those from the element, turns keys into commands, and the component moves its state:
- Arrow Down opens the list (or moves to the next option); Arrow Up opens it on the last option (or moves back).
- Enter chooses the active option, and does not submit a form while it does. With no active option it does nothing.
- Escape closes an open list and keeps what was typed.
- Tab closes an open list and leaves the field as usual.
- It sets
role="combobox",aria-autocomplete="list",aria-expanded="false"andautocomplete="off"if the input has none of them, so the pattern is complete even if the component forgot one. - A disabled input (or one with
aria-disabled="true") gives no commands. Keys during text composition (an input method editor) are left alone.
Returns a function that removes everything it added.
ts
function attachCombobox<N, E extends N>(adapter: RendererAdapter<N, E>, input: E, options: ComboboxOptions): () => voidattachDialog
function
Dialog: a modal dialog on a native <dialog> element.
The browser does the hard parts of a modal: showModal() makes everything behind it inert, keeps focus inside, moves focus in, and gives it back to what opened it on close. The behavior adds what it does not:
- It follows
data-open. The component owns whether it is open and bindsdata-open="true"to its state; the behavior shows the dialog modally when that is set and closes it when it is removed. - Dismissing is a request. Escape (the browser's
cancel), a click outside the panel and the browser closing the dialog by itself are reported asdismisswith a reason, and the dialog stays as it is until the component moves its state. A component that wants to confirm first can refuse. - Tab stays inside. Tab on the last control goes to the first, and Shift+Tab on the first goes to the last, so focus cannot leave through the browser's own controls.
- The page does not scroll behind it, and the scrollbar's width is kept so nothing shifts. Nested dialogs share one lock.
Returns a function that removes everything it added and closes the dialog.
ts
function attachDialog<N, E extends N>(adapter: RendererAdapter<N, E>, el: E, options: DialogOptions): () => voidattachDisclosure
function
Disclosure: a trigger that shows and hides a region, for an accordion item or a "show more".
Attach it to the trigger. It is press, reading the current state from aria-expanded on the trigger (the component binds it, and owns the state) and reporting where it is going. It starts as collapsed if the trigger has no state yet. Showing and hiding the content is the component's job: bind hidden to the same state.
Returns a function that removes everything it added.
ts
function attachDisclosure<N, E extends N>(adapter: RendererAdapter<N, E>, trigger: E, options: DisclosureOptions): () => voidattachPress
function
Press: a click, a tap, or Enter or Space on the element, with none of the differences between them.
- Disabled is read from the element (
disabledoraria-disabled="true"), so it follows whatever the template binds. A disabled element never presses. - While the pointer or Space or Enter is held, the element has
data-state="pressed", so styles can use it without JavaScript. - On an element that is not natively pressable it adds
tabindex="0"and the role, listens for Enter (on keydown) and Space (on keyup, after preventing the page from scrolling), and ignores key repeat.
Works with no styles applied, and talks to the element only through the renderer adapter. Returns a function that removes everything it added.
ts
function attachPress<N, E extends N>(adapter: RendererAdapter<N, E>, el: E, options: PressOptions): () => voidattachTabs
function
Tabs: the keys and the pointer of a tab list (the ARIA tabs pattern, with automatic activation), on the element that has role="tablist". It needs a DOM: it finds the tabs and moves focus.
It does not own which tab is selected. The component does, and binds on each tab aria-selected, tabindex (0 for the selected one, -1 for the others, so Tab leaves the list) and aria-controls, and on each panel hidden and aria-labelledby. The behavior reports where the person is going and the component moves its state:
- Arrow Right and Arrow Left (Down and Up when the list has
aria-orientation="vertical") focus the next and previous tab and activate it, wrapping at the ends. Home and End go to the first and last. - A click on a tab activates it (so does Enter or Space on a
<button>, which the browser turns into a click). - A disabled tab (
disabled, oraria-disabled="true") is skipped by the keys and gives no click. - It sets
role="tablist"if the element has none.
Returns a function that removes everything it added.
ts
function attachTabs<N, E extends N>(adapter: RendererAdapter<N, E>, list: E, options: TabsOptions): () => voidattachToggle
function
Toggle: press that moves between on and off, for a checkbox, a switch or a toggle button.
- The current state is read from the element (
aria-checked, oraria-pressedfor a toggle button), so the component stays the owner of the value: it binds the attribute, and reacts to the event by setting its own state. The event says where it is going, never that it got there. - Space toggles. Enter toggles a switch and a toggle button but not a checkbox, which is what browsers do for a native one. Everything else about activation (disabled, key repeat,
data-state="pressed") ispress. - Starts as off if the element has no state yet, so assistive technology always has a value to read.
Returns a function that removes everything it added.
ts
function attachToggle<N, E extends N>(adapter: RendererAdapter<N, E>, el: E, options: ToggleOptions): () => voidBEHAVIOR_ATTACH
constant
The function each behavior is attached with, exported from @pixelscriptio/tessera/behavior.
ts
const BEHAVIOR_ATTACH: Readonly<Record<string, string>>BEHAVIOR_EVENTS
constant
Behavior event name to the behavior that produces it, so @press in a template can be wired.
ts
const BEHAVIOR_EVENTS: Readonly<Record<string, string>>BEHAVIOR_REQUIREMENTS
constant
What a contract must declare to use each behavior. The compiler validates "behavior" against this table.
ts
const BEHAVIOR_REQUIREMENTS: Readonly<Record<string, BehaviorRequirements>>BehaviorRequirements
interface
Not documented yet.
ts
export interface BehaviorRequirements {
parts: readonly string[]
events: readonly string[]
states: readonly string[]
implemented: boolean
}ComboboxCommand
interface
What a command event carries: what the person asked the combobox to do, from the keyboard.
ts
export interface ComboboxCommand {
command: 'open' | 'open-last' | 'next' | 'prev' | 'select' | 'close'
source: 'keyboard'
event: unknown
}ComboboxOptions
interface
Not documented yet.
ts
export interface ComboboxOptions {
onCommand: (detail: ComboboxCommand) => void
native?: boolean
role?: string
}DialogOptions
interface
Not documented yet.
ts
export interface DialogOptions {
onDismiss: (detail: DismissDetail) => void
native?: boolean
role?: string
}DisclosureOptions
interface
Not documented yet.
ts
export interface DisclosureOptions {
onExpand: (detail: ExpandDetail) => void
native?: boolean
}DismissDetail
interface
What a dismiss event carries.
ts
export interface DismissDetail {
reason: 'escape' | 'backdrop' | 'native'
event: unknown
}ExpandDetail
interface
What an expand event carries.
ts
export interface ExpandDetail {
expanded: boolean
source: PressDetail['source']
event: unknown
}PressDetail
interface
What a press event carries.
ts
export interface PressDetail {
source: 'pointer' | 'keyboard' | 'virtual'
event: unknown
}PressOptions
interface
Not documented yet.
ts
export interface PressOptions {
onPress: (detail: PressDetail) => void
native?: boolean
role?: string
}TabsActivate
interface
What an activate event carries: the tab the person chose, from the keyboard or the pointer.
ts
export interface TabsActivate {
value: string
source: 'keyboard' | 'pointer'
event: unknown
}TabsOptions
interface
Not documented yet.
ts
export interface TabsOptions {
onActivate: (detail: TabsActivate) => void
}toggleAttribute
constant
A toggle button is aria-pressed; a checkbox and a switch are aria-checked.
ts
const toggleAttribute: (role: string | undefined) => "aria-pressed" | "aria-checked"ToggleDetail
interface
What a toggle event carries.
ts
export interface ToggleDetail {
checked: boolean
source: PressDetail['source']
event: unknown
}ToggleOptions
interface
Not documented yet.
ts
export interface ToggleOptions {
onToggle: (detail: ToggleDetail) => void
native?: boolean
role?: string
}