@antadesign/anta 0.3.25 → 0.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +26 -28
  2. package/dist/anta_helpers.d.ts +4 -0
  3. package/dist/anta_helpers.js +17 -3
  4. package/dist/box-types.d.ts +161 -0
  5. package/dist/box-types.js +0 -0
  6. package/dist/bundle.css +1 -1
  7. package/dist/bundle.js +25 -25
  8. package/dist/capture-types.d.ts +156 -0
  9. package/dist/capture-types.js +0 -0
  10. package/dist/components/Box.d.ts +52 -0
  11. package/dist/components/Box.js +55 -0
  12. package/dist/components/Capture.d.ts +39 -0
  13. package/dist/components/Capture.js +36 -0
  14. package/dist/components/capture-props.d.ts +30 -0
  15. package/dist/components/capture-props.js +44 -0
  16. package/dist/elements/a-avatar.css +1 -1
  17. package/dist/elements/a-banner.css +1 -1
  18. package/dist/elements/a-banner.js +4 -4
  19. package/dist/elements/a-box.css +1 -0
  20. package/dist/elements/a-box.d.ts +41 -0
  21. package/dist/elements/a-box.js +584 -0
  22. package/dist/elements/a-button.css +1 -1
  23. package/dist/elements/a-calendar.js +2 -2
  24. package/dist/elements/a-capture.css +1 -0
  25. package/dist/elements/a-capture.d.ts +10 -0
  26. package/dist/elements/a-capture.js +24 -0
  27. package/dist/elements/a-card.css +1 -1
  28. package/dist/elements/a-checkbox.css +1 -1
  29. package/dist/elements/a-checkbox.js +4 -4
  30. package/dist/elements/a-dialog.css +1 -1
  31. package/dist/elements/a-expander.css +1 -1
  32. package/dist/elements/a-expander.js +2 -2
  33. package/dist/elements/a-input-time.css +1 -1
  34. package/dist/elements/a-input-time.js +3 -3
  35. package/dist/elements/a-input.css +1 -1
  36. package/dist/elements/a-input.js +6 -6
  37. package/dist/elements/a-menu-item.css +1 -1
  38. package/dist/elements/a-menu-item.js +3 -3
  39. package/dist/elements/a-menu.css +1 -1
  40. package/dist/elements/a-menu.js +2 -2
  41. package/dist/elements/a-radio-group.js +3 -3
  42. package/dist/elements/a-radio.css +1 -1
  43. package/dist/elements/a-slider.css +1 -1
  44. package/dist/elements/a-slider.js +6 -6
  45. package/dist/elements/a-switch.css +1 -1
  46. package/dist/elements/a-switch.js +2 -2
  47. package/dist/elements/a-tabpanel.js +2 -2
  48. package/dist/elements/a-tabs.css +1 -1
  49. package/dist/elements/a-tabs.js +6 -6
  50. package/dist/elements/a-tag.css +1 -1
  51. package/dist/elements/a-text.css +1 -1
  52. package/dist/elements/a-title.css +1 -1
  53. package/dist/elements/a-toast.js +2 -2
  54. package/dist/elements/a-tooltip.css +1 -1
  55. package/dist/elements/a-tooltip.d.ts +4 -3
  56. package/dist/elements/a-tooltip.js +9 -5
  57. package/dist/elements/capture-input.d.ts +5 -0
  58. package/dist/elements/capture-input.js +609 -0
  59. package/dist/elements/index.d.ts +2 -0
  60. package/dist/elements/index.js +6 -0
  61. package/dist/general_types.d.ts +70 -0
  62. package/dist/index.d.ts +6 -0
  63. package/dist/index.js +4 -0
  64. package/dist/jsx-runtime.d.ts +3 -1
  65. package/docs/accessibility.md +24 -0
  66. package/docs/changelog.md +45 -3
  67. package/docs/colors.md +141 -0
  68. package/docs/comparison.md +463 -28
  69. package/docs/components/avatar.md +364 -0
  70. package/docs/components/banner.md +13 -27
  71. package/docs/components/box.md +354 -0
  72. package/docs/components/breadcrumbs.md +7 -6
  73. package/docs/components/button.md +48 -37
  74. package/docs/components/capture.md +345 -0
  75. package/docs/components/card.md +17 -38
  76. package/docs/components/checkbox.md +16 -39
  77. package/docs/components/dialog.md +14 -36
  78. package/docs/components/expander.md +15 -40
  79. package/docs/components/icon.md +111 -9
  80. package/docs/components/input-autocomplete.md +9 -11
  81. package/docs/components/input-date.md +33 -23
  82. package/docs/components/input-time.md +15 -23
  83. package/docs/components/input.md +27 -76
  84. package/docs/components/loader.md +7 -5
  85. package/docs/components/menu.md +73 -77
  86. package/docs/components/progress.md +10 -14
  87. package/docs/components/radio.md +17 -36
  88. package/docs/components/select-faceted.md +7 -25
  89. package/docs/components/select.md +24 -97
  90. package/docs/components/slider.md +16 -21
  91. package/docs/components/steps.md +38 -8
  92. package/docs/components/switch.md +11 -14
  93. package/docs/components/tabs.md +52 -40
  94. package/docs/components/tag.md +11 -22
  95. package/docs/components/text.md +10 -23
  96. package/docs/components/title.md +7 -8
  97. package/docs/components/toaster.md +6 -3
  98. package/docs/components/tooltip.md +13 -23
  99. package/docs/index.md +6 -3
  100. package/docs/install-config.md +24 -0
  101. package/docs/normalization.md +45 -0
  102. package/docs/packages/stickers.md +56 -0
  103. package/docs/theming.md +270 -0
  104. package/package.json +3 -1
@@ -0,0 +1,345 @@
1
+ # Capture
2
+
3
+ `Capture` is a light-DOM input surface for custom tables, plots, and drag
4
+ interactions. Wheel capture, pointer capture, and panning are all disabled by default.
5
+
6
+ ## Usage
7
+
8
+ Each capability is opt-in; handlers alone enable nothing. Choose by interaction:
9
+
10
+ | Interaction | Capability | Your component handles |
11
+ | --- | --- | --- |
12
+ | Mouse wheel or trackpad scrolling | `wheelCapture` | Scroll offsets or zoom |
13
+ | Touchscreen drag to scroll | `pan` | Scroll offsets from pan deltas |
14
+ | Drag to select, resize, or draw | `pointerCapture` | Raw pointer samples |
15
+
16
+ Disabled Capture surfaces add no input state or listeners.
17
+
18
+ Capture cancels accepted native input on the browser thread, then sends plain data
19
+ to your handler. Your component performs the action. Forward the serializable
20
+ `detail` across worker boundaries. Handler return values cannot change cancellation.
21
+
22
+ For a 2D table, combine `wheelCapture` and `pan` for scrolling with
23
+ `pointerCapture={{ pointerTypes: ['mouse', 'pen'] }}` for selection. Each
24
+ handler updates the corresponding scroll or selection state.
25
+
26
+ ## Wheel ownership
27
+
28
+ Set `wheelCapture` to `true` for all directions, or update allowed directions
29
+ from your component's bounds. Declined input reaches the enclosing editor or
30
+ scroll container. The dominant axis decides ownership; both deltas are delivered.
31
+ All-false bounds preserve settling while declining input. Passing `false` or
32
+ removing `wheelCapture` disables capture and clears settling.
33
+
34
+ Enable **Capture wheel**, then scroll over the rows. The controls let you try
35
+ different activation and settling rules.
36
+
37
+ ```tsx title="Custom table wheel"
38
+ const [offset, setOffset] = useState(0)
39
+ const maxOffset = 28 * 32 - 192
40
+
41
+ // These settings come from the preview controls.
42
+ <div onKeyDown={event => {
43
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
44
+ event.preventDefault()
45
+ const delta = event.key === 'ArrowDown' ? 32 : -32
46
+ setOffset(value => Math.max(0, Math.min(maxOffset, value + delta)))
47
+ }}>
48
+ <Capture
49
+ wheelCapture={enabled && { up: offset > 0, down: offset < maxOffset }}
50
+ wheelActivation={activation}
51
+ wheelSettle={{ delay, tolerance: 5, resetOnMove }}
52
+ tabIndex={0}
53
+ aria-label="Wheel capture surface"
54
+ style={{ height: 192, overflow: 'hidden' }}
55
+ onWheelInput={(_, { wheelEvent, boxHeight }) => {
56
+ const unit = wheelEvent.deltaMode === 1 ? 32 : wheelEvent.deltaMode === 2 ? boxHeight : 1
57
+ setOffset(value => Math.max(0, Math.min(maxOffset, value + wheelEvent.deltaY * unit)))
58
+ }}
59
+ >
60
+ <div style={{ transform: `translateY(${-offset}px)` }}>
61
+ {Array.from({ length: 28 }, (_, index) => (
62
+ <div key={index} style={{ height: 32 }}>Sample {index + 1}</div>
63
+ ))}
64
+ </div>
65
+ </Capture>
66
+ </div>
67
+ ```
68
+
69
+ The default `"settled"` activation waits for the pointer to rest inside Capture
70
+ (150ms, 5px tolerance). It stays active until the pointer leaves.
71
+ `wheelSettle.resetOnMove` requires settling again after movement beyond tolerance.
72
+ Scrolling a Capture under a stationary pointer does not activate it.
73
+
74
+ Use `"hover"` for immediate activation, `"focus"` for `:focus-within`, or
75
+ `"settled-or-focus"` for either condition. Wheel input must still target Capture.
76
+ Capture never takes focus automatically; provide `tabIndex` or a focusable child.
77
+
78
+ Modified input stays native by default, including Ctrl/pinch zoom. Use
79
+ `wheelModifier="ctrl"` to capture Ctrl-wheel, or `"any"` for all modifiers.
80
+ Named modifiers allow other keys too.
81
+
82
+ The innermost eligible Capture handles wheel input on its host. Canceled and
83
+ non-cancelable events are ignored; Capture cannot undo an ancestor's capture-phase
84
+ handler. Capture skips nested `textarea`, `select`, numeric/range inputs, and
85
+ Anta menus. Mark other native scroll panes with `data-capture-ignore`.
86
+
87
+ `detail.wheelEvent` preserves native event data, signs, and `deltaMode` units
88
+ (0: pixels, 1: lines, 2: pages). Capture does not infer OS scrolling preferences.
89
+ `localX` and `localY` are CSS pixels from Capture's viewport rectangle's top-left,
90
+ without undoing transforms. Native `offsetX` and `offsetY` refer to the original
91
+ target, which may be a child.
92
+
93
+ ## Pointer sessions
94
+
95
+ `pointerCapture` sends raw data for one primary mouse, pen, or touch pointer,
96
+ including movement outside Capture. Your component interprets the gesture.
97
+ Use `threshold` to delay capture until dragging starts. Drag an area in the
98
+ preview, or select **Select sample area** with the keyboard.
99
+
100
+ ```tsx title="Mouse or pen selection"
101
+ type Selection = { x: number; y: number; width: number; height: number }
102
+ const [selection, setSelection] = useState<Selection | null>(null)
103
+
104
+ <Button label="Select sample area" onClick={() => setSelection({ x: 24, y: 24, width: 100, height: 64 })} />
105
+ <Button label="Clear selection" onClick={() => setSelection(null)} />
106
+ <Capture
107
+ className="pointer-probe-surface"
108
+ aria-label="Pointer selection surface"
109
+ pointerCapture={{ pointerTypes: ['mouse', 'pen'], threshold: 3 }}
110
+ onPointerInput={(_, detail) => {
111
+ if (detail.phase === 'cancel') return
112
+ const x = Math.max(0, Math.min(detail.boxWidth, detail.localX))
113
+ const y = Math.max(0, Math.min(detail.boxHeight, detail.localY))
114
+ setSelection({
115
+ x: Math.min(detail.start.localX, x), y: Math.min(detail.start.localY, y),
116
+ width: Math.abs(x - detail.start.localX), height: Math.abs(y - detail.start.localY),
117
+ })
118
+ }}
119
+ >
120
+ <Text size="small" priority="tertiary">Drag to select an area.</Text>
121
+ {selection && <div className="pointer-probe-selection"
122
+ style={{ left: selection.x, top: selection.y, width: selection.width, height: selection.height }} />}
123
+ </Capture>
124
+ ```
125
+
126
+ ```css
127
+ .pointer-probe-surface { position: relative; height: 160px; overflow: hidden; border: 1px solid var(--border-4); border-radius: 8px; background: var(--bg-canvas); }
128
+ .pointer-probe-surface a-text { display: block; padding: 12px; }
129
+ .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); }
130
+ ```
131
+
132
+ `onPointerInput` reports `start`, `move`, `end`, and `cancel`. `deltaX/Y` are
133
+ incremental movement; `movementX/Y` are totals from the press, positive right/down.
134
+
135
+ Presses below threshold emit nothing. Captured gestures suppress the following
136
+ pointer-generated click. Cancellation reports `cancelReason` on pointer
137
+ cancellation, lost capture, disabling, removal, or window blur/hiding.
138
+ Lifecycle cancellation sets `pointerEvent: null`.
139
+ Capture cancels native behavior without stopping pointer-event propagation.
140
+
141
+ Pointer and pan capture skip nested native/ARIA controls, links, and editable content.
142
+ `pointerCapture.includeInteractive` includes them. Use `data-capture-ignore`
143
+ to exclude a subtree from all capture, including wheel. It does not remove
144
+ ancestor `touch-action` restrictions.
145
+
146
+ ## Touch panning and inertia
147
+
148
+ `pan` converts touchscreen dragging into scroll deltas without inertia. It
149
+ captures the pointer internally; it does not require `pointerCapture`. Set
150
+ `pan.pointerTypes` to enable mouse or pen panning. If your component already
151
+ interprets touch gestures, use `pointerCapture` for touch and omit `pan`.
152
+
153
+ Enabling both for the same pointer type emits both streams. Assign separate
154
+ pointer types when each gesture should have one handler.
155
+
156
+ Try touch panning, or enable **Pan with mouse**. **Inertia** continues motion
157
+ after release; turn it off to stop when the pointer lifts.
158
+
159
+ ```tsx title="Custom touch scrolling"
160
+ const [offset, setOffset] = useState(0)
161
+ const maxOffset = 18 * 32 - 160
162
+ const move = (delta: number) => setOffset(value => Math.max(0, Math.min(maxOffset, value + delta)))
163
+
164
+ // mouse and inertia come from the preview checkboxes.
165
+ <div onKeyDown={event => {
166
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
167
+ event.preventDefault()
168
+ move(event.key === 'ArrowDown' ? 32 : -32)
169
+ }}>
170
+ <Capture
171
+ className="pan-probe-surface"
172
+ tabIndex={0}
173
+ aria-label="Pan capture surface"
174
+ pan={{
175
+ axis: 'y',
176
+ pointerTypes: mouse ? ['touch', 'mouse'] : ['touch'],
177
+ directions: { up: offset > 0, down: offset < maxOffset },
178
+ inertia,
179
+ }}
180
+ onPanInput={(_, { deltaY }) => move(deltaY)}
181
+ >
182
+ <div style={{ transform: `translateY(${-offset}px)` }}>
183
+ {Array.from({ length: 18 }, (_, index) => (
184
+ <div className="pan-probe-row" key={index}>Sample {index + 1}</div>
185
+ ))}
186
+ </div>
187
+ </Capture>
188
+ </div>
189
+ ```
190
+
191
+ ```css
192
+ .pan-probe-surface { height: 160px; overflow: hidden; border: 1px solid var(--border-4); border-radius: 8px; background: var(--bg-canvas); }
193
+ .pan-probe-row { height: 32px; box-sizing: border-box; padding: 6px 12px; border-bottom: 1px solid var(--border-5); }
194
+ ```
195
+
196
+ Pan deltas oppose finger movement. Apply deltas from every phase, including
197
+ `release`. Inertia samples have `pointerEvent: null`. Motion stops on new input,
198
+ disabled directions, or cancellation.
199
+
200
+ Capture sets CSS `touch-action` before contact. Single-axis pan leaves the other
201
+ axis and pinch zoom to the browser. Both-axis pan or touch pointer capture owns
202
+ the whole gesture; mouse-only capture leaves touch unchanged. Unlike wheel,
203
+ an active touch gesture cannot return to native scrolling at a bound. Update
204
+ directions to stop custom motion and control the next gesture.
205
+ Empty pointer-type or button lists disable that capability without restricting
206
+ native text selection or touch gestures.
207
+
208
+ ## Component props
209
+
210
+ | Prop | Type | Default | Description |
211
+ |------|------|---------|-------------|
212
+ | `onPanInput?` | (event, detail) => void | — | Custom pan motion and its lifecycle. Inertial samples have no native pointer event. |
213
+ | `onPointerInput?` | (event, detail) => void | — | Start, movement, end, and cancellation of an opted-in pointer session. |
214
+ | `onWheelInput?` | (event, detail) => void | — | Accepted wheel input, with a serialized original event, Capture-relative geometry, focus state, and activation reason. Cancellation is already complete. |
215
+ | `pan?` | boolean \| CapturePan | — | 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. |
216
+ | `pointerCapture?` | boolean \| CapturePointerCapture | — | 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. |
217
+ | `wheelActivation?` | CaptureWheelActivation | settled | Pointer or focus condition required before wheel input can be captured. Focus applies only to input targeted within this Capture. |
218
+ | `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. |
219
+ | `wheelModifier?` | CaptureInputModifier | none | Required modifier for wheel capture. `none` preserves browser Ctrl/pinch zoom. |
220
+ | `wheelSettle?` | CaptureWheelSettle | { delay: 150, tolerance: 5, resetOnMove: false } | Dwell delay, movement tolerance, and whether movement resets eligibility. |
221
+
222
+ ### CaptureWheelSettle
223
+
224
+ | Option | Type | Default | Description |
225
+ |------|------|---------|-------------|
226
+ | `delay?` | number | 150 | Pointer dwell time in milliseconds. |
227
+ | `resetOnMove?` | boolean | false | Restart dwell after movement beyond tolerance, including after activation. |
228
+ | `tolerance?` | number | 5 | Maximum movement from the dwell anchor on either axis, in CSS pixels. |
229
+
230
+ ### CapturePointerCapture
231
+
232
+ | Option | Type | Default | Description |
233
+ |------|------|---------|-------------|
234
+ | `buttons?` | readonly number[] | [0] | Accepted initiating buttons, using PointerEvent.button values. |
235
+ | `includeInteractive?` | boolean | false | Allow capture to start on nested native or ARIA controls, links, or editable regions. |
236
+ | `modifier?` | CaptureInputModifier | any | Modifier required to start a capture session. |
237
+ | `pointerTypes?` | readonly CapturePointerType[] | ['mouse', 'pen', 'touch'] | Accepted pointer devices. One primary pointer is tracked per Capture. |
238
+ | `threshold?` | number | 0 | Movement required before activation, in viewport CSS pixels. |
239
+
240
+ ### CapturePan
241
+
242
+ | Option | Type | Default | Description |
243
+ |------|------|---------|-------------|
244
+ | `axis?` | 'x' \| 'y' \| 'both' | both | Axes handled by custom panning. CSS touch-action leaves the other axis to the browser. |
245
+ | `directions?` | CaptureInputDirections | true | Allowed scroll directions. Updating these can stop motion at application bounds. |
246
+ | `inertia?` | boolean \| CapturePanInertia | false | Continue panning after release with browser-side velocity decay. |
247
+ | `pointerTypes?` | readonly CapturePointerType[] | ['touch'] | Accepted pointer devices. |
248
+ | `threshold?` | number | 3 | Movement required before activation, in viewport CSS pixels. |
249
+
250
+ ### CapturePanInertia
251
+
252
+ | Option | Type | Default | Description |
253
+ |------|------|---------|-------------|
254
+ | `minVelocity?` | number | 0.02 | Stop when speed on both axes falls below this value, in CSS pixels/ms. |
255
+ | `timeConstant?` | number | 325 | Exponential velocity decay time constant in milliseconds. |
256
+
257
+ ### CaptureWheelInput
258
+
259
+ | Field | Type | Default | Description |
260
+ |------|------|---------|-------------|
261
+ | `activationReason` | CaptureWheelActivationReason | — | |
262
+ | `boxHeight` | number | — | |
263
+ | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
264
+ | `focusWithin` | boolean | — | |
265
+ | `inside` | boolean | — | Whether the pointer is inside that rectangle. Capture can continue outside it. |
266
+ | `localX` | number | — | Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels. |
267
+ | `localY` | number | — | |
268
+ | `wheelEvent` | SerializedWheelEvent | — | |
269
+
270
+ ### CapturePointerInput
271
+
272
+ | Field | Type | Default | Description |
273
+ |------|------|---------|-------------|
274
+ | `activationReason` | CapturePointerActivationReason | — | |
275
+ | `boxHeight` | number | — | |
276
+ | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
277
+ | `deltaX` | number | — | Movement since the previous delivered sample, in viewport CSS pixels. |
278
+ | `deltaY` | number | — | |
279
+ | `focusWithin` | boolean | — | |
280
+ | `inside` | boolean | — | Whether the pointer is inside that rectangle. Capture can continue outside it. |
281
+ | `localX` | number | — | Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels. |
282
+ | `localY` | number | — | |
283
+ | `movementX` | number | — | Total movement from the initial press, in viewport CSS pixels. |
284
+ | `movementY` | number | — | |
285
+ | `phase` | 'start' \| 'move' \| 'end' \| 'cancel' | — | |
286
+ | `pointerEvent` | SerializedPointerEvent \| null | — | Null when cancellation comes from lifecycle or configuration changes. |
287
+ | `start` | CapturePointerStart | — | |
288
+ | `cancelReason?` | CaptureInputCancelReason | — | |
289
+
290
+ ### CapturePanInput
291
+
292
+ | Field | Type | Default | Description |
293
+ |------|------|---------|-------------|
294
+ | `activationReason` | CapturePointerActivationReason | — | |
295
+ | `boxHeight` | number | — | |
296
+ | `boxWidth` | number | — | Dimensions of the same viewport bounding rectangle, in CSS pixels. |
297
+ | `deltaX` | number | — | Incremental scroll motion in viewport CSS pixels. |
298
+ | `deltaY` | number | — | |
299
+ | `focusWithin` | boolean | — | |
300
+ | `inside` | boolean | — | Whether the pointer is inside that rectangle. Capture can continue outside it. |
301
+ | `localX` | number | — | Pointer position relative to the viewport bounding rectangle's top-left, in CSS pixels. |
302
+ | `localY` | number | — | |
303
+ | `phase` | 'start' \| 'move' \| 'release' \| 'inertia' \| 'end' \| 'cancel' | — | |
304
+ | `pointerEvent` | SerializedPointerEvent \| null | — | Null during momentum or cancellation without a pointer event. |
305
+ | `start` | CapturePointerStart | — | |
306
+ | `velocityX` | number | — | Scroll velocity in viewport CSS pixels/ms. |
307
+ | `velocityY` | number | — | |
308
+ | `cancelReason?` | CaptureInputCancelReason | — | |
309
+
310
+ ## Web component
311
+
312
+ Use `<a-capture>` without JSX. Events are non-bubbling `CustomEvent`s
313
+ with the same `detail`. Adding listeners enables no capabilities.
314
+
315
+ Import `@antadesign/anta/elements/a-capture` to register the element. A bare
316
+ Capture leaves wheel and touch scrolling native:
317
+
318
+ ```html title="a-capture"
319
+ <a-capture class="native-capture">
320
+ <p>Native scrolling stays enabled.</p>
321
+ <p>No wheel, pointer, or pan input is captured.</p>
322
+ <p>Scroll to the end of this content.</p>
323
+ <p>The browser owns this scroll position.</p>
324
+ </a-capture>
325
+ ```
326
+
327
+ ```css
328
+ .native-capture { width: 100%; height: 120px; overflow: auto; padding: 12px; border: 1px solid var(--border-4); border-radius: 8px; }
329
+ .native-capture p { margin: 0; padding-block: 12px; }
330
+ ```
331
+
332
+ Configure capture with attributes such as `wheel-capture="up down"` or `pan="y"`.
333
+ Bare capability attributes match passing `true` in JSX. Remove an attribute to
334
+ disable it. Listen for `wheelinput`, `pointerinput`, and `paninput` with
335
+ `addEventListener`; each event exposes the same `detail` as the JSX handler.
336
+
337
+ ## Styling
338
+
339
+ Capture renders a block container with no shadow root. Use `className` or
340
+ `style` for layout, size, and appearance. Keep a CSS box on the host so its
341
+ bounds define the input surface and local coordinates.
342
+
343
+ Use [Box](./box.md) around Capture when you also need measurements, context events,
344
+ or edge fades. Box measures its own bounds; Capture reports coordinates relative
345
+ to its own bounds.
@@ -206,52 +206,29 @@ it also blocks navigation until the load resolves.
206
206
  </Card>
207
207
  ```
208
208
 
209
- ### Props
209
+ ## Component props
210
210
 
211
211
  | Prop | Type | Default | Description |
212
212
  |------|------|---------|-------------|
213
- | `children?` | ReactNode | — | The card body. A string is wrapped in a `<Text>` at secondary priority,
214
- following the card's `tone` and `size`; pass your own nodes to take over. |
215
- | `footer?` | ReactNode | — | Footer content, usually action buttons. Rendered as a **left-aligned** row
216
- (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. |
217
- | `header?` | ReactNode | — | Header content, in the top zone. A string is wrapped in a `<Title>` — a real
218
- heading in the document outline, tinted to match `tone`, at a level that
219
- tracks `size` (small → 5, medium → 4, large → 3). Pass your own node
220
- (`<Title level={2}>`, an `<h2>`) for a different level or styling. Omit for
221
- no header. |
222
- | `href?` | string | — | Turn the whole card into a link. The card renders a focusable anchor and its
223
- accessible name comes from `header` → body → this URL (override with
224
- `aria-label`). A link card is display content — don't nest interactive
225
- controls inside it. |
226
- | `icon?` | (string & {}) \| string \| number \| bigint \| boolean \| ReactElement \| Iterable \| ReactPortal \| Promise \| null | — | Leading visual for the header, laid out to the left of the `header` /
227
- `subtitle` as one aligned row (so it doesn't sit inline in the title, where it
228
- mis-aligns). A **string** is an icon shape, rendered as an `<Icon>` inside a
229
- circular chip (sized per the card via `--card-icon-size`); pass an `<img>`,
230
- initials (`<Text>`), or any node to use it as-is. Omit for none. |
231
- | `loading?` | boolean | — | Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in
232
- link mode) blocks navigation. |
233
- | `media?` | ReactNode | — | Media (image, illustration) rendered full-bleed to the card edge, clipped to
234
- its corners. Position it with `mediaPosition`. |
213
+ | `children?` | ReactNode | — | The card body. A string is wrapped in a `<Text>` at secondary priority, following the card's `tone` and `size`; pass your own nodes to take over. |
214
+ | `footer?` | ReactNode | — | Footer content, usually action buttons. Rendered as a **left-aligned** row (wraps under pressure) — the opposite of `Dialog`'s right-aligned footer. |
215
+ | `header?` | ReactNode | — | Header content, in the top zone. A string is wrapped in a `<Title>` — a real heading in the document outline, tinted to match `tone`, at a level that tracks `size` (small → 5, medium → 4, large → 3). Pass your own node (`<Title level={2}>`, an `<h2>`) for a different level or styling. Omit for no header. |
216
+ | `href?` | string | — | Turn the whole card into a link. The card renders a focusable anchor and its accessible name comes from `header` → body → this URL (override with `aria-label`). A link card is display content — don't nest interactive controls inside it. |
217
+ | `icon?` | (string & {}) \| string \| number \| bigint \| boolean \| ReactElement \| Iterable \| ReactPortal \| Promise \| null | — | Leading visual for the header, laid out to the left of the `header` / `subtitle` as one aligned row (so it doesn't sit inline in the title, where it mis-aligns). A **string** is an icon shape, rendered as an `<Icon>` inside a circular chip (sized per the card via `--card-icon-size`); pass an `<img>`, initials (`<Text>`), or any node to use it as-is. Omit for none. |
218
+ | `loading?` | boolean | — | Loading state — dims the card with a skeleton pulse, sets `aria-busy`, and (in link mode) blocks navigation. |
219
+ | `media?` | ReactNode | — | Media (image, illustration) rendered full-bleed to the card edge, clipped to its corners. Position it with `mediaPosition`. |
235
220
  | `mediaPosition?` | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Which edge the `media` bleeds to. |
236
221
  | `ping?` | string | — | Space-separated URLs the browser pings on navigation (only with `href`). |
237
- | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'primary' | Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a
238
- subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop
239
- blur. |
222
+ | `priority?` | 'primary' \| 'secondary' \| 'tertiary' | 'primary' | Surface emphasis. `primary` (the default) is a clean sheet; `secondary` is a subtle fill; `tertiary` is a frosted, semi-transparent panel with a backdrop blur. |
240
223
  | `rel?` | string | — | Anchor rel (only with `href`). |
241
- | `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
242
- `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for
243
- the default `--card-radius`. |
244
- | `selected?` | boolean | — | Selected / chosen state — draws an inset ring in the tone color, for
245
- choice-card / plan-picker patterns. |
224
+ | `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a `number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit for the default `--card-radius`. |
225
+ | `selected?` | boolean | — | Selected / chosen state — draws an inset ring in the tone color, for choice-card / plan-picker patterns. |
246
226
  | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant — scales the padding. |
247
- | `subtitle?` | ReactNode | — | A secondary line under the header title, rendered as smaller text
248
- (`<Text size="small">`) in the heading section. Tinted to match `tone`, like
249
- the title. Omit for no subtitle. |
227
+ | `subtitle?` | ReactNode | — | A secondary line under the header title, rendered as smaller text (`<Text size="small">`) in the heading section. Tinted to match `tone`, like the title. Omit for no subtitle. |
250
228
  | `target?` | string | — | Anchor target (only with `href`). |
251
- | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for
252
- a one-off custom tone. Named tones re-point the surface + text; a custom
253
- color keeps its hue while lightness/chroma are pinned. `'neutral'` (the
254
- default) is the same as omitting it. |
229
+ | `tone?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Semantic tone, or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone. Named tones re-point the surface + text; a custom color keeps its hue while lightness/chroma are pinned. `'neutral'` (the default) is the same as omitting it. |
230
+
231
+ ## Web Component
255
232
 
256
233
  Use the web component directly when you are not using React or Preact and a native control does not fit.
257
234
 
@@ -267,6 +244,8 @@ Slots hold media, header, and footer content. Unslotted content is the card body
267
244
  </a-card>
268
245
  ```
269
246
 
247
+ ## Styling
248
+
270
249
  Reach for the props first: **`tone`** sets the color (any CSS color for a custom
271
250
  tone — it derives the surface in oklch), **`priority`** the fill, **`size`** the
272
251
  padding. The one custom-color knob worth keeping is **`--card-tone-source`** (the
@@ -167,52 +167,27 @@ The wrapper sets `role="checkbox"` and `aria-checked` (`"true"` / `"false"` / `"
167
167
 
168
168
  Space toggles the checkbox. Follows the [WAI-ARIA checkbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/).
169
169
 
170
- ### Props
170
+ ## Component props
171
171
 
172
172
  | Prop | Type | Default | Description |
173
173
  |------|------|---------|-------------|
174
- | `checked?` | CheckboxValue | — | Controlled checked state. When provided the checkbox is controlled — it
175
- renders exactly this and never self-applies; `onStateChange` is a *request*
176
- the consumer accepts by updating this prop. Use `defaultChecked` for an
177
- uncontrolled checkbox. `'indeterminate'` shows the minus glyph and takes
178
- visual precedence; clicking it requests `true`. |
179
- | `defaultChecked?` | CheckboxValue | false | Initial checked state for an uncontrolled checkbox. Read once; later
180
- changes are ignored and the element updates its state after interaction. |
174
+ | `checked?` | CheckboxValue | — | Controlled checked state. When provided the checkbox is controlled — it renders exactly this and never self-applies; `onStateChange` is a *request* the consumer accepts by updating this prop. Use `defaultChecked` for an uncontrolled checkbox. `'indeterminate'` shows the minus glyph and takes visual precedence; clicking it requests `true`. |
175
+ | `defaultChecked?` | CheckboxValue | false | Initial checked state for an uncontrolled checkbox. Read once; later changes are ignored and the element updates its state after interaction. |
181
176
  | `disabled?` | boolean | — | Disable the checkbox (no interaction, dropped from the tab order). |
182
- | `hint?` | ReactNode | — | Secondary text rendered under the label — explanatory copy, like
183
- Input's hint. Not part of the accessible name. |
184
- | `label?` | string | — | Visible label — the *value* of the checkbox (clicked along with the box).
185
- Convenience for the common single-string case; for richer content (markup,
186
- a link, an info icon) use `children`. When both are supplied, `label`
187
- renders first. Required unless `children` or `aria-label` is provided
188
- (a `role="checkbox"` takes its name from the author, not the markup). |
189
- | `name?` | string | — | Form field name. Inside a `<form>` the checkbox submits under this name,
190
- contributing `value` when checked — like a native checkbox. |
191
- | `onChange?` | (event) => void | — | Fired *after* the checked state changes — a native `change` event (the
192
- post-apply counterpart to `onStateChange`). Not cancelable. For a controlled
193
- checkbox this fires once you've updated `checked`. |
194
- | `onStateChange?` | (event, detail) => void | — | Fired on click / Space *before* the element applies any change. Event-first
195
- so `event.preventDefault()` is the synchronous veto (uncontrolled mode);
196
- `detail` carries `{ next, prev }`. In controlled mode the element never
197
- self-applies — answer by updating `checked`, reject by doing nothing. |
198
- | `onValueChange?` | (event, attrs) => void | — | Like `onChange`, but with a `{ checked, indeterminate, name, value }` snapshot
199
- as the second argument, matching `Input`'s `onValueChange`. |
200
- | `round?` | boolean \| number \| string | — | Round the checkbox mark to a circle (`border-radius: 999px` on the box). Pass
201
- a `number` (px) or a CSS length string for a rounded-square mark instead. |
177
+ | `hint?` | ReactNode | — | Secondary text rendered under the label — explanatory copy, like Input's hint. Not part of the accessible name. |
178
+ | `label?` | string | — | Visible label — the *value* of the checkbox (clicked along with the box). Convenience for the common single-string case; for richer content (markup, a link, an info icon) use `children`. When both are supplied, `label` renders first. Required unless `children` or `aria-label` is provided (a `role="checkbox"` takes its name from the author, not the markup). |
179
+ | `name?` | string | — | Form field name. Inside a `<form>` the checkbox submits under this name, contributing `value` when checked — like a native checkbox. |
180
+ | `onChange?` | (event) => void | — | Fired *after* the checked state changes — a native `change` event (the post-apply counterpart to `onStateChange`). Not cancelable. For a controlled checkbox this fires once you've updated `checked`. |
181
+ | `onStateChange?` | (event, detail) => void | — | Fired on click / Space *before* the element applies any change. Event-first so `event.preventDefault()` is the synchronous veto (uncontrolled mode); `detail` carries `{ next, prev }`. In controlled mode the element never self-applies — answer by updating `checked`, reject by doing nothing. |
182
+ | `onValueChange?` | (event, attrs) => void | — | Like `onChange`, but with a `{ checked, indeterminate, name, value }` snapshot as the second argument, matching `Input`'s `onValueChange`. |
183
+ | `round?` | boolean \| number \| string | — | Round the checkbox mark to a circle (`border-radius: 999px` on the box). Pass a `number` (px) or a CSS length string for a rounded-square mark instead. |
202
184
  | `size?` | 'small' \| 'medium' \| 'large' | 'medium' | Size variant. small=14px, medium=16px, large=18px box. |
203
- | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the **mark** in every state — the checked-box fill *and* the
204
- unselected box border. A named tone or any literal CSS color (`'#ff1493'`,
205
- `'rebeccapurple'`) for a one-off custom tone. Named tones track light/dark mode
206
- automatically; a custom color keeps its hue + chroma and pins lightness to the
207
- fill curve. Use `toneSelected` instead to tone only the checked mark and leave
208
- the empty box neutral. The label + hint stay neutral — recolor them in plain
209
- CSS via the theme-aware `--text-N-{tone}` tokens. |
210
- | `toneSelected?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Like `tone`, but colored onto the **checked mark only** — the empty box stays
211
- neutral grey until it's checked. Prefer this over `tone` when a resting tinted
212
- border would read as a validation state. Same value set as `tone`; if both are
213
- set, `tone` governs the off-state border and `toneSelected` the checked fill. |
185
+ | `tone?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Color of the **mark** in every state — the checked-box fill *and* the unselected box border. A named tone or any literal CSS color (`'#ff1493'`, `'rebeccapurple'`) for a one-off custom tone. Named tones track light/dark mode automatically; a custom color keeps its hue + chroma and pins lightness to the fill curve. Use `toneSelected` instead to tone only the checked mark and leave the empty box neutral. The label + hint stay neutral — recolor them in plain CSS via the theme-aware `--text-N-{tone}` tokens. |
186
+ | `toneSelected?` | 'brand' \| 'neutral' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | 'neutral' | Like `tone`, but colored onto the **checked mark only** — the empty box stays neutral grey until it's checked. Prefer this over `tone` when a resting tinted border would read as a validation state. Same value set as `tone`; if both are set, `tone` governs the off-state border and `toneSelected` the checked fill. |
214
187
  | `value?` | string | "on" | Value submitted with the form when checked — like a native checkbox. |
215
188
 
189
+ ## Web Component
190
+
216
191
  Use the web component directly when you are not using React or Preact and a native control does not fit.
217
192
 
218
193
  The focusable host carries the state. Label and hint are light-DOM children.
@@ -241,6 +216,8 @@ treatments.
241
216
  </div>
242
217
  ```
243
218
 
219
+ ## Styling
220
+
244
221
  Reach for the props first: **`tone`** colors the mark in every state,
245
222
  **`toneSelected`** only the checked mark (any CSS color for a custom tone — it
246
223
  derives the full rest/hover/active curve in oklch), **`size`** the dimensions + type.
@@ -203,47 +203,23 @@ On close, focus returns to whatever opened the dialog, from the native `<dialog>
203
203
  </Dialog>
204
204
  ```
205
205
 
206
- ### Props
206
+ ## Component props
207
207
 
208
208
  | Prop | Type | Default | Description |
209
209
  |------|------|---------|-------------|
210
210
  | `children?` | ReactNode | — | The dialog body. Scrolls when it overflows the available height. |
211
- | `closable?` | boolean | true | Whether the top-right ✕ button is present. It's one way to close the dialog,
212
- alongside Esc, the backdrop, a `data-dialog-close` / footer action, and your
213
- own code; `false` removes the ✕ without making the dialog unclosable. |
211
+ | `closable?` | boolean | true | Whether the top-right ✕ button is present. It's one way to close the dialog, alongside Esc, the backdrop, a `data-dialog-close` / footer action, and your own code; `false` removes the ✕ without making the dialog unclosable. |
214
212
  | `defaultOpen?` | boolean | — | Initial open state for the uncontrolled case (read once on mount). |
215
- | `footer?` | ReactNode | — | Footer content, usually the action buttons. Rendered in the bottom zone as
216
- a right-aligned row (wraps under pressure). Omit for none. |
217
- | `header?` | ReactNode | — | Header content: a title, or any node. Rendered in the top zone. Omit for a
218
- chromeless dialog with only a body. |
219
- | `name?` | string | — | Uncontrolled trigger name. Any element with `data-dialog-open="{name}"`
220
- opens this dialog, `data-dialog-close="{name}"` closes it. A convenience for
221
- triggers rendered elsewhere; ignored in controlled mode. |
222
- | `onStateChange?` | (event, detail) => void | — | Fired before the open state changes, on every user open or dismiss.
223
- `event` is the cancelable `statechange`: call `event.preventDefault()` to
224
- veto an *uncontrolled* transition. `detail.next` is the requested open
225
- state, `detail.prev` the current one (booleans). In controlled mode, apply
226
- `detail.next` to `open` to accept, or do nothing to reject. |
227
- | `open?` | boolean | — | Controlled open state. When provided, the consumer owns open/close: the
228
- dialog only follows this prop, and every user dismiss (Esc, backdrop, close
229
- button) *requests* a change via `onStateChange` (reject by not updating).
230
- Leave undefined for uncontrolled. |
231
- | `persistent?` | boolean | — | Turn off light-dismiss: a backdrop click and Esc no longer close the dialog.
232
- It stays closable through explicit controls (the ✕ when `closable`, a footer
233
- action, your own code); `persistent` isn't "un-closable", it stops an
234
- accidental click or stray Esc from dismissing. For an alert / confirm that
235
- should be answered deliberately. Omit for the default dismissible behavior. |
236
- | `position?` | 'center' \| 'left' \| 'right' \| 'top' \| 'bottom' \| 'fullscreen' | 'center' | Placement. `center` (the default) is a centered modal; `left` / `right` /
237
- `top` / `bottom` turn it into an edge drawer (full height for left/right,
238
- full width for top/bottom); `fullscreen` fills the whole viewport with no
239
- edge gap or corner radius. |
240
- | `round?` | boolean \| number \| 'top' \| 'right' \| 'bottom' \| 'left' \| (string & {}) | — | Corner radius. A side keyword — `'top'` / `'right'` / `'bottom'` / `'left'` —
241
- rounds only that edge's two corners (at `--dialog-radius`), which pairs with
242
- a drawer's exposed edge (a bottom sheet → `'top'`, a right drawer → `'left'`).
243
- A `number` (px) or CSS string rounds all corners at that value; the string
244
- may be a full `border-radius` shorthand (`'12px 12px 0 0'`). `true` rounds all
245
- corners at `--dialog-radius`. Omit for the position default: `center` is
246
- rounded, drawers and `fullscreen` are square. |
213
+ | `footer?` | ReactNode | — | Footer content, usually the action buttons. Rendered in the bottom zone as a right-aligned row (wraps under pressure). Omit for none. |
214
+ | `header?` | ReactNode | — | Header content: a title, or any node. Rendered in the top zone. Omit for a chromeless dialog with only a body. |
215
+ | `name?` | string | — | Uncontrolled trigger name. Any element with `data-dialog-open="{name}"` opens this dialog, `data-dialog-close="{name}"` closes it. A convenience for triggers rendered elsewhere; ignored in controlled mode. |
216
+ | `onStateChange?` | (event, detail) => void | — | Fired before the open state changes, on every user open or dismiss. `event` is the cancelable `statechange`: call `event.preventDefault()` to veto an *uncontrolled* transition. `detail.next` is the requested open state, `detail.prev` the current one (booleans). In controlled mode, apply `detail.next` to `open` to accept, or do nothing to reject. |
217
+ | `open?` | boolean | — | Controlled open state. When provided, the consumer owns open/close: the dialog only follows this prop, and every user dismiss (Esc, backdrop, close button) *requests* a change via `onStateChange` (reject by not updating). Leave undefined for uncontrolled. |
218
+ | `persistent?` | boolean | — | Turn off light-dismiss: a backdrop click and Esc no longer close the dialog. It stays closable through explicit controls (the ✕ when `closable`, a footer action, your own code); `persistent` isn't "un-closable", it stops an accidental click or stray Esc from dismissing. For an alert / confirm that should be answered deliberately. Omit for the default dismissible behavior. |
219
+ | `position?` | 'center' \| 'left' \| 'right' \| 'top' \| 'bottom' \| 'fullscreen' | 'center' | Placement. `center` (the default) is a centered modal; `left` / `right` / `top` / `bottom` turn it into an edge drawer (full height for left/right, full width for top/bottom); `fullscreen` fills the whole viewport with no edge gap or corner radius. |
220
+ | `round?` | boolean \| number \| 'top' \| 'right' \| 'bottom' \| 'left' \| (string & {}) | — | Corner radius. A side keyword — `'top'` / `'right'` / `'bottom'` / `'left'` — rounds only that edge's two corners (at `--dialog-radius`), which pairs with a drawer's exposed edge (a bottom sheet → `'top'`, a right drawer → `'left'`). A `number` (px) or CSS string rounds all corners at that value; the string may be a full `border-radius` shorthand (`'12px 12px 0 0'`). `true` rounds all corners at `--dialog-radius`. Omit for the position default: `center` is rounded, drawers and `fullscreen` are square. |
221
+
222
+ ## Web Component
247
223
 
248
224
  Use the web component directly when you are not using React or Preact and a native control does not fit.
249
225
 
@@ -262,6 +238,8 @@ raw `<a-button>`.
262
238
  </a-dialog>
263
239
  ```
264
240
 
241
+ ## Styling
242
+
265
243
  Reach for the props first: **`position`** (modal, drawer edge, or fullscreen),
266
244
  **`closable`** (the ✕), **`persistent`** (light-dismiss off), **`round`** (corner
267
245
  radius). For everything else, style the shadow **parts** in plain CSS.