Press
Headless Press; covers React Aria usePress, Pointer Events activation.
| Package | @vizejs/ui/press |
| Maturity | stable |
| Own the source | vize lib pull press |
| Requires | — |
| Aliases | press, activation, pointer |
| Covers | React Aria usePress, Pointer Events activation |
API
createPress
Create an SSR-safe press normalizer for one host element. Spread the returned pressProps onto the host and call dispose when using this factory outside a Vue effect scope. No DOM global is read at setup.
function createPress(options: PressOptions = {}): PressController
usePress
Create a press normalizer disposed with the current Vue effect scope.
function usePress(options: PressOptions = {}): PressController
PressActivationMemory
Own short-lived tokens that associate release, cancellation, and click.
class PressActivationMemory
readBooleanOption
Resolve and validate a reactive boolean option for JavaScript consumers.
function readBooleanOption( value: PressOptions[keyof Pick< PressOptions, "allowTextSelectionOnPress" | "isDisabled" | "preventFocusOnPress" | "shouldCancelOnPointerExit" >], name: string, ): boolean
readKeyboardBehavior
Resolve the closed keyboard behavior union without accepting mistyped JS.
function readKeyboardBehavior( value: PressOptions["keyboardBehavior"], ): PressKeyboardBehavior
eventElement
Resolve a cross-realm event currentTarget without instanceof assumptions.
function eventElement(event: Event): Element | null
pointerTypeOf
Map Pointer Events' extensible device string to the stable public union.
function pointerTypeOf(event: PointerEvent): PressPointerType
isPrimaryPointer
Only a primary contact and its primary button can activate a control.
function isPrimaryPointer(event: PointerEvent): boolean
createPressEvent
Build a frozen snapshot so later browser mutation cannot change callbacks' data.
function createPressEvent( type: PressEventType, target: Element, pointerType: PressPointerType, originalEvent: Event | null, isCanceled = false, touchIdentifier: number | null = null, ): PressEvent
keyboardActivation
Native elements retain their own activation timing and default actions.
function keyboardActivation( target: Element, key: string, behavior: PressKeyboardBehavior, ): "custom" | "native" | null
isEventInside
Determine pointer containment without assuming events originate in one realm.
function isEventInside( event: Event, target: Element, touchIdentifier: number | null = null, ): boolean
disableTextSelection
Apply a transient selection guard and return an exact, idempotent restore.
function disableTextSelection(target: Element): () => void
validatePressOptions
Validate callback slots eagerly so setup failures never install listeners.
function validatePressOptions(options: PressOptions): void
createPressHandlers
Adapt Pointer Events plus legacy mouse/touch and keyboard events to one lifecycle.
function createPressHandlers(lifecycle: PressLifecycle): PressHandlers
PressLifecycle
Internal press state machine shared by all native-event adapters.
class PressLifecycle
Behavior
Normative state × input → outcome table for @vizejs/ui/press. Every row is
exercised by src/families/interaction/press/press*.test.ts;
compile-only API assertions live in
src/families/interaction/press/press.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| P1 | idle | primary pointer down | pressed state starts with the normalized device family | normalizes a primary pointer lifecycle… |
| P2 | idle | secondary or non-primary pointer | input is ignored | ignores non-primary contacts and secondary buttons |
| P3 | active pointer | another pointer down | original pointer retains exclusive ownership | ignores non-primary contacts and secondary buttons |
| P4 | active, inside | primary pointer release then click | up, end, and exactly one press are delivered | normalizes a primary pointer lifecycle… |
| P5 | active, inside | pointer leaves | pressed state pauses with a canceled end snapshot | pauses outside, resumes inside… |
| P6 | paused outside | owning pointer re-enters | pressed state resumes with another start | pauses outside, resumes inside… |
| P7 | paused outside | owning pointer releases | interaction ends without activation | an outside release cancels the default resumable interaction |
| P8 | cancel-on-exit | first pointer exit | interaction ends permanently and compatibility click is canceled | cancel-on-exit suppresses the following compatibility click |
| P9 | active | pointer cancel, drag, blur, or hidden | state and transient resources are canceled | lifecycle listener and cancellation tests |
| P10 | active | reactive disabled becomes true | release cancels; subsequent click default is prevented | cancels when disabled changes during a press |
| P11 | disabled | pointer, keyboard, or click | no callback is emitted; click default is prevented | disabled and reactive-option tests |
| P12 | idle custom button | Enter | start/up/end/press lifecycle is synthesized | emulates button and link keyboard semantics… |
| P13 | idle custom button | Space | scrolling is prevented; activation occurs on keyup | emulates button and link keyboard semantics… |
| P14 | idle custom link | Space / Enter | Space is ignored; Enter activates | emulates button and link keyboard semantics… |
| P15 | native activatable element | browser keyboard click | native timing/default action wins; one normalized press is reported | preserves native keyboard click timing… |
| P16 | keyboard interaction | IME, repeat, or nested interactive key | event is ignored and no ancestor activation is invented | ignores IME, repeats, nested targets… |
| P17 | idle | coordinate-free click | complete lifecycle is reported as virtual |
maps coordinate-free and click-only activation… |
| P18 | idle | click with coordinates/detail | complete lifecycle is reported as mouse |
maps coordinate-free and click-only activation… |
| P19 | active pointer | selection guard | exact inline user-select values and priorities restore at end | restores exact selection styles… |
| P20 | prevent-focus option | compatibility mousedown | default focus transfer is prevented without suppressing keyboard focus | restores exact selection styles… |
| P21 | callback re-enters controller | start callback cancels | stale pressed=true change cannot publish after the cancellation |
a reentrant cancel cannot publish… |
| P22 | multiple callbacks throw | coordinate-free activation | all lifecycle phases run, state settles, then errors surface | synthetic activation completes every phase… |
| P23 | active callback throws | explicit cancellation | interaction remains owned and can still release every listener | callback failure leaves an active interaction… |
| P24 | Vue effect scope | scope stops | controller disposes, state resets, listeners and timers release | the composable requires and follows… |
| P25 | disposed | late renderer event / explicit cancel | bound handlers are inert; imperative mutation throws a stable diagnostic | disposal lifecycle tests |
| P26 | concurrent SSR requests | identical component trees | byte-identical markup contains no serialized handler or global DOM read | renders byte-identical SSR output… |
| P27 | SSR followed by hydration | coordinate-free client activation | root identity is retained; no diagnostics; callback updates render | hydrates without diagnostics… |
| P28 | public TypeScript API | mutation or invalid closed-union use | compilation rejects the misuse | src/families/interaction/press/press.types.test-d.ts |
| P29 | template consumer | native DOM / SSR / Vapor compilation | handler spread and reactive state compile with no warning or fallback | scripts/check-renderers.ts press fixture |
Accessibility and native behavior
clickis the activation authority. Pointer release producespressupandpressend, while the browser's following click producespress. This keeps link navigation, form submission, label forwarding, and assistive-technology activation intact without double invocation.- Native buttons, relevant input types, links with
href, and summaries retain browser keyboard timing and default actions. Custom button-like hosts receive Enter and Space behavior; custom link-like hosts receive Enter only. An<a>or<area>withouthrefhas no native activation, so it followskeyboardBehaviorlike any other custom host. - Space scrolling is canceled only for custom button semantics. The primitive does not cancel native key events whose default action creates the click.
- Disabled state is evaluated at every phase. If it changes during an active gesture, the gesture and its compatibility click are both canceled.
- Events from focused descendants never invent an activation on the bound ancestor. Composite widgets remain responsible for their own key routing.
Pointer ownership and cancellation
- Only the primary contact and primary button can own a press. An active interaction ignores additional contacts until it ends.
- Pointer Events are preferred. Legacy mouse and touch handlers are present for older engines, including the compatibility-mouse suppression window after a touch contact.
- Leaving the target pauses pressed feedback and re-entry resumes it. Set
shouldCancelOnPointerExitto make the first exit terminal. - Window blur, hidden documents, pointer cancellation, drag start, manual cancellation, scope disposal, and disabled transitions all release document listeners and restore transient text-selection state. Only a blur of the window itself cancels; focus moving between elements during a press does not.
preventFocusOnPresssuppresses themousedownfocus default only for an enabled host and a primary button, including in Pointer Events engines wheremousedownstill owns that default.
SSR, Vapor, styling, and tree shaking
- Module evaluation and
createPress()perform no DOM read, native listener installation, timer scheduling, style injection, or request-global mutation. Listeners and timers exist only during a client interaction. usePress()only requires an active Vue effect scope; its plain handler props are compatible with DOM rendering, standard SSR/hydration, and Vapor-compiled consumers. No renderer-specific VNode or component instance is captured.- This entry emits zero CSS and no attributes. Consumers map readonly
isPressedto any classes, data attributes, utility system, CSS variables, or preset package they choose. @vizejs/ui/pressis an exact ESM subpath. The production consumer gate requires root/subpath byte parity, zero CSS, unused-family elimination, and a hard gzip budget.