@allxsmith/bestax-bulma 5.26.6 → 5.27.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/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/columns/Column.js +48 -0
- package/dist/esm/columns/Column.js.map +1 -0
- package/dist/esm/columns/Columns.js +53 -0
- package/dist/esm/columns/Columns.js.map +1 -0
- package/dist/esm/components/Avatar.js +299 -0
- package/dist/esm/components/Avatar.js.map +1 -0
- package/dist/esm/components/Avatars.js +81 -0
- package/dist/esm/components/Avatars.js.map +1 -0
- package/dist/esm/components/Badge.js +105 -0
- package/dist/esm/components/Badge.js.map +1 -0
- package/dist/esm/components/Breadcrumb.js +36 -0
- package/dist/esm/components/Breadcrumb.js.map +1 -0
- package/dist/esm/components/Card.js +252 -0
- package/dist/esm/components/Card.js.map +1 -0
- package/dist/esm/components/Carousel.js +351 -0
- package/dist/esm/components/Carousel.js.map +1 -0
- package/dist/esm/components/Collapse.js +193 -0
- package/dist/esm/components/Collapse.js.map +1 -0
- package/dist/esm/components/Collapses.js +99 -0
- package/dist/esm/components/Collapses.js.map +1 -0
- package/dist/esm/components/Dialog.js +230 -0
- package/dist/esm/components/Dialog.js.map +1 -0
- package/dist/esm/components/Dropdown.js +362 -0
- package/dist/esm/components/Dropdown.js.map +1 -0
- package/dist/esm/components/Loading.js +87 -0
- package/dist/esm/components/Loading.js.map +1 -0
- package/dist/esm/components/Menu.js +117 -0
- package/dist/esm/components/Menu.js.map +1 -0
- package/dist/esm/components/Message.js +56 -0
- package/dist/esm/components/Message.js.map +1 -0
- package/dist/esm/components/Modal.js +354 -0
- package/dist/esm/components/Modal.js.map +1 -0
- package/dist/esm/components/Navbar.js +287 -0
- package/dist/esm/components/Navbar.js.map +1 -0
- package/dist/esm/components/Pagination.js +191 -0
- package/dist/esm/components/Pagination.js.map +1 -0
- package/dist/esm/components/Panel.js +100 -0
- package/dist/esm/components/Panel.js.map +1 -0
- package/dist/esm/components/Popover.js +298 -0
- package/dist/esm/components/Popover.js.map +1 -0
- package/dist/esm/components/Reveal.js +157 -0
- package/dist/esm/components/Reveal.js.map +1 -0
- package/dist/esm/components/Sidebar.js +209 -0
- package/dist/esm/components/Sidebar.js.map +1 -0
- package/dist/esm/components/Steps.js +128 -0
- package/dist/esm/components/Steps.js.map +1 -0
- package/dist/esm/components/Tabs.js +335 -0
- package/dist/esm/components/Tabs.js.map +1 -0
- package/dist/esm/components/Toast.js +322 -0
- package/dist/esm/components/Toast.js.map +1 -0
- package/dist/esm/components/Tooltip.js +202 -0
- package/dist/esm/components/Tooltip.js.map +1 -0
- package/dist/esm/components/collapsesContext.js +12 -0
- package/dist/esm/components/collapsesContext.js.map +1 -0
- package/dist/esm/elements/Block.js +29 -0
- package/dist/esm/elements/Block.js.map +1 -0
- package/dist/esm/elements/Box.js +32 -0
- package/dist/esm/elements/Box.js.map +1 -0
- package/dist/esm/elements/Button.js +139 -0
- package/dist/esm/elements/Button.js.map +1 -0
- package/dist/esm/elements/Buttons.js +42 -0
- package/dist/esm/elements/Buttons.js.map +1 -0
- package/dist/esm/elements/Code.js +25 -0
- package/dist/esm/elements/Code.js.map +1 -0
- package/dist/esm/elements/Content.js +33 -0
- package/dist/esm/elements/Content.js.map +1 -0
- package/dist/esm/elements/Delete.js +35 -0
- package/dist/esm/elements/Delete.js.map +1 -0
- package/dist/esm/elements/Divider.js +27 -0
- package/dist/esm/elements/Divider.js.map +1 -0
- package/dist/esm/elements/Emphasis.js +25 -0
- package/dist/esm/elements/Emphasis.js.map +1 -0
- package/dist/esm/elements/Figure.js +58 -0
- package/dist/esm/elements/Figure.js.map +1 -0
- package/dist/esm/elements/Icon.js +256 -0
- package/dist/esm/elements/Icon.js.map +1 -0
- package/dist/esm/elements/IconText.js +36 -0
- package/dist/esm/elements/IconText.js.map +1 -0
- package/dist/esm/elements/Image.js +46 -0
- package/dist/esm/elements/Image.js.map +1 -0
- package/dist/esm/elements/Link.js +36 -0
- package/dist/esm/elements/Link.js.map +1 -0
- package/dist/esm/elements/LinkButton.js +37 -0
- package/dist/esm/elements/LinkButton.js.map +1 -0
- package/dist/esm/elements/ListItem.js +28 -0
- package/dist/esm/elements/ListItem.js.map +1 -0
- package/dist/esm/elements/Loader.js +37 -0
- package/dist/esm/elements/Loader.js.map +1 -0
- package/dist/esm/elements/Notification.js +316 -0
- package/dist/esm/elements/Notification.js.map +1 -0
- package/dist/esm/elements/OrderedList.js +31 -0
- package/dist/esm/elements/OrderedList.js.map +1 -0
- package/dist/esm/elements/Paragraph.js +28 -0
- package/dist/esm/elements/Paragraph.js.map +1 -0
- package/dist/esm/elements/Pre.js +25 -0
- package/dist/esm/elements/Pre.js.map +1 -0
- package/dist/esm/elements/Progress.js +33 -0
- package/dist/esm/elements/Progress.js.map +1 -0
- package/dist/esm/elements/Skeleton.js +30 -0
- package/dist/esm/elements/Skeleton.js.map +1 -0
- package/dist/esm/elements/Span.js +28 -0
- package/dist/esm/elements/Span.js.map +1 -0
- package/dist/esm/elements/Strong.js +25 -0
- package/dist/esm/elements/Strong.js.map +1 -0
- package/dist/esm/elements/SubTitle.js +51 -0
- package/dist/esm/elements/SubTitle.js.map +1 -0
- package/dist/esm/elements/Table.js +51 -0
- package/dist/esm/elements/Table.js.map +1 -0
- package/dist/esm/elements/Tag.js +49 -0
- package/dist/esm/elements/Tag.js.map +1 -0
- package/dist/esm/elements/Tags.js +34 -0
- package/dist/esm/elements/Tags.js.map +1 -0
- package/dist/esm/elements/Tbody.js +26 -0
- package/dist/esm/elements/Tbody.js.map +1 -0
- package/dist/esm/elements/Td.js +61 -0
- package/dist/esm/elements/Td.js.map +1 -0
- package/dist/esm/elements/Tfoot.js +26 -0
- package/dist/esm/elements/Tfoot.js.map +1 -0
- package/dist/esm/elements/Th.js +34 -0
- package/dist/esm/elements/Th.js.map +1 -0
- package/dist/esm/elements/Thead.js +26 -0
- package/dist/esm/elements/Thead.js.map +1 -0
- package/dist/esm/elements/Title.js +44 -0
- package/dist/esm/elements/Title.js.map +1 -0
- package/dist/esm/elements/Tr.js +31 -0
- package/dist/esm/elements/Tr.js.map +1 -0
- package/dist/esm/elements/UnorderedList.js +31 -0
- package/dist/esm/elements/UnorderedList.js.map +1 -0
- package/dist/esm/elements/iconProps.js +49 -0
- package/dist/esm/elements/iconProps.js.map +1 -0
- package/dist/esm/form/Autocomplete.js +279 -0
- package/dist/esm/form/Autocomplete.js.map +1 -0
- package/dist/esm/form/Checkbox.js +111 -0
- package/dist/esm/form/Checkbox.js.map +1 -0
- package/dist/esm/form/Checkboxes.js +89 -0
- package/dist/esm/form/Checkboxes.js.map +1 -0
- package/dist/esm/form/Control.js +79 -0
- package/dist/esm/form/Control.js.map +1 -0
- package/dist/esm/form/DateInput.js +87 -0
- package/dist/esm/form/DateInput.js.map +1 -0
- package/dist/esm/form/DateInputBase.js +263 -0
- package/dist/esm/form/DateInputBase.js.map +1 -0
- package/dist/esm/form/DateRangeInput.js +76 -0
- package/dist/esm/form/DateRangeInput.js.map +1 -0
- package/dist/esm/form/DateRangeInputBase.js +301 -0
- package/dist/esm/form/DateRangeInputBase.js.map +1 -0
- package/dist/esm/form/DateTimeInput.js +87 -0
- package/dist/esm/form/DateTimeInput.js.map +1 -0
- package/dist/esm/form/DateTimeInputBase.js +333 -0
- package/dist/esm/form/DateTimeInputBase.js.map +1 -0
- package/dist/esm/form/Field.js +153 -0
- package/dist/esm/form/Field.js.map +1 -0
- package/dist/esm/form/File.js +109 -0
- package/dist/esm/form/File.js.map +1 -0
- package/dist/esm/form/FormContext.js +170 -0
- package/dist/esm/form/FormContext.js.map +1 -0
- package/dist/esm/form/Input.js +88 -0
- package/dist/esm/form/Input.js.map +1 -0
- package/dist/esm/form/InputBase.js +40 -0
- package/dist/esm/form/InputBase.js.map +1 -0
- package/dist/esm/form/Numberinput.js +199 -0
- package/dist/esm/form/Numberinput.js.map +1 -0
- package/dist/esm/form/Radio.js +108 -0
- package/dist/esm/form/Radio.js.map +1 -0
- package/dist/esm/form/Radios.js +91 -0
- package/dist/esm/form/Radios.js.map +1 -0
- package/dist/esm/form/Rate.js +279 -0
- package/dist/esm/form/Rate.js.map +1 -0
- package/dist/esm/form/Select.js +81 -0
- package/dist/esm/form/Select.js.map +1 -0
- package/dist/esm/form/SelectBase.js +54 -0
- package/dist/esm/form/SelectBase.js.map +1 -0
- package/dist/esm/form/Slider.js +459 -0
- package/dist/esm/form/Slider.js.map +1 -0
- package/dist/esm/form/Switch.js +96 -0
- package/dist/esm/form/Switch.js.map +1 -0
- package/dist/esm/form/Taginput.js +352 -0
- package/dist/esm/form/Taginput.js.map +1 -0
- package/dist/esm/form/TextArea.js +77 -0
- package/dist/esm/form/TextArea.js.map +1 -0
- package/dist/esm/form/TextAreaBase.js +45 -0
- package/dist/esm/form/TextAreaBase.js.map +1 -0
- package/dist/esm/form/TimeInput.js +83 -0
- package/dist/esm/form/TimeInput.js.map +1 -0
- package/dist/esm/form/TimeInputBase.js +246 -0
- package/dist/esm/form/TimeInputBase.js.map +1 -0
- package/dist/esm/form/_pickerInternals/Calendar.js +760 -0
- package/dist/esm/form/_pickerInternals/Calendar.js.map +1 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js +95 -0
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -0
- package/dist/esm/form/_pickerInternals/TimeWheels.js +721 -0
- package/dist/esm/form/_pickerInternals/TimeWheels.js.map +1 -0
- package/dist/esm/form/_pickerInternals/audioTick.js +109 -0
- package/dist/esm/form/_pickerInternals/audioTick.js.map +1 -0
- package/dist/esm/form/_pickerInternals/dateUtils.js +267 -0
- package/dist/esm/form/_pickerInternals/dateUtils.js.map +1 -0
- package/dist/esm/form/_pickerInternals/formatters.js +322 -0
- package/dist/esm/form/_pickerInternals/formatters.js.map +1 -0
- package/dist/esm/form/_pickerInternals/haptics.js +38 -0
- package/dist/esm/form/_pickerInternals/haptics.js.map +1 -0
- package/dist/esm/form/_pickerInternals/nativeInputSupport.js +25 -0
- package/dist/esm/form/_pickerInternals/nativeInputSupport.js.map +1 -0
- package/dist/esm/form/_pickerInternals/pickerTypes.js +50 -0
- package/dist/esm/form/_pickerInternals/pickerTypes.js.map +1 -0
- package/dist/esm/form/_pickerInternals/segmentMap.js +315 -0
- package/dist/esm/form/_pickerInternals/segmentMap.js.map +1 -0
- package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js +49 -0
- package/dist/esm/form/_pickerInternals/useNativeMobilePicker.js.map +1 -0
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js +427 -0
- package/dist/esm/form/_pickerInternals/useSegmentedEntry.js.map +1 -0
- package/dist/esm/form/controlLoading.js +18 -0
- package/dist/esm/form/controlLoading.js.map +1 -0
- package/dist/esm/form/useAutoLabelId.js +54 -0
- package/dist/esm/form/useAutoLabelId.js.map +1 -0
- package/dist/esm/grid/Cell.js +35 -0
- package/dist/esm/grid/Cell.js.map +1 -0
- package/dist/esm/grid/Grid.js +51 -0
- package/dist/esm/grid/Grid.js.map +1 -0
- package/dist/esm/helpers/ClientOnly.js +33 -0
- package/dist/esm/helpers/ClientOnly.js.map +1 -0
- package/dist/esm/helpers/Config.js +60 -0
- package/dist/esm/helpers/Config.js.map +1 -0
- package/dist/esm/helpers/Theme.js +947 -0
- package/dist/esm/helpers/Theme.js.map +1 -0
- package/dist/esm/helpers/anchorAttrs.js +72 -0
- package/dist/esm/helpers/anchorAttrs.js.map +1 -0
- package/dist/esm/helpers/bulmaClassHelpers.js +415 -0
- package/dist/esm/helpers/bulmaClassHelpers.js.map +1 -0
- package/dist/esm/helpers/buttonType.js +24 -0
- package/dist/esm/helpers/buttonType.js.map +1 -0
- package/dist/esm/helpers/classNames.js +137 -0
- package/dist/esm/helpers/classNames.js.map +1 -0
- package/dist/esm/helpers/colorDeprecations.js +50 -0
- package/dist/esm/helpers/colorDeprecations.js.map +1 -0
- package/dist/esm/helpers/devWarnings.js +27 -0
- package/dist/esm/helpers/devWarnings.js.map +1 -0
- package/dist/esm/helpers/inertProps.js +27 -0
- package/dist/esm/helpers/inertProps.js.map +1 -0
- package/dist/esm/helpers/mergeBulmaStyles.js +20 -0
- package/dist/esm/helpers/mergeBulmaStyles.js.map +1 -0
- package/dist/esm/helpers/polymorphic.js +18 -0
- package/dist/esm/helpers/polymorphic.js.map +1 -0
- package/dist/esm/helpers/portal.js +76 -0
- package/dist/esm/helpers/portal.js.map +1 -0
- package/dist/esm/helpers/positionStacks.js +45 -0
- package/dist/esm/helpers/positionStacks.js.map +1 -0
- package/dist/esm/helpers/scrollLock.js +38 -0
- package/dist/esm/helpers/scrollLock.js.map +1 -0
- package/dist/esm/helpers/shadowDom.js +71 -0
- package/dist/esm/helpers/shadowDom.js.map +1 -0
- package/dist/esm/helpers/statusRegion.js +282 -0
- package/dist/esm/helpers/statusRegion.js.map +1 -0
- package/dist/esm/helpers/useAnchoredPosition.js +86 -0
- package/dist/esm/helpers/useAnchoredPosition.js.map +1 -0
- package/dist/esm/helpers/useBulmaClasses.js +158 -0
- package/dist/esm/helpers/useBulmaClasses.js.map +1 -0
- package/dist/esm/helpers/useClientLayoutEffect.js +13 -0
- package/dist/esm/helpers/useClientLayoutEffect.js.map +1 -0
- package/dist/esm/helpers/useColorClasses.js +84 -0
- package/dist/esm/helpers/useColorClasses.js.map +1 -0
- package/dist/esm/helpers/useFlexboxClasses.js +92 -0
- package/dist/esm/helpers/useFlexboxClasses.js.map +1 -0
- package/dist/esm/helpers/useFocusTrap.js +470 -0
- package/dist/esm/helpers/useFocusTrap.js.map +1 -0
- package/dist/esm/helpers/useIsHydrated.js +31 -0
- package/dist/esm/helpers/useIsHydrated.js.map +1 -0
- package/dist/esm/helpers/useOtherClasses.js +115 -0
- package/dist/esm/helpers/useOtherClasses.js.map +1 -0
- package/dist/esm/helpers/useSpacingClasses.js +82 -0
- package/dist/esm/helpers/useSpacingClasses.js.map +1 -0
- package/dist/esm/helpers/useTypographyClasses.js +93 -0
- package/dist/esm/helpers/useTypographyClasses.js.map +1 -0
- package/dist/esm/helpers/useVisibilityClasses.js +134 -0
- package/dist/esm/helpers/useVisibilityClasses.js.map +1 -0
- package/dist/esm/helpers/withSubComponents.js +24 -0
- package/dist/esm/helpers/withSubComponents.js.map +1 -0
- package/dist/esm/layout/Container.js +48 -0
- package/dist/esm/layout/Container.js.map +1 -0
- package/dist/esm/layout/Footer.js +33 -0
- package/dist/esm/layout/Footer.js.map +1 -0
- package/dist/esm/layout/Hero.js +90 -0
- package/dist/esm/layout/Hero.js.map +1 -0
- package/dist/esm/layout/Level.js +110 -0
- package/dist/esm/layout/Level.js.map +1 -0
- package/dist/esm/layout/Media.js +86 -0
- package/dist/esm/layout/Media.js.map +1 -0
- package/dist/esm/layout/Section.js +30 -0
- package/dist/esm/layout/Section.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +821 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +115 -16845
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Tabs.d.ts +25 -3
- package/dist/types/elements/Icon.d.ts +10 -7
- package/dist/types/form/DateInputBase.d.ts +8 -6
- package/dist/types/form/DateRangeInput.d.ts +91 -0
- package/dist/types/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types/form/DateTimeInputBase.d.ts +10 -6
- package/dist/types/form/File.d.ts +18 -5
- package/dist/types/form/FormContext.d.ts +11 -0
- package/dist/types/form/TimeInputBase.d.ts +1 -1
- package/dist/types/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types/form/_pickerInternals/dateUtils.d.ts +7 -0
- package/dist/types/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types/form/_pickerInternals/pickerTypes.d.ts +13 -0
- package/dist/types/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types/index.d.ts +4 -0
- package/dist/types-cjs/components/Tabs.d.ts +25 -3
- package/dist/types-cjs/elements/Icon.d.ts +10 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +8 -6
- package/dist/types-cjs/form/DateRangeInput.d.ts +91 -0
- package/dist/types-cjs/form/DateRangeInputBase.d.ts +172 -0
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +10 -6
- package/dist/types-cjs/form/File.d.ts +18 -5
- package/dist/types-cjs/form/FormContext.d.ts +11 -0
- package/dist/types-cjs/form/TimeInputBase.d.ts +1 -1
- package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +1 -0
- package/dist/types-cjs/form/_pickerInternals/dateUtils.d.ts +7 -0
- package/dist/types-cjs/form/_pickerInternals/formatters.d.ts +14 -0
- package/dist/types-cjs/form/_pickerInternals/pickerTypes.d.ts +13 -0
- package/dist/types-cjs/form/_pickerInternals/useSegmentedEntry.d.ts +12 -3
- package/dist/types-cjs/index.d.ts +4 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_tabs.scss +44 -0
- package/src/scss/form/_dateinput.scss +87 -8
- package/src/scss/form/_daterangeinput.scss +63 -0
- package/src/scss/form/_datetimeinput.scss +7 -2
- package/src/scss/form/_file.scss +37 -0
- package/src/scss/form/_index.scss +2 -0
- package/src/scss/form/_picker-popover.scss +1 -4
package/dist/index.cjs
CHANGED
|
@@ -2447,7 +2447,9 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
|
|
|
2447
2447
|
// Closing hides the item that has focus, and the browser drops focus
|
|
2448
2448
|
// to the page when that happens. Hand it back to the trigger, as
|
|
2449
2449
|
// Escape does. An item that moved focus somewhere else keeps it there.
|
|
2450
|
-
|
|
2450
|
+
// The menu around this content is what a click between items focuses.
|
|
2451
|
+
const menu = e.currentTarget.parentElement;
|
|
2452
|
+
if (menu.contains(getActiveElementInTree(e.currentTarget))) {
|
|
2451
2453
|
triggerRef.current?.focus();
|
|
2452
2454
|
}
|
|
2453
2455
|
}
|
|
@@ -2608,7 +2610,10 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
|
|
|
2608
2610
|
}
|
|
2609
2611
|
};
|
|
2610
2612
|
const dropdownClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
2611
|
-
return (jsxRuntime.jsxs("div", { className: dropdownClasses, ref: setRefs, id: id, "data-testid": "dropdown-root", ...rest, children: [jsxRuntime.jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxRuntime.jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsxRuntime.jsx("span", { children: label }), jsxRuntime.jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsxRuntime.jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown,
|
|
2613
|
+
return (jsxRuntime.jsxs("div", { className: dropdownClasses, ref: setRefs, id: id, "data-testid": "dropdown-root", ...rest, children: [jsxRuntime.jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxRuntime.jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsxRuntime.jsx("span", { children: label }), jsxRuntime.jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsxRuntime.jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsxRuntime.jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown,
|
|
2614
|
+
// The menu, not its content, takes focus from a click between items,
|
|
2615
|
+
// so the menu owns nothing focusable but its items.
|
|
2616
|
+
tabIndex: -1, children: jsxRuntime.jsx("div", { className: usePrefixedClassNames('dropdown-content'), onClick: handleMenuClick, children: children }) })] }));
|
|
2612
2617
|
});
|
|
2613
2618
|
/**
|
|
2614
2619
|
* The anchor's attributes, minus the one a `<button>` legitimately takes.
|
|
@@ -3910,7 +3915,7 @@ function getIconClasses(library, name, variant, features) {
|
|
|
3910
3915
|
* @see {@link https://bulma.io/documentation/elements/icon/ | Bulma Icon documentation}
|
|
3911
3916
|
*/
|
|
3912
3917
|
const Icon = ({ className, textColor, bgColor, name, library, variant, features, libraryFeatures, // Deprecated but maintained for backward compatibility
|
|
3913
|
-
size, ariaLabel
|
|
3918
|
+
size, ariaLabel, style, icon, // Capture and exclude the deprecated 'icon' prop from DOM
|
|
3914
3919
|
color: _color, // Exclude 'color' prop if passed directly
|
|
3915
3920
|
containerClassName, children, ...restProps }) => {
|
|
3916
3921
|
// Get the default icon library from context, fallback to 'fa' if not set
|
|
@@ -3934,10 +3939,22 @@ containerClassName, children, ...restProps }) => {
|
|
|
3934
3939
|
? containerClassName
|
|
3935
3940
|
: defaultIconClasses;
|
|
3936
3941
|
const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
|
|
3942
|
+
// A named icon is an image, whose name is its label alone, so a ligature inside
|
|
3943
|
+
// never leaks into it. An unnamed one is decorative and hidden outright. Spread
|
|
3944
|
+
// before `rest`, so a caller's own `role` or `aria-hidden` still wins.
|
|
3945
|
+
const named = !!ariaLabel || !!restProps['aria-label'] || !!restProps['aria-labelledby'];
|
|
3946
|
+
// A caller's own `role` or `tabIndex` on an unnamed icon means it isn't decoration (an
|
|
3947
|
+
// icon made a button, say), so it isn't hidden either: `aria-hidden` on something
|
|
3948
|
+
// focusable is a failure of its own.
|
|
3949
|
+
const a11yProps = named
|
|
3950
|
+
? { role: 'img', 'aria-label': ariaLabel || undefined }
|
|
3951
|
+
: restProps.role !== undefined || restProps.tabIndex !== undefined
|
|
3952
|
+
? {}
|
|
3953
|
+
: { 'aria-hidden': true };
|
|
3937
3954
|
if (children !== undefined) {
|
|
3938
3955
|
// `IconChildrenProps`: render the caller's node (an inline SVG, a `react-icons`
|
|
3939
3956
|
// component, …) in place of a class-based glyph. Library/variant/features don't apply.
|
|
3940
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses,
|
|
3957
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: children }));
|
|
3941
3958
|
}
|
|
3942
3959
|
// `name` is guaranteed once `children` is absent UNLESS the caller is on the deprecated
|
|
3943
3960
|
// `icon` path, which `IconDeprecatedProps` declares with `name?: undefined`.
|
|
@@ -3971,7 +3988,7 @@ containerClassName, children, ...restProps }) => {
|
|
|
3971
3988
|
// by a plain-JS caller (or one that casts). Building a class off an absent name produced
|
|
3972
3989
|
// a bogus `fa-undefined` glyph; render the bare container instead, matching the
|
|
3973
3990
|
// `children` branch.
|
|
3974
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses,
|
|
3991
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest }));
|
|
3975
3992
|
}
|
|
3976
3993
|
// Backward compatibility: if libraryFeatures is provided, parse it for variant and features
|
|
3977
3994
|
let finalVariant = variant;
|
|
@@ -4034,16 +4051,16 @@ containerClassName, children, ...restProps }) => {
|
|
|
4034
4051
|
else if (finalVariant === 'sharp') {
|
|
4035
4052
|
ionName = `${finalName}-sharp`;
|
|
4036
4053
|
}
|
|
4037
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses,
|
|
4054
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("ion-icon", { name: ionName }) }));
|
|
4038
4055
|
}
|
|
4039
4056
|
// Legacy CSS-based icons
|
|
4040
4057
|
const iClasses = getIconClasses(finalLibrary, finalName, finalVariant, finalFeatures);
|
|
4041
4058
|
// Material Icons and Material Symbols use text content, not CSS classes for the icon name
|
|
4042
4059
|
if (finalLibrary === 'material-icons' ||
|
|
4043
4060
|
finalLibrary === 'material-symbols') {
|
|
4044
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses,
|
|
4061
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses, children: finalName }) }));
|
|
4045
4062
|
}
|
|
4046
|
-
return (jsxRuntime.jsx("span", { className: iconContainerClasses,
|
|
4063
|
+
return (jsxRuntime.jsx("span", { className: iconContainerClasses, ...a11yProps, style: style, ...rest, children: jsxRuntime.jsx("i", { className: iClasses }) }));
|
|
4047
4064
|
};
|
|
4048
4065
|
|
|
4049
4066
|
/**
|
|
@@ -4141,9 +4158,65 @@ const TabsContext = React.createContext(null);
|
|
|
4141
4158
|
function useTabsContext() {
|
|
4142
4159
|
return React.useContext(TabsContext);
|
|
4143
4160
|
}
|
|
4161
|
+
/**
|
|
4162
|
+
* The tabs and panels currently mounted under one `Tabs`. Each registers from
|
|
4163
|
+
* an effect and removes itself on cleanup, so the links between them only
|
|
4164
|
+
* ever name elements that exist, wherever in the tree the panels sit.
|
|
4165
|
+
*/
|
|
4166
|
+
function useRegistry() {
|
|
4167
|
+
const [records, setRecords] = React.useState([]);
|
|
4168
|
+
const register = React.useCallback((record) => {
|
|
4169
|
+
setRecords(prev => [...prev, record]);
|
|
4170
|
+
return () => setRecords(prev => prev.filter(r => r !== record));
|
|
4171
|
+
}, []);
|
|
4172
|
+
return [records, register];
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* The list a tab belongs to: its `role="tablist"` ancestor, so an element
|
|
4176
|
+
* wrapped around a tab doesn't cut it off from the others, or its parent when
|
|
4177
|
+
* it sits outside a tab list.
|
|
4178
|
+
*/
|
|
4179
|
+
function tabListOf(tab) {
|
|
4180
|
+
return (tab.closest('[role="tablist"]') ?? tab.parentElement);
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* The tab a key press moves focus to, found among the tabs of the same list
|
|
4184
|
+
* in DOM order, skipping disabled ones and wrapping at either end.
|
|
4185
|
+
* `undefined` for a key the tab list does not handle, or when no enabled tab
|
|
4186
|
+
* is left to go to.
|
|
4187
|
+
*/
|
|
4188
|
+
function findTargetTab(current, key, vertical) {
|
|
4189
|
+
const tabs = Array.from(tabListOf(current).querySelectorAll('[role="tab"]'));
|
|
4190
|
+
const isEnabled = (el) => el.getAttribute('aria-disabled') !== 'true';
|
|
4191
|
+
if (key === 'Home')
|
|
4192
|
+
return tabs.find(isEnabled);
|
|
4193
|
+
if (key === 'End')
|
|
4194
|
+
return [...tabs].reverse().find(isEnabled);
|
|
4195
|
+
let step;
|
|
4196
|
+
if (key === 'ArrowRight' || (vertical && key === 'ArrowDown'))
|
|
4197
|
+
step = 1;
|
|
4198
|
+
else if (key === 'ArrowLeft' || (vertical && key === 'ArrowUp'))
|
|
4199
|
+
step = -1;
|
|
4200
|
+
else
|
|
4201
|
+
return undefined;
|
|
4202
|
+
const start = tabs.indexOf(current);
|
|
4203
|
+
for (let i = 1; i <= tabs.length; i++) {
|
|
4204
|
+
const candidate = tabs[(((start + step * i) % tabs.length) + tabs.length) % tabs.length];
|
|
4205
|
+
if (isEnabled(candidate))
|
|
4206
|
+
return candidate;
|
|
4207
|
+
}
|
|
4208
|
+
return undefined;
|
|
4209
|
+
}
|
|
4144
4210
|
/**
|
|
4145
4211
|
* The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.
|
|
4146
4212
|
*
|
|
4213
|
+
* Built from `Tabs.Tab`, the tabs follow the WAI-ARIA tabs pattern with
|
|
4214
|
+
* manual activation: the tab list is a single tab stop, the arrow keys, Home
|
|
4215
|
+
* and End move focus between tabs (skipping disabled ones), and Enter or
|
|
4216
|
+
* Space activates the focused tab. Each tab names its `Tabs.Content.Item` in
|
|
4217
|
+
* `aria-controls`, and each panel names its tab in `aria-labelledby`. Built
|
|
4218
|
+
* from `Tabs.Item` with links inside, the tabs stay plain Bulma navigation.
|
|
4219
|
+
*
|
|
4147
4220
|
* @function
|
|
4148
4221
|
* @param {TabsProps} props - Props for the Tabs component.
|
|
4149
4222
|
* @returns {JSX.Element} The rendered tabs component.
|
|
@@ -4175,7 +4248,45 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4175
4248
|
}
|
|
4176
4249
|
onChange?.(index);
|
|
4177
4250
|
}, [isControlled, onChange]);
|
|
4178
|
-
const
|
|
4251
|
+
const baseId = React.useId();
|
|
4252
|
+
const [tabs, registerTab] = useRegistry();
|
|
4253
|
+
const [panels, registerPanel] = useRegistry();
|
|
4254
|
+
const [focusedTab, setFocusedTab] = React.useState(null);
|
|
4255
|
+
// One tab stop for the whole list (roving tabindex). While focus is inside
|
|
4256
|
+
// the list the stop follows it, so Tab and Shift+Tab leave the list from
|
|
4257
|
+
// whichever tab has focus; once focus leaves, the stop is the selected tab
|
|
4258
|
+
// again. A disabled tab never holds it, and when the selected tab is
|
|
4259
|
+
// disabled or missing the enabled tab with the lowest index does, so the
|
|
4260
|
+
// list can always be reached. Before the tabs register (the server render
|
|
4261
|
+
// and the first client render), the selected tab holds it. The registry is
|
|
4262
|
+
// in the order the tabs last registered, which a tab changing its
|
|
4263
|
+
// `disabled` or `id` reshuffles, hence the sort.
|
|
4264
|
+
const enabledTabs = tabs
|
|
4265
|
+
.filter(t => !t.disabled)
|
|
4266
|
+
.sort((a, b) => a.index - b.index);
|
|
4267
|
+
const canHoldStop = (index) => enabledTabs.some(t => t.index === index);
|
|
4268
|
+
let tabStop;
|
|
4269
|
+
if (canHoldStop(focusedTab))
|
|
4270
|
+
tabStop = focusedTab;
|
|
4271
|
+
else if (tabs.length === 0 || canHoldStop(activeTab))
|
|
4272
|
+
tabStop = activeTab;
|
|
4273
|
+
else
|
|
4274
|
+
tabStop = enabledTabs.length > 0 ? enabledTabs[0].index : null;
|
|
4275
|
+
// Check if children include TabsContent
|
|
4276
|
+
const childArray = React.Children.toArray(children);
|
|
4277
|
+
const hasContent = childArray.some(child => React.isValidElement(child) && child.type === TabsContent);
|
|
4278
|
+
const contextValue = {
|
|
4279
|
+
activeTab,
|
|
4280
|
+
setActiveTab,
|
|
4281
|
+
baseId,
|
|
4282
|
+
tabStop,
|
|
4283
|
+
setFocusedTab,
|
|
4284
|
+
tabId: index => tabs.find(t => t.index === index)?.id,
|
|
4285
|
+
panelId: index => panels.find(p => p.index === index)?.id,
|
|
4286
|
+
registerTab,
|
|
4287
|
+
registerPanel,
|
|
4288
|
+
orientation: hasContent && vertical ? 'vertical' : 'horizontal',
|
|
4289
|
+
};
|
|
4179
4290
|
// Build classes for the .tabs div
|
|
4180
4291
|
const tabsClasses = usePrefixedClassNames('tabs', {
|
|
4181
4292
|
[`is-${align}`]: align,
|
|
@@ -4186,9 +4297,6 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4186
4297
|
'is-toggle': toggle,
|
|
4187
4298
|
'is-toggle-rounded': rounded,
|
|
4188
4299
|
});
|
|
4189
|
-
// Check if children include TabsContent
|
|
4190
|
-
const childArray = React.Children.toArray(children);
|
|
4191
|
-
const hasContent = childArray.some(child => React.isValidElement(child) && child.type === TabsContent);
|
|
4192
4300
|
// Hoisted unconditionally to respect rules-of-hooks. Modifiers gate themselves
|
|
4193
4301
|
// via their truthy values — `tabs-root` is always prefixed, modifiers only
|
|
4194
4302
|
// apply in the vertical-with-content branch.
|
|
@@ -4237,26 +4345,82 @@ const TabsComponent = ({ align, size, isFullwidth, isFullWidth, fullwidth, boxed
|
|
|
4237
4345
|
* @param {TabListProps} props - Props for the TabList component.
|
|
4238
4346
|
* @returns {JSX.Element} The rendered tab list.
|
|
4239
4347
|
*/
|
|
4240
|
-
const TabList = ({ className, children, ...props }) =>
|
|
4348
|
+
const TabList = ({ className, children, ...props }) => {
|
|
4349
|
+
const ctx = useTabsContext();
|
|
4350
|
+
return (jsxRuntime.jsx("ul", { role: "tablist", "aria-orientation": ctx?.orientation === 'vertical' ? 'vertical' : undefined, className: classNames(className) || undefined, ...props, children: children }));
|
|
4351
|
+
};
|
|
4241
4352
|
/**
|
|
4242
4353
|
* Individual tab button. Consumes Tabs context for active state management.
|
|
4243
4354
|
* Renders `<a>` internally — consumers provide only the label text/children.
|
|
4355
|
+
* Inside a `Tabs` it takes part in the tab list's keyboard support and links
|
|
4356
|
+
* to the `Tabs.Content.Item` with the same `index`.
|
|
4244
4357
|
*
|
|
4245
4358
|
* @function
|
|
4246
4359
|
* @param {TabProps} props - Props for the Tab component.
|
|
4247
4360
|
* @returns {JSX.Element} The rendered tab.
|
|
4248
4361
|
*/
|
|
4249
|
-
const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'small', iconFeatures, className, children, ...props }) => {
|
|
4362
|
+
const Tab = ({ index, disabled, icon, iconLibrary, iconVariant, iconSize = 'small', iconFeatures, className, children, id, onKeyDown, onFocus, onBlur, ...props }) => {
|
|
4250
4363
|
const ctx = useTabsContext();
|
|
4251
4364
|
const isActive = ctx ? ctx.activeTab === index : false;
|
|
4365
|
+
const anchorRef = React.useRef(null);
|
|
4252
4366
|
const activeClass = usePrefixedClassNames({ 'is-active': isActive });
|
|
4367
|
+
const tabId = id ?? (ctx ? `${ctx.baseId}-tab-${index}` : undefined);
|
|
4368
|
+
const registerTab = ctx?.registerTab;
|
|
4369
|
+
React.useEffect(() => {
|
|
4370
|
+
if (!registerTab || tabId === undefined)
|
|
4371
|
+
return undefined;
|
|
4372
|
+
return registerTab({ index, id: tabId, disabled: !!disabled });
|
|
4373
|
+
}, [registerTab, index, tabId, disabled]);
|
|
4374
|
+
// Outside a Tabs there is no list to rove, so every enabled tab stays in
|
|
4375
|
+
// the page tab order, as it always did.
|
|
4376
|
+
let tabIndex;
|
|
4377
|
+
if (disabled)
|
|
4378
|
+
tabIndex = -1;
|
|
4379
|
+
else if (ctx)
|
|
4380
|
+
tabIndex = ctx.tabStop === index ? 0 : -1;
|
|
4381
|
+
else
|
|
4382
|
+
tabIndex = 0;
|
|
4253
4383
|
const handleClick = React.useCallback((e) => {
|
|
4254
4384
|
e.preventDefault();
|
|
4255
4385
|
if (!disabled && ctx) {
|
|
4256
4386
|
ctx.setActiveTab(index);
|
|
4257
4387
|
}
|
|
4258
4388
|
}, [disabled, ctx, index]);
|
|
4259
|
-
|
|
4389
|
+
const handleKeyDown = (e) => {
|
|
4390
|
+
onKeyDown?.(e);
|
|
4391
|
+
// A key on something inside the tab is that element's to answer.
|
|
4392
|
+
if (e.defaultPrevented ||
|
|
4393
|
+
e.target !== e.currentTarget ||
|
|
4394
|
+
e.altKey ||
|
|
4395
|
+
e.ctrlKey ||
|
|
4396
|
+
e.metaKey) {
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4399
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
4400
|
+
e.preventDefault();
|
|
4401
|
+
// Activate through a click on the inner <a>, the element a pointer
|
|
4402
|
+
// clicks, so click listeners see keyboard activation the way they see
|
|
4403
|
+
// a native button's.
|
|
4404
|
+
anchorRef.current.click();
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
const target = findTargetTab(e.currentTarget, e.key, ctx?.orientation === 'vertical');
|
|
4408
|
+
if (target) {
|
|
4409
|
+
e.preventDefault();
|
|
4410
|
+
target.focus();
|
|
4411
|
+
}
|
|
4412
|
+
};
|
|
4413
|
+
const handleFocus = (e) => {
|
|
4414
|
+
onFocus?.(e);
|
|
4415
|
+
ctx?.setFocusedTab(index);
|
|
4416
|
+
};
|
|
4417
|
+
const handleBlur = (e) => {
|
|
4418
|
+
onBlur?.(e);
|
|
4419
|
+
if (!tabListOf(e.currentTarget).contains(e.relatedTarget)) {
|
|
4420
|
+
ctx?.setFocusedTab(null);
|
|
4421
|
+
}
|
|
4422
|
+
};
|
|
4423
|
+
return (jsxRuntime.jsx("li", { className: classNames(activeClass, className) || undefined, role: "tab", id: tabId, "aria-selected": isActive, "aria-controls": ctx?.panelId(index), "aria-disabled": disabled || undefined, tabIndex: tabIndex, onKeyDown: handleKeyDown, onFocus: handleFocus, onBlur: handleBlur, ...props, children: jsxRuntime.jsxs("a", { ref: anchorRef, onClick: handleClick, "aria-disabled": disabled || undefined, children: [icon && (jsxRuntime.jsx(Icon, { name: icon, library: iconLibrary, variant: iconVariant, size: iconSize, features: iconFeatures })), children && jsxRuntime.jsx("span", { children: children })] }) }));
|
|
4260
4424
|
};
|
|
4261
4425
|
/**
|
|
4262
4426
|
* Each tab; accepts `active`, `onClick`, etc.
|
|
@@ -4286,13 +4450,20 @@ const TabsContentComponent = ({ className, children, ...props }) => {
|
|
|
4286
4450
|
* @param {TabContentItemProps} props - Props for the TabContentItem component.
|
|
4287
4451
|
* @returns {JSX.Element} The rendered tab content panel.
|
|
4288
4452
|
*/
|
|
4289
|
-
const TabContentItem = ({ index, className, children, ...props }) => {
|
|
4453
|
+
const TabContentItem = ({ index, className, children, id, ...props }) => {
|
|
4290
4454
|
const ctx = useTabsContext();
|
|
4291
4455
|
const isActive = ctx ? ctx.activeTab === index : false;
|
|
4292
4456
|
const itemClass = usePrefixedClassNames('tabs-content-item', {
|
|
4293
4457
|
'is-active': isActive,
|
|
4294
4458
|
});
|
|
4295
|
-
|
|
4459
|
+
const panelId = id ?? (ctx ? `${ctx.baseId}-panel-${index}` : undefined);
|
|
4460
|
+
const registerPanel = ctx?.registerPanel;
|
|
4461
|
+
React.useEffect(() => {
|
|
4462
|
+
if (!registerPanel || panelId === undefined)
|
|
4463
|
+
return undefined;
|
|
4464
|
+
return registerPanel({ index, id: panelId });
|
|
4465
|
+
}, [registerPanel, index, panelId]);
|
|
4466
|
+
return (jsxRuntime.jsx("div", { className: classNames(itemClass, className), role: "tabpanel", id: panelId, "aria-labelledby": ctx?.tabId(index), "aria-hidden": !isActive, ...props, children: children }));
|
|
4296
4467
|
};
|
|
4297
4468
|
// ---------------------------------------------------------------------------
|
|
4298
4469
|
// Static property attachment
|
|
@@ -8850,6 +9021,10 @@ const ON_CONTROL = {
|
|
|
8850
9021
|
* only the props it hands its own `Control`, as the caller gave them, so a
|
|
8851
9022
|
* default the wrapper fills in itself never warns. A prop counts when it is
|
|
8852
9023
|
* truthy, since a falsy one would change nothing on a `Control` either.
|
|
9024
|
+
* `iconLeftSize` and `hasIconsLeft` show nothing on a `Control` without a
|
|
9025
|
+
* glyph, so when they move and the caller chose no left icon, the advice
|
|
9026
|
+
* names the wrapper's default glyph too, and following it draws what the
|
|
9027
|
+
* wrapper's own `Control` did.
|
|
8853
9028
|
* Internal; not part of the public API.
|
|
8854
9029
|
*/
|
|
8855
9030
|
const rendersOwnControl = (component, options) => {
|
|
@@ -8860,18 +9035,32 @@ const rendersOwnControl = (component, options) => {
|
|
|
8860
9035
|
return false;
|
|
8861
9036
|
const names = held.join(' ');
|
|
8862
9037
|
const doNothing = held.length > 1 ? 'those props do' : 'that prop does';
|
|
9038
|
+
let message;
|
|
8863
9039
|
if (options.inline) {
|
|
8864
|
-
|
|
8865
|
-
`
|
|
8866
|
-
|
|
8867
|
-
|
|
9040
|
+
message =
|
|
9041
|
+
`[bestax-bulma] <${component} inline ${names}> renders no <Control> ` +
|
|
9042
|
+
`in inline mode, inside a <Control> or not, so ${doNothing} ` +
|
|
9043
|
+
`nothing. Leave ${held.length > 1 ? 'them' : 'it'} out of an ` +
|
|
9044
|
+
`inline picker.`;
|
|
8868
9045
|
}
|
|
8869
9046
|
else {
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
9047
|
+
const toSet = held.map(prop => ON_CONTROL[prop]);
|
|
9048
|
+
const needsGlyph = (options.iconLeftSize || options.hasIconsLeft) &&
|
|
9049
|
+
!options.iconLeft &&
|
|
9050
|
+
options.iconLeftName === undefined &&
|
|
9051
|
+
options.defaultIconLeftName;
|
|
9052
|
+
if (needsGlyph) {
|
|
9053
|
+
toSet.push(`iconLeftName="${options.defaultIconLeftName}" (its default icon)`);
|
|
9054
|
+
}
|
|
9055
|
+
message =
|
|
9056
|
+
`[bestax-bulma] <${component} ${names}> inside a <Control> renders ` +
|
|
9057
|
+
`no <Control> of its own, so ${doNothing} nothing there. Set ` +
|
|
9058
|
+
`${listOf(toSet)} on that <Control> instead.`;
|
|
8874
9059
|
}
|
|
9060
|
+
// Keyed by the message itself: the props named do not settle the advice
|
|
9061
|
+
// (an empty `iconLeftName` and an unset one name the same props), so two
|
|
9062
|
+
// calls that would advise differently must not share a key.
|
|
9063
|
+
warnOnce(message, message);
|
|
8875
9064
|
return false;
|
|
8876
9065
|
};
|
|
8877
9066
|
const FieldLabelIdContext = React.createContext(undefined);
|
|
@@ -9358,7 +9547,40 @@ const Checkboxes = withSubComponents(CheckboxesComponent, { Checkbox }, 'Checkbo
|
|
|
9358
9547
|
*/
|
|
9359
9548
|
const File = React.forwardRef(({
|
|
9360
9549
|
// Field props
|
|
9361
|
-
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, ...props }, ref) => {
|
|
9550
|
+
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, color, size, isBoxed, isFullwidth, isFullWidth, isRight, isCentered, hasName, buttonLabel, iconLeft, iconRight, className, inputClassName, fileName, pickedFilesLabel, onChange, ...props }, ref) => {
|
|
9551
|
+
// What the user last picked, and the form a reset of which clears it.
|
|
9552
|
+
const [picked, setPicked] = React.useState();
|
|
9553
|
+
const pickedForm = picked?.form;
|
|
9554
|
+
React.useEffect(() => {
|
|
9555
|
+
if (!pickedForm)
|
|
9556
|
+
return;
|
|
9557
|
+
let timer;
|
|
9558
|
+
// The reset event fires before the form resets, and a listener after this one can still
|
|
9559
|
+
// cancel it, so the name is cleared a task later, and only if the reset went ahead.
|
|
9560
|
+
const onReset = (event) => {
|
|
9561
|
+
timer = setTimeout(() => {
|
|
9562
|
+
if (!event.defaultPrevented)
|
|
9563
|
+
setPicked(undefined);
|
|
9564
|
+
});
|
|
9565
|
+
};
|
|
9566
|
+
pickedForm.addEventListener('reset', onReset);
|
|
9567
|
+
return () => {
|
|
9568
|
+
pickedForm.removeEventListener('reset', onReset);
|
|
9569
|
+
clearTimeout(timer);
|
|
9570
|
+
};
|
|
9571
|
+
}, [pickedForm]);
|
|
9572
|
+
const handleChange = (event) => {
|
|
9573
|
+
const { files, form } = event.target;
|
|
9574
|
+
setPicked(files?.length
|
|
9575
|
+
? { count: files.length, firstName: files[0].name, form }
|
|
9576
|
+
: undefined);
|
|
9577
|
+
onChange?.(event);
|
|
9578
|
+
};
|
|
9579
|
+
const pickedText = picked &&
|
|
9580
|
+
(picked.count === 1
|
|
9581
|
+
? picked.firstName
|
|
9582
|
+
: (pickedFilesLabel?.(picked.count) ?? `${picked.count} files`));
|
|
9583
|
+
const shownName = fileName ?? pickedText;
|
|
9362
9584
|
const insideField = useInsideField();
|
|
9363
9585
|
const insideControl = useInsideControl();
|
|
9364
9586
|
const ownField = rendersOwnField('File', {
|
|
@@ -9404,7 +9626,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
9404
9626
|
[`is-${messageColor}`]: !!messageColor,
|
|
9405
9627
|
});
|
|
9406
9628
|
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
9407
|
-
const fileElement = (jsxRuntime.jsx("div", { className: fileClass, children: jsxRuntime.jsxs("label", { className: usePrefixedClassNames('file-label'), children: [jsxRuntime.jsx("input", { ref: ref, className: classNames(usePrefixedClassNames('file-input'), inputClassName), type: "file", id: controlId, ...rest }), jsxRuntime.jsxs("span", { className: usePrefixedClassNames('file-cta'), children: [iconLeft && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconLeft })), jsxRuntime.jsx("span", { className: usePrefixedClassNames('file-label'), children: buttonLabel || 'Choose a file\u2026' }), iconRight && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconRight }))] }), hasName &&
|
|
9629
|
+
const fileElement = (jsxRuntime.jsx("div", { className: fileClass, children: jsxRuntime.jsxs("label", { className: usePrefixedClassNames('file-label'), children: [jsxRuntime.jsx("input", { ref: ref, className: classNames(usePrefixedClassNames('file-input'), inputClassName), type: "file", id: controlId, onChange: handleChange, ...rest }), jsxRuntime.jsxs("span", { className: usePrefixedClassNames('file-cta'), children: [iconLeft && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconLeft })), jsxRuntime.jsx("span", { className: usePrefixedClassNames('file-label'), children: buttonLabel || 'Choose a file\u2026' }), iconRight && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-icon'), children: iconRight }))] }), hasName && shownName && (jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'file-name'), children: shownName }))] }) }));
|
|
9408
9630
|
if (ownField) {
|
|
9409
9631
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [fileElement, messageEl] }));
|
|
9410
9632
|
}
|
|
@@ -10363,6 +10585,7 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10363
10585
|
rendersLabel: ownField,
|
|
10364
10586
|
});
|
|
10365
10587
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
10588
|
+
const starBaseId = React.useId();
|
|
10366
10589
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
10367
10590
|
const [hoverValue, setHoverValue] = React.useState(null);
|
|
10368
10591
|
const defaultIconLibrary = useIconLibrary();
|
|
@@ -10524,6 +10747,14 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10524
10747
|
const clipId = `rate-clip-${iconIndex}`;
|
|
10525
10748
|
return (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", "aria-hidden": "true", children: [jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("clipPath", { id: clipId, children: jsxRuntime.jsx("rect", { x: "0", y: "0", width: `${(fillPercent / 100) * 24}`, height: "24" }) }) }), jsxRuntime.jsx("path", { fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", d: "M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" }), jsxRuntime.jsx("path", { clipPath: `url(#${clipId})`, fill: "currentColor", d: "M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" })] }));
|
|
10526
10749
|
};
|
|
10750
|
+
// The star the value falls in is the checked radio, and it carries the
|
|
10751
|
+
// value: a radiogroup takes no aria-value* of its own. A fractional value
|
|
10752
|
+
// names that star by the value itself ("3.5 stars"). Both go by the value
|
|
10753
|
+
// as that name writes it, to hundredths, so a value a hair over a whole
|
|
10754
|
+
// number checks that number's star under its own name, and no two stars
|
|
10755
|
+
// share a name.
|
|
10756
|
+
const shownValue = Number(currentValue.toFixed(2));
|
|
10757
|
+
const checkedIndex = shownValue > 0 ? Math.min(Math.ceil(shownValue), max) - 1 : -1;
|
|
10527
10758
|
// Render icons
|
|
10528
10759
|
const renderIcons = () => {
|
|
10529
10760
|
const icons = [];
|
|
@@ -10539,17 +10770,13 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10539
10770
|
icons.push(jsxRuntime.jsx("span", { className: prefixedClassNames(classPrefix, 'rate-item', {
|
|
10540
10771
|
'is-active': isActive,
|
|
10541
10772
|
'is-hovered': isHovered,
|
|
10542
|
-
}), onClick: e => handleClick(iconIndex, e), onMouseEnter: () => handleMouseEnter(iconIndex), onMouseMove: e => handleMouseMove(iconIndex, e), onMouseLeave: handleMouseLeave, role: "radio", "aria-checked":
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
currentValue <= iconIndex + 1), "aria-label": `${iconIndex + 1} star${iconIndex === 0 ? '' : 's'}`, tabIndex: -1, children: icon }, iconIndex));
|
|
10773
|
+
}), onClick: e => handleClick(iconIndex, e), onMouseEnter: () => handleMouseEnter(iconIndex), onMouseMove: e => handleMouseMove(iconIndex, e), onMouseLeave: handleMouseLeave, id: `${starBaseId}-${iconIndex}`, role: "radio", "aria-checked": iconIndex === checkedIndex, "aria-label": iconIndex === checkedIndex && shownValue % 1 !== 0
|
|
10774
|
+
? `${shownValue} stars`
|
|
10775
|
+
: `${iconIndex + 1} star${iconIndex === 0 ? '' : 's'}`, tabIndex: -1, children: icon }, iconIndex));
|
|
10546
10776
|
}
|
|
10547
10777
|
return icons;
|
|
10548
10778
|
};
|
|
10549
10779
|
const text = getText();
|
|
10550
|
-
const ariaValueText = precision < 1 && currentValue % 1 !== 0
|
|
10551
|
-
? `${currentValue.toFixed(1)} out of ${max} stars`
|
|
10552
|
-
: undefined;
|
|
10553
10780
|
const helpClass = usePrefixedClassNames('help', {
|
|
10554
10781
|
[`is-${messageColor}`]: !!messageColor,
|
|
10555
10782
|
});
|
|
@@ -10558,7 +10785,7 @@ const Rate = React.forwardRef(({ label, labelSize, labelProps, horizontal, messa
|
|
|
10558
10785
|
const rateScoreClass = usePrefixedClassNames('rate-score');
|
|
10559
10786
|
const rateTextClass = usePrefixedClassNames('rate-text');
|
|
10560
10787
|
const rateCustomTextClass = usePrefixedClassNames('rate-custom-text');
|
|
10561
|
-
const rateElement = (jsxRuntime.jsxs("div", { ref: ref, className: combinedClasses, role: "radiogroup", "aria-label": ariaLabelledBy ? undefined : 'Rating', "aria-labelledby": ariaLabelledBy, "aria-
|
|
10788
|
+
const rateElement = (jsxRuntime.jsxs("div", { ref: ref, className: combinedClasses, role: "radiogroup", "aria-label": ariaLabelledBy ? undefined : 'Rating', "aria-labelledby": ariaLabelledBy, "aria-activedescendant": checkedIndex >= 0 ? `${starBaseId}-${checkedIndex}` : undefined, tabIndex: disabled ? -1 : 0, onKeyDown: handleKeyDown, ...rest, children: [jsxRuntime.jsx("div", { className: rateItemsClass, children: renderIcons() }), showScore && (jsxRuntime.jsx("span", { className: rateScoreClass, children: getScoreDisplay() })), text && jsxRuntime.jsx("span", { className: rateTextClass, children: text }), customText && (jsxRuntime.jsx("span", { className: rateCustomTextClass, children: customText })), name && (jsxRuntime.jsx("input", { type: "hidden", name: name, value: currentValue, form: form }))] }));
|
|
10562
10789
|
let content = rateElement;
|
|
10563
10790
|
if (rendersOwnControl('Rate', { insideControl })) {
|
|
10564
10791
|
content = jsxRuntime.jsx(Control, { children: content });
|
|
@@ -10612,6 +10839,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10612
10839
|
const [internalValue, setInternalValue] = React.useState('');
|
|
10613
10840
|
const [isActive, setIsActive] = React.useState(false);
|
|
10614
10841
|
const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
|
|
10842
|
+
const listboxId = `${React.useId()}-listbox`;
|
|
10615
10843
|
// Use controlled or internal value
|
|
10616
10844
|
const inputValue = controlledValue !== undefined ? controlledValue : internalValue;
|
|
10617
10845
|
// Get display value from item
|
|
@@ -10803,19 +11031,33 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
10803
11031
|
// or that of a loading Control this sits in.
|
|
10804
11032
|
const controlLoading = useControlLoading();
|
|
10805
11033
|
const showClear = clearable && !!inputValue && !disabled && !loading && !controlLoading;
|
|
10806
|
-
|
|
11034
|
+
// The combobox controls its listbox and points at the highlighted option.
|
|
11035
|
+
// The listbox takes its name from the label this renders, or a fallback
|
|
11036
|
+
// when it renders none.
|
|
11037
|
+
const listOpen = isActive && (filteredData.length > 0 || !!empty);
|
|
11038
|
+
const listLabelId = !insideField && label
|
|
11039
|
+
? (fieldLabelProps?.id ?? `${listboxId}-label`)
|
|
11040
|
+
: undefined;
|
|
11041
|
+
const activeOptionId = listOpen &&
|
|
11042
|
+
highlightedIndex >= 0 &&
|
|
11043
|
+
highlightedIndex < filteredData.length
|
|
11044
|
+
? `${listboxId}-${highlightedIndex}`
|
|
11045
|
+
: undefined;
|
|
11046
|
+
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), listOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), filteredData.length > 0 ? (filteredData.map((item, index) => {
|
|
10807
11047
|
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
10808
11048
|
const isHighlighted = index === highlightedIndex;
|
|
10809
11049
|
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
10810
11050
|
'is-active': isHighlighted,
|
|
10811
11051
|
'is-disabled': isDisabled,
|
|
10812
11052
|
});
|
|
10813
|
-
return (jsxRuntime.jsx("a", { "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
|
|
11053
|
+
return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
|
|
10814
11054
|
? itemTemplate(item)
|
|
10815
11055
|
: getDisplayValue(item) }, index));
|
|
10816
11056
|
})) : (jsxRuntime.jsx("div", { className: emptyItemClasses, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) }))] }));
|
|
10817
11057
|
if (!insideField) {
|
|
10818
|
-
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps:
|
|
11058
|
+
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: listLabelId
|
|
11059
|
+
? { ...fieldLabelProps, id: listLabelId }
|
|
11060
|
+
: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
|
|
10819
11061
|
}
|
|
10820
11062
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [autocompleteElement, messageEl] }));
|
|
10821
11063
|
});
|
|
@@ -11481,6 +11723,12 @@ fieldClassName, controlClassName,
|
|
|
11481
11723
|
});
|
|
11482
11724
|
TextArea.displayName = 'TextArea';
|
|
11483
11725
|
|
|
11726
|
+
/**
|
|
11727
|
+
* Whether a key is Alt+ArrowDown or Alt+ArrowUp, which open and close a
|
|
11728
|
+
* picker's popover. The calendar and the time wheels leave both to it rather
|
|
11729
|
+
* than reading them as plain arrows.
|
|
11730
|
+
*/
|
|
11731
|
+
const isPopoverToggleKey = (e) => e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp');
|
|
11484
11732
|
const DEFAULT_PICKER_LABELS = {
|
|
11485
11733
|
prevMonth: 'Previous month',
|
|
11486
11734
|
nextMonth: 'Next month',
|
|
@@ -11492,6 +11740,8 @@ const DEFAULT_PICKER_LABELS = {
|
|
|
11492
11740
|
rangeStart: 'Start date',
|
|
11493
11741
|
rangeEnd: 'End date',
|
|
11494
11742
|
rangePreviewEnd: 'Choose as end date',
|
|
11743
|
+
rangeSeparator: '–',
|
|
11744
|
+
chooseDateRange: 'Choose date range',
|
|
11495
11745
|
hours: 'hours',
|
|
11496
11746
|
minutes: 'minutes',
|
|
11497
11747
|
seconds: 'seconds',
|
|
@@ -11699,6 +11949,12 @@ function addMonths(d, n) {
|
|
|
11699
11949
|
function addYears(d, n) {
|
|
11700
11950
|
return addMonths(d, n * 12);
|
|
11701
11951
|
}
|
|
11952
|
+
/**
|
|
11953
|
+
* A copy of `d` with the given parts of its time of day set and the rest
|
|
11954
|
+
* left as they are. A time started from the clock rather than from a value
|
|
11955
|
+
* should pass the seconds its picker does not show, and the milliseconds,
|
|
11956
|
+
* as `0`, or it keeps the ones the clock had.
|
|
11957
|
+
*/
|
|
11702
11958
|
function setTimeOfDay(d, parts) {
|
|
11703
11959
|
const r = new Date(d);
|
|
11704
11960
|
if (parts.hours !== undefined)
|
|
@@ -11707,6 +11963,8 @@ function setTimeOfDay(d, parts) {
|
|
|
11707
11963
|
r.setMinutes(parts.minutes);
|
|
11708
11964
|
if (parts.seconds !== undefined)
|
|
11709
11965
|
r.setSeconds(parts.seconds);
|
|
11966
|
+
if (parts.milliseconds !== undefined)
|
|
11967
|
+
r.setMilliseconds(parts.milliseconds);
|
|
11710
11968
|
return r;
|
|
11711
11969
|
}
|
|
11712
11970
|
/**
|
|
@@ -11855,6 +12113,36 @@ function formatDateTime(d, fmt, locale) {
|
|
|
11855
12113
|
return formatTokenString(d, f);
|
|
11856
12114
|
return formatWithIntl(d, f, locale);
|
|
11857
12115
|
}
|
|
12116
|
+
/**
|
|
12117
|
+
* The value as a native input and a hidden form input carry it:
|
|
12118
|
+
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
12119
|
+
* `<input type="month">` use. The year is padded to four digits, as those
|
|
12120
|
+
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
12121
|
+
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
12122
|
+
*/
|
|
12123
|
+
function toIsoValue(d, granularity) {
|
|
12124
|
+
if (d.getFullYear() < FIRST_YEAR)
|
|
12125
|
+
return '';
|
|
12126
|
+
const year = pad(d.getFullYear(), 4);
|
|
12127
|
+
if (granularity === 'year')
|
|
12128
|
+
return year;
|
|
12129
|
+
const month = `${year}-${pad(d.getMonth() + 1)}`;
|
|
12130
|
+
return granularity === 'month' ? month : `${month}-${pad(d.getDate())}`;
|
|
12131
|
+
}
|
|
12132
|
+
/**
|
|
12133
|
+
* Read a native `type="date"` or `type="month"` value back into a Date. HTML
|
|
12134
|
+
* allows a year of four or more digits, as `toIsoValue` writes one past 9999.
|
|
12135
|
+
*/
|
|
12136
|
+
function fromIsoValue(s, granularity) {
|
|
12137
|
+
if (granularity === 'month') {
|
|
12138
|
+
const m = /^(\d{4,})-(\d{2})$/.exec(s);
|
|
12139
|
+
return m ? makeDate(Number(m[1]), Number(m[2]) - 1) : null;
|
|
12140
|
+
}
|
|
12141
|
+
const m = /^(\d{4,})-(\d{2})-(\d{2})$/.exec(s);
|
|
12142
|
+
if (!m)
|
|
12143
|
+
return null;
|
|
12144
|
+
return makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
12145
|
+
}
|
|
11858
12146
|
/**
|
|
11859
12147
|
* Derive the hour cycle (`'12'` or `'24'`) a display format will render, from
|
|
11860
12148
|
* its first hour token: `h`/`hh` → 12-hour with meridiem, `H`/`HH` → 24-hour.
|
|
@@ -12356,6 +12644,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12356
12644
|
}
|
|
12357
12645
|
}, [min, max, isDateUnselectable, onFocusedDateChange]);
|
|
12358
12646
|
const handleKeyDown = React.useCallback((e) => {
|
|
12647
|
+
if (isPopoverToggleKey(e))
|
|
12648
|
+
return;
|
|
12359
12649
|
// A key takes the range preview back from the pointer.
|
|
12360
12650
|
setHoverDate(null);
|
|
12361
12651
|
switch (e.key) {
|
|
@@ -12448,6 +12738,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12448
12738
|
}
|
|
12449
12739
|
}, [min, max, isMonthUnselectable, onFocusedDateChange]);
|
|
12450
12740
|
const handleMonthKeyDown = React.useCallback((e) => {
|
|
12741
|
+
if (isPopoverToggleKey(e))
|
|
12742
|
+
return;
|
|
12451
12743
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
12452
12744
|
e.preventDefault();
|
|
12453
12745
|
if (!isMonthUnselectable(focusedDate)) {
|
|
@@ -12582,6 +12874,8 @@ const Calendar = ({ value, focusedDate, onSelect, onFocusedDateChange, min: minP
|
|
|
12582
12874
|
}
|
|
12583
12875
|
}, [yearList, disabledYears, onFocusedDateChange, min, max]);
|
|
12584
12876
|
const handleYearListKeyDown = React.useCallback((e) => {
|
|
12877
|
+
if (isPopoverToggleKey(e))
|
|
12878
|
+
return;
|
|
12585
12879
|
if (isYearGranularity) {
|
|
12586
12880
|
const step = yearStep(e.key, focusedYear, yearList);
|
|
12587
12881
|
if (!step)
|
|
@@ -12843,6 +13137,8 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12843
13137
|
document.addEventListener('pointerdown', handler);
|
|
12844
13138
|
return () => document.removeEventListener('pointerdown', handler);
|
|
12845
13139
|
}, [isOpen, closeOnClickOutside, anchorRef, onClose]);
|
|
13140
|
+
// Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
|
|
13141
|
+
// which the calendar and the time wheels inside leave to it.
|
|
12846
13142
|
React.useEffect(() => {
|
|
12847
13143
|
if (!isOpen || !closeOnEscape || !isBrowser)
|
|
12848
13144
|
return undefined;
|
|
@@ -12851,6 +13147,11 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
12851
13147
|
e.stopPropagation();
|
|
12852
13148
|
onClose();
|
|
12853
13149
|
}
|
|
13150
|
+
else if (e.altKey && e.key === 'ArrowUp') {
|
|
13151
|
+
e.preventDefault();
|
|
13152
|
+
e.stopPropagation();
|
|
13153
|
+
onClose();
|
|
13154
|
+
}
|
|
12854
13155
|
};
|
|
12855
13156
|
document.addEventListener('keydown', handler);
|
|
12856
13157
|
return () => document.removeEventListener('keydown', handler);
|
|
@@ -13254,7 +13555,7 @@ const SEPARATOR_RE = /[/:\-.\s]/;
|
|
|
13254
13555
|
* null and the input transparently falls back to free-form text entry.
|
|
13255
13556
|
*/
|
|
13256
13557
|
function useSegmentedEntry(params) {
|
|
13257
|
-
const { format, skipKinds, value, commitValue, formatFn, tryParse, text, setText, makeBaseDate, locale, min, max, isBlocked, disabled, readOnly, editable = true, popover = true, openOnFocus = true, closeOnSelect = false, isOpen, setOpen, inputRef, containerRef, onFocus, onClick, onKeyDown, onBlur, } = params;
|
|
13558
|
+
const { format, skipKinds, value, commitValue, formatFn, tryParse, text, setText, makeBaseDate, locale, min, max, isBlocked, isUnfinishedBlocked, disabled, readOnly, editable = true, popover = true, openOnFocus = true, closeOnSelect = false, isOpen, setOpen, inputRef, containerRef, onFocus, onClick, onKeyDown, onBlur, } = params;
|
|
13258
13559
|
const segmentMap = React.useMemo(() => {
|
|
13259
13560
|
const map = typeof format === 'string' ? buildSegmentMap(format) : null;
|
|
13260
13561
|
if (!map || !skipKinds)
|
|
@@ -13450,7 +13751,7 @@ function useSegmentedEntry(params) {
|
|
|
13450
13751
|
// Clicking the field opens the popover only when `openOnFocus` is on —
|
|
13451
13752
|
// a click is just a focus. With `openOnFocus={false}` (manual-entry
|
|
13452
13753
|
// mode) the click positions the caret for typing and the popover is
|
|
13453
|
-
// opened explicitly via the right launcher button (or ArrowDown).
|
|
13754
|
+
// opened explicitly via the right launcher button (or Alt+ArrowDown).
|
|
13454
13755
|
if (openOnFocus && popover && !disabled && !readOnly)
|
|
13455
13756
|
setOpen(true);
|
|
13456
13757
|
if (segmentEditable && segmentMap && inputRef.current) {
|
|
@@ -13486,14 +13787,23 @@ function useSegmentedEntry(params) {
|
|
|
13486
13787
|
const isPm = base.getHours() >= 12;
|
|
13487
13788
|
switch (e.key) {
|
|
13488
13789
|
case 'ArrowUp':
|
|
13489
|
-
e.preventDefault();
|
|
13490
|
-
typedDigitsRef.current = '';
|
|
13491
|
-
applyDateFromSegment(incrementSegmentValue(seg, base, 1, isPm));
|
|
13492
|
-
return;
|
|
13493
13790
|
case 'ArrowDown':
|
|
13494
13791
|
e.preventDefault();
|
|
13792
|
+
// The plain arrows step the segment, so the popover opens on
|
|
13793
|
+
// Alt+ArrowDown and closes on Alt+ArrowUp, as a combobox's does,
|
|
13794
|
+
// and neither steps it.
|
|
13795
|
+
if (e.altKey) {
|
|
13796
|
+
if (e.key === 'ArrowUp') {
|
|
13797
|
+
if (isOpen)
|
|
13798
|
+
setOpen(false);
|
|
13799
|
+
}
|
|
13800
|
+
else if (!isOpen && popover) {
|
|
13801
|
+
setOpen(true);
|
|
13802
|
+
}
|
|
13803
|
+
return;
|
|
13804
|
+
}
|
|
13495
13805
|
typedDigitsRef.current = '';
|
|
13496
|
-
applyDateFromSegment(incrementSegmentValue(seg, base, -1, isPm));
|
|
13806
|
+
applyDateFromSegment(incrementSegmentValue(seg, base, e.key === 'ArrowUp' ? 1 : -1, isPm));
|
|
13497
13807
|
return;
|
|
13498
13808
|
case 'ArrowRight':
|
|
13499
13809
|
e.preventDefault();
|
|
@@ -13559,6 +13869,8 @@ function useSegmentedEntry(params) {
|
|
|
13559
13869
|
const nextBuffer = typedDigitsRef.current + e.key;
|
|
13560
13870
|
typedDigitsRef.current = nextBuffer;
|
|
13561
13871
|
const { date, advance } = setSegmentValue(seg, base, nextBuffer, isPm);
|
|
13872
|
+
if (!advance && isUnfinishedBlocked?.(date))
|
|
13873
|
+
return;
|
|
13562
13874
|
applyDateFromSegment(date);
|
|
13563
13875
|
if (advance) {
|
|
13564
13876
|
typedDigitsRef.current = '';
|
|
@@ -13569,11 +13881,23 @@ function useSegmentedEntry(params) {
|
|
|
13569
13881
|
}
|
|
13570
13882
|
}
|
|
13571
13883
|
// ----- free-form / popover key handling -----
|
|
13572
|
-
|
|
13884
|
+
// With no segment to step, ArrowDown opens the popover with or without
|
|
13885
|
+
// Alt, unless the field is read-only or disabled, as focus and the
|
|
13886
|
+
// launcher don't open it then either. Alt+ArrowUp closes it.
|
|
13887
|
+
if (e.key === 'ArrowDown' &&
|
|
13888
|
+
!isOpen &&
|
|
13889
|
+
popover &&
|
|
13890
|
+
!disabled &&
|
|
13891
|
+
!readOnly) {
|
|
13573
13892
|
e.preventDefault();
|
|
13574
13893
|
setOpen(true);
|
|
13575
13894
|
return;
|
|
13576
13895
|
}
|
|
13896
|
+
if (e.key === 'ArrowUp' && e.altKey && isOpen) {
|
|
13897
|
+
e.preventDefault();
|
|
13898
|
+
setOpen(false);
|
|
13899
|
+
return;
|
|
13900
|
+
}
|
|
13577
13901
|
if (e.key === 'Escape' && isOpen) {
|
|
13578
13902
|
e.preventDefault();
|
|
13579
13903
|
setOpen(false);
|
|
@@ -13603,11 +13927,14 @@ function useSegmentedEntry(params) {
|
|
|
13603
13927
|
activeSegmentIdx,
|
|
13604
13928
|
segmentBaseDate,
|
|
13605
13929
|
applyDateFromSegment,
|
|
13930
|
+
isUnfinishedBlocked,
|
|
13606
13931
|
moveSegment,
|
|
13607
13932
|
isOpen,
|
|
13608
13933
|
setOpen,
|
|
13609
13934
|
closeOnSelect,
|
|
13610
13935
|
popover,
|
|
13936
|
+
disabled,
|
|
13937
|
+
readOnly,
|
|
13611
13938
|
isUntouched,
|
|
13612
13939
|
tryParse,
|
|
13613
13940
|
text,
|
|
@@ -13652,37 +13979,6 @@ function supportsInputType(type) {
|
|
|
13652
13979
|
return supported;
|
|
13653
13980
|
}
|
|
13654
13981
|
|
|
13655
|
-
const pad2$1 = (n) => String(n).padStart(2, '0');
|
|
13656
|
-
/**
|
|
13657
|
-
* The value as the native input and the hidden form input carry it:
|
|
13658
|
-
* `YYYY-MM-DD`, `YYYY-MM` or `YYYY`, the shapes `<input type="date">` and
|
|
13659
|
-
* `<input type="month">` use. The year is padded to four digits, as those
|
|
13660
|
-
* inputs require and as the `YYYY` token displays it. HTML has no such shape
|
|
13661
|
-
* for a year before 1, so a date then is empty, as those inputs would make it.
|
|
13662
|
-
*/
|
|
13663
|
-
const toIsoValue = (d, granularity) => {
|
|
13664
|
-
if (d.getFullYear() < FIRST_YEAR)
|
|
13665
|
-
return '';
|
|
13666
|
-
const year = String(d.getFullYear()).padStart(4, '0');
|
|
13667
|
-
if (granularity === 'year')
|
|
13668
|
-
return year;
|
|
13669
|
-
const month = `${year}-${pad2$1(d.getMonth() + 1)}`;
|
|
13670
|
-
return granularity === 'month' ? month : `${month}-${pad2$1(d.getDate())}`;
|
|
13671
|
-
};
|
|
13672
|
-
/**
|
|
13673
|
-
* Read a native `type="date"` or `type="month"` value back into a Date. HTML
|
|
13674
|
-
* allows a year of four or more digits, as `toIsoValue` writes one past 9999.
|
|
13675
|
-
*/
|
|
13676
|
-
const fromIsoValue = (s, granularity) => {
|
|
13677
|
-
if (granularity === 'month') {
|
|
13678
|
-
const m = /^(\d{4,})-(\d{2})$/.exec(s);
|
|
13679
|
-
return m ? makeDate(Number(m[1]), Number(m[2]) - 1) : null;
|
|
13680
|
-
}
|
|
13681
|
-
const m = /^(\d{4,})-(\d{2})-(\d{2})$/.exec(s);
|
|
13682
|
-
if (!m)
|
|
13683
|
-
return null;
|
|
13684
|
-
return makeDate(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
13685
|
-
};
|
|
13686
13982
|
const DEFAULT_FORMATS = {
|
|
13687
13983
|
day: DEFAULT_DATE_FORMAT,
|
|
13688
13984
|
month: DEFAULT_MONTH_FORMAT,
|
|
@@ -13784,7 +14080,11 @@ const DateInputBase = React.forwardRef((props, ref) => {
|
|
|
13784
14080
|
// them, so the month or year containing `min` stays reachable.
|
|
13785
14081
|
const periodMin = React.useMemo(() => isDayGranularity ? lowerBound : startOfPeriod(lowerBound, granularity), [lowerBound, isDayGranularity, granularity]);
|
|
13786
14082
|
const periodMax = React.useMemo(() => (max && !isDayGranularity ? endOfPeriod(max, granularity) : max), [max, isDayGranularity, granularity]);
|
|
13787
|
-
|
|
14083
|
+
// A pick is the day at midnight, as a click on its cell gives. Enter hands
|
|
14084
|
+
// over the focused date, which can carry a time of day from the clock, a
|
|
14085
|
+
// value or a bound, so its time is dropped here.
|
|
14086
|
+
const handleSelect = React.useCallback((picked) => {
|
|
14087
|
+
const d = startOfDay(picked);
|
|
13788
14088
|
if (!isWithin(d, periodMin, periodMax))
|
|
13789
14089
|
return;
|
|
13790
14090
|
commitValue(d);
|
|
@@ -13956,13 +14256,14 @@ const DateInput = React.forwardRef((props, ref) => {
|
|
|
13956
14256
|
// icon-left container has nothing to anchor to, and it renders no
|
|
13957
14257
|
// Control inside one or out. The left icon is checked as the caller
|
|
13958
14258
|
// passed it: the default glyph is this component's own choice, not a
|
|
13959
|
-
// prop the caller set.
|
|
14259
|
+
// prop the caller set, so it goes in only for the advice.
|
|
13960
14260
|
const ownControl = rendersOwnControl('DateInput', {
|
|
13961
14261
|
insideControl,
|
|
13962
14262
|
inline: baseProps.inline,
|
|
13963
14263
|
isLoading,
|
|
13964
14264
|
iconLeft,
|
|
13965
14265
|
iconLeftName: props.iconLeftName,
|
|
14266
|
+
defaultIconLeftName: iconLeftName,
|
|
13966
14267
|
iconLeftSize,
|
|
13967
14268
|
iconRight,
|
|
13968
14269
|
iconRightName,
|
|
@@ -14352,23 +14653,28 @@ const WheelInner = ({ values, index, onChange, formatLabel, formatAriaText, aria
|
|
|
14352
14653
|
cancelRaf();
|
|
14353
14654
|
commitPosition(Math.round(positionRef.current) + delta);
|
|
14354
14655
|
}, [disabled, cancelRaf, commitPosition]);
|
|
14656
|
+
// The keys are a spinbutton's: up raises the value and down lowers it. The
|
|
14657
|
+
// values run down the wheel, so up brings the one below the band into it,
|
|
14658
|
+
// the way a drag or a scroll that moves the items up does.
|
|
14355
14659
|
const onKeyDown = React.useCallback((e) => {
|
|
14660
|
+
if (isPopoverToggleKey(e))
|
|
14661
|
+
return;
|
|
14356
14662
|
switch (e.key) {
|
|
14357
14663
|
case 'ArrowUp':
|
|
14358
14664
|
e.preventDefault();
|
|
14359
|
-
moveBy(
|
|
14665
|
+
moveBy(1);
|
|
14360
14666
|
break;
|
|
14361
14667
|
case 'ArrowDown':
|
|
14362
14668
|
e.preventDefault();
|
|
14363
|
-
moveBy(1);
|
|
14669
|
+
moveBy(-1);
|
|
14364
14670
|
break;
|
|
14365
14671
|
case 'PageUp':
|
|
14366
14672
|
e.preventDefault();
|
|
14367
|
-
moveBy(
|
|
14673
|
+
moveBy(5);
|
|
14368
14674
|
break;
|
|
14369
14675
|
case 'PageDown':
|
|
14370
14676
|
e.preventDefault();
|
|
14371
|
-
moveBy(5);
|
|
14677
|
+
moveBy(-5);
|
|
14372
14678
|
break;
|
|
14373
14679
|
case 'Home':
|
|
14374
14680
|
e.preventDefault();
|
|
@@ -14645,13 +14951,16 @@ const WheelInner = ({ values, index, onChange, formatLabel, formatAriaText, aria
|
|
|
14645
14951
|
const top = centreTop + offCentreItems * itemHeight;
|
|
14646
14952
|
const selected = vIdx === virtualIdx;
|
|
14647
14953
|
const itemDisabled = disabled || (disabledFor ? disabledFor(values[aIdx]) : false);
|
|
14648
|
-
return (jsxRuntime.jsx("button", { type: "button",
|
|
14954
|
+
return (jsxRuntime.jsx("button", { type: "button", "aria-hidden": "true", tabIndex: -1, className: itemClass(selected), style: {
|
|
14649
14955
|
position: 'absolute',
|
|
14650
14956
|
left: 0,
|
|
14651
14957
|
right: 0,
|
|
14652
14958
|
height: itemHeight,
|
|
14653
14959
|
top: `${top}px`,
|
|
14654
|
-
}, disabled: itemDisabled,
|
|
14960
|
+
}, disabled: itemDisabled,
|
|
14961
|
+
// Keep a press from focusing the item, which assistive technology
|
|
14962
|
+
// can't see; the click below hands focus to the wheel instead.
|
|
14963
|
+
onMouseDown: e => e.preventDefault(), onClick: () => {
|
|
14655
14964
|
moveBy(vIdx - virtualIdx);
|
|
14656
14965
|
// Click focuses the <button> child by default; pull focus back
|
|
14657
14966
|
// to the wheel root so the column emphasis (:focus rules) match.
|
|
@@ -14685,6 +14994,7 @@ const TimeWheels = ({ value, onChange, hourFormat = '24', enableSeconds = false,
|
|
|
14685
14994
|
hours: h,
|
|
14686
14995
|
minutes: m,
|
|
14687
14996
|
seconds: s,
|
|
14997
|
+
milliseconds: 0,
|
|
14688
14998
|
});
|
|
14689
14999
|
return !unselectableTimes(probe);
|
|
14690
15000
|
}, [unselectableTimes]);
|
|
@@ -14834,7 +15144,7 @@ const TimeWheels = ({ value, onChange, hourFormat = '24', enableSeconds = false,
|
|
|
14834
15144
|
});
|
|
14835
15145
|
columnOrder[nearestIdx]?.current?.startDrag(e.pointerId, e.clientY);
|
|
14836
15146
|
};
|
|
14837
|
-
return (jsxRuntime.jsx("div", { id: id, className: classNames(rootClass, className), "aria-disabled": disabled, children: jsxRuntime.jsxs("div", { className: wheelsClass, ref: wheelsContainerRef, onPointerDown: onWheelsContainerPointerDown, children: [jsxRuntime.jsx(Wheel, { ref: hoursRef, values: hourValues, index: hourIndex, onChange: onHourIndex, formatLabel: pad2, ariaLabel: t.hours, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onCommit: onCommit, onFocusPrev: focusPrevOf(hoursRef), onFocusNext: focusNextOf(hoursRef), disabledFor: hourDisabledFor, audioTick: audioTick }), jsxRuntime.jsx("div", { className: sepClass, "aria-hidden": "true", children: ":" }), jsxRuntime.jsx(Wheel, { ref: minutesRef, values: minuteValues, index: minuteIndex, onChange: onMinuteIndex, formatLabel: pad2, ariaLabel: t.minutes, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onCommit: onCommit, onFocusPrev: focusPrevOf(minutesRef), onFocusNext: focusNextOf(minutesRef), disabledFor: minuteDisabledFor, audioTick: audioTick }), enableSeconds && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: sepClass, "aria-hidden": "true", children: ":" }), jsxRuntime.jsx(Wheel, { ref: secondsRef, values: secondValues, index: secondIndex, onChange: onSecondIndex, formatLabel: pad2, ariaLabel: t.seconds, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onFocusPrev: focusPrevOf(secondsRef), onFocusNext: focusNextOf(secondsRef), disabledFor: secondDisabledFor, audioTick: audioTick })] })), hourFormat === '12' && (jsxRuntime.jsx(Wheel, { ref: ampmRef, values: ['AM', 'PM'], index: ampmIndex, onChange: onAmpmIndex, formatLabel: v => v, ariaLabel: t.ampm, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, onCommit: onCommit, onFocusPrev: focusPrevOf(ampmRef), onFocusNext: focusNextOf(ampmRef), audioTick: audioTick }))] }) }));
|
|
15147
|
+
return (jsxRuntime.jsx("div", { id: id, className: classNames(rootClass, className), "aria-disabled": disabled, children: jsxRuntime.jsxs("div", { className: wheelsClass, ref: wheelsContainerRef, onPointerDown: onWheelsContainerPointerDown, children: [jsxRuntime.jsx(Wheel, { ref: hoursRef, values: hourValues, index: hourIndex, onChange: onHourIndex, formatLabel: pad2, ariaLabel: t.hours, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onCommit: onCommit, onFocusPrev: focusPrevOf(hoursRef), onFocusNext: focusNextOf(hoursRef), disabledFor: hourDisabledFor, audioTick: audioTick }), jsxRuntime.jsx("div", { className: sepClass, "aria-hidden": "true", children: ":" }), jsxRuntime.jsx(Wheel, { ref: minutesRef, values: minuteValues, index: minuteIndex, onChange: onMinuteIndex, formatLabel: pad2, ariaLabel: t.minutes, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onCommit: onCommit, onFocusPrev: focusPrevOf(minutesRef), onFocusNext: focusNextOf(minutesRef), disabledFor: minuteDisabledFor, audioTick: audioTick }), enableSeconds && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: sepClass, "aria-hidden": "true", children: ":" }), jsxRuntime.jsx(Wheel, { ref: secondsRef, values: secondValues, index: secondIndex, onChange: onSecondIndex, formatLabel: pad2, ariaLabel: t.seconds, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, wrap: true, onCommit: onCommit, onFocusPrev: focusPrevOf(secondsRef), onFocusNext: focusNextOf(secondsRef), disabledFor: secondDisabledFor, audioTick: audioTick })] })), hourFormat === '12' && (jsxRuntime.jsx(Wheel, { ref: ampmRef, values: ['AM', 'PM'], index: ampmIndex, onChange: onAmpmIndex, formatLabel: v => v, ariaLabel: t.ampm, visibleCount: visibleCount, itemHeight: itemHeight, disabled: disabled, size: size, color: color, onCommit: onCommit, onFocusPrev: focusPrevOf(ampmRef), onFocusNext: focusNextOf(ampmRef), audioTick: audioTick }))] }) }));
|
|
14838
15148
|
};
|
|
14839
15149
|
TimeWheels.displayName = 'TimeWheels';
|
|
14840
15150
|
|
|
@@ -14951,9 +15261,16 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
14951
15261
|
setInternalValue(next);
|
|
14952
15262
|
onChange?.(next);
|
|
14953
15263
|
}, [isControlled, onChange]);
|
|
15264
|
+
// An empty field starts from today, at the whole minute or second the
|
|
15265
|
+
// wheels show.
|
|
14954
15266
|
const handleSpinnerChange = React.useCallback((parts) => {
|
|
14955
|
-
const
|
|
14956
|
-
|
|
15267
|
+
const next = value
|
|
15268
|
+
? setTimeOfDay(value, parts)
|
|
15269
|
+
: setTimeOfDay(new Date(), {
|
|
15270
|
+
...parts,
|
|
15271
|
+
seconds: parts.seconds ?? 0,
|
|
15272
|
+
milliseconds: 0,
|
|
15273
|
+
});
|
|
14957
15274
|
if (!isWithin(next, lowerBound, max))
|
|
14958
15275
|
return;
|
|
14959
15276
|
commitValue(next);
|
|
@@ -15030,7 +15347,11 @@ const TimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15030
15347
|
minutes: value?.getMinutes() ?? 0,
|
|
15031
15348
|
seconds: enableSeconds ? (value?.getSeconds() ?? 0) : undefined,
|
|
15032
15349
|
};
|
|
15033
|
-
const panel = (jsxRuntime.jsxs("div", { className: panelClass, children: [jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleSpinnerChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled,
|
|
15350
|
+
const panel = (jsxRuntime.jsxs("div", { className: panelClass, children: [jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleSpinnerChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled,
|
|
15351
|
+
// The popover panel takes `popoverId`, so the wheels inside it take
|
|
15352
|
+
// one of their own. Inline there is no panel, and the wheels keep
|
|
15353
|
+
// the id they released with.
|
|
15354
|
+
id: inline ? popoverId : `${popoverId}-time`, labels: labels, itemHeight: wheelItemHeight, audioTick: effectiveAudioTick, onCommit: () => setOpen(false) }), !inline && !isSmallViewport && (jsxRuntime.jsxs(Buttons, { className: footerClass, hasAddons: true, children: [jsxRuntime.jsx("button", { type: "button", className: footerOkClass, onClick: () => setOpen(false), children: t.ok }), jsxRuntime.jsx("button", { type: "button", className: footerButton, onClick: () => {
|
|
15034
15355
|
commitValue(snapTimeToIncrement(new Date(), {
|
|
15035
15356
|
incrementHours,
|
|
15036
15357
|
incrementMinutes,
|
|
@@ -15084,13 +15405,14 @@ const TimeInput = React.forwardRef((props, ref) => {
|
|
|
15084
15405
|
// icon-left container has nothing to anchor to, and it renders no
|
|
15085
15406
|
// Control inside one or out. The left icon is checked as the caller
|
|
15086
15407
|
// passed it: the default glyph is this component's own choice, not a
|
|
15087
|
-
// prop the caller set.
|
|
15408
|
+
// prop the caller set, so it goes in only for the advice.
|
|
15088
15409
|
const ownControl = rendersOwnControl('TimeInput', {
|
|
15089
15410
|
insideControl,
|
|
15090
15411
|
inline: baseProps.inline,
|
|
15091
15412
|
isLoading,
|
|
15092
15413
|
iconLeft,
|
|
15093
15414
|
iconLeftName: props.iconLeftName,
|
|
15415
|
+
defaultIconLeftName: iconLeftName,
|
|
15094
15416
|
iconLeftSize,
|
|
15095
15417
|
iconRight,
|
|
15096
15418
|
iconRightName,
|
|
@@ -15211,6 +15533,7 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15211
15533
|
const valueAtOpenRef = React.useRef(null);
|
|
15212
15534
|
const reactId = React.useId();
|
|
15213
15535
|
const popoverId = id ? `${id}-popover` : `picker-${reactId}`;
|
|
15536
|
+
const timeWheelsId = `${popoverId}-time`;
|
|
15214
15537
|
const { bulmaHelperClasses, rest: cleanRest } = useBulmaClasses(rest);
|
|
15215
15538
|
const { shouldUseNative, isSmallViewport } = useNativeMobilePicker({
|
|
15216
15539
|
force: mobileNative === 'auto' ? undefined : mobileNative,
|
|
@@ -15268,6 +15591,31 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15268
15591
|
if (!open)
|
|
15269
15592
|
setTimeOpen(false);
|
|
15270
15593
|
}, [open]);
|
|
15594
|
+
// The footer's Time button opens the wheels, and the hours wheel takes
|
|
15595
|
+
// focus so the keys turn it straight away. The calendar they cover is
|
|
15596
|
+
// inert meanwhile, so no tab stop, pointer or screen reader reaches a day
|
|
15597
|
+
// behind them. Closing the wheels hands focus back to the button: focus
|
|
15598
|
+
// in them would fall to the page as they unmount, and a pointer press
|
|
15599
|
+
// outside them has already dropped it on the panel or the page. Focus the
|
|
15600
|
+
// keys moved on to the footer, such as Reset, stays there.
|
|
15601
|
+
const panelRef = React.useRef(null);
|
|
15602
|
+
const timeButtonRef = React.useRef(null);
|
|
15603
|
+
const timeOverlayRef = React.useRef(null);
|
|
15604
|
+
React.useEffect(() => {
|
|
15605
|
+
if (!timeOpen)
|
|
15606
|
+
return;
|
|
15607
|
+
timeOverlayRef.current
|
|
15608
|
+
?.querySelector('[role="spinbutton"]')
|
|
15609
|
+
?.focus();
|
|
15610
|
+
}, [timeOpen]);
|
|
15611
|
+
const closeTime = React.useCallback(() => {
|
|
15612
|
+
const panel = panelRef.current;
|
|
15613
|
+
const active = getActiveElementInTree(panel);
|
|
15614
|
+
const movedOn = !!panel?.contains(active) && !timeOverlayRef.current?.contains(active);
|
|
15615
|
+
if (!movedOn)
|
|
15616
|
+
timeButtonRef.current?.focus();
|
|
15617
|
+
setTimeOpen(false);
|
|
15618
|
+
}, []);
|
|
15271
15619
|
const commitValue = React.useCallback((next) => {
|
|
15272
15620
|
if (!isControlled)
|
|
15273
15621
|
setInternalValue(next);
|
|
@@ -15276,20 +15624,32 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15276
15624
|
setFocusedDate(next);
|
|
15277
15625
|
onChange?.(next);
|
|
15278
15626
|
}, [isControlled, onChange]);
|
|
15627
|
+
// A picked day takes the value's whole time of day, seconds and all, or
|
|
15628
|
+
// midnight in an empty field. The day handed over by the keyboard is the
|
|
15629
|
+
// calendar's focused date, which can carry the time of the clock it
|
|
15630
|
+
// started from, so none of its time is kept.
|
|
15279
15631
|
const handleDateSelect = React.useCallback((d) => {
|
|
15280
15632
|
const merged = setTimeOfDay(d, {
|
|
15281
15633
|
hours: value?.getHours() ?? 0,
|
|
15282
15634
|
minutes: value?.getMinutes() ?? 0,
|
|
15283
|
-
seconds:
|
|
15635
|
+
seconds: value?.getSeconds() ?? 0,
|
|
15636
|
+
milliseconds: value?.getMilliseconds() ?? 0,
|
|
15284
15637
|
});
|
|
15285
15638
|
if (!isWithin(merged, lowerBound, max))
|
|
15286
15639
|
return;
|
|
15287
15640
|
commitValue(merged);
|
|
15288
15641
|
setFocusedDate(d);
|
|
15289
|
-
}, [value,
|
|
15642
|
+
}, [value, lowerBound, max, commitValue]);
|
|
15643
|
+
// The wheels set the value's time. An empty field starts from the focused
|
|
15644
|
+
// day, at the whole minute or second the wheels show.
|
|
15290
15645
|
const handleTimeChange = React.useCallback((parts) => {
|
|
15291
|
-
const
|
|
15292
|
-
|
|
15646
|
+
const next = value
|
|
15647
|
+
? setTimeOfDay(value, parts)
|
|
15648
|
+
: setTimeOfDay(focusedDate, {
|
|
15649
|
+
...parts,
|
|
15650
|
+
seconds: parts.seconds ?? 0,
|
|
15651
|
+
milliseconds: 0,
|
|
15652
|
+
});
|
|
15293
15653
|
if (!isWithin(next, lowerBound, max))
|
|
15294
15654
|
return;
|
|
15295
15655
|
commitValue(next);
|
|
@@ -15303,8 +15663,12 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15303
15663
|
const fmt = typeof defaultFormat === 'string' ? defaultFormat : undefined;
|
|
15304
15664
|
return parseDate(trimmed, fmt ?? DEFAULT_DATETIME_FORMAT);
|
|
15305
15665
|
}, [parse, defaultFormat, locale]);
|
|
15306
|
-
// The Date the user edits when starting without a current value
|
|
15307
|
-
|
|
15666
|
+
// The Date the user edits when starting without a current value: now, at
|
|
15667
|
+
// the whole minute, or the whole second with `enableSeconds`.
|
|
15668
|
+
const makeBaseDate = React.useCallback(() => setTimeOfDay(new Date(), {
|
|
15669
|
+
seconds: enableSeconds ? undefined : 0,
|
|
15670
|
+
milliseconds: 0,
|
|
15671
|
+
}), [enableSeconds]);
|
|
15308
15672
|
const inputReadOnlyAttr = !!readOnly || !editable;
|
|
15309
15673
|
const canOpen = !!popover && !disabled && !readOnly;
|
|
15310
15674
|
// Blocking predicate for manual entry, combining the calendar's
|
|
@@ -15368,20 +15732,24 @@ const DateTimeInputBase = React.forwardRef((props, ref) => {
|
|
|
15368
15732
|
minutes: value?.getMinutes() ?? 0,
|
|
15369
15733
|
seconds: enableSeconds ? (value?.getSeconds() ?? 0) : undefined,
|
|
15370
15734
|
};
|
|
15371
|
-
const panel = (jsxRuntime.jsxs("div", { className: panelClass, onKeyDown: e => {
|
|
15735
|
+
const panel = (jsxRuntime.jsxs("div", { ref: panelRef, className: panelClass, onKeyDown: e => {
|
|
15372
15736
|
// First Escape collapses the floating time wheels; the popover's own
|
|
15373
15737
|
// Escape handler (which closes the whole panel) only sees the second.
|
|
15374
15738
|
if (timeOpen && e.key === 'Escape') {
|
|
15375
15739
|
e.stopPropagation();
|
|
15376
|
-
|
|
15740
|
+
closeTime();
|
|
15377
15741
|
}
|
|
15378
|
-
}, children: [jsxRuntime.jsxs("div", { className: calendarWrapClass, children: [jsxRuntime.jsx(Calendar, { value: value, focusedDate: focusedDate, onSelect: handleDateSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size, id: `${popoverId}-cal`, autoFocusCell: open, labels: labels }), timeOpen && (jsxRuntime.jsx("div", { className: timeOverlayClass, onClick: e => {
|
|
15742
|
+
}, children: [jsxRuntime.jsxs("div", { className: calendarWrapClass, children: [jsxRuntime.jsx("div", { ...inertProps(timeOpen), children: jsxRuntime.jsx(Calendar, { value: value, focusedDate: focusedDate, onSelect: handleDateSelect, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size, id: `${popoverId}-cal`, autoFocusCell: open, labels: labels }) }), timeOpen && (jsxRuntime.jsx("div", { ref: timeOverlayRef, className: timeOverlayClass, onClick: e => {
|
|
15379
15743
|
// Tap outside the wheel card (on the covered calendar area)
|
|
15380
15744
|
// collapses the wheels without selecting a date, matching the
|
|
15381
15745
|
// native behavior.
|
|
15382
15746
|
if (e.target === e.currentTarget)
|
|
15383
|
-
|
|
15384
|
-
}, children: jsxRuntime.jsx("div", { className: timeCardClass, children: jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleTimeChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled, id:
|
|
15747
|
+
closeTime();
|
|
15748
|
+
}, children: jsxRuntime.jsx("div", { className: timeCardClass, children: jsxRuntime.jsx(TimeWheels, { value: spinnerValue, onChange: handleTimeChange, hourFormat: effectiveHourFormat, enableSeconds: enableSeconds, incrementHours: incrementHours, incrementMinutes: incrementMinutes, incrementSeconds: incrementSeconds, unselectableTimes: unselectableTimes, color: color, size: size, disabled: disabled, id: timeWheelsId, labels: labels, itemHeight: wheelItemHeight, audioTick: effectiveAudioTick,
|
|
15749
|
+
// Enter on a wheel commits the time, which the wheels already
|
|
15750
|
+
// did live, and closes: the popover, or inline, where there
|
|
15751
|
+
// is no popover, the wheels, as Escape does.
|
|
15752
|
+
onCommit: () => (inline ? closeTime() : setOpen(false)) }) }) }))] }), jsxRuntime.jsxs("div", { className: footerClass, children: [jsxRuntime.jsxs("button", { ref: timeButtonRef, type: "button", className: footerTimeClass, onClick: () => (timeOpen ? closeTime() : setTimeOpen(true)), "aria-expanded": timeOpen, "aria-controls": timeOpen ? timeWheelsId : undefined, disabled: disabled, children: [jsxRuntime.jsx("span", { children: t.time }), jsxRuntime.jsx("span", { className: footerTimePillClass, children: value ? formatTime(value, timeDisplayFormat, locale) : '—' })] }), jsxRuntime.jsxs("div", { className: footerActionsClass, children: [jsxRuntime.jsx("button", { type: "button", className: footerResetClass, onClick: () => commitValue(valueAtOpenRef.current), children: t.reset }), jsxRuntime.jsx("button", { type: "button", className: footerDoneClass, "aria-label": t.done, onClick: () => setOpen(false), children: jsxRuntime.jsx("svg", { width: "22", height: "22", viewBox: "0 0 22 22", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: "M5 11l4 4 8-8", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] })] })] }));
|
|
15385
15753
|
if (inline) {
|
|
15386
15754
|
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, className: classNames(containerClass, bulmaHelperClasses, className), children: [panel, name && (jsxRuntime.jsx("input", { type: "hidden", name: name, form: form, value: value ? toIsoDateTime(value, enableSeconds) : '', required: required }))] }));
|
|
15387
15755
|
}
|
|
@@ -15425,13 +15793,14 @@ const DateTimeInput = React.forwardRef((props, ref) => {
|
|
|
15425
15793
|
// icon-left container has nothing to anchor to, and it renders no
|
|
15426
15794
|
// Control inside one or out. The left icon is checked as the caller
|
|
15427
15795
|
// passed it: the default glyph is this component's own choice, not a
|
|
15428
|
-
// prop the caller set.
|
|
15796
|
+
// prop the caller set, so it goes in only for the advice.
|
|
15429
15797
|
const ownControl = rendersOwnControl('DateTimeInput', {
|
|
15430
15798
|
insideControl,
|
|
15431
15799
|
inline: baseProps.inline,
|
|
15432
15800
|
isLoading,
|
|
15433
15801
|
iconLeft,
|
|
15434
15802
|
iconLeftName: props.iconLeftName,
|
|
15803
|
+
defaultIconLeftName: iconLeftName,
|
|
15435
15804
|
iconLeftSize,
|
|
15436
15805
|
iconRight,
|
|
15437
15806
|
iconRightName,
|
|
@@ -15466,6 +15835,354 @@ const DateTimeInput = React.forwardRef((props, ref) => {
|
|
|
15466
15835
|
});
|
|
15467
15836
|
DateTimeInput.displayName = 'DateTimeInput';
|
|
15468
15837
|
|
|
15838
|
+
const EMPTY_RANGE = [null, null];
|
|
15839
|
+
/** A range's end as the form and the native inputs carry it. */
|
|
15840
|
+
const isoDay = (d) => (d ? toIsoValue(d, 'day') : '');
|
|
15841
|
+
/** Today at local midnight, where typing into an empty end starts. */
|
|
15842
|
+
const today = () => startOfDay(new Date());
|
|
15843
|
+
/**
|
|
15844
|
+
* Raw DateRangeInput: the two inputs and the popover calendar without
|
|
15845
|
+
* Field/Control wrapping. Use `DateRangeInput` for the convenience wrapper.
|
|
15846
|
+
*
|
|
15847
|
+
* @function
|
|
15848
|
+
* @param {DateRangeInputBaseProps} props
|
|
15849
|
+
* @returns {JSX.Element}
|
|
15850
|
+
*/
|
|
15851
|
+
const DateRangeInputBase = React.forwardRef((props, ref) => {
|
|
15852
|
+
const { value: controlledValue, defaultValue, onChange, onOpen, onClose, min, max, disabled, readOnly, placeholder, format, parse, locale, inline = false, mobileNative = 'auto', editable = true, popover = true, openOnFocus = true, closeOnSelect = true, position = 'bottom-left', appendToBody = false, color, size, isRounded, shouldDisableDate, unselectableDates, allowDisabledInRange = false, firstDayOfWeek = 0, dayNames, monthNames, nearbyMonthDays = true, iconLeftName: _iconLeftName, triggerIcon: triggerIconProp, triggerIconName = 'chevron-down', labels, name, startName, endName, form, required, id, className, onFocus, onFocusCapture, ...rest } = props;
|
|
15853
|
+
// The launcher gives way to the loading spinner of a Control this sits in.
|
|
15854
|
+
const controlLoading = useControlLoading();
|
|
15855
|
+
const triggerIcon = triggerIconProp ?? !controlLoading;
|
|
15856
|
+
const t = mergeLabels(labels);
|
|
15857
|
+
const resolvedFormat = format ?? DEFAULT_DATE_FORMAT;
|
|
15858
|
+
const isControlled = controlledValue !== undefined;
|
|
15859
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue ?? EMPTY_RANGE);
|
|
15860
|
+
const range = isControlled ? controlledValue : internalValue;
|
|
15861
|
+
const [start, end] = range;
|
|
15862
|
+
// Nothing before year 1 is in range, as in HTML's date inputs.
|
|
15863
|
+
const lowerBound = React.useMemo(() => floorMin(min), [min]);
|
|
15864
|
+
const [focusedDate, setFocusedDate] = React.useState(() => clampDate(start ?? end ?? new Date(), lowerBound, max));
|
|
15865
|
+
// Re-clamp the focused day if the bounds change after mount, so the
|
|
15866
|
+
// calendar never shows a month outside them.
|
|
15867
|
+
React.useEffect(() => {
|
|
15868
|
+
setFocusedDate(prev => clampDate(prev, lowerBound, max));
|
|
15869
|
+
}, [lowerBound, max]);
|
|
15870
|
+
const [open, setOpenState] = React.useState(false);
|
|
15871
|
+
const [startText, setStartText] = React.useState(() => start ? formatDate(start, resolvedFormat, locale) : '');
|
|
15872
|
+
const [endText, setEndText] = React.useState(() => end ? formatDate(end, resolvedFormat, locale) : '');
|
|
15873
|
+
// Each input shows its end of the range as it changes from outside.
|
|
15874
|
+
React.useEffect(() => {
|
|
15875
|
+
setStartText(start ? formatDate(start, resolvedFormat, locale) : '');
|
|
15876
|
+
}, [start, resolvedFormat, locale]);
|
|
15877
|
+
React.useEffect(() => {
|
|
15878
|
+
setEndText(end ? formatDate(end, resolvedFormat, locale) : '');
|
|
15879
|
+
}, [end, resolvedFormat, locale]);
|
|
15880
|
+
const containerRef = React.useRef(null);
|
|
15881
|
+
const startRef = React.useRef(null);
|
|
15882
|
+
const endRef = React.useRef(null);
|
|
15883
|
+
const reactId = React.useId();
|
|
15884
|
+
const popoverId = id ? `${id}-popover` : `picker-${reactId}`;
|
|
15885
|
+
const { bulmaHelperClasses, rest: cleanRest } = useBulmaClasses(rest);
|
|
15886
|
+
const { shouldUseNative } = useNativeMobilePicker({
|
|
15887
|
+
force: mobileNative === 'auto' ? undefined : mobileNative,
|
|
15888
|
+
});
|
|
15889
|
+
const useNative = !inline && shouldUseNative;
|
|
15890
|
+
// The root borrows DateInput's container class, which places the launcher
|
|
15891
|
+
// and sizes a Control's icons around the field.
|
|
15892
|
+
const containerClass = usePrefixedClassNames('daterangeinput', 'dateinput-container');
|
|
15893
|
+
const fieldClass = usePrefixedClassNames('input', 'daterangeinput-field', {
|
|
15894
|
+
[`is-${color}`]: !!color,
|
|
15895
|
+
[`is-${size}`]: !!size,
|
|
15896
|
+
'is-rounded': isRounded,
|
|
15897
|
+
'is-disabled': disabled,
|
|
15898
|
+
});
|
|
15899
|
+
const inputClass = usePrefixedClassNames('daterangeinput-input');
|
|
15900
|
+
const separatorClass = usePrefixedClassNames('daterangeinput-separator');
|
|
15901
|
+
const triggerClass = usePrefixedClassNames('dateinput-trigger');
|
|
15902
|
+
// What the latest request asked for, so a second request in the same event
|
|
15903
|
+
// sees it before React renders. The callbacks fire here rather than in a
|
|
15904
|
+
// state updater, which StrictMode calls twice.
|
|
15905
|
+
const requestedOpenRef = React.useRef(false);
|
|
15906
|
+
const setOpen = React.useCallback((next) => {
|
|
15907
|
+
if (requestedOpenRef.current === next)
|
|
15908
|
+
return;
|
|
15909
|
+
requestedOpenRef.current = next;
|
|
15910
|
+
setOpenState(next);
|
|
15911
|
+
if (next)
|
|
15912
|
+
onOpen?.();
|
|
15913
|
+
else
|
|
15914
|
+
onClose?.();
|
|
15915
|
+
}, [onOpen, onClose]);
|
|
15916
|
+
// Focus that moves inside the field, from one input to the other or from
|
|
15917
|
+
// the launcher, is not focus arriving, so it opens nothing. Set as the
|
|
15918
|
+
// focus is captured on the way in, and cleared once the input has handled
|
|
15919
|
+
// it, so a click that follows still opens the popover.
|
|
15920
|
+
const focusFromInsideRef = React.useRef(false);
|
|
15921
|
+
const handleFocusCapture = (e) => {
|
|
15922
|
+
const from = e.relatedTarget;
|
|
15923
|
+
focusFromInsideRef.current = !!from && e.currentTarget.contains(from);
|
|
15924
|
+
onFocusCapture?.(e);
|
|
15925
|
+
};
|
|
15926
|
+
const handleFocus = (e) => {
|
|
15927
|
+
focusFromInsideRef.current = false;
|
|
15928
|
+
onFocus?.(e);
|
|
15929
|
+
};
|
|
15930
|
+
const requestOpen = React.useCallback((next) => {
|
|
15931
|
+
if (next && focusFromInsideRef.current)
|
|
15932
|
+
return;
|
|
15933
|
+
setOpen(next);
|
|
15934
|
+
}, [setOpen]);
|
|
15935
|
+
const commitRange = React.useCallback((next) => {
|
|
15936
|
+
if (!isControlled)
|
|
15937
|
+
setInternalValue(next);
|
|
15938
|
+
onChange?.(next);
|
|
15939
|
+
}, [isControlled, onChange]);
|
|
15940
|
+
const constraints = React.useMemo(() => ({ shouldDisableDate, unselectableDates }), [shouldDisableDate, unselectableDates]);
|
|
15941
|
+
// A day the calendar disables: shouldDisableDate or unselectableDates.
|
|
15942
|
+
// Typing and the native inputs check the bounds alongside it.
|
|
15943
|
+
const isBlockedDay = React.useCallback((d) => !!shouldDisableDate?.(d) ||
|
|
15944
|
+
!!unselectableDates?.some(u => isSameDay(u, d)), [shouldDisableDate, unselectableDates]);
|
|
15945
|
+
// An end the range can take: it is not disabled, and it closes a range
|
|
15946
|
+
// from the start, which it can't when it comes first or, unless allowed,
|
|
15947
|
+
// reaches over a disabled day.
|
|
15948
|
+
const isBlockedEnd = React.useCallback((d) => isBlockedDay(d) ||
|
|
15949
|
+
(!!start && !canCloseRange(start, d, constraints, allowDisabledInRange)), [isBlockedDay, start, constraints, allowDisabledInRange]);
|
|
15950
|
+
// A new start keeps the end only while the two still make a range, so a
|
|
15951
|
+
// start moved past the end, or over a disabled day before it, clears it.
|
|
15952
|
+
const commitStart = React.useCallback((next) => {
|
|
15953
|
+
const keepsEnd = !next ||
|
|
15954
|
+
!end ||
|
|
15955
|
+
canCloseRange(next, end, constraints, allowDisabledInRange);
|
|
15956
|
+
commitRange([next, keepsEnd ? end : null]);
|
|
15957
|
+
if (next)
|
|
15958
|
+
setFocusedDate(next);
|
|
15959
|
+
}, [end, constraints, allowDisabledInRange, commitRange]);
|
|
15960
|
+
// Every digit typed into the start commits, so a start passes through
|
|
15961
|
+
// values on its way to the one being typed. The first digits of a year make
|
|
15962
|
+
// a start centuries back, which can't keep the end once a disabled day may
|
|
15963
|
+
// lie between. Such a value waits for the segment's next digit rather than
|
|
15964
|
+
// clearing an end the finished start may keep.
|
|
15965
|
+
const startCostsEnd = React.useCallback((d) => !!end && !canCloseRange(d, end, constraints, allowDisabledInRange), [end, constraints, allowDisabledInRange]);
|
|
15966
|
+
const commitEnd = React.useCallback((next) => {
|
|
15967
|
+
commitRange([start, next]);
|
|
15968
|
+
if (next)
|
|
15969
|
+
setFocusedDate(next);
|
|
15970
|
+
}, [start, commitRange]);
|
|
15971
|
+
const handleRangeSelect = React.useCallback((from, to) => {
|
|
15972
|
+
commitRange([from, to]);
|
|
15973
|
+
setFocusedDate(to);
|
|
15974
|
+
if (closeOnSelect)
|
|
15975
|
+
setOpen(false);
|
|
15976
|
+
}, [commitRange, closeOnSelect, setOpen]);
|
|
15977
|
+
const tryParse = React.useCallback((s) => {
|
|
15978
|
+
const trimmed = s.trim();
|
|
15979
|
+
if (!trimmed)
|
|
15980
|
+
return null;
|
|
15981
|
+
const fmt = typeof format === 'string' ? format : undefined;
|
|
15982
|
+
return parse
|
|
15983
|
+
? parse(trimmed)
|
|
15984
|
+
: parseDate(trimmed, fmt ?? DEFAULT_DATE_FORMAT);
|
|
15985
|
+
}, [parse, format, locale]);
|
|
15986
|
+
// Typing into an empty end starts from the start, so only what differs
|
|
15987
|
+
// needs typing; an empty start starts from today.
|
|
15988
|
+
const makeEndBase = React.useCallback(() => (start ? startOfDay(start) : today()), [start]);
|
|
15989
|
+
// Where focus goes back as the popover closes: the input focused last,
|
|
15990
|
+
// which is the one that opened it unless the launcher did, or the start
|
|
15991
|
+
// input before either has had focus.
|
|
15992
|
+
const openerRef = React.useRef(null);
|
|
15993
|
+
const restoreFocusRef = React.useMemo(() => ({
|
|
15994
|
+
get current() {
|
|
15995
|
+
return openerRef.current ?? startRef.current;
|
|
15996
|
+
},
|
|
15997
|
+
}), []);
|
|
15998
|
+
const shared = {
|
|
15999
|
+
format: resolvedFormat,
|
|
16000
|
+
formatFn: formatDate,
|
|
16001
|
+
tryParse,
|
|
16002
|
+
locale,
|
|
16003
|
+
min: lowerBound,
|
|
16004
|
+
max,
|
|
16005
|
+
disabled,
|
|
16006
|
+
readOnly,
|
|
16007
|
+
editable,
|
|
16008
|
+
popover,
|
|
16009
|
+
openOnFocus,
|
|
16010
|
+
closeOnSelect,
|
|
16011
|
+
isOpen: open,
|
|
16012
|
+
setOpen: requestOpen,
|
|
16013
|
+
};
|
|
16014
|
+
// Each input is its own picker field. Its blur counts focus leaving it,
|
|
16015
|
+
// even for the other input, so text typed into one is read as focus moves
|
|
16016
|
+
// to the other.
|
|
16017
|
+
const { inputHandlers: startHandlers } = useSegmentedEntry({
|
|
16018
|
+
...shared,
|
|
16019
|
+
value: start,
|
|
16020
|
+
commitValue: commitStart,
|
|
16021
|
+
text: startText,
|
|
16022
|
+
setText: setStartText,
|
|
16023
|
+
makeBaseDate: today,
|
|
16024
|
+
isBlocked: isBlockedDay,
|
|
16025
|
+
isUnfinishedBlocked: startCostsEnd,
|
|
16026
|
+
inputRef: startRef,
|
|
16027
|
+
containerRef: startRef,
|
|
16028
|
+
onFocus: () => {
|
|
16029
|
+
openerRef.current = startRef.current;
|
|
16030
|
+
},
|
|
16031
|
+
});
|
|
16032
|
+
const { inputHandlers: endHandlers } = useSegmentedEntry({
|
|
16033
|
+
...shared,
|
|
16034
|
+
value: end,
|
|
16035
|
+
commitValue: commitEnd,
|
|
16036
|
+
text: endText,
|
|
16037
|
+
setText: setEndText,
|
|
16038
|
+
makeBaseDate: makeEndBase,
|
|
16039
|
+
isBlocked: isBlockedEnd,
|
|
16040
|
+
inputRef: endRef,
|
|
16041
|
+
containerRef: endRef,
|
|
16042
|
+
onFocus: () => {
|
|
16043
|
+
openerRef.current = endRef.current;
|
|
16044
|
+
},
|
|
16045
|
+
});
|
|
16046
|
+
const combinedRef = React.useCallback((node) => {
|
|
16047
|
+
startRef.current =
|
|
16048
|
+
node;
|
|
16049
|
+
if (typeof ref === 'function')
|
|
16050
|
+
ref(node);
|
|
16051
|
+
else if (ref)
|
|
16052
|
+
ref.current = node;
|
|
16053
|
+
}, [ref]);
|
|
16054
|
+
const handlePopoverClose = React.useCallback(() => setOpen(false), [setOpen]);
|
|
16055
|
+
const startField = startName ?? (name ? `${name}[start]` : undefined);
|
|
16056
|
+
const endField = endName ?? (name ? `${name}[end]` : undefined);
|
|
16057
|
+
const hiddenInputs = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [startField && (jsxRuntime.jsx("input", { type: "hidden", name: startField, form: form, value: isoDay(start) })), endField && (jsxRuntime.jsx("input", { type: "hidden", name: endField, form: form, value: isoDay(end) }))] }));
|
|
16058
|
+
const separator = (jsxRuntime.jsx("span", { className: separatorClass, "aria-hidden": "true", children: t.rangeSeparator }));
|
|
16059
|
+
// Native mobile path: two <input type="date">s, held to the same rules as
|
|
16060
|
+
// typing. A change the rules refuse leaves the input showing the range.
|
|
16061
|
+
if (useNative) {
|
|
16062
|
+
const nativeChange = (commit, isBlocked) => (e) => {
|
|
16063
|
+
// An empty value is the end cleared. A date input gives nothing else
|
|
16064
|
+
// that doesn't read as a date.
|
|
16065
|
+
const parsed = fromIsoValue(e.target.value, 'day');
|
|
16066
|
+
if (!parsed)
|
|
16067
|
+
commit(null);
|
|
16068
|
+
else if (isWithin(parsed, lowerBound, max) && !isBlocked(parsed)) {
|
|
16069
|
+
commit(parsed);
|
|
16070
|
+
}
|
|
16071
|
+
};
|
|
16072
|
+
const minIso = min ? isoDay(lowerBound) : undefined;
|
|
16073
|
+
const maxIso = max ? isoDay(max) : undefined;
|
|
16074
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: onFocus, onFocusCapture: onFocusCapture, children: [jsxRuntime.jsxs("div", { className: fieldClass, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "date", id: id, "aria-label": t.rangeStart, className: inputClass, value: isoDay(start), onChange: nativeChange(commitStart, isBlockedDay), min: minIso, max: maxIso, disabled: disabled, readOnly: readOnly, required: required }), separator, jsxRuntime.jsx("input", { ref: endRef, type: "date", id: id ? `${id}-end` : undefined, "aria-label": t.rangeEnd, className: inputClass, value: isoDay(end), onChange: nativeChange(commitEnd, isBlockedEnd), min: start ? isoDay(start) : minIso, max: maxIso, disabled: disabled, readOnly: readOnly, required: required })] }), hiddenInputs] }));
|
|
16075
|
+
}
|
|
16076
|
+
const calendar = (jsxRuntime.jsx(Calendar, { range: range, onRangeSelect: handleRangeSelect, allowDisabledInRange: allowDisabledInRange, focusedDate: focusedDate, onFocusedDateChange: setFocusedDate, min: min, max: max, shouldDisableDate: shouldDisableDate, unselectableDates: unselectableDates, firstDayOfWeek: firstDayOfWeek, locale: locale, dayNames: dayNames, monthNames: monthNames, nearbyMonthDays: nearbyMonthDays, color: color, size: size,
|
|
16077
|
+
// The popover panel takes `popoverId`, so the calendar inside it takes
|
|
16078
|
+
// one of its own. Inline there is no panel.
|
|
16079
|
+
id: inline ? popoverId : `${popoverId}-cal`, autoFocusCell: open, labels: labels }));
|
|
16080
|
+
if (inline) {
|
|
16081
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: onFocus, onFocusCapture: onFocusCapture, children: [calendar, hiddenInputs] }));
|
|
16082
|
+
}
|
|
16083
|
+
const canOpen = !!popover && !disabled && !readOnly;
|
|
16084
|
+
const inputReadOnly = !!readOnly || !editable;
|
|
16085
|
+
// The field reads as one text box, so a press on it that misses both
|
|
16086
|
+
// inputs, on its padding, the gap or the separator, goes to the nearer
|
|
16087
|
+
// input and does what a press on that input does.
|
|
16088
|
+
const handleFieldMouseDown = (e) => {
|
|
16089
|
+
const startInput = startRef.current;
|
|
16090
|
+
const endInput = endRef.current;
|
|
16091
|
+
if (disabled || e.button !== 0)
|
|
16092
|
+
return;
|
|
16093
|
+
if (e.target === startInput || e.target === endInput)
|
|
16094
|
+
return;
|
|
16095
|
+
e.preventDefault();
|
|
16096
|
+
const middle = (startInput.getBoundingClientRect().right +
|
|
16097
|
+
endInput.getBoundingClientRect().left) /
|
|
16098
|
+
2;
|
|
16099
|
+
(e.clientX < middle ? startInput : endInput).focus();
|
|
16100
|
+
if (openOnFocus && canOpen)
|
|
16101
|
+
requestOpen(true);
|
|
16102
|
+
};
|
|
16103
|
+
const comboboxProps = {
|
|
16104
|
+
type: 'text',
|
|
16105
|
+
role: 'combobox',
|
|
16106
|
+
'aria-haspopup': 'dialog',
|
|
16107
|
+
'aria-expanded': open,
|
|
16108
|
+
'aria-controls': popoverId,
|
|
16109
|
+
autoComplete: 'off',
|
|
16110
|
+
className: inputClass,
|
|
16111
|
+
placeholder,
|
|
16112
|
+
disabled,
|
|
16113
|
+
readOnly: inputReadOnly,
|
|
16114
|
+
required,
|
|
16115
|
+
};
|
|
16116
|
+
return (jsxRuntime.jsxs("div", { ...cleanRest, ref: containerRef, role: "group", className: classNames(containerClass, bulmaHelperClasses, className), onFocus: handleFocus, onFocusCapture: handleFocusCapture, children: [jsxRuntime.jsxs("div", { className: fieldClass, onMouseDown: handleFieldMouseDown, children: [jsxRuntime.jsx("input", { ...comboboxProps, ref: combinedRef, id: id, "aria-label": t.rangeStart, value: startText, ...startHandlers }), separator, jsxRuntime.jsx("input", { ...comboboxProps, ref: endRef, id: id ? `${id}-end` : undefined, "aria-label": t.rangeEnd, value: endText, ...endHandlers })] }), popover && triggerIcon && (jsxRuntime.jsx("button", { type: "button", className: triggerClass,
|
|
16117
|
+
// Disabled whenever the popover can't open, so a click can.
|
|
16118
|
+
onClick: () => setOpen(!open), disabled: !canOpen, "aria-label": t.chooseDateRange, "aria-haspopup": "dialog", "aria-controls": popoverId, "aria-expanded": open, tabIndex: canOpen ? 0 : -1, children: jsxRuntime.jsx(Icon, { name: triggerIconName, size: size }) })), popover && (jsxRuntime.jsx(PickerPopover, { isOpen: open, onClose: handlePopoverClose, anchorRef: containerRef, position: position, appendToBody: appendToBody, ariaLabel: t.chooseDateRange, id: popoverId, restoreFocusRef: restoreFocusRef, initialFocusSelector: CALENDAR_FOCUSED_CELL, children: calendar })), hiddenInputs] }));
|
|
16119
|
+
});
|
|
16120
|
+
DateRangeInputBase.displayName = 'DateRangeInputBase';
|
|
16121
|
+
|
|
16122
|
+
/**
|
|
16123
|
+
* The `DateRangeInput` component is a form input for a start and end date, picked from one popover calendar or typed into two segmented inputs.
|
|
16124
|
+
*
|
|
16125
|
+
* @function
|
|
16126
|
+
* @param {DateRangeInputProps} props - Props for the DateRangeInput.
|
|
16127
|
+
* @returns {JSX.Element}
|
|
16128
|
+
*
|
|
16129
|
+
* @example
|
|
16130
|
+
* <DateRangeInput label="Stay" name="stay" min={new Date()} />
|
|
16131
|
+
*/
|
|
16132
|
+
const DateRangeInput = React.forwardRef((props, ref) => {
|
|
16133
|
+
const { label, labelSize, labelProps, horizontal, iconLeft, iconRight, iconLeftName = 'calendar', iconRightName, iconLeftSize, iconRightSize, hasIconsLeft, hasIconsRight, isLoading, isExpanded, controlSize, message, messageColor, fieldClassName, controlClassName, ...baseProps } = props;
|
|
16134
|
+
const insideField = useInsideField();
|
|
16135
|
+
const insideControl = useInsideControl();
|
|
16136
|
+
const ownField = rendersOwnField('DateRangeInput', {
|
|
16137
|
+
insideField,
|
|
16138
|
+
insideControl,
|
|
16139
|
+
label,
|
|
16140
|
+
message,
|
|
16141
|
+
horizontal,
|
|
16142
|
+
fieldClassName,
|
|
16143
|
+
});
|
|
16144
|
+
// Inline mode renders a bare calendar with no input, so the Control's
|
|
16145
|
+
// icon containers have nothing to anchor to, and it renders no Control
|
|
16146
|
+
// inside one or out. The left icon is checked as the caller passed it:
|
|
16147
|
+
// the default glyph is this component's own choice, not a prop the
|
|
16148
|
+
// caller set, so it goes in only for the advice.
|
|
16149
|
+
const ownControl = rendersOwnControl('DateRangeInput', {
|
|
16150
|
+
insideControl,
|
|
16151
|
+
inline: baseProps.inline,
|
|
16152
|
+
isLoading,
|
|
16153
|
+
iconLeft,
|
|
16154
|
+
iconLeftName: props.iconLeftName,
|
|
16155
|
+
defaultIconLeftName: iconLeftName,
|
|
16156
|
+
iconLeftSize,
|
|
16157
|
+
iconRight,
|
|
16158
|
+
iconRightName,
|
|
16159
|
+
iconRightSize,
|
|
16160
|
+
hasIconsLeft,
|
|
16161
|
+
hasIconsRight,
|
|
16162
|
+
isExpanded,
|
|
16163
|
+
controlSize,
|
|
16164
|
+
controlClassName,
|
|
16165
|
+
});
|
|
16166
|
+
const { ariaLabelledBy, fieldLabelProps } = useAutoLabelledBy({
|
|
16167
|
+
label,
|
|
16168
|
+
labelProps,
|
|
16169
|
+
rendersLabel: ownField,
|
|
16170
|
+
});
|
|
16171
|
+
const helpClass = usePrefixedClassNames('help', {
|
|
16172
|
+
[`is-${messageColor}`]: !!messageColor,
|
|
16173
|
+
});
|
|
16174
|
+
let content = (jsxRuntime.jsx(DateRangeInputBase, { ref: ref, "aria-labelledby": ariaLabelledBy, ...baseProps }));
|
|
16175
|
+
if (ownControl) {
|
|
16176
|
+
content = (jsxRuntime.jsx(Control, { iconLeft: iconLeft, iconRight: iconRight, iconLeftName: iconLeftName, iconRightName: iconRightName, iconLeftSize: iconLeftSize, iconRightSize: iconRightSize, hasIconsLeft: hasIconsLeft || !!iconLeftName, hasIconsRight: hasIconsRight, isLoading: isLoading, isExpanded: isExpanded, size: controlSize, className: controlClassName, children: content }));
|
|
16177
|
+
}
|
|
16178
|
+
const messageEl = message ? jsxRuntime.jsx("p", { className: helpClass, children: message }) : null;
|
|
16179
|
+
if (ownField) {
|
|
16180
|
+
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [content, messageEl] }));
|
|
16181
|
+
}
|
|
16182
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [content, messageEl] }));
|
|
16183
|
+
});
|
|
16184
|
+
DateRangeInput.displayName = 'DateRangeInput';
|
|
16185
|
+
|
|
15469
16186
|
/**
|
|
15470
16187
|
* The `Cell` component provides a single Bulma grid cell for use inside the [`Grid`](./grid.md) component.
|
|
15471
16188
|
*
|
|
@@ -16872,6 +17589,8 @@ exports.Content = Content;
|
|
|
16872
17589
|
exports.Control = Control;
|
|
16873
17590
|
exports.DateInput = DateInput;
|
|
16874
17591
|
exports.DateInputBase = DateInputBase;
|
|
17592
|
+
exports.DateRangeInput = DateRangeInput;
|
|
17593
|
+
exports.DateRangeInputBase = DateRangeInputBase;
|
|
16875
17594
|
exports.DateTimeInput = DateTimeInput;
|
|
16876
17595
|
exports.DateTimeInputBase = DateTimeInputBase;
|
|
16877
17596
|
exports.Delete = Delete;
|