@react-md/core 6.4.0 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_a11y.scss +3 -1
- package/dist/_box-shadows.scss +20 -12
- package/dist/_core.scss +1 -1
- package/dist/_utils.scss +26 -11
- package/dist/app-bar/_app-bar.scss +3 -3
- package/dist/autocomplete/AutocompleteChip.js +2 -2
- package/dist/autocomplete/AutocompleteChip.js.map +1 -1
- package/dist/autocomplete/AutocompleteListboxChildren.js +1 -1
- package/dist/autocomplete/AutocompleteListboxChildren.js.map +1 -1
- package/dist/autocomplete/_autocomplete.scss +20 -16
- package/dist/autocomplete/useAutocomplete.js +4 -4
- package/dist/autocomplete/useAutocomplete.js.map +1 -1
- package/dist/autocomplete/utils.js +3 -3
- package/dist/autocomplete/utils.js.map +1 -1
- package/dist/avatar/_avatar.scss +2 -1
- package/dist/box/styles.js +2 -2
- package/dist/box/styles.js.map +1 -1
- package/dist/button/AsyncButton.js +1 -1
- package/dist/button/AsyncButton.js.map +1 -1
- package/dist/button/_button.scss +9 -5
- package/dist/card/_card.scss +6 -6
- package/dist/chip/Chip.js +1 -1
- package/dist/chip/Chip.js.map +1 -1
- package/dist/chip/_chip.scss +6 -6
- package/dist/cssUtils.d.ts +11 -6
- package/dist/cssUtils.js.map +1 -1
- package/dist/datetime/useTimeField.js +1 -1
- package/dist/datetime/useTimeField.js.map +1 -1
- package/dist/delegateEvent.js +9 -9
- package/dist/delegateEvent.js.map +1 -1
- package/dist/dialog/_dialog.scss +6 -6
- package/dist/divider/_divider.scss +6 -2
- package/dist/draggable/useDraggable.js +4 -4
- package/dist/draggable/useDraggable.js.map +1 -1
- package/dist/draggable/utils.js +1 -1
- package/dist/draggable/utils.js.map +1 -1
- package/dist/expansion-panel/ExpansionPanel.js +1 -1
- package/dist/expansion-panel/ExpansionPanel.js.map +1 -1
- package/dist/expansion-panel/useExpansionPanels.js +1 -1
- package/dist/expansion-panel/useExpansionPanels.js.map +1 -1
- package/dist/files/FileInput.js +1 -1
- package/dist/files/FileInput.js.map +1 -1
- package/dist/files/createAcceptFromExtensions.d.ts +5 -0
- package/dist/files/createAcceptFromExtensions.js +15 -0
- package/dist/files/createAcceptFromExtensions.js.map +1 -0
- package/dist/files/useFileUpload.js +45 -41
- package/dist/files/useFileUpload.js.map +1 -1
- package/dist/files/utils.js +14 -10
- package/dist/files/utils.js.map +1 -1
- package/dist/files/validation.js +7 -8
- package/dist/files/validation.js.map +1 -1
- package/dist/focus/useFocusContainer.js +1 -1
- package/dist/focus/useFocusContainer.js.map +1 -1
- package/dist/focus/utils.js +12 -7
- package/dist/focus/utils.js.map +1 -1
- package/dist/form/InputToggleIcon.js +5 -1
- package/dist/form/InputToggleIcon.js.map +1 -1
- package/dist/form/NativeSelect.js +1 -1
- package/dist/form/NativeSelect.js.map +1 -1
- package/dist/form/Select.d.ts +24 -0
- package/dist/form/Select.js +19 -8
- package/dist/form/Select.js.map +1 -1
- package/dist/form/SelectedOption.d.ts +1 -2
- package/dist/form/SelectedOption.js +2 -2
- package/dist/form/SelectedOption.js.map +1 -1
- package/dist/form/_input-toggle.scss +6 -5
- package/dist/form/_label.scss +2 -2
- package/dist/form/_legend.scss +22 -13
- package/dist/form/_slider.scss +7 -5
- package/dist/form/_switch.scss +7 -5
- package/dist/form/_text-field.scss +13 -11
- package/dist/form/formConfig.js +1 -1
- package/dist/form/formConfig.js.map +1 -1
- package/dist/form/inputToggleStyles.js +7 -1
- package/dist/form/inputToggleStyles.js.map +1 -1
- package/dist/form/legendStyles.d.ts +1 -1
- package/dist/form/legendStyles.js.map +1 -1
- package/dist/form/selectUtils.js +2 -2
- package/dist/form/selectUtils.js.map +1 -1
- package/dist/form/useCombobox.js +1 -0
- package/dist/form/useCombobox.js.map +1 -1
- package/dist/form/useFormReset.js +2 -2
- package/dist/form/useFormReset.js.map +1 -1
- package/dist/form/useNumberField.js +1 -1
- package/dist/form/useNumberField.js.map +1 -1
- package/dist/form/useResizingTextArea.js +4 -4
- package/dist/form/useResizingTextArea.js.map +1 -1
- package/dist/form/useSelectCombobox.js +1 -1
- package/dist/form/useSelectCombobox.js.map +1 -1
- package/dist/form/validation.js +1 -1
- package/dist/form/validation.js.map +1 -1
- package/dist/hoverMode/useHoverMode.js +8 -8
- package/dist/hoverMode/useHoverMode.js.map +1 -1
- package/dist/hoverMode/useHoverModeProvider.js +3 -3
- package/dist/hoverMode/useHoverModeProvider.js.map +1 -1
- package/dist/icon/config.js +3 -3
- package/dist/icon/config.js.map +1 -1
- package/dist/icon/materialConfig.js +1 -1
- package/dist/icon/materialConfig.js.map +1 -1
- package/dist/interaction/UserInteractionModeProvider.js +11 -10
- package/dist/interaction/UserInteractionModeProvider.js.map +1 -1
- package/dist/interaction/_interaction.scss +5 -3
- package/dist/interaction/utils.js +7 -3
- package/dist/interaction/utils.js.map +1 -1
- package/dist/layout/useExpandableLayout.js +3 -4
- package/dist/layout/useExpandableLayout.js.map +1 -1
- package/dist/layout/useMainTabIndex.js +1 -1
- package/dist/layout/useMainTabIndex.js.map +1 -1
- package/dist/list/ListItem.js +1 -1
- package/dist/list/ListItem.js.map +1 -1
- package/dist/media-queries/AppSizeProvider.js +1 -1
- package/dist/media-queries/AppSizeProvider.js.map +1 -1
- package/dist/media-queries/config.js +2 -2
- package/dist/media-queries/config.js.map +1 -1
- package/dist/media-queries/useMediaQuery.js +3 -3
- package/dist/media-queries/useMediaQuery.js.map +1 -1
- package/dist/menu/Menu.js +4 -4
- package/dist/menu/Menu.js.map +1 -1
- package/dist/menu/MenuItemButton.js +1 -1
- package/dist/menu/MenuItemButton.js.map +1 -1
- package/dist/menu/MenuItemFileInput.js +1 -1
- package/dist/menu/MenuItemFileInput.js.map +1 -1
- package/dist/menu/MenuWidget.js +2 -2
- package/dist/menu/MenuWidget.js.map +1 -1
- package/dist/movement/findMatchIndex.js +2 -2
- package/dist/movement/findMatchIndex.js.map +1 -1
- package/dist/movement/useKeyboardMovementProvider.js +2 -2
- package/dist/movement/useKeyboardMovementProvider.js.map +1 -1
- package/dist/movement/utils.js +12 -10
- package/dist/movement/utils.js.map +1 -1
- package/dist/navigation/getTableOfContentsHeadings.js +4 -3
- package/dist/navigation/getTableOfContentsHeadings.js.map +1 -1
- package/dist/navigation/useActiveHeadingId.js +9 -9
- package/dist/navigation/useActiveHeadingId.js.map +1 -1
- package/dist/navigation/useTableOfContentsHeadings.js +1 -1
- package/dist/navigation/useTableOfContentsHeadings.js.map +1 -1
- package/dist/navigation/utils.js +6 -5
- package/dist/navigation/utils.js.map +1 -1
- package/dist/portal/PortalContainerProvider.js +5 -3
- package/dist/portal/PortalContainerProvider.js.map +1 -1
- package/dist/positioning/getFixedPosition.js +2 -4
- package/dist/positioning/getFixedPosition.js.map +1 -1
- package/dist/positioning/useFixedPositioning.js +2 -2
- package/dist/positioning/useFixedPositioning.js.map +1 -1
- package/dist/positioning/utils.js +3 -3
- package/dist/positioning/utils.js.map +1 -1
- package/dist/scroll/getScrollbarWidth.js +4 -4
- package/dist/scroll/getScrollbarWidth.js.map +1 -1
- package/dist/searching/fuzzy.js +3 -2
- package/dist/searching/fuzzy.js.map +1 -1
- package/dist/searching/toSearchQuery.js +1 -1
- package/dist/searching/toSearchQuery.js.map +1 -1
- package/dist/searching/utils.js +1 -1
- package/dist/searching/utils.js.map +1 -1
- package/dist/snackbar/Toast.js +1 -1
- package/dist/snackbar/Toast.js.map +1 -1
- package/dist/snackbar/ToastContent.js +2 -2
- package/dist/snackbar/ToastContent.js.map +1 -1
- package/dist/snackbar/ToastManager.d.ts +1 -1
- package/dist/snackbar/ToastManager.js +11 -11
- package/dist/snackbar/ToastManager.js.map +1 -1
- package/dist/snackbar/_snackbar.scss +3 -3
- package/dist/spinbutton/useSpinButton.js +1 -1
- package/dist/spinbutton/useSpinButton.js.map +1 -1
- package/dist/spinbutton/utils/deselectNode.js +1 -1
- package/dist/spinbutton/utils/deselectNode.js.map +1 -1
- package/dist/spinbutton/utils/resolveInputEvent.js +1 -1
- package/dist/spinbutton/utils/resolveInputEvent.js.map +1 -1
- package/dist/spinbutton/utils/selectNode.js +1 -1
- package/dist/spinbutton/utils/selectNode.js.map +1 -1
- package/dist/storage/useStorage.js +8 -3
- package/dist/storage/useStorage.js.map +1 -1
- package/dist/table/useStickyTableSection.js +1 -1
- package/dist/table/useStickyTableSection.js.map +1 -1
- package/dist/tabs/TabList.js +2 -2
- package/dist/tabs/TabList.js.map +1 -1
- package/dist/tabs/_tabs.scss +5 -6
- package/dist/tabs/useMaxTabPanelHeight.js +4 -3
- package/dist/tabs/useMaxTabPanelHeight.js.map +1 -1
- package/dist/tabs/useTabList.js +1 -1
- package/dist/tabs/useTabList.js.map +1 -1
- package/dist/test-utils/jest-globals/match-media.d.ts +1 -1
- package/dist/test-utils/jest-globals/match-media.js +1 -1
- package/dist/test-utils/jest-globals/match-media.js.map +1 -1
- package/dist/test-utils/jest-globals/timers.js +1 -1
- package/dist/test-utils/jest-globals/timers.js.map +1 -1
- package/dist/test-utils/jest-globals/uploadMenuItemFileInput.js +1 -1
- package/dist/test-utils/jest-globals/uploadMenuItemFileInput.js.map +1 -1
- package/dist/test-utils/mocks/ResizeObserver.js +2 -2
- package/dist/test-utils/mocks/ResizeObserver.js.map +1 -1
- package/dist/test-utils/polyfills/IntersectionObserver.js +2 -2
- package/dist/test-utils/polyfills/IntersectionObserver.js.map +1 -1
- package/dist/test-utils/polyfills/ResizeObserver.js +2 -2
- package/dist/test-utils/polyfills/ResizeObserver.js.map +1 -1
- package/dist/test-utils/polyfills/TextDecoder.js +2 -2
- package/dist/test-utils/polyfills/TextDecoder.js.map +1 -1
- package/dist/test-utils/polyfills/TextEncoder.js +2 -2
- package/dist/test-utils/polyfills/TextEncoder.js.map +1 -1
- package/dist/test-utils/polyfills/matchMedia.js +2 -2
- package/dist/test-utils/polyfills/matchMedia.js.map +1 -1
- package/dist/test-utils/polyfills/offsetParent.js +2 -2
- package/dist/test-utils/polyfills/offsetParent.js.map +1 -1
- package/dist/test-utils/polyfills/scrollIntoView.js +1 -1
- package/dist/test-utils/polyfills/scrollIntoView.js.map +1 -1
- package/dist/test-utils/queries/select.js +2 -2
- package/dist/test-utils/queries/select.js.map +1 -1
- package/dist/test-utils/queries/slider.js +1 -1
- package/dist/test-utils/queries/slider.js.map +1 -1
- package/dist/test-utils/utils/createFileList.js +2 -0
- package/dist/test-utils/utils/createFileList.js.map +1 -1
- package/dist/test-utils/utils/createMatchMediaSpy.d.ts +1 -1
- package/dist/test-utils/utils/createMatchMediaSpy.js +3 -3
- package/dist/test-utils/utils/createMatchMediaSpy.js.map +1 -1
- package/dist/test-utils/vitest/match-media.d.ts +1 -1
- package/dist/test-utils/vitest/match-media.js +1 -1
- package/dist/test-utils/vitest/match-media.js.map +1 -1
- package/dist/test-utils/vitest/timers.js +1 -1
- package/dist/test-utils/vitest/timers.js.map +1 -1
- package/dist/test-utils/vitest/uploadMenuItemFileInput.js +1 -1
- package/dist/test-utils/vitest/uploadMenuItemFileInput.js.map +1 -1
- package/dist/theme/ThemeProvider.js +2 -2
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/_a11y.scss +3 -1
- package/dist/theme/_theme.scss +16 -12
- package/dist/theme/getDerivedTheme.js +1 -1
- package/dist/theme/getDerivedTheme.js.map +1 -1
- package/dist/theme/useCSSVariables.js +5 -5
- package/dist/theme/useCSSVariables.js.map +1 -1
- package/dist/theme/useColorSchemeMetaTag.js +2 -2
- package/dist/theme/useColorSchemeMetaTag.js.map +1 -1
- package/dist/theme/useInlineCSSVariables.js +4 -3
- package/dist/theme/useInlineCSSVariables.js.map +1 -1
- package/dist/theme/utils.js +8 -8
- package/dist/theme/utils.js.map +1 -1
- package/dist/tooltip/useTooltip.js +7 -7
- package/dist/tooltip/useTooltip.js.map +1 -1
- package/dist/tooltip/useTooltipPosition.js +1 -1
- package/dist/tooltip/useTooltipPosition.js.map +1 -1
- package/dist/transition/useCarousel.js +2 -2
- package/dist/transition/useCarousel.js.map +1 -1
- package/dist/transition/useCollapseTransition.js +1 -1
- package/dist/transition/useCollapseTransition.js.map +1 -1
- package/dist/transition/useSkeletonPlaceholder.js +4 -4
- package/dist/transition/useSkeletonPlaceholder.js.map +1 -1
- package/dist/transition/useTransition.js +2 -2
- package/dist/transition/useTransition.js.map +1 -1
- package/dist/transition/utils.js +5 -5
- package/dist/transition/utils.js.map +1 -1
- package/dist/tree/TreeItem.js +1 -1
- package/dist/tree/TreeItem.js.map +1 -1
- package/dist/tree/useTreeItems.js +7 -5
- package/dist/tree/useTreeItems.js.map +1 -1
- package/dist/tree/useTreeMovement.js +1 -1
- package/dist/tree/useTreeMovement.js.map +1 -1
- package/dist/tree/utils.js +6 -9
- package/dist/tree/utils.js.map +1 -1
- package/dist/typography/HighlightText.js +2 -1
- package/dist/typography/HighlightText.js.map +1 -1
- package/dist/typography/SrOnly.js +7 -1
- package/dist/typography/SrOnly.js.map +1 -1
- package/dist/useDebouncedFunction.js +4 -4
- package/dist/useDebouncedFunction.js.map +1 -1
- package/dist/useDropzone.js +9 -9
- package/dist/useDropzone.js.map +1 -1
- package/dist/useEnsuredState.js +5 -5
- package/dist/useEnsuredState.js.map +1 -1
- package/dist/useIntersectionObserver.js +3 -3
- package/dist/useIntersectionObserver.js.map +1 -1
- package/dist/useIsomorphicLayoutEffect.js +1 -1
- package/dist/useIsomorphicLayoutEffect.js.map +1 -1
- package/dist/useOrientation.js +1 -1
- package/dist/useOrientation.js.map +1 -1
- package/dist/useReadonlySet.js +1 -1
- package/dist/useReadonlySet.js.map +1 -1
- package/dist/useResizeListener.js +2 -2
- package/dist/useResizeListener.js.map +1 -1
- package/dist/useResizeObserver.js +3 -4
- package/dist/useResizeObserver.js.map +1 -1
- package/dist/useThrottledFunction.js +3 -3
- package/dist/useThrottledFunction.js.map +1 -1
- package/dist/useWindowSize.js +1 -1
- package/dist/useWindowSize.js.map +1 -1
- package/dist/utils/alphaNumericSort.js +3 -1
- package/dist/utils/alphaNumericSort.js.map +1 -1
- package/dist/utils/bem.js +9 -12
- package/dist/utils/bem.js.map +1 -1
- package/dist/utils/getNumberOfDigits.js +1 -0
- package/dist/utils/getNumberOfDigits.js.map +1 -1
- package/dist/utils/getRangeDefaultValue.js +1 -1
- package/dist/utils/getRangeDefaultValue.js.map +1 -1
- package/dist/utils/nearest.js +2 -2
- package/dist/utils/nearest.js.map +1 -1
- package/dist/utils/parseCssLengthUnit.js +3 -3
- package/dist/utils/parseCssLengthUnit.js.map +1 -1
- package/dist/utils/trigonometry.js +1 -1
- package/dist/utils/trigonometry.js.map +1 -1
- package/dist/window-splitter/_window-splitter.scss +15 -17
- package/package.json +9 -7
- package/src/autocomplete/AutocompleteChip.tsx +2 -2
- package/src/autocomplete/AutocompleteListboxChildren.tsx +1 -1
- package/src/autocomplete/useAutocomplete.ts +4 -4
- package/src/autocomplete/utils.ts +3 -3
- package/src/box/styles.ts +2 -2
- package/src/button/AsyncButton.tsx +1 -3
- package/src/chip/Chip.tsx +1 -2
- package/src/cssUtils.ts +12 -6
- package/src/datetime/useTimeField.ts +1 -1
- package/src/delegateEvent.ts +9 -9
- package/src/draggable/useDraggable.ts +4 -4
- package/src/draggable/utils.ts +1 -1
- package/src/expansion-panel/ExpansionPanel.tsx +1 -1
- package/src/expansion-panel/useExpansionPanels.ts +1 -1
- package/src/files/FileInput.tsx +1 -1
- package/src/files/createAcceptFromExtensions.ts +18 -0
- package/src/files/useFileUpload.ts +36 -37
- package/src/files/utils.ts +15 -11
- package/src/files/validation.ts +7 -9
- package/src/focus/useFocusContainer.ts +1 -1
- package/src/focus/utils.ts +11 -6
- package/src/form/InputToggleIcon.tsx +5 -5
- package/src/form/NativeSelect.tsx +1 -1
- package/src/form/Select.tsx +58 -7
- package/src/form/SelectedOption.tsx +2 -4
- package/src/form/formConfig.ts +1 -1
- package/src/form/inputToggleStyles.ts +9 -4
- package/src/form/legendStyles.ts +1 -1
- package/src/form/selectUtils.ts +2 -2
- package/src/form/useCombobox.ts +1 -0
- package/src/form/useFormReset.ts +2 -2
- package/src/form/useNumberField.ts +1 -1
- package/src/form/useResizingTextArea.ts +5 -5
- package/src/form/useSelectCombobox.ts +1 -4
- package/src/form/validation.ts +1 -1
- package/src/hoverMode/useHoverMode.ts +9 -9
- package/src/hoverMode/useHoverModeProvider.ts +4 -4
- package/src/icon/config.tsx +3 -3
- package/src/icon/materialConfig.ts +1 -1
- package/src/interaction/UserInteractionModeProvider.tsx +11 -10
- package/src/interaction/utils.ts +3 -3
- package/src/layout/useExpandableLayout.ts +3 -4
- package/src/layout/useMainTabIndex.ts +1 -1
- package/src/list/ListItem.tsx +1 -1
- package/src/media-queries/AppSizeProvider.tsx +1 -1
- package/src/media-queries/config.ts +2 -2
- package/src/media-queries/useMediaQuery.ts +3 -3
- package/src/menu/Menu.tsx +4 -4
- package/src/menu/MenuItemButton.tsx +1 -1
- package/src/menu/MenuItemFileInput.tsx +1 -1
- package/src/menu/MenuWidget.tsx +6 -4
- package/src/movement/findMatchIndex.ts +2 -2
- package/src/movement/useKeyboardMovementProvider.ts +2 -2
- package/src/movement/utils.ts +15 -14
- package/src/navigation/getTableOfContentsHeadings.ts +4 -3
- package/src/navigation/useActiveHeadingId.ts +8 -8
- package/src/navigation/useTableOfContentsHeadings.ts +1 -1
- package/src/navigation/utils.ts +6 -5
- package/src/portal/PortalContainerProvider.tsx +5 -3
- package/src/positioning/getFixedPosition.ts +9 -6
- package/src/positioning/useFixedPositioning.ts +2 -2
- package/src/positioning/utils.ts +3 -3
- package/src/scroll/getScrollbarWidth.ts +4 -4
- package/src/searching/fuzzy.ts +7 -3
- package/src/searching/toSearchQuery.ts +1 -1
- package/src/searching/utils.ts +1 -1
- package/src/snackbar/Toast.tsx +1 -1
- package/src/snackbar/ToastContent.tsx +2 -2
- package/src/snackbar/ToastManager.ts +11 -12
- package/src/spinbutton/useSpinButton.ts +1 -1
- package/src/spinbutton/utils/deselectNode.ts +1 -1
- package/src/spinbutton/utils/resolveInputEvent.ts +1 -1
- package/src/spinbutton/utils/selectNode.ts +1 -1
- package/src/storage/useStorage.ts +7 -2
- package/src/table/useStickyTableSection.tsx +1 -1
- package/src/tabs/TabList.tsx +2 -2
- package/src/tabs/useMaxTabPanelHeight.ts +6 -3
- package/src/tabs/useTabList.ts +2 -2
- package/src/test-utils/jest-globals/match-media.ts +5 -2
- package/src/test-utils/jest-globals/timers.ts +1 -1
- package/src/test-utils/jest-globals/uploadMenuItemFileInput.ts +1 -1
- package/src/test-utils/mocks/ResizeObserver.ts +2 -2
- package/src/test-utils/polyfills/IntersectionObserver.ts +2 -2
- package/src/test-utils/polyfills/ResizeObserver.ts +2 -2
- package/src/test-utils/polyfills/TextDecoder.ts +2 -2
- package/src/test-utils/polyfills/TextEncoder.ts +2 -2
- package/src/test-utils/polyfills/matchMedia.ts +5 -2
- package/src/test-utils/polyfills/offsetParent.ts +2 -2
- package/src/test-utils/polyfills/scrollIntoView.ts +1 -1
- package/src/test-utils/queries/select.ts +2 -2
- package/src/test-utils/queries/slider.ts +1 -1
- package/src/test-utils/utils/createFileList.ts +2 -0
- package/src/test-utils/utils/createMatchMediaSpy.ts +4 -4
- package/src/test-utils/vitest/match-media.ts +2 -2
- package/src/test-utils/vitest/timers.ts +1 -1
- package/src/test-utils/vitest/uploadMenuItemFileInput.ts +1 -1
- package/src/theme/ThemeProvider.tsx +2 -2
- package/src/theme/getDerivedTheme.ts +1 -1
- package/src/theme/useCSSVariables.ts +5 -5
- package/src/theme/useColorSchemeMetaTag.ts +2 -2
- package/src/theme/useInlineCSSVariables.ts +6 -7
- package/src/theme/utils.ts +8 -8
- package/src/tooltip/useTooltip.ts +7 -7
- package/src/tooltip/useTooltipPosition.ts +1 -1
- package/src/transition/useCarousel.ts +2 -2
- package/src/transition/useCollapseTransition.ts +1 -1
- package/src/transition/useSkeletonPlaceholder.ts +4 -4
- package/src/transition/useTransition.ts +2 -2
- package/src/transition/utils.ts +5 -5
- package/src/tree/TreeItem.tsx +1 -1
- package/src/tree/useTreeItems.ts +5 -5
- package/src/tree/useTreeMovement.ts +1 -1
- package/src/tree/utils.ts +9 -9
- package/src/typography/HighlightText.tsx +4 -3
- package/src/typography/SrOnly.tsx +9 -2
- package/src/useDebouncedFunction.ts +5 -5
- package/src/useDropzone.ts +10 -10
- package/src/useEnsuredState.ts +5 -5
- package/src/useIntersectionObserver.ts +3 -3
- package/src/useIsomorphicLayoutEffect.ts +3 -3
- package/src/useOrientation.ts +1 -1
- package/src/useReadonlySet.ts +3 -1
- package/src/useResizeListener.ts +2 -2
- package/src/useResizeObserver.ts +3 -4
- package/src/useThrottledFunction.ts +4 -4
- package/src/useWindowSize.ts +1 -1
- package/src/utils/alphaNumericSort.ts +1 -1
- package/src/utils/bem.ts +15 -16
- package/src/utils/getNumberOfDigits.ts +1 -0
- package/src/utils/getRangeDefaultValue.ts +1 -1
- package/src/utils/nearest.ts +5 -2
- package/src/utils/parseCssLengthUnit.ts +5 -4
- package/src/utils/trigonometry.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/files/useFileUpload.ts"],"sourcesContent":["\"use client\";\n\nimport { nanoid } from \"nanoid\";\nimport {\n type ChangeEvent,\n type DragEvent,\n useCallback,\n useEffect,\n useReducer,\n} from \"react\";\n\nimport {\n type CompletedFileUploadStats,\n type FileReaderResult,\n type FileUploadHandlers,\n type FileUploadStats,\n type GetFileParser,\n type ProcessingFileUploadStats,\n getFileParser as defaultGetFileParser,\n} from \"./utils.js\";\nimport {\n FileAccessError,\n type FileValidationError,\n type FileValidationOptions,\n type FilesValidator,\n isValidFileName as defaultIsValidFileName,\n validateFiles as defaultValidateFiles,\n} from \"./validation.js\";\n\nconst noop = (): void => {\n // do nothing\n};\n\n/**\n *\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadState<CustomError = never> {\n /**\n * All the files that have been validated and are either:\n * - pending upload\n * - uploading\n * - complete\n *\n * Each key in this object is the {@link BaseFileUploadStats.key} generated\n * once the upload starts pending.\n */\n stats: Readonly<Record<string, Readonly<FileUploadStats>>>;\n\n /**\n * A list of validation errors that have occurred before starting the upload\n * process.\n *\n * @see {@link FileAccessError}\n * @see {@link TooManyFilesError}\n * @see {@link FileValidationError}\n */\n errors: readonly FileValidationError<CustomError>[];\n}\n\n/**\n *\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n * @internal\n */\nexport interface FileUploadHookState<\n CustomError = never,\n> extends FileUploadState<CustomError> {\n /**\n * All the current readers used for uploading files to the browser.\n *\n * Note: Once an upload has completed, the reader will be removed.\n */\n readers: Readonly<Record<string, FileReader>>;\n}\n\n/**\n *\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadOptions<E extends HTMLElement, CustomError = never>\n extends FileUploadHandlers<E>, FileValidationOptions {\n /**\n * Setting this value to a number greater than `0` will update the browser\n * upload process to queue the uploads in chunks instead of all at once. This\n * can help prevent the browser from freezing if dealing with large files that\n * are being converted to data urls.\n *\n * @defaultValue `-1`\n */\n concurrency?: number;\n\n /** {@inheritDoc FilesValidator} */\n validateFiles?: FilesValidator<CustomError>;\n /** {@inheritDoc GetFileParser} */\n getFileParser?: GetFileParser;\n}\n\n/** @internal */\ntype Action<E = never> =\n | {\n type: \"queue\";\n errors: readonly FileValidationError<E>[];\n files: readonly File[];\n }\n | { type: \"reset\" }\n | { type: \"remove\"; files: readonly string[] }\n | { type: \"start\"; key: string; reader: FileReader }\n | { type: \"progress\"; key: string; progress: number }\n | { type: \"complete\"; key: string; result: FileReaderResult }\n | { type: \"clearErrors\" };\n\n/** @since 2.9.0 */\nexport interface FileUploadActions {\n /**\n * Reset everything related to uploads ensuring that all file readers have\n * been aborted.\n */\n reset: () => void;\n\n /**\n * Removes all the errors that exist in state without canceling any of the\n * uploads already in progress.\n */\n clearErrors: () => void;\n\n /**\n * This function is used to cancel pending and uploading files or removing\n * completed files.\n *\n * @param keyOrKeys - A single or list of {@link BaseFileUploadStats.key} to\n * remove from state.\n */\n remove: (keyOrKeys: string | readonly string[]) => void;\n}\n\n/**\n *\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadHookReturnValue<\n E extends HTMLElement = HTMLElement,\n CustomError = never,\n>\n extends FileUploadActions, Required<FileUploadHandlers<E>> {\n /** {@inheritDoc FileUploadState.errors} */\n errors: readonly FileValidationError<CustomError>[];\n\n /**\n * A list of all the {@link FileUploadStats}.\n *\n * @see {@link getSplitFileUploads} for separating by status\n */\n stats: readonly Readonly<FileUploadStats>[];\n\n /**\n * The total number of bytes for all the files that exist in the\n * {@link stats} list.\n */\n totalBytes: number;\n\n /**\n * The total number of files in the {@link stats} list.\n */\n totalFiles: number;\n\n /**\n * An `accept` string that can be passed to the {@link FileInput} component\n * when the {@link FileValidationOptions.extensions} list has been provided to\n * limit which files the OS will _attempt_ to allow access to.\n *\n * @example Simple example\n * ```ts\n * const extensions = ['pdf', 'docx', 'ppt'];\n * const { accept } = useFileUpload({ extensions, ...others });\n *\n * expect(accept).toBe(\"*.pdf,*.docx,*.ppt\")\n * ```\n *\n * @defaultValue `\"*\"`\n */\n accept: string;\n}\n\n/** @internal */\nconst EMPTY_LIST = [] as const;\n/** @internal */\nconst EMPTY_OBJECT = {} as const;\n\n/**\n * This hook is generally used to upload files **to the browser** in different\n * formats to be previewed `<img>`, `<video>`, `<embed>`, etc tags. However, it\n * can also be used to upload the files as an `ArrayBuffer` and then uploaded to\n * a server.\n *\n * @see {@link https://react-md.dev/components/file-input | FileInput Demos}\n * @see {@link https://react-md.dev/hooks/use-file-input | useFileInput Demos}\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @param options - All the {@link FileUploadOptions}\n * @returns the {@link FileUploadHookReturnValue}\n * @since 2.9.0\n */\nexport function useFileUpload<E extends HTMLElement, CustomError = never>(\n options: FileUploadOptions<E, CustomError> = {}\n): Readonly<FileUploadHookReturnValue<E, CustomError>> {\n const {\n maxFiles = -1,\n extensions = EMPTY_LIST,\n minFileSize = -1,\n maxFileSize = -1,\n totalFileSize = -1,\n concurrency = -1,\n onDrop: propOnDrop = noop,\n onChange: propOnChange = noop,\n validateFiles = defaultValidateFiles,\n getFileParser = defaultGetFileParser,\n isValidFileName = defaultIsValidFileName,\n } = options;\n\n const [state, dispatch] = useReducer(\n function reducer(\n state: FileUploadHookState<CustomError>,\n action: Action<CustomError>\n ): FileUploadHookState<CustomError> {\n switch (action.type) {\n case \"reset\":\n // need to reuse constants so that calling reset doesn't cause an\n // infinite loop in an effect\n return {\n stats: EMPTY_OBJECT,\n errors: EMPTY_LIST,\n readers: EMPTY_OBJECT,\n };\n case \"remove\": {\n const stats: Record<string, FileUploadStats> = {};\n for (const key in state.stats) {\n if (!action.files.includes(key)) {\n stats[key] = state.stats[key];\n }\n }\n\n return {\n ...state,\n stats,\n };\n }\n case \"queue\":\n return {\n ...state,\n stats: {\n ...state.stats,\n ...action.files.reduce<Record<string, ProcessingFileUploadStats>>(\n (files, file) => {\n const key = nanoid();\n files[key] = {\n key,\n file,\n progress: 0,\n status: \"pending\",\n };\n\n return files;\n },\n {}\n ),\n },\n errors: [...state.errors, ...action.errors],\n };\n case \"start\": {\n const { key, reader } = action;\n const fileStats: ProcessingFileUploadStats = {\n key,\n file: state.stats[key].file,\n progress: 0,\n status: \"uploading\",\n };\n\n return {\n ...state,\n readers: {\n ...state.readers,\n [key]: reader,\n },\n stats: {\n ...state.stats,\n [key]: fileStats,\n },\n };\n }\n case \"progress\": {\n const { key, progress } = action;\n return {\n ...state,\n stats: {\n ...state.stats,\n [key]: {\n ...state.stats[key],\n progress,\n },\n },\n };\n }\n case \"complete\": {\n const { key, result } = action;\n const file: CompletedFileUploadStats = {\n key,\n file: state.stats[key].file,\n status: \"complete\",\n result,\n progress: 100,\n };\n const { [key]: _reader, ...readers } = state.readers;\n\n return {\n ...state,\n readers,\n stats: {\n ...state.stats,\n [key]: file,\n },\n };\n }\n case \"clearErrors\":\n return { ...state, errors: [] };\n }\n },\n {\n stats: EMPTY_OBJECT,\n errors: EMPTY_LIST,\n readers: EMPTY_OBJECT,\n }\n );\n const { stats, errors, readers } = state;\n\n const statsList = Object.values(stats);\n const totalFiles = statsList.length;\n const totalBytes = statsList.reduce(\n (result, { file: { size } }) => result + size,\n 0\n );\n const queueFiles = useCallback(\n (files: readonly File[]) => {\n const { pending, errors } = validateFiles(files, {\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n });\n\n dispatch({ type: \"queue\", errors, files: pending });\n },\n [\n validateFiles,\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n ]\n );\n const onDrop = useCallback(\n (event: DragEvent<E>) => {\n propOnDrop(event);\n event.preventDefault();\n event.stopPropagation();\n\n try {\n const files = event.dataTransfer.files;\n if (files) {\n queueFiles(Array.from(files));\n }\n } catch (e) {\n dispatch({\n type: \"queue\",\n files: [],\n errors: [\n new FileAccessError(e instanceof Error ? e.message : undefined),\n ],\n });\n }\n },\n [queueFiles, propOnDrop]\n );\n const onChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n propOnChange(event);\n try {\n const files = event.currentTarget.files;\n if (files) {\n queueFiles(Array.from(files));\n } else {\n throw new Error();\n }\n } catch (e) {\n dispatch({\n type: \"queue\",\n files: [],\n errors: [\n new FileAccessError(e instanceof Error ? e.message : undefined),\n ],\n });\n }\n },\n [queueFiles, propOnChange]\n );\n\n const remove = useCallback(\n (keyOrKeys: string | readonly string[]) => {\n const files = typeof keyOrKeys === \"string\" ? [keyOrKeys] : keyOrKeys;\n files.forEach((fileKey) => {\n readers[fileKey]?.abort();\n });\n\n dispatch({ type: \"remove\", files });\n },\n [readers]\n );\n const reset = useCallback(() => {\n Object.values(readers).forEach((reader) => {\n reader.abort();\n });\n\n dispatch({ type: \"reset\" });\n }, [readers]);\n const clearErrors = useCallback(() => {\n dispatch({ type: \"clearErrors\" });\n }, []);\n const start = useCallback((key: string, reader: FileReader) => {\n dispatch({ type: \"start\", key, reader });\n }, []);\n const complete = useCallback(\n (key: string, result: FileReaderResult = null) => {\n dispatch({ type: \"complete\", key, result });\n },\n []\n );\n const createProgressEventHandler = useCallback(\n (key: string) => (event: ProgressEvent) => {\n if (event.lengthComputable) {\n const percentage = Math.round((event.loaded * 100) / event.total);\n dispatch({ type: \"progress\", key, progress: percentage });\n }\n },\n []\n );\n\n useEffect(() => {\n const pending: ProcessingFileUploadStats[] = [];\n const uploading: ProcessingFileUploadStats[] = [];\n Object.values(stats).forEach((file) => {\n if (file.status === \"pending\") {\n pending.push(file);\n } else if (file.status === \"uploading\") {\n uploading.push(file);\n }\n });\n\n const lastIndex =\n concurrency === -1\n ? pending.length\n : Math.max(0, concurrency - uploading.length);\n const queue = pending.slice(0, lastIndex);\n if (!queue.length) {\n return;\n }\n\n queue.forEach((stats) => {\n const { key, file } = stats;\n const reader = new FileReader();\n\n // using `addEventListener` instead of directly setting to\n // `reader.progress`/`reader.load` so it's easier to test\n reader.addEventListener(\"progress\", createProgressEventHandler(key));\n reader.addEventListener(\"load\", () => {\n complete(key, reader.result);\n });\n\n start(key, reader);\n const parser = getFileParser(file);\n /* istanbul ignore next */\n if (\n process.env.NODE_ENV !== \"production\" &&\n ![\"readAsText\", \"readAsDataURL\", \"readAsArrayBuffer\"].includes(parser)\n ) {\n throw new Error(\"Invalid file reader parser\");\n }\n\n reader[parser](file);\n });\n }, [\n concurrency,\n stats,\n getFileParser,\n createProgressEventHandler,\n start,\n complete,\n ]);\n\n let accept = \"\";\n if (extensions.length) {\n accept = extensions.reduce((s, ext) => `${s ? `${s},` : \"\"}.${ext}`, \"\");\n }\n\n return {\n stats: statsList,\n errors,\n accept,\n totalBytes,\n totalFiles,\n onDrop,\n onChange,\n reset,\n remove,\n clearErrors,\n };\n}\n"],"names":["nanoid","useCallback","useEffect","useReducer","getFileParser","defaultGetFileParser","FileAccessError","isValidFileName","defaultIsValidFileName","validateFiles","defaultValidateFiles","noop","EMPTY_LIST","EMPTY_OBJECT","useFileUpload","options","maxFiles","extensions","minFileSize","maxFileSize","totalFileSize","concurrency","onDrop","propOnDrop","onChange","propOnChange","state","dispatch","reducer","action","type","stats","errors","readers","key","files","includes","reduce","file","progress","status","reader","fileStats","result","_reader","statsList","Object","values","totalFiles","length","totalBytes","size","queueFiles","pending","event","preventDefault","stopPropagation","dataTransfer","Array","from","e","Error","message","undefined","currentTarget","remove","keyOrKeys","forEach","fileKey","abort","reset","clearErrors","start","complete","createProgressEventHandler","lengthComputable","percentage","Math","round","loaded","total","uploading","push","lastIndex","max","queue","slice","FileReader","addEventListener","parser","process","env","NODE_ENV","accept","s","ext"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/files/useFileUpload.ts"],"sourcesContent":["\"use client\";\n\nimport { nanoid } from \"nanoid\";\nimport {\n type ChangeEvent,\n type DragEvent,\n useCallback,\n useEffect,\n useReducer,\n} from \"react\";\n\nimport { createAcceptFromExtensions } from \"./createAcceptFromExtensions.js\";\nimport {\n type CompletedFileUploadStats,\n type FileReaderResult,\n type FileUploadHandlers,\n type FileUploadStats,\n type GetFileParser,\n type ProcessingFileUploadStats,\n getFileParser as defaultGetFileParser,\n} from \"./utils.js\";\nimport {\n FileAccessError,\n type FileValidationError,\n type FileValidationOptions,\n type FilesValidator,\n isValidFileName as defaultIsValidFileName,\n validateFiles as defaultValidateFiles,\n} from \"./validation.js\";\n\nconst noop = (): void => {\n // do nothing\n};\n\n/**\n *\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadState<CustomError = never> {\n /**\n * All the files that have been validated and are either:\n * - pending upload\n * - uploading\n * - complete\n *\n * Each key in this object is the {@link BaseFileUploadStats.key} generated\n * once the upload starts pending.\n */\n stats: Readonly<Record<string, Readonly<FileUploadStats>>>;\n\n /**\n * A list of validation errors that have occurred before starting the upload\n * process.\n *\n * @see {@link FileAccessError}\n * @see {@link TooManyFilesError}\n * @see {@link FileValidationError}\n */\n errors: readonly FileValidationError<CustomError>[];\n}\n\n/**\n *\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n * @internal\n */\nexport interface FileUploadHookState<\n CustomError = never,\n> extends FileUploadState<CustomError> {\n /**\n * All the current readers used for uploading files to the browser.\n *\n * Note: Once an upload has completed, the reader will be removed.\n */\n readers: Readonly<Record<string, FileReader>>;\n}\n\n/**\n *\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadOptions<E extends HTMLElement, CustomError = never>\n extends FileUploadHandlers<E>, FileValidationOptions {\n /**\n * Setting this value to a number greater than `0` will update the browser\n * upload process to queue the uploads in chunks instead of all at once. This\n * can help prevent the browser from freezing if dealing with large files that\n * are being converted to data urls.\n *\n * @defaultValue `-1`\n */\n concurrency?: number;\n\n /** {@inheritDoc FilesValidator} */\n validateFiles?: FilesValidator<CustomError>;\n /** {@inheritDoc GetFileParser} */\n getFileParser?: GetFileParser;\n}\n\n/** @internal */\ntype Action<E = never> =\n | {\n type: \"queue\";\n errors: readonly FileValidationError<E>[];\n files: readonly File[];\n }\n | { type: \"reset\" }\n | { type: \"remove\"; files: readonly string[] }\n | { type: \"start\"; key: string; reader: FileReader }\n | { type: \"progress\"; key: string; progress: number }\n | { type: \"complete\"; key: string; result: FileReaderResult }\n | { type: \"clearErrors\" };\n\n/** @since 2.9.0 */\nexport interface FileUploadActions {\n /**\n * Reset everything related to uploads ensuring that all file readers have\n * been aborted.\n */\n reset: () => void;\n\n /**\n * Removes all the errors that exist in state without canceling any of the\n * uploads already in progress.\n */\n clearErrors: () => void;\n\n /**\n * This function is used to cancel pending and uploading files or removing\n * completed files.\n *\n * @param keyOrKeys - A single or list of {@link BaseFileUploadStats.key} to\n * remove from state.\n */\n remove: (keyOrKeys: string | readonly string[]) => void;\n}\n\n/**\n *\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @since 2.9.0\n */\nexport interface FileUploadHookReturnValue<\n E extends HTMLElement = HTMLElement,\n CustomError = never,\n>\n extends FileUploadActions, Required<FileUploadHandlers<E>> {\n /** {@inheritDoc FileUploadState.errors} */\n errors: readonly FileValidationError<CustomError>[];\n\n /**\n * A list of all the {@link FileUploadStats}.\n *\n * @see {@link getSplitFileUploads} for separating by status\n */\n stats: readonly Readonly<FileUploadStats>[];\n\n /**\n * The total number of bytes for all the files that exist in the\n * {@link stats} list.\n */\n totalBytes: number;\n\n /**\n * The total number of files in the {@link stats} list.\n */\n totalFiles: number;\n\n /**\n * An `accept` string that can be passed to the {@link FileInput} component\n * when the {@link FileValidationOptions.extensions} list has been provided to\n * limit which files the OS will _attempt_ to allow access to.\n *\n * @example Simple example\n * ```ts\n * const extensions = ['pdf', 'docx', 'ppt'];\n * const { accept } = useFileUpload({ extensions, ...others });\n *\n * expect(accept).toBe(\"*.pdf,*.docx,*.ppt\")\n * ```\n *\n * @defaultValue `\"*\"`\n */\n accept: string;\n}\n\n/** @internal */\nconst EMPTY_LIST = [] as const;\n/** @internal */\nconst EMPTY_OBJECT = {} as const;\n\n/**\n * This hook is generally used to upload files **to the browser** in different\n * formats to be previewed `<img>`, `<video>`, `<embed>`, etc tags. However, it\n * can also be used to upload the files as an `ArrayBuffer` and then uploaded to\n * a server.\n *\n * @see {@link https://react-md.dev/components/file-input | FileInput Demos}\n * @see {@link https://react-md.dev/hooks/use-file-input | useFileInput Demos}\n * @typeParam E - An optional HTMLElement type that is used for the\n * {@link FileUploadHandlers}.\n * @typeParam CustomError - An optional error type that gets returned from the\n * {@link FilesValidator}.\n * @param options - All the {@link FileUploadOptions}\n * @returns the {@link FileUploadHookReturnValue}\n * @since 2.9.0\n */\nexport function useFileUpload<E extends HTMLElement, CustomError = never>(\n options: FileUploadOptions<E, CustomError> = {}\n): Readonly<FileUploadHookReturnValue<E, CustomError>> {\n const {\n maxFiles = -1,\n extensions = EMPTY_LIST,\n minFileSize = -1,\n maxFileSize = -1,\n totalFileSize = -1,\n concurrency = -1,\n onDrop: propOnDrop = noop,\n onChange: propOnChange = noop,\n validateFiles = defaultValidateFiles,\n getFileParser = defaultGetFileParser,\n isValidFileName = defaultIsValidFileName,\n } = options;\n\n const [state, dispatch] = useReducer(\n function reducer(\n state: FileUploadHookState<CustomError>,\n action: Action<CustomError>\n ): FileUploadHookState<CustomError> {\n switch (action.type) {\n case \"reset\":\n // need to reuse constants so that calling reset doesn't cause an\n // infinite loop in an effect\n return {\n stats: EMPTY_OBJECT,\n errors: EMPTY_LIST,\n readers: EMPTY_OBJECT,\n };\n case \"remove\": {\n const stats: Record<string, FileUploadStats> = {};\n for (const key in state.stats) {\n if (!action.files.includes(key)) {\n stats[key] = state.stats[key];\n }\n }\n\n return {\n ...state,\n stats,\n };\n }\n case \"queue\": {\n const nextStats: Record<string, ProcessingFileUploadStats> = {};\n for (const file of action.files) {\n const key = nanoid();\n nextStats[key] = {\n key,\n file,\n progress: 0,\n status: \"pending\",\n };\n }\n\n return {\n ...state,\n stats: {\n ...state.stats,\n ...nextStats,\n },\n errors: [...state.errors, ...action.errors],\n };\n }\n case \"start\": {\n const { key, reader } = action;\n const fileStats: ProcessingFileUploadStats = {\n key,\n file: state.stats[key].file,\n progress: 0,\n status: \"uploading\",\n };\n\n return {\n ...state,\n readers: {\n ...state.readers,\n [key]: reader,\n },\n stats: {\n ...state.stats,\n [key]: fileStats,\n },\n };\n }\n case \"progress\": {\n const { key, progress } = action;\n return {\n ...state,\n stats: {\n ...state.stats,\n [key]: {\n ...state.stats[key],\n progress,\n },\n },\n };\n }\n case \"complete\": {\n const { key, result } = action;\n const file: CompletedFileUploadStats = {\n key,\n file: state.stats[key].file,\n status: \"complete\",\n result,\n progress: 100,\n };\n const { [key]: _reader, ...readers } = state.readers;\n\n return {\n ...state,\n readers,\n stats: {\n ...state.stats,\n [key]: file,\n },\n };\n }\n case \"clearErrors\":\n return { ...state, errors: [] };\n }\n },\n {\n stats: EMPTY_OBJECT,\n errors: EMPTY_LIST,\n readers: EMPTY_OBJECT,\n }\n );\n const { stats, errors, readers } = state;\n\n const statsList = Object.values(stats);\n const totalFiles = statsList.length;\n const totalBytes = statsList.reduce(\n (result, { file: { size } }) => result + size,\n 0\n );\n const queueFiles = useCallback(\n (files: readonly File[]) => {\n const { pending, errors } = validateFiles(files, {\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n });\n\n dispatch({ type: \"queue\", errors, files: pending });\n },\n [\n validateFiles,\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n ]\n );\n const onDrop = useCallback(\n (event: DragEvent<E>) => {\n propOnDrop(event);\n event.preventDefault();\n event.stopPropagation();\n\n try {\n const files = event.dataTransfer.files;\n if (files) {\n queueFiles([...files]);\n }\n } catch (error) {\n dispatch({\n type: \"queue\",\n files: [],\n errors: [\n new FileAccessError(\n error instanceof Error ? error.message : undefined\n ),\n ],\n });\n }\n },\n [queueFiles, propOnDrop]\n );\n const onChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n propOnChange(event);\n try {\n const files = event.currentTarget.files;\n if (files) {\n queueFiles([...files]);\n } else {\n throw new Error(\"There are no files\");\n }\n } catch (error) {\n dispatch({\n type: \"queue\",\n files: [],\n errors: [\n new FileAccessError(\n error instanceof Error ? error.message : undefined\n ),\n ],\n });\n }\n },\n [queueFiles, propOnChange]\n );\n\n const remove = useCallback(\n (keyOrKeys: string | readonly string[]) => {\n const files = typeof keyOrKeys === \"string\" ? [keyOrKeys] : keyOrKeys;\n for (const fileKey of files) {\n readers[fileKey]?.abort();\n }\n\n dispatch({ type: \"remove\", files });\n },\n [readers]\n );\n const reset = useCallback(() => {\n for (const reader of Object.values(readers)) {\n reader.abort();\n }\n\n dispatch({ type: \"reset\" });\n }, [readers]);\n const clearErrors = useCallback(() => {\n dispatch({ type: \"clearErrors\" });\n }, []);\n const start = useCallback((key: string, reader: FileReader) => {\n dispatch({ type: \"start\", key, reader });\n }, []);\n const complete = useCallback(\n (key: string, result: FileReaderResult = null) => {\n dispatch({ type: \"complete\", key, result });\n },\n []\n );\n const createProgressEventHandler = useCallback(\n (key: string) => (event: ProgressEvent) => {\n if (event.lengthComputable) {\n const percentage = Math.round((event.loaded * 100) / event.total);\n dispatch({ type: \"progress\", key, progress: percentage });\n }\n },\n []\n );\n\n useEffect(() => {\n const pending: ProcessingFileUploadStats[] = [];\n const uploading: ProcessingFileUploadStats[] = [];\n for (const file of Object.values(stats)) {\n if (file.status === \"pending\") {\n pending.push(file);\n } else if (file.status === \"uploading\") {\n uploading.push(file);\n }\n }\n\n const lastIndex =\n concurrency === -1\n ? pending.length\n : Math.max(0, concurrency - uploading.length);\n const queue = pending.slice(0, lastIndex);\n if (queue.length === 0) {\n return;\n }\n\n for (const stats of queue) {\n const { key, file } = stats;\n const reader = new FileReader();\n\n // using `addEventListener` instead of directly setting to\n // `reader.progress`/`reader.load` so it's easier to test\n reader.addEventListener(\"progress\", createProgressEventHandler(key));\n reader.addEventListener(\"load\", () => {\n complete(key, reader.result);\n });\n\n start(key, reader);\n const parser = getFileParser(file);\n /* istanbul ignore next */\n if (\n process.env.NODE_ENV !== \"production\" &&\n ![\"readAsText\", \"readAsDataURL\", \"readAsArrayBuffer\"].includes(parser)\n ) {\n throw new Error(\"Invalid file reader parser\");\n }\n\n reader[parser](file);\n }\n }, [\n concurrency,\n stats,\n getFileParser,\n createProgressEventHandler,\n start,\n complete,\n ]);\n\n return {\n stats: statsList,\n errors,\n accept: createAcceptFromExtensions(extensions),\n totalBytes,\n totalFiles,\n onDrop,\n onChange,\n reset,\n remove,\n clearErrors,\n };\n}\n"],"names":["nanoid","useCallback","useEffect","useReducer","createAcceptFromExtensions","getFileParser","defaultGetFileParser","FileAccessError","isValidFileName","defaultIsValidFileName","validateFiles","defaultValidateFiles","noop","EMPTY_LIST","EMPTY_OBJECT","useFileUpload","options","maxFiles","extensions","minFileSize","maxFileSize","totalFileSize","concurrency","onDrop","propOnDrop","onChange","propOnChange","state","dispatch","reducer","action","type","stats","errors","readers","key","files","includes","nextStats","file","progress","status","reader","fileStats","result","_reader","statsList","Object","values","totalFiles","length","totalBytes","reduce","size","queueFiles","pending","event","preventDefault","stopPropagation","dataTransfer","error","Error","message","undefined","currentTarget","remove","keyOrKeys","fileKey","abort","reset","clearErrors","start","complete","createProgressEventHandler","lengthComputable","percentage","Math","round","loaded","total","uploading","push","lastIndex","max","queue","slice","FileReader","addEventListener","parser","process","env","NODE_ENV","accept"],"mappings":"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"}
|
package/dist/files/utils.js
CHANGED
|
@@ -123,17 +123,21 @@
|
|
|
123
123
|
const pending = [];
|
|
124
124
|
const uploading = [];
|
|
125
125
|
const complete = [];
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
pending
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
for (const stat of stats){
|
|
127
|
+
switch(stat.status){
|
|
128
|
+
case "pending":
|
|
129
|
+
pending.push(stat);
|
|
130
|
+
break;
|
|
131
|
+
case "uploading":
|
|
132
|
+
uploading.push(stat);
|
|
133
|
+
break;
|
|
134
|
+
case "complete":
|
|
135
|
+
complete.push(stat);
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
/* istanbul ignore next */ throw new Error("Invalid upload stat");
|
|
135
139
|
}
|
|
136
|
-
}
|
|
140
|
+
}
|
|
137
141
|
return {
|
|
138
142
|
pending,
|
|
139
143
|
uploading,
|
package/dist/files/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/files/utils.ts"],"sourcesContent":["import { type ChangeEventHandler, type DragEventHandler } from \"react\";\n\n/** @since 2.9.0 */\nexport interface BaseFileUploadStats {\n /**\n * A unique key associated with each upload generated by `nanoid`.\n */\n key: string;\n\n /**\n * The file instance that is being uploaded.\n */\n file: File;\n\n /**\n * The current upload progress as a percentage from 0 - 100 percent.\n */\n progress: number;\n}\n\n/** @since 2.9.0 */\nexport interface ProcessingFileUploadStats extends BaseFileUploadStats {\n status: \"pending\" | \"uploading\";\n}\n\n/** @since 2.9.0 */\nexport type FileReaderResult = FileReader[\"result\"];\n\n/** @since 2.9.0 */\nexport interface CompletedFileUploadStats extends BaseFileUploadStats {\n status: \"complete\";\n\n /**\n * The result after a `FileReader` has read a file completely.\n *\n * Note: This _should_ be an `ArrayBuffer` if the next step is to upload to a\n * server.\n *\n * @see {@link FileReaderParser}\n * @see {@link getFileParser}\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader | FileReader}\n */\n result: FileReaderResult;\n}\n\n/** @since 2.9.0 */\nexport type FileUploadStats =\n | ProcessingFileUploadStats\n | CompletedFileUploadStats;\n\n/** @since 2.9.0 */\nexport type FileUploadStatus = FileUploadStats[\"status\"];\n\n/** @since 2.9.0 */\nexport interface FileUploadHandlers<E extends HTMLElement> {\n onDrop?: DragEventHandler<E>;\n onChange?: ChangeEventHandler<HTMLInputElement>;\n}\n\n/**\n * This will first check if the mime-type of the file starts with `text/` and\n * fallback to checking a few file names or extensions that should be considered\n * text.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a text-content file.\n * @since 2.9.0\n */\nexport function isTextFile(file: File): boolean {\n return /\\.((j|t)sx?|json|lock|hbs|ya?ml|log|txt|md)$/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `text\\/` and\n * fallback to checking a few file names or extensions that should be considered\n * text.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a text content file.\n * @since 2.9.0\n */\nexport function isImageFile(file: File): boolean {\n return /\\.(a?png|avif|svg|tiff|gifv?|jpe?g)/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `audio/` and\n * fallback to checking a few file names or extensions that should be considered\n * audio.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a audio content file.\n * @since 2.9.0\n */\nexport function isAudioFile(file: File): boolean {\n return /\\.(mp3|wav|ogg|m4p|flac)$/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `video/` and\n * fallback to checking a few file names or extensions that should be considered\n * video.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a video content file.\n * @since 2.9.0\n */\nexport function isVideoFile(file: File): boolean {\n return /\\.(mkv|mpe?g|mov|avi|flv|webm|mp4)$/i.test(file.name);\n}\n\n/**\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file matches an image, audio, or video file.\n * @since 2.9.0\n */\nexport function isMediaFile(file: File): boolean {\n return isImageFile(file) || isAudioFile(file) || isVideoFile(file);\n}\n\n/**\n * One of the function names from a `FileReader` to upload a file to the\n * client.\n *\n * Note: If this file does not need to be previewed in the browser and will\n * immediately be uploaded to a server, use `readAsArrayBuffer`.\n *\n * @since 2.9.0\n * @since 6.0.0 Removed `\"readAsBinaryString` since it is deprecated\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString}\n */\nexport type FileReaderParser =\n | \"readAsText\"\n | \"readAsDataURL\"\n | \"readAsArrayBuffer\";\n\n/**\n * A function that should return one of the {@link FileReaderParser} functions\n * to start uploading a file to the browser.\n *\n * @example The Default File Upload Parser\n * ```ts\n * export const getFileParser: GetFileParser = (file) => {\n * if (isMediaFile(file)) {\n * return \"readAsDataURL\";\n * }\n *\n * if (isTextFile(file)) {\n * return \"readAsText\";\n * }\n *\n * return \"readAsArrayBuffer\";\n * };\n * ```\n *\n * @param file - The file to get a parser for\n * @returns the {@link FileReaderParser} string.\n * @since 2.9.0\n */\nexport type GetFileParser = (file: File) => FileReaderParser;\n\n/**\n * This function will attempt to read:\n * - media (image, audio, and video) files as a data url so they can be\n * previewed in `<img>`, `<audio>`, and `<video>` tags\n * - text files as plain text\n * - everything else as an `ArrayBuffer` which can be manually converted into a\n * data url if needed with `URL.createObjectURL`\n *\n * @since 2.9.0\n */\nexport const getFileParser: GetFileParser = (file) => {\n if (isMediaFile(file)) {\n return \"readAsDataURL\";\n }\n\n if (isTextFile(file)) {\n return \"readAsText\";\n }\n\n return \"readAsArrayBuffer\";\n};\n\n/** @since 2.9.0 */\nexport interface SplitFileUploads {\n readonly pending: readonly ProcessingFileUploadStats[];\n readonly uploading: readonly ProcessingFileUploadStats[];\n readonly complete: readonly CompletedFileUploadStats[];\n}\n\n/**\n * This util will split all the current upload stats by status.\n *\n * @example Main Usage\n * ```tsx\n * import { FileUpload } from \"@react-md/core/files/FileUpload\";\n * import { useFileUpload } from \"@react-md/core/files/useFileUpload\";\n * import { getSplitFileUploads } from \"@react-md/core/files/utils\";\n *\n * function Example() {\n * const { stats, errors, accept, onChange } = useFileUpload();\n * const { pending, uploading, completed } = getSplitFileUploads(stats);\n *\n * return (\n * <>\n * <FileUpload accept={accept} onChange={onChange} />\n * {pending.map(({ key, file, progress, status }) => {\n * // pretend some UI for each pending item with the provided data\n * return null;\n * })}\n * {uploading.map(({ file, key, progress, status }) => {\n * // pretend some UI for each uploading item with the provided data\n * return null;\n * })}\n * {complete.map(({ file, key, progress, result, status }) => {\n * // pretend some UI for each complete item with the provided data\n * return null;\n * })}\n * </>\n * );\n * }\n * ```\n *\n * @param stats - The {@link FileUploadStats} list generally returned by the\n * {@link useFileUpload} hook.\n * @returns the {@link SplitFileUploads}.\n * @since 2.9.0\n */\nexport function getSplitFileUploads(\n stats: readonly FileUploadStats[]\n): SplitFileUploads {\n const pending: ProcessingFileUploadStats[] = [];\n const uploading: ProcessingFileUploadStats[] = [];\n const complete: CompletedFileUploadStats[] = [];\n
|
|
1
|
+
{"version":3,"sources":["../../src/files/utils.ts"],"sourcesContent":["import { type ChangeEventHandler, type DragEventHandler } from \"react\";\n\n/** @since 2.9.0 */\nexport interface BaseFileUploadStats {\n /**\n * A unique key associated with each upload generated by `nanoid`.\n */\n key: string;\n\n /**\n * The file instance that is being uploaded.\n */\n file: File;\n\n /**\n * The current upload progress as a percentage from 0 - 100 percent.\n */\n progress: number;\n}\n\n/** @since 2.9.0 */\nexport interface ProcessingFileUploadStats extends BaseFileUploadStats {\n status: \"pending\" | \"uploading\";\n}\n\n/** @since 2.9.0 */\nexport type FileReaderResult = FileReader[\"result\"];\n\n/** @since 2.9.0 */\nexport interface CompletedFileUploadStats extends BaseFileUploadStats {\n status: \"complete\";\n\n /**\n * The result after a `FileReader` has read a file completely.\n *\n * Note: This _should_ be an `ArrayBuffer` if the next step is to upload to a\n * server.\n *\n * @see {@link FileReaderParser}\n * @see {@link getFileParser}\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader | FileReader}\n */\n result: FileReaderResult;\n}\n\n/** @since 2.9.0 */\nexport type FileUploadStats =\n | ProcessingFileUploadStats\n | CompletedFileUploadStats;\n\n/** @since 2.9.0 */\nexport type FileUploadStatus = FileUploadStats[\"status\"];\n\n/** @since 2.9.0 */\nexport interface FileUploadHandlers<E extends HTMLElement> {\n onDrop?: DragEventHandler<E>;\n onChange?: ChangeEventHandler<HTMLInputElement>;\n}\n\n/**\n * This will first check if the mime-type of the file starts with `text/` and\n * fallback to checking a few file names or extensions that should be considered\n * text.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a text-content file.\n * @since 2.9.0\n */\nexport function isTextFile(file: File): boolean {\n return /\\.((j|t)sx?|json|lock|hbs|ya?ml|log|txt|md)$/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `text\\/` and\n * fallback to checking a few file names or extensions that should be considered\n * text.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a text content file.\n * @since 2.9.0\n */\nexport function isImageFile(file: File): boolean {\n return /\\.(a?png|avif|svg|tiff|gifv?|jpe?g)/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `audio/` and\n * fallback to checking a few file names or extensions that should be considered\n * audio.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a audio content file.\n * @since 2.9.0\n */\nexport function isAudioFile(file: File): boolean {\n return /\\.(mp3|wav|ogg|m4p|flac)$/i.test(file.name);\n}\n\n/**\n * This will first check if the mime-type of the file starts with `video/` and\n * fallback to checking a few file names or extensions that should be considered\n * video.\n *\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file should be considered as a video content file.\n * @since 2.9.0\n */\nexport function isVideoFile(file: File): boolean {\n return /\\.(mkv|mpe?g|mov|avi|flv|webm|mp4)$/i.test(file.name);\n}\n\n/**\n * This function is not guaranteed to be 100% correct and is only useful if\n * trying to generate a preview of files uploaded to the browser.\n *\n * @param file - The file to check\n * @returns `true` if the file matches an image, audio, or video file.\n * @since 2.9.0\n */\nexport function isMediaFile(file: File): boolean {\n return isImageFile(file) || isAudioFile(file) || isVideoFile(file);\n}\n\n/**\n * One of the function names from a `FileReader` to upload a file to the\n * client.\n *\n * Note: If this file does not need to be previewed in the browser and will\n * immediately be uploaded to a server, use `readAsArrayBuffer`.\n *\n * @since 2.9.0\n * @since 6.0.0 Removed `\"readAsBinaryString` since it is deprecated\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString}\n */\nexport type FileReaderParser =\n | \"readAsText\"\n | \"readAsDataURL\"\n | \"readAsArrayBuffer\";\n\n/**\n * A function that should return one of the {@link FileReaderParser} functions\n * to start uploading a file to the browser.\n *\n * @example The Default File Upload Parser\n * ```ts\n * export const getFileParser: GetFileParser = (file) => {\n * if (isMediaFile(file)) {\n * return \"readAsDataURL\";\n * }\n *\n * if (isTextFile(file)) {\n * return \"readAsText\";\n * }\n *\n * return \"readAsArrayBuffer\";\n * };\n * ```\n *\n * @param file - The file to get a parser for\n * @returns the {@link FileReaderParser} string.\n * @since 2.9.0\n */\nexport type GetFileParser = (file: File) => FileReaderParser;\n\n/**\n * This function will attempt to read:\n * - media (image, audio, and video) files as a data url so they can be\n * previewed in `<img>`, `<audio>`, and `<video>` tags\n * - text files as plain text\n * - everything else as an `ArrayBuffer` which can be manually converted into a\n * data url if needed with `URL.createObjectURL`\n *\n * @since 2.9.0\n */\nexport const getFileParser: GetFileParser = (file) => {\n if (isMediaFile(file)) {\n return \"readAsDataURL\";\n }\n\n if (isTextFile(file)) {\n return \"readAsText\";\n }\n\n return \"readAsArrayBuffer\";\n};\n\n/** @since 2.9.0 */\nexport interface SplitFileUploads {\n readonly pending: readonly ProcessingFileUploadStats[];\n readonly uploading: readonly ProcessingFileUploadStats[];\n readonly complete: readonly CompletedFileUploadStats[];\n}\n\n/**\n * This util will split all the current upload stats by status.\n *\n * @example Main Usage\n * ```tsx\n * import { FileUpload } from \"@react-md/core/files/FileUpload\";\n * import { useFileUpload } from \"@react-md/core/files/useFileUpload\";\n * import { getSplitFileUploads } from \"@react-md/core/files/utils\";\n *\n * function Example() {\n * const { stats, errors, accept, onChange } = useFileUpload();\n * const { pending, uploading, completed } = getSplitFileUploads(stats);\n *\n * return (\n * <>\n * <FileUpload accept={accept} onChange={onChange} />\n * {pending.map(({ key, file, progress, status }) => {\n * // pretend some UI for each pending item with the provided data\n * return null;\n * })}\n * {uploading.map(({ file, key, progress, status }) => {\n * // pretend some UI for each uploading item with the provided data\n * return null;\n * })}\n * {complete.map(({ file, key, progress, result, status }) => {\n * // pretend some UI for each complete item with the provided data\n * return null;\n * })}\n * </>\n * );\n * }\n * ```\n *\n * @param stats - The {@link FileUploadStats} list generally returned by the\n * {@link useFileUpload} hook.\n * @returns the {@link SplitFileUploads}.\n * @since 2.9.0\n */\nexport function getSplitFileUploads(\n stats: readonly FileUploadStats[]\n): SplitFileUploads {\n const pending: ProcessingFileUploadStats[] = [];\n const uploading: ProcessingFileUploadStats[] = [];\n const complete: CompletedFileUploadStats[] = [];\n for (const stat of stats) {\n switch (stat.status) {\n case \"pending\":\n pending.push(stat);\n break;\n case \"uploading\":\n uploading.push(stat);\n break;\n case \"complete\":\n complete.push(stat);\n break;\n default:\n /* istanbul ignore next */\n throw new Error(\"Invalid upload stat\");\n }\n }\n\n return { pending, uploading, complete };\n}\n"],"names":["isTextFile","file","test","name","isImageFile","isAudioFile","isVideoFile","isMediaFile","getFileParser","getSplitFileUploads","stats","pending","uploading","complete","stat","status","push","Error"],"mappings":"AA2DA;;;;;;;;;;;CAWC,GACD,OAAO,SAASA,WAAWC,IAAU;IACnC,OAAO,gDAAgDC,IAAI,CAACD,KAAKE,IAAI;AACvE;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,YAAYH,IAAU;IACpC,OAAO,uCAAuCC,IAAI,CAACD,KAAKE,IAAI;AAC9D;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASE,YAAYJ,IAAU;IACpC,OAAO,6BAA6BC,IAAI,CAACD,KAAKE,IAAI;AACpD;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASG,YAAYL,IAAU;IACpC,OAAO,uCAAuCC,IAAI,CAACD,KAAKE,IAAI;AAC9D;AAEA;;;;;;;CAOC,GACD,OAAO,SAASI,YAAYN,IAAU;IACpC,OAAOG,YAAYH,SAASI,YAAYJ,SAASK,YAAYL;AAC/D;AA2CA;;;;;;;;;CASC,GACD,OAAO,MAAMO,gBAA+B,CAACP;IAC3C,IAAIM,YAAYN,OAAO;QACrB,OAAO;IACT;IAEA,IAAID,WAAWC,OAAO;QACpB,OAAO;IACT;IAEA,OAAO;AACT,EAAE;AASF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCC,GACD,OAAO,SAASQ,oBACdC,KAAiC;IAEjC,MAAMC,UAAuC,EAAE;IAC/C,MAAMC,YAAyC,EAAE;IACjD,MAAMC,WAAuC,EAAE;IAC/C,KAAK,MAAMC,QAAQJ,MAAO;QACxB,OAAQI,KAAKC,MAAM;YACjB,KAAK;gBACHJ,QAAQK,IAAI,CAACF;gBACb;YACF,KAAK;gBACHF,UAAUI,IAAI,CAACF;gBACf;YACF,KAAK;gBACHD,SAASG,IAAI,CAACF;gBACd;YACF;gBACE,wBAAwB,GACxB,MAAM,IAAIG,MAAM;QACpB;IACF;IAEA,OAAO;QAAEN;QAASC;QAAWC;IAAS;AACxC"}
|
package/dist/files/validation.js
CHANGED
|
@@ -167,15 +167,14 @@ import { nanoid } from "nanoid";
|
|
|
167
167
|
const errors = [];
|
|
168
168
|
const pending = [];
|
|
169
169
|
const extraFiles = [];
|
|
170
|
-
const extensionRegExp = extensions.length > 0 ? new RegExp(
|
|
170
|
+
const extensionRegExp = extensions.length > 0 ? new RegExp(String.raw`\.(${extensions.join("|")})$`, "i") : undefined;
|
|
171
171
|
let maxFilesReached = maxFiles > 0 && totalFiles >= maxFiles;
|
|
172
172
|
let remainingBytes = totalFileSize - totalBytes;
|
|
173
173
|
const extensionErrors = [];
|
|
174
174
|
const minErrors = [];
|
|
175
175
|
const maxErrors = [];
|
|
176
176
|
const totalSizeErrors = [];
|
|
177
|
-
for(
|
|
178
|
-
const file = files[i];
|
|
177
|
+
for (const file of files){
|
|
179
178
|
let valid = true;
|
|
180
179
|
const { size } = file;
|
|
181
180
|
if (!isValidFileName(file, extensionRegExp, extensions)) {
|
|
@@ -202,19 +201,19 @@ import { nanoid } from "nanoid";
|
|
|
202
201
|
maxFilesReached = maxFilesReached || maxFiles > 0 && totalFiles + pending.length >= maxFiles;
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
|
-
if (extensionErrors.length) {
|
|
204
|
+
if (extensionErrors.length > 0) {
|
|
206
205
|
errors.push(new FileExtensionError(extensionErrors, extensions));
|
|
207
206
|
}
|
|
208
|
-
if (minErrors.length) {
|
|
207
|
+
if (minErrors.length > 0) {
|
|
209
208
|
errors.push(new FileSizeError(minErrors, "min", minFileSize));
|
|
210
209
|
}
|
|
211
|
-
if (maxErrors.length) {
|
|
210
|
+
if (maxErrors.length > 0) {
|
|
212
211
|
errors.push(new FileSizeError(maxErrors, "max", maxFileSize));
|
|
213
212
|
}
|
|
214
|
-
if (totalSizeErrors.length) {
|
|
213
|
+
if (totalSizeErrors.length > 0) {
|
|
215
214
|
errors.push(new FileSizeError(totalSizeErrors, "total", totalFileSize));
|
|
216
215
|
}
|
|
217
|
-
if (extraFiles.length) {
|
|
216
|
+
if (extraFiles.length > 0) {
|
|
218
217
|
errors.push(new TooManyFilesError(extraFiles, maxFiles));
|
|
219
218
|
}
|
|
220
219
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/files/validation.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\n\n/**\n * An error that will be created if a user tries dragging and dropping files\n * from a shared directory that they do not have access to. This error will not\n * occur much.\n *\n * @since 2.9.0\n */\nexport class FileAccessError extends Error {\n /**\n * A unique key generated by `nanoid` that can be used as a `React` key\n */\n public key: string;\n\n /**\n *\n * @param message - An optional message for the error.\n */\n constructor(message?: string) {\n super(message);\n this.key = nanoid();\n this.name = \"FileAccessError\";\n }\n}\n\n/**\n * An error that just requires a `File` to be passed as the first argument.\n *\n * @since 2.9.0\n */\nexport class GenericFileError extends Error {\n /**\n * A unique key generated by `nanoid` that can be used as a `React` key\n */\n public key: string;\n\n /**\n *\n * @param files - A list of files that caused the error.\n * @param reason - An optional reason for the error\n */\n constructor(\n public files: readonly File[],\n public reason?: string\n ) {\n super(\"Invalid files\");\n this.key = nanoid();\n this.name = \"GenericFileError\";\n }\n}\n\n/**\n * An error that is created during the upload process if the number of files\n * exceeds the {@link FileUploadOptions.maxFiles} amount.\n *\n * @since 2.9.0\n */\nexport class TooManyFilesError extends GenericFileError {\n /**\n *\n * @param files - The list of files that could not be uploaded due to the file\n * limit defined.\n * @param limit - The max limit of files allowed.\n */\n constructor(\n files: readonly File[],\n public limit: number\n ) {\n super(files, \"file limit\");\n this.name = \"TooManyFilesError\";\n }\n}\n\n/**\n * An error that will be created if a user tries to upload a file that\n * is either:\n * - less than the {@link FileValidationOptions.minFileSize}\n * - greater than the {@link FileValidationOptions.maxFileSize}\n * - including the file would be greater than the {@link FileValidationOptions.totalFileSize}\n *\n * @since 2.9.0\n */\nexport class FileSizeError extends GenericFileError {\n /**\n *\n * @param files - The list of files that have the file size error\n * @param type - The file size error type\n * @param limit - The number of bytes allowed based on the type\n */\n constructor(\n files: readonly File[],\n public type: \"min\" | \"max\" | \"total\",\n public limit: number\n ) {\n super(files, \"file size\");\n this.name = \"FileSizeError\";\n }\n}\n\n/**\n * An error that will be created if a user tries to upload a file that does not\n * end with one of the {@link FileValidationOptions.extensions}.\n *\n * @since 2.9.0\n */\nexport class FileExtensionError extends GenericFileError {\n /**\n *\n * @param files - The file that caused the error\n * @param extensions - The allowed list of file extensions\n */\n constructor(\n files: readonly File[],\n public extensions: readonly string[]\n ) {\n super(files, \"extension\");\n this.name = \"FileExtensionError\";\n }\n}\n\n/**\n * Mostly an internal type that is used to allow custom validation errors\n *\n * @since 2.9.0\n */\nexport type FileValidationError<E = GenericFileError> =\n | FileAccessError\n | TooManyFilesError\n | FileSizeError\n | FileExtensionError\n | E;\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link GenericFileError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileAccessError}\n */\nexport function isGenericFileError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is GenericFileError {\n return \"name\" in error && error.name === \"GenericFileError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileAccessError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileAccessError}\n */\nexport function isFileAccessError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileAccessError {\n return \"name\" in error && error.name === \"FileAccessError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link TooManyFilesError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link TooManyFilesError}\n */\nexport function isTooManyFilesError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is TooManyFilesError {\n return \"name\" in error && error.name === \"TooManyFilesError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileSizeError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileSizeError}\n */\nexport function isFileSizeError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileSizeError {\n return \"name\" in error && error.name === \"FileSizeError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileExtensionError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileExtensionError}\n */\nexport function isFileExtensionError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileExtensionError {\n return \"name\" in error && error.name === \"FileExtensionError\";\n}\n\n/**\n * This function is used to determine if a file should be added to the\n * {@link FileExtensionError}. The default implementation should work for most\n * use cases except when files that do not have extensions can be uploaded. i.e.\n * LICENSE files.\n *\n * @param file - The file being checked\n * @param extensionRegExp - A regex that will only be defined if the\n * `extensions` list had at least one value.\n * @param extensions - The list of extensions allowed\n * @returns true if the file has a valid name.\n * @since 3.1.0\n */\nexport type IsValidFileName = (\n file: File,\n extensionRegExp: RegExp | undefined,\n extensions: readonly string[]\n) => boolean;\n\n/**\n *\n * @defaultValue `matcher?.test(file.name) ?? true`\n * @since 3.1.0\n */\nexport const isValidFileName: IsValidFileName = (file, matcher) =>\n matcher?.test(file.name) ?? true;\n\n/** @since 2.9.0 */\nexport interface FileValidationOptions {\n /**\n * If the number of files should be limited, set this value to a number\n * greater than `0`.\n *\n * Note: This still allows \"infinite\" files when set to `0` since the\n * `<input>` element should normally be set to `disabled` if files should not\n * be able to be uploaded.\n *\n * @defaultValue `-1`\n */\n maxFiles?: number;\n\n /**\n * An optional minimum file size to enforce for each file. This will only be\n * used when it is greater than `0`.\n *\n * @defaultValue `-1`\n */\n minFileSize?: number;\n\n /**\n * An optional maximum file size to enforce for each file. This will only be\n * used when it is greater than `0`.\n *\n * @defaultValue `-1`\n */\n maxFileSize?: number;\n\n /**\n * An optional list of extensions to enforce when uploading files.\n *\n * Note: The extensions and file names will be compared ignoring case.\n *\n * @example Only Allow Images\n * ```ts\n * const extensions = [\"png\", \"jpeg\", \"jpg\", \"gif\"];\n * ```\n */\n extensions?: readonly string[];\n\n /** {@inheritDoc IsValidFileName} */\n isValidFileName?: IsValidFileName;\n\n /**\n * An optional total file size to enforce when the {@link maxFiles} option is\n * not set to `1`.\n *\n * @defaultValue `-1`\n */\n totalFileSize?: number;\n}\n\n/** @since 2.9.0 */\nexport interface FilesValidationOptions extends Required<FileValidationOptions> {\n /**\n * The total number of bytes in the {@link FileUploadHookReturnValue.stats}\n * list. This is really just:\n *\n * ```ts\n * const totalBytes = stats.reduce((total, stat) => total + stat.file.size, 0);\n * ```\n */\n totalBytes: number;\n\n /**\n * The total number of files in the {@link FileUploadHookReturnValue.stats}.\n */\n totalFiles: number;\n}\n\n/**\n * @since 2.9.0\n */\nexport interface ValidatedFilesResult<CustomError> {\n /**\n * A filtered list of files that have been validated and can be queued for the\n * upload process.\n */\n pending: readonly File[];\n\n /**\n * A list of {@link FileValidationError} that occurred during the validation\n * step.\n *\n * Note: If an error has occurred, the file **should not** be added to the\n * {@link pending} list of files.\n */\n errors: readonly FileValidationError<CustomError>[];\n}\n\n/**\n * This function will be called whenever a file has been uploaded by the user\n * either through an `<input type=\"file\">` or drag and drop behavior.\n *\n * @example Simple Example\n * ```ts\n * const validateFiles: FilesValidator = (files, options) => {\n * const invalid: File[] = [];\n * const pending: File[] = [];\n * for (const file of files) {\n * if (!/\\.(jpe?g|svg|png)$/i.test(name)) {\n * invalid.push(file);\n * } else {\n * pending.push(file);\n * }\n * }\n *\n * const errors: FileValidationError[] = [];\n * if (invalid.length) {\n * errors.push(new GenericFileError(invalid))\n * }\n *\n * return { pending, errors };\n * };\n * ```\n *\n * @typeParam E - An optional custom file validation error.\n * @param files - The list of files to check\n * @param options - The {@link FilesValidationOptions}\n * @returns the {@link ValidatedFilesResult}\n * @see {@link validateFiles} for the default implementation\n * @since 2.9.0\n */\nexport type FilesValidator<CustomError = never> = (\n files: readonly File[],\n options: FilesValidationOptions\n) => ValidatedFilesResult<CustomError>;\n\n/**\n * A pretty decent default implementation for validating files with the\n * {@link useFileUpload} that ensures the {@link FilesValidationOptions} are\n * enforced before allowing a file to be uploaded.\n *\n * @typeParam E - An optional custom file validation error.\n * @param files - The list of files to check\n * @param options - The {@link FilesValidationOptions}\n * @returns the {@link ValidatedFilesResult}\n * @since 2.9.0\n */\nexport function validateFiles<CustomError>(\n files: readonly File[],\n options: FilesValidationOptions\n): ValidatedFilesResult<CustomError> {\n const {\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n } = options;\n\n const errors: FileValidationError<CustomError>[] = [];\n const pending: File[] = [];\n const extraFiles: File[] = [];\n const extensionRegExp =\n extensions.length > 0\n ? new RegExp(`\\\\.(${extensions.join(\"|\")})$`, \"i\")\n : undefined;\n\n let maxFilesReached = maxFiles > 0 && totalFiles >= maxFiles;\n let remainingBytes = totalFileSize - totalBytes;\n const extensionErrors: File[] = [];\n const minErrors: File[] = [];\n const maxErrors: File[] = [];\n const totalSizeErrors: File[] = [];\n for (let i = 0; i < files.length; i += 1) {\n const file = files[i];\n\n let valid = true;\n const { size } = file;\n if (!isValidFileName(file, extensionRegExp, extensions)) {\n valid = false;\n extensionErrors.push(file);\n }\n\n if (minFileSize > 0 && size < minFileSize) {\n valid = false;\n minErrors.push(file);\n }\n\n if (maxFileSize > 0 && size > maxFileSize) {\n valid = false;\n maxErrors.push(file);\n } else if (totalFileSize > 0 && remainingBytes - file.size < 0) {\n // don't want both errors displaying\n valid = false;\n totalSizeErrors.push(file);\n }\n\n if (maxFilesReached && valid) {\n extraFiles.push(file);\n } else if (!maxFilesReached && valid) {\n pending.push(file);\n remainingBytes -= file.size;\n maxFilesReached =\n maxFilesReached ||\n (maxFiles > 0 && totalFiles + pending.length >= maxFiles);\n }\n }\n\n if (extensionErrors.length) {\n errors.push(new FileExtensionError(extensionErrors, extensions));\n }\n\n if (minErrors.length) {\n errors.push(new FileSizeError(minErrors, \"min\", minFileSize));\n }\n\n if (maxErrors.length) {\n errors.push(new FileSizeError(maxErrors, \"max\", maxFileSize));\n }\n\n if (totalSizeErrors.length) {\n errors.push(new FileSizeError(totalSizeErrors, \"total\", totalFileSize));\n }\n\n if (extraFiles.length) {\n errors.push(new TooManyFilesError(extraFiles, maxFiles));\n }\n\n return { pending, errors };\n}\n"],"names":["nanoid","FileAccessError","Error","message","key","name","GenericFileError","files","reason","TooManyFilesError","limit","FileSizeError","type","FileExtensionError","extensions","isGenericFileError","error","isFileAccessError","isTooManyFilesError","isFileSizeError","isFileExtensionError","isValidFileName","file","matcher","test","validateFiles","options","maxFiles","minFileSize","maxFileSize","totalBytes","totalFiles","totalFileSize","errors","pending","extraFiles","extensionRegExp","length","RegExp","join","undefined","maxFilesReached","remainingBytes","extensionErrors","minErrors","maxErrors","totalSizeErrors","i","valid","size","push"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/files/validation.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\n\n/**\n * An error that will be created if a user tries dragging and dropping files\n * from a shared directory that they do not have access to. This error will not\n * occur much.\n *\n * @since 2.9.0\n */\nexport class FileAccessError extends Error {\n /**\n * A unique key generated by `nanoid` that can be used as a `React` key\n */\n public key: string;\n\n /**\n *\n * @param message - An optional message for the error.\n */\n constructor(message?: string) {\n super(message);\n this.key = nanoid();\n this.name = \"FileAccessError\";\n }\n}\n\n/**\n * An error that just requires a `File` to be passed as the first argument.\n *\n * @since 2.9.0\n */\nexport class GenericFileError extends Error {\n /**\n * A unique key generated by `nanoid` that can be used as a `React` key\n */\n public key: string;\n\n /**\n *\n * @param files - A list of files that caused the error.\n * @param reason - An optional reason for the error\n */\n constructor(\n public files: readonly File[],\n public reason?: string\n ) {\n super(\"Invalid files\");\n this.key = nanoid();\n this.name = \"GenericFileError\";\n }\n}\n\n/**\n * An error that is created during the upload process if the number of files\n * exceeds the {@link FileUploadOptions.maxFiles} amount.\n *\n * @since 2.9.0\n */\nexport class TooManyFilesError extends GenericFileError {\n /**\n *\n * @param files - The list of files that could not be uploaded due to the file\n * limit defined.\n * @param limit - The max limit of files allowed.\n */\n constructor(\n files: readonly File[],\n public limit: number\n ) {\n super(files, \"file limit\");\n this.name = \"TooManyFilesError\";\n }\n}\n\n/**\n * An error that will be created if a user tries to upload a file that\n * is either:\n * - less than the {@link FileValidationOptions.minFileSize}\n * - greater than the {@link FileValidationOptions.maxFileSize}\n * - including the file would be greater than the {@link FileValidationOptions.totalFileSize}\n *\n * @since 2.9.0\n */\nexport class FileSizeError extends GenericFileError {\n /**\n *\n * @param files - The list of files that have the file size error\n * @param type - The file size error type\n * @param limit - The number of bytes allowed based on the type\n */\n constructor(\n files: readonly File[],\n public type: \"min\" | \"max\" | \"total\",\n public limit: number\n ) {\n super(files, \"file size\");\n this.name = \"FileSizeError\";\n }\n}\n\n/**\n * An error that will be created if a user tries to upload a file that does not\n * end with one of the {@link FileValidationOptions.extensions}.\n *\n * @since 2.9.0\n */\nexport class FileExtensionError extends GenericFileError {\n /**\n *\n * @param files - The file that caused the error\n * @param extensions - The allowed list of file extensions\n */\n constructor(\n files: readonly File[],\n public extensions: readonly string[]\n ) {\n super(files, \"extension\");\n this.name = \"FileExtensionError\";\n }\n}\n\n/**\n * Mostly an internal type that is used to allow custom validation errors\n *\n * @since 2.9.0\n */\nexport type FileValidationError<E = GenericFileError> =\n | FileAccessError\n | TooManyFilesError\n | FileSizeError\n | FileExtensionError\n | E;\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link GenericFileError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileAccessError}\n */\nexport function isGenericFileError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is GenericFileError {\n return \"name\" in error && error.name === \"GenericFileError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileAccessError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileAccessError}\n */\nexport function isFileAccessError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileAccessError {\n return \"name\" in error && error.name === \"FileAccessError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link TooManyFilesError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link TooManyFilesError}\n */\nexport function isTooManyFilesError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is TooManyFilesError {\n return \"name\" in error && error.name === \"TooManyFilesError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileSizeError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileSizeError}\n */\nexport function isFileSizeError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileSizeError {\n return \"name\" in error && error.name === \"FileSizeError\";\n}\n\n/**\n * A simple type-guard that can be used to check if the\n * {@link FileValidationError} is the {@link FileExtensionError} which can be\n * useful when displaying the errors to the user.\n *\n * @param error - The error to check\n * @returns true if the error is a {@link FileExtensionError}\n */\nexport function isFileExtensionError<CustomError extends object>(\n error: FileValidationError<CustomError>\n): error is FileExtensionError {\n return \"name\" in error && error.name === \"FileExtensionError\";\n}\n\n/**\n * This function is used to determine if a file should be added to the\n * {@link FileExtensionError}. The default implementation should work for most\n * use cases except when files that do not have extensions can be uploaded. i.e.\n * LICENSE files.\n *\n * @param file - The file being checked\n * @param extensionRegExp - A regex that will only be defined if the\n * `extensions` list had at least one value.\n * @param extensions - The list of extensions allowed\n * @returns true if the file has a valid name.\n * @since 3.1.0\n */\nexport type IsValidFileName = (\n file: File,\n extensionRegExp: RegExp | undefined,\n extensions: readonly string[]\n) => boolean;\n\n/**\n *\n * @defaultValue `matcher?.test(file.name) ?? true`\n * @since 3.1.0\n */\nexport const isValidFileName: IsValidFileName = (file, matcher) =>\n matcher?.test(file.name) ?? true;\n\n/** @since 2.9.0 */\nexport interface FileValidationOptions {\n /**\n * If the number of files should be limited, set this value to a number\n * greater than `0`.\n *\n * Note: This still allows \"infinite\" files when set to `0` since the\n * `<input>` element should normally be set to `disabled` if files should not\n * be able to be uploaded.\n *\n * @defaultValue `-1`\n */\n maxFiles?: number;\n\n /**\n * An optional minimum file size to enforce for each file. This will only be\n * used when it is greater than `0`.\n *\n * @defaultValue `-1`\n */\n minFileSize?: number;\n\n /**\n * An optional maximum file size to enforce for each file. This will only be\n * used when it is greater than `0`.\n *\n * @defaultValue `-1`\n */\n maxFileSize?: number;\n\n /**\n * An optional list of extensions to enforce when uploading files.\n *\n * Note: The extensions and file names will be compared ignoring case.\n *\n * @example Only Allow Images\n * ```ts\n * const extensions = [\"png\", \"jpeg\", \"jpg\", \"gif\"];\n * ```\n */\n extensions?: readonly string[];\n\n /** {@inheritDoc IsValidFileName} */\n isValidFileName?: IsValidFileName;\n\n /**\n * An optional total file size to enforce when the {@link maxFiles} option is\n * not set to `1`.\n *\n * @defaultValue `-1`\n */\n totalFileSize?: number;\n}\n\n/** @since 2.9.0 */\nexport interface FilesValidationOptions extends Required<FileValidationOptions> {\n /**\n * The total number of bytes in the {@link FileUploadHookReturnValue.stats}\n * list. This is really just:\n *\n * ```ts\n * const totalBytes = stats.reduce((total, stat) => total + stat.file.size, 0);\n * ```\n */\n totalBytes: number;\n\n /**\n * The total number of files in the {@link FileUploadHookReturnValue.stats}.\n */\n totalFiles: number;\n}\n\n/**\n * @since 2.9.0\n */\nexport interface ValidatedFilesResult<CustomError> {\n /**\n * A filtered list of files that have been validated and can be queued for the\n * upload process.\n */\n pending: readonly File[];\n\n /**\n * A list of {@link FileValidationError} that occurred during the validation\n * step.\n *\n * Note: If an error has occurred, the file **should not** be added to the\n * {@link pending} list of files.\n */\n errors: readonly FileValidationError<CustomError>[];\n}\n\n/**\n * This function will be called whenever a file has been uploaded by the user\n * either through an `<input type=\"file\">` or drag and drop behavior.\n *\n * @example Simple Example\n * ```ts\n * const validateFiles: FilesValidator = (files, options) => {\n * const invalid: File[] = [];\n * const pending: File[] = [];\n * for (const file of files) {\n * if (!/\\.(jpe?g|svg|png)$/i.test(name)) {\n * invalid.push(file);\n * } else {\n * pending.push(file);\n * }\n * }\n *\n * const errors: FileValidationError[] = [];\n * if (invalid.length) {\n * errors.push(new GenericFileError(invalid))\n * }\n *\n * return { pending, errors };\n * };\n * ```\n *\n * @typeParam E - An optional custom file validation error.\n * @param files - The list of files to check\n * @param options - The {@link FilesValidationOptions}\n * @returns the {@link ValidatedFilesResult}\n * @see {@link validateFiles} for the default implementation\n * @since 2.9.0\n */\nexport type FilesValidator<CustomError = never> = (\n files: readonly File[],\n options: FilesValidationOptions\n) => ValidatedFilesResult<CustomError>;\n\n/**\n * A pretty decent default implementation for validating files with the\n * {@link useFileUpload} that ensures the {@link FilesValidationOptions} are\n * enforced before allowing a file to be uploaded.\n *\n * @typeParam E - An optional custom file validation error.\n * @param files - The list of files to check\n * @param options - The {@link FilesValidationOptions}\n * @returns the {@link ValidatedFilesResult}\n * @since 2.9.0\n */\nexport function validateFiles<CustomError>(\n files: readonly File[],\n options: FilesValidationOptions\n): ValidatedFilesResult<CustomError> {\n const {\n maxFiles,\n extensions,\n minFileSize,\n maxFileSize,\n totalBytes,\n totalFiles,\n totalFileSize,\n isValidFileName,\n } = options;\n\n const errors: FileValidationError<CustomError>[] = [];\n const pending: File[] = [];\n const extraFiles: File[] = [];\n const extensionRegExp =\n extensions.length > 0\n ? new RegExp(String.raw`\\.(${extensions.join(\"|\")})$`, \"i\")\n : undefined;\n\n let maxFilesReached = maxFiles > 0 && totalFiles >= maxFiles;\n let remainingBytes = totalFileSize - totalBytes;\n const extensionErrors: File[] = [];\n const minErrors: File[] = [];\n const maxErrors: File[] = [];\n const totalSizeErrors: File[] = [];\n for (const file of files) {\n let valid = true;\n const { size } = file;\n if (!isValidFileName(file, extensionRegExp, extensions)) {\n valid = false;\n extensionErrors.push(file);\n }\n\n if (minFileSize > 0 && size < minFileSize) {\n valid = false;\n minErrors.push(file);\n }\n\n if (maxFileSize > 0 && size > maxFileSize) {\n valid = false;\n maxErrors.push(file);\n } else if (totalFileSize > 0 && remainingBytes - file.size < 0) {\n // don't want both errors displaying\n valid = false;\n totalSizeErrors.push(file);\n }\n\n if (maxFilesReached && valid) {\n extraFiles.push(file);\n } else if (!maxFilesReached && valid) {\n pending.push(file);\n remainingBytes -= file.size;\n maxFilesReached =\n maxFilesReached ||\n (maxFiles > 0 && totalFiles + pending.length >= maxFiles);\n }\n }\n\n if (extensionErrors.length > 0) {\n errors.push(new FileExtensionError(extensionErrors, extensions));\n }\n\n if (minErrors.length > 0) {\n errors.push(new FileSizeError(minErrors, \"min\", minFileSize));\n }\n\n if (maxErrors.length > 0) {\n errors.push(new FileSizeError(maxErrors, \"max\", maxFileSize));\n }\n\n if (totalSizeErrors.length > 0) {\n errors.push(new FileSizeError(totalSizeErrors, \"total\", totalFileSize));\n }\n\n if (extraFiles.length > 0) {\n errors.push(new TooManyFilesError(extraFiles, maxFiles));\n }\n\n return { pending, errors };\n}\n"],"names":["nanoid","FileAccessError","Error","message","key","name","GenericFileError","files","reason","TooManyFilesError","limit","FileSizeError","type","FileExtensionError","extensions","isGenericFileError","error","isFileAccessError","isTooManyFilesError","isFileSizeError","isFileExtensionError","isValidFileName","file","matcher","test","validateFiles","options","maxFiles","minFileSize","maxFileSize","totalBytes","totalFiles","totalFileSize","errors","pending","extraFiles","extensionRegExp","length","RegExp","String","raw","join","undefined","maxFilesReached","remainingBytes","extensionErrors","minErrors","maxErrors","totalSizeErrors","valid","size","push"],"mappings":"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"}
|
|
@@ -81,7 +81,7 @@ const noop = ()=>{
|
|
|
81
81
|
}
|
|
82
82
|
// For some reason, the `"Enter"` keydown event fires at a different timing
|
|
83
83
|
// than the Space keydown event.
|
|
84
|
-
|
|
84
|
+
globalThis.requestAnimationFrame(()=>{
|
|
85
85
|
prevFocus.current?.focus();
|
|
86
86
|
});
|
|
87
87
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/focus/useFocusContainer.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type KeyboardEventHandler,\n type Ref,\n type RefObject,\n useEffect,\n useRef,\n} from \"react\";\n\nimport { getTransitionCallbacks } from \"../transition/getTransitionCallbacks.js\";\nimport { type TransitionCallbacks } from \"../transition/types.js\";\nimport { useEnsuredRef } from \"../useEnsuredRef.js\";\nimport {\n type FocusElementWithinType,\n focusElementWithin,\n getFocusableElements,\n} from \"./utils.js\";\n\nconst noop = (): void => {\n // do nothing\n};\n\n/**\n * `\"mount\"` - this will attempt to focus the container element if:\n * - there is no `document.activeElement`\n * - the container element does not contain the `document.activeElement`\n *\n * `\"unmount\"` - attempts to re-focus the element that was focused before the\n * focus container became active. The previous focus element is captured\n * whenever the `activate` option on the `useFocusContainer` hook is set to\n * `true`. This is normally when an element becomes `visible`.\n *\n * `\"keyboard\"` - refocuses the first focusable element if pressing `Tab` would\n * move the focus outside of the container element. If `Shift + Tab` was used,\n * the last focusable element will be used instead.\n *\n * @since 6.0.0\n */\nexport type FocusType = \"mount\" | \"unmount\" | \"keyboard\";\n\n/**\n * @since 6.0.0\n * @deprecated Use `TransitionCallbacks` instead.\n */\nexport type FocusContainerTransitionCallbacks = TransitionCallbacks;\n\n/**\n * @since 6.0.0\n * @since 6.3.2 Fixed by extending `TransitionCallbacks` after the\n * `onEnteredOnce` and `onExitedOnce` support was added to CSS transitions.\n */\nexport interface FocusContainerTransitionOptions<\n E extends HTMLElement,\n> extends TransitionCallbacks {\n /**\n * An optional ref that will be merged with the\n * {@link FocusContainerImplementation.nodeRef}\n */\n nodeRef?: Ref<E>;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerEventHandlers<E extends HTMLElement> {\n onKeyDown?: KeyboardEventHandler<E>;\n}\n\n/**\n * @since 6.0.0\n */\nexport type IsFocusTypeDisabled = (type: FocusType) => boolean;\n\nexport interface FocusContainerComponentProps {\n /**\n * @defaultValue `() => false`\n */\n isFocusTypeDisabled?: IsFocusTypeDisabled;\n\n /**\n * @defaultValue `false`\n */\n disableTransition?: boolean;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerOptions<E extends HTMLElement>\n extends FocusContainerTransitionOptions<E>, FocusContainerComponentProps {\n onKeyDown?: KeyboardEventHandler<E>;\n /**\n * This to `true` will capture the current focused element as a focus target\n * once the `onExited` hook is fired. This should usually be set to the\n * `transitionIn` prop for `useTransition`.\n */\n activate: boolean;\n\n /**\n * Set this to true if elements that can be programmatically focused should be\n * included. These would be elements with a `tabIndex={-1}`.\n *\n * @defaultValue `false`\n */\n programmatic?: boolean;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerImplementation<E extends HTMLElement> {\n nodeRef: RefObject<E>;\n eventHandlers: Required<FocusContainerEventHandlers<E>>;\n transitionOptions: Required<FocusContainerTransitionOptions<E>>;\n}\n\n/**\n * This hook is mostly for internal use only for dialog accessibility behavior\n * to prevent the focus from moving outside of the dialog while it is visible.\n * This API was developed to be used with the `useCSSTransition`/`useTransition`\n * hooks as well.\n *\n * @example Main Usage\n * ```tsx\n * import { Button } from \"@react-md/core/button/Button\"\n * import { useFocusContainer } from \"@react-md/core/focus/useFocusContainer\"\n * import { useScaleTransition } from \"@react-md/core/transition/useScaleTransition\"\n * import { useToggle } from \"@react-md/core/useToggle\"\n * import { type ReactElement } from \"react\";\n *\n * function Example(): ReactElement {\n * const { toggled, enable, disable } = useToggle(false);\n *\n * const { eventHandlers, transitionOptions } = useFocusContainer({\n * activate: toggled,\n * });\n * const { elementProps, rendered } = useScaleTransition({\n * transitionIn: toggled,\n * temporary: true,\n * ...transitionOptions,\n * });\n *\n * return (\n * <>\n * <Button onClick={enable}>Toggle</Button>\n * {rendered && (\n * <div {...eventHandlers} {...elementProps}>\n * <Button onClick={disable}>Button 1</Button>\n * <Button onClick={disable}>Button 2</Button>\n * <Button onClick={disable}>Button 3</Button>\n * <Button onClick={disable}>Button 4</Button>\n * </div>\n * )}\n * </>\n * );\n * }\n * ```\n *\n * @since 6.0.0\n */\nexport function useFocusContainer<E extends HTMLElement>(\n options: FocusContainerOptions<E>\n): FocusContainerImplementation<E> {\n const {\n nodeRef,\n activate,\n onEnter,\n onEntering,\n onEntered,\n onExit,\n onExiting,\n onExited,\n onKeyDown = noop,\n programmatic = false,\n disableTransition = false,\n isFocusTypeDisabled = noop,\n } = options;\n\n const [ref, refCallback] = useEnsuredRef(nodeRef);\n const prevFocus = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!activate || !(document.activeElement instanceof HTMLElement)) {\n return;\n }\n\n prevFocus.current = document.activeElement;\n }, [activate]);\n\n return {\n nodeRef: ref,\n transitionOptions: {\n nodeRef: refCallback,\n ...getTransitionCallbacks({\n onEnter,\n onEnterOnce: () => {\n const instance = ref.current;\n if (\n instance &&\n !isFocusTypeDisabled(\"mount\") &&\n (!document.activeElement ||\n !instance.contains(document.activeElement))\n ) {\n instance.focus();\n }\n },\n onEntering,\n onEntered,\n onExitOnce: () => {\n if (isFocusTypeDisabled(\"unmount\")) {\n return;\n }\n\n // For some reason, the `\"Enter\"` keydown event fires at a different timing\n // than the Space keydown event.\n window.requestAnimationFrame(() => {\n prevFocus.current?.focus();\n });\n },\n onExit,\n onExiting,\n onExited,\n disableTransition,\n }),\n },\n eventHandlers: {\n onKeyDown(event) {\n onKeyDown(event);\n if (\n event.isPropagationStopped() ||\n event.key !== \"Tab\" ||\n isFocusTypeDisabled(\"keyboard\")\n ) {\n return;\n }\n\n const { target, shiftKey, currentTarget } = event;\n const elements = getFocusableElements(currentTarget, programmatic);\n const count = elements.length;\n if (count === 0) {\n event.preventDefault();\n return;\n }\n\n // if the container element is the current focus, need to either focus\n // the first or last element so focus doesn't escape\n let type: FocusElementWithinType | undefined;\n if (\n count === 1 ||\n (!shiftKey &&\n (target === currentTarget || target === elements[count - 1]))\n ) {\n type = \"first\";\n } else if (\n shiftKey &&\n (target === currentTarget || target === elements[0])\n ) {\n type = \"last\";\n }\n\n if (type) {\n event.preventDefault();\n focusElementWithin({\n type,\n elements,\n container: currentTarget,\n });\n }\n },\n },\n };\n}\n"],"names":["useEffect","useRef","getTransitionCallbacks","useEnsuredRef","focusElementWithin","getFocusableElements","noop","useFocusContainer","options","nodeRef","activate","onEnter","onEntering","onEntered","onExit","onExiting","onExited","onKeyDown","programmatic","disableTransition","isFocusTypeDisabled","ref","refCallback","prevFocus","document","activeElement","HTMLElement","current","transitionOptions","onEnterOnce","instance","contains","focus","onExitOnce","window","requestAnimationFrame","eventHandlers","event","isPropagationStopped","key","target","shiftKey","currentTarget","elements","count","length","preventDefault","type","container"],"mappings":"AAAA;AAEA,SAIEA,SAAS,EACTC,MAAM,QACD,QAAQ;AAEf,SAASC,sBAAsB,QAAQ,0CAA0C;AAEjF,SAASC,aAAa,QAAQ,sBAAsB;AACpD,SAEEC,kBAAkB,EAClBC,oBAAoB,QACf,aAAa;AAEpB,MAAMC,OAAO;AACX,aAAa;AACf;AA0FA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CC,GACD,OAAO,SAASC,kBACdC,OAAiC;IAEjC,MAAM,EACJC,OAAO,EACPC,QAAQ,EACRC,OAAO,EACPC,UAAU,EACVC,SAAS,EACTC,MAAM,EACNC,SAAS,EACTC,QAAQ,EACRC,YAAYX,IAAI,EAChBY,eAAe,KAAK,EACpBC,oBAAoB,KAAK,EACzBC,sBAAsBd,IAAI,EAC3B,GAAGE;IAEJ,MAAM,CAACa,KAAKC,YAAY,GAAGnB,cAAcM;IACzC,MAAMc,YAAYtB,OAA2B;IAE7CD,UAAU;QACR,IAAI,CAACU,YAAY,CAAEc,CAAAA,SAASC,aAAa,YAAYC,WAAU,GAAI;YACjE;QACF;QAEAH,UAAUI,OAAO,GAAGH,SAASC,aAAa;IAC5C,GAAG;QAACf;KAAS;IAEb,OAAO;QACLD,SAASY;QACTO,mBAAmB;YACjBnB,SAASa;YACT,GAAGpB,uBAAuB;gBACxBS;gBACAkB,aAAa;oBACX,MAAMC,WAAWT,IAAIM,OAAO;oBAC5B,IACEG,YACA,CAACV,oBAAoB,YACpB,CAAA,CAACI,SAASC,aAAa,IACtB,CAACK,SAASC,QAAQ,CAACP,SAASC,aAAa,CAAA,GAC3C;wBACAK,SAASE,KAAK;oBAChB;gBACF;gBACApB;gBACAC;gBACAoB,YAAY;oBACV,IAAIb,oBAAoB,YAAY;wBAClC;oBACF;oBAEA,2EAA2E;oBAC3E,iCAAiC;oBACjCc,OAAOC,qBAAqB,CAAC;wBAC3BZ,UAAUI,OAAO,EAAEK;oBACrB;gBACF;gBACAlB;gBACAC;gBACAC;gBACAG;YACF,EAAE;QACJ;QACAiB,eAAe;YACbnB,WAAUoB,KAAK;gBACbpB,UAAUoB;gBACV,IACEA,MAAMC,oBAAoB,MAC1BD,MAAME,GAAG,KAAK,SACdnB,oBAAoB,aACpB;oBACA;gBACF;gBAEA,MAAM,EAAEoB,MAAM,EAAEC,QAAQ,EAAEC,aAAa,EAAE,GAAGL;gBAC5C,MAAMM,WAAWtC,qBAAqBqC,eAAexB;gBACrD,MAAM0B,QAAQD,SAASE,MAAM;gBAC7B,IAAID,UAAU,GAAG;oBACfP,MAAMS,cAAc;oBACpB;gBACF;gBAEA,sEAAsE;gBACtE,oDAAoD;gBACpD,IAAIC;gBACJ,IACEH,UAAU,KACT,CAACH,YACCD,CAAAA,WAAWE,iBAAiBF,WAAWG,QAAQ,CAACC,QAAQ,EAAE,AAAD,GAC5D;oBACAG,OAAO;gBACT,OAAO,IACLN,YACCD,CAAAA,WAAWE,iBAAiBF,WAAWG,QAAQ,CAAC,EAAE,AAAD,GAClD;oBACAI,OAAO;gBACT;gBAEA,IAAIA,MAAM;oBACRV,MAAMS,cAAc;oBACpB1C,mBAAmB;wBACjB2C;wBACAJ;wBACAK,WAAWN;oBACb;gBACF;YACF;QACF;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/focus/useFocusContainer.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type KeyboardEventHandler,\n type Ref,\n type RefObject,\n useEffect,\n useRef,\n} from \"react\";\n\nimport { getTransitionCallbacks } from \"../transition/getTransitionCallbacks.js\";\nimport { type TransitionCallbacks } from \"../transition/types.js\";\nimport { useEnsuredRef } from \"../useEnsuredRef.js\";\nimport {\n type FocusElementWithinType,\n focusElementWithin,\n getFocusableElements,\n} from \"./utils.js\";\n\nconst noop = (): void => {\n // do nothing\n};\n\n/**\n * `\"mount\"` - this will attempt to focus the container element if:\n * - there is no `document.activeElement`\n * - the container element does not contain the `document.activeElement`\n *\n * `\"unmount\"` - attempts to re-focus the element that was focused before the\n * focus container became active. The previous focus element is captured\n * whenever the `activate` option on the `useFocusContainer` hook is set to\n * `true`. This is normally when an element becomes `visible`.\n *\n * `\"keyboard\"` - refocuses the first focusable element if pressing `Tab` would\n * move the focus outside of the container element. If `Shift + Tab` was used,\n * the last focusable element will be used instead.\n *\n * @since 6.0.0\n */\nexport type FocusType = \"mount\" | \"unmount\" | \"keyboard\";\n\n/**\n * @since 6.0.0\n * @deprecated Use `TransitionCallbacks` instead.\n */\nexport type FocusContainerTransitionCallbacks = TransitionCallbacks;\n\n/**\n * @since 6.0.0\n * @since 6.3.2 Fixed by extending `TransitionCallbacks` after the\n * `onEnteredOnce` and `onExitedOnce` support was added to CSS transitions.\n */\nexport interface FocusContainerTransitionOptions<\n E extends HTMLElement,\n> extends TransitionCallbacks {\n /**\n * An optional ref that will be merged with the\n * {@link FocusContainerImplementation.nodeRef}\n */\n nodeRef?: Ref<E>;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerEventHandlers<E extends HTMLElement> {\n onKeyDown?: KeyboardEventHandler<E>;\n}\n\n/**\n * @since 6.0.0\n */\nexport type IsFocusTypeDisabled = (type: FocusType) => boolean;\n\nexport interface FocusContainerComponentProps {\n /**\n * @defaultValue `() => false`\n */\n isFocusTypeDisabled?: IsFocusTypeDisabled;\n\n /**\n * @defaultValue `false`\n */\n disableTransition?: boolean;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerOptions<E extends HTMLElement>\n extends FocusContainerTransitionOptions<E>, FocusContainerComponentProps {\n onKeyDown?: KeyboardEventHandler<E>;\n /**\n * This to `true` will capture the current focused element as a focus target\n * once the `onExited` hook is fired. This should usually be set to the\n * `transitionIn` prop for `useTransition`.\n */\n activate: boolean;\n\n /**\n * Set this to true if elements that can be programmatically focused should be\n * included. These would be elements with a `tabIndex={-1}`.\n *\n * @defaultValue `false`\n */\n programmatic?: boolean;\n}\n\n/** @since 6.0.0 */\nexport interface FocusContainerImplementation<E extends HTMLElement> {\n nodeRef: RefObject<E>;\n eventHandlers: Required<FocusContainerEventHandlers<E>>;\n transitionOptions: Required<FocusContainerTransitionOptions<E>>;\n}\n\n/**\n * This hook is mostly for internal use only for dialog accessibility behavior\n * to prevent the focus from moving outside of the dialog while it is visible.\n * This API was developed to be used with the `useCSSTransition`/`useTransition`\n * hooks as well.\n *\n * @example Main Usage\n * ```tsx\n * import { Button } from \"@react-md/core/button/Button\"\n * import { useFocusContainer } from \"@react-md/core/focus/useFocusContainer\"\n * import { useScaleTransition } from \"@react-md/core/transition/useScaleTransition\"\n * import { useToggle } from \"@react-md/core/useToggle\"\n * import { type ReactElement } from \"react\";\n *\n * function Example(): ReactElement {\n * const { toggled, enable, disable } = useToggle(false);\n *\n * const { eventHandlers, transitionOptions } = useFocusContainer({\n * activate: toggled,\n * });\n * const { elementProps, rendered } = useScaleTransition({\n * transitionIn: toggled,\n * temporary: true,\n * ...transitionOptions,\n * });\n *\n * return (\n * <>\n * <Button onClick={enable}>Toggle</Button>\n * {rendered && (\n * <div {...eventHandlers} {...elementProps}>\n * <Button onClick={disable}>Button 1</Button>\n * <Button onClick={disable}>Button 2</Button>\n * <Button onClick={disable}>Button 3</Button>\n * <Button onClick={disable}>Button 4</Button>\n * </div>\n * )}\n * </>\n * );\n * }\n * ```\n *\n * @since 6.0.0\n */\nexport function useFocusContainer<E extends HTMLElement>(\n options: FocusContainerOptions<E>\n): FocusContainerImplementation<E> {\n const {\n nodeRef,\n activate,\n onEnter,\n onEntering,\n onEntered,\n onExit,\n onExiting,\n onExited,\n onKeyDown = noop,\n programmatic = false,\n disableTransition = false,\n isFocusTypeDisabled = noop,\n } = options;\n\n const [ref, refCallback] = useEnsuredRef(nodeRef);\n const prevFocus = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!activate || !(document.activeElement instanceof HTMLElement)) {\n return;\n }\n\n prevFocus.current = document.activeElement;\n }, [activate]);\n\n return {\n nodeRef: ref,\n transitionOptions: {\n nodeRef: refCallback,\n ...getTransitionCallbacks({\n onEnter,\n onEnterOnce: () => {\n const instance = ref.current;\n if (\n instance &&\n !isFocusTypeDisabled(\"mount\") &&\n (!document.activeElement ||\n !instance.contains(document.activeElement))\n ) {\n instance.focus();\n }\n },\n onEntering,\n onEntered,\n onExitOnce: () => {\n if (isFocusTypeDisabled(\"unmount\")) {\n return;\n }\n\n // For some reason, the `\"Enter\"` keydown event fires at a different timing\n // than the Space keydown event.\n globalThis.requestAnimationFrame(() => {\n prevFocus.current?.focus();\n });\n },\n onExit,\n onExiting,\n onExited,\n disableTransition,\n }),\n },\n eventHandlers: {\n onKeyDown(event) {\n onKeyDown(event);\n if (\n event.isPropagationStopped() ||\n event.key !== \"Tab\" ||\n isFocusTypeDisabled(\"keyboard\")\n ) {\n return;\n }\n\n const { target, shiftKey, currentTarget } = event;\n const elements = getFocusableElements(currentTarget, programmatic);\n const count = elements.length;\n if (count === 0) {\n event.preventDefault();\n return;\n }\n\n // if the container element is the current focus, need to either focus\n // the first or last element so focus doesn't escape\n let type: FocusElementWithinType | undefined;\n if (\n count === 1 ||\n (!shiftKey &&\n (target === currentTarget || target === elements[count - 1]))\n ) {\n type = \"first\";\n } else if (\n shiftKey &&\n (target === currentTarget || target === elements[0])\n ) {\n type = \"last\";\n }\n\n if (type) {\n event.preventDefault();\n focusElementWithin({\n type,\n elements,\n container: currentTarget,\n });\n }\n },\n },\n };\n}\n"],"names":["useEffect","useRef","getTransitionCallbacks","useEnsuredRef","focusElementWithin","getFocusableElements","noop","useFocusContainer","options","nodeRef","activate","onEnter","onEntering","onEntered","onExit","onExiting","onExited","onKeyDown","programmatic","disableTransition","isFocusTypeDisabled","ref","refCallback","prevFocus","document","activeElement","HTMLElement","current","transitionOptions","onEnterOnce","instance","contains","focus","onExitOnce","globalThis","requestAnimationFrame","eventHandlers","event","isPropagationStopped","key","target","shiftKey","currentTarget","elements","count","length","preventDefault","type","container"],"mappings":"AAAA;AAEA,SAIEA,SAAS,EACTC,MAAM,QACD,QAAQ;AAEf,SAASC,sBAAsB,QAAQ,0CAA0C;AAEjF,SAASC,aAAa,QAAQ,sBAAsB;AACpD,SAEEC,kBAAkB,EAClBC,oBAAoB,QACf,aAAa;AAEpB,MAAMC,OAAO;AACX,aAAa;AACf;AA0FA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CC,GACD,OAAO,SAASC,kBACdC,OAAiC;IAEjC,MAAM,EACJC,OAAO,EACPC,QAAQ,EACRC,OAAO,EACPC,UAAU,EACVC,SAAS,EACTC,MAAM,EACNC,SAAS,EACTC,QAAQ,EACRC,YAAYX,IAAI,EAChBY,eAAe,KAAK,EACpBC,oBAAoB,KAAK,EACzBC,sBAAsBd,IAAI,EAC3B,GAAGE;IAEJ,MAAM,CAACa,KAAKC,YAAY,GAAGnB,cAAcM;IACzC,MAAMc,YAAYtB,OAA2B;IAE7CD,UAAU;QACR,IAAI,CAACU,YAAY,CAAEc,CAAAA,SAASC,aAAa,YAAYC,WAAU,GAAI;YACjE;QACF;QAEAH,UAAUI,OAAO,GAAGH,SAASC,aAAa;IAC5C,GAAG;QAACf;KAAS;IAEb,OAAO;QACLD,SAASY;QACTO,mBAAmB;YACjBnB,SAASa;YACT,GAAGpB,uBAAuB;gBACxBS;gBACAkB,aAAa;oBACX,MAAMC,WAAWT,IAAIM,OAAO;oBAC5B,IACEG,YACA,CAACV,oBAAoB,YACpB,CAAA,CAACI,SAASC,aAAa,IACtB,CAACK,SAASC,QAAQ,CAACP,SAASC,aAAa,CAAA,GAC3C;wBACAK,SAASE,KAAK;oBAChB;gBACF;gBACApB;gBACAC;gBACAoB,YAAY;oBACV,IAAIb,oBAAoB,YAAY;wBAClC;oBACF;oBAEA,2EAA2E;oBAC3E,iCAAiC;oBACjCc,WAAWC,qBAAqB,CAAC;wBAC/BZ,UAAUI,OAAO,EAAEK;oBACrB;gBACF;gBACAlB;gBACAC;gBACAC;gBACAG;YACF,EAAE;QACJ;QACAiB,eAAe;YACbnB,WAAUoB,KAAK;gBACbpB,UAAUoB;gBACV,IACEA,MAAMC,oBAAoB,MAC1BD,MAAME,GAAG,KAAK,SACdnB,oBAAoB,aACpB;oBACA;gBACF;gBAEA,MAAM,EAAEoB,MAAM,EAAEC,QAAQ,EAAEC,aAAa,EAAE,GAAGL;gBAC5C,MAAMM,WAAWtC,qBAAqBqC,eAAexB;gBACrD,MAAM0B,QAAQD,SAASE,MAAM;gBAC7B,IAAID,UAAU,GAAG;oBACfP,MAAMS,cAAc;oBACpB;gBACF;gBAEA,sEAAsE;gBACtE,oDAAoD;gBACpD,IAAIC;gBACJ,IACEH,UAAU,KACT,CAACH,YACCD,CAAAA,WAAWE,iBAAiBF,WAAWG,QAAQ,CAACC,QAAQ,EAAE,AAAD,GAC5D;oBACAG,OAAO;gBACT,OAAO,IACLN,YACCD,CAAAA,WAAWE,iBAAiBF,WAAWG,QAAQ,CAAC,EAAE,AAAD,GAClD;oBACAI,OAAO;gBACT;gBAEA,IAAIA,MAAM;oBACRV,MAAMS,cAAc;oBACpB1C,mBAAmB;wBACjB2C;wBACAJ;wBACAK,WAAWN;oBACb;gBACF;YACF;QACF;IACF;AACF"}
|
package/dist/focus/utils.js
CHANGED
|
@@ -30,7 +30,8 @@ const queries = [
|
|
|
30
30
|
* @since 6.0.0 This was updated to remove ALL elements that have a
|
|
31
31
|
* `tabindex="-1"` applied instead of only elements that had a manual tab index
|
|
32
32
|
* applied.
|
|
33
|
-
*/
|
|
33
|
+
*/ // eslint-disable-next-line unicorn/no-array-reduce
|
|
34
|
+
export const TAB_FOCUSABLE = queries.reduce((fullQuery, query)=>{
|
|
34
35
|
const prefix = `${fullQuery}${fullQuery ? "," : ""}`;
|
|
35
36
|
const notProgrammaticQuery = `${query}:not([tabindex="-1"])`;
|
|
36
37
|
return `${prefix}${notProgrammaticQuery}`;
|
|
@@ -81,12 +82,16 @@ export function focusElementWithin(options) {
|
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
83
84
|
let element = null;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
switch(type){
|
|
86
|
+
case "first":
|
|
87
|
+
[element] = elements;
|
|
88
|
+
break;
|
|
89
|
+
case "last":
|
|
90
|
+
element = elements.at(-1) ?? null;
|
|
91
|
+
break;
|
|
92
|
+
case "query":
|
|
93
|
+
element = document.querySelector(query);
|
|
94
|
+
break;
|
|
90
95
|
}
|
|
91
96
|
if (!element && isFocusable(container)) {
|
|
92
97
|
element = container;
|
package/dist/focus/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/focus/utils.ts"],"sourcesContent":["const queries = [\n 'input:not([type=\"hidden\"]):not([disabled])',\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"a[href]\",\n \"area[href]\",\n \"[tabindex]\",\n] as const;\n\n/**\n * A query selector to find elements that are programmatically focusable.\n *\n * Example:\n *\n * ```ts\n * const focusableElements = document.querySelectorAll(PROGRAMMATICALLY_FOCUSABLE);\n * // do something with elements\n * ```\n */\nexport const PROGRAMMATICALLY_FOCUSABLE = queries.join(\",\");\n\n/**\n * A query selector to find elements that are focusable only with tab and shift+tab.\n *\n * Example:\n *\n * ```ts\n * const focusableElements = document.querySelectorAll(TAB_FOCUSABLE);\n * // do something with elements\n * ```\n *\n * @since 6.0.0 This was updated to remove ALL elements that have a\n * `tabindex=\"-1\"` applied instead of only elements that had a manual tab index\n * applied.\n */\nexport const TAB_FOCUSABLE = queries.reduce((fullQuery, query) => {\n const prefix = `${fullQuery}${fullQuery ? \",\" : \"\"}`;\n const notProgrammaticQuery = `${query}:not([tabindex=\"-1\"])`;\n\n return `${prefix}${notProgrammaticQuery}`;\n}, \"\");\n\n/**\n * A simple util that will find all the tab focusable elements within a\n * container element. The container should normally be a specific HTMLElement,\n * but it can also be the entire document if you want to find **all** focusable\n * elements within your page.\n *\n * @since 6.0.0 This function will be used for all keyboard focus behavior\n * instead of trying to use refs. I did some performance testing in Firefox and\n * Chrome and the `querySelectorAll` takes 4ms with 5000 elements for my use\n * cases. There is more of a performance issue around rendering all 5000\n * elements and since this is only used for specific keydown events, this is\n * good enough for me.\n *\n * @param container - The container element/document to find focusable elements\n * within.\n * @param programmatic - Boolean if programmatically focusable elements should be\n * included instead of only tab focusable.\n * @returns A list of HTMLElements that are focusable within the container.\n */\nexport function getFocusableElements(\n container: HTMLElement | Document,\n programmatic = false\n): readonly HTMLElement[] {\n // spread operator is faster than Array.from\n return [\n ...container.querySelectorAll<HTMLElement>(\n programmatic ? PROGRAMMATICALLY_FOCUSABLE : TAB_FOCUSABLE\n ),\n ];\n}\n\n/**\n * Attempts to find the first focusable element within a container.\n *\n * @since 6.0.0\n * @param container - The container element/document to find focusable elements\n * within.\n * @param programmatic - Boolean if programmatically focusable elements should be\n * included instead of only tab focusable.\n * @returns A list of HTMLElements that are focusable within the container.\n */\nexport function getFocusableElement(\n container: HTMLElement | Document,\n programmatic = false\n): HTMLElement | null {\n return container.querySelector<HTMLElement>(\n programmatic ? PROGRAMMATICALLY_FOCUSABLE : TAB_FOCUSABLE\n );\n}\n\n/**\n * An element can be tab focused if it is:\n * - an anchor or area with an `href`\n * - a non-disabled `input` element that is not `type=\"hidden\"`\n * - a non-disabled `button`, `textarea`, or `select` element\n * - an element with a `tabIndex >= 0`\n *\n * An element can be noted as \"programmatically focusable only\" has the above\n * rules, but the `tabIndex` will be set to `-1`.\n *\n * @since 2.8.0\n */\nexport type ElementFocusType = \"tab\" | \"programmatic\";\n\nexport function isFocusable(\n element: HTMLElement | Document | Window,\n type: ElementFocusType = \"programmatic\"\n): element is HTMLElement {\n const selector = type === \"tab\" ? TAB_FOCUSABLE : PROGRAMMATICALLY_FOCUSABLE;\n\n return \"matches\" in element && element.matches(selector);\n}\n\nexport type FocusElementWithinType =\n | \"first\"\n | \"last\"\n | \"query\"\n | \"self\"\n | \"none\";\n\nexport interface FocusElementWithinOptions {\n type: FocusElementWithinType;\n query?: string;\n elements: readonly HTMLElement[];\n container: HTMLElement | Document;\n}\n\nexport function focusElementWithin(options: FocusElementWithinOptions): void {\n const { type, query = \"\", container, elements } = options;\n if (type === \"none\") {\n return;\n }\n\n let element: HTMLElement | null = null;\n
|
|
1
|
+
{"version":3,"sources":["../../src/focus/utils.ts"],"sourcesContent":["const queries = [\n 'input:not([type=\"hidden\"]):not([disabled])',\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"select:not([disabled])\",\n \"a[href]\",\n \"area[href]\",\n \"[tabindex]\",\n] as const;\n\n/**\n * A query selector to find elements that are programmatically focusable.\n *\n * Example:\n *\n * ```ts\n * const focusableElements = document.querySelectorAll(PROGRAMMATICALLY_FOCUSABLE);\n * // do something with elements\n * ```\n */\nexport const PROGRAMMATICALLY_FOCUSABLE = queries.join(\",\");\n\n/**\n * A query selector to find elements that are focusable only with tab and shift+tab.\n *\n * Example:\n *\n * ```ts\n * const focusableElements = document.querySelectorAll(TAB_FOCUSABLE);\n * // do something with elements\n * ```\n *\n * @since 6.0.0 This was updated to remove ALL elements that have a\n * `tabindex=\"-1\"` applied instead of only elements that had a manual tab index\n * applied.\n */\n// eslint-disable-next-line unicorn/no-array-reduce\nexport const TAB_FOCUSABLE = queries.reduce((fullQuery, query) => {\n const prefix = `${fullQuery}${fullQuery ? \",\" : \"\"}`;\n const notProgrammaticQuery = `${query}:not([tabindex=\"-1\"])`;\n\n return `${prefix}${notProgrammaticQuery}`;\n}, \"\");\n\n/**\n * A simple util that will find all the tab focusable elements within a\n * container element. The container should normally be a specific HTMLElement,\n * but it can also be the entire document if you want to find **all** focusable\n * elements within your page.\n *\n * @since 6.0.0 This function will be used for all keyboard focus behavior\n * instead of trying to use refs. I did some performance testing in Firefox and\n * Chrome and the `querySelectorAll` takes 4ms with 5000 elements for my use\n * cases. There is more of a performance issue around rendering all 5000\n * elements and since this is only used for specific keydown events, this is\n * good enough for me.\n *\n * @param container - The container element/document to find focusable elements\n * within.\n * @param programmatic - Boolean if programmatically focusable elements should be\n * included instead of only tab focusable.\n * @returns A list of HTMLElements that are focusable within the container.\n */\nexport function getFocusableElements(\n container: HTMLElement | Document,\n programmatic = false\n): readonly HTMLElement[] {\n // spread operator is faster than Array.from\n return [\n ...container.querySelectorAll<HTMLElement>(\n programmatic ? PROGRAMMATICALLY_FOCUSABLE : TAB_FOCUSABLE\n ),\n ];\n}\n\n/**\n * Attempts to find the first focusable element within a container.\n *\n * @since 6.0.0\n * @param container - The container element/document to find focusable elements\n * within.\n * @param programmatic - Boolean if programmatically focusable elements should be\n * included instead of only tab focusable.\n * @returns A list of HTMLElements that are focusable within the container.\n */\nexport function getFocusableElement(\n container: HTMLElement | Document,\n programmatic = false\n): HTMLElement | null {\n return container.querySelector<HTMLElement>(\n programmatic ? PROGRAMMATICALLY_FOCUSABLE : TAB_FOCUSABLE\n );\n}\n\n/**\n * An element can be tab focused if it is:\n * - an anchor or area with an `href`\n * - a non-disabled `input` element that is not `type=\"hidden\"`\n * - a non-disabled `button`, `textarea`, or `select` element\n * - an element with a `tabIndex >= 0`\n *\n * An element can be noted as \"programmatically focusable only\" has the above\n * rules, but the `tabIndex` will be set to `-1`.\n *\n * @since 2.8.0\n */\nexport type ElementFocusType = \"tab\" | \"programmatic\";\n\nexport function isFocusable(\n element: HTMLElement | Document | Window,\n type: ElementFocusType = \"programmatic\"\n): element is HTMLElement {\n const selector = type === \"tab\" ? TAB_FOCUSABLE : PROGRAMMATICALLY_FOCUSABLE;\n\n return \"matches\" in element && element.matches(selector);\n}\n\nexport type FocusElementWithinType =\n | \"first\"\n | \"last\"\n | \"query\"\n | \"self\"\n | \"none\";\n\nexport interface FocusElementWithinOptions {\n type: FocusElementWithinType;\n query?: string;\n elements: readonly HTMLElement[];\n container: HTMLElement | Document;\n}\n\nexport function focusElementWithin(options: FocusElementWithinOptions): void {\n const { type, query = \"\", container, elements } = options;\n if (type === \"none\") {\n return;\n }\n\n let element: HTMLElement | null = null;\n switch (type) {\n case \"first\":\n [element] = elements;\n break;\n case \"last\":\n element = elements.at(-1) ?? null;\n break;\n case \"query\":\n element = document.querySelector<HTMLElement>(query);\n break;\n }\n\n if (!element && isFocusable(container)) {\n element = container;\n }\n\n // TODO: Why did I add preventScroll?\n element?.focus();\n}\n"],"names":["queries","PROGRAMMATICALLY_FOCUSABLE","join","TAB_FOCUSABLE","reduce","fullQuery","query","prefix","notProgrammaticQuery","getFocusableElements","container","programmatic","querySelectorAll","getFocusableElement","querySelector","isFocusable","element","type","selector","matches","focusElementWithin","options","elements","at","document","focus"],"mappings":"AAAA,MAAMA,UAAU;IACd;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED;;;;;;;;;CASC,GACD,OAAO,MAAMC,6BAA6BD,QAAQE,IAAI,CAAC,KAAK;AAE5D;;;;;;;;;;;;;CAaC,GACD,mDAAmD;AACnD,OAAO,MAAMC,gBAAgBH,QAAQI,MAAM,CAAC,CAACC,WAAWC;IACtD,MAAMC,SAAS,GAAGF,YAAYA,YAAY,MAAM,IAAI;IACpD,MAAMG,uBAAuB,GAAGF,MAAM,qBAAqB,CAAC;IAE5D,OAAO,GAAGC,SAASC,sBAAsB;AAC3C,GAAG,IAAI;AAEP;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,qBACdC,SAAiC,EACjCC,eAAe,KAAK;IAEpB,4CAA4C;IAC5C,OAAO;WACFD,UAAUE,gBAAgB,CAC3BD,eAAeV,6BAA6BE;KAE/C;AACH;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASU,oBACdH,SAAiC,EACjCC,eAAe,KAAK;IAEpB,OAAOD,UAAUI,aAAa,CAC5BH,eAAeV,6BAA6BE;AAEhD;AAgBA,OAAO,SAASY,YACdC,OAAwC,EACxCC,OAAyB,cAAc;IAEvC,MAAMC,WAAWD,SAAS,QAAQd,gBAAgBF;IAElD,OAAO,aAAae,WAAWA,QAAQG,OAAO,CAACD;AACjD;AAgBA,OAAO,SAASE,mBAAmBC,OAAkC;IACnE,MAAM,EAAEJ,IAAI,EAAEX,QAAQ,EAAE,EAAEI,SAAS,EAAEY,QAAQ,EAAE,GAAGD;IAClD,IAAIJ,SAAS,QAAQ;QACnB;IACF;IAEA,IAAID,UAA8B;IAClC,OAAQC;QACN,KAAK;YACH,CAACD,QAAQ,GAAGM;YACZ;QACF,KAAK;YACHN,UAAUM,SAASC,EAAE,CAAC,CAAC,MAAM;YAC7B;QACF,KAAK;YACHP,UAAUQ,SAASV,aAAa,CAAcR;YAC9C;IACJ;IAEA,IAAI,CAACU,WAAWD,YAAYL,YAAY;QACtCM,UAAUN;IACZ;IAEA,qCAAqC;IACrCM,SAASS;AACX"}
|
|
@@ -19,7 +19,11 @@ import { inputToggle } from "./inputToggleStyles.js";
|
|
|
19
19
|
const uncontrolled = typeof checked !== "boolean";
|
|
20
20
|
if (!uncontrolled || !FORM_CONFIG.uncontrolledToggles) {
|
|
21
21
|
active = !!checked && !error;
|
|
22
|
-
|
|
22
|
+
if (checked) {
|
|
23
|
+
icon = indeterminate ? indeterminateIcon : checkedIcon;
|
|
24
|
+
} else {
|
|
25
|
+
icon = uncheckedIcon;
|
|
26
|
+
}
|
|
23
27
|
} else {
|
|
24
28
|
icon = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
25
29
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/form/InputToggleIcon.tsx"],"sourcesContent":["import { cnb } from \"cnbuilder\";\nimport { type HTMLAttributes, type ReactNode, forwardRef } from \"react\";\n\nimport { getIcon } from \"../icon/config.js\";\nimport { FORM_CONFIG } from \"./formConfig.js\";\nimport { type InputToggleSize, inputToggle } from \"./inputToggleStyles.js\";\n\n/**\n * @since 2.8.0\n * @since 6.0.0 Removed the `circle` and `overlay` props since they are no\n * longer needed. Added the `icon`, `disableEm`, `checkedIcon` and\n * `indeterminateIcon` props.\n * @internal\n */\nexport interface InputToggleIconProps extends HTMLAttributes<HTMLSpanElement> {\n type: \"checkbox\" | \"radio\";\n /**\n * Note: If this is `undefined` and the {@link FORM_CONFIG.uncontrolledToggles} is `false`,\n * the icon state won't work.\n */\n checked?: boolean;\n\n /**\n * The icon to display while {@link checked} is `false`.\n */\n icon?: ReactNode;\n\n /**\n * The icon to display while {@link checked} is `true` and\n * {@link indeterminate} is `false`.\n */\n checkedIcon?: ReactNode;\n\n /**\n * The icon to display while both {@link checked} and {@link indeterminate}\n * are `true`.\n */\n indeterminateIcon?: ReactNode;\n\n /** @defaultValue `\"normal\"` */\n size?: InputToggleSize;\n\n /** @defaultValue `false` */\n error?: boolean;\n\n /** @defaultValue `false` */\n disabled?: boolean;\n\n /** @defaultValue `false` */\n disableEm?: boolean;\n\n /** @defaultValue `false` */\n indeterminate?: boolean;\n}\n\n/**\n * @since 2.8.0\n * @since 6.0.0 Updated to use different icons for each checked state instead\n * of a css overlay hack.\n * @internal\n */\nexport const InputToggleIcon = forwardRef<\n HTMLSpanElement,\n InputToggleIconProps\n>(function InputToggleIcon(props, ref) {\n const {\n type,\n size = \"normal\",\n icon: propIcon,\n error,\n checked,\n children,\n disabled,\n indeterminate,\n className,\n disableEm = false,\n checkedIcon: propCheckedIcon,\n indeterminateIcon: propIndeterminateIcon,\n ...remaining\n } = props;\n const uncheckedIcon = getIcon(type, propIcon);\n const checkedIcon = getIcon(`${type}Checked`, propCheckedIcon);\n const indeterminateIcon = getIcon(\n \"checkboxIndeterminate\",\n propIndeterminateIcon\n );\n\n let icon: ReactNode;\n let active = false;\n const uncontrolled = typeof checked !== \"boolean\";\n if (!uncontrolled || !FORM_CONFIG.uncontrolledToggles) {\n active = !!checked && !error;\n
|
|
1
|
+
{"version":3,"sources":["../../src/form/InputToggleIcon.tsx"],"sourcesContent":["import { cnb } from \"cnbuilder\";\nimport { type HTMLAttributes, type ReactNode, forwardRef } from \"react\";\n\nimport { getIcon } from \"../icon/config.js\";\nimport { FORM_CONFIG } from \"./formConfig.js\";\nimport { type InputToggleSize, inputToggle } from \"./inputToggleStyles.js\";\n\n/**\n * @since 2.8.0\n * @since 6.0.0 Removed the `circle` and `overlay` props since they are no\n * longer needed. Added the `icon`, `disableEm`, `checkedIcon` and\n * `indeterminateIcon` props.\n * @internal\n */\nexport interface InputToggleIconProps extends HTMLAttributes<HTMLSpanElement> {\n type: \"checkbox\" | \"radio\";\n /**\n * Note: If this is `undefined` and the {@link FORM_CONFIG.uncontrolledToggles} is `false`,\n * the icon state won't work.\n */\n checked?: boolean;\n\n /**\n * The icon to display while {@link checked} is `false`.\n */\n icon?: ReactNode;\n\n /**\n * The icon to display while {@link checked} is `true` and\n * {@link indeterminate} is `false`.\n */\n checkedIcon?: ReactNode;\n\n /**\n * The icon to display while both {@link checked} and {@link indeterminate}\n * are `true`.\n */\n indeterminateIcon?: ReactNode;\n\n /** @defaultValue `\"normal\"` */\n size?: InputToggleSize;\n\n /** @defaultValue `false` */\n error?: boolean;\n\n /** @defaultValue `false` */\n disabled?: boolean;\n\n /** @defaultValue `false` */\n disableEm?: boolean;\n\n /** @defaultValue `false` */\n indeterminate?: boolean;\n}\n\n/**\n * @since 2.8.0\n * @since 6.0.0 Updated to use different icons for each checked state instead\n * of a css overlay hack.\n * @internal\n */\nexport const InputToggleIcon = forwardRef<\n HTMLSpanElement,\n InputToggleIconProps\n>(function InputToggleIcon(props, ref) {\n const {\n type,\n size = \"normal\",\n icon: propIcon,\n error,\n checked,\n children,\n disabled,\n indeterminate,\n className,\n disableEm = false,\n checkedIcon: propCheckedIcon,\n indeterminateIcon: propIndeterminateIcon,\n ...remaining\n } = props;\n const uncheckedIcon = getIcon(type, propIcon);\n const checkedIcon = getIcon(`${type}Checked`, propCheckedIcon);\n const indeterminateIcon = getIcon(\n \"checkboxIndeterminate\",\n propIndeterminateIcon\n );\n\n let icon: ReactNode;\n let active = false;\n const uncontrolled = typeof checked !== \"boolean\";\n if (!uncontrolled || !FORM_CONFIG.uncontrolledToggles) {\n active = !!checked && !error;\n if (checked) {\n icon = indeterminate ? indeterminateIcon : checkedIcon;\n } else {\n icon = uncheckedIcon;\n }\n } else {\n icon = (\n <>\n {uncheckedIcon}\n {indeterminate ? indeterminateIcon : checkedIcon}\n </>\n );\n }\n\n return (\n <span\n {...remaining}\n ref={ref}\n className={cnb(\n inputToggle({\n em: !disableEm,\n size,\n type,\n error,\n active,\n disabled,\n uncontrolled,\n className,\n })\n )}\n >\n {children}\n {icon}\n </span>\n );\n});\n"],"names":["cnb","forwardRef","getIcon","FORM_CONFIG","inputToggle","InputToggleIcon","props","ref","type","size","icon","propIcon","error","checked","children","disabled","indeterminate","className","disableEm","checkedIcon","propCheckedIcon","indeterminateIcon","propIndeterminateIcon","remaining","uncheckedIcon","active","uncontrolled","uncontrolledToggles","span","em"],"mappings":";AAAA,SAASA,GAAG,QAAQ,YAAY;AAChC,SAA8CC,UAAU,QAAQ,QAAQ;AAExE,SAASC,OAAO,QAAQ,oBAAoB;AAC5C,SAASC,WAAW,QAAQ,kBAAkB;AAC9C,SAA+BC,WAAW,QAAQ,yBAAyB;AAkD3E;;;;;CAKC,GACD,OAAO,MAAMC,gCAAkBJ,WAG7B,SAASI,gBAAgBC,KAAK,EAAEC,GAAG;IACnC,MAAM,EACJC,IAAI,EACJC,OAAO,QAAQ,EACfC,MAAMC,QAAQ,EACdC,KAAK,EACLC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,aAAa,EACbC,SAAS,EACTC,YAAY,KAAK,EACjBC,aAAaC,eAAe,EAC5BC,mBAAmBC,qBAAqB,EACxC,GAAGC,WACJ,GAAGjB;IACJ,MAAMkB,gBAAgBtB,QAAQM,MAAMG;IACpC,MAAMQ,cAAcjB,QAAQ,GAAGM,KAAK,OAAO,CAAC,EAAEY;IAC9C,MAAMC,oBAAoBnB,QACxB,yBACAoB;IAGF,IAAIZ;IACJ,IAAIe,SAAS;IACb,MAAMC,eAAe,OAAOb,YAAY;IACxC,IAAI,CAACa,gBAAgB,CAACvB,YAAYwB,mBAAmB,EAAE;QACrDF,SAAS,CAAC,CAACZ,WAAW,CAACD;QACvB,IAAIC,SAAS;YACXH,OAAOM,gBAAgBK,oBAAoBF;QAC7C,OAAO;YACLT,OAAOc;QACT;IACF,OAAO;QACLd,qBACE;;gBACGc;gBACAR,gBAAgBK,oBAAoBF;;;IAG3C;IAEA,qBACE,MAACS;QACE,GAAGL,SAAS;QACbhB,KAAKA;QACLU,WAAWjB,IACTI,YAAY;YACVyB,IAAI,CAACX;YACLT;YACAD;YACAI;YACAa;YACAV;YACAW;YACAT;QACF;;YAGDH;YACAJ;;;AAGP,GAAG"}
|
|
@@ -60,7 +60,7 @@ import { nativeSelect, nativeSelectContainer } from "./nativeSelectStyles.js";
|
|
|
60
60
|
const icon = getIcon("dropdown", propIcon);
|
|
61
61
|
const underlined = theme === "underline" || theme === "filled";
|
|
62
62
|
let rightAddon = propRightAddon;
|
|
63
|
-
if (
|
|
63
|
+
if (propRightAddon === undefined && !multiple) {
|
|
64
64
|
rightAddon = icon;
|
|
65
65
|
}
|
|
66
66
|
return /*#__PURE__*/ _jsx(FormMessageContainer, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/form/NativeSelect.tsx"],"sourcesContent":["import {\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type SelectHTMLAttributes,\n forwardRef,\n} from \"react\";\n\nimport { getIcon } from \"../icon/config.js\";\nimport { type PropsWithRef } from \"../types.js\";\nimport { useEnsuredId } from \"../useEnsuredId.js\";\nimport { FormMessageContainer } from \"./FormMessageContainer.js\";\nimport { Label } from \"./Label.js\";\nimport { TextFieldContainer } from \"./TextFieldContainer.js\";\nimport { getFormConfig } from \"./formConfig.js\";\nimport { nativeSelect, nativeSelectContainer } from \"./nativeSelectStyles.js\";\nimport {\n type FormFieldOptions,\n type UserAgentAutocompleteProps,\n} from \"./types.js\";\n\n/**\n * @since 6.0.0 Added support for {@link UserAgentAutocompleteProps}\n */\nexport interface NativeSelectProps\n extends\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"autoComplete\">,\n UserAgentAutocompleteProps,\n FormFieldOptions {\n /**\n * A custom dropdown icon to use instead of the browser's default select\n * dropdown icon.\n *\n * Set this to `null` if the browser's default icon should be used instead.\n *\n * @defaultValue `getIcon(\"dropdown\")`\n */\n icon?: ReactNode;\n\n /**\n * This applies custom inline styles to the `<select>` element since the\n * `style` prop is applied to the container element instead.\n */\n selectStyle?: CSSProperties;\n\n /**\n * This applies custom className to the `<select>` element since the\n * `className` prop is applied to the container element instead.\n */\n selectClassName?: string;\n\n /**\n * This should be a list of `<option>` elements for specific values within the\n * `<select>`.\n *\n * Check out the {@link NativeSelect} for examples around using \"placeholder\"\n * text and requiring a value to be selected.\n */\n children: ReactNode;\n\n /**\n * Optional props to provide to the {@link TextFieldContainer} component.\n * There probably isn't any real use for this prop other than if you need to\n * add a `ref` for some DOM behavior.\n */\n containerProps?: PropsWithRef<HTMLAttributes<HTMLDivElement>>;\n}\n\n/**\n * This component is a wrapper for the native `<select>` field that applies the\n * same theming as `TextField` and `TextArea` components. This component might\n * not be used much since the `Select` offers more styling options.\n *\n * @example Simple Example\n * ```tsx\n * <NativeSelect label=\"Label\">\n * <option value=\"a\">Value 1</option>\n * <option value=\"b\">Value 2</option>\n * <option value=\"c\">Value 3</option>\n * <option value=\"d\">Value 4</option>\n * </NativeSelect>\n * ```\n *\n * @example Required Value Example\n * ```tsx\n * function Example(): ReactElement {\n * // using `defaultValue=\"\"` makes it so the first option selected by default\n * // and considered an \"invalid\" value since it is `disabled`\n * //\n * // a `name` must be set with `required` so that the form validation will\n * // fire if the value is still the empty string when the form is submitted\n * //\n * // the first `<option>` is kind of like placeholder text since it doesn't\n * // have a value and is disabled by default\n *\n * return (\n * <NativeSelect\n * label=\"State\"\n * name=\"state\"\n * required\n * defaultValue=\"\"\n * >\n * <option value=\"\" disabled>Choose a state</option>\n * {states.map(({ name, code }) => (\n * <option key={code} value={code}>{name}</option>\n * ))}\n * </NativeSelect>\n * );\n * }\n * ```\n *\n * @see {@link https://react-md.dev/components/native-select | NativeSelect Demos}\n */\nexport const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(\n function NativeSelect(props, ref) {\n const {\n id: propId,\n style,\n className,\n icon: propIcon,\n label,\n labelProps,\n labelStyle,\n labelClassName,\n selectStyle,\n selectClassName,\n autoCompleteValue,\n autoComplete = autoCompleteValue,\n name = autoCompleteValue,\n dense,\n error,\n active,\n inline,\n leftAddon,\n rightAddon: propRightAddon,\n leftAddonProps,\n rightAddonProps,\n disableLeftAddonStyles,\n disableRightAddonStyles,\n theme: propTheme,\n underlineDirection: propUnderlineDirection,\n messageProps,\n messageContainerProps,\n containerProps,\n children,\n ...remaining\n } = props;\n const { disabled, readOnly, multiple } = props;\n const id = useEnsuredId(propId, \"select\");\n const theme = getFormConfig(\"theme\", propTheme);\n const underlineDirection = getFormConfig(\n \"underlineDirection\",\n propUnderlineDirection\n );\n const icon = getIcon(\"dropdown\", propIcon);\n const underlined = theme === \"underline\" || theme === \"filled\";\n\n let rightAddon = propRightAddon;\n if (
|
|
1
|
+
{"version":3,"sources":["../../src/form/NativeSelect.tsx"],"sourcesContent":["import {\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n type SelectHTMLAttributes,\n forwardRef,\n} from \"react\";\n\nimport { getIcon } from \"../icon/config.js\";\nimport { type PropsWithRef } from \"../types.js\";\nimport { useEnsuredId } from \"../useEnsuredId.js\";\nimport { FormMessageContainer } from \"./FormMessageContainer.js\";\nimport { Label } from \"./Label.js\";\nimport { TextFieldContainer } from \"./TextFieldContainer.js\";\nimport { getFormConfig } from \"./formConfig.js\";\nimport { nativeSelect, nativeSelectContainer } from \"./nativeSelectStyles.js\";\nimport {\n type FormFieldOptions,\n type UserAgentAutocompleteProps,\n} from \"./types.js\";\n\n/**\n * @since 6.0.0 Added support for {@link UserAgentAutocompleteProps}\n */\nexport interface NativeSelectProps\n extends\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"autoComplete\">,\n UserAgentAutocompleteProps,\n FormFieldOptions {\n /**\n * A custom dropdown icon to use instead of the browser's default select\n * dropdown icon.\n *\n * Set this to `null` if the browser's default icon should be used instead.\n *\n * @defaultValue `getIcon(\"dropdown\")`\n */\n icon?: ReactNode;\n\n /**\n * This applies custom inline styles to the `<select>` element since the\n * `style` prop is applied to the container element instead.\n */\n selectStyle?: CSSProperties;\n\n /**\n * This applies custom className to the `<select>` element since the\n * `className` prop is applied to the container element instead.\n */\n selectClassName?: string;\n\n /**\n * This should be a list of `<option>` elements for specific values within the\n * `<select>`.\n *\n * Check out the {@link NativeSelect} for examples around using \"placeholder\"\n * text and requiring a value to be selected.\n */\n children: ReactNode;\n\n /**\n * Optional props to provide to the {@link TextFieldContainer} component.\n * There probably isn't any real use for this prop other than if you need to\n * add a `ref` for some DOM behavior.\n */\n containerProps?: PropsWithRef<HTMLAttributes<HTMLDivElement>>;\n}\n\n/**\n * This component is a wrapper for the native `<select>` field that applies the\n * same theming as `TextField` and `TextArea` components. This component might\n * not be used much since the `Select` offers more styling options.\n *\n * @example Simple Example\n * ```tsx\n * <NativeSelect label=\"Label\">\n * <option value=\"a\">Value 1</option>\n * <option value=\"b\">Value 2</option>\n * <option value=\"c\">Value 3</option>\n * <option value=\"d\">Value 4</option>\n * </NativeSelect>\n * ```\n *\n * @example Required Value Example\n * ```tsx\n * function Example(): ReactElement {\n * // using `defaultValue=\"\"` makes it so the first option selected by default\n * // and considered an \"invalid\" value since it is `disabled`\n * //\n * // a `name` must be set with `required` so that the form validation will\n * // fire if the value is still the empty string when the form is submitted\n * //\n * // the first `<option>` is kind of like placeholder text since it doesn't\n * // have a value and is disabled by default\n *\n * return (\n * <NativeSelect\n * label=\"State\"\n * name=\"state\"\n * required\n * defaultValue=\"\"\n * >\n * <option value=\"\" disabled>Choose a state</option>\n * {states.map(({ name, code }) => (\n * <option key={code} value={code}>{name}</option>\n * ))}\n * </NativeSelect>\n * );\n * }\n * ```\n *\n * @see {@link https://react-md.dev/components/native-select | NativeSelect Demos}\n */\nexport const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(\n function NativeSelect(props, ref) {\n const {\n id: propId,\n style,\n className,\n icon: propIcon,\n label,\n labelProps,\n labelStyle,\n labelClassName,\n selectStyle,\n selectClassName,\n autoCompleteValue,\n autoComplete = autoCompleteValue,\n name = autoCompleteValue,\n dense,\n error,\n active,\n inline,\n leftAddon,\n rightAddon: propRightAddon,\n leftAddonProps,\n rightAddonProps,\n disableLeftAddonStyles,\n disableRightAddonStyles,\n theme: propTheme,\n underlineDirection: propUnderlineDirection,\n messageProps,\n messageContainerProps,\n containerProps,\n children,\n ...remaining\n } = props;\n const { disabled, readOnly, multiple } = props;\n const id = useEnsuredId(propId, \"select\");\n const theme = getFormConfig(\"theme\", propTheme);\n const underlineDirection = getFormConfig(\n \"underlineDirection\",\n propUnderlineDirection\n );\n const icon = getIcon(\"dropdown\", propIcon);\n const underlined = theme === \"underline\" || theme === \"filled\";\n\n let rightAddon = propRightAddon;\n if (propRightAddon === undefined && !multiple) {\n rightAddon = icon;\n }\n\n return (\n <FormMessageContainer\n inline={inline}\n {...messageContainerProps}\n messageProps={\n messageProps && {\n error,\n theme,\n ...messageProps,\n }\n }\n >\n <TextFieldContainer\n {...containerProps}\n style={style}\n className={nativeSelectContainer({\n label: !!label,\n multiple,\n underlined,\n className,\n })}\n theme={theme}\n label={!!label}\n error={error}\n dense={dense}\n inline={inline}\n active={active}\n readOnly={readOnly}\n disabled={disabled}\n leftAddon={leftAddon}\n leftAddonProps={leftAddonProps}\n rightAddon={rightAddon}\n rightAddonProps={rightAddonProps}\n underlineDirection={underlineDirection}\n disableLeftAddonStyles={disableLeftAddonStyles}\n disableRightAddonStyles={disableRightAddonStyles}\n >\n <select\n {...remaining}\n id={id}\n ref={ref}\n autoComplete={autoComplete}\n name={name}\n disabled={disabled}\n style={selectStyle}\n className={nativeSelect({\n icon: !!icon,\n className: selectClassName,\n })}\n >\n {children}\n </select>\n {label && (\n <Label\n {...labelProps}\n htmlFor={id}\n style={labelProps?.style ?? labelStyle}\n className={labelProps?.className ?? labelClassName}\n floating\n dense={dense}\n error={error}\n active={active}\n disabled={disabled}\n >\n {label}\n </Label>\n )}\n </TextFieldContainer>\n </FormMessageContainer>\n );\n }\n);\n"],"names":["forwardRef","getIcon","useEnsuredId","FormMessageContainer","Label","TextFieldContainer","getFormConfig","nativeSelect","nativeSelectContainer","NativeSelect","props","ref","id","propId","style","className","icon","propIcon","label","labelProps","labelStyle","labelClassName","selectStyle","selectClassName","autoCompleteValue","autoComplete","name","dense","error","active","inline","leftAddon","rightAddon","propRightAddon","leftAddonProps","rightAddonProps","disableLeftAddonStyles","disableRightAddonStyles","theme","propTheme","underlineDirection","propUnderlineDirection","messageProps","messageContainerProps","containerProps","children","remaining","disabled","readOnly","multiple","underlined","undefined","select","htmlFor","floating"],"mappings":";AAAA,SAKEA,UAAU,QACL,QAAQ;AAEf,SAASC,OAAO,QAAQ,oBAAoB;AAE5C,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,oBAAoB,QAAQ,4BAA4B;AACjE,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,kBAAkB,QAAQ,0BAA0B;AAC7D,SAASC,aAAa,QAAQ,kBAAkB;AAChD,SAASC,YAAY,EAAEC,qBAAqB,QAAQ,0BAA0B;AAqD9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CC,GACD,OAAO,MAAMC,6BAAeT,WAC1B,SAASS,aAAaC,KAAK,EAAEC,GAAG;IAC9B,MAAM,EACJC,IAAIC,MAAM,EACVC,KAAK,EACLC,SAAS,EACTC,MAAMC,QAAQ,EACdC,KAAK,EACLC,UAAU,EACVC,UAAU,EACVC,cAAc,EACdC,WAAW,EACXC,eAAe,EACfC,iBAAiB,EACjBC,eAAeD,iBAAiB,EAChCE,OAAOF,iBAAiB,EACxBG,KAAK,EACLC,KAAK,EACLC,MAAM,EACNC,MAAM,EACNC,SAAS,EACTC,YAAYC,cAAc,EAC1BC,cAAc,EACdC,eAAe,EACfC,sBAAsB,EACtBC,uBAAuB,EACvBC,OAAOC,SAAS,EAChBC,oBAAoBC,sBAAsB,EAC1CC,YAAY,EACZC,qBAAqB,EACrBC,cAAc,EACdC,QAAQ,EACR,GAAGC,WACJ,GAAGpC;IACJ,MAAM,EAAEqC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAGvC;IACzC,MAAME,KAAKV,aAAaW,QAAQ;IAChC,MAAMyB,QAAQhC,cAAc,SAASiC;IACrC,MAAMC,qBAAqBlC,cACzB,sBACAmC;IAEF,MAAMzB,OAAOf,QAAQ,YAAYgB;IACjC,MAAMiC,aAAaZ,UAAU,eAAeA,UAAU;IAEtD,IAAIN,aAAaC;IACjB,IAAIA,mBAAmBkB,aAAa,CAACF,UAAU;QAC7CjB,aAAahB;IACf;IAEA,qBACE,KAACb;QACC2B,QAAQA;QACP,GAAGa,qBAAqB;QACzBD,cACEA,gBAAgB;YACdd;YACAU;YACA,GAAGI,YAAY;QACjB;kBAGF,cAAA,MAACrC;YACE,GAAGuC,cAAc;YAClB9B,OAAOA;YACPC,WAAWP,sBAAsB;gBAC/BU,OAAO,CAAC,CAACA;gBACT+B;gBACAC;gBACAnC;YACF;YACAuB,OAAOA;YACPpB,OAAO,CAAC,CAACA;YACTU,OAAOA;YACPD,OAAOA;YACPG,QAAQA;YACRD,QAAQA;YACRmB,UAAUA;YACVD,UAAUA;YACVhB,WAAWA;YACXG,gBAAgBA;YAChBF,YAAYA;YACZG,iBAAiBA;YACjBK,oBAAoBA;YACpBJ,wBAAwBA;YACxBC,yBAAyBA;;8BAEzB,KAACe;oBACE,GAAGN,SAAS;oBACblC,IAAIA;oBACJD,KAAKA;oBACLc,cAAcA;oBACdC,MAAMA;oBACNqB,UAAUA;oBACVjC,OAAOQ;oBACPP,WAAWR,aAAa;wBACtBS,MAAM,CAAC,CAACA;wBACRD,WAAWQ;oBACb;8BAECsB;;gBAEF3B,uBACC,KAACd;oBACE,GAAGe,UAAU;oBACdkC,SAASzC;oBACTE,OAAOK,YAAYL,SAASM;oBAC5BL,WAAWI,YAAYJ,aAAaM;oBACpCiC,QAAQ;oBACR3B,OAAOA;oBACPC,OAAOA;oBACPC,QAAQA;oBACRkB,UAAUA;8BAET7B;;;;;AAMb,GACA"}
|