@astryxdesign/core 0.6.0 → 0.6.1
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.
- package/CHANGELOG.md +41 -3
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
- package/dist/Center/Center.d.ts +23 -16
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +7 -5
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +12 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -0
- package/dist/Field/InputClearButton.d.ts +2 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +16 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +12 -1
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Indicator/CheckboxIndicator.js +2 -2
- package/dist/Indicator/RadioIndicator.js +2 -2
- package/dist/Layer/layerStack.d.ts +10 -0
- package/dist/Layer/layerStack.d.ts.map +1 -1
- package/dist/Layer/layerStack.js +21 -9
- package/dist/Layer/useLayerDismissal.d.ts +2 -3
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
- package/dist/Layer/useLayerDismissal.js +2 -3
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +12 -2
- package/dist/Popover/usePopover.d.ts +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +4 -2
- package/dist/ProgressBar/ProgressBar.js +2 -2
- package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
- package/dist/ScrollableArea/ScrollableArea.js +144 -0
- package/dist/ScrollableArea/index.d.ts +11 -0
- package/dist/ScrollableArea/index.d.ts.map +1 -0
- package/dist/ScrollableArea/index.js +11 -0
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +19 -4
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +12 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +56 -20
- package/dist/astryx.css +15 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scrollGeometry.d.ts +24 -0
- package/dist/hooks/scrollGeometry.d.ts.map +1 -0
- package/dist/hooks/scrollGeometry.js +86 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
- package/dist/hooks/scrollOwnerRegistry.js +24 -0
- package/dist/hooks/useFocusTrap.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +22 -11
- package/dist/hooks/useScrollableArea.d.ts +51 -0
- package/dist/hooks/useScrollableArea.d.ts.map +1 -0
- package/dist/hooks/useScrollableArea.js +287 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +2 -6
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/derivedVarRegistry.js +1 -1
- package/dist/theme/localTokens.d.ts +8 -11
- package/dist/theme/localTokens.d.ts.map +1 -1
- package/dist/theme/localTokens.js +17 -71
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +4 -4
- package/dist/utils/themeProps.d.ts +10 -10
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +27 -10
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -2
- package/src/AppShell/AppShell.test.tsx +36 -0
- package/src/AppShell/AppShell.tsx +4 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +3 -1
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
- package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
- package/src/Center/Center.doc.mjs +32 -28
- package/src/Center/Center.spec.md +225 -0
- package/src/Center/Center.test.tsx +42 -4
- package/src/Center/Center.tsx +24 -17
- package/src/Chat/ChatSystemMessage.test.tsx +2 -9
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +4 -4
- package/src/DateInput/DateInput.tsx +15 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
- package/src/DateTimeInput/DateTimeInput.tsx +18 -7
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
- package/src/Field/Field.test.tsx +42 -0
- package/src/Field/Field.tsx +6 -0
- package/src/Field/InputClearButton.test.tsx +35 -1
- package/src/Field/InputClearButton.tsx +7 -3
- package/src/Field/PanelSearchInput.tsx +21 -8
- package/src/FieldStatus/FieldStatus.spec.md +27 -17
- package/src/FieldStatus/FieldStatus.test.tsx +7 -5
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
- package/src/FileInput/FileInput.tsx +10 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +4 -4
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Indicator/CheckboxIndicator.tsx +2 -2
- package/src/Indicator/Indicator.doc.mjs +2 -2
- package/src/Indicator/Indicator.test.tsx +1 -1
- package/src/Indicator/RadioIndicator.tsx +2 -2
- package/src/Layer/layerStack.ts +20 -9
- package/src/Layer/useLayerDismissal.ts +2 -3
- package/src/MultiSelector/MultiSelector.test.tsx +4 -4
- package/src/NavIcon/NavIcon.doc.mjs +4 -4
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +18 -7
- package/src/Popover/Popover.doc.mjs +10 -10
- package/src/Popover/Popover.spec.md +55 -65
- package/src/Popover/Popover.test.tsx +29 -0
- package/src/Popover/usePopover.doc.mjs +4 -4
- package/src/Popover/usePopover.tsx +7 -4
- package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
- package/src/ProgressBar/ProgressBar.test.tsx +1 -31
- package/src/ProgressBar/ProgressBar.tsx +2 -2
- package/src/RadioList/RadioList.test.tsx +5 -144
- package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
- package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
- package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
- package/src/ScrollableArea/ScrollableArea.tsx +259 -0
- package/src/ScrollableArea/index.ts +26 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
- package/src/Selector/Selector.test.tsx +4 -4
- package/src/Spinner/Spinner.test.tsx +0 -18
- package/src/StatusDot/StatusDot.doc.mjs +4 -4
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/TabList/TabList.test.tsx +5 -9
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
- package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
- package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
- package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
- package/src/Table/Table.doc.mjs +2 -2
- package/src/TextArea/TextArea.doc.mjs +4 -4
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.doc.mjs +2 -1
- package/src/TextInput/TextInput.test.tsx +94 -0
- package/src/TextInput/TextInput.tsx +22 -6
- package/src/TimeInput/TimeInput.tsx +18 -7
- package/src/Toast/ToastViewport.test.tsx +1 -39
- package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
- package/src/Typeahead/BaseTypeahead.spec.md +269 -0
- package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
- package/src/Typeahead/BaseTypeahead.tsx +99 -30
- package/src/hooks/index.ts +13 -0
- package/src/hooks/scrollGeometry.ts +155 -0
- package/src/hooks/scrollOwnerRegistry.ts +47 -0
- package/src/hooks/useFocusTrap.ts +22 -11
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
- package/src/hooks/useScrollableArea.doc.mjs +108 -0
- package/src/hooks/useScrollableArea.test.tsx +437 -0
- package/src/hooks/useScrollableArea.ts +469 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +65 -105
- package/src/theme/defineTheme.ts +3 -9
- package/src/theme/derivedVarRegistry.ts +1 -1
- package/src/theme/localTokens.ts +25 -96
- package/src/theme/publicThemeHelperContract.test.ts +2 -2
- package/src/theme/themeAdaptations.test.ts +16 -42
- package/src/theme/themeAdaptations.ts +6 -9
- package/src/utils/themeProps.test.ts +29 -10
- package/src/utils/themeProps.ts +36 -17
|
@@ -38,6 +38,7 @@ import {TypeaheadItem} from './TypeaheadItem';
|
|
|
38
38
|
import {Icon} from '../Icon';
|
|
39
39
|
import {Spinner} from '../Spinner';
|
|
40
40
|
import {
|
|
41
|
+
borderVars,
|
|
41
42
|
colorVars,
|
|
42
43
|
spacingVars,
|
|
43
44
|
radiusVars,
|
|
@@ -45,7 +46,13 @@ import {
|
|
|
45
46
|
fontWeightVars,
|
|
46
47
|
typeScaleVars,
|
|
47
48
|
} from '../theme/tokens.stylex';
|
|
48
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
characterCount,
|
|
51
|
+
composeEventHandlers,
|
|
52
|
+
getKey,
|
|
53
|
+
groupItems,
|
|
54
|
+
mergeProps,
|
|
55
|
+
} from '../utils';
|
|
49
56
|
import type {BaseProps} from '../BaseProps';
|
|
50
57
|
import type {SearchableItem, SearchSource} from './types';
|
|
51
58
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -98,7 +105,7 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
98
105
|
*/
|
|
99
106
|
maxMenuItems?: number;
|
|
100
107
|
|
|
101
|
-
/**
|
|
108
|
+
/** Requested dropdown width in pixels before viewport clamping. */
|
|
102
109
|
menuWidth?: number;
|
|
103
110
|
|
|
104
111
|
/**
|
|
@@ -107,8 +114,8 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
107
114
|
* for a result set that cannot be meaningful yet — and the user does not
|
|
108
115
|
* see "no results" for a query that was never searched.
|
|
109
116
|
*
|
|
110
|
-
* Measured
|
|
111
|
-
*
|
|
117
|
+
* Measured by grapheme cluster, so one visible character counts once even
|
|
118
|
+
* when JavaScript represents it with multiple UTF-16 code units.
|
|
112
119
|
*
|
|
113
120
|
* @default 1 — every non-empty query is searched.
|
|
114
121
|
*/
|
|
@@ -130,8 +137,10 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
130
137
|
* When disabled with a reason, keeps the input focusable via `aria-disabled`
|
|
131
138
|
* (instead of the native `disabled` attribute) and `readOnly` so an
|
|
132
139
|
* associated disabled-reason tooltip stays discoverable by keyboard and
|
|
133
|
-
* assistive technology.
|
|
134
|
-
*
|
|
140
|
+
* assistive technology. Query and text mutation are blocked, but an
|
|
141
|
+
* already-open highlighted option can still be selected with Enter after a
|
|
142
|
+
* transition into this state. Consumers (Typeahead) own the tooltip and
|
|
143
|
+
* wrapper.
|
|
135
144
|
* @default false
|
|
136
145
|
*/
|
|
137
146
|
isFocusableDisabled?: boolean;
|
|
@@ -182,17 +191,20 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
182
191
|
debounceMs?: number;
|
|
183
192
|
|
|
184
193
|
/**
|
|
185
|
-
*
|
|
194
|
+
* Legacy input-specific alias for the native `id` prop. When provided, this
|
|
195
|
+
* alias takes precedence; otherwise the native prop is preserved.
|
|
186
196
|
*/
|
|
187
197
|
inputId?: string;
|
|
188
198
|
|
|
189
199
|
/**
|
|
190
|
-
*
|
|
200
|
+
* Legacy input-specific alias for native `aria-describedby`. When provided,
|
|
201
|
+
* this alias takes precedence; otherwise the native prop is preserved.
|
|
191
202
|
*/
|
|
192
203
|
ariaDescribedBy?: string;
|
|
193
204
|
|
|
194
205
|
/**
|
|
195
|
-
*
|
|
206
|
+
* Legacy input-specific alias for native `aria-labelledby`. When provided,
|
|
207
|
+
* this alias takes precedence; otherwise the native prop is preserved.
|
|
196
208
|
*/
|
|
197
209
|
ariaLabelledBy?: string;
|
|
198
210
|
|
|
@@ -202,12 +214,13 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
202
214
|
inputXStyle?: StyleXStyles;
|
|
203
215
|
|
|
204
216
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
217
|
+
* Legacy input-specific alias for native `tabIndex`. When provided, this
|
|
218
|
+
* alias takes precedence; otherwise the native prop is preserved. Typeahead
|
|
219
|
+
* passes `-1` while its selected-value token is shown: the input is visually
|
|
220
|
+
* collapsed (width 0 / opacity 0) but must stay programmatically focusable
|
|
221
|
+
* for token edit/clear interactions, so removing it from the Tab order is
|
|
222
|
+
* what prevents an invisible tab stop (WCAG 2.4.3 / 2.4.7). The input remains
|
|
223
|
+
* focusable via `.focus()` regardless of this value.
|
|
211
224
|
*/
|
|
212
225
|
inputTabIndex?: number;
|
|
213
226
|
|
|
@@ -236,6 +249,10 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
236
249
|
// Styles
|
|
237
250
|
// =============================================================================
|
|
238
251
|
|
|
252
|
+
const TYPEAHEAD_VIEWPORT_GUTTER = spacingVars['--spacing-4'];
|
|
253
|
+
const TYPEAHEAD_POSITION_AREA_MAX_INLINE_SIZE = `calc(100% - max(${TYPEAHEAD_VIEWPORT_GUTTER}, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)))`;
|
|
254
|
+
const TYPEAHEAD_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK = `calc(100% - ${TYPEAHEAD_VIEWPORT_GUTTER})`;
|
|
255
|
+
|
|
239
256
|
const styles = stylex.create({
|
|
240
257
|
input: {
|
|
241
258
|
display: 'block',
|
|
@@ -267,7 +284,12 @@ const styles = stylex.create({
|
|
|
267
284
|
padding: spacingVars['--spacing-1'],
|
|
268
285
|
},
|
|
269
286
|
popover: {
|
|
287
|
+
boxSizing: 'border-box',
|
|
270
288
|
minWidth: 'anchor-size(width)',
|
|
289
|
+
maxInlineSize: stylex.firstThatWorks(
|
|
290
|
+
TYPEAHEAD_POSITION_AREA_MAX_INLINE_SIZE,
|
|
291
|
+
TYPEAHEAD_POSITION_AREA_MAX_INLINE_SIZE_FALLBACK,
|
|
292
|
+
),
|
|
271
293
|
},
|
|
272
294
|
popoverCustomWidth: (width: number) => ({
|
|
273
295
|
width: `${width}px`,
|
|
@@ -299,6 +321,18 @@ const styles = stylex.create({
|
|
|
299
321
|
},
|
|
300
322
|
itemHighlighted: {
|
|
301
323
|
backgroundColor: colorVars['--color-overlay-hover'],
|
|
324
|
+
outlineColor: {
|
|
325
|
+
default: null,
|
|
326
|
+
'@media (forced-colors: active)': 'Highlight',
|
|
327
|
+
},
|
|
328
|
+
outlineStyle: {
|
|
329
|
+
default: null,
|
|
330
|
+
'@media (forced-colors: active)': 'solid',
|
|
331
|
+
},
|
|
332
|
+
outlineWidth: {
|
|
333
|
+
default: null,
|
|
334
|
+
'@media (forced-colors: active)': borderVars['--border-width'],
|
|
335
|
+
},
|
|
302
336
|
},
|
|
303
337
|
itemSelected: {
|
|
304
338
|
fontWeight: fontWeightVars['--font-weight-medium'],
|
|
@@ -307,6 +341,11 @@ const styles = stylex.create({
|
|
|
307
341
|
display: 'flex',
|
|
308
342
|
flex: 1,
|
|
309
343
|
minWidth: 0,
|
|
344
|
+
overflow: 'hidden',
|
|
345
|
+
},
|
|
346
|
+
defaultItem: {
|
|
347
|
+
minWidth: 0,
|
|
348
|
+
width: '100%',
|
|
310
349
|
},
|
|
311
350
|
emptyState: {
|
|
312
351
|
padding: spacingVars['--spacing-3'],
|
|
@@ -355,7 +394,8 @@ const itemSizeStyles = stylex.create({
|
|
|
355
394
|
* untouched state, and `hasEntriesOnFocus` owns what happens there.
|
|
356
395
|
*/
|
|
357
396
|
function isBelowMinQueryLength(query: string, minQueryLength: number): boolean {
|
|
358
|
-
|
|
397
|
+
const length = characterCount(query);
|
|
398
|
+
return length > 0 && length < minQueryLength;
|
|
359
399
|
}
|
|
360
400
|
|
|
361
401
|
// =============================================================================
|
|
@@ -376,6 +416,7 @@ function isBelowMinQueryLength(query: string, minQueryLength: number): boolean {
|
|
|
376
416
|
* searchSource={source}
|
|
377
417
|
* value={selected}
|
|
378
418
|
* onChange={setSelected}
|
|
419
|
+
* aria-label="Search frameworks"
|
|
379
420
|
* anchorRef={wrapperRef}
|
|
380
421
|
* placeholder="Search..."
|
|
381
422
|
* />
|
|
@@ -407,7 +448,18 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
407
448
|
onKeyDown: externalOnKeyDown,
|
|
408
449
|
debounceMs = 150,
|
|
409
450
|
size = 'md',
|
|
451
|
+
xstyle,
|
|
452
|
+
className,
|
|
453
|
+
style,
|
|
454
|
+
onPointerDown: onPointerDownProp,
|
|
455
|
+
onFocus: onFocusProp,
|
|
456
|
+
onBlur: onBlurProp,
|
|
457
|
+
id: nativeInputId,
|
|
458
|
+
'aria-describedby': nativeAriaDescribedBy,
|
|
459
|
+
'aria-labelledby': nativeAriaLabelledBy,
|
|
460
|
+
tabIndex: nativeInputTabIndex,
|
|
410
461
|
ref,
|
|
462
|
+
...rest
|
|
411
463
|
}: BaseTypeaheadProps<T>) {
|
|
412
464
|
const t = useTranslator();
|
|
413
465
|
const placeholder =
|
|
@@ -416,7 +468,12 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
416
468
|
emptySearchResultsTextFromProps ??
|
|
417
469
|
t('@astryx.typeahead.emptySearchResults');
|
|
418
470
|
const generatedId = useId();
|
|
419
|
-
|
|
471
|
+
// Keep the released input-specific aliases authoritative when a caller uses
|
|
472
|
+
// them, but do not let an omitted alias erase the equivalent native BaseProp.
|
|
473
|
+
const inputId = externalInputId ?? nativeInputId ?? generatedId;
|
|
474
|
+
const inputAriaDescribedBy = ariaDescribedBy ?? nativeAriaDescribedBy;
|
|
475
|
+
const inputAriaLabelledBy = ariaLabelledBy ?? nativeAriaLabelledBy;
|
|
476
|
+
const resolvedInputTabIndex = inputTabIndex ?? nativeInputTabIndex;
|
|
420
477
|
const listboxId = useId();
|
|
421
478
|
|
|
422
479
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -943,6 +1000,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
943
1000
|
return (
|
|
944
1001
|
<>
|
|
945
1002
|
<input
|
|
1003
|
+
{...rest}
|
|
946
1004
|
ref={useMergedRefs(ref, inputRef, fallbackAnchorRef)}
|
|
947
1005
|
id={inputId}
|
|
948
1006
|
type="text"
|
|
@@ -958,13 +1016,13 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
958
1016
|
}
|
|
959
1017
|
aria-autocomplete="list"
|
|
960
1018
|
aria-busy={isLoading || undefined}
|
|
961
|
-
aria-describedby={
|
|
962
|
-
aria-labelledby={
|
|
1019
|
+
aria-describedby={inputAriaDescribedBy}
|
|
1020
|
+
aria-labelledby={inputAriaLabelledBy}
|
|
963
1021
|
aria-disabled={isFocusableDisabled ? 'true' : undefined}
|
|
964
|
-
tabIndex={
|
|
1022
|
+
tabIndex={resolvedInputTabIndex}
|
|
965
1023
|
value={query}
|
|
966
1024
|
onChange={handleInputChange}
|
|
967
|
-
onPointerDown={() => {
|
|
1025
|
+
onPointerDown={composeEventHandlers(() => {
|
|
968
1026
|
pointerActiveRef.current = true;
|
|
969
1027
|
document.addEventListener(
|
|
970
1028
|
'click',
|
|
@@ -973,23 +1031,29 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
973
1031
|
},
|
|
974
1032
|
{once: true},
|
|
975
1033
|
);
|
|
976
|
-
}}
|
|
977
|
-
onFocus={handleFocus}
|
|
978
|
-
onBlur={handleBlur}
|
|
1034
|
+
}, onPointerDownProp)}
|
|
1035
|
+
onFocus={composeEventHandlers(handleFocus, onFocusProp)}
|
|
1036
|
+
onBlur={composeEventHandlers(handleBlur, onBlurProp)}
|
|
979
1037
|
onKeyDown={handleKeyDown}
|
|
980
1038
|
placeholder={placeholder}
|
|
981
1039
|
// When a disabled-reason tooltip is shown the input keeps focusability
|
|
982
1040
|
// via aria-disabled + readOnly instead of the native disabled
|
|
983
|
-
// attribute
|
|
1041
|
+
// attribute. Query and text mutation are blocked, but an already-open
|
|
1042
|
+
// highlighted option can still be selected with Enter after transition.
|
|
984
1043
|
disabled={isDisabled && !isFocusableDisabled}
|
|
985
1044
|
readOnly={isFocusableDisabled || undefined}
|
|
986
1045
|
autoFocus={hasAutoFocus}
|
|
987
1046
|
data-autofocus={hasAutoFocus || undefined}
|
|
988
1047
|
autoComplete="off"
|
|
989
|
-
{...
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1048
|
+
{...mergeProps(
|
|
1049
|
+
stylex.props(
|
|
1050
|
+
styles.input,
|
|
1051
|
+
isDisabled && styles.inputDisabled,
|
|
1052
|
+
inputXStyle,
|
|
1053
|
+
xstyle,
|
|
1054
|
+
),
|
|
1055
|
+
className,
|
|
1056
|
+
style,
|
|
993
1057
|
)}
|
|
994
1058
|
/>
|
|
995
1059
|
{isLoading && busyLane == null && (
|
|
@@ -1008,6 +1072,8 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
1008
1072
|
)}>
|
|
1009
1073
|
{results.length === 0 && hasSearched ? (
|
|
1010
1074
|
<div
|
|
1075
|
+
role="option"
|
|
1076
|
+
aria-disabled="true"
|
|
1011
1077
|
{...mergeProps(
|
|
1012
1078
|
themeProps('typeahead-empty-state'),
|
|
1013
1079
|
stylex.props(styles.emptyState),
|
|
@@ -1040,7 +1106,10 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
1040
1106
|
{renderItem ? (
|
|
1041
1107
|
renderItem(item)
|
|
1042
1108
|
) : (
|
|
1043
|
-
<TypeaheadItem
|
|
1109
|
+
<TypeaheadItem
|
|
1110
|
+
item={item}
|
|
1111
|
+
xstyle={styles.defaultItem}
|
|
1112
|
+
/>
|
|
1044
1113
|
)}
|
|
1045
1114
|
</span>
|
|
1046
1115
|
{isSelected && (
|
package/src/hooks/index.ts
CHANGED
|
@@ -56,6 +56,19 @@ export type {UseOverflowOptions, UseOverflowReturn} from './useOverflow';
|
|
|
56
56
|
export {useScrollOverflow} from './useScrollOverflow';
|
|
57
57
|
export type {ScrollOverflowState} from './useScrollOverflow';
|
|
58
58
|
|
|
59
|
+
export {useScrollableArea} from './useScrollableArea';
|
|
60
|
+
export type {
|
|
61
|
+
ScrollAxis,
|
|
62
|
+
ScrollAxisState,
|
|
63
|
+
ScrollOverscroll,
|
|
64
|
+
ScrollStickyContainment,
|
|
65
|
+
ScrollKeyboardAccess,
|
|
66
|
+
ScrollableAreaState,
|
|
67
|
+
ScrollableElementProps,
|
|
68
|
+
UseScrollableAreaOptions,
|
|
69
|
+
UseScrollableAreaResult,
|
|
70
|
+
} from './useScrollableArea';
|
|
71
|
+
|
|
59
72
|
export {useScrollLock} from './useScrollLock';
|
|
60
73
|
|
|
61
74
|
export {useEntryAnimation} from './useEntryAnimation';
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** Shared geometry primitives for logical-axis scroll ownership. */
|
|
4
|
+
|
|
5
|
+
export const SCROLL_OVERFLOW_TOLERANCE = 1;
|
|
6
|
+
|
|
7
|
+
export type LogicalScrollAxis = 'inline' | 'block';
|
|
8
|
+
export type PhysicalScrollAxis = 'x' | 'y';
|
|
9
|
+
|
|
10
|
+
export interface LogicalAxisMapping {
|
|
11
|
+
inline: PhysicalScrollAxis;
|
|
12
|
+
block: PhysicalScrollAxis;
|
|
13
|
+
inlineReversed: boolean;
|
|
14
|
+
blockReversed: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MeasuredScrollAxisState {
|
|
18
|
+
isScrollable: boolean;
|
|
19
|
+
atStart: boolean;
|
|
20
|
+
atEnd: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LogicalOverflowGeometry {
|
|
24
|
+
inline: boolean;
|
|
25
|
+
block: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const INACTIVE_AXIS_STATE: MeasuredScrollAxisState = {
|
|
29
|
+
isScrollable: false,
|
|
30
|
+
atStart: true,
|
|
31
|
+
atEnd: true,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function getLogicalAxisMapping(
|
|
35
|
+
writingMode: string,
|
|
36
|
+
direction: string,
|
|
37
|
+
): LogicalAxisMapping {
|
|
38
|
+
const mode = writingMode.toLowerCase();
|
|
39
|
+
const isVertical = mode.startsWith('vertical') || mode.startsWith('sideways');
|
|
40
|
+
const isRtl = direction === 'rtl';
|
|
41
|
+
|
|
42
|
+
if (!isVertical) {
|
|
43
|
+
return {
|
|
44
|
+
inline: 'x',
|
|
45
|
+
block: 'y',
|
|
46
|
+
inlineReversed: isRtl,
|
|
47
|
+
blockReversed: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// sideways-lr runs its natural inline direction from bottom to top. The
|
|
52
|
+
// other vertical/sideways modes run top to bottom; `direction` reverses each.
|
|
53
|
+
const inlineReversed = mode === 'sideways-lr' ? !isRtl : isRtl;
|
|
54
|
+
const blockReversed = mode.endsWith('-rl');
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
inline: 'y',
|
|
58
|
+
block: 'x',
|
|
59
|
+
inlineReversed,
|
|
60
|
+
blockReversed,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isScrollCapableOverflow(value: string): boolean {
|
|
65
|
+
return value === 'auto' || value === 'scroll' || value === 'overlay';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function physicalMetrics(
|
|
69
|
+
element: HTMLElement,
|
|
70
|
+
physicalAxis: PhysicalScrollAxis,
|
|
71
|
+
): {
|
|
72
|
+
clientExtent: number;
|
|
73
|
+
contentExtent: number;
|
|
74
|
+
offset: number;
|
|
75
|
+
overflow: string;
|
|
76
|
+
} {
|
|
77
|
+
const computedStyle = getComputedStyle(element);
|
|
78
|
+
if (physicalAxis === 'x') {
|
|
79
|
+
return {
|
|
80
|
+
clientExtent: element.clientWidth,
|
|
81
|
+
contentExtent: element.scrollWidth,
|
|
82
|
+
offset: element.scrollLeft,
|
|
83
|
+
overflow: computedStyle.overflowX,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
clientExtent: element.clientHeight,
|
|
88
|
+
contentExtent: element.scrollHeight,
|
|
89
|
+
offset: element.scrollTop,
|
|
90
|
+
overflow: computedStyle.overflowY,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isMeasurable(
|
|
95
|
+
element: HTMLElement,
|
|
96
|
+
computedStyle: CSSStyleDeclaration,
|
|
97
|
+
) {
|
|
98
|
+
return (
|
|
99
|
+
element.isConnected &&
|
|
100
|
+
computedStyle.display !== 'none' &&
|
|
101
|
+
element.clientWidth > 0 &&
|
|
102
|
+
element.clientHeight > 0
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function measureLogicalOverflowGeometry(
|
|
107
|
+
element: HTMLElement,
|
|
108
|
+
logicalAxis: LogicalScrollAxis,
|
|
109
|
+
mapping: LogicalAxisMapping,
|
|
110
|
+
): boolean | null {
|
|
111
|
+
const computedStyle = getComputedStyle(element);
|
|
112
|
+
if (!isMeasurable(element, computedStyle)) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const physicalAxis = mapping[logicalAxis];
|
|
117
|
+
const clientExtent =
|
|
118
|
+
physicalAxis === 'x' ? element.clientWidth : element.clientHeight;
|
|
119
|
+
const contentExtent =
|
|
120
|
+
physicalAxis === 'x' ? element.scrollWidth : element.scrollHeight;
|
|
121
|
+
return contentExtent - clientExtent > SCROLL_OVERFLOW_TOLERANCE;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function measureLogicalScrollAxis(
|
|
125
|
+
element: HTMLElement,
|
|
126
|
+
logicalAxis: LogicalScrollAxis,
|
|
127
|
+
mapping: LogicalAxisMapping,
|
|
128
|
+
): MeasuredScrollAxisState | null {
|
|
129
|
+
const computedStyle = getComputedStyle(element);
|
|
130
|
+
if (!isMeasurable(element, computedStyle)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const physicalAxis = mapping[logicalAxis];
|
|
135
|
+
const metrics = physicalMetrics(element, physicalAxis);
|
|
136
|
+
const maxOffset = metrics.contentExtent - metrics.clientExtent;
|
|
137
|
+
if (
|
|
138
|
+
!isScrollCapableOverflow(metrics.overflow) ||
|
|
139
|
+
maxOffset <= SCROLL_OVERFLOW_TOLERANCE
|
|
140
|
+
) {
|
|
141
|
+
return INACTIVE_AXIS_STATE;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const isReversed = mapping[`${logicalAxis}Reversed`];
|
|
145
|
+
const logicalOffset = Math.min(
|
|
146
|
+
maxOffset,
|
|
147
|
+
Math.max(0, isReversed ? Math.abs(metrics.offset) : metrics.offset),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
isScrollable: true,
|
|
152
|
+
atStart: logicalOffset <= SCROLL_OVERFLOW_TOLERANCE,
|
|
153
|
+
atEnd: logicalOffset >= maxOffset - SCROLL_OVERFLOW_TOLERANCE,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** Private DOM registry used by future axis-aware Sticky consumers. */
|
|
4
|
+
|
|
5
|
+
export interface RegisteredScrollAxisState {
|
|
6
|
+
isScrollable: boolean;
|
|
7
|
+
atStart: boolean;
|
|
8
|
+
atEnd: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RegisteredScrollOwnerState {
|
|
12
|
+
inline: RegisteredScrollAxisState;
|
|
13
|
+
block: RegisteredScrollAxisState;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const owners = new WeakMap<HTMLElement, RegisteredScrollOwnerState>();
|
|
17
|
+
|
|
18
|
+
export function registerScrollOwner(
|
|
19
|
+
element: HTMLElement,
|
|
20
|
+
state: RegisteredScrollOwnerState,
|
|
21
|
+
): void {
|
|
22
|
+
owners.set(element, state);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function unregisterScrollOwner(element: HTMLElement): void {
|
|
26
|
+
owners.delete(element);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getRegisteredScrollOwnerState(
|
|
30
|
+
element: HTMLElement,
|
|
31
|
+
): RegisteredScrollOwnerState | undefined {
|
|
32
|
+
return owners.get(element);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function findNearestScrollOwner(
|
|
36
|
+
element: Element,
|
|
37
|
+
axis: 'inline' | 'block',
|
|
38
|
+
): HTMLElement | null {
|
|
39
|
+
let candidate = element.parentElement;
|
|
40
|
+
while (candidate != null) {
|
|
41
|
+
if (owners.get(candidate)?.[axis].isScrollable === true) {
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
candidate = candidate.parentElement;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useFocusTrap.ts
|
|
7
7
|
* @input Uses React useCallback, useEffect, useRef
|
|
8
|
-
* @output Exports useFocusTrap
|
|
9
|
-
*
|
|
8
|
+
* @output Exports useFocusTrap, the deprecated Escape-state shim, and an
|
|
9
|
+
* internal compatibility-signal hook
|
|
10
10
|
* @position Core hook; used by dialogs, modals, date pickers
|
|
11
11
|
*
|
|
12
12
|
* Based on WAI-ARIA dialog pattern:
|
|
@@ -41,6 +41,25 @@ export function hasActiveFocusTrapEscape(): boolean {
|
|
|
41
41
|
return activeEscapeTrapCount > 0;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Keep the deprecated `hasActiveFocusTrapEscape` signal accurate for a focus
|
|
46
|
+
* trap whose Escape is coordinated by another registration in the shared
|
|
47
|
+
* stack. This hook is intentionally internal (not re-exported from `hooks`): it
|
|
48
|
+
* preserves the released read-only compatibility signal without creating a
|
|
49
|
+
* second dismissal owner.
|
|
50
|
+
*/
|
|
51
|
+
export function useFocusTrapEscapeCompatibilitySignal(isActive: boolean): void {
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!isActive) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
activeEscapeTrapCount += 1;
|
|
57
|
+
return () => {
|
|
58
|
+
activeEscapeTrapCount -= 1;
|
|
59
|
+
};
|
|
60
|
+
}, [isActive]);
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
/**
|
|
45
64
|
* Whether an element is currently perceivable/focusable — excludes ones hidden
|
|
46
65
|
* via `display:none`/`visibility:hidden` or inside an `inert`/`hidden` subtree,
|
|
@@ -213,15 +232,7 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
|
|
|
213
232
|
getContainer: () => containerRef.current,
|
|
214
233
|
});
|
|
215
234
|
|
|
216
|
-
|
|
217
|
-
if (!isEscapeTrap) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
activeEscapeTrapCount += 1;
|
|
221
|
-
return () => {
|
|
222
|
-
activeEscapeTrapCount -= 1;
|
|
223
|
-
};
|
|
224
|
-
}, [isEscapeTrap]);
|
|
235
|
+
useFocusTrapEscapeCompatibilitySignal(isEscapeTrap);
|
|
225
236
|
|
|
226
237
|
/**
|
|
227
238
|
* Focus the first focusable element.
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* @position Testing; guards the deprecated public shim in useFocusTrap.ts
|
|
10
10
|
*
|
|
11
11
|
* The shim is public API. Its answer must not change now that Escape moved to
|
|
12
|
-
* the shared stack:
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* the shared stack: existing consumers still observe whether an
|
|
13
|
+
* Escape-participating focus trap is active. Families that never trapped focus
|
|
14
|
+
* stay out of this count; BottomSheetSwitcher preserves its historical modal
|
|
15
|
+
* signal without registering a second dismissal owner.
|
|
15
16
|
*
|
|
16
17
|
* SYNC: When useFocusTrap.ts changes, update tests to match new behavior
|
|
17
18
|
*/
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'useScrollableArea',
|
|
6
|
+
displayName: 'useScrollableArea',
|
|
7
|
+
keywords: [
|
|
8
|
+
'scroll',
|
|
9
|
+
'overflow',
|
|
10
|
+
'logical axis',
|
|
11
|
+
'keyboard',
|
|
12
|
+
'overscroll',
|
|
13
|
+
'sticky',
|
|
14
|
+
'resize',
|
|
15
|
+
],
|
|
16
|
+
params: [
|
|
17
|
+
{
|
|
18
|
+
name: 'options',
|
|
19
|
+
type: 'UseScrollableAreaOptions',
|
|
20
|
+
description:
|
|
21
|
+
'Logical scroll intent, keyboard owner, overscroll policy, and fitting Sticky containment.',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
returns: [
|
|
26
|
+
{
|
|
27
|
+
name: 'getViewportProps',
|
|
28
|
+
type: '<E extends HTMLElement>(props?: ScrollableElementProps<E>) => ScrollableElementProps<E>',
|
|
29
|
+
description:
|
|
30
|
+
'Consumes caller viewport props, xstyle, and refs; composes fitting/active overflow, Sticky containment, accessibility, chaining, and owner registration.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'getContentProps',
|
|
34
|
+
type: '<E extends HTMLElement>(props?: ScrollableElementProps<E>) => ScrollableElementProps<E>',
|
|
35
|
+
description:
|
|
36
|
+
'Composes caller content-box props and refs with content observation.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'state',
|
|
40
|
+
type: 'ScrollableAreaState',
|
|
41
|
+
description:
|
|
42
|
+
'Stable inline and block effective-scroll and logical-edge state.',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
usage: {
|
|
46
|
+
description:
|
|
47
|
+
'Adds canonical axis-aware scroll behavior to structure owned by the caller. An axis is effective only when its computed overflow is scroll-capable and geometry exceeds the shared 1px tolerance. Both viewport and content boxes are observed.',
|
|
48
|
+
bestPractices: [
|
|
49
|
+
{
|
|
50
|
+
guidance: true,
|
|
51
|
+
description:
|
|
52
|
+
'Pass already-resolved props and refs through both prop getters, then spread each returned object once.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
guidance: true,
|
|
56
|
+
description:
|
|
57
|
+
'Use viewport keyboard ownership only when the viewport itself should enter the tab order; provide a concise accessible label.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
guidance: true,
|
|
61
|
+
description:
|
|
62
|
+
'Use content keyboard ownership when an existing focusable descendant gives keyboard users access to all overflowed content.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
guidance: true,
|
|
66
|
+
description:
|
|
67
|
+
'Pass caller `xstyle` through `getViewportProps`; the getter composes it with fitting clip, active overflow, and Sticky containment.',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
guidance: false,
|
|
71
|
+
description:
|
|
72
|
+
'Attach only the viewport getter. A real observed content box is required for live overflow changes.',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
relatedComponents: ['ScrollableArea'],
|
|
77
|
+
relatedHooks: ['useScrollOverflow'],
|
|
78
|
+
importPath: '@astryxdesign/core/hooks',
|
|
79
|
+
category: 'layout',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
|
|
83
|
+
export const docsDense = {
|
|
84
|
+
description:
|
|
85
|
+
'Composes logical-axis scrolling into caller-owned viewport/content elements with stable effective-axis and edge state.',
|
|
86
|
+
paramDescriptions: {
|
|
87
|
+
options:
|
|
88
|
+
'axis, keyboard owner, allow/contain overscroll policy, and fitting Sticky containment.',
|
|
89
|
+
},
|
|
90
|
+
returnDescriptions: {
|
|
91
|
+
getViewportProps:
|
|
92
|
+
'safe viewport prop/ref/xstyle composition with behavior-owned overflow, accessibility, and chaining.',
|
|
93
|
+
getContentProps: 'safe observed content-box prop/ref composition.',
|
|
94
|
+
state: 'inline/block isScrollable, atStart, and atEnd state.',
|
|
95
|
+
},
|
|
96
|
+
usage: {
|
|
97
|
+
description:
|
|
98
|
+
'Use for existing structures that need ScrollableArea behavior without another wrapper.',
|
|
99
|
+
bestPractices: [
|
|
100
|
+
{
|
|
101
|
+
guidance: true,
|
|
102
|
+
description:
|
|
103
|
+
'Spread each prop getter result once on its owner element.',
|
|
104
|
+
},
|
|
105
|
+
{guidance: false, description: 'Skip the real content-box getter.'},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
};
|