Controllers
A controller is a small class that adds one behavior to an element: listening, dismissing, positioning, moving focus, dragging. Radiant UI components are assembled from them, and so can yours be.
Controllers need a browser. Pure logic with no DOM (dates, masks, numbers, ordering math) is in Helpers. Both entries are tree-shakeable: a bundle contains only what it imports.
import { createSwipeController } from '@ecopages/radiant-ui/controllers';The shape of a controller
Every controller is a plain class with attach() and destroy(). The constructor does nothing; attach() starts work and destroy() ends all of it, and both are safe to call again. Each class has a createX(host, config) factory that attaches on connect and destroys on disconnect, where host is a RadiantElement or anything with registerConnectedCallback and registerCleanupCallback. Without a host, construct the class and call attach() and destroy(), as the examples on these pages do. See bindController.
Config is getters (getItems, getTarget) and callbacks (onChange), so the element keeps its own state and the controller never reaches into it. Where a decision may need to differ, a controller documents a protected hook; see Composing a component.
Controllers
| Controller | What it does | Reach for it when |
|---|---|---|
| bindController | The contract and the connect and cleanup wiring behind every factory. | You write a new controller, or hold one without a factory. |
| EventListenerController | Listens to event types on a target while attached; typed from target and type. | Listeners on document or window, added and removed at runtime, or with once. |
| MutationController | A MutationObserver on a node. | You react to children or attributes changing. |
| ResizeController | A ResizeObserver on elements. | You react to an element changing size. |
| DismissController | Reports a pointer press outside a surface, focus leaving it, or Escape. | A dialog, drawer or popup that closes itself. |
| PopoverController | Portal mount, floating position, dismissal and teardown of a popup. | A popup, hover card or menu panel beside an anchor. |
| PortalController | Moves a node into document.body (or a container) and carries the origin theme. | A surface that must escape clipping or stacking contexts. |
| FocusTrapController | Keeps Tab inside a modal surface and returns focus on destroy. | A modal dialog. |
| ScrollLockController | Locks scroll on the page or a scroll container, counted across controllers. | A modal that must stop the page or a container scrolling. |
| RovingController | Roving tabindex with arrows, Home and End over the items you return. | Tabs, toolbars, lists and grids with one tab stop. |
| SwipeController | Reports a left, right, up or down swipe on release. | A carousel or pager. |
| PointerDragController | Pointer capture, move, end and cancel, with position, delta and the element under the pointer. | A slider, splitter, resize handle or free-form drag. |
| SortableController | Reorders items, or moves them between containers, by pointer or keyboard with announcements. It never moves a node: onChange does. | A reorderable list or board, including drag between lists. |
| MenuTreeController | Nested submenu coordination: timers, keyboard, popovers. | A menu button or menubar with submenus. |
DOM utilities
The same entry exports functions that touch elements. They have no page of their own; the TSDoc of each says how to use it.
| Export | What it does | Reach for it when |
|---|---|---|
applyFloatingPosition, attachFloating | Floating placement with flip and clamping, applied to an element and kept on scroll and resize. | You position a surface without a popover. |
queryFocusableCandidates, getFocusableElements, findFirstFocusableCandidate, queryRovingTabindexItems | Query focusable descendants; getFocusableElements also skips hidden ones (needs layout). | Custom focus loops and initial focus. |
applyRovingTabindex, focusRovingItem, navigateRovingTabindex | The roving tabindex functions RovingController composes. | One-off roving navigation without a controller. |
getVisibleCollectionOptions, setCollectionActive, clearCollectionActive | aria-activedescendant and data-active bookkeeping for option collections. | Active-descendant lists, such as a combobox you build yourself. |
SORTABLE_*_ATTRIBUTE, SORTABLE_OFFSET_PROPERTIES | The data-sortable-* attributes and --sortable-dx and --sortable-dy the sortable sets. | Styling a sortable. |
How the examples run
Each example on these pages is a mount(root) function in the page's script. It receives one [data-example="<name>"] element, wires the controllers to that element and returns the function that undoes it, which is where destroy() goes. A controller that listens on document or window must be destroyed there, or it keeps listening after the example is gone.
The page script ends with a call to mountExample, which the code blocks leave out. It mounts each matching element once when the page loads and again for the new elements the router swaps in (eco:page-load and eco:after-swap), and it runs the returned function for an element that left the page. A script is a module and runs once per tab, so the page cannot rely on it running again after a navigation.