@allxsmith/bestax-bulma 5.27.0 → 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 +75 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +115 -17516
- package/dist/index.esm.js.map +1 -1
- package/dist/types/elements/Icon.d.ts +10 -7
- package/dist/types/form/DateInputBase.d.ts +3 -4
- package/dist/types/form/DateTimeInputBase.d.ts +3 -4
- package/dist/types/form/File.d.ts +3 -3
- package/dist/types-cjs/elements/Icon.d.ts +10 -7
- package/dist/types-cjs/form/DateInputBase.d.ts +3 -4
- package/dist/types-cjs/form/DateTimeInputBase.d.ts +3 -4
- package/dist/types-cjs/form/File.d.ts +3 -3
- 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/form/_dateinput.scss +37 -6
- package/src/scss/form/_datetimeinput.scss +7 -2
- package/src/scss/form/_picker-popover.scss +1 -4
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { useMemo, useState, useRef, useCallback, useLayoutEffect, useEffect } from 'react';
|
|
3
|
+
import { isWithin } from './dateUtils.js';
|
|
4
|
+
import { buildSegmentMap, segmentIndexAtCaret, setAmPm, setSegmentValue, incrementSegmentValue } from './segmentMap.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Single-character literals that, when typed, jump the caret to the next
|
|
8
|
+
* editable segment — the slash / colon / dash / dot / space separators that
|
|
9
|
+
* appear between date and time tokens. Lets a user type `12/25/2026` or
|
|
10
|
+
* `13:45` straight through, skipping over the format's punctuation.
|
|
11
|
+
*/
|
|
12
|
+
const SEPARATOR_RE = /[/:\-.\s]/;
|
|
13
|
+
/**
|
|
14
|
+
* Segmented manual keyboard entry for a date / time picker input. Computes a
|
|
15
|
+
* segment map from the token format, tracks the active segment, and returns
|
|
16
|
+
* the full set of `<input>` event handlers — segment-mutating keys (arrows,
|
|
17
|
+
* digits, AM/PM, separators) are handled here, while popover open/close and
|
|
18
|
+
* free-form parse-on-blur/Enter are driven through the host-supplied
|
|
19
|
+
* `setOpen` / `tryParse` / `commitValue` callbacks. When the format is an
|
|
20
|
+
* `Intl` options object or contains variable-width tokens, the segment map is
|
|
21
|
+
* null and the input transparently falls back to free-form text entry.
|
|
22
|
+
*/
|
|
23
|
+
function useSegmentedEntry(params) {
|
|
24
|
+
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;
|
|
25
|
+
const segmentMap = useMemo(() => {
|
|
26
|
+
const map = typeof format === 'string' ? buildSegmentMap(format) : null;
|
|
27
|
+
if (!map || !skipKinds)
|
|
28
|
+
return map;
|
|
29
|
+
const editable = map.editable.filter(i => !skipKinds.includes(map.segments[i].kind));
|
|
30
|
+
return editable.length > 0 ? { ...map, editable } : null;
|
|
31
|
+
}, [format, skipKinds]);
|
|
32
|
+
const [activeSegmentIdx, setActiveSegmentIdx] = useState(null);
|
|
33
|
+
// Buffer of digits typed within the current segment, cleared on segment
|
|
34
|
+
// change. Kept in a ref because key handlers read+update synchronously
|
|
35
|
+
// within the same event tick.
|
|
36
|
+
const typedDigitsRef = useRef('');
|
|
37
|
+
// The text focus seeded an empty picker with, until the user edits it or
|
|
38
|
+
// leaves.
|
|
39
|
+
const seedRef = useRef(null);
|
|
40
|
+
// Whether the text is still what the picker put there, so nothing in it was
|
|
41
|
+
// typed: the value's display, or an empty picker's seed until an edit drops
|
|
42
|
+
// it. Against a value it compares text alone, so typing that ends back on
|
|
43
|
+
// the display counts as untouched.
|
|
44
|
+
const isUntouched = useCallback((s) => value
|
|
45
|
+
? s === formatFn(value, format, locale)
|
|
46
|
+
: seedRef.current !== null && s === seedRef.current, [value, formatFn, format, locale]);
|
|
47
|
+
// A seed the popover never turned into a value closes with it, wherever
|
|
48
|
+
// focus goes, so a dismissed empty field stays empty.
|
|
49
|
+
//
|
|
50
|
+
// As the popover closes, its focus trap hands focus back to the input. That
|
|
51
|
+
// focus is not the user arriving: it must not open the popover again under
|
|
52
|
+
// `openOnFocus`, or seed an empty picker. `closingRef` is set in the layout
|
|
53
|
+
// phase of the commit that closes it and cleared by that commit's effects,
|
|
54
|
+
// and React runs a commit's effect cleanups, the trap's among them, before
|
|
55
|
+
// any of its effects, so a focus in between is the one handed back.
|
|
56
|
+
const closingRef = useRef(false);
|
|
57
|
+
const wasOpenRef = useRef(isOpen);
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
if (wasOpenRef.current && !isOpen) {
|
|
60
|
+
closingRef.current = true;
|
|
61
|
+
if (!value && isUntouched(text))
|
|
62
|
+
setText('');
|
|
63
|
+
seedRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
wasOpenRef.current = isOpen;
|
|
66
|
+
}, [isOpen, value, isUntouched, text, setText]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
closingRef.current = false;
|
|
69
|
+
}, [isOpen]);
|
|
70
|
+
// Segment mode is unavailable for disabled / read-only / non-editable
|
|
71
|
+
// pickers and for Intl-options / variable-width formats (segmentMap null).
|
|
72
|
+
const segmentEditable = !!segmentMap && !!editable && !disabled && !readOnly;
|
|
73
|
+
// The Date we edit when the user starts without a current value.
|
|
74
|
+
const segmentBaseDate = useCallback(() => value ?? makeBaseDate(), [value, makeBaseDate]);
|
|
75
|
+
// Single gate for every manual-entry commit: min/max bounds plus the
|
|
76
|
+
// host's blocking predicate.
|
|
77
|
+
const isAllowed = useCallback((d) => isWithin(d, min, max) && !isBlocked?.(d), [min, max, isBlocked]);
|
|
78
|
+
// Apply a segment-driven date update: commit through the host pipeline (so
|
|
79
|
+
// onChange / controlled mode keep working) and re-render the text so the
|
|
80
|
+
// active selection range stays valid.
|
|
81
|
+
const applyDateFromSegment = useCallback((next) => {
|
|
82
|
+
if (!isAllowed(next))
|
|
83
|
+
return;
|
|
84
|
+
commitValue(next);
|
|
85
|
+
setText(formatFn(next, format, locale));
|
|
86
|
+
}, [commitValue, isAllowed, formatFn, format, locale, setText]);
|
|
87
|
+
// After every segment-driven change, restore the system selection on the
|
|
88
|
+
// active segment's char range. useLayoutEffect runs synchronously before
|
|
89
|
+
// paint so the user never sees a stray caret position. We deliberately do
|
|
90
|
+
// NOT gate on document.activeElement: when openOnFocus is true the focus
|
|
91
|
+
// trap moves focus into the popover, but setSelectionRange on an unfocused
|
|
92
|
+
// input is well-defined and the selection becomes visible on refocus.
|
|
93
|
+
useLayoutEffect(() => {
|
|
94
|
+
if (activeSegmentIdx === null || !segmentMap || !inputRef.current)
|
|
95
|
+
return;
|
|
96
|
+
const seg = segmentMap.segments[activeSegmentIdx];
|
|
97
|
+
if (!seg)
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
inputRef.current.setSelectionRange(seg.start, seg.end);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Some read-only inputs reject setSelectionRange; harmless.
|
|
104
|
+
}
|
|
105
|
+
}, [activeSegmentIdx, segmentMap, text, inputRef]);
|
|
106
|
+
const moveSegment = useCallback((delta) => {
|
|
107
|
+
if (!segmentMap || activeSegmentIdx === null)
|
|
108
|
+
return;
|
|
109
|
+
const pos = segmentMap.editable.indexOf(activeSegmentIdx);
|
|
110
|
+
if (pos < 0)
|
|
111
|
+
return;
|
|
112
|
+
const nextPos = Math.max(0, Math.min(segmentMap.editable.length - 1, pos + delta));
|
|
113
|
+
const nextIdx = segmentMap.editable[nextPos];
|
|
114
|
+
if (nextIdx !== activeSegmentIdx) {
|
|
115
|
+
typedDigitsRef.current = '';
|
|
116
|
+
setActiveSegmentIdx(nextIdx);
|
|
117
|
+
}
|
|
118
|
+
}, [segmentMap, activeSegmentIdx]);
|
|
119
|
+
const handleChange = useCallback((e) => {
|
|
120
|
+
// In segment mode the text is managed programmatically (every edit
|
|
121
|
+
// re-formats from the canonical Date). Browser-issued input events
|
|
122
|
+
// would otherwise clobber it. The free-form path runs only when
|
|
123
|
+
// segment mode is inactive.
|
|
124
|
+
if (activeSegmentIdx !== null && segmentEditable)
|
|
125
|
+
return;
|
|
126
|
+
seedRef.current = null;
|
|
127
|
+
setText(e.target.value);
|
|
128
|
+
}, [activeSegmentIdx, segmentEditable, setText]);
|
|
129
|
+
const handleBlur = useCallback((e) => {
|
|
130
|
+
// The popover opens on focus by default, and the focus trap moves focus
|
|
131
|
+
// into the popover panel — that fires this blur even though the user is
|
|
132
|
+
// still in the same picker. Only tear down segment state and re-parse
|
|
133
|
+
// when focus has truly left the picker's container.
|
|
134
|
+
const nextFocus = e.relatedTarget;
|
|
135
|
+
const stayingInPicker = nextFocus !== null &&
|
|
136
|
+
containerRef.current !== null &&
|
|
137
|
+
containerRef.current.contains(nextFocus);
|
|
138
|
+
if (stayingInPicker) {
|
|
139
|
+
onBlur?.(e);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
setActiveSegmentIdx(null);
|
|
143
|
+
typedDigitsRef.current = '';
|
|
144
|
+
// Leaving commits only what was typed. Re-parsing the value's display
|
|
145
|
+
// could drop what the format leaves out, such as seconds, and a seed
|
|
146
|
+
// nobody typed over is not the user's date.
|
|
147
|
+
const untouched = isUntouched(text);
|
|
148
|
+
// An open popover keeps an unused seed until it closes, so one portaled
|
|
149
|
+
// outside the picker, whose focus reads as leaving, keeps it as one
|
|
150
|
+
// inside does.
|
|
151
|
+
if (!isOpen)
|
|
152
|
+
seedRef.current = null;
|
|
153
|
+
if (untouched) {
|
|
154
|
+
if (!value && !isOpen)
|
|
155
|
+
setText('');
|
|
156
|
+
onBlur?.(e);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const parsed = tryParse(text);
|
|
160
|
+
if (parsed && isAllowed(parsed)) {
|
|
161
|
+
commitValue(parsed);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
setText(value ? formatFn(value, format, locale) : '');
|
|
165
|
+
}
|
|
166
|
+
onBlur?.(e);
|
|
167
|
+
}, [
|
|
168
|
+
containerRef,
|
|
169
|
+
isUntouched,
|
|
170
|
+
isOpen,
|
|
171
|
+
tryParse,
|
|
172
|
+
text,
|
|
173
|
+
isAllowed,
|
|
174
|
+
commitValue,
|
|
175
|
+
value,
|
|
176
|
+
formatFn,
|
|
177
|
+
format,
|
|
178
|
+
locale,
|
|
179
|
+
setText,
|
|
180
|
+
onBlur,
|
|
181
|
+
]);
|
|
182
|
+
const handleFocus = useCallback((e) => {
|
|
183
|
+
const handedBack = closingRef.current;
|
|
184
|
+
if (!handedBack && openOnFocus && popover && !disabled && !readOnly) {
|
|
185
|
+
setOpen(true);
|
|
186
|
+
}
|
|
187
|
+
// Enter segment mode: prime an initial value (so editing works even when
|
|
188
|
+
// empty), seed the text, and highlight the first editable segment. The
|
|
189
|
+
// segment base doesn't need the seed, so focus handed back skips it.
|
|
190
|
+
if (segmentEditable && segmentMap) {
|
|
191
|
+
if (!value && !handedBack) {
|
|
192
|
+
const seed = formatFn(makeBaseDate(), format, locale);
|
|
193
|
+
seedRef.current = seed;
|
|
194
|
+
setText(seed);
|
|
195
|
+
}
|
|
196
|
+
typedDigitsRef.current = '';
|
|
197
|
+
setActiveSegmentIdx(segmentMap.editable[0]);
|
|
198
|
+
}
|
|
199
|
+
onFocus?.(e);
|
|
200
|
+
}, [
|
|
201
|
+
openOnFocus,
|
|
202
|
+
popover,
|
|
203
|
+
disabled,
|
|
204
|
+
readOnly,
|
|
205
|
+
setOpen,
|
|
206
|
+
segmentEditable,
|
|
207
|
+
segmentMap,
|
|
208
|
+
value,
|
|
209
|
+
makeBaseDate,
|
|
210
|
+
formatFn,
|
|
211
|
+
format,
|
|
212
|
+
locale,
|
|
213
|
+
setText,
|
|
214
|
+
onFocus,
|
|
215
|
+
]);
|
|
216
|
+
const handleClick = useCallback((e) => {
|
|
217
|
+
// Clicking the field opens the popover only when `openOnFocus` is on —
|
|
218
|
+
// a click is just a focus. With `openOnFocus={false}` (manual-entry
|
|
219
|
+
// mode) the click positions the caret for typing and the popover is
|
|
220
|
+
// opened explicitly via the right launcher button (or Alt+ArrowDown).
|
|
221
|
+
if (openOnFocus && popover && !disabled && !readOnly)
|
|
222
|
+
setOpen(true);
|
|
223
|
+
if (segmentEditable && segmentMap && inputRef.current) {
|
|
224
|
+
const caret = inputRef.current.selectionStart ?? 0;
|
|
225
|
+
const idx = segmentIndexAtCaret(segmentMap, caret);
|
|
226
|
+
if (idx !== null && idx !== activeSegmentIdx) {
|
|
227
|
+
typedDigitsRef.current = '';
|
|
228
|
+
setActiveSegmentIdx(idx);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
onClick?.(e);
|
|
232
|
+
}, [
|
|
233
|
+
openOnFocus,
|
|
234
|
+
popover,
|
|
235
|
+
disabled,
|
|
236
|
+
readOnly,
|
|
237
|
+
setOpen,
|
|
238
|
+
segmentEditable,
|
|
239
|
+
segmentMap,
|
|
240
|
+
inputRef,
|
|
241
|
+
activeSegmentIdx,
|
|
242
|
+
onClick,
|
|
243
|
+
]);
|
|
244
|
+
const inSegmentMode = segmentEditable && segmentMap !== null && activeSegmentIdx !== null;
|
|
245
|
+
const handleKeyDown = useCallback((e) => {
|
|
246
|
+
// Segment-mode key routing comes first. When segment mode isn't engaged
|
|
247
|
+
// (not editable, no map, or no active segment — e.g. after a typed-text
|
|
248
|
+
// fallback), unhandled keys fall through to the popover / free-form
|
|
249
|
+
// handling below.
|
|
250
|
+
if (inSegmentMode && segmentMap && activeSegmentIdx !== null) {
|
|
251
|
+
const seg = segmentMap.segments[activeSegmentIdx];
|
|
252
|
+
const base = segmentBaseDate();
|
|
253
|
+
const isPm = base.getHours() >= 12;
|
|
254
|
+
switch (e.key) {
|
|
255
|
+
case 'ArrowUp':
|
|
256
|
+
case 'ArrowDown':
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
// The plain arrows step the segment, so the popover opens on
|
|
259
|
+
// Alt+ArrowDown and closes on Alt+ArrowUp, as a combobox's does,
|
|
260
|
+
// and neither steps it.
|
|
261
|
+
if (e.altKey) {
|
|
262
|
+
if (e.key === 'ArrowUp') {
|
|
263
|
+
if (isOpen)
|
|
264
|
+
setOpen(false);
|
|
265
|
+
}
|
|
266
|
+
else if (!isOpen && popover) {
|
|
267
|
+
setOpen(true);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
typedDigitsRef.current = '';
|
|
272
|
+
applyDateFromSegment(incrementSegmentValue(seg, base, e.key === 'ArrowUp' ? 1 : -1, isPm));
|
|
273
|
+
return;
|
|
274
|
+
case 'ArrowRight':
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
moveSegment(1);
|
|
277
|
+
return;
|
|
278
|
+
case 'ArrowLeft':
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
moveSegment(-1);
|
|
281
|
+
return;
|
|
282
|
+
case 'Backspace':
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
if (typedDigitsRef.current.length > 0) {
|
|
285
|
+
typedDigitsRef.current = '';
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
moveSegment(-1);
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
case 'Escape':
|
|
292
|
+
if (isOpen) {
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
setOpen(false);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
case 'Enter':
|
|
298
|
+
e.preventDefault();
|
|
299
|
+
if (closeOnSelect)
|
|
300
|
+
setOpen(false);
|
|
301
|
+
return;
|
|
302
|
+
case 'Tab':
|
|
303
|
+
setActiveSegmentIdx(null);
|
|
304
|
+
typedDigitsRef.current = '';
|
|
305
|
+
return;
|
|
306
|
+
default:
|
|
307
|
+
// AM/PM keys.
|
|
308
|
+
if (seg.kind === 'ampm') {
|
|
309
|
+
if (e.key === 'a' || e.key === 'A') {
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
typedDigitsRef.current = '';
|
|
312
|
+
applyDateFromSegment(setAmPm(base, false));
|
|
313
|
+
moveSegment(1);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (e.key === 'p' || e.key === 'P') {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
typedDigitsRef.current = '';
|
|
319
|
+
applyDateFromSegment(setAmPm(base, true));
|
|
320
|
+
moveSegment(1);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Typing a format separator (/, :, -, ., space) jumps to the next
|
|
325
|
+
// segment, skipping over the literal punctuation.
|
|
326
|
+
if (e.key.length === 1 && SEPARATOR_RE.test(e.key)) {
|
|
327
|
+
e.preventDefault();
|
|
328
|
+
typedDigitsRef.current = '';
|
|
329
|
+
moveSegment(1);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// Digit entry for numeric segments.
|
|
333
|
+
if (/^[0-9]$/.test(e.key) && seg.kind !== 'ampm') {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
const nextBuffer = typedDigitsRef.current + e.key;
|
|
336
|
+
typedDigitsRef.current = nextBuffer;
|
|
337
|
+
const { date, advance } = setSegmentValue(seg, base, nextBuffer, isPm);
|
|
338
|
+
if (!advance && isUnfinishedBlocked?.(date))
|
|
339
|
+
return;
|
|
340
|
+
applyDateFromSegment(date);
|
|
341
|
+
if (advance) {
|
|
342
|
+
typedDigitsRef.current = '';
|
|
343
|
+
moveSegment(1);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ----- free-form / popover key handling -----
|
|
350
|
+
// With no segment to step, ArrowDown opens the popover with or without
|
|
351
|
+
// Alt, unless the field is read-only or disabled, as focus and the
|
|
352
|
+
// launcher don't open it then either. Alt+ArrowUp closes it.
|
|
353
|
+
if (e.key === 'ArrowDown' &&
|
|
354
|
+
!isOpen &&
|
|
355
|
+
popover &&
|
|
356
|
+
!disabled &&
|
|
357
|
+
!readOnly) {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
setOpen(true);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (e.key === 'ArrowUp' && e.altKey && isOpen) {
|
|
363
|
+
e.preventDefault();
|
|
364
|
+
setOpen(false);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (e.key === 'Escape' && isOpen) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
setOpen(false);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (e.key === 'Enter') {
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
// Untouched text has nothing to commit, as on leaving, but Enter
|
|
375
|
+
// still confirms what the field shows.
|
|
376
|
+
if (isUntouched(text)) {
|
|
377
|
+
if (closeOnSelect)
|
|
378
|
+
setOpen(false);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
const parsed = tryParse(text);
|
|
382
|
+
if (parsed && isAllowed(parsed)) {
|
|
383
|
+
commitValue(parsed);
|
|
384
|
+
if (closeOnSelect)
|
|
385
|
+
setOpen(false);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
onKeyDown?.(e);
|
|
390
|
+
}, [
|
|
391
|
+
inSegmentMode,
|
|
392
|
+
segmentMap,
|
|
393
|
+
activeSegmentIdx,
|
|
394
|
+
segmentBaseDate,
|
|
395
|
+
applyDateFromSegment,
|
|
396
|
+
isUnfinishedBlocked,
|
|
397
|
+
moveSegment,
|
|
398
|
+
isOpen,
|
|
399
|
+
setOpen,
|
|
400
|
+
closeOnSelect,
|
|
401
|
+
popover,
|
|
402
|
+
disabled,
|
|
403
|
+
readOnly,
|
|
404
|
+
isUntouched,
|
|
405
|
+
tryParse,
|
|
406
|
+
text,
|
|
407
|
+
isAllowed,
|
|
408
|
+
commitValue,
|
|
409
|
+
onKeyDown,
|
|
410
|
+
]);
|
|
411
|
+
return {
|
|
412
|
+
segmentMap,
|
|
413
|
+
activeSegmentIdx,
|
|
414
|
+
segmentEditable,
|
|
415
|
+
inSegmentMode,
|
|
416
|
+
inputHandlers: {
|
|
417
|
+
onChange: handleChange,
|
|
418
|
+
onFocus: handleFocus,
|
|
419
|
+
onClick: handleClick,
|
|
420
|
+
onKeyDown: handleKeyDown,
|
|
421
|
+
onBlur: handleBlur,
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export { useSegmentedEntry };
|
|
427
|
+
//# sourceMappingURL=useSegmentedEntry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSegmentedEntry.js","sources":["../../../../src/form/_pickerInternals/useSegmentedEntry.ts"],"sourcesContent":["import React, {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { DateFormatOption } from './formatters';\nimport { isWithin } from './dateUtils';\nimport {\n buildSegmentMap,\n incrementSegmentValue,\n setSegmentValue,\n setAmPm,\n segmentIndexAtCaret,\n SegmentKind,\n SegmentMap,\n} from './segmentMap';\n\n/**\n * Single-character literals that, when typed, jump the caret to the next\n * editable segment — the slash / colon / dash / dot / space separators that\n * appear between date and time tokens. Lets a user type `12/25/2026` or\n * `13:45` straight through, skipping over the format's punctuation.\n */\nconst SEPARATOR_RE = /[/:\\-.\\s]/;\n\nexport interface UseSegmentedEntryParams {\n /** Resolved format actually used to render (the host's default format). */\n format: DateFormatOption;\n /**\n * Segment kinds the caret never stops on: they still render, but arrows,\n * digits and separators pass over them. A month picker skips `'day'` so a\n * format such as `'YYYY-MM-DD'` edits only the year and month. Must be\n * referentially stable. A format left with no editable segment falls back\n * to free-form entry.\n */\n skipKinds?: readonly SegmentKind[];\n /** Current canonical value (controlled or internal). */\n value: Date | null;\n /** Commit a new Date through the host's onChange / internal pipeline. */\n commitValue: (next: Date | null) => void;\n /** Host formatter: formatDate | formatTime | formatDateTime. */\n formatFn: (\n d: Date,\n fmt: DateFormatOption | undefined,\n locale?: string\n ) => string;\n /** Host parser used by the free-form fallback (parseDate / parseTime / custom). */\n tryParse: (s: string) => Date | null;\n /** Host's displayed-text state and setter (the hook drives it during edits). */\n text: string;\n setText: (s: string) => void;\n /**\n * Seed for an empty value when the user starts typing: Time → today at noon,\n * Date → today at 00:00, DateTime → now. Should be referentially stable.\n */\n makeBaseDate: () => Date;\n locale?: string;\n min?: Date;\n max?: Date;\n /**\n * Host-supplied blocking predicate (composed from shouldDisableDate /\n * unselectableDates / unselectableTimes). Checked alongside min/max on\n * every manual-entry commit; return true to reject the candidate value.\n */\n isBlocked?: (d: Date) => boolean;\n /**\n * Rejects a candidate only while the digits typed into its segment leave\n * it unfinished, such as the first digits of a year. The digit stays in\n * the segment and the next one tries again, so a value passing through a\n * candidate it can't commit on the way to one it can costs nothing. A\n * finished segment, an arrow step and parsed text answer to `isBlocked`\n * alone.\n */\n isUnfinishedBlocked?: (d: Date) => boolean;\n disabled?: boolean;\n readOnly?: boolean;\n /** Allow segmented typing. When false, segment mode never engages. */\n editable?: boolean;\n /** Whether a popover exists; gates open-on-focus / click / Alt+ArrowDown. */\n popover?: boolean;\n openOnFocus?: boolean;\n closeOnSelect?: boolean;\n isOpen: boolean;\n /**\n * The host's open-state setter. The hook asks it to open on focus, click\n * or Alt+ArrowDown, but not on the focus the popover's focus trap hands\n * back to the input as it closes, which is not the user arriving.\n */\n setOpen: (next: boolean) => void;\n inputRef: React.RefObject<HTMLInputElement | null>;\n containerRef: React.RefObject<HTMLElement | null>;\n onFocus?: React.FocusEventHandler<HTMLInputElement>;\n onClick?: React.MouseEventHandler<HTMLInputElement>;\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n onBlur?: React.FocusEventHandler<HTMLInputElement>;\n}\n\nexport interface UseSegmentedEntryResult {\n segmentMap: SegmentMap | null;\n activeSegmentIdx: number | null;\n /** True when segment typing is available (map + editable + not disabled/readOnly). */\n segmentEditable: boolean;\n /** True when a segment is actively selected. */\n inSegmentMode: boolean;\n /** Spread onto the combobox `<input>`. */\n inputHandlers: {\n onChange: React.ChangeEventHandler<HTMLInputElement>;\n onFocus: React.FocusEventHandler<HTMLInputElement>;\n onClick: React.MouseEventHandler<HTMLInputElement>;\n onKeyDown: React.KeyboardEventHandler<HTMLInputElement>;\n onBlur: React.FocusEventHandler<HTMLInputElement>;\n };\n}\n\n/**\n * Segmented manual keyboard entry for a date / time picker input. Computes a\n * segment map from the token format, tracks the active segment, and returns\n * the full set of `<input>` event handlers — segment-mutating keys (arrows,\n * digits, AM/PM, separators) are handled here, while popover open/close and\n * free-form parse-on-blur/Enter are driven through the host-supplied\n * `setOpen` / `tryParse` / `commitValue` callbacks. When the format is an\n * `Intl` options object or contains variable-width tokens, the segment map is\n * null and the input transparently falls back to free-form text entry.\n */\nexport function useSegmentedEntry(\n params: UseSegmentedEntryParams\n): UseSegmentedEntryResult {\n const {\n format,\n skipKinds,\n value,\n commitValue,\n formatFn,\n tryParse,\n text,\n setText,\n makeBaseDate,\n locale,\n min,\n max,\n isBlocked,\n isUnfinishedBlocked,\n disabled,\n readOnly,\n editable = true,\n popover = true,\n openOnFocus = true,\n closeOnSelect = false,\n isOpen,\n setOpen,\n inputRef,\n containerRef,\n onFocus,\n onClick,\n onKeyDown,\n onBlur,\n } = params;\n\n const segmentMap: SegmentMap | null = useMemo(() => {\n const map = typeof format === 'string' ? buildSegmentMap(format) : null;\n if (!map || !skipKinds) return map;\n const editable = map.editable.filter(\n i => !skipKinds.includes(map.segments[i].kind)\n );\n return editable.length > 0 ? { ...map, editable } : null;\n }, [format, skipKinds]);\n const [activeSegmentIdx, setActiveSegmentIdx] = useState<number | null>(null);\n // Buffer of digits typed within the current segment, cleared on segment\n // change. Kept in a ref because key handlers read+update synchronously\n // within the same event tick.\n const typedDigitsRef = useRef<string>('');\n // The text focus seeded an empty picker with, until the user edits it or\n // leaves.\n const seedRef = useRef<string | null>(null);\n\n // Whether the text is still what the picker put there, so nothing in it was\n // typed: the value's display, or an empty picker's seed until an edit drops\n // it. Against a value it compares text alone, so typing that ends back on\n // the display counts as untouched.\n const isUntouched = useCallback(\n (s: string) =>\n value\n ? s === formatFn(value, format, locale)\n : seedRef.current !== null && s === seedRef.current,\n [value, formatFn, format, locale]\n );\n\n // A seed the popover never turned into a value closes with it, wherever\n // focus goes, so a dismissed empty field stays empty.\n //\n // As the popover closes, its focus trap hands focus back to the input. That\n // focus is not the user arriving: it must not open the popover again under\n // `openOnFocus`, or seed an empty picker. `closingRef` is set in the layout\n // phase of the commit that closes it and cleared by that commit's effects,\n // and React runs a commit's effect cleanups, the trap's among them, before\n // any of its effects, so a focus in between is the one handed back.\n const closingRef = useRef(false);\n const wasOpenRef = useRef(isOpen);\n useLayoutEffect(() => {\n if (wasOpenRef.current && !isOpen) {\n closingRef.current = true;\n if (!value && isUntouched(text)) setText('');\n seedRef.current = null;\n }\n wasOpenRef.current = isOpen;\n }, [isOpen, value, isUntouched, text, setText]);\n useEffect(() => {\n closingRef.current = false;\n }, [isOpen]);\n\n // Segment mode is unavailable for disabled / read-only / non-editable\n // pickers and for Intl-options / variable-width formats (segmentMap null).\n const segmentEditable = !!segmentMap && !!editable && !disabled && !readOnly;\n\n // The Date we edit when the user starts without a current value.\n const segmentBaseDate = useCallback(\n (): Date => value ?? makeBaseDate(),\n [value, makeBaseDate]\n );\n\n // Single gate for every manual-entry commit: min/max bounds plus the\n // host's blocking predicate.\n const isAllowed = useCallback(\n (d: Date) => isWithin(d, min, max) && !isBlocked?.(d),\n [min, max, isBlocked]\n );\n\n // Apply a segment-driven date update: commit through the host pipeline (so\n // onChange / controlled mode keep working) and re-render the text so the\n // active selection range stays valid.\n const applyDateFromSegment = useCallback(\n (next: Date) => {\n if (!isAllowed(next)) return;\n commitValue(next);\n setText(formatFn(next, format, locale));\n },\n [commitValue, isAllowed, formatFn, format, locale, setText]\n );\n\n // After every segment-driven change, restore the system selection on the\n // active segment's char range. useLayoutEffect runs synchronously before\n // paint so the user never sees a stray caret position. We deliberately do\n // NOT gate on document.activeElement: when openOnFocus is true the focus\n // trap moves focus into the popover, but setSelectionRange on an unfocused\n // input is well-defined and the selection becomes visible on refocus.\n useLayoutEffect(() => {\n if (activeSegmentIdx === null || !segmentMap || !inputRef.current) return;\n const seg = segmentMap.segments[activeSegmentIdx];\n if (!seg) return;\n try {\n inputRef.current.setSelectionRange(seg.start, seg.end);\n } catch {\n // Some read-only inputs reject setSelectionRange; harmless.\n }\n }, [activeSegmentIdx, segmentMap, text, inputRef]);\n\n const moveSegment = useCallback(\n (delta: 1 | -1) => {\n if (!segmentMap || activeSegmentIdx === null) return;\n const pos = segmentMap.editable.indexOf(activeSegmentIdx);\n if (pos < 0) return;\n const nextPos = Math.max(\n 0,\n Math.min(segmentMap.editable.length - 1, pos + delta)\n );\n const nextIdx = segmentMap.editable[nextPos];\n if (nextIdx !== activeSegmentIdx) {\n typedDigitsRef.current = '';\n setActiveSegmentIdx(nextIdx);\n }\n },\n [segmentMap, activeSegmentIdx]\n );\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n // In segment mode the text is managed programmatically (every edit\n // re-formats from the canonical Date). Browser-issued input events\n // would otherwise clobber it. The free-form path runs only when\n // segment mode is inactive.\n if (activeSegmentIdx !== null && segmentEditable) return;\n seedRef.current = null;\n setText(e.target.value);\n },\n [activeSegmentIdx, segmentEditable, setText]\n );\n\n const handleBlur = useCallback(\n (e: React.FocusEvent<HTMLInputElement>) => {\n // The popover opens on focus by default, and the focus trap moves focus\n // into the popover panel — that fires this blur even though the user is\n // still in the same picker. Only tear down segment state and re-parse\n // when focus has truly left the picker's container.\n const nextFocus = e.relatedTarget as HTMLElement | null;\n const stayingInPicker =\n nextFocus !== null &&\n containerRef.current !== null &&\n containerRef.current.contains(nextFocus);\n if (stayingInPicker) {\n onBlur?.(e);\n return;\n }\n setActiveSegmentIdx(null);\n typedDigitsRef.current = '';\n // Leaving commits only what was typed. Re-parsing the value's display\n // could drop what the format leaves out, such as seconds, and a seed\n // nobody typed over is not the user's date.\n const untouched = isUntouched(text);\n // An open popover keeps an unused seed until it closes, so one portaled\n // outside the picker, whose focus reads as leaving, keeps it as one\n // inside does.\n if (!isOpen) seedRef.current = null;\n if (untouched) {\n if (!value && !isOpen) setText('');\n onBlur?.(e);\n return;\n }\n const parsed = tryParse(text);\n if (parsed && isAllowed(parsed)) {\n commitValue(parsed);\n } else {\n setText(value ? formatFn(value, format, locale) : '');\n }\n onBlur?.(e);\n },\n [\n containerRef,\n isUntouched,\n isOpen,\n tryParse,\n text,\n isAllowed,\n commitValue,\n value,\n formatFn,\n format,\n locale,\n setText,\n onBlur,\n ]\n );\n\n const handleFocus = useCallback(\n (e: React.FocusEvent<HTMLInputElement>) => {\n const handedBack = closingRef.current;\n if (!handedBack && openOnFocus && popover && !disabled && !readOnly) {\n setOpen(true);\n }\n // Enter segment mode: prime an initial value (so editing works even when\n // empty), seed the text, and highlight the first editable segment. The\n // segment base doesn't need the seed, so focus handed back skips it.\n if (segmentEditable && segmentMap) {\n if (!value && !handedBack) {\n const seed = formatFn(makeBaseDate(), format, locale);\n seedRef.current = seed;\n setText(seed);\n }\n typedDigitsRef.current = '';\n setActiveSegmentIdx(segmentMap.editable[0]);\n }\n onFocus?.(e);\n },\n [\n openOnFocus,\n popover,\n disabled,\n readOnly,\n setOpen,\n segmentEditable,\n segmentMap,\n value,\n makeBaseDate,\n formatFn,\n format,\n locale,\n setText,\n onFocus,\n ]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLInputElement>) => {\n // Clicking the field opens the popover only when `openOnFocus` is on —\n // a click is just a focus. With `openOnFocus={false}` (manual-entry\n // mode) the click positions the caret for typing and the popover is\n // opened explicitly via the right launcher button (or Alt+ArrowDown).\n if (openOnFocus && popover && !disabled && !readOnly) setOpen(true);\n if (segmentEditable && segmentMap && inputRef.current) {\n const caret = inputRef.current.selectionStart ?? 0;\n const idx = segmentIndexAtCaret(segmentMap, caret);\n if (idx !== null && idx !== activeSegmentIdx) {\n typedDigitsRef.current = '';\n setActiveSegmentIdx(idx);\n }\n }\n onClick?.(e);\n },\n [\n openOnFocus,\n popover,\n disabled,\n readOnly,\n setOpen,\n segmentEditable,\n segmentMap,\n inputRef,\n activeSegmentIdx,\n onClick,\n ]\n );\n\n const inSegmentMode =\n segmentEditable && segmentMap !== null && activeSegmentIdx !== null;\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n // Segment-mode key routing comes first. When segment mode isn't engaged\n // (not editable, no map, or no active segment — e.g. after a typed-text\n // fallback), unhandled keys fall through to the popover / free-form\n // handling below.\n if (inSegmentMode && segmentMap && activeSegmentIdx !== null) {\n const seg = segmentMap.segments[activeSegmentIdx];\n const base = segmentBaseDate();\n const isPm = base.getHours() >= 12;\n switch (e.key) {\n case 'ArrowUp':\n case 'ArrowDown':\n e.preventDefault();\n // The plain arrows step the segment, so the popover opens on\n // Alt+ArrowDown and closes on Alt+ArrowUp, as a combobox's does,\n // and neither steps it.\n if (e.altKey) {\n if (e.key === 'ArrowUp') {\n if (isOpen) setOpen(false);\n } else if (!isOpen && popover) {\n setOpen(true);\n }\n return;\n }\n typedDigitsRef.current = '';\n applyDateFromSegment(\n incrementSegmentValue(\n seg,\n base,\n e.key === 'ArrowUp' ? 1 : -1,\n isPm\n )\n );\n return;\n case 'ArrowRight':\n e.preventDefault();\n moveSegment(1);\n return;\n case 'ArrowLeft':\n e.preventDefault();\n moveSegment(-1);\n return;\n case 'Backspace':\n e.preventDefault();\n if (typedDigitsRef.current.length > 0) {\n typedDigitsRef.current = '';\n } else {\n moveSegment(-1);\n }\n return;\n case 'Escape':\n if (isOpen) {\n e.preventDefault();\n setOpen(false);\n }\n return;\n case 'Enter':\n e.preventDefault();\n if (closeOnSelect) setOpen(false);\n return;\n case 'Tab':\n setActiveSegmentIdx(null);\n typedDigitsRef.current = '';\n return;\n default:\n // AM/PM keys.\n if (seg.kind === 'ampm') {\n if (e.key === 'a' || e.key === 'A') {\n e.preventDefault();\n typedDigitsRef.current = '';\n applyDateFromSegment(setAmPm(base, false));\n moveSegment(1);\n return;\n }\n if (e.key === 'p' || e.key === 'P') {\n e.preventDefault();\n typedDigitsRef.current = '';\n applyDateFromSegment(setAmPm(base, true));\n moveSegment(1);\n return;\n }\n }\n // Typing a format separator (/, :, -, ., space) jumps to the next\n // segment, skipping over the literal punctuation.\n if (e.key.length === 1 && SEPARATOR_RE.test(e.key)) {\n e.preventDefault();\n typedDigitsRef.current = '';\n moveSegment(1);\n return;\n }\n // Digit entry for numeric segments.\n if (/^[0-9]$/.test(e.key) && seg.kind !== 'ampm') {\n e.preventDefault();\n const nextBuffer = typedDigitsRef.current + e.key;\n typedDigitsRef.current = nextBuffer;\n const { date, advance } = setSegmentValue(\n seg,\n base,\n nextBuffer,\n isPm\n );\n if (!advance && isUnfinishedBlocked?.(date)) return;\n applyDateFromSegment(date);\n if (advance) {\n typedDigitsRef.current = '';\n moveSegment(1);\n }\n return;\n }\n }\n }\n // ----- free-form / popover key handling -----\n // With no segment to step, ArrowDown opens the popover with or without\n // Alt, unless the field is read-only or disabled, as focus and the\n // launcher don't open it then either. Alt+ArrowUp closes it.\n if (\n e.key === 'ArrowDown' &&\n !isOpen &&\n popover &&\n !disabled &&\n !readOnly\n ) {\n e.preventDefault();\n setOpen(true);\n return;\n }\n if (e.key === 'ArrowUp' && e.altKey && isOpen) {\n e.preventDefault();\n setOpen(false);\n return;\n }\n if (e.key === 'Escape' && isOpen) {\n e.preventDefault();\n setOpen(false);\n return;\n }\n if (e.key === 'Enter') {\n e.preventDefault();\n // Untouched text has nothing to commit, as on leaving, but Enter\n // still confirms what the field shows.\n if (isUntouched(text)) {\n if (closeOnSelect) setOpen(false);\n } else {\n const parsed = tryParse(text);\n if (parsed && isAllowed(parsed)) {\n commitValue(parsed);\n if (closeOnSelect) setOpen(false);\n }\n }\n }\n onKeyDown?.(e);\n },\n [\n inSegmentMode,\n segmentMap,\n activeSegmentIdx,\n segmentBaseDate,\n applyDateFromSegment,\n isUnfinishedBlocked,\n moveSegment,\n isOpen,\n setOpen,\n closeOnSelect,\n popover,\n disabled,\n readOnly,\n isUntouched,\n tryParse,\n text,\n isAllowed,\n commitValue,\n onKeyDown,\n ]\n );\n\n return {\n segmentMap,\n activeSegmentIdx,\n segmentEditable,\n inSegmentMode,\n inputHandlers: {\n onChange: handleChange,\n onFocus: handleFocus,\n onClick: handleClick,\n onKeyDown: handleKeyDown,\n onBlur: handleBlur,\n },\n };\n}\n"],"names":[],"mappings":";;;;;AAoBA;;;;;AAKG;AACH,MAAM,YAAY,GAAG,WAAW;AA2FhC;;;;;;;;;AASG;AACG,SAAU,iBAAiB,CAC/B,MAA+B,EAAA;AAE/B,IAAA,MAAM,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,YAAY,EACZ,MAAM,EACN,GAAG,EACH,GAAG,EACH,SAAS,EACT,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,QAAQ,GAAG,IAAI,EACf,OAAO,GAAG,IAAI,EACd,WAAW,GAAG,IAAI,EAClB,aAAa,GAAG,KAAK,EACrB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,OAAO,EACP,SAAS,EACT,MAAM,GACP,GAAG,MAAM;AAEV,IAAA,MAAM,UAAU,GAAsB,OAAO,CAAC,MAAK;AACjD,QAAA,MAAM,GAAG,GAAG,OAAO,MAAM,KAAK,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,IAAI;AACvE,QAAA,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS;AAAE,YAAA,OAAO,GAAG;QAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAClC,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAC/C;AACD,QAAA,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI;AAC1D,IAAA,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvB,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC;;;;AAI7E,IAAA,MAAM,cAAc,GAAG,MAAM,CAAS,EAAE,CAAC;;;AAGzC,IAAA,MAAM,OAAO,GAAG,MAAM,CAAgB,IAAI,CAAC;;;;;IAM3C,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,CAAS,KACR;UACI,CAAC,KAAK,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM;UACpC,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,EACvD,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAClC;;;;;;;;;;AAWD,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;AAChC,IAAA,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;IACjC,eAAe,CAAC,MAAK;AACnB,QAAA,IAAI,UAAU,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE;AACjC,YAAA,UAAU,CAAC,OAAO,GAAG,IAAI;AACzB,YAAA,IAAI,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,EAAE,CAAC;AAC5C,YAAA,OAAO,CAAC,OAAO,GAAG,IAAI;QACxB;AACA,QAAA,UAAU,CAAC,OAAO,GAAG,MAAM;AAC7B,IAAA,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,SAAS,CAAC,MAAK;AACb,QAAA,UAAU,CAAC,OAAO,GAAG,KAAK;AAC5B,IAAA,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;;;AAIZ,IAAA,MAAM,eAAe,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;;AAG5E,IAAA,MAAM,eAAe,GAAG,WAAW,CACjC,MAAY,KAAK,IAAI,YAAY,EAAE,EACnC,CAAC,KAAK,EAAE,YAAY,CAAC,CACtB;;;AAID,IAAA,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,CAAO,KAAK,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,EACrD,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CACtB;;;;AAKD,IAAA,MAAM,oBAAoB,GAAG,WAAW,CACtC,CAAC,IAAU,KAAI;AACb,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAAE;QACtB,WAAW,CAAC,IAAI,CAAC;QACjB,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzC,IAAA,CAAC,EACD,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAC5D;;;;;;;IAQD,eAAe,CAAC,MAAK;QACnB,IAAI,gBAAgB,KAAK,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,QAAQ,CAAC,OAAO;YAAE;QACnE,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC;AACjD,QAAA,IAAI,CAAC,GAAG;YAAE;AACV,QAAA,IAAI;AACF,YAAA,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC;QACxD;AAAE,QAAA,MAAM;;QAER;IACF,CAAC,EAAE,CAAC,gBAAgB,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAElD,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,KAAa,KAAI;AAChB,QAAA,IAAI,CAAC,UAAU,IAAI,gBAAgB,KAAK,IAAI;YAAE;QAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACzD,IAAI,GAAG,GAAG,CAAC;YAAE;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CACtB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC,CACtD;QACD,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC5C,QAAA,IAAI,OAAO,KAAK,gBAAgB,EAAE;AAChC,YAAA,cAAc,CAAC,OAAO,GAAG,EAAE;YAC3B,mBAAmB,CAAC,OAAO,CAAC;QAC9B;AACF,IAAA,CAAC,EACD,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAC/B;AAED,IAAA,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,CAAsC,KAAI;;;;;AAKzC,QAAA,IAAI,gBAAgB,KAAK,IAAI,IAAI,eAAe;YAAE;AAClD,QAAA,OAAO,CAAC,OAAO,GAAG,IAAI;AACtB,QAAA,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;IACzB,CAAC,EACD,CAAC,gBAAgB,EAAE,eAAe,EAAE,OAAO,CAAC,CAC7C;AAED,IAAA,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,CAAqC,KAAI;;;;;AAKxC,QAAA,MAAM,SAAS,GAAG,CAAC,CAAC,aAAmC;AACvD,QAAA,MAAM,eAAe,GACnB,SAAS,KAAK,IAAI;YAClB,YAAY,CAAC,OAAO,KAAK,IAAI;AAC7B,YAAA,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1C,IAAI,eAAe,EAAE;AACnB,YAAA,MAAM,GAAG,CAAC,CAAC;YACX;QACF;QACA,mBAAmB,CAAC,IAAI,CAAC;AACzB,QAAA,cAAc,CAAC,OAAO,GAAG,EAAE;;;;AAI3B,QAAA,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC;;;;AAInC,QAAA,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,CAAC,OAAO,GAAG,IAAI;QACnC,IAAI,SAAS,EAAE;AACb,YAAA,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,EAAE,CAAC;AAClC,YAAA,MAAM,GAAG,CAAC,CAAC;YACX;QACF;AACA,QAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC;AAC7B,QAAA,IAAI,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE;YAC/B,WAAW,CAAC,MAAM,CAAC;QACrB;aAAO;AACL,YAAA,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;QACvD;AACA,QAAA,MAAM,GAAG,CAAC,CAAC;AACb,IAAA,CAAC,EACD;QACE,YAAY;QACZ,WAAW;QACX,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,SAAS;QACT,WAAW;QACX,KAAK;QACL,QAAQ;QACR,MAAM;QACN,MAAM;QACN,OAAO;QACP,MAAM;AACP,KAAA,CACF;AAED,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,CAAqC,KAAI;AACxC,QAAA,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO;AACrC,QAAA,IAAI,CAAC,UAAU,IAAI,WAAW,IAAI,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,EAAE;YACnE,OAAO,CAAC,IAAI,CAAC;QACf;;;;AAIA,QAAA,IAAI,eAAe,IAAI,UAAU,EAAE;AACjC,YAAA,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,EAAE;gBACzB,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC;AACrD,gBAAA,OAAO,CAAC,OAAO,GAAG,IAAI;gBACtB,OAAO,CAAC,IAAI,CAAC;YACf;AACA,YAAA,cAAc,CAAC,OAAO,GAAG,EAAE;YAC3B,mBAAmB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC7C;AACA,QAAA,OAAO,GAAG,CAAC,CAAC;AACd,IAAA,CAAC,EACD;QACE,WAAW;QACX,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,eAAe;QACf,UAAU;QACV,KAAK;QACL,YAAY;QACZ,QAAQ;QACR,MAAM;QACN,MAAM;QACN,OAAO;QACP,OAAO;AACR,KAAA,CACF;AAED,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,CAAqC,KAAI;;;;;QAKxC,IAAI,WAAW,IAAI,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC;QACnE,IAAI,eAAe,IAAI,UAAU,IAAI,QAAQ,CAAC,OAAO,EAAE;YACrD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC;YAClD,MAAM,GAAG,GAAG,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC;YAClD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,gBAAgB,EAAE;AAC5C,gBAAA,cAAc,CAAC,OAAO,GAAG,EAAE;gBAC3B,mBAAmB,CAAC,GAAG,CAAC;YAC1B;QACF;AACA,QAAA,OAAO,GAAG,CAAC,CAAC;AACd,IAAA,CAAC,EACD;QACE,WAAW;QACX,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,eAAe;QACf,UAAU;QACV,QAAQ;QACR,gBAAgB;QAChB,OAAO;AACR,KAAA,CACF;IAED,MAAM,aAAa,GACjB,eAAe,IAAI,UAAU,KAAK,IAAI,IAAI,gBAAgB,KAAK,IAAI;AAErE,IAAA,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAwC,KAAI;;;;;QAK3C,IAAI,aAAa,IAAI,UAAU,IAAI,gBAAgB,KAAK,IAAI,EAAE;YAC5D,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC;AACjD,YAAA,MAAM,IAAI,GAAG,eAAe,EAAE;YAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE;AAClC,YAAA,QAAQ,CAAC,CAAC,GAAG;AACX,gBAAA,KAAK,SAAS;AACd,gBAAA,KAAK,WAAW;oBACd,CAAC,CAAC,cAAc,EAAE;;;;AAIlB,oBAAA,IAAI,CAAC,CAAC,MAAM,EAAE;AACZ,wBAAA,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,EAAE;AACvB,4BAAA,IAAI,MAAM;gCAAE,OAAO,CAAC,KAAK,CAAC;wBAC5B;AAAO,6BAAA,IAAI,CAAC,MAAM,IAAI,OAAO,EAAE;4BAC7B,OAAO,CAAC,IAAI,CAAC;wBACf;wBACA;oBACF;AACA,oBAAA,cAAc,CAAC,OAAO,GAAG,EAAE;oBAC3B,oBAAoB,CAClB,qBAAqB,CACnB,GAAG,EACH,IAAI,EACJ,CAAC,CAAC,GAAG,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,EAC5B,IAAI,CACL,CACF;oBACD;AACF,gBAAA,KAAK,YAAY;oBACf,CAAC,CAAC,cAAc,EAAE;oBAClB,WAAW,CAAC,CAAC,CAAC;oBACd;AACF,gBAAA,KAAK,WAAW;oBACd,CAAC,CAAC,cAAc,EAAE;AAClB,oBAAA,WAAW,CAAC,EAAE,CAAC;oBACf;AACF,gBAAA,KAAK,WAAW;oBACd,CAAC,CAAC,cAAc,EAAE;oBAClB,IAAI,cAAc,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;AACrC,wBAAA,cAAc,CAAC,OAAO,GAAG,EAAE;oBAC7B;yBAAO;AACL,wBAAA,WAAW,CAAC,EAAE,CAAC;oBACjB;oBACA;AACF,gBAAA,KAAK,QAAQ;oBACX,IAAI,MAAM,EAAE;wBACV,CAAC,CAAC,cAAc,EAAE;wBAClB,OAAO,CAAC,KAAK,CAAC;oBAChB;oBACA;AACF,gBAAA,KAAK,OAAO;oBACV,CAAC,CAAC,cAAc,EAAE;AAClB,oBAAA,IAAI,aAAa;wBAAE,OAAO,CAAC,KAAK,CAAC;oBACjC;AACF,gBAAA,KAAK,KAAK;oBACR,mBAAmB,CAAC,IAAI,CAAC;AACzB,oBAAA,cAAc,CAAC,OAAO,GAAG,EAAE;oBAC3B;AACF,gBAAA;;AAEE,oBAAA,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE;AACvB,wBAAA,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE;4BAClC,CAAC,CAAC,cAAc,EAAE;AAClB,4BAAA,cAAc,CAAC,OAAO,GAAG,EAAE;4BAC3B,oBAAoB,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;4BAC1C,WAAW,CAAC,CAAC,CAAC;4BACd;wBACF;AACA,wBAAA,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE;4BAClC,CAAC,CAAC,cAAc,EAAE;AAClB,4BAAA,cAAc,CAAC,OAAO,GAAG,EAAE;4BAC3B,oBAAoB,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;4BACzC,WAAW,CAAC,CAAC,CAAC;4BACd;wBACF;oBACF;;;AAGA,oBAAA,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;wBAClD,CAAC,CAAC,cAAc,EAAE;AAClB,wBAAA,cAAc,CAAC,OAAO,GAAG,EAAE;wBAC3B,WAAW,CAAC,CAAC,CAAC;wBACd;oBACF;;AAEA,oBAAA,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE;wBAChD,CAAC,CAAC,cAAc,EAAE;wBAClB,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG;AACjD,wBAAA,cAAc,CAAC,OAAO,GAAG,UAAU;AACnC,wBAAA,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,eAAe,CACvC,GAAG,EACH,IAAI,EACJ,UAAU,EACV,IAAI,CACL;AACD,wBAAA,IAAI,CAAC,OAAO,IAAI,mBAAmB,GAAG,IAAI,CAAC;4BAAE;wBAC7C,oBAAoB,CAAC,IAAI,CAAC;wBAC1B,IAAI,OAAO,EAAE;AACX,4BAAA,cAAc,CAAC,OAAO,GAAG,EAAE;4BAC3B,WAAW,CAAC,CAAC,CAAC;wBAChB;wBACA;oBACF;;QAEN;;;;;AAKA,QAAA,IACE,CAAC,CAAC,GAAG,KAAK,WAAW;AACrB,YAAA,CAAC,MAAM;YACP,OAAO;AACP,YAAA,CAAC,QAAQ;YACT,CAAC,QAAQ,EACT;YACA,CAAC,CAAC,cAAc,EAAE;YAClB,OAAO,CAAC,IAAI,CAAC;YACb;QACF;AACA,QAAA,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM,EAAE;YAC7C,CAAC,CAAC,cAAc,EAAE;YAClB,OAAO,CAAC,KAAK,CAAC;YACd;QACF;QACA,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,EAAE;YAChC,CAAC,CAAC,cAAc,EAAE;YAClB,OAAO,CAAC,KAAK,CAAC;YACd;QACF;AACA,QAAA,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE;YACrB,CAAC,CAAC,cAAc,EAAE;;;AAGlB,YAAA,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE;AACrB,gBAAA,IAAI,aAAa;oBAAE,OAAO,CAAC,KAAK,CAAC;YACnC;iBAAO;AACL,gBAAA,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC;AAC7B,gBAAA,IAAI,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE;oBAC/B,WAAW,CAAC,MAAM,CAAC;AACnB,oBAAA,IAAI,aAAa;wBAAE,OAAO,CAAC,KAAK,CAAC;gBACnC;YACF;QACF;AACA,QAAA,SAAS,GAAG,CAAC,CAAC;AAChB,IAAA,CAAC,EACD;QACE,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,eAAe;QACf,oBAAoB;QACpB,mBAAmB;QACnB,WAAW;QACX,MAAM;QACN,OAAO;QACP,aAAa;QACb,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,IAAI;QACJ,SAAS;QACT,WAAW;QACX,SAAS;AACV,KAAA,CACF;IAED,OAAO;QACL,UAAU;QACV,gBAAgB;QAChB,eAAe;QACf,aAAa;AACb,QAAA,aAAa,EAAE;AACb,YAAA,QAAQ,EAAE,YAAY;AACtB,YAAA,OAAO,EAAE,WAAW;AACpB,YAAA,OAAO,EAAE,WAAW;AACpB,YAAA,SAAS,EAAE,aAAa;AACxB,YAAA,MAAM,EAAE,UAAU;AACnB,SAAA;KACF;AACH;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
// Whether the nearest enclosing `Control` shows its `isLoading` spinner. The
|
|
5
|
+
// picker bases read it so their right-side launcher gives way to that
|
|
6
|
+
// spinner, which sits at the same edge, whether the `Control` is the one a
|
|
7
|
+
// convenience input renders or one the caller wrapped around the input.
|
|
8
|
+
// Autocomplete reads it for its clear button, which sits there too.
|
|
9
|
+
// Kept apart from `FormContext`, whose contexts only report whether a `Field`
|
|
10
|
+
// or `Control` is present. Internal; not part of the public API.
|
|
11
|
+
const ControlLoadingContext = createContext(false);
|
|
12
|
+
/** Provider for the loading state, used internally by `Control`. */
|
|
13
|
+
const ControlLoadingProvider = ControlLoadingContext.Provider;
|
|
14
|
+
/** Whether the nearest enclosing `Control` is loading; `false` outside one. */
|
|
15
|
+
const useControlLoading = () => useContext(ControlLoadingContext);
|
|
16
|
+
|
|
17
|
+
export { ControlLoadingProvider, useControlLoading };
|
|
18
|
+
//# sourceMappingURL=controlLoading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controlLoading.js","sources":["../../../src/form/controlLoading.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\n// Whether the nearest enclosing `Control` shows its `isLoading` spinner. The\n// picker bases read it so their right-side launcher gives way to that\n// spinner, which sits at the same edge, whether the `Control` is the one a\n// convenience input renders or one the caller wrapped around the input.\n// Autocomplete reads it for its clear button, which sits there too.\n// Kept apart from `FormContext`, whose contexts only report whether a `Field`\n// or `Control` is present. Internal; not part of the public API.\nconst ControlLoadingContext = createContext(false);\n\n/** Provider for the loading state, used internally by `Control`. */\nexport const ControlLoadingProvider = ControlLoadingContext.Provider;\n\n/** Whether the nearest enclosing `Control` is loading; `false` outside one. */\nexport const useControlLoading = () => useContext(ControlLoadingContext);\n"],"names":[],"mappings":";;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,qBAAqB,GAAG,aAAa,CAAC,KAAK,CAAC;AAElD;AACO,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAE5D;AACO,MAAM,iBAAiB,GAAG,MAAM,UAAU,CAAC,qBAAqB;;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Associates the convenience `label` prop with its control (#368): generates
|
|
6
|
+
* an id for the control and returns labelProps carrying a matching `htmlFor`.
|
|
7
|
+
* A user-supplied `id` is used as the target instead of the generated one, and
|
|
8
|
+
* an explicit `htmlFor` key in labelProps — even set to `undefined` — disables
|
|
9
|
+
* generation entirely: the user has taken over the association (#495 presence
|
|
10
|
+
* semantics). Internal; not part of the public API.
|
|
11
|
+
*/
|
|
12
|
+
function useAutoLabelId({ label, id, labelProps, rendersLabel, }) {
|
|
13
|
+
// Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.
|
|
14
|
+
const generatedId = useId();
|
|
15
|
+
// Truthiness mirrors Field's own `if (label)` render gate.
|
|
16
|
+
const active = !!label && rendersLabel;
|
|
17
|
+
// Presence, not truthiness: `htmlFor: undefined` is an explicit opt-out and
|
|
18
|
+
// must not leave an orphan generated id on the control.
|
|
19
|
+
const userWired = !!labelProps && 'htmlFor' in labelProps;
|
|
20
|
+
const controlId = id ?? (active && !userWired ? generatedId : undefined);
|
|
21
|
+
// Inactive with a label still means an own Field may render it (pickers'
|
|
22
|
+
// inline mode, Taginput at maxTags) — the explicit `htmlFor: undefined`
|
|
23
|
+
// tells Field the association is owned here, so it must not generate one
|
|
24
|
+
// that would dangle (#495 presence semantics).
|
|
25
|
+
const fieldLabelProps = active
|
|
26
|
+
? { htmlFor: controlId, ...labelProps }
|
|
27
|
+
: label
|
|
28
|
+
? { htmlFor: undefined, ...labelProps }
|
|
29
|
+
: labelProps;
|
|
30
|
+
return { controlId, fieldLabelProps };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Group-input counterpart of {@link useAutoLabelId} (#494): a group of
|
|
34
|
+
* controls cannot take a single `htmlFor`, so instead the rendered `<label>`
|
|
35
|
+
* gets a generated id and the group container points at it with
|
|
36
|
+
* `aria-labelledby`. A user-supplied `labelProps.id` is used as the target
|
|
37
|
+
* instead of generating one. Any caller `htmlFor` is stripped — a group label
|
|
38
|
+
* names the group, never a single control — so the merged labelProps always
|
|
39
|
+
* carry an explicit `htmlFor: undefined`.
|
|
40
|
+
* Internal; not part of the public API.
|
|
41
|
+
*/
|
|
42
|
+
function useAutoLabelledBy({ label, labelProps, rendersLabel, }) {
|
|
43
|
+
// Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.
|
|
44
|
+
const generatedId = useId();
|
|
45
|
+
const active = !!label && rendersLabel;
|
|
46
|
+
const labelId = labelProps?.id ?? (active ? generatedId : undefined);
|
|
47
|
+
const fieldLabelProps = active
|
|
48
|
+
? { ...labelProps, id: labelId, htmlFor: undefined }
|
|
49
|
+
: labelProps;
|
|
50
|
+
return { ariaLabelledBy: active ? labelId : undefined, fieldLabelProps };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { useAutoLabelId, useAutoLabelledBy };
|
|
54
|
+
//# sourceMappingURL=useAutoLabelId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAutoLabelId.js","sources":["../../../src/form/useAutoLabelId.ts"],"sourcesContent":["import React, { useId } from 'react';\nimport type { FieldProps } from './Field';\n\ninterface UseAutoLabelIdOptions {\n /** The convenience `label` prop as passed by the caller. */\n label: React.ReactNode;\n /** User-supplied id for the control, if any. */\n id?: string;\n /** User-supplied labelProps, if any. */\n labelProps?: FieldProps['labelProps'];\n /**\n * True when this render actually outputs the label wired to a control —\n * false inside an outer Field (label is dropped), in variants that render\n * no Field, and in modes with no labellable control (e.g. inline pickers).\n */\n rendersLabel: boolean;\n}\n\n/**\n * Associates the convenience `label` prop with its control (#368): generates\n * an id for the control and returns labelProps carrying a matching `htmlFor`.\n * A user-supplied `id` is used as the target instead of the generated one, and\n * an explicit `htmlFor` key in labelProps — even set to `undefined` — disables\n * generation entirely: the user has taken over the association (#495 presence\n * semantics). Internal; not part of the public API.\n */\nexport function useAutoLabelId({\n label,\n id,\n labelProps,\n rendersLabel,\n}: UseAutoLabelIdOptions): {\n controlId: string | undefined;\n fieldLabelProps: FieldProps['labelProps'] | undefined;\n} {\n // Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.\n const generatedId = useId();\n // Truthiness mirrors Field's own `if (label)` render gate.\n const active = !!label && rendersLabel;\n // Presence, not truthiness: `htmlFor: undefined` is an explicit opt-out and\n // must not leave an orphan generated id on the control.\n const userWired = !!labelProps && 'htmlFor' in labelProps;\n const controlId = id ?? (active && !userWired ? generatedId : undefined);\n // Inactive with a label still means an own Field may render it (pickers'\n // inline mode, Taginput at maxTags) — the explicit `htmlFor: undefined`\n // tells Field the association is owned here, so it must not generate one\n // that would dangle (#495 presence semantics).\n const fieldLabelProps = active\n ? { htmlFor: controlId, ...labelProps }\n : label\n ? { htmlFor: undefined, ...labelProps }\n : labelProps;\n return { controlId, fieldLabelProps };\n}\n\ninterface UseAutoLabelledByOptions {\n /** The convenience `label` prop as passed by the caller. */\n label: React.ReactNode;\n /** User-supplied labelProps, if any. */\n labelProps?: FieldProps['labelProps'];\n /** True when this render actually outputs the label naming the group. */\n rendersLabel: boolean;\n}\n\n/**\n * Group-input counterpart of {@link useAutoLabelId} (#494): a group of\n * controls cannot take a single `htmlFor`, so instead the rendered `<label>`\n * gets a generated id and the group container points at it with\n * `aria-labelledby`. A user-supplied `labelProps.id` is used as the target\n * instead of generating one. Any caller `htmlFor` is stripped — a group label\n * names the group, never a single control — so the merged labelProps always\n * carry an explicit `htmlFor: undefined`.\n * Internal; not part of the public API.\n */\nexport function useAutoLabelledBy({\n label,\n labelProps,\n rendersLabel,\n}: UseAutoLabelledByOptions): {\n ariaLabelledBy: string | undefined;\n fieldLabelProps: FieldProps['labelProps'] | undefined;\n} {\n // Called unconditionally per the rules of hooks; SSR-safe on React 18 and 19.\n const generatedId = useId();\n const active = !!label && rendersLabel;\n const labelId = labelProps?.id ?? (active ? generatedId : undefined);\n const fieldLabelProps = active\n ? { ...labelProps, id: labelId, htmlFor: undefined }\n : labelProps;\n return { ariaLabelledBy: active ? labelId : undefined, fieldLabelProps };\n}\n"],"names":[],"mappings":";;;AAkBA;;;;;;;AAOG;AACG,SAAU,cAAc,CAAC,EAC7B,KAAK,EACL,EAAE,EACF,UAAU,EACV,YAAY,GACU,EAAA;;AAKtB,IAAA,MAAM,WAAW,GAAG,KAAK,EAAE;;AAE3B,IAAA,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,YAAY;;;IAGtC,MAAM,SAAS,GAAG,CAAC,CAAC,UAAU,IAAI,SAAS,IAAI,UAAU;AACzD,IAAA,MAAM,SAAS,GAAG,EAAE,KAAK,MAAM,IAAI,CAAC,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;;;;;IAKxE,MAAM,eAAe,GAAG;UACpB,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,UAAU;AACrC,UAAE;cACE,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,UAAU;cACnC,UAAU;AAChB,IAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE;AACvC;AAWA;;;;;;;;;AASG;AACG,SAAU,iBAAiB,CAAC,EAChC,KAAK,EACL,UAAU,EACV,YAAY,GACa,EAAA;;AAKzB,IAAA,MAAM,WAAW,GAAG,KAAK,EAAE;AAC3B,IAAA,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,YAAY;AACtC,IAAA,MAAM,OAAO,GAAG,UAAU,EAAE,EAAE,KAAK,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IACpE,MAAM,eAAe,GAAG;AACtB,UAAE,EAAE,GAAG,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS;UAChD,UAAU;AACd,IAAA,OAAO,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EAAE,eAAe,EAAE;AAC1E;;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
4
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `Cell` component provides a single Bulma grid cell for use inside the [`Grid`](./grid.md) component.
|
|
8
|
+
*
|
|
9
|
+
* @function
|
|
10
|
+
* @param {CellProps} props - Props for the Cell component.
|
|
11
|
+
* @returns {JSX.Element} The rendered grid cell.
|
|
12
|
+
* @see {@link https://bulma.io/documentation/grid/ | Bulma Grid documentation}
|
|
13
|
+
*/
|
|
14
|
+
const Cell = ({ colStart, colFromEnd, colSpan, rowStart, rowFromEnd, rowSpan, className, textColor, color: _fieldColor, bgColor, children, ...props }) => {
|
|
15
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
16
|
+
color: textColor,
|
|
17
|
+
backgroundColor: bgColor,
|
|
18
|
+
...props,
|
|
19
|
+
});
|
|
20
|
+
const mainClass = usePrefixedClassNames('cell');
|
|
21
|
+
// Build cell grid classes with prefixes
|
|
22
|
+
const cellGridClasses = usePrefixedClassNames('', {
|
|
23
|
+
[`is-col-start-${colStart}`]: colStart !== undefined && colStart !== null,
|
|
24
|
+
[`is-col-from-end-${colFromEnd}`]: colFromEnd !== undefined && colFromEnd !== null,
|
|
25
|
+
[`is-col-span-${colSpan}`]: colSpan !== undefined && colSpan !== null,
|
|
26
|
+
[`is-row-start-${rowStart}`]: rowStart !== undefined && rowStart !== null,
|
|
27
|
+
[`is-row-from-end-${rowFromEnd}`]: rowFromEnd !== undefined && rowFromEnd !== null,
|
|
28
|
+
[`is-row-span-${rowSpan}`]: rowSpan !== undefined && rowSpan !== null,
|
|
29
|
+
});
|
|
30
|
+
const cellClasses = classNames(mainClass, cellGridClasses, className, bulmaHelperClasses);
|
|
31
|
+
return (jsx("div", { className: cellClasses, ...rest, children: children }));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { Cell };
|
|
35
|
+
//# sourceMappingURL=Cell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Cell.js","sources":["../../../src/grid/Cell.tsx"],"sourcesContent":["import React from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\n\n/**\n * Type for grid cell span values.\n */\nexport type CellSpanValue = number;\n\n/**\n * Props for the Cell component.\n */\nexport interface CellProps\n extends\n React.HTMLAttributes<HTMLDivElement>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Which column the cell starts at (Bulma: `is-col-start-x`). */\n colStart?: number;\n /** Which column the cell ends at, counting from the end (Bulma: `is-col-from-end-x`). */\n colFromEnd?: number;\n /** How many columns the cell will span (Bulma: `is-col-span-x`). */\n colSpan?: CellSpanValue;\n /** Which row the cell starts at (Bulma: `is-row-start-x`). */\n rowStart?: number;\n /** Which row the cell ends at, counting from the end (Bulma: `is-row-from-end-x`). */\n rowFromEnd?: number;\n /** How many rows the cell will span (Bulma: `is-row-span-x`). */\n rowSpan?: CellSpanValue;\n /** Additional CSS class names. */\n className?: string;\n /** Text color. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Bulma color modifier for the cell. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Background color. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Content to render inside the cell. */\n children?: React.ReactNode;\n}\n\n/**\n * The `Cell` component provides a single Bulma grid cell for use inside the [`Grid`](./grid.md) component.\n *\n * @function\n * @param {CellProps} props - Props for the Cell component.\n * @returns {JSX.Element} The rendered grid cell.\n * @see {@link https://bulma.io/documentation/grid/ | Bulma Grid documentation}\n */\nexport const Cell: React.FC<CellProps> = ({\n colStart,\n colFromEnd,\n colSpan,\n rowStart,\n rowFromEnd,\n rowSpan,\n className,\n textColor,\n color: _fieldColor,\n bgColor,\n children,\n ...props\n}) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n const mainClass = usePrefixedClassNames('cell');\n\n // Build cell grid classes with prefixes\n const cellGridClasses = usePrefixedClassNames('', {\n [`is-col-start-${colStart}`]: colStart !== undefined && colStart !== null,\n [`is-col-from-end-${colFromEnd}`]:\n colFromEnd !== undefined && colFromEnd !== null,\n [`is-col-span-${colSpan}`]: colSpan !== undefined && colSpan !== null,\n [`is-row-start-${rowStart}`]: rowStart !== undefined && rowStart !== null,\n [`is-row-from-end-${rowFromEnd}`]:\n rowFromEnd !== undefined && rowFromEnd !== null,\n [`is-row-span-${rowSpan}`]: rowSpan !== undefined && rowSpan !== null,\n });\n\n const cellClasses = classNames(\n mainClass,\n cellGridClasses,\n className,\n bulmaHelperClasses\n );\n\n return (\n <div className={cellClasses} {...rest}>\n {children}\n </div>\n );\n};\n"],"names":["_jsx"],"mappings":";;;;;AA4CA;;;;;;;AAOG;AACI,MAAM,IAAI,GAAwB,CAAC,EACxC,QAAQ,EACR,UAAU,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,KAAK,EAAE,WAAW,EAClB,OAAO,EACP,QAAQ,EACR,GAAG,KAAK,EACT,KAAI;AACH,IAAA,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC;AACnD,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,eAAe,EAAE,OAAO;AACxB,QAAA,GAAG,KAAK;AACT,KAAA,CAAC;AAEF,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC;;AAG/C,IAAA,MAAM,eAAe,GAAG,qBAAqB,CAAC,EAAE,EAAE;QAChD,CAAC,CAAA,aAAA,EAAgB,QAAQ,CAAA,CAAE,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI;QACzE,CAAC,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAE,GAC9B,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI;QACjD,CAAC,CAAA,YAAA,EAAe,OAAO,CAAA,CAAE,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QACrE,CAAC,CAAA,aAAA,EAAgB,QAAQ,CAAA,CAAE,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI;QACzE,CAAC,CAAA,gBAAA,EAAmB,UAAU,CAAA,CAAE,GAC9B,UAAU,KAAK,SAAS,IAAI,UAAU,KAAK,IAAI;QACjD,CAAC,CAAA,YAAA,EAAe,OAAO,CAAA,CAAE,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;AACtE,KAAA,CAAC;AAEF,IAAA,MAAM,WAAW,GAAG,UAAU,CAC5B,SAAS,EACT,eAAe,EACf,SAAS,EACT,kBAAkB,CACnB;IAED,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,WAAW,EAAA,GAAM,IAAI,EAAA,QAAA,EAClC,QAAQ,EAAA,CACL;AAEV;;"}
|