@astryxdesign/core 0.1.2 → 0.1.3-canary.6d57c8d
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 +165 -0
- package/README.md +12 -12
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +11 -5
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +0 -1
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +82 -43
- package/dist/Calendar/hooks/index.d.ts +0 -2
- package/dist/Calendar/hooks/index.d.ts.map +1 -1
- package/dist/Calendar/hooks/index.js +1 -2
- package/dist/Calendar/index.d.ts +3 -3
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/Calendar/index.js +1 -1
- package/dist/Calendar/styles.d.ts +3 -9
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +3 -11
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +17 -3
- package/dist/Center/Center.d.ts +11 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +9 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +0 -1
- package/dist/Chat/ChatMessageList.d.ts +14 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +2 -0
- package/dist/Chat/useTriggerMenu.d.ts +8 -1
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +15 -3
- package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +51 -9
- package/dist/CheckboxList/CheckboxList.d.ts +13 -1
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +39 -9
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +0 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +9 -5
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +51 -37
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -5
- package/dist/ContextMenu/ContextMenu.d.ts +4 -5
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +93 -11
- package/dist/DateInput/DateInput.d.ts +23 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +167 -99
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +42 -9
- package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +78 -15
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +12 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +42 -14
- 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 +41 -26
- package/dist/FileInput/FileInput.d.ts +22 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +56 -10
- package/dist/Grid/Grid.d.ts +11 -1
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +32 -6
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +16 -6
- package/dist/InputGroup/InputGroupContext.d.ts +4 -0
- package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
- package/dist/InputGroup/InputGroupContext.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +3 -3
- package/dist/InputGroup/index.d.ts +1 -0
- package/dist/InputGroup/index.d.ts.map +1 -1
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +33 -1
- package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +8 -4
- package/dist/Layer/useLayer.d.ts +10 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +64 -9
- package/dist/Layout/Layout.d.ts +4 -11
- package/dist/Layout/Layout.d.ts.map +1 -1
- package/dist/Layout/Layout.js +10 -11
- package/dist/Layout/LayoutFooter.d.ts +2 -2
- package/dist/Layout/LayoutFooter.d.ts.map +1 -1
- package/dist/Layout/LayoutHeader.d.ts +2 -2
- package/dist/Layout/LayoutHeader.d.ts.map +1 -1
- package/dist/Layout/LayoutPanel.d.ts +2 -2
- package/dist/Layout/LayoutPanel.d.ts.map +1 -1
- 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/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +4 -2
- package/dist/Link/Link.d.ts +16 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +18 -7
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MoreMenu/MoreMenu.d.ts +1 -7
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +0 -2
- package/dist/MultiSelector/MultiSelector.d.ts +27 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +141 -42
- 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 +34 -3
- package/dist/NumberInput/NumberInput.d.ts +23 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +62 -12
- package/dist/Pagination/Pagination.d.ts +1 -1
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +15 -1
- package/dist/Popover/Popover.d.ts +18 -2
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +5 -2
- package/dist/Popover/usePopover.d.ts +38 -3
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +24 -9
- package/dist/PowerSearch/PowerSearch.d.ts +11 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +6 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +3 -5
- package/dist/RadioList/RadioList.d.ts +20 -2
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +105 -9
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +78 -44
- package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
- package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
- package/dist/Selector/Selector.d.ts +24 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +122 -43
- 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/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +2 -0
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +1 -1
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +8 -2
- package/dist/Slider/Slider.d.ts +21 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +36 -9
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +1 -1
- package/dist/Stack/Stack.d.ts +38 -23
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +25 -1
- package/dist/Stack/StackItem.d.ts +12 -23
- package/dist/Stack/StackItem.d.ts.map +1 -1
- package/dist/Stack/StackItem.js +8 -1
- package/dist/Switch/Switch.d.ts +23 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +41 -11
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +6 -0
- package/dist/TabList/TabList.d.ts +16 -4
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +87 -10
- 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.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +11 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +10 -1
- package/dist/Table/TableCell.d.ts +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +10 -10
- package/dist/Table/TableHeaderCell.d.ts +8 -1
- package/dist/Table/TableHeaderCell.d.ts.map +1 -1
- package/dist/Table/TableHeaderCell.js +16 -16
- package/dist/Table/index.d.ts +2 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +8 -3
- package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
- package/dist/Table/tableContextMenu.d.ts +33 -0
- package/dist/Table/tableContextMenu.d.ts.map +1 -0
- package/dist/Table/tableContextMenu.js +133 -0
- package/dist/Table/types.d.ts +66 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +22 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +41 -9
- package/dist/TextInput/TextInput.d.ts +23 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +55 -10
- package/dist/TimeInput/TimeInput.d.ts +23 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +147 -76
- 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 +131 -3
- package/dist/Tokenizer/Tokenizer.d.ts +12 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +26 -4
- package/dist/Toolbar/Toolbar.d.ts +3 -3
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +51 -7
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +56 -9
- package/dist/TopNav/TopNavHeading.d.ts +7 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +8 -0
- package/dist/TreeList/TreeList.d.ts +1 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +72 -2
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +51 -12
- 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 +13 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +69 -6
- package/dist/Typeahead/Typeahead.d.ts +26 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +65 -29
- package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
- package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
- package/dist/VisuallyHidden/index.d.ts +11 -0
- package/dist/VisuallyHidden/index.d.ts.map +1 -0
- package/dist/VisuallyHidden/index.js +13 -0
- package/dist/astryx.css +18 -0
- package/dist/astryx.umd.js +55 -55
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +22 -0
- package/dist/hooks/focusableSelector.d.ts.map +1 -0
- package/dist/hooks/focusableSelector.js +24 -0
- package/dist/hooks/index.d.ts +12 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +7 -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/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +4 -4
- package/dist/hooks/useFocusTrap.d.ts +18 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +87 -3
- package/dist/hooks/useGridFocus.d.ts +88 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +261 -62
- package/dist/hooks/useKeyboardHint.d.ts +82 -0
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
- package/dist/hooks/useKeyboardHint.js +220 -0
- package/dist/hooks/useListFocus.d.ts +78 -7
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +305 -52
- 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 +157 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -0
- package/dist/hooks/useTreeFocus.js +337 -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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/dist/utils/inputAria.d.ts +29 -0
- package/dist/utils/inputAria.d.ts.map +1 -0
- package/dist/utils/inputAria.js +30 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -2
- package/docs.mjs +2 -2
- package/package.json +12 -8
- package/src/Avatar/Avatar.test.tsx +37 -0
- package/src/Avatar/Avatar.tsx +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
- package/src/Button/Button.tsx +3 -16
- package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
- package/src/ButtonGroup/ButtonGroup.tsx +0 -1
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +168 -30
- package/src/Calendar/Calendar.tsx +121 -66
- package/src/Calendar/hooks/index.ts +0 -6
- package/src/Calendar/index.ts +1 -3
- package/src/Calendar/styles.ts +4 -9
- package/src/Carousel/Carousel.doc.mjs +7 -1
- package/src/Carousel/Carousel.test.tsx +20 -0
- package/src/Carousel/Carousel.tsx +16 -1
- package/src/Center/Center.doc.mjs +12 -2
- package/src/Center/Center.tsx +28 -3
- package/src/Chat/ChatComposerInput.test.tsx +26 -7
- package/src/Chat/ChatComposerInput.tsx +0 -1
- package/src/Chat/ChatDictationButton.doc.mjs +1 -1
- package/src/Chat/ChatMessageList.doc.mjs +8 -0
- package/src/Chat/ChatMessageList.test.tsx +18 -0
- package/src/Chat/ChatMessageList.tsx +16 -0
- package/src/Chat/useTriggerMenu.tsx +22 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
- package/src/CheckboxInput/CheckboxInput.tsx +78 -6
- package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
- package/src/CheckboxList/CheckboxList.test.tsx +192 -26
- package/src/CheckboxList/CheckboxList.tsx +56 -3
- package/src/CheckboxList/CheckboxListContext.tsx +7 -0
- package/src/CheckboxList/CheckboxListItem.tsx +0 -3
- package/src/Citation/Citation.doc.mjs +10 -0
- package/src/Citation/Citation.test.tsx +121 -0
- package/src/Citation/Citation.tsx +15 -3
- package/src/CodeBlock/CodeBlock.test.tsx +115 -0
- package/src/CodeBlock/CodeBlock.tsx +63 -33
- package/src/CommandPalette/CommandPalette.test.tsx +47 -1
- package/src/CommandPalette/CommandPalette.tsx +9 -21
- package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
- package/src/ContextMenu/ContextMenu.test.tsx +155 -1
- package/src/ContextMenu/ContextMenu.tsx +113 -16
- package/src/DateInput/DateInput.doc.mjs +286 -56
- package/src/DateInput/DateInput.test.tsx +248 -2
- package/src/DateInput/DateInput.tsx +196 -100
- package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
- package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
- package/src/DateRangeInput/DateRangeInput.tsx +63 -5
- package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
- package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
- package/src/DateTimeInput/DateTimeInput.tsx +125 -17
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/Dialog/Dialog.tsx +12 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
- package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
- package/src/DropdownMenu/DropdownMenu.tsx +52 -18
- package/src/Field/Field.doc.mjs +59 -18
- package/src/Field/Field.test.tsx +32 -0
- package/src/Field/Field.tsx +23 -5
- package/src/Field/FieldLabel.tsx +55 -17
- package/src/FileInput/FileInput.doc.mjs +17 -0
- package/src/FileInput/FileInput.test.tsx +283 -49
- package/src/FileInput/FileInput.tsx +76 -4
- package/src/Grid/Grid.doc.mjs +17 -4
- package/src/Grid/Grid.test.tsx +43 -30
- package/src/Grid/Grid.tsx +38 -3
- package/src/Icon/Icon.doc.mjs +3 -3
- package/src/InputGroup/InputGroup.doc.mjs +111 -23
- package/src/InputGroup/InputGroup.test.tsx +257 -60
- package/src/InputGroup/InputGroup.tsx +23 -5
- package/src/InputGroup/InputGroupContext.ts +9 -4
- package/src/InputGroup/groupStyles.ts +7 -1
- package/src/InputGroup/index.ts +1 -0
- package/src/Kbd/Kbd.test.tsx +22 -4
- package/src/Kbd/Kbd.tsx +35 -3
- package/src/Layer/layerAnimations.stylex.ts +20 -4
- package/src/Layer/useLayer.doc.mjs +5 -0
- package/src/Layer/useLayer.test.tsx +111 -0
- package/src/Layer/useLayer.tsx +90 -9
- package/src/Layout/Layout.tsx +8 -16
- package/src/Layout/LayoutFooter.doc.mjs +4 -4
- package/src/Layout/LayoutFooter.tsx +3 -3
- package/src/Layout/LayoutHeader.doc.mjs +4 -4
- package/src/Layout/LayoutHeader.tsx +3 -3
- package/src/Layout/LayoutPanel.tsx +3 -3
- package/src/Layout/padding.stylex.ts +52 -0
- package/src/Lightbox/Lightbox.doc.mjs +9 -0
- package/src/Lightbox/Lightbox.test.tsx +53 -5
- package/src/Lightbox/Lightbox.tsx +9 -4
- package/src/Link/Link.doc.mjs +14 -0
- package/src/Link/Link.test.tsx +50 -0
- package/src/Link/Link.tsx +39 -4
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +7 -0
- package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
- package/src/MoreMenu/MoreMenu.tsx +0 -9
- package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
- package/src/MultiSelector/MultiSelector.test.tsx +375 -8
- package/src/MultiSelector/MultiSelector.tsx +194 -48
- package/src/MultiSelector/hooks.ts +27 -0
- package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
- package/src/NavMenu/NavHeadingMenu.tsx +50 -3
- package/src/NumberInput/NumberInput.doc.mjs +17 -0
- package/src/NumberInput/NumberInput.test.tsx +242 -40
- package/src/NumberInput/NumberInput.tsx +89 -11
- package/src/Pagination/Pagination.doc.mjs +3 -3
- package/src/Pagination/Pagination.test.tsx +117 -5
- package/src/Pagination/Pagination.tsx +21 -1
- package/src/Popover/Popover.doc.mjs +26 -0
- package/src/Popover/Popover.test.tsx +121 -0
- package/src/Popover/Popover.tsx +23 -2
- package/src/Popover/usePopover.doc.mjs +9 -3
- package/src/Popover/usePopover.tsx +70 -12
- package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
- package/src/PowerSearch/PowerSearch.test.tsx +97 -1
- package/src/PowerSearch/PowerSearch.tsx +16 -3
- package/src/ProgressBar/ProgressBar.test.tsx +34 -26
- package/src/ProgressBar/ProgressBar.tsx +3 -4
- package/src/RadioList/RadioList.doc.mjs +9 -0
- package/src/RadioList/RadioList.test.tsx +252 -16
- package/src/RadioList/RadioList.tsx +166 -10
- package/src/RadioList/RadioListItem.tsx +14 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
- package/src/SegmentedControl/SegmentedControl.tsx +102 -54
- package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/Selector/Selector.doc.mjs +26 -0
- package/src/Selector/Selector.test.tsx +387 -15
- package/src/Selector/Selector.tsx +175 -67
- package/src/Selector/hooks.ts +23 -0
- package/src/SideNav/SideNav.test.tsx +48 -0
- package/src/SideNav/SideNavHeading.tsx +8 -2
- package/src/SideNav/SideNavItem.tsx +2 -4
- package/src/Skeleton/Skeleton.test.tsx +25 -0
- package/src/Skeleton/Skeleton.tsx +11 -1
- package/src/Slider/Slider.doc.mjs +15 -0
- package/src/Slider/Slider.test.tsx +155 -2
- package/src/Slider/Slider.tsx +53 -5
- package/src/Spinner/Spinner.tsx +7 -1
- package/src/Stack/Stack.doc.mjs +77 -0
- package/src/Stack/Stack.test.tsx +55 -5
- package/src/Stack/Stack.tsx +73 -24
- package/src/Stack/StackItem.test.tsx +23 -0
- package/src/Stack/StackItem.tsx +20 -23
- package/src/Switch/Switch.doc.mjs +15 -0
- package/src/Switch/Switch.test.tsx +162 -27
- package/src/Switch/Switch.tsx +61 -21
- package/src/TabList/Tab.tsx +6 -0
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/TabList/TabList.test.tsx +251 -0
- package/src/TabList/TabList.tsx +103 -12
- package/src/TabList/TabListContext.ts +7 -3
- package/src/TabList/TabMenu.tsx +6 -3
- package/src/Table/BaseTable.tsx +21 -17
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.test.tsx +10 -0
- package/src/Table/Table.tsx +8 -0
- package/src/Table/TableCell.tsx +25 -23
- package/src/Table/TableHeaderCell.tsx +28 -29
- package/src/Table/index.ts +3 -2
- package/src/Table/plugins/pagination/paginateData.ts +10 -3
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
- package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
- package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
- package/src/Table/tableContextMenu.test.tsx +157 -0
- package/src/Table/tableContextMenu.tsx +142 -0
- package/src/Table/types.ts +74 -0
- package/src/Table/useTableColumnResize.doc.mjs +3 -3
- package/src/Table/useTableStickyColumns.doc.mjs +2 -2
- package/src/Text/Text.doc.mjs +18 -18
- package/src/TextArea/TextArea.doc.mjs +17 -0
- package/src/TextArea/TextArea.test.tsx +167 -35
- package/src/TextArea/TextArea.tsx +62 -15
- package/src/TextInput/TextInput.doc.mjs +17 -0
- package/src/TextInput/TextInput.test.tsx +168 -34
- package/src/TextInput/TextInput.tsx +77 -9
- package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
- package/src/TimeInput/TimeInput.doc.mjs +340 -60
- package/src/TimeInput/TimeInput.test.tsx +255 -7
- package/src/TimeInput/TimeInput.tsx +176 -70
- package/src/Timestamp/Timestamp.doc.mjs +19 -19
- package/src/Toast/Toast.tsx +14 -0
- package/src/Toast/ToastViewport.test.tsx +214 -0
- package/src/Toast/ToastViewport.tsx +167 -21
- package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
- package/src/Tokenizer/Tokenizer.test.tsx +105 -1
- package/src/Tokenizer/Tokenizer.tsx +37 -1
- package/src/Toolbar/Toolbar.test.tsx +109 -6
- package/src/Toolbar/Toolbar.tsx +59 -5
- package/src/Tooltip/Tooltip.test.tsx +68 -0
- package/src/Tooltip/useTooltip.tsx +55 -8
- package/src/TopNav/TopNav.test.tsx +50 -0
- package/src/TopNav/TopNavHeading.tsx +17 -1
- package/src/TreeList/TreeList.test.tsx +295 -6
- package/src/TreeList/TreeList.tsx +73 -1
- package/src/TreeList/TreeListItem.tsx +51 -5
- package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
- package/src/Typeahead/BaseTypeahead.tsx +93 -9
- package/src/Typeahead/Typeahead.doc.mjs +141 -30
- package/src/Typeahead/Typeahead.test.tsx +331 -1
- package/src/Typeahead/Typeahead.tsx +105 -33
- package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
- package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
- package/src/VisuallyHidden/index.ts +15 -0
- package/src/docs-script.test.mjs +50 -0
- package/src/hooks/focusableSelector.ts +25 -0
- package/src/hooks/index.ts +28 -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/useEntryAnimation.ts +16 -4
- package/src/hooks/useFocusTrap.test.tsx +132 -0
- package/src/hooks/useFocusTrap.ts +98 -4
- package/src/hooks/useGridFocus.doc.mjs +40 -2
- package/src/hooks/useGridFocus.test.tsx +132 -0
- package/src/hooks/useGridFocus.ts +343 -67
- package/src/hooks/useKeyboardHint.doc.mjs +101 -0
- package/src/hooks/useKeyboardHint.test.tsx +70 -0
- package/src/hooks/useKeyboardHint.tsx +332 -0
- package/src/hooks/useListFocus.doc.mjs +50 -10
- package/src/hooks/useListFocus.test.tsx +375 -0
- package/src/hooks/useListFocus.ts +395 -57
- package/src/hooks/useLongPress.test.tsx +134 -0
- package/src/hooks/useLongPress.ts +119 -0
- package/src/hooks/useTreeFocus.doc.mjs +140 -0
- package/src/hooks/useTreeFocus.test.tsx +214 -0
- package/src/hooks/useTreeFocus.ts +559 -0
- package/src/hooks/useTypeahead.test.tsx +109 -0
- package/src/hooks/useTypeahead.ts +183 -0
- package/src/index.ts +1 -0
- package/src/theme/derivedVarRegistry.test.ts +10 -5
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/inputAria.test.ts +39 -0
- package/src/utils/inputAria.ts +58 -0
- package/src/utils/timeParser.test.ts +10 -0
- package/src/utils/timeParser.ts +5 -2
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
- package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
- package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
- package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
|
@@ -88,6 +88,12 @@ export const docs = {
|
|
|
88
88
|
'Disables the input, preventing interaction and dimming the element.',
|
|
89
89
|
default: 'false',
|
|
90
90
|
},
|
|
91
|
+
{
|
|
92
|
+
name: 'disabledMessage',
|
|
93
|
+
type: 'string',
|
|
94
|
+
description:
|
|
95
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (opening the file picker stays blocked). Use this instead of wrapping a disabled FileInput in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
|
|
96
|
+
},
|
|
91
97
|
{
|
|
92
98
|
name: 'isLoading',
|
|
93
99
|
type: 'boolean',
|
|
@@ -135,6 +141,7 @@ export const docs = {
|
|
|
135
141
|
{guidance: true, description: 'Use changeAction for immediate upload workflows that benefit from optimistic UI.'},
|
|
136
142
|
{guidance: false, description: "Don't use FileInput for directory or folder uploads; that is not supported in v1."},
|
|
137
143
|
{guidance: false, description: "Don't avoid dropzone mode unless space is constrained; drag-and-drop is the expected interaction for file uploads."},
|
|
144
|
+
{guidance: false, description: "Don't wrap a disabled FileInput in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead."},
|
|
138
145
|
],
|
|
139
146
|
anatomy: [
|
|
140
147
|
{name: 'Label', required: true, description: 'Text that identifies the field. Always rendered for accessibility even when visually hidden.'},
|
|
@@ -211,6 +218,12 @@ export const docsZh = {
|
|
|
211
218
|
description: '禁用输入框,阻止交互并使元素变暗。',
|
|
212
219
|
default: 'false',
|
|
213
220
|
},
|
|
221
|
+
{
|
|
222
|
+
name: 'disabledMessage',
|
|
223
|
+
type: 'string',
|
|
224
|
+
description:
|
|
225
|
+
'说明输入框被禁用的原因。与 isDisabled 一起使用时,在悬停/键盘聚焦时显示工具提示,并通过 aria-disabled 保持触发器可聚焦(打开文件选择器仍被阻止)。请使用此属性,而不是用 Tooltip 包裹已禁用的 FileInput。',
|
|
226
|
+
},
|
|
214
227
|
{
|
|
215
228
|
name: 'isLoading',
|
|
216
229
|
type: 'boolean',
|
|
@@ -248,6 +261,7 @@ export const docsZh = {
|
|
|
248
261
|
{guidance: true, description: 'Add a description to communicate constraints.'},
|
|
249
262
|
{guidance: false, description: "Don't use FileInput for directory uploads."},
|
|
250
263
|
{guidance: false, description: "Don't use mode='input' unless space is constrained; dropzone mode provides a better experience."},
|
|
264
|
+
{guidance: false, description: "Don't wrap a disabled FileInput in Tooltip to explain the disabled state; use the disabledMessage prop instead."},
|
|
251
265
|
],
|
|
252
266
|
anatomy: [
|
|
253
267
|
{name: 'Label', required: true, description: 'Text identifying the field.'},
|
|
@@ -272,6 +286,7 @@ export const docsDense = {
|
|
|
272
286
|
{guidance: true, description: 'Use changeAction for immediate upload workflows that benefit from optimistic UI.'},
|
|
273
287
|
{guidance: false, description: "Don't use FileInput for directory uploads."},
|
|
274
288
|
{guidance: false, description: "Don't use mode='input' unless space is constrained; dropzone mode provides a better experience."},
|
|
289
|
+
{guidance: false, description: "Don't wrap a disabled FileInput in Tooltip to explain the disabled state; use the disabledMessage prop instead."},
|
|
275
290
|
],
|
|
276
291
|
anatomy: [
|
|
277
292
|
{name: 'Label', required: true, description: 'Text identifying the field.'},
|
|
@@ -295,6 +310,8 @@ export const docsDense = {
|
|
|
295
310
|
isOptional: 'Shows "Optional" indicator.',
|
|
296
311
|
isRequired: 'Shows "Required" indicator+sets aria-required.',
|
|
297
312
|
isDisabled: 'Disables input, prevents interaction.',
|
|
313
|
+
disabledMessage:
|
|
314
|
+
'Explains why input is disabled. With isDisabled, shows tooltip on hover/focus + keeps trigger focusable via aria-disabled (opening picker stays blocked). Use instead of wrapping a disabled FileInput in Tooltip.',
|
|
298
315
|
isLoading: 'Loading state w/ spinner+aria-busy.',
|
|
299
316
|
placeholder: 'Placeholder when no files selected.',
|
|
300
317
|
mode: "Visual mode: 'input' (compact) or 'dropzone' (drag-and-drop).",
|
|
@@ -9,10 +9,64 @@
|
|
|
9
9
|
* SYNC: When FileInput.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen, fireEvent} from '@testing-library/react';
|
|
12
|
+
import {describe, it, expect, vi, afterEach, beforeEach} from 'vitest';
|
|
13
|
+
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {FileInput} from './FileInput';
|
|
16
|
+
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
__resetLiveRegionsForTest();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Mock showPopover/hidePopover since jsdom does not implement them. Used by the
|
|
23
|
+
// disabledMessage tooltip.
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
26
|
+
this.setAttribute('popover-open', '');
|
|
27
|
+
const event = new Event('toggle', {bubbles: false});
|
|
28
|
+
Object.defineProperty(event, 'newState', {value: 'open'});
|
|
29
|
+
this.dispatchEvent(event);
|
|
30
|
+
});
|
|
31
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
32
|
+
this.removeAttribute('popover-open');
|
|
33
|
+
const event = new Event('toggle', {bubbles: false});
|
|
34
|
+
Object.defineProperty(event, 'newState', {value: 'closed'});
|
|
35
|
+
this.dispatchEvent(event);
|
|
36
|
+
});
|
|
37
|
+
const originalMatches = HTMLElement.prototype.matches;
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
(HTMLElement.prototype as any).matches = function (
|
|
40
|
+
selector: string,
|
|
41
|
+
): boolean {
|
|
42
|
+
if (selector === ':popover-open') {
|
|
43
|
+
return this.hasAttribute('popover-open');
|
|
44
|
+
}
|
|
45
|
+
// jsdom does not resolve :focus-visible for role="button" divs the way a
|
|
46
|
+
// browser does after keyboard focus; treat the active element as
|
|
47
|
+
// focus-visible so the tooltip's keyboard-focus path is exercised.
|
|
48
|
+
if (selector === ':focus-visible') {
|
|
49
|
+
return this === document.activeElement;
|
|
50
|
+
}
|
|
51
|
+
return originalMatches.call(this, selector);
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// jsdom popover content is in the DOM but may not be "visible" in the
|
|
56
|
+
// accessibility tree. Use hidden: true to find it.
|
|
57
|
+
const h = {hidden: true} as const;
|
|
58
|
+
|
|
59
|
+
function politeRegion(): HTMLElement | null {
|
|
60
|
+
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fileInputEl(): HTMLInputElement {
|
|
64
|
+
const el = document.querySelector('input[type="file"]');
|
|
65
|
+
if (!(el instanceof HTMLInputElement)) {
|
|
66
|
+
throw new Error('file input not found');
|
|
67
|
+
}
|
|
68
|
+
return el;
|
|
69
|
+
}
|
|
16
70
|
|
|
17
71
|
function createFile(
|
|
18
72
|
name: string,
|
|
@@ -36,12 +90,7 @@ describe('FileInput', () => {
|
|
|
36
90
|
|
|
37
91
|
it('renders default placeholder for multiple files', () => {
|
|
38
92
|
render(
|
|
39
|
-
<FileInput
|
|
40
|
-
label="Files"
|
|
41
|
-
value={null}
|
|
42
|
-
onChange={() => {}}
|
|
43
|
-
isMultiple
|
|
44
|
-
/>,
|
|
93
|
+
<FileInput label="Files" value={null} onChange={() => {}} isMultiple />,
|
|
45
94
|
);
|
|
46
95
|
expect(screen.getByText('Choose files')).toBeInTheDocument();
|
|
47
96
|
});
|
|
@@ -67,12 +116,7 @@ describe('FileInput', () => {
|
|
|
67
116
|
it('displays multiple file names', () => {
|
|
68
117
|
const files = [createFile('a.txt', 100), createFile('b.txt', 200)];
|
|
69
118
|
render(
|
|
70
|
-
<FileInput
|
|
71
|
-
label="Files"
|
|
72
|
-
value={files}
|
|
73
|
-
onChange={() => {}}
|
|
74
|
-
isMultiple
|
|
75
|
-
/>,
|
|
119
|
+
<FileInput label="Files" value={files} onChange={() => {}} isMultiple />,
|
|
76
120
|
);
|
|
77
121
|
expect(screen.getByText('a.txt, b.txt')).toBeInTheDocument();
|
|
78
122
|
});
|
|
@@ -80,12 +124,7 @@ describe('FileInput', () => {
|
|
|
80
124
|
it('forwards ref to the native input', () => {
|
|
81
125
|
const ref = vi.fn();
|
|
82
126
|
render(
|
|
83
|
-
<FileInput
|
|
84
|
-
ref={ref}
|
|
85
|
-
label="Upload"
|
|
86
|
-
value={null}
|
|
87
|
-
onChange={() => {}}
|
|
88
|
-
/>,
|
|
127
|
+
<FileInput ref={ref} label="Upload" value={null} onChange={() => {}} />,
|
|
89
128
|
);
|
|
90
129
|
expect(ref).toHaveBeenCalledWith(expect.any(HTMLInputElement));
|
|
91
130
|
});
|
|
@@ -103,32 +142,44 @@ describe('FileInput', () => {
|
|
|
103
142
|
});
|
|
104
143
|
|
|
105
144
|
it('sets aria-required when isRequired is true', () => {
|
|
145
|
+
render(
|
|
146
|
+
<FileInput label="Resume" isRequired value={null} onChange={() => {}} />,
|
|
147
|
+
);
|
|
148
|
+
// aria-required lives on the focusable role="button" wrapper, not the
|
|
149
|
+
// hidden file input (forms-6).
|
|
150
|
+
expect(screen.getByRole('button', {name: 'Resume'})).toHaveAttribute(
|
|
151
|
+
'aria-required',
|
|
152
|
+
'true',
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('places aria-describedby on the focusable button, not the hidden input (forms-6)', () => {
|
|
106
157
|
render(
|
|
107
158
|
<FileInput
|
|
108
159
|
label="Resume"
|
|
109
|
-
|
|
160
|
+
description="PDF only"
|
|
110
161
|
value={null}
|
|
111
162
|
onChange={() => {}}
|
|
112
163
|
/>,
|
|
113
164
|
);
|
|
165
|
+
const button = screen.getByRole('button', {name: 'Resume'});
|
|
166
|
+
expect(button).toHaveAttribute('aria-describedby');
|
|
167
|
+
// The hidden file input no longer carries the describedby/required/invalid.
|
|
114
168
|
const input = document.querySelector('input[type="file"]')!;
|
|
115
|
-
expect(input).toHaveAttribute('aria-
|
|
169
|
+
expect(input).not.toHaveAttribute('aria-describedby');
|
|
170
|
+
expect(input).toHaveAttribute('aria-hidden', 'true');
|
|
116
171
|
});
|
|
117
172
|
|
|
118
173
|
it('does not set aria-required by default', () => {
|
|
119
174
|
render(<FileInput label="Resume" value={null} onChange={() => {}} />);
|
|
120
|
-
|
|
121
|
-
|
|
175
|
+
expect(screen.getByRole('button', {name: 'Resume'})).not.toHaveAttribute(
|
|
176
|
+
'aria-required',
|
|
177
|
+
);
|
|
122
178
|
});
|
|
123
179
|
|
|
124
180
|
it('sets disabled attribute when isDisabled is true', () => {
|
|
125
181
|
render(
|
|
126
|
-
<FileInput
|
|
127
|
-
label="Upload"
|
|
128
|
-
isDisabled
|
|
129
|
-
value={null}
|
|
130
|
-
onChange={() => {}}
|
|
131
|
-
/>,
|
|
182
|
+
<FileInput label="Upload" isDisabled value={null} onChange={() => {}} />,
|
|
132
183
|
);
|
|
133
184
|
const input = document.querySelector('input[type="file"]')!;
|
|
134
185
|
expect(input).toBeDisabled();
|
|
@@ -143,8 +194,10 @@ describe('FileInput', () => {
|
|
|
143
194
|
status={{type: 'error', message: 'Something went wrong'}}
|
|
144
195
|
/>,
|
|
145
196
|
);
|
|
146
|
-
|
|
147
|
-
|
|
197
|
+
expect(screen.getByRole('button', {name: 'Upload'})).toHaveAttribute(
|
|
198
|
+
'aria-invalid',
|
|
199
|
+
'true',
|
|
200
|
+
);
|
|
148
201
|
});
|
|
149
202
|
|
|
150
203
|
it('does not set aria-invalid for warning status', () => {
|
|
@@ -156,8 +209,9 @@ describe('FileInput', () => {
|
|
|
156
209
|
status={{type: 'warning'}}
|
|
157
210
|
/>,
|
|
158
211
|
);
|
|
159
|
-
|
|
160
|
-
|
|
212
|
+
expect(screen.getByRole('button', {name: 'Upload'})).not.toHaveAttribute(
|
|
213
|
+
'aria-invalid',
|
|
214
|
+
);
|
|
161
215
|
});
|
|
162
216
|
|
|
163
217
|
it('renders status message when provided', () => {
|
|
@@ -187,9 +241,7 @@ describe('FileInput', () => {
|
|
|
187
241
|
describe('file selection via native input', () => {
|
|
188
242
|
it('calls onChange when a file is selected', () => {
|
|
189
243
|
const handleChange = vi.fn();
|
|
190
|
-
render(
|
|
191
|
-
<FileInput label="Upload" value={null} onChange={handleChange} />,
|
|
192
|
-
);
|
|
244
|
+
render(<FileInput label="Upload" value={null} onChange={handleChange} />);
|
|
193
245
|
const input = document.querySelector(
|
|
194
246
|
'input[type="file"]',
|
|
195
247
|
) as HTMLInputElement;
|
|
@@ -247,6 +299,55 @@ describe('FileInput', () => {
|
|
|
247
299
|
});
|
|
248
300
|
});
|
|
249
301
|
|
|
302
|
+
describe('announcements', () => {
|
|
303
|
+
it('announces a single file selection politely', async () => {
|
|
304
|
+
render(<FileInput label="Upload" value={null} onChange={() => {}} />);
|
|
305
|
+
fireEvent.change(fileInputEl(), {
|
|
306
|
+
target: {files: [createFile('report.pdf', 100)]},
|
|
307
|
+
});
|
|
308
|
+
await waitFor(() => {
|
|
309
|
+
expect(politeRegion()).toHaveTextContent('1 file selected: report.pdf');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('announces a multi-file count politely', async () => {
|
|
314
|
+
render(
|
|
315
|
+
<FileInput
|
|
316
|
+
label="Upload"
|
|
317
|
+
value={null}
|
|
318
|
+
onChange={() => {}}
|
|
319
|
+
isMultiple
|
|
320
|
+
/>,
|
|
321
|
+
);
|
|
322
|
+
const files = [
|
|
323
|
+
createFile('a.txt', 100),
|
|
324
|
+
createFile('b.txt', 200),
|
|
325
|
+
createFile('c.txt', 300),
|
|
326
|
+
];
|
|
327
|
+
fireEvent.change(fileInputEl(), {target: {files}});
|
|
328
|
+
await waitFor(() => {
|
|
329
|
+
expect(politeRegion()).toHaveTextContent('3 files selected');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('does not announce a selection when validation rejects all files', async () => {
|
|
334
|
+
render(
|
|
335
|
+
<FileInput
|
|
336
|
+
label="Upload"
|
|
337
|
+
value={null}
|
|
338
|
+
onChange={() => {}}
|
|
339
|
+
accept=".pdf"
|
|
340
|
+
/>,
|
|
341
|
+
);
|
|
342
|
+
fireEvent.change(fileInputEl(), {
|
|
343
|
+
target: {files: [createFile('note.txt', 100)]},
|
|
344
|
+
});
|
|
345
|
+
// A rejected selection creates no polite region (only the error goes to
|
|
346
|
+
// the existing role="status" region).
|
|
347
|
+
expect(politeRegion()).toBeNull();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
250
351
|
describe('validation', () => {
|
|
251
352
|
it('rejects files exceeding maxSize', () => {
|
|
252
353
|
const handleChange = vi.fn();
|
|
@@ -382,9 +483,7 @@ describe('FileInput', () => {
|
|
|
382
483
|
const user = userEvent.setup();
|
|
383
484
|
const handleChange = vi.fn();
|
|
384
485
|
const file = createFile('test.txt', 100);
|
|
385
|
-
render(
|
|
386
|
-
<FileInput label="Upload" value={file} onChange={handleChange} />,
|
|
387
|
-
);
|
|
486
|
+
render(<FileInput label="Upload" value={file} onChange={handleChange} />);
|
|
388
487
|
await user.click(screen.getByRole('button', {name: 'Clear Upload'}));
|
|
389
488
|
expect(handleChange).toHaveBeenCalledWith(null);
|
|
390
489
|
});
|
|
@@ -392,12 +491,7 @@ describe('FileInput', () => {
|
|
|
392
491
|
it('does not show clear button during loading', () => {
|
|
393
492
|
const file = createFile('test.txt', 100);
|
|
394
493
|
render(
|
|
395
|
-
<FileInput
|
|
396
|
-
label="Upload"
|
|
397
|
-
value={file}
|
|
398
|
-
onChange={() => {}}
|
|
399
|
-
isLoading
|
|
400
|
-
/>,
|
|
494
|
+
<FileInput label="Upload" value={file} onChange={() => {}} isLoading />,
|
|
401
495
|
);
|
|
402
496
|
expect(
|
|
403
497
|
screen.queryByRole('button', {name: 'Clear Upload'}),
|
|
@@ -426,9 +520,7 @@ describe('FileInput', () => {
|
|
|
426
520
|
|
|
427
521
|
it('does not handle drop in input mode', () => {
|
|
428
522
|
const handleChange = vi.fn();
|
|
429
|
-
render(
|
|
430
|
-
<FileInput label="Upload" value={null} onChange={handleChange} />,
|
|
431
|
-
);
|
|
523
|
+
render(<FileInput label="Upload" value={null} onChange={handleChange} />);
|
|
432
524
|
const dropzone = screen.getByRole('button', {name: 'Upload'});
|
|
433
525
|
const file = createFile('dropped.txt', 100);
|
|
434
526
|
fireEvent.drop(dropzone, {
|
|
@@ -526,4 +618,146 @@ describe('FileInput', () => {
|
|
|
526
618
|
expect(input).toHaveAttribute('data-testid', 'file-upload');
|
|
527
619
|
});
|
|
528
620
|
});
|
|
621
|
+
|
|
622
|
+
describe('disabledMessage', () => {
|
|
623
|
+
it('shows the reason tooltip on hover when disabled with a reason', async () => {
|
|
624
|
+
render(
|
|
625
|
+
<FileInput
|
|
626
|
+
label="Resume"
|
|
627
|
+
value={null}
|
|
628
|
+
onChange={() => {}}
|
|
629
|
+
isDisabled
|
|
630
|
+
disabledMessage="You need the Editor role"
|
|
631
|
+
/>,
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
const trigger = screen.getByRole('button');
|
|
635
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
636
|
+
expect(tooltip).toHaveTextContent('You need the Editor role');
|
|
637
|
+
|
|
638
|
+
fireEvent.mouseEnter(trigger);
|
|
639
|
+
await waitFor(() => {
|
|
640
|
+
expect(tooltip).toHaveAttribute('popover-open');
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
fireEvent.mouseLeave(trigger);
|
|
644
|
+
await waitFor(() => {
|
|
645
|
+
expect(tooltip).not.toHaveAttribute('popover-open');
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('shows the reason tooltip on keyboard focus', async () => {
|
|
650
|
+
const user = userEvent.setup();
|
|
651
|
+
render(
|
|
652
|
+
<FileInput
|
|
653
|
+
label="Resume"
|
|
654
|
+
value={null}
|
|
655
|
+
onChange={() => {}}
|
|
656
|
+
isDisabled
|
|
657
|
+
disabledMessage="You need the Editor role"
|
|
658
|
+
/>,
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
662
|
+
await user.tab();
|
|
663
|
+
expect(screen.getByRole('button')).toHaveFocus();
|
|
664
|
+
await waitFor(() => {
|
|
665
|
+
expect(tooltip).toHaveAttribute('popover-open');
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('does not render a tooltip when not disabled', () => {
|
|
670
|
+
render(
|
|
671
|
+
<FileInput
|
|
672
|
+
label="Resume"
|
|
673
|
+
value={null}
|
|
674
|
+
onChange={() => {}}
|
|
675
|
+
disabledMessage="You need the Editor role"
|
|
676
|
+
/>,
|
|
677
|
+
);
|
|
678
|
+
expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it('does not render a tooltip when disabled without a reason', () => {
|
|
682
|
+
render(
|
|
683
|
+
<FileInput
|
|
684
|
+
label="Resume"
|
|
685
|
+
value={null}
|
|
686
|
+
onChange={() => {}}
|
|
687
|
+
isDisabled
|
|
688
|
+
/>,
|
|
689
|
+
);
|
|
690
|
+
expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('keeps the trigger focusable via aria-disabled when a reason is provided', () => {
|
|
694
|
+
render(
|
|
695
|
+
<FileInput
|
|
696
|
+
label="Resume"
|
|
697
|
+
value={null}
|
|
698
|
+
onChange={() => {}}
|
|
699
|
+
isDisabled
|
|
700
|
+
disabledMessage="You need the Editor role"
|
|
701
|
+
/>,
|
|
702
|
+
);
|
|
703
|
+
const trigger = screen.getByRole('button');
|
|
704
|
+
expect(trigger).toHaveAttribute('aria-disabled', 'true');
|
|
705
|
+
expect(trigger).toHaveAttribute('tabindex', '0');
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('links the reason tooltip from the trigger via aria-describedby', () => {
|
|
709
|
+
render(
|
|
710
|
+
<FileInput
|
|
711
|
+
label="Resume"
|
|
712
|
+
value={null}
|
|
713
|
+
onChange={() => {}}
|
|
714
|
+
isDisabled
|
|
715
|
+
disabledMessage="You need the Editor role"
|
|
716
|
+
/>,
|
|
717
|
+
);
|
|
718
|
+
const trigger = screen.getByRole('button');
|
|
719
|
+
const tooltip = screen.getByRole('tooltip', h);
|
|
720
|
+
expect(trigger.getAttribute('aria-describedby')).toContain(tooltip.id);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it('blocks opening the file picker while focusable-disabled', async () => {
|
|
724
|
+
const user = userEvent.setup();
|
|
725
|
+
render(
|
|
726
|
+
<FileInput
|
|
727
|
+
label="Resume"
|
|
728
|
+
value={null}
|
|
729
|
+
onChange={() => {}}
|
|
730
|
+
isDisabled
|
|
731
|
+
disabledMessage="You need the Editor role"
|
|
732
|
+
/>,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const trigger = screen.getByRole('button');
|
|
736
|
+
const input = document.querySelector(
|
|
737
|
+
'input[type="file"]',
|
|
738
|
+
) as HTMLInputElement;
|
|
739
|
+
const clickSpy = vi.spyOn(input, 'click');
|
|
740
|
+
|
|
741
|
+
await user.click(trigger);
|
|
742
|
+
trigger.focus();
|
|
743
|
+
await user.keyboard('{Enter}');
|
|
744
|
+
await user.keyboard(' ');
|
|
745
|
+
|
|
746
|
+
expect(clickSpy).not.toHaveBeenCalled();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('keeps the trigger non-focusable when disabled without a reason', () => {
|
|
750
|
+
render(
|
|
751
|
+
<FileInput
|
|
752
|
+
label="Resume"
|
|
753
|
+
value={null}
|
|
754
|
+
onChange={() => {}}
|
|
755
|
+
isDisabled
|
|
756
|
+
/>,
|
|
757
|
+
);
|
|
758
|
+
const trigger = screen.getByRole('button');
|
|
759
|
+
expect(trigger).toHaveAttribute('tabindex', '-1');
|
|
760
|
+
expect(trigger).not.toHaveAttribute('aria-disabled');
|
|
761
|
+
});
|
|
762
|
+
});
|
|
529
763
|
});
|
|
@@ -44,6 +44,8 @@ import {
|
|
|
44
44
|
} from '../Field';
|
|
45
45
|
import {Icon, type IconName} from '../Icon';
|
|
46
46
|
import {Spinner} from '../Spinner';
|
|
47
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
48
|
+
import {useTooltip} from '../Tooltip';
|
|
47
49
|
|
|
48
50
|
export type {
|
|
49
51
|
InputStatus as FileInputStatus,
|
|
@@ -320,6 +322,27 @@ export interface FileInputProps extends Omit<
|
|
|
320
322
|
* @default false
|
|
321
323
|
*/
|
|
322
324
|
isDisabled?: boolean;
|
|
325
|
+
/**
|
|
326
|
+
* Explains why the input is disabled. When set together with `isDisabled`,
|
|
327
|
+
* the file input shows a tooltip with this text on hover and keyboard focus,
|
|
328
|
+
* and its trigger stays focusable (via `aria-disabled`) so the reason is
|
|
329
|
+
* discoverable by keyboard and assistive technology. Opening the file picker
|
|
330
|
+
* stays blocked.
|
|
331
|
+
*
|
|
332
|
+
* Use this instead of wrapping a disabled input in `Tooltip` — disabled
|
|
333
|
+
* controls don't emit the pointer events an external tooltip needs.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```
|
|
337
|
+
* <FileInput
|
|
338
|
+
* label="Resume"
|
|
339
|
+
* value={file}
|
|
340
|
+
* isDisabled
|
|
341
|
+
* disabledMessage="Uploads are locked until your profile is verified"
|
|
342
|
+
* />
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
disabledMessage?: string;
|
|
323
346
|
/**
|
|
324
347
|
* Whether the input is required.
|
|
325
348
|
* @default false
|
|
@@ -386,6 +409,7 @@ export function FileInput({
|
|
|
386
409
|
maxSize,
|
|
387
410
|
maxFiles,
|
|
388
411
|
isDisabled = false,
|
|
412
|
+
disabledMessage,
|
|
389
413
|
isRequired = false,
|
|
390
414
|
isLoading = false,
|
|
391
415
|
status: statusProp,
|
|
@@ -410,6 +434,23 @@ export function FileInput({
|
|
|
410
434
|
const [validationError, setValidationError] = useState<string | null>(null);
|
|
411
435
|
const [, startTransition] = useTransition();
|
|
412
436
|
|
|
437
|
+
// Announce successful file selection to screen readers via a persistent
|
|
438
|
+
// live region (forms-17). The component's own role="status" region only
|
|
439
|
+
// carries validation errors, so a successful attach was previously silent.
|
|
440
|
+
const announce = useAnnounce();
|
|
441
|
+
|
|
442
|
+
// Disabled-reason tooltip. Disabled controls swallow pointer events, so the
|
|
443
|
+
// tooltip listeners attach to the role="button" trigger (which already
|
|
444
|
+
// exists) and the trigger stays perceivable via aria-disabled instead of the
|
|
445
|
+
// native disabled attribute. Opening the picker is blocked by the isDisabled
|
|
446
|
+
// guards in handleClick / handleKeyDown / handleFiles.
|
|
447
|
+
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
448
|
+
const disabledMessageTooltip = useTooltip({
|
|
449
|
+
placement: 'above',
|
|
450
|
+
focusTrigger: 'always',
|
|
451
|
+
isEnabled: showsDisabledMessage,
|
|
452
|
+
});
|
|
453
|
+
|
|
413
454
|
const status =
|
|
414
455
|
statusProp ??
|
|
415
456
|
(validationError
|
|
@@ -435,6 +476,7 @@ export function FileInput({
|
|
|
435
476
|
[
|
|
436
477
|
description ? descriptionID : null,
|
|
437
478
|
status?.message ? statusMessageID : null,
|
|
479
|
+
showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
|
|
438
480
|
]
|
|
439
481
|
.filter(Boolean)
|
|
440
482
|
.join(' ') || undefined;
|
|
@@ -470,6 +512,17 @@ export function FileInput({
|
|
|
470
512
|
const result = isMultiple ? valid : valid[0];
|
|
471
513
|
onChange(result);
|
|
472
514
|
|
|
515
|
+
// Announce the successful selection politely. Validation errors are
|
|
516
|
+
// handled by the role="status" region below, so only announce the
|
|
517
|
+
// attach here (do not double-announce errors).
|
|
518
|
+
if (errors.length === 0) {
|
|
519
|
+
announce(
|
|
520
|
+
valid.length === 1
|
|
521
|
+
? `1 file selected: ${valid[0].name}`
|
|
522
|
+
: `${valid.length} files selected`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
473
526
|
if (changeAction) {
|
|
474
527
|
startTransition(async () => {
|
|
475
528
|
await changeAction(result);
|
|
@@ -485,6 +538,7 @@ export function FileInput({
|
|
|
485
538
|
onChange,
|
|
486
539
|
changeAction,
|
|
487
540
|
startTransition,
|
|
541
|
+
announce,
|
|
488
542
|
],
|
|
489
543
|
);
|
|
490
544
|
|
|
@@ -666,12 +720,28 @@ export function FileInput({
|
|
|
666
720
|
labelTooltip={labelTooltip}
|
|
667
721
|
width={width}>
|
|
668
722
|
<div
|
|
723
|
+
ref={el => {
|
|
724
|
+
// Anchor + hover/focus listeners for the disabled-message tooltip.
|
|
725
|
+
// Handlers are gated internally by isEnabled, and anchor names
|
|
726
|
+
// compose, so attaching unconditionally is safe.
|
|
727
|
+
disabledMessageTooltip.ref(el);
|
|
728
|
+
}}
|
|
669
729
|
role="button"
|
|
670
|
-
|
|
730
|
+
// With a disabledMessage the trigger keeps focusability via
|
|
731
|
+
// aria-disabled so the reason is focus-discoverable; opening the picker
|
|
732
|
+
// is still blocked by the isDisabled guards in the handlers.
|
|
733
|
+
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
734
|
+
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
671
735
|
onClick={handleClick}
|
|
672
736
|
onKeyDown={handleKeyDown}
|
|
673
737
|
aria-label={label}
|
|
674
738
|
aria-busy={isLoading || undefined}
|
|
739
|
+
// These describe the operable control, so they belong on the focusable
|
|
740
|
+
// role="button" wrapper — not the hidden tabIndex={-1} file input the
|
|
741
|
+
// user never focuses (forms-6).
|
|
742
|
+
aria-describedby={ariaDescribedBy}
|
|
743
|
+
aria-required={isRequired ? 'true' : undefined}
|
|
744
|
+
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
675
745
|
{...dragDropProps}
|
|
676
746
|
{...mergeProps(
|
|
677
747
|
themeProps('file-input', {mode, status: status?.type ?? null}),
|
|
@@ -696,9 +766,9 @@ export function FileInput({
|
|
|
696
766
|
multiple={isMultiple}
|
|
697
767
|
disabled={isDisabled}
|
|
698
768
|
onChange={handleInputChange}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
aria-
|
|
769
|
+
// The visually-hidden native input is never focused (tabIndex={-1});
|
|
770
|
+
// its describedby/required/invalid live on the role="button" wrapper.
|
|
771
|
+
aria-hidden="true"
|
|
702
772
|
tabIndex={-1}
|
|
703
773
|
{...stylex.props(styles.hiddenInput)}
|
|
704
774
|
/>
|
|
@@ -714,6 +784,8 @@ export function FileInput({
|
|
|
714
784
|
{...stylex.props(styles.liveRegion)}>
|
|
715
785
|
{validationError}
|
|
716
786
|
</div>
|
|
787
|
+
{showsDisabledMessage &&
|
|
788
|
+
disabledMessageTooltip.renderTooltip(disabledMessage)}
|
|
717
789
|
</Field>
|
|
718
790
|
);
|
|
719
791
|
}
|