AntaCapture
Switch to dark theme
Search documentation
On this page

Capture

Capture is a light-DOM input surface for custom tables, plots, and drag interactions. Wheel capture, pointer capture, and panning are all disabled by default.

Usage

Each capability is opt-in; handlers alone enable nothing. Choose by interaction:

InteractionCapabilityYour component handles
Mouse wheel or trackpad scrollingwheelCaptureScroll offsets or zoom
Touchscreen drag to scrollpanScroll offsets from pan deltas
Drag to select, resize, or drawpointerCaptureRaw pointer samples

Disabled Capture surfaces add no input state or listeners.

Capture cancels accepted native input on the browser thread, then sends plain data to your handler. Your component performs the action. Forward the serializable detail across worker boundaries. Handler return values cannot change cancellation.

For a 2D table, combine wheelCapture and pan for scrolling with pointerCapture={{ pointerTypes: ['mouse', 'pen'] }} for selection. Each handler updates the corresponding scroll or selection state.

Wheel ownership

Set wheelCapture to true for all directions, or update allowed directions from your component’s bounds. Declined input reaches the enclosing editor or scroll container. The dominant axis decides ownership; both deltas are delivered. All-false bounds preserve settling while declining input. Passing false or removing wheelCapture disables capture and clears settling.

Enable Capture wheel, then scroll over the rows. The controls let you try different activation and settling rules.

const [offset, setOffset] = useState(0)
const maxOffset = 28 * 32 - 192
// These settings come from the preview controls.
<div onKeyDown={event => {
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
event.preventDefault()
const delta = event.key === 'ArrowDown' ? 32 : -32
setOffset(value => Math.max(0, Math.min(maxOffset, value + delta)))
}}>
<Capture
wheelCapture={enabled && { up: offset > 0, down: offset < maxOffset }}
wheelActivation={activation}
wheelSettle={{ delay, tolerance: 5, resetOnMove }}
tabIndex={0}
aria-label="Wheel capture surface"
style={{ height: 192, overflow: 'hidden' }}
onWheelInput={(_, { wheelEvent, boxHeight }) => {
const unit = wheelEvent.deltaMode === 1 ? 32 : wheelEvent.deltaMode === 2 ? boxHeight : 1
setOffset(value => Math.max(0, Math.min(maxOffset, value + wheelEvent.deltaY * unit)))
}}
>
<div style={{ transform: `translateY(${-offset}px)` }}>
{Array.from({ length: 28 }, (_, index) => (
<div key={index} style={{ height: 32 }}>Sample {index + 1}</div>
))}
</div>
</Capture>
</div>

The default "settled" activation waits for the pointer to rest inside Capture (150ms, 5px tolerance). It stays active until the pointer leaves. wheelSettle.resetOnMove requires settling again after movement beyond tolerance. Scrolling a Capture under a stationary pointer does not activate it.

Use "hover" for immediate activation, "focus" for :focus-within, or "settled-or-focus" for either condition. Wheel input must still target Capture. Capture never takes focus automatically; provide tabIndex or a focusable child.

Modified input stays native by default, including Ctrl/pinch zoom. Use wheelModifier="ctrl" to capture Ctrl-wheel, or "any" for all modifiers. Named modifiers allow other keys too.

The innermost eligible Capture handles wheel input on its host. Canceled and non-cancelable events are ignored; Capture cannot undo an ancestor’s capture-phase handler. Capture skips nested textarea, select, numeric/range inputs, and Anta menus. Mark other native scroll panes with data-capture-ignore.

detail.wheelEvent preserves native event data, signs, and deltaMode units (0: pixels, 1: lines, 2: pages). Capture does not infer OS scrolling preferences. localX and localY are CSS pixels from Capture’s viewport rectangle’s top-left, without undoing transforms. Native offsetX and offsetY refer to the original target, which may be a child.

Pointer sessions

pointerCapture sends raw data for one primary mouse, pen, or touch pointer, including movement outside Capture. Your component interprets the gesture. Use threshold to delay capture until dragging starts. Drag an area in the preview, or select Select sample area with the keyboard.

type Selection = { x: number; y: number; width: number; height: number }
const [selection, setSelection] = useState<Selection | null>(null)
<Button label="Select sample area" onClick={() => setSelection({ x: 24, y: 24, width: 100, height: 64 })} />
<Button label="Clear selection" onClick={() => setSelection(null)} />
<Capture
className="pointer-probe-surface"
aria-label="Pointer selection surface"
pointerCapture={{ pointerTypes: ['mouse', 'pen'], threshold: 3 }}
onPointerInput={(_, detail) => {
if (detail.phase === 'cancel') return
const x = Math.max(0, Math.min(detail.boxWidth, detail.localX))
const y = Math.max(0, Math.min(detail.boxHeight, detail.localY))
setSelection({
x: Math.min(detail.start.localX, x), y: Math.min(detail.start.localY, y),
width: Math.abs(x - detail.start.localX), height: Math.abs(y - detail.start.localY),
})
}}
>
<Text size="small" priority="tertiary">Drag to select an area.</Text>
{selection && <div className="pointer-probe-selection"
style={{ left: selection.x, top: selection.y, width: selection.width, height: selection.height }} />}
</Capture>
.pointer-probe-surface { position: relative; height: 160px; overflow: hidden; border: 1px solid var(--border-4); border-radius: 8px; background: var(--bg-canvas); }
.pointer-probe-surface a-text { display: block; padding: 12px; }
.pointer-probe-selection { position: absolute; pointer-events: none; border: 1px solid var(--border-3-brand); background: color-mix(in oklch, var(--text-1-brand) 12%, transparent); }

onPointerInput reports start, move, end, and cancel. deltaX/Y are incremental movement; movementX/Y are totals from the press, positive right/down.

Presses below threshold emit nothing. Captured gestures suppress the following pointer-generated click. Cancellation reports cancelReason on pointer cancellation, lost capture, disabling, removal, or window blur/hiding. Lifecycle cancellation sets pointerEvent: null. Capture cancels native behavior without stopping pointer-event propagation.

Pointer and pan capture skip nested native/ARIA controls, links, and editable content. pointerCapture.includeInteractive includes them. Use data-capture-ignore to exclude a subtree from all capture, including wheel. It does not remove ancestor touch-action restrictions.

Touch panning and inertia

pan converts touchscreen dragging into scroll deltas without inertia. It captures the pointer internally; it does not require pointerCapture. Set pan.pointerTypes to enable mouse or pen panning. If your component already interprets touch gestures, use pointerCapture for touch and omit pan.

Enabling both for the same pointer type emits both streams. Assign separate pointer types when each gesture should have one handler.

Try touch panning, or enable Pan with mouse. Inertia continues motion after release; turn it off to stop when the pointer lifts.

const [offset, setOffset] = useState(0)
const maxOffset = 18 * 32 - 160
const move = (delta: number) => setOffset(value => Math.max(0, Math.min(maxOffset, value + delta)))
// mouse and inertia come from the preview checkboxes.
<div onKeyDown={event => {
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
event.preventDefault()
move(event.key === 'ArrowDown' ? 32 : -32)
}}>
<Capture
className="pan-probe-surface"
tabIndex={0}
aria-label="Pan capture surface"
pan={{
axis: 'y',
pointerTypes: mouse ? ['touch', 'mouse'] : ['touch'],
directions: { up: offset > 0, down: offset < maxOffset },
inertia,
}}
onPanInput={(_, { deltaY }) => move(deltaY)}
>
<div style={{ transform: `translateY(${-offset}px)` }}>
{Array.from({ length: 18 }, (_, index) => (
<div className="pan-probe-row" key={index}>Sample {index + 1}</div>
))}
</div>
</Capture>
</div>
.pan-probe-surface { height: 160px; overflow: hidden; border: 1px solid var(--border-4); border-radius: 8px; background: var(--bg-canvas); }
.pan-probe-row { height: 32px; box-sizing: border-box; padding: 6px 12px; border-bottom: 1px solid var(--border-5); }

Pan deltas oppose finger movement. Apply deltas from every phase, including release. Inertia samples have pointerEvent: null. Motion stops on new input, disabled directions, or cancellation.

Capture sets CSS touch-action before contact. Single-axis pan leaves the other axis and pinch zoom to the browser. Both-axis pan or touch pointer capture owns the whole gesture; mouse-only capture leaves touch unchanged. Unlike wheel, an active touch gesture cannot return to native scrolling at a bound. Update directions to stop custom motion and control the next gesture. Empty pointer-type or button lists disable that capability without restricting native text selection or touch gestures.

Component props

Prop Type Default Description
wheelCapture? CaptureInputDirections Capture wheel input in the enabled directions and emit onWheelInput. true accepts all directions. Omit or pass false to leave wheel input alone. Nested native wheel controls and Anta menus are excluded. All-false direction bounds preserve pointer settling while declining input. A listener alone never enables capture.
wheelActivation? hoversettledfocussettled-or-focus settled Pointer or focus condition required before wheel input can be captured. Focus applies only to input targeted within this Capture.
wheelModifier? noneanyaltctrlmetashift none Required modifier for wheel capture. none preserves browser Ctrl/pinch zoom.
wheelSettle? CaptureWheelSettle { delay: 150, tolerance: 5, resetOnMove: false } Dwell delay, movement tolerance, and whether movement resets eligibility.
onWheelInput? (event, detail) => void Accepted wheel input, with a serialized original event, Capture-relative geometry, focus state, and activation reason. Cancellation is already complete.
pointerCapture? booleanCapturePointerCapture Emit raw data for a primary pointer until release or cancellation. An options object filters devices/buttons and configures activation. Nested interactive controls are excluded unless explicitly included. A listener alone enables nothing.
onPointerInput? (event, detail) => void Start, movement, end, and cancellation of an opted-in pointer session.
pan? booleanCapturePan Emit custom pan motion. true enables touch panning on both axes without momentum. Options select devices, axes, bounds directions, and optional inertia. Captures the pointer internally; pointerCapture is not required. Sets CSS touch-action through attributes before the gesture starts.
onPanInput? (event, detail) => void Custom pan motion and its lifecycle. Inertial samples have no native pointer event.
Inherited props (children, className, id, slot, style, tabIndex, title)
Prop Type Default Description
children? ReactNode Child elements. When provided, replaces the component's default label/content.
className? string CSS class on the component's root element (merged with the component's own classes). Use it directly for layout and positioning — grid/flex placement, margins, alignment — rather than wrapping the component in a <div>/<span>.
id? string HTML id attribute.
slot? string Assigns the element to a named <slot> of a parent web component (e.g. slot="header" inside a <Card>, slot="footer" inside a <Dialog>).
style? CSSProperties Inline styles on the component's root element. Set layout/positioning here (or via className) directly on the component instead of adding a wrapper.
tabIndex? number Tab order. Set to -1 to skip the element when tabbing.
title? string HTML title attribute — native browser tooltip on hover.

Plus every standard DOM event handler (onClick, onFocus, onKeyDown, …) and any data-* or aria-* attribute — forwarded as-is to the underlying <a-*> element.

CaptureWheelSettle

Option Type Default Description
delay? number 150 Pointer dwell time in milliseconds.
tolerance? number 5 Maximum movement from the dwell anchor on either axis, in CSS pixels.
resetOnMove? boolean false Restart dwell after movement beyond tolerance, including after activation.

CapturePointerCapture

Option Type Default Description
pointerTypes? readonly CapturePointerType[] ['mouse', 'pen', 'touch'] Accepted pointer devices. One primary pointer is tracked per Capture.
buttons? readonly number[] [0] Accepted initiating buttons, using PointerEvent.button values.
threshold? number 0 Movement required before activation, in viewport CSS pixels.
modifier? noneanyaltctrlmetashift any Modifier required to start a capture session.
includeInteractive? boolean false Allow capture to start on nested native or ARIA controls, links, or editable regions.

CapturePan

Option Type Default Description
pointerTypes? readonly CapturePointerType[] ['touch'] Accepted pointer devices.
axis? xyboth both Axes handled by custom panning. CSS touch-action leaves the other axis to the browser.
threshold? number 3 Movement required before activation, in viewport CSS pixels.
directions? CaptureInputDirections true Allowed scroll directions. Updating these can stop motion at application bounds.
inertia? booleanCapturePanInertia false Continue panning after release with browser-side velocity decay.

CapturePanInertia

Option Type Default Description
timeConstant? number 325 Exponential velocity decay time constant in milliseconds.
minVelocity? number 0.02 Stop when speed on both axes falls below this value, in CSS pixels/ms.

CaptureWheelInput

Field Type Default Description
localX number Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels.
localY number
boxWidth number Dimensions of the same viewport bounding rectangle, in CSS pixels.
boxHeight number
inside boolean Whether the pointer is inside that rectangle. Capture can continue outside it.
focusWithin boolean
wheelEvent SerializedWheelEvent
activationReason immediatesettledfocus

CapturePointerInput

Field Type Default Description
localX number Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels.
localY number
boxWidth number Dimensions of the same viewport bounding rectangle, in CSS pixels.
boxHeight number
inside boolean Whether the pointer is inside that rectangle. Capture can continue outside it.
focusWithin boolean
phase startmoveendcancel
pointerEvent SerializedPointerEventnull Null when cancellation comes from lifecycle or configuration changes.
start CapturePointerStart
deltaX number Movement since the previous delivered sample, in viewport CSS pixels.
deltaY number
movementX number Total movement from the initial press, in viewport CSS pixels.
movementY number
activationReason pointer-downdrag-threshold
cancelReason? pointer-cancellost-capturedisableddisconnectedblurinterrupted

CapturePanInput

Field Type Default Description
localX number Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels.
localY number
boxWidth number Dimensions of the same viewport bounding rectangle, in CSS pixels.
boxHeight number
inside boolean Whether the pointer is inside that rectangle. Capture can continue outside it.
focusWithin boolean
phase startmovereleaseinertiaendcancel
pointerEvent SerializedPointerEventnull Null during momentum or cancellation without a pointer event.
start CapturePointerStart
deltaX number Incremental scroll motion in viewport CSS pixels.
deltaY number
velocityX number Scroll velocity in viewport CSS pixels/ms.
velocityY number
activationReason pointer-downdrag-threshold
cancelReason? pointer-cancellost-capturedisableddisconnectedblurinterrupted

Web component

Use <a-capture> without JSX. Events are non-bubbling CustomEvents with the same detail. Adding listeners enables no capabilities.

Import @antadesign/anta/elements/a-capture to register the element. A bare Capture leaves wheel and touch scrolling native:

Native scrolling stays enabled.

No wheel, pointer, or pan input is captured.

Scroll to the end of this content.

The browser owns this scroll position.

<a-capture class="native-capture">
<p>Native scrolling stays enabled.</p>
<p>No wheel, pointer, or pan input is captured.</p>
<p>Scroll to the end of this content.</p>
<p>The browser owns this scroll position.</p>
</a-capture>
.native-capture { width: 100%; height: 120px; overflow: auto; padding: 12px; border: 1px solid var(--border-4); border-radius: 8px; }
.native-capture p { margin: 0; padding-block: 12px; }

Configure capture with attributes such as wheel-capture="up down" or pan="y". Bare capability attributes match passing true in JSX. Remove an attribute to disable it. Listen for wheelinput, pointerinput, and paninput with addEventListener; each event exposes the same detail as the JSX handler.

Styling

Capture renders a block container with no shadow root. Use className or style for layout, size, and appearance. Keep a CSS box on the host so its bounds define the input surface and local coordinates.

Use Box around Capture when you also need measurements, context events, or edge fades. Box measures its own bounds; Capture reports coordinates relative to its own bounds.