@astryxdesign/core 0.1.2-canary.bd8c3d3 → 0.1.2-canary.bfcbf64
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/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +20 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +11 -11
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +4 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +12 -4
- package/dist/ContextMenu/ContextMenu.d.ts +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +58 -7
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +17 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +9 -4
- package/dist/DateTimeInput/DateTimeInput.d.ts +7 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +15 -3
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +25 -4
- package/dist/Field/Field.d.ts +19 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -0
- package/dist/Field/FieldLabel.d.ts +21 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +37 -22
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +27 -6
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +4 -1
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +33 -1
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +36 -5
- package/dist/Layout/padding.stylex.d.ts +51 -0
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +63 -0
- package/dist/Link/Link.d.ts +7 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +12 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +35 -6
- package/dist/MultiSelector/hooks.d.ts +12 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +14 -1
- package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
- package/dist/NavMenu/NavHeadingMenu.js +17 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +7 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +8 -0
- package/dist/ProgressBar/ProgressBar.js +1 -1
- package/dist/RadioList/RadioList.d.ts +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +76 -3
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +6 -2
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +15 -8
- package/dist/Selector/hooks.d.ts +8 -1
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +14 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +8 -2
- package/dist/Stack/Stack.d.ts +28 -1
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +17 -1
- package/dist/Stack/StackItem.d.ts +12 -1
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +8 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +6 -0
- package/dist/TabList/TabList.d.ts +15 -4
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +58 -4
- package/dist/TabList/TabListContext.d.ts +5 -0
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +5 -0
- package/dist/TabList/TabMenu.js +2 -0
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +14 -0
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +132 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +7 -3
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +78 -2
- package/dist/TreeList/TreeListItem.d.ts +10 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +44 -10
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
- package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +41 -3
- package/dist/astryx.css +3 -0
- package/dist/astryx.umd.js +47 -47
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +10 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +6 -2
- package/dist/hooks/useAnnounce.d.ts +40 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -0
- package/dist/hooks/useAnnounce.js +142 -0
- package/dist/hooks/useClickableContainer.d.ts +16 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +16 -4
- package/dist/hooks/useFocusTrap.d.ts +18 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +65 -0
- package/dist/hooks/useListFocus.d.ts +79 -8
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +262 -48
- package/dist/hooks/useLongPress.d.ts +21 -0
- package/dist/hooks/useLongPress.d.ts.map +1 -0
- package/dist/hooks/useLongPress.js +89 -0
- package/dist/hooks/useTreeFocus.d.ts +133 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -0
- package/dist/hooks/useTreeFocus.js +274 -0
- package/dist/hooks/useTypeahead.d.ts +63 -0
- package/dist/hooks/useTypeahead.d.ts.map +1 -0
- package/dist/hooks/useTypeahead.js +117 -0
- package/package.json +1 -1
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +20 -5
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Calendar/Calendar.test.tsx +25 -0
- package/src/Calendar/Calendar.tsx +10 -12
- package/src/CheckboxInput/CheckboxInput.test.tsx +14 -25
- package/src/CheckboxInput/CheckboxInput.tsx +4 -2
- package/src/CheckboxList/CheckboxList.test.tsx +30 -14
- package/src/CheckboxList/CheckboxList.tsx +18 -3
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.test.tsx +115 -1
- package/src/ContextMenu/ContextMenu.tsx +75 -4
- package/src/DateInput/DateInput.test.tsx +59 -0
- package/src/DateInput/DateInput.tsx +24 -7
- package/src/DateRangeInput/DateRangeInput.test.tsx +69 -18
- package/src/DateRangeInput/DateRangeInput.tsx +7 -3
- package/src/DateTimeInput/DateTimeInput.doc.mjs +7 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +99 -6
- package/src/DateTimeInput/DateTimeInput.tsx +32 -3
- package/src/Dialog/Dialog.tsx +12 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +26 -4
- package/src/DropdownMenu/DropdownMenu.tsx +36 -2
- package/src/Field/Field.test.tsx +32 -0
- package/src/Field/Field.tsx +23 -5
- package/src/Field/FieldLabel.tsx +53 -17
- package/src/FileInput/FileInput.test.tsx +104 -49
- package/src/FileInput/FileInput.tsx +27 -3
- package/src/InputGroup/InputGroup.test.tsx +21 -63
- package/src/InputGroup/InputGroup.tsx +4 -1
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +35 -3
- package/src/Layer/useLayer.tsx +52 -5
- package/src/Layout/padding.stylex.ts +52 -0
- package/src/Link/Link.doc.mjs +14 -0
- package/src/Link/Link.test.tsx +32 -0
- package/src/Link/Link.tsx +23 -1
- package/src/MultiSelector/MultiSelector.test.tsx +124 -2
- package/src/MultiSelector/MultiSelector.tsx +50 -9
- package/src/MultiSelector/hooks.ts +27 -0
- package/src/NavMenu/NavHeadingMenu.test.tsx +32 -1
- package/src/NavMenu/NavHeadingMenu.tsx +30 -2
- package/src/NumberInput/NumberInput.test.tsx +72 -38
- package/src/NumberInput/NumberInput.tsx +12 -1
- package/src/Pagination/Pagination.test.tsx +58 -5
- package/src/Pagination/Pagination.tsx +12 -0
- package/src/ProgressBar/ProgressBar.test.tsx +34 -26
- package/src/ProgressBar/ProgressBar.tsx +1 -1
- package/src/RadioList/RadioList.test.tsx +129 -14
- package/src/RadioList/RadioList.tsx +103 -8
- package/src/SegmentedControl/SegmentedControl.test.tsx +35 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +4 -1
- package/src/Selector/Selector.test.tsx +53 -0
- package/src/Selector/Selector.tsx +15 -8
- package/src/Selector/hooks.ts +23 -0
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Stack/Stack.doc.mjs +57 -0
- package/src/Stack/Stack.test.tsx +55 -5
- package/src/Stack/Stack.tsx +53 -1
- package/src/Stack/StackItem.test.tsx +23 -0
- package/src/Stack/StackItem.tsx +20 -0
- package/src/TabList/Tab.tsx +6 -0
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/TabList/TabList.test.tsx +167 -0
- package/src/TabList/TabList.tsx +72 -9
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +3 -3
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +204 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/Toolbar/Toolbar.test.tsx +45 -0
- package/src/Toolbar/Toolbar.tsx +5 -2
- package/src/TreeList/TreeList.test.tsx +273 -0
- package/src/TreeList/TreeList.tsx +84 -1
- package/src/TreeList/TreeListItem.tsx +42 -2
- package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
- package/src/Typeahead/BaseTypeahead.tsx +48 -2
- package/src/Typeahead/Typeahead.test.tsx +125 -0
- package/src/hooks/index.ts +21 -2
- package/src/hooks/useAnnounce.test.tsx +101 -0
- package/src/hooks/useAnnounce.ts +168 -0
- package/src/hooks/useClickableContainer.ts +16 -4
- package/src/hooks/useFocusTrap.test.tsx +67 -3
- package/src/hooks/useFocusTrap.ts +75 -0
- package/src/hooks/useListFocus.doc.mjs +49 -10
- package/src/hooks/useListFocus.test.tsx +269 -0
- package/src/hooks/useListFocus.ts +339 -54
- package/src/hooks/useLongPress.test.tsx +134 -0
- package/src/hooks/useLongPress.ts +119 -0
- package/src/hooks/useTreeFocus.doc.mjs +126 -0
- package/src/hooks/useTreeFocus.test.tsx +214 -0
- package/src/hooks/useTreeFocus.ts +464 -0
- package/src/hooks/useTypeahead.test.tsx +109 -0
- package/src/hooks/useTypeahead.ts +183 -0
package/dist/hooks/index.d.ts
CHANGED
|
@@ -8,10 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { useFocusTrap } from './useFocusTrap';
|
|
10
10
|
export type { UseFocusTrapOptions, UseFocusTrapReturn } from './useFocusTrap';
|
|
11
|
+
export { useAnnounce } from './useAnnounce';
|
|
12
|
+
export type { AnnounceFn, AnnouncePoliteness } from './useAnnounce';
|
|
11
13
|
export { useGridFocus } from './useGridFocus';
|
|
12
14
|
export type { UseGridFocusOptions, UseGridFocusReturn } from './useGridFocus';
|
|
13
15
|
export { useListFocus } from './useListFocus';
|
|
14
|
-
export type { UseListFocusOptions, UseListFocusReturn } from './useListFocus';
|
|
16
|
+
export type { UseListFocusOptions, UseListFocusReturn, ListFocusOrientation, } from './useListFocus';
|
|
17
|
+
export { useTreeFocus } from './useTreeFocus';
|
|
18
|
+
export type { UseTreeFocusOptions, UseTreeFocusReturn } from './useTreeFocus';
|
|
19
|
+
export { useTypeahead } from './useTypeahead';
|
|
20
|
+
export type { UseTypeaheadOptions, UseTypeaheadReturn } from './useTypeahead';
|
|
15
21
|
export { useMediaQuery } from './useMediaQuery';
|
|
16
22
|
export { useOverflow } from './useOverflow';
|
|
17
23
|
export type { UseOverflowOptions, UseOverflowReturn } from './useOverflow';
|
|
@@ -24,10 +30,12 @@ export { useStreamingText } from './useStreamingText';
|
|
|
24
30
|
export type { StreamingTextSpeed, UseStreamingTextOptions, } from './useStreamingText';
|
|
25
31
|
export { useImageMode } from './useImageMode';
|
|
26
32
|
export type { ImageSampleRegion, UseImageModeOptions } from './useImageMode';
|
|
27
|
-
export { useClickableContainer } from './useClickableContainer';
|
|
33
|
+
export { useClickableContainer, INTERACTIVE_SELECTORS } from './useClickableContainer';
|
|
28
34
|
export type { UseClickableContainerOptions, ClickableContainerResult, } from './useClickableContainer';
|
|
29
35
|
export { useInputContainer } from './useInputContainer';
|
|
30
36
|
export type { UseInputContainerOptions } from './useInputContainer';
|
|
31
37
|
export { useInteractiveRole } from './useInteractiveRole';
|
|
32
38
|
export type { InteractiveRole, UseInteractiveRoleOptions, } from './useInteractiveRole';
|
|
39
|
+
export { useLongPress } from './useLongPress';
|
|
40
|
+
export type { UseLongPressOptions, UseLongPressHandlers, } from './useLongPress';
|
|
33
41
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAC,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,iBAAiB,EAAE,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAE3E,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAC,UAAU,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAElE,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAE5E,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAC1C,YAAY,EAAC,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAC,oBAAoB,EAAC,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAC,iBAAiB,EAAE,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAE3E,OAAO,EAAC,qBAAqB,EAAE,qBAAqB,EAAC,MAAM,yBAAyB,CAAC;AACrF,YAAY,EACV,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAC,wBAAwB,EAAC,MAAM,qBAAqB,CAAC;AAElE,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AACxD,YAAY,EACV,eAAe,EACf,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* SYNC: When modified, update this header
|
|
12
12
|
*/
|
|
13
13
|
export { useFocusTrap } from "./useFocusTrap.js";
|
|
14
|
+
export { useAnnounce } from "./useAnnounce.js";
|
|
14
15
|
export { useGridFocus } from "./useGridFocus.js";
|
|
15
16
|
export { useListFocus } from "./useListFocus.js";
|
|
17
|
+
export { useTreeFocus } from "./useTreeFocus.js";
|
|
18
|
+
export { useTypeahead } from "./useTypeahead.js";
|
|
16
19
|
export { useMediaQuery } from "./useMediaQuery.js";
|
|
17
20
|
export { useOverflow } from "./useOverflow.js";
|
|
18
21
|
export { useScrollOverflow } from "./useScrollOverflow.js";
|
|
@@ -20,6 +23,7 @@ export { useScrollLock } from "./useScrollLock.js";
|
|
|
20
23
|
export { useEntryAnimation } from "./useEntryAnimation.js";
|
|
21
24
|
export { useStreamingText } from "./useStreamingText.js";
|
|
22
25
|
export { useImageMode } from "./useImageMode.js";
|
|
23
|
-
export { useClickableContainer } from "./useClickableContainer.js";
|
|
26
|
+
export { useClickableContainer, INTERACTIVE_SELECTORS } from "./useClickableContainer.js";
|
|
24
27
|
export { useInputContainer } from "./useInputContainer.js";
|
|
25
|
-
export { useInteractiveRole } from "./useInteractiveRole.js";
|
|
28
|
+
export { useInteractiveRole } from "./useInteractiveRole.js";
|
|
29
|
+
export { useLongPress } from "./useLongPress.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Announcement urgency:
|
|
3
|
+
* - `'polite'` (default): announced when the screen reader is idle. Use for
|
|
4
|
+
* status updates, result counts, "no results", non-urgent confirmations.
|
|
5
|
+
* - `'assertive'`: interrupts the current announcement. Reserve for errors and
|
|
6
|
+
* time-sensitive alerts.
|
|
7
|
+
*/
|
|
8
|
+
export type AnnouncePoliteness = 'polite' | 'assertive';
|
|
9
|
+
/**
|
|
10
|
+
* Imperative screen-reader announcement function.
|
|
11
|
+
*/
|
|
12
|
+
export type AnnounceFn = (message: string, politeness?: AnnouncePoliteness) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Returns an imperative `announce(message, politeness?)` function that speaks a
|
|
15
|
+
* message through a persistently-mounted, visually-hidden live region.
|
|
16
|
+
*
|
|
17
|
+
* The polite and assertive regions are created once on first use and kept
|
|
18
|
+
* mounted, so announcements are reliable even for messages that appear
|
|
19
|
+
* immediately (unlike a live region rendered together with its content).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```
|
|
23
|
+
* function Search() {
|
|
24
|
+
* const announce = useAnnounce();
|
|
25
|
+
* const onResults = (n: number) => {
|
|
26
|
+
* announce(n === 0 ? 'No results found' : `${n} results`);
|
|
27
|
+
* };
|
|
28
|
+
* // ...
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function useAnnounce(): AnnounceFn;
|
|
33
|
+
/**
|
|
34
|
+
* Test-only helper: removes the singleton live regions and resets state so
|
|
35
|
+
* each test starts clean. Not part of the public runtime API.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare function __resetLiveRegionsForTest(): void;
|
|
40
|
+
//# sourceMappingURL=useAnnounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAnnounce.d.ts","sourceRoot":"","sources":["../../src/hooks/useAnnounce.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,WAAW,CAAC;AAExD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CACvB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,kBAAkB,KAC5B,IAAI,CAAC;AAyFV;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,IAAI,UAAU,CAaxC;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAMhD"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useAnnounce.ts
|
|
7
|
+
* @input Uses React useCallback; DOM APIs for a singleton live-region pair
|
|
8
|
+
* @output Exports useAnnounce hook and AnnouncePoliteness type
|
|
9
|
+
* @position Core a11y hook; provides imperative screen-reader announcements
|
|
10
|
+
* via persistently-mounted polite/assertive live regions
|
|
11
|
+
*
|
|
12
|
+
* SYNC: When modified, update:
|
|
13
|
+
* - /packages/core/src/hooks/index.ts
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback } from 'react';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Announcement urgency:
|
|
19
|
+
* - `'polite'` (default): announced when the screen reader is idle. Use for
|
|
20
|
+
* status updates, result counts, "no results", non-urgent confirmations.
|
|
21
|
+
* - `'assertive'`: interrupts the current announcement. Reserve for errors and
|
|
22
|
+
* time-sensitive alerts.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Imperative screen-reader announcement function.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const CONTAINER_ATTR = 'data-astryx-live-region';
|
|
30
|
+
|
|
31
|
+
// Visually-hidden clip block (matches the VisuallyHidden primitive). Applied
|
|
32
|
+
// inline so the regions work without any stylesheet being present.
|
|
33
|
+
const VISUALLY_HIDDEN_CSS = 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;' + 'overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;' + 'inset-block-start:0;inset-inline-start:0;pointer-events:none;' + 'user-select:none;';
|
|
34
|
+
// Singleton: the live regions are created ONCE and stay mounted for the
|
|
35
|
+
// lifetime of the document. This is the crux of reliable announcements — many
|
|
36
|
+
// screen readers will not announce content injected into a live region that is
|
|
37
|
+
// created together with its content ("born with content"). By mounting empty
|
|
38
|
+
// regions up front and only mutating their text later, updates are announced.
|
|
39
|
+
let regions = null;
|
|
40
|
+
function createRegion(politeness) {
|
|
41
|
+
const el = document.createElement('div');
|
|
42
|
+
el.setAttribute(CONTAINER_ATTR, politeness);
|
|
43
|
+
el.setAttribute('aria-live', politeness);
|
|
44
|
+
el.setAttribute('aria-atomic', 'true');
|
|
45
|
+
el.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status');
|
|
46
|
+
el.style.cssText = VISUALLY_HIDDEN_CSS;
|
|
47
|
+
document.body.appendChild(el);
|
|
48
|
+
return el;
|
|
49
|
+
}
|
|
50
|
+
function getRegions() {
|
|
51
|
+
if (typeof document === 'undefined') {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (regions) {
|
|
55
|
+
// Re-attach if a region was removed from the DOM (e.g. by a test cleanup).
|
|
56
|
+
if (!regions.polite.isConnected) {
|
|
57
|
+
document.body.appendChild(regions.polite);
|
|
58
|
+
}
|
|
59
|
+
if (!regions.assertive.isConnected) {
|
|
60
|
+
document.body.appendChild(regions.assertive);
|
|
61
|
+
}
|
|
62
|
+
return regions;
|
|
63
|
+
}
|
|
64
|
+
regions = {
|
|
65
|
+
polite: createRegion('polite'),
|
|
66
|
+
assertive: createRegion('assertive')
|
|
67
|
+
};
|
|
68
|
+
return regions;
|
|
69
|
+
}
|
|
70
|
+
function announceMessage(message, politeness) {
|
|
71
|
+
const r = getRegions();
|
|
72
|
+
if (!r) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const target = politeness === 'assertive' ? r.assertive : r.polite;
|
|
76
|
+
// Clearing first guarantees the mutation is observed even when the new
|
|
77
|
+
// message equals the current text — some AT deduplicate identical content.
|
|
78
|
+
target.textContent = '';
|
|
79
|
+
// A microtask/rAF-free re-set in the next frame is more reliable across AT
|
|
80
|
+
// than an immediate re-set, which can be coalesced with the clear.
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
target.textContent = message;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear any pending announcement from a region without announcing anything new.
|
|
88
|
+
* Used when the triggering context goes away (e.g. a search query is cleared),
|
|
89
|
+
* so stale status text does not linger in the accessibility tree.
|
|
90
|
+
*/
|
|
91
|
+
function clearRegion(politeness) {
|
|
92
|
+
if (!regions) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const target = politeness === 'assertive' ? regions.assertive : regions.polite;
|
|
96
|
+
target.textContent = '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns an imperative `announce(message, politeness?)` function that speaks a
|
|
101
|
+
* message through a persistently-mounted, visually-hidden live region.
|
|
102
|
+
*
|
|
103
|
+
* The polite and assertive regions are created once on first use and kept
|
|
104
|
+
* mounted, so announcements are reliable even for messages that appear
|
|
105
|
+
* immediately (unlike a live region rendered together with its content).
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```
|
|
109
|
+
* function Search() {
|
|
110
|
+
* const announce = useAnnounce();
|
|
111
|
+
* const onResults = (n: number) => {
|
|
112
|
+
* announce(n === 0 ? 'No results found' : `${n} results`);
|
|
113
|
+
* };
|
|
114
|
+
* // ...
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function useAnnounce() {
|
|
119
|
+
return useCallback((message, politeness = 'polite') => {
|
|
120
|
+
if (!message) {
|
|
121
|
+
// An empty message clears any lingering status from the region rather
|
|
122
|
+
// than announcing nothing — useful when the triggering context ends.
|
|
123
|
+
clearRegion(politeness);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
announceMessage(message, politeness);
|
|
127
|
+
}, []);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Test-only helper: removes the singleton live regions and resets state so
|
|
132
|
+
* each test starts clean. Not part of the public runtime API.
|
|
133
|
+
*
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
export function __resetLiveRegionsForTest() {
|
|
137
|
+
if (regions) {
|
|
138
|
+
regions.polite.remove();
|
|
139
|
+
regions.assertive.remove();
|
|
140
|
+
regions = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file useClickableContainer.ts
|
|
3
3
|
* @input Container ref, interactive element ref, click/href handlers
|
|
4
|
-
* @output onClick and onMouseUp handlers for the container
|
|
4
|
+
* @output onClick and onMouseUp handlers for the container; INTERACTIVE_SELECTORS list
|
|
5
5
|
* @position Core hook for clickable containers that safely handle nested interactive elements
|
|
6
6
|
*
|
|
7
7
|
* Solves the "nested interactive elements" problem: when a card is clickable
|
|
8
8
|
* but contains buttons/links, clicking those should NOT trigger the card's action.
|
|
9
|
+
*
|
|
10
|
+
* SYNC: When modified, update:
|
|
11
|
+
* - /packages/core/src/hooks/index.ts (export)
|
|
9
12
|
*/
|
|
10
13
|
import { type RefObject, type MouseEvent } from 'react';
|
|
14
|
+
/**
|
|
15
|
+
* Canonical list of interactive element selectors — native controls plus
|
|
16
|
+
* role-based interactive elements. Clicks on these (or their descendants)
|
|
17
|
+
* should NOT bubble to a clickable container's click handler.
|
|
18
|
+
*
|
|
19
|
+
* Exported so other focus/interaction utilities can share one comprehensive
|
|
20
|
+
* definition of "interactive target" rather than hand-rolling divergent lists.
|
|
21
|
+
* Note: this list is about "don't bubble clicks", not focus-eligibility —
|
|
22
|
+
* consumers that need focusable elements must additionally exclude
|
|
23
|
+
* unfocusable/disabled targets (e.g. `[tabindex="-1"]`, `:disabled`).
|
|
24
|
+
*/
|
|
25
|
+
export declare const INTERACTIVE_SELECTORS: string;
|
|
11
26
|
export interface UseClickableContainerOptions {
|
|
12
27
|
/** Ref to the outer container element */
|
|
13
28
|
containerRef: RefObject<HTMLElement | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClickableContainer.d.ts","sourceRoot":"","sources":["../../src/hooks/useClickableContainer.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"useClickableContainer.d.ts","sourceRoot":"","sources":["../../src/hooks/useClickableContainer.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,UAAU,EAAC,MAAM,OAAO,CAAC;AAE9E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,QAmBvB,CAAC;AAmCZ,MAAM,WAAW,4BAA4B;IAC3C,yCAAyC;IACzC,YAAY,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC5C,mEAAmE;IACnE,cAAc,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC/C,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IACnD,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClD,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,YAAY,EACZ,cAAc,EACd,OAAO,EAAE,WAAW,EACpB,IAAI,EACJ,MAAM,EACN,QAAgB,GACjB,EAAE,4BAA4B,GAAG,wBAAwB,CA6GzD"}
|
|
@@ -5,17 +5,29 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useClickableContainer.ts
|
|
7
7
|
* @input Container ref, interactive element ref, click/href handlers
|
|
8
|
-
* @output onClick and onMouseUp handlers for the container
|
|
8
|
+
* @output onClick and onMouseUp handlers for the container; INTERACTIVE_SELECTORS list
|
|
9
9
|
* @position Core hook for clickable containers that safely handle nested interactive elements
|
|
10
10
|
*
|
|
11
11
|
* Solves the "nested interactive elements" problem: when a card is clickable
|
|
12
12
|
* but contains buttons/links, clicking those should NOT trigger the card's action.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update:
|
|
15
|
+
* - /packages/core/src/hooks/index.ts (export)
|
|
13
16
|
*/
|
|
14
17
|
import { useCallback, useEffect } from 'react';
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Canonical list of interactive element selectors — native controls plus
|
|
21
|
+
* role-based interactive elements. Clicks on these (or their descendants)
|
|
22
|
+
* should NOT bubble to a clickable container's click handler.
|
|
23
|
+
*
|
|
24
|
+
* Exported so other focus/interaction utilities can share one comprehensive
|
|
25
|
+
* definition of "interactive target" rather than hand-rolling divergent lists.
|
|
26
|
+
* Note: this list is about "don't bubble clicks", not focus-eligibility —
|
|
27
|
+
* consumers that need focusable elements must additionally exclude
|
|
28
|
+
* unfocusable/disabled targets (e.g. `[tabindex="-1"]`, `:disabled`).
|
|
29
|
+
*/
|
|
30
|
+
export const INTERACTIVE_SELECTORS = ['button', 'a', 'input', 'select', 'textarea', '[role="button"]', '[role="link"]', '[role="checkbox"]', '[role="radio"]', '[role="switch"]', '[role="tab"]', '[role="menuitem"]', '[role="option"]', '[role="combobox"]', '[role="listbox"]', '[role="slider"]', '[role="spinbutton"]', '[data-pressable-container]'].join(',');
|
|
19
31
|
const NON_INTERACTIVE_SELECTORS = '[aria-readonly="true"]';
|
|
20
32
|
|
|
21
33
|
/**
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether any focus-trap Escape handler is currently active (i.e. a popover
|
|
3
|
+
* layer is open). Other overlay primitives that manage their own Escape (e.g.
|
|
4
|
+
* Dialog) can consult this to defer to a popover layered on top of them,
|
|
5
|
+
* giving topmost-only dismissal until a full layer stack exists.
|
|
6
|
+
*/
|
|
7
|
+
export declare function hasActiveFocusTrapEscape(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether an Escape keydown should be ignored because it is cancelling an
|
|
10
|
+
* in-progress IME composition. CJK/IME users press Escape to cancel
|
|
11
|
+
* composition; that must not close the surrounding overlay. `keyCode === 229`
|
|
12
|
+
* covers browsers that fire keydown before `isComposing` is set. Exported so
|
|
13
|
+
* other overlays (Dialog, Drawer, CommandPalette) share one definition.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isImeKeyEvent(event: {
|
|
16
|
+
isComposing?: boolean;
|
|
17
|
+
keyCode?: number;
|
|
18
|
+
}): boolean;
|
|
1
19
|
/**
|
|
2
20
|
* Configuration for focus trap behavior
|
|
3
21
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useFocusTrap.d.ts","sourceRoot":"","sources":["../../src/hooks/useFocusTrap.ts"],"names":[],"mappings":"AA+CA;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAEV;AA0ED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE;;OAEG;IACH,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAExC;;OAEG;IACH,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,EAAE,mBAAmB,GAC3B,kBAAkB,CAAC,CAAC,CAAC,CAoJvB"}
|
|
@@ -17,6 +17,50 @@
|
|
|
17
17
|
import { useCallback, useEffect, useRef } from 'react';
|
|
18
18
|
import { FOCUSABLE_SELECTOR } from "./focusableSelector.js";
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Module-level stack of active focus-trap Escape handlers.
|
|
22
|
+
*
|
|
23
|
+
* Every active `useFocusTrap` used to attach its own document-level `keydown`
|
|
24
|
+
* listener with no coordination, so a single Escape press closed *every* open
|
|
25
|
+
* layer at once (e.g. a popover nested inside a Dialog closed both). Tracking
|
|
26
|
+
* traps in a shared stack lets only the most recently activated (top-most)
|
|
27
|
+
* trap respond to Escape.
|
|
28
|
+
*/
|
|
29
|
+
const escapeStack = [];
|
|
30
|
+
function pushEscapeHandler(handler) {
|
|
31
|
+
escapeStack.push(handler);
|
|
32
|
+
}
|
|
33
|
+
function removeEscapeHandler(handler) {
|
|
34
|
+
const index = escapeStack.lastIndexOf(handler);
|
|
35
|
+
if (index !== -1) {
|
|
36
|
+
escapeStack.splice(index, 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function isTopEscapeHandler(handler) {
|
|
40
|
+
return escapeStack[escapeStack.length - 1] === handler;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether any focus-trap Escape handler is currently active (i.e. a popover
|
|
45
|
+
* layer is open). Other overlay primitives that manage their own Escape (e.g.
|
|
46
|
+
* Dialog) can consult this to defer to a popover layered on top of them,
|
|
47
|
+
* giving topmost-only dismissal until a full layer stack exists.
|
|
48
|
+
*/
|
|
49
|
+
export function hasActiveFocusTrapEscape() {
|
|
50
|
+
return escapeStack.length > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether an Escape keydown should be ignored because it is cancelling an
|
|
55
|
+
* in-progress IME composition. CJK/IME users press Escape to cancel
|
|
56
|
+
* composition; that must not close the surrounding overlay. `keyCode === 229`
|
|
57
|
+
* covers browsers that fire keydown before `isComposing` is set. Exported so
|
|
58
|
+
* other overlays (Dialog, Drawer, CommandPalette) share one definition.
|
|
59
|
+
*/
|
|
60
|
+
export function isImeKeyEvent(event) {
|
|
61
|
+
return event.isComposing === true || event.keyCode === 229;
|
|
62
|
+
}
|
|
63
|
+
|
|
20
64
|
/**
|
|
21
65
|
* Whether an element is currently perceivable/focusable — excludes ones hidden
|
|
22
66
|
* via `display:none`/`visibility:hidden` or inside an `inert`/`hidden` subtree,
|
|
@@ -190,13 +234,31 @@ export function useFocusTrap(options) {
|
|
|
190
234
|
if (!isActive) {
|
|
191
235
|
return;
|
|
192
236
|
}
|
|
237
|
+
|
|
238
|
+
// Register this trap on the shared Escape stack so only the top-most
|
|
239
|
+
// active trap responds to Escape. A stable identity per active period is
|
|
240
|
+
// enough — we push on activate and remove on cleanup.
|
|
241
|
+
const escapeHandler = () => {
|
|
242
|
+
onEscape?.();
|
|
243
|
+
};
|
|
244
|
+
if (onEscape) {
|
|
245
|
+
pushEscapeHandler(escapeHandler);
|
|
246
|
+
}
|
|
193
247
|
const handleKeyDown = event => {
|
|
194
248
|
const container = containerRef.current;
|
|
195
249
|
if (!container) {
|
|
196
250
|
return;
|
|
197
251
|
}
|
|
198
252
|
if (event.key === 'Escape' && onEscape) {
|
|
253
|
+
// Ignore Escape that is cancelling an IME composition, already handled
|
|
254
|
+
// by a nested handler, or not targeting the top-most trap.
|
|
255
|
+
if (event.defaultPrevented || isImeKeyEvent(event) || !isTopEscapeHandler(escapeHandler)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// Mark handled and stop propagation so an outer layer (e.g. a Dialog
|
|
259
|
+
// hosting this popover) does not also dismiss on the same press.
|
|
199
260
|
event.preventDefault();
|
|
261
|
+
event.stopPropagation();
|
|
200
262
|
onEscape();
|
|
201
263
|
return;
|
|
202
264
|
}
|
|
@@ -227,6 +289,9 @@ export function useFocusTrap(options) {
|
|
|
227
289
|
document.addEventListener('keydown', handleKeyDown);
|
|
228
290
|
return () => {
|
|
229
291
|
document.removeEventListener('keydown', handleKeyDown);
|
|
292
|
+
if (onEscape) {
|
|
293
|
+
removeEscapeHandler(escapeHandler);
|
|
294
|
+
}
|
|
230
295
|
};
|
|
231
296
|
}, [isActive, onEscape]);
|
|
232
297
|
return {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navigation orientation for a linear list.
|
|
3
|
+
* - `'horizontal'`: ArrowLeft/ArrowRight move between items.
|
|
4
|
+
* - `'vertical'`: ArrowUp/ArrowDown move between items.
|
|
5
|
+
* - `'both'`: all four arrows move between items (in linear DOM order).
|
|
6
|
+
*/
|
|
7
|
+
export type ListFocusOrientation = 'horizontal' | 'vertical' | 'both';
|
|
1
8
|
/**
|
|
2
9
|
* Configuration for list focus behavior
|
|
3
10
|
*/
|
|
4
11
|
export interface UseListFocusOptions {
|
|
5
12
|
/**
|
|
6
13
|
* Selector for focusable items within the list.
|
|
7
|
-
* @default '[role="menuitem"]
|
|
14
|
+
* @default '[role="menuitem"]'
|
|
8
15
|
*/
|
|
9
16
|
itemSelector?: string;
|
|
10
17
|
/**
|
|
@@ -17,11 +24,47 @@ export interface UseListFocusOptions {
|
|
|
17
24
|
*/
|
|
18
25
|
onEscape?: () => void;
|
|
19
26
|
/**
|
|
20
|
-
* Navigation orientation. 'horizontal' uses ArrowLeft/ArrowRight,
|
|
21
|
-
* 'vertical' uses ArrowUp/ArrowDown.
|
|
27
|
+
* Navigation orientation. `'horizontal'` uses ArrowLeft/ArrowRight,
|
|
28
|
+
* `'vertical'` uses ArrowUp/ArrowDown, `'both'` accepts all four arrows.
|
|
22
29
|
* @default 'vertical'
|
|
23
30
|
*/
|
|
24
|
-
orientation?:
|
|
31
|
+
orientation?: ListFocusOrientation;
|
|
32
|
+
/**
|
|
33
|
+
* Whether Home/End jump to the first/last enabled item.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
hasHomeEnd?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the list is in a right-to-left context. When true, ArrowLeft and
|
|
39
|
+
* ArrowRight are swapped so horizontal navigation follows visual direction.
|
|
40
|
+
* Only affects horizontal (`'horizontal'`/`'both'`) navigation.
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
isRtl?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Roving-tabindex ownership. When true, the hook manages a single tab stop
|
|
46
|
+
* across the items: exactly one enabled item carries `tabindex="0"` and the
|
|
47
|
+
* rest `tabindex="-1"`. The tab stop is stamped on mount and repaired
|
|
48
|
+
* whenever items mount/unmount or toggle disabled, and moves with arrow
|
|
49
|
+
* navigation. Attach the returned {@link UseListFocusReturn.handleFocus} to
|
|
50
|
+
* the container's `onFocus` to keep the stop in sync after clicks or
|
|
51
|
+
* programmatic focus.
|
|
52
|
+
*
|
|
53
|
+
* When false (the default), the hook only *moves* focus (`.focus()`) and
|
|
54
|
+
* never touches `tabindex` — the caller owns tab-stop management.
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
hasRovingTabIndex?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* When true, arrow keys are not stolen from a nested text input/textarea
|
|
60
|
+
* whose caret is not at the boundary in the direction of travel (or that has
|
|
61
|
+
* a non-collapsed selection), and are never stolen from a nested
|
|
62
|
+
* `contenteditable` (rich-text editor / chat composer). This preserves
|
|
63
|
+
* normal caret movement while the user is editing inline within the list
|
|
64
|
+
* (e.g. a toolbar search field or composer).
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
hasCaretGuard?: boolean;
|
|
25
68
|
}
|
|
26
69
|
/**
|
|
27
70
|
* Return type for useListFocus hook
|
|
@@ -35,18 +78,24 @@ export interface UseListFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
35
78
|
* Key down handler to attach to the list container.
|
|
36
79
|
*/
|
|
37
80
|
handleKeyDown: (e: React.KeyboardEvent) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Focus handler to attach to the container's `onFocus`. Keeps the roving tab
|
|
83
|
+
* stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it is
|
|
84
|
+
* always safe to attach.
|
|
85
|
+
*/
|
|
86
|
+
handleFocus: (e: React.FocusEvent) => void;
|
|
38
87
|
/**
|
|
39
88
|
* Focus a specific item by index.
|
|
40
89
|
*/
|
|
41
90
|
focusItem: (index: number) => void;
|
|
42
91
|
/**
|
|
43
|
-
* Focus the first
|
|
92
|
+
* Focus the first enabled item. Returns true when an item was focused.
|
|
44
93
|
*/
|
|
45
|
-
focusFirst: () =>
|
|
94
|
+
focusFirst: () => boolean;
|
|
46
95
|
/**
|
|
47
|
-
* Focus the last
|
|
96
|
+
* Focus the last enabled item. Returns true when an item was focused.
|
|
48
97
|
*/
|
|
49
|
-
focusLast: () =>
|
|
98
|
+
focusLast: () => boolean;
|
|
50
99
|
}
|
|
51
100
|
/**
|
|
52
101
|
* Hook for managing keyboard navigation within a linear list.
|
|
@@ -58,6 +107,12 @@ export interface UseListFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
58
107
|
* - End: Move to last item
|
|
59
108
|
* - Escape: Custom callback (e.g., close menu)
|
|
60
109
|
*
|
|
110
|
+
* By default the hook only *moves* focus and leaves `tabindex` management to
|
|
111
|
+
* the caller. Opt into {@link UseListFocusOptions.hasRovingTabIndex} for a hook
|
|
112
|
+
* that owns a single tab stop (roving tabindex) across the items — stamping and
|
|
113
|
+
* repairing it as items mount/unmount or toggle disabled — for toolbars,
|
|
114
|
+
* segmented controls, tab strips, and similar composite widgets.
|
|
115
|
+
*
|
|
61
116
|
* @example
|
|
62
117
|
* ```
|
|
63
118
|
* const {listRef, handleKeyDown} = useListFocus({
|
|
@@ -68,6 +123,22 @@ export interface UseListFocusReturn<T extends HTMLElement = HTMLElement> {
|
|
|
68
123
|
* {items.map(item => <div role="menuitem" tabIndex={0}>{item}</div>)}
|
|
69
124
|
* </div>
|
|
70
125
|
* ```
|
|
126
|
+
*
|
|
127
|
+
* Roving-tabindex composite (e.g. a toolbar):
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```
|
|
131
|
+
* const {listRef, handleKeyDown, handleFocus} = useListFocus<HTMLDivElement>({
|
|
132
|
+
* itemSelector: 'button, input, [tabindex]',
|
|
133
|
+
* orientation: 'horizontal',
|
|
134
|
+
* hasRovingTabIndex: true,
|
|
135
|
+
* hasCaretGuard: true,
|
|
136
|
+
* });
|
|
137
|
+
*
|
|
138
|
+
* <div ref={listRef} role="toolbar" onKeyDown={handleKeyDown} onFocus={handleFocus}>
|
|
139
|
+
* {children}
|
|
140
|
+
* </div>
|
|
141
|
+
* ```
|
|
71
142
|
*/
|
|
72
143
|
export declare function useListFocus<T extends HTMLElement = HTMLElement>(options?: UseListFocusOptions): UseListFocusReturn<T>;
|
|
73
144
|
//# sourceMappingURL=useListFocus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useListFocus.d.ts","sourceRoot":"","sources":["../../src/hooks/useListFocus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useListFocus.d.ts","sourceRoot":"","sources":["../../src/hooks/useListFocus.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,oBAAoB,CAAC;IAEnC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW;IACrE;;OAEG;IACH,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEnC;;OAEG;IACH,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;IAEhD;;;;OAIG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC;IAE1B;;OAEG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;CAC1B;AA+FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC9D,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAAC,CAAC,CAAC,CAwSvB"}
|