@ahrowe/ui 0.52.0 → 0.53.0

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs-types/common/otpInput/otpInput.types.d.ts +1 -1
  3. package/dist/cjs-types/index.d.ts +1 -1
  4. package/dist/cjs-types/services/formValidation/index.d.ts +1 -1
  5. package/dist/cjs-types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  6. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  7. package/dist/esm/common/datePicker/datePicker.mjs.map +1 -1
  8. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  9. package/dist/esm/common/dropdown/dropdown.mjs.map +1 -1
  10. package/dist/esm/common/iconPicker/iconPicker.mjs +1 -1
  11. package/dist/esm/common/iconPicker/iconPicker.mjs.map +1 -1
  12. package/dist/esm/common/input/input.mjs +1 -1
  13. package/dist/esm/common/input/input.mjs.map +1 -1
  14. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  15. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  16. package/dist/esm/common/multiSelect/multiSelect.mjs +1 -1
  17. package/dist/esm/common/multiSelect/multiSelect.mjs.map +1 -1
  18. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  19. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  20. package/dist/esm/common/otpInput/otpInput.mjs +1 -1
  21. package/dist/esm/common/otpInput/otpInput.mjs.map +1 -1
  22. package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
  23. package/dist/esm/common/radioGroup/radioGroup.mjs +1 -1
  24. package/dist/esm/common/radioGroup/radioGroup.mjs.map +1 -1
  25. package/dist/esm/common/timeInput/timeInput.mjs +1 -1
  26. package/dist/esm/common/timeInput/timeInput.mjs.map +1 -1
  27. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs +1 -1
  28. package/dist/esm/services/formValidation/useFormValidatorGroup.mjs.map +1 -1
  29. package/dist/esm/services/formValidation/validatableComponent.mjs +1 -1
  30. package/dist/esm/services/formValidation/validatableComponent.mjs.map +1 -1
  31. package/dist/index.cjs +3 -3
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/types/common/otpInput/otpInput.types.d.ts +1 -1
  34. package/dist/types/index.d.ts +1 -1
  35. package/dist/types/services/formValidation/index.d.ts +1 -1
  36. package/dist/types/services/formValidation/useFormValidatorGroup.d.ts +1 -1
  37. package/docs/FormValidator.md +23 -1
  38. package/docs/FormValidatorGroup.md +5 -2
  39. package/docs/InputDropdown.md +1 -1
  40. package/docs/MultiSelect.md +1 -1
  41. package/docs/OtpInput.md +1 -1
  42. package/docs/RadioGroup.md +1 -1
  43. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"inputDropdown.mjs","names":[],"sources":["../../../../package/common/inputDropdown/inputDropdown.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport styles from './inputDropdown.module.pcss';\nimport Input from '../input';\nimport Popover from '../popover';\nimport { PopoverAlign, PopoverPlacement } from '../popover/popover.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport type { InputDropdownItem, InputDropdownLabels, InputDropdownProps } from './inputDropdown.types';\nimport { nextIndex, scrollOptionIntoView } from '../utils/listNavigation';\n\nfunction getKeyFromProps(value: string | undefined, formValidator: InputDropdownProps['formValidator']): string {\n return formValidator ? ((formValidator.value as string) ?? '') : (value ?? '');\n}\n\nfunction labelForKey(key: string, items: InputDropdownItem[]): string {\n const match = items.find((item) => item.value === key);\n return match ? (match.label ?? match.value) : key;\n}\n\n/**\n * The suggestions do not wrap: the list is filtered by what is being typed, so running off one\n * end and reappearing at the other reads as the list having changed under the caret.\n */\nconst NO_WRAP = { wrap: false };\n\nconst DEFAULT_LABELS: Required<InputDropdownLabels> = { empty: 'No matches' };\n\nexport default function InputDropdown(props: InputDropdownProps) {\n const {\n label = '',\n value = '',\n formValidator = null,\n error,\n isRequired = false,\n items = [],\n allowCustomValue = false,\n maxItems = 100,\n className,\n style,\n classNames,\n styles: slotStyles,\n onSelect = () => null,\n onChange,\n placeholder = '',\n readOnly = false,\n labels,\n ...rest\n } = useComponentDefaults('InputDropdown', props);\n const text = useLabels('InputDropdown', labels, DEFAULT_LABELS);\n const currentKey = getKeyFromProps(value, formValidator);\n const [, forceUpdate] = useState(0);\n const fvRef = useRef(formValidator);\n\n useEffect(() => {\n fvRef.current = formValidator;\n if (!formValidator) return;\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => {\n formValidator.removeOnUpdateListener(listener);\n };\n }, [formValidator]);\n\n const computedIsRequired =\n isRequired || !!formValidator?.validators.find((v) => (v as { validatorId?: string }).validatorId === 'required');\n\n const [inputText, setInputText] = useState(() => labelForKey(currentKey, items));\n const [prevKey, setPrevKey] = useState(currentKey);\n const [isMenuShown, setIsMenuShown] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState<number | null>(null);\n const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Sync display text when the controlled key changes externally (e.g. parent reset).\n if (currentKey !== prevKey) {\n setPrevKey(currentKey);\n const match = items.find((item) => item.value === currentKey);\n if (match) setInputText(match.label ?? match.value);\n else if (allowCustomValue) setInputText(currentKey);\n }\n\n // `items` often arrives after the value, leaving the raw key on display. Not while open, or a key\n // being typed would turn into its label under the caret.\n if (!isMenuShown && !allowCustomValue && inputText === currentKey) {\n const selectedLabel = labelForKey(currentKey, items);\n if (selectedLabel !== inputText) setInputText(selectedLabel);\n }\n\n const currItems = useMemo(() => {\n const needle = inputText.toLowerCase();\n const matches: InputDropdownItem[] = [];\n for (const item of items) {\n if (!(item.label ?? item.value).toLowerCase().includes(needle)) continue;\n matches.push(item);\n if (maxItems > 0 && matches.length >= maxItems) break;\n }\n return matches;\n }, [items, inputText, maxItems]);\n\n // Only text that blur swaps for the selection's label needs a warning. Comparing with the label\n // rather than '' keeps a preset value missing from `items` from showing \"No matches\" unprompted.\n const isDiscardedOnBlur = !allowCustomValue && inputText !== '' && inputText !== labelForKey(currentKey, items);\n const showEmpty = currItems.length === 0 && isDiscardedOnBlur;\n // Closed rather than hidden when empty: a mounted panel stays on the overlay stack, so inside a\n // Modal an invisible list would take the Escape meant for the modal.\n const isOpen = isMenuShown && !readOnly && (currItems.length > 0 || showEmpty);\n\n // A ref callback rather than a layout effect: the list is portaled out of the field's font size,\n // and Popover positions it in its own layout effect, which runs before this component's would.\n const menuFontSizeOverride = slotStyles?.dropdown?.fontSize;\n const menuRef = useCallback(\n (menu: HTMLDivElement | null) => {\n const trigger = triggerRef.current;\n if (!menu || !trigger || menuFontSizeOverride != null) return;\n menu.style.fontSize = window.getComputedStyle(trigger).fontSize;\n },\n [menuFontSizeOverride],\n );\n\n const handleFocus = () => {\n if (readOnly) return;\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n hideTimeoutRef.current = null;\n }\n setIsMenuShown(true);\n };\n\n const handleBlur = () => {\n if (fvRef.current) fvRef.current.touched = true;\n hideTimeoutRef.current = setTimeout(() => {\n setIsMenuShown(false);\n setHighlightedIndex(null);\n hideTimeoutRef.current = null;\n // Without custom values, uncommitted typed text is invalid — snap the\n // display back to the currently selected item's label.\n if (!allowCustomValue) setInputText(labelForKey(currentKey, items));\n }, 100);\n };\n\n useEffect(() => {\n if (!isOpen || highlightedIndex === null) return;\n scrollOptionIntoView(scrollRef.current, scrollRef.current?.children[highlightedIndex]);\n }, [isOpen, highlightedIndex]);\n\n const handleSelect = useCallback(\n (item: InputDropdownItem) => {\n setInputText(item.label ?? item.value);\n setIsMenuShown(false);\n setHighlightedIndex(null);\n if (formValidator) formValidator.set(item.value);\n if (onChange) onChange(item.value);\n onSelect(item.value);\n },\n [formValidator, onChange, onSelect],\n );\n\n const onKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (readOnly) return;\n const isArrow = event.key === 'ArrowUp' || event.key === 'ArrowDown';\n // Enter belongs to the form while closed, or it would pick a suggestion the user never saw.\n if (!isOpen) {\n if (isArrow) {\n event.preventDefault();\n setIsMenuShown(true);\n }\n return;\n }\n if (isArrow) {\n event.preventDefault();\n const step = event.key === 'ArrowDown' ? 1 : -1;\n setHighlightedIndex((idx) => {\n // The text being typed is part of the cycle, so both ends lead back to it and on round\n // to the far end: last → text → first, first → text → last. That is how a browser's own\n // address bar behaves, and it is why this list does not wrap straight around the way\n // Dropdown and MultiSelect do — they have no typed text to pass through.\n const atFarEnd = idx !== null && idx === (step === 1 ? currItems.length - 1 : 0);\n return atFarEnd ? null : nextIndex(idx, step, currItems.length, NO_WRAP);\n });\n } else if (event.key === 'Enter') {\n if (highlightedIndex !== null && currItems[highlightedIndex]) {\n // This Enter picked a suggestion, so it must not also reach the form around the field.\n // Without this the browser's implicit submission runs as well, and choosing from the\n // list submitted the form underneath the user. The blank state deliberately does not\n // do this: Enter there means the text as typed, and submitting is a fair reading of it.\n event.preventDefault();\n handleSelect(currItems[highlightedIndex]);\n } else if (allowCustomValue) {\n setIsMenuShown(false);\n setHighlightedIndex(null);\n }\n }\n },\n [currItems, highlightedIndex, handleSelect, allowCustomValue, readOnly, isOpen],\n );\n\n const menu = (\n <div ref={menuRef} className={cx(styles.dropdown, classNames?.dropdown)} style={slotStyles?.dropdown}>\n {showEmpty && (\n <div\n className={cx(styles.dropdownEmpty, classNames?.empty)}\n style={slotStyles?.empty}\n // A blur would throw the typed text away.\n onMouseDown={(e) => e.preventDefault()}\n >\n {text.empty}\n </div>\n )}\n <div ref={scrollRef} className={styles.dropdownScroll}>\n {currItems.map((item, key) => (\n <div\n key={`${key}_${item.label}_${item.value}`}\n className={cx(\n styles.dropdownItem,\n highlightedIndex === key && styles.dropdownItemHighlighted,\n classNames?.item,\n )}\n style={slotStyles?.item}\n // Prevent the input from blurring before the click resolves.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelect(item)}\n >\n {item.label ?? item.value}\n </div>\n ))}\n </div>\n </div>\n );\n\n return (\n <div\n {...rest}\n ref={triggerRef}\n className={cx(styles.inputDropdown, classNames?.root, className)}\n style={{ ...slotStyles?.root, ...style }}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={onKeyDown}\n >\n <Popover\n // The field owns the open state: it opens on typing and closes on blur, on\n // a pick, or on Escape. Only a close is taken from Popover, which is the\n // outside click its portaled panel would otherwise not see.\n isOpen={isOpen}\n onOpenChange={(next) => {\n if (next) return;\n setIsMenuShown(false);\n setHighlightedIndex(null);\n }}\n // Never a sheet, whatever the app-wide `presentation`: its backdrop would cover the field\n // being typed into.\n presentation={Presentation.Default}\n placement={PopoverPlacement.Bottom}\n align={PopoverAlign.Start}\n offset={0}\n withArrow={false}\n constrainWidth={false}\n closeOnTriggerClick={false}\n focusOnOpen={false}\n classNames={{ panel: styles.inputDropdownPanel }}\n content={menu}\n >\n <Input\n // A combobox renders its own suggestion list; the browser's autofill dropdown\n // opens on top of it, so two lists compete for the same space.\n autoComplete='off'\n label={label}\n value={inputText}\n readOnly={readOnly}\n formValidator={null}\n isRequired={computedIsRequired}\n isValid={formValidator ? (formValidator.touched ? !formValidator.hasError() : true) : !error}\n errorMessage={\n formValidator\n ? formValidator.touched\n ? (formValidator.getCurrentErrorMessage() ?? '')\n : ''\n : (error ?? '')\n }\n onChange={(typed) => {\n setInputText(typed);\n setHighlightedIndex(null);\n setIsMenuShown(true);\n if (allowCustomValue) {\n if (formValidator) formValidator.set(typed);\n if (onChange) onChange(typed);\n } else if (typed === '') {\n if (formValidator) formValidator.set('');\n if (onChange) onChange('');\n onSelect('');\n } else {\n // Emit when the typed text exactly matches an item's label/value.\n const match = items.find((item) => (item.label ?? item.value).toLowerCase() === typed.toLowerCase());\n if (match) {\n if (formValidator) formValidator.set(match.value);\n if (onChange) onChange(match.value);\n onSelect(match.value);\n }\n }\n }}\n placeholder={placeholder}\n />\n </Popover>\n {/* Outside the portal and always mounted: a live region that mounts already filled is not\n reliably announced. */}\n <span className={styles.inputDropdownStatus} role='status' aria-live='polite'>\n {isOpen && showEmpty ? text.empty : ''}\n </span>\n </div>\n );\n}\n"],"mappings":"iqBAWA,SAAS,GAAgB,EAA2B,EAA4D,CAC9G,OAAO,EAAkB,EAAc,OAAoB,GAAO,GAAS,EAC7E,CAEA,SAAS,EAAY,EAAa,EAAoC,CACpE,IAAM,EAAQ,EAAM,KAAM,GAAS,EAAK,QAAU,CAAG,EACrD,OAAO,EAAS,EAAM,OAAS,EAAM,MAAS,CAChD,CAMA,IAAM,GAAU,CAAE,KAAM,EAAM,EAExB,GAAgD,CAAE,MAAO,YAAa,EAE5E,SAAwB,EAAc,EAA2B,CAC/D,GAAM,CACJ,SAAQ,GACR,QAAQ,GACR,gBAAgB,KAChB,QACA,aAAa,GACb,QAAQ,CAAC,EACT,mBAAmB,GACnB,WAAW,IACX,YACA,QACA,aACA,OAAQ,EACR,eAAiB,KACjB,WACA,eAAc,GACd,WAAW,GACX,SACA,GAAG,IACD,EAAqB,gBAAiB,CAAK,EACzC,EAAO,EAAU,gBAAiB,EAAQ,EAAc,EACxD,EAAa,GAAgB,EAAO,CAAa,EACjD,EAAG,GAAe,EAAS,CAAC,EAC5B,EAAQ,EAAO,CAAa,EAElC,MAAgB,CAEd,GADA,EAAM,QAAU,EACZ,CAAC,EAAe,OACpB,IAAM,MAAiB,EAAa,GAAM,EAAI,CAAC,EAE/C,OADA,EAAc,yBAAyB,CAAQ,MAClC,CACX,EAAc,uBAAuB,CAAQ,CAC/C,CACF,EAAG,CAAC,CAAa,CAAC,EAElB,IAAM,EACJ,GAAc,CAAC,CAAC,GAAe,WAAW,KAAM,GAAO,EAA+B,cAAgB,UAAU,EAE5G,CAAC,EAAW,GAAgB,MAAe,EAAY,EAAY,CAAK,CAAC,EACzE,CAAC,EAAS,GAAc,EAAS,CAAU,EAC3C,CAAC,EAAa,GAAkB,EAAS,EAAK,EAC9C,CAAC,EAAkB,GAAuB,EAAwB,IAAI,EACtE,EAAiB,EAA6C,IAAI,EAClE,EAAa,EAAuB,IAAI,EACxC,EAAY,EAAuB,IAAI,EAG7C,GAAI,IAAe,EAAS,CAC1B,EAAW,CAAU,EACrB,IAAM,EAAQ,EAAM,KAAM,GAAS,EAAK,QAAU,CAAU,EACxD,EAAO,EAAa,EAAM,OAAS,EAAM,KAAK,EACzC,GAAkB,EAAa,CAAU,CACpD,CAIA,GAAI,CAAC,GAAe,CAAC,GAAoB,IAAc,EAAY,CACjE,IAAM,EAAgB,EAAY,EAAY,CAAK,EAC/C,IAAkB,GAAW,EAAa,CAAa,CAC7D,CAEA,IAAM,EAAY,OAAc,CAC9B,IAAM,EAAS,EAAU,YAAY,EAC/B,EAA+B,CAAC,EACtC,IAAK,IAAM,KAAQ,EACb,IAAE,EAAK,OAAS,EAAK,MAAA,CAAO,YAAY,CAAC,CAAC,SAAS,CAAM,IAC7D,EAAQ,KAAK,CAAI,EACb,EAAW,GAAK,EAAQ,QAAU,GAAU,MAElD,OAAO,CACT,EAAG,CAAC,EAAO,EAAW,CAAQ,CAAC,EAIzB,GAAoB,CAAC,GAAoB,IAAc,IAAM,IAAc,EAAY,EAAY,CAAK,EACxG,EAAY,EAAU,SAAW,GAAK,GAGtC,EAAS,GAAe,CAAC,IAAa,EAAU,OAAS,GAAK,GAI9D,EAAuB,GAAY,UAAU,SAC7C,GAAU,EACb,GAAgC,CAC/B,IAAM,EAAU,EAAW,QACtB,GAAS,GAAW,GAAwB,OACjD,EAAK,MAAM,SAAW,OAAO,iBAAiB,CAAO,CAAC,CAAC,SACzD,EACA,CAAC,CAAoB,CACvB,EAEM,OAAoB,CACpB,IACJ,AAEE,EAAe,WADf,aAAa,EAAe,OAAO,EACV,MAE3B,EAAe,EAAI,EACrB,EAEM,OAAmB,CACnB,EAAM,UAAS,EAAM,QAAQ,QAAU,IAC3C,EAAe,QAAU,eAAiB,CACxC,EAAe,EAAK,EACpB,EAAoB,IAAI,EACxB,EAAe,QAAU,KAGpB,GAAkB,EAAa,EAAY,EAAY,CAAK,CAAC,CACpE,EAAG,GAAG,CACR,EAEA,MAAgB,CACT,GAAU,IAAqB,MACpC,EAAqB,EAAU,QAAS,EAAU,SAAS,SAAS,EAAiB,CACvF,EAAG,CAAC,EAAQ,CAAgB,CAAC,EAE7B,IAAM,EAAe,EAClB,GAA4B,CAC3B,EAAa,EAAK,OAAS,EAAK,KAAK,EACrC,EAAe,EAAK,EACpB,EAAoB,IAAI,EACpB,GAAe,EAAc,IAAI,EAAK,KAAK,EAC3C,GAAU,EAAS,EAAK,KAAK,EACjC,EAAS,EAAK,KAAK,CACrB,EACA,CAAC,EAAe,EAAU,CAAQ,CACpC,EAEM,GAAY,EACf,GAA+B,CAC9B,GAAI,EAAU,OACd,IAAM,EAAU,EAAM,MAAQ,WAAa,EAAM,MAAQ,YAEzD,GAAI,CAAC,EAAQ,CACP,IACF,EAAM,eAAe,EACrB,EAAe,EAAI,GAErB,MACF,CACA,GAAI,EAAS,CACX,EAAM,eAAe,EACrB,IAAM,EAAO,EAAM,MAAQ,YAAc,EAAI,GAC7C,EAAqB,GAKF,IAAQ,MAAQ,KAAS,IAAS,EAAI,EAAU,OAAS,EAAI,GAC5D,KAAO,EAAU,EAAK,EAAM,EAAU,OAAQ,EAAO,CACxE,CACH,MAAW,EAAM,MAAQ,UACnB,IAAqB,MAAQ,EAAU,IAKzC,EAAM,eAAe,EACrB,EAAa,EAAU,EAAiB,GAC/B,IACT,EAAe,EAAK,EACpB,EAAoB,IAAI,GAG9B,EACA,CAAC,EAAW,EAAkB,EAAc,EAAkB,EAAU,CAAM,CAChF,EAEM,GACJ,EAAC,MAAD,CAAK,IAAK,GAAS,UAAW,EAAG,EAAO,SAAU,GAAY,QAAQ,EAAG,MAAO,GAAY,SAA5F,SAAA,CACG,GACC,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,cAAe,GAAY,KAAK,EACrD,MAAO,GAAY,MAEnB,YAAc,GAAM,EAAE,eAAe,EAEpC,SAAA,EAAK,KACH,CAAA,EAEP,EAAC,MAAD,CAAK,IAAK,EAAW,UAAW,EAAO,eACpC,SAAA,EAAU,KAAK,EAAM,IACpB,EAAC,MAAD,CAEE,UAAW,EACT,EAAO,aACP,IAAqB,GAAO,EAAO,wBACnC,GAAY,IACd,EACA,MAAO,GAAY,KAEnB,YAAc,GAAM,EAAE,eAAe,EACrC,YAAe,EAAa,CAAI,EAE/B,SAAA,EAAK,OAAS,EAAK,KACjB,EAZE,GAAG,EAAI,GAAG,EAAK,MAAM,GAAG,EAAK,OAY/B,CACN,CACE,CAAA,CACF,IAGP,OACE,EAAC,MAAD,CACE,GAAI,GACJ,IAAK,EACL,UAAW,EAAG,EAAO,cAAe,GAAY,KAAM,CAAS,EAC/D,MAAO,CAAE,GAAG,GAAY,KAAM,GAAG,CAAM,EACvC,QAAS,GACT,OAAQ,GACG,aAPb,SAAA,CASE,EAAC,GAAD,CAIU,SACR,aAAe,GAAS,CAClB,IACJ,EAAe,EAAK,EACpB,EAAoB,IAAI,EAC1B,EAGA,aAAc,EAAa,QAC3B,UAAW,EAAiB,OAC5B,MAAO,EAAa,MACpB,OAAQ,EACR,UAAW,GACX,eAAgB,GAChB,oBAAqB,GACrB,YAAa,GACb,WAAY,CAAE,MAAO,EAAO,kBAAmB,EAC/C,QAAS,GAET,SAAA,EAAC,EAAD,CAGE,aAAa,MACN,SACP,MAAO,EACG,WACV,cAAe,KACf,WAAY,EACZ,QAAS,EAAiB,GAAc,SAAU,CAAC,EAAc,SAAS,EAAY,CAAC,EACvF,aACE,EACI,EAAc,QACX,EAAc,uBAAuB,GAAK,GAC3C,GACD,GAAS,GAEhB,SAAW,GAAU,CAInB,GAHA,EAAa,CAAK,EAClB,EAAoB,IAAI,EACxB,EAAe,EAAI,EACf,EACE,GAAe,EAAc,IAAI,CAAK,EACtC,GAAU,EAAS,CAAK,OACvB,GAAI,IAAU,GACf,GAAe,EAAc,IAAI,EAAE,EACnC,GAAU,EAAS,EAAE,EACzB,EAAS,EAAE,MACN,CAEL,IAAM,EAAQ,EAAM,KAAM,IAAU,EAAK,OAAS,EAAK,MAAA,CAAO,YAAY,IAAM,EAAM,YAAY,CAAC,EAC/F,IACE,GAAe,EAAc,IAAI,EAAM,KAAK,EAC5C,GAAU,EAAS,EAAM,KAAK,EAClC,EAAS,EAAM,KAAK,EAExB,CACF,EACa,cACd,CAAA,CACM,CAAA,EAGT,EAAC,OAAD,CAAM,UAAW,EAAO,oBAAqB,KAAK,SAAS,YAAU,SAClE,SAAA,GAAU,EAAY,EAAK,MAAQ,EAChC,CAAA,CACH,GAET"}
1
+ {"version":3,"file":"inputDropdown.mjs","names":[],"sources":["../../../../package/common/inputDropdown/inputDropdown.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport cx from 'classnames';\nimport styles from './inputDropdown.module.pcss';\nimport Input from '../input';\nimport Popover from '../popover';\nimport { PopoverAlign, PopoverPlacement } from '../popover/popover.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport type { InputDropdownItem, InputDropdownLabels, InputDropdownProps } from './inputDropdown.types';\nimport { nextIndex, scrollOptionIntoView } from '../utils/listNavigation';\n\nfunction getKeyFromProps(value: string | undefined, formValidator: InputDropdownProps['formValidator']): string {\n return formValidator ? ((formValidator.value as string) ?? '') : (value ?? '');\n}\n\nfunction labelForKey(key: string, items: InputDropdownItem[]): string {\n const match = items.find((item) => item.value === key);\n return match ? (match.label ?? match.value) : key;\n}\n\n/**\n * The suggestions do not wrap: the list is filtered by what is being typed, so running off one\n * end and reappearing at the other reads as the list having changed under the caret.\n */\nconst NO_WRAP = { wrap: false };\n\nconst DEFAULT_LABELS: Required<InputDropdownLabels> = { empty: 'No matches' };\n\nexport default function InputDropdown(props: InputDropdownProps) {\n const {\n label = '',\n value = '',\n formValidator = null,\n error,\n isRequired = false,\n items = [],\n allowCustomValue = false,\n maxItems = 100,\n className,\n style,\n classNames,\n styles: slotStyles,\n onSelect = () => null,\n onChange,\n placeholder = '',\n readOnly = false,\n labels,\n ...rest\n } = useComponentDefaults('InputDropdown', props);\n const text = useLabels('InputDropdown', labels, DEFAULT_LABELS);\n const currentKey = getKeyFromProps(value, formValidator);\n const [, forceUpdate] = useState(0);\n const fvRef = useRef(formValidator);\n\n useEffect(() => {\n fvRef.current = formValidator;\n if (!formValidator) return;\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => {\n formValidator.removeOnUpdateListener(listener);\n };\n }, [formValidator]);\n\n const computedIsRequired =\n isRequired || !!formValidator?.validators.find((v) => (v as { validatorId?: string }).validatorId === 'required');\n\n const [inputText, setInputText] = useState(() => labelForKey(currentKey, items));\n const [prevKey, setPrevKey] = useState(currentKey);\n const [isMenuShown, setIsMenuShown] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState<number | null>(null);\n const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Sync display text when the controlled key changes externally (e.g. parent reset).\n if (currentKey !== prevKey) {\n setPrevKey(currentKey);\n const match = items.find((item) => item.value === currentKey);\n if (match) setInputText(match.label ?? match.value);\n else if (allowCustomValue) setInputText(currentKey);\n }\n\n // `items` often arrives after the value, leaving the raw key on display. Not while open, or a key\n // being typed would turn into its label under the caret.\n if (!isMenuShown && !allowCustomValue && inputText === currentKey) {\n const selectedLabel = labelForKey(currentKey, items);\n if (selectedLabel !== inputText) setInputText(selectedLabel);\n }\n\n const currItems = useMemo(() => {\n const needle = inputText.toLowerCase();\n const matches: InputDropdownItem[] = [];\n for (const item of items) {\n if (!(item.label ?? item.value).toLowerCase().includes(needle)) continue;\n matches.push(item);\n if (maxItems > 0 && matches.length >= maxItems) break;\n }\n return matches;\n }, [items, inputText, maxItems]);\n\n // Only text that blur swaps for the selection's label needs a warning. Comparing with the label\n // rather than '' keeps a preset value missing from `items` from showing \"No matches\" unprompted.\n const isDiscardedOnBlur = !allowCustomValue && inputText !== '' && inputText !== labelForKey(currentKey, items);\n const showEmpty = currItems.length === 0 && isDiscardedOnBlur;\n // Closed rather than hidden when empty: a mounted panel stays on the overlay stack, so inside a\n // Modal an invisible list would take the Escape meant for the modal.\n const isOpen = isMenuShown && !readOnly && (currItems.length > 0 || showEmpty);\n\n // A ref callback rather than a layout effect: the list is portaled out of the field's font size,\n // and Popover positions it in its own layout effect, which runs before this component's would.\n const menuFontSizeOverride = slotStyles?.dropdown?.fontSize;\n const menuRef = useCallback(\n (menu: HTMLDivElement | null) => {\n const trigger = triggerRef.current;\n if (!menu || !trigger || menuFontSizeOverride != null) return;\n menu.style.fontSize = window.getComputedStyle(trigger).fontSize;\n },\n [menuFontSizeOverride],\n );\n\n const handleFocus = () => {\n if (readOnly) return;\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n hideTimeoutRef.current = null;\n }\n setIsMenuShown(true);\n };\n\n const handleBlur = () => {\n if (fvRef.current) fvRef.current.touched = true;\n hideTimeoutRef.current = setTimeout(() => {\n setIsMenuShown(false);\n setHighlightedIndex(null);\n hideTimeoutRef.current = null;\n // Without custom values, uncommitted typed text is invalid — snap the\n // display back to the currently selected item's label.\n if (!allowCustomValue) setInputText(labelForKey(currentKey, items));\n }, 100);\n };\n\n useEffect(() => {\n if (!isOpen || highlightedIndex === null) return;\n scrollOptionIntoView(scrollRef.current, scrollRef.current?.children[highlightedIndex]);\n }, [isOpen, highlightedIndex]);\n\n const handleSelect = useCallback(\n (item: InputDropdownItem) => {\n setInputText(item.label ?? item.value);\n setIsMenuShown(false);\n setHighlightedIndex(null);\n if (formValidator) formValidator.set(item.value);\n if (onChange) onChange(item.value);\n onSelect(item.value);\n },\n [formValidator, onChange, onSelect],\n );\n\n const onKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (readOnly) return;\n const isArrow = event.key === 'ArrowUp' || event.key === 'ArrowDown';\n // Enter belongs to the form while closed, or it would pick a suggestion the user never saw.\n if (!isOpen) {\n if (isArrow) {\n event.preventDefault();\n setIsMenuShown(true);\n }\n return;\n }\n if (isArrow) {\n event.preventDefault();\n const step = event.key === 'ArrowDown' ? 1 : -1;\n setHighlightedIndex((idx) => {\n // The text being typed is part of the cycle, so both ends lead back to it and on round\n // to the far end: last → text → first, first → text → last. That is how a browser's own\n // address bar behaves, and it is why this list does not wrap straight around the way\n // Dropdown and MultiSelect do — they have no typed text to pass through.\n const atFarEnd = idx !== null && idx === (step === 1 ? currItems.length - 1 : 0);\n return atFarEnd ? null : nextIndex(idx, step, currItems.length, NO_WRAP);\n });\n } else if (event.key === 'Enter') {\n if (highlightedIndex !== null && currItems[highlightedIndex]) {\n // This Enter picked a suggestion, so it must not also reach the form around the field.\n // Without this the browser's implicit submission runs as well, and choosing from the\n // list submitted the form underneath the user. The blank state deliberately does not\n // do this: Enter there means the text as typed, and submitting is a fair reading of it.\n event.preventDefault();\n handleSelect(currItems[highlightedIndex]);\n } else if (allowCustomValue) {\n setIsMenuShown(false);\n setHighlightedIndex(null);\n }\n }\n },\n [currItems, highlightedIndex, handleSelect, allowCustomValue, readOnly, isOpen],\n );\n\n const menu = (\n <div ref={menuRef} className={cx(styles.dropdown, classNames?.dropdown)} style={slotStyles?.dropdown}>\n {showEmpty && (\n <div\n className={cx(styles.dropdownEmpty, classNames?.empty)}\n style={slotStyles?.empty}\n // A blur would throw the typed text away.\n onMouseDown={(e) => e.preventDefault()}\n >\n {text.empty}\n </div>\n )}\n <div ref={scrollRef} className={styles.dropdownScroll}>\n {currItems.map((item, key) => (\n <div\n key={`${key}_${item.label}_${item.value}`}\n className={cx(\n styles.dropdownItem,\n highlightedIndex === key && styles.dropdownItemHighlighted,\n classNames?.item,\n )}\n style={slotStyles?.item}\n // Prevent the input from blurring before the click resolves.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => handleSelect(item)}\n >\n {item.label ?? item.value}\n </div>\n ))}\n </div>\n </div>\n );\n\n return (\n <div\n {...rest}\n ref={triggerRef}\n className={cx(styles.inputDropdown, classNames?.root, className)}\n style={{ ...slotStyles?.root, ...style }}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={onKeyDown}\n >\n <Popover\n // The field owns the open state: it opens on typing and closes on blur, on\n // a pick, or on Escape. Only a close is taken from Popover, which is the\n // outside click its portaled panel would otherwise not see.\n isOpen={isOpen}\n onOpenChange={(next) => {\n if (next) return;\n setIsMenuShown(false);\n setHighlightedIndex(null);\n }}\n // Never a sheet, whatever the app-wide `presentation`: its backdrop would cover the field\n // being typed into.\n presentation={Presentation.Default}\n placement={PopoverPlacement.Bottom}\n align={PopoverAlign.Start}\n offset={0}\n withArrow={false}\n constrainWidth={false}\n closeOnTriggerClick={false}\n focusOnOpen={false}\n classNames={{ panel: styles.inputDropdownPanel }}\n content={menu}\n >\n <Input\n // A combobox renders its own suggestion list; the browser's autofill dropdown\n // opens on top of it, so two lists compete for the same space.\n autoComplete='off'\n label={label}\n value={inputText}\n readOnly={readOnly}\n formValidator={null}\n isRequired={computedIsRequired}\n isValid={formValidator ? !formValidator.shouldShowError : !error}\n errorMessage={\n formValidator\n ? formValidator.shouldShowError\n ? (formValidator.getCurrentErrorMessage() ?? '')\n : ''\n : (error ?? '')\n }\n onChange={(typed) => {\n setInputText(typed);\n setHighlightedIndex(null);\n setIsMenuShown(true);\n if (allowCustomValue) {\n if (formValidator) formValidator.set(typed);\n if (onChange) onChange(typed);\n } else if (typed === '') {\n if (formValidator) formValidator.set('');\n if (onChange) onChange('');\n onSelect('');\n } else {\n // Emit when the typed text exactly matches an item's label/value.\n const match = items.find((item) => (item.label ?? item.value).toLowerCase() === typed.toLowerCase());\n if (match) {\n if (formValidator) formValidator.set(match.value);\n if (onChange) onChange(match.value);\n onSelect(match.value);\n }\n }\n }}\n placeholder={placeholder}\n />\n </Popover>\n {/* Outside the portal and always mounted: a live region that mounts already filled is not\n reliably announced. */}\n <span className={styles.inputDropdownStatus} role='status' aria-live='polite'>\n {isOpen && showEmpty ? text.empty : ''}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{PopoverAlign as n,PopoverPlacement as r,PopoverTrigger as ee}from"../popover/popover.types.mjs";import te from"../popover/popover.mjs";import ne from"../chip/chip.mjs";import re from"../tooltip/tooltip.mjs";import{edgeIndex as ie,nextIndex as ae,scrollOptionIntoView as oe}from"../utils/listNavigation.mjs";import se from"../searchInput/searchInput.mjs";import i from"./multiSelect.module.mjs";import{useEffect as a,useId as ce,useRef as o,useState as s}from"react";import c from"classnames";import{FontAwesomeIcon as l}from"@fortawesome/react-fontawesome";import{faCheck as le,faChevronDown as ue,faXmark as de}from"@fortawesome/free-solid-svg-icons";import{jsx as u,jsxs as d}from"react/jsx-runtime";var fe=[],pe={clearSelection:`Clear selection`,create:e=>`Add "${e}"`,searchPlaceholder:`Search…`,empty:`No matches`};function f(f){let{items:p,value:m,defaultValue:me=[],onChange:he=()=>{},label:h=``,alwaysFloatLabel:ge=!1,placeholder:g=``,searchable:_e=!1,allowCustomValues:_=!1,maxSelected:v,clearable:ve=!0,disabled:y=!1,readOnly:b=!1,presentation:ye,formValidator:x=null,errorMessage:S=``,isValid:be=!0,className:xe,style:Se,classNames:C,styles:w,onMouseEnter:Ce,onMouseLeave:we,onFocus:Te,onBlur:Ee,labels:De,...Oe}=e(`MultiSelect`,f),T=t(`MultiSelect`,De,pe),E=o(null),D=o(null),O=o(null),k=o(null),A=ce(),[ke,Ae]=s(me),[j,je]=s(!1),[M,N]=s(``),[P,F]=s(0),[I,L]=s(!1),[Me,Ne]=s(!1),[Pe,Fe]=s(),R=o(x),[,Ie]=s(0);a(()=>{R.current=x;let e=R.current;if(!e)return;e.label=h,e.validate();let t=()=>Ie(e=>e+1);return e.registerOnUpdateListener(t),()=>{e.removeOnUpdateListener(t)}},[x,h]);let z=m!==void 0&&!x,B=x?x.value:z?m:ke,V=Array.isArray(B)?B:fe,H=x?x.touched&&x.hasError():!be||!!S,Le=x?x.getCurrentErrorMessage():S||``,U=!y&&!b,W=v!==void 0&&V.length>=v,G=ge||j||I||V.length>0,K=_e||_,q=M.trim(),J=K?q.toLowerCase():``,Y=J?p.filter(e=>e.label.toLowerCase().includes(J)):p,X=_&&q.length>0&&!W&&!p.some(e=>e.label.toLowerCase()===J||String(e.key).toLowerCase()===J)&&!V.some(e=>String(e).toLowerCase()===J),Z=X?[...Y,{key:q,label:T.create(q)}]:Y,Re=V.map(e=>p.find(t=>t.key===e)??(_?{key:e,label:String(e)}:void 0)).filter(e=>e!==void 0);function Q(e){x?x.set(e):z||Ae(e),he(e)}function $(e){U&&!e.disabled&&(V.includes(e.key)?Q(V.filter(t=>t!==e.key)):W||Q([...V,e.key]),X&&e.key===q&&N(``))}function ze(){E.current?.contains(document.activeElement)&&D.current?.focus()}function Be(e){(!e||U)&&(je(e),e?(Fe(D.current?.offsetWidth),F(0)):(N(``),R.current&&(R.current.touched=!0)))}a(()=>{j&&K&&O.current?.querySelector(`input`)?.focus()},[j,K]),a(()=>{j&&oe(k.current,document.getElementById(`${A}-${P}`))},[j,P,A]);let Ve={isDisabled:e=>{let t=Z[e];return!!t.disabled||W&&!V.includes(t.key)}};function He(e){if(U){if(!j){e.key===`ArrowDown`||e.key===`Enter`||e.key===` `?(e.preventDefault(),Be(!0)):e.key===`Backspace`&&V.length>0&&Q(V.slice(0,-1));return}if(e.key===`ArrowDown`||e.key===`ArrowUp`)e.preventDefault(),F(ae(P,e.key===`ArrowDown`?1:-1,Z.length,Ve)??0);else if(e.key===`Home`||e.key===`End`)e.preventDefault(),F(ie(e.key===`Home`?`first`:`last`,Z.length,Ve)??0);else if(e.key===`Enter`){e.preventDefault();let t=Z[P];t&&$(t)}else e.key===`Backspace`&&!M&&V.length>0&&Q(V.slice(0,-1))}}let Ue=d(`div`,{className:c(i.multiSelectPanel,C?.panel),style:w?.panel,children:[K&&u(`div`,{ref:O,className:c(i.multiSelectSearch,C?.search),style:w?.search,children:u(se,{value:M,onChange:e=>{N(e),F(0)},placeholder:T.searchPlaceholder,onKeyDown:He})}),u(`div`,{ref:k,id:A,role:`listbox`,"aria-multiselectable":`true`,"aria-label":h||void 0,className:c(i.multiSelectOptions,C?.options),style:w?.options,children:Z.length===0?u(`div`,{className:c(i.multiSelectEmpty,C?.empty),style:w?.empty,children:T.empty}):Z.map((e,t)=>{let n=V.includes(e.key),r=e.disabled||W&&!n;return d(`div`,{id:`${A}-${t}`,role:`option`,"aria-selected":n,"aria-disabled":r||void 0,className:c(i.multiSelectOption,t===P&&i.multiSelectOptionActive,n&&i.multiSelectOptionSelected,r&&i.multiSelectOptionDisabled,C?.option),style:w?.option,onMouseDown:e=>e.preventDefault(),onMouseEnter:()=>F(t),onClick:()=>$(e),children:[u(`span`,{className:c(i.multiSelectOptionCheck,C?.optionCheck),style:w?.optionCheck,children:n&&u(l,{icon:le})}),u(`span`,{className:c(i.multiSelectOptionLabel,C?.optionLabel),style:w?.optionLabel,children:e.label})]},e.key)})})]});return d(`div`,{...Oe,ref:E,className:c(i.multiSelect,y&&i.multiSelectDisabled,!!h&&i.multiSelectWithLabel,C?.root,xe),style:{...Se,...w?.root},onMouseEnter:e=>{Ne(!0),Ce?.(e)},onMouseLeave:e=>{Ne(!1),we?.(e)},onFocus:e=>{L(!0),Te?.(e)},onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||(L(!1),Ee?.(e))},children:[u(te,{isOpen:j,onOpenChange:Be,trigger:ee.Click,placement:r.Bottom,align:n.Start,offset:4,withArrow:!1,focusOnOpen:K,constrainWidth:!1,width:`trigger`,disabled:!U,presentation:ye,content:Ue,styles:{panel:Pe?{minWidth:Pe}:void 0},children:d(`div`,{ref:D,role:`combobox`,tabIndex:y?-1:0,"aria-expanded":j,"aria-haspopup":`listbox`,"aria-controls":A,"aria-activedescendant":j&&Z.length>0?`${A}-${P}`:void 0,"aria-disabled":y||void 0,"aria-readonly":b||void 0,className:c(i.multiSelectControl,C?.control),style:w?.control,onKeyDown:He,children:[g&&Re.length===0&&u(`span`,{className:c(i.multiSelectPlaceholder,(!h||G)&&i.multiSelectPlaceholderShown,!h&&i.multiSelectPlaceholderInstant,C?.placeholder),style:w?.placeholder,children:g}),u(`div`,{className:c(i.multiSelectValues,C?.values),style:w?.values,children:Re.map(e=>u(ne,{text:e.label,showRemoveIcon:U,labels:{remove:`Remove ${e.label}`},onMouseDown:e=>e.preventDefault(),onRemove:()=>{$(e),ze()},className:c(i.multiSelectChip,C?.chip),style:w?.chip},e.key))}),ve&&U&&V.length>0&&u(`button`,{type:`button`,tabIndex:-1,"aria-label":T.clearSelection,className:c(i.multiSelectClearButton,C?.clearButton),style:w?.clearButton,onMouseDown:e=>e.preventDefault(),onClick:e=>{e.stopPropagation(),Q([]),ze()},children:u(l,{icon:de})}),u(l,{icon:ue,className:c(i.multiSelectIcon,j&&i.multiSelectIconOpen,C?.icon),style:w?.icon}),h&&u(`div`,{className:c(i.multiSelectLabel,G&&i.multiSelectLabelFloating,C?.label),style:w?.label,children:h}),u(`fieldset`,{className:c(i.multiSelectFieldset,(I||j)&&i.multiSelectFieldsetFocused,H&&i.multiSelectFieldsetError,C?.fieldset),style:w?.fieldset,children:u(`legend`,{className:c(i.multiSelectFieldsetLegend,G&&i.multiSelectFieldsetLegendActive),children:u(`span`,{children:h})})})]})}),u(re,{variant:`error`,message:Le,isVisible:H&&(I||Me)})]})}export{f as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{PopoverAlign as n,PopoverPlacement as r,PopoverTrigger as ee}from"../popover/popover.types.mjs";import te from"../popover/popover.mjs";import ne from"../chip/chip.mjs";import re from"../tooltip/tooltip.mjs";import{edgeIndex as ie,nextIndex as ae,scrollOptionIntoView as oe}from"../utils/listNavigation.mjs";import se from"../searchInput/searchInput.mjs";import i from"./multiSelect.module.mjs";import{useEffect as a,useId as ce,useRef as o,useState as s}from"react";import c from"classnames";import{FontAwesomeIcon as l}from"@fortawesome/react-fontawesome";import{faCheck as le,faChevronDown as ue,faXmark as de}from"@fortawesome/free-solid-svg-icons";import{jsx as u,jsxs as d}from"react/jsx-runtime";var fe=[],pe={clearSelection:`Clear selection`,create:e=>`Add "${e}"`,searchPlaceholder:`Search…`,empty:`No matches`};function f(f){let{items:p,value:m,defaultValue:me=[],onChange:he=()=>{},label:h=``,alwaysFloatLabel:ge=!1,placeholder:g=``,searchable:_e=!1,allowCustomValues:_=!1,maxSelected:v,clearable:ve=!0,disabled:y=!1,readOnly:b=!1,presentation:ye,formValidator:x=null,errorMessage:S=``,isValid:be=!0,className:xe,style:Se,classNames:C,styles:w,onMouseEnter:Ce,onMouseLeave:we,onFocus:Te,onBlur:Ee,labels:De,...Oe}=e(`MultiSelect`,f),T=t(`MultiSelect`,De,pe),E=o(null),D=o(null),O=o(null),k=o(null),A=ce(),[ke,Ae]=s(me),[j,je]=s(!1),[M,N]=s(``),[P,F]=s(0),[I,L]=s(!1),[Me,Ne]=s(!1),[Pe,Fe]=s(),R=o(x),[,Ie]=s(0);a(()=>{R.current=x;let e=R.current;if(!e)return;e.label=h,e.validate();let t=()=>Ie(e=>e+1);return e.registerOnUpdateListener(t),()=>{e.removeOnUpdateListener(t)}},[x,h]);let z=m!==void 0&&!x,B=x?x.value:z?m:ke,V=Array.isArray(B)?B:fe,H=x?x.shouldShowError:!be||!!S,Le=x?x.getCurrentErrorMessage():S||``,U=!y&&!b,W=v!==void 0&&V.length>=v,G=ge||j||I||V.length>0,K=_e||_,q=M.trim(),J=K?q.toLowerCase():``,Y=J?p.filter(e=>e.label.toLowerCase().includes(J)):p,X=_&&q.length>0&&!W&&!p.some(e=>e.label.toLowerCase()===J||String(e.key).toLowerCase()===J)&&!V.some(e=>String(e).toLowerCase()===J),Z=X?[...Y,{key:q,label:T.create(q)}]:Y,Re=V.map(e=>p.find(t=>t.key===e)??(_?{key:e,label:String(e)}:void 0)).filter(e=>e!==void 0);function Q(e){x?x.set(e):z||Ae(e),he(e)}function $(e){U&&!e.disabled&&(V.includes(e.key)?Q(V.filter(t=>t!==e.key)):W||Q([...V,e.key]),X&&e.key===q&&N(``))}function ze(){E.current?.contains(document.activeElement)&&D.current?.focus()}function Be(e){(!e||U)&&(je(e),e?(Fe(D.current?.offsetWidth),F(0)):(N(``),R.current&&(R.current.touched=!0)))}a(()=>{j&&K&&O.current?.querySelector(`input`)?.focus()},[j,K]),a(()=>{j&&oe(k.current,document.getElementById(`${A}-${P}`))},[j,P,A]);let Ve={isDisabled:e=>{let t=Z[e];return!!t.disabled||W&&!V.includes(t.key)}};function He(e){if(U){if(!j){e.key===`ArrowDown`||e.key===`Enter`||e.key===` `?(e.preventDefault(),Be(!0)):e.key===`Backspace`&&V.length>0&&Q(V.slice(0,-1));return}if(e.key===`ArrowDown`||e.key===`ArrowUp`)e.preventDefault(),F(ae(P,e.key===`ArrowDown`?1:-1,Z.length,Ve)??0);else if(e.key===`Home`||e.key===`End`)e.preventDefault(),F(ie(e.key===`Home`?`first`:`last`,Z.length,Ve)??0);else if(e.key===`Enter`){e.preventDefault();let t=Z[P];t&&$(t)}else e.key===`Backspace`&&!M&&V.length>0&&Q(V.slice(0,-1))}}let Ue=d(`div`,{className:c(i.multiSelectPanel,C?.panel),style:w?.panel,children:[K&&u(`div`,{ref:O,className:c(i.multiSelectSearch,C?.search),style:w?.search,children:u(se,{value:M,onChange:e=>{N(e),F(0)},placeholder:T.searchPlaceholder,onKeyDown:He})}),u(`div`,{ref:k,id:A,role:`listbox`,"aria-multiselectable":`true`,"aria-label":h||void 0,className:c(i.multiSelectOptions,C?.options),style:w?.options,children:Z.length===0?u(`div`,{className:c(i.multiSelectEmpty,C?.empty),style:w?.empty,children:T.empty}):Z.map((e,t)=>{let n=V.includes(e.key),r=e.disabled||W&&!n;return d(`div`,{id:`${A}-${t}`,role:`option`,"aria-selected":n,"aria-disabled":r||void 0,className:c(i.multiSelectOption,t===P&&i.multiSelectOptionActive,n&&i.multiSelectOptionSelected,r&&i.multiSelectOptionDisabled,C?.option),style:w?.option,onMouseDown:e=>e.preventDefault(),onMouseEnter:()=>F(t),onClick:()=>$(e),children:[u(`span`,{className:c(i.multiSelectOptionCheck,C?.optionCheck),style:w?.optionCheck,children:n&&u(l,{icon:le})}),u(`span`,{className:c(i.multiSelectOptionLabel,C?.optionLabel),style:w?.optionLabel,children:e.label})]},e.key)})})]});return d(`div`,{...Oe,ref:E,className:c(i.multiSelect,y&&i.multiSelectDisabled,!!h&&i.multiSelectWithLabel,C?.root,xe),style:{...Se,...w?.root},onMouseEnter:e=>{Ne(!0),Ce?.(e)},onMouseLeave:e=>{Ne(!1),we?.(e)},onFocus:e=>{L(!0),Te?.(e)},onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||(L(!1),Ee?.(e))},children:[u(te,{isOpen:j,onOpenChange:Be,trigger:ee.Click,placement:r.Bottom,align:n.Start,offset:4,withArrow:!1,focusOnOpen:K,constrainWidth:!1,width:`trigger`,disabled:!U,presentation:ye,content:Ue,styles:{panel:Pe?{minWidth:Pe}:void 0},children:d(`div`,{ref:D,role:`combobox`,tabIndex:y?-1:0,"aria-expanded":j,"aria-haspopup":`listbox`,"aria-controls":A,"aria-activedescendant":j&&Z.length>0?`${A}-${P}`:void 0,"aria-disabled":y||void 0,"aria-readonly":b||void 0,className:c(i.multiSelectControl,C?.control),style:w?.control,onKeyDown:He,children:[g&&Re.length===0&&u(`span`,{className:c(i.multiSelectPlaceholder,(!h||G)&&i.multiSelectPlaceholderShown,!h&&i.multiSelectPlaceholderInstant,C?.placeholder),style:w?.placeholder,children:g}),u(`div`,{className:c(i.multiSelectValues,C?.values),style:w?.values,children:Re.map(e=>u(ne,{text:e.label,showRemoveIcon:U,labels:{remove:`Remove ${e.label}`},onMouseDown:e=>e.preventDefault(),onRemove:()=>{$(e),ze()},className:c(i.multiSelectChip,C?.chip),style:w?.chip},e.key))}),ve&&U&&V.length>0&&u(`button`,{type:`button`,tabIndex:-1,"aria-label":T.clearSelection,className:c(i.multiSelectClearButton,C?.clearButton),style:w?.clearButton,onMouseDown:e=>e.preventDefault(),onClick:e=>{e.stopPropagation(),Q([]),ze()},children:u(l,{icon:de})}),u(l,{icon:ue,className:c(i.multiSelectIcon,j&&i.multiSelectIconOpen,C?.icon),style:w?.icon}),h&&u(`div`,{className:c(i.multiSelectLabel,G&&i.multiSelectLabelFloating,C?.label),style:w?.label,children:h}),u(`fieldset`,{className:c(i.multiSelectFieldset,(I||j)&&i.multiSelectFieldsetFocused,H&&i.multiSelectFieldsetError,C?.fieldset),style:w?.fieldset,children:u(`legend`,{className:c(i.multiSelectFieldsetLegend,G&&i.multiSelectFieldsetLegendActive),children:u(`span`,{children:h})})})]})}),u(re,{variant:`error`,message:Le,isVisible:H&&(I||Me)})]})}export{f as default};
2
2
  //# sourceMappingURL=multiSelect.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"multiSelect.mjs","names":[],"sources":["../../../../package/common/multiSelect/multiSelect.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from 'react';\nimport type { ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faChevronDown, faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\n\nimport Chip from 'components/common/chip';\nimport SearchInput from 'components/common/searchInput';\nimport Tooltip from 'components/common/tooltip';\nimport Popover from 'components/common/popover';\nimport { PopoverAlign, PopoverPlacement, PopoverTrigger } from '../popover/popover.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { edgeIndex, nextIndex, scrollOptionIntoView } from '../utils/listNavigation';\nimport type { MultiSelectItem, MultiSelectLabels, MultiSelectProps, MultiSelectValue } from './multiSelect.types';\n\nimport styles from './multiSelect.module.pcss';\n\nconst EMPTY: MultiSelectValue[] = [];\n\nconst DEFAULT_LABELS: Required<MultiSelectLabels> = {\n clearSelection: 'Clear selection',\n create: (value) => `Add \"${value}\"`,\n searchPlaceholder: 'Search…',\n empty: 'No matches',\n};\n\nfunction MultiSelect(props: MultiSelectProps): ReactElement {\n const {\n items,\n value,\n defaultValue = [],\n onChange = () => {},\n label = '',\n alwaysFloatLabel = false,\n placeholder = '',\n searchable = false,\n allowCustomValues = false,\n maxSelected,\n clearable = true,\n disabled = false,\n readOnly = false,\n presentation,\n formValidator = null,\n errorMessage = '',\n isValid = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n labels,\n ...rest\n } = useComponentDefaults('MultiSelect', props);\n const text = useLabels('MultiSelect', labels, DEFAULT_LABELS);\n\n const rootRef = useRef<HTMLDivElement>(null);\n const controlRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLDivElement>(null);\n const optionsRef = useRef<HTMLDivElement>(null);\n const listboxId = useId();\n\n const [internalValue, setInternalValue] = useState<MultiSelectValue[]>(defaultValue);\n const [isOpen, setIsOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [activeIndex, setActiveIndex] = useState(0);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [panelWidth, setPanelWidth] = useState<number>();\n\n // A ref as well as the prop: react-hooks/immutability refuses a mutation of the prop itself in\n // an event handler, and marking the validator touched happens in one.\n const fvRef = useRef(formValidator);\n const [, forceUpdate] = useState(0);\n useEffect(() => {\n fvRef.current = formValidator;\n const fv = fvRef.current;\n if (!fv) return;\n fv.label = label;\n fv.validate();\n const listener = () => forceUpdate((n) => n + 1);\n fv.registerOnUpdateListener(listener);\n return () => {\n fv.removeOnUpdateListener(listener);\n };\n }, [formValidator, label]);\n\n const isControlled = value !== undefined && !formValidator;\n const rawValue = formValidator ? formValidator.value : isControlled ? value : internalValue;\n const selected = Array.isArray(rawValue) ? (rawValue as MultiSelectValue[]) : EMPTY;\n\n const hasError = formValidator ? formValidator.touched && formValidator.hasError() : !isValid || !!errorMessage;\n const errorText = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n const isInteractive = !disabled && !readOnly;\n const atMax = maxSelected !== undefined && selected.length >= maxSelected;\n\n const shouldFloatLabel = alwaysFloatLabel || isOpen || isFocused || selected.length > 0;\n\n const isSearchable = searchable || allowCustomValues;\n const typed = search.trim();\n const needle = isSearchable ? typed.toLowerCase() : '';\n const matches = needle ? items.filter((item) => item.label.toLowerCase().includes(needle)) : items;\n\n const canCreate =\n allowCustomValues &&\n typed.length > 0 &&\n !atMax &&\n !items.some((item) => item.label.toLowerCase() === needle || String(item.key).toLowerCase() === needle) &&\n !selected.some((key) => String(key).toLowerCase() === needle);\n\n const visibleItems: MultiSelectItem[] = canCreate ? [...matches, { key: typed, label: text.create(typed) }] : matches;\n\n // Mapped from `selected`, not filtered from `items`, to keep the chips in pick order. A custom\n // value has no entry in `items`, so it stands in for itself.\n const selectedItems = selected\n .map<MultiSelectItem | undefined>(\n (key) => items.find((item) => item.key === key) ?? (allowCustomValues ? { key, label: String(key) } : undefined),\n )\n .filter((item): item is MultiSelectItem => item !== undefined);\n\n function commit(next: MultiSelectValue[]) {\n if (formValidator) formValidator.set(next);\n else if (!isControlled) setInternalValue(next);\n onChange(next);\n }\n\n function toggle(item: MultiSelectItem) {\n if (!isInteractive || item.disabled) return;\n if (selected.includes(item.key)) commit(selected.filter((key) => key !== item.key));\n else if (!atMax) commit([...selected, item.key]);\n // The typed text has become a chip, so leaving it in the box would offer to add it again.\n if (canCreate && item.key === typed) setSearch('');\n }\n\n /**\n * A focused element that unmounts fires no blur, so removing the chip or clear button that was\n * just clicked would leave `isFocused` stuck true. Hand focus back so there is still something\n * left to blur.\n */\n function keepFocusInControl() {\n if (rootRef.current?.contains(document.activeElement)) controlRef.current?.focus();\n }\n\n function handleOpenChange(next: boolean) {\n if (next && !isInteractive) return;\n setIsOpen(next);\n if (next) {\n setPanelWidth(controlRef.current?.offsetWidth);\n setActiveIndex(0);\n } else {\n setSearch('');\n if (fvRef.current) fvRef.current.touched = true;\n }\n }\n\n useEffect(() => {\n if (!isOpen || !isSearchable) return;\n searchRef.current?.querySelector('input')?.focus();\n }, [isOpen, isSearchable]);\n\n useEffect(() => {\n if (!isOpen) return;\n scrollOptionIntoView(optionsRef.current, document.getElementById(`${listboxId}-${activeIndex}`));\n }, [isOpen, activeIndex, listboxId]);\n\n // Matches what the options render as `aria-disabled`, so the arrows cannot land on one.\n const navigation = {\n isDisabled: (index: number) => {\n const item = visibleItems[index];\n return !!item.disabled || (atMax && !selected.includes(item.key));\n },\n };\n\n function handleKeyDown(event: React.KeyboardEvent) {\n if (!isInteractive) return;\n\n if (!isOpen) {\n if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleOpenChange(true);\n } else if (event.key === 'Backspace' && selected.length > 0) {\n commit(selected.slice(0, -1));\n }\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n setActiveIndex(nextIndex(activeIndex, event.key === 'ArrowDown' ? 1 : -1, visibleItems.length, navigation) ?? 0);\n } else if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault();\n setActiveIndex(edgeIndex(event.key === 'Home' ? 'first' : 'last', visibleItems.length, navigation) ?? 0);\n } else if (event.key === 'Enter') {\n event.preventDefault();\n const item = visibleItems[activeIndex];\n if (item) toggle(item);\n } else if (event.key === 'Backspace' && !search && selected.length > 0) {\n commit(selected.slice(0, -1));\n }\n }\n\n const panel = (\n <div className={cx(styles.multiSelectPanel, classNames?.panel)} style={slotStyles?.panel}>\n {isSearchable && (\n <div ref={searchRef} className={cx(styles.multiSelectSearch, classNames?.search)} style={slotStyles?.search}>\n <SearchInput\n value={search}\n onChange={(value) => {\n setSearch(value);\n setActiveIndex(0);\n }}\n placeholder={text.searchPlaceholder}\n onKeyDown={handleKeyDown}\n />\n </div>\n )}\n <div\n ref={optionsRef}\n id={listboxId}\n role='listbox'\n aria-multiselectable='true'\n aria-label={label || undefined}\n className={cx(styles.multiSelectOptions, classNames?.options)}\n style={slotStyles?.options}\n >\n {visibleItems.length === 0 ? (\n <div className={cx(styles.multiSelectEmpty, classNames?.empty)} style={slotStyles?.empty}>\n {text.empty}\n </div>\n ) : (\n visibleItems.map((item, index) => {\n const isSelected = selected.includes(item.key);\n const isDisabled = item.disabled || (atMax && !isSelected);\n\n return (\n <div\n key={item.key}\n id={`${listboxId}-${index}`}\n role='option'\n aria-selected={isSelected}\n aria-disabled={isDisabled || undefined}\n className={cx(\n styles.multiSelectOption,\n index === activeIndex && styles.multiSelectOptionActive,\n isSelected && styles.multiSelectOptionSelected,\n isDisabled && styles.multiSelectOptionDisabled,\n classNames?.option,\n )}\n style={slotStyles?.option}\n onMouseDown={(event) => event.preventDefault()}\n onMouseEnter={() => setActiveIndex(index)}\n onClick={() => toggle(item)}\n >\n <span\n className={cx(styles.multiSelectOptionCheck, classNames?.optionCheck)}\n style={slotStyles?.optionCheck}\n >\n {isSelected && <FontAwesomeIcon icon={faCheck} />}\n </span>\n <span\n className={cx(styles.multiSelectOptionLabel, classNames?.optionLabel)}\n style={slotStyles?.optionLabel}\n >\n {item.label}\n </span>\n </div>\n );\n })\n )}\n </div>\n </div>\n );\n\n return (\n <div\n {...rest}\n ref={rootRef}\n className={cx(\n styles.multiSelect,\n disabled && styles.multiSelectDisabled,\n !!label && styles.multiSelectWithLabel,\n classNames?.root,\n className,\n )}\n style={{ ...style, ...slotStyles?.root }}\n onMouseEnter={(event) => {\n setIsHovered(true);\n onMouseEnter?.(event);\n }}\n onMouseLeave={(event) => {\n setIsHovered(false);\n onMouseLeave?.(event);\n }}\n onFocus={(event) => {\n setIsFocused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n onBlur?.(event);\n }}\n >\n <Popover\n isOpen={isOpen}\n onOpenChange={handleOpenChange}\n trigger={PopoverTrigger.Click}\n placement={PopoverPlacement.Bottom}\n align={PopoverAlign.Start}\n offset={4}\n withArrow={false}\n // Neither half of this is the keyboard-only default. A search field takes\n // the caret however the panel was opened, and without one the control\n // keeps the focus and points at the active option with\n // `aria-activedescendant`, so nothing may take it away.\n focusOnOpen={isSearchable}\n constrainWidth={false}\n width='trigger'\n disabled={!isInteractive}\n presentation={presentation}\n content={panel}\n styles={{ panel: panelWidth ? { minWidth: panelWidth } : undefined }}\n >\n <div\n ref={controlRef}\n role='combobox'\n tabIndex={disabled ? -1 : 0}\n aria-expanded={isOpen}\n aria-haspopup='listbox'\n aria-controls={listboxId}\n aria-activedescendant={isOpen && visibleItems.length > 0 ? `${listboxId}-${activeIndex}` : undefined}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n className={cx(styles.multiSelectControl, classNames?.control)}\n style={slotStyles?.control}\n onKeyDown={handleKeyDown}\n >\n {placeholder && selectedItems.length === 0 && (\n <span\n className={cx(\n styles.multiSelectPlaceholder,\n (!label || shouldFloatLabel) && styles.multiSelectPlaceholderShown,\n !label && styles.multiSelectPlaceholderInstant,\n classNames?.placeholder,\n )}\n style={slotStyles?.placeholder}\n >\n {placeholder}\n </span>\n )}\n\n <div className={cx(styles.multiSelectValues, classNames?.values)} style={slotStyles?.values}>\n {selectedItems.map((item) => (\n <Chip\n key={item.key}\n text={item.label}\n showRemoveIcon={isInteractive}\n labels={{ remove: `Remove ${item.label}` }}\n onMouseDown={(event) => event.preventDefault()}\n onRemove={() => {\n toggle(item);\n keepFocusInControl();\n }}\n className={cx(styles.multiSelectChip, classNames?.chip)}\n style={slotStyles?.chip}\n />\n ))}\n </div>\n\n {clearable && isInteractive && selected.length > 0 && (\n <button\n type='button'\n tabIndex={-1}\n aria-label={text.clearSelection}\n className={cx(styles.multiSelectClearButton, classNames?.clearButton)}\n style={slotStyles?.clearButton}\n onMouseDown={(event) => event.preventDefault()}\n onClick={(event) => {\n event.stopPropagation();\n commit([]);\n keepFocusInControl();\n }}\n >\n <FontAwesomeIcon icon={faXmark} />\n </button>\n )}\n\n <FontAwesomeIcon\n icon={faChevronDown}\n className={cx(styles.multiSelectIcon, isOpen && styles.multiSelectIconOpen, classNames?.icon)}\n style={slotStyles?.icon as React.CSSProperties & Record<`--fa-font-${string}`, string>}\n />\n\n {label && (\n <div\n className={cx(\n styles.multiSelectLabel,\n shouldFloatLabel && styles.multiSelectLabelFloating,\n classNames?.label,\n )}\n style={slotStyles?.label}\n >\n {label}\n </div>\n )}\n\n {/* The visible border lives here rather than on the control, because the legend is what\n cuts the notch the floating label sits in. Same construction as Input. */}\n <fieldset\n className={cx(\n styles.multiSelectFieldset,\n (isFocused || isOpen) && styles.multiSelectFieldsetFocused,\n hasError && styles.multiSelectFieldsetError,\n classNames?.fieldset,\n )}\n style={slotStyles?.fieldset}\n >\n <legend\n className={cx(\n styles.multiSelectFieldsetLegend,\n shouldFloatLabel && styles.multiSelectFieldsetLegendActive,\n )}\n >\n <span>{label}</span>\n </legend>\n </fieldset>\n </div>\n </Popover>\n\n <Tooltip variant='error' message={errorText} isVisible={hasError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default MultiSelect;\n"],"mappings":"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"}
1
+ {"version":3,"file":"multiSelect.mjs","names":[],"sources":["../../../../package/common/multiSelect/multiSelect.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from 'react';\nimport type { ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faChevronDown, faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\n\nimport Chip from 'components/common/chip';\nimport SearchInput from 'components/common/searchInput';\nimport Tooltip from 'components/common/tooltip';\nimport Popover from 'components/common/popover';\nimport { PopoverAlign, PopoverPlacement, PopoverTrigger } from '../popover/popover.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { edgeIndex, nextIndex, scrollOptionIntoView } from '../utils/listNavigation';\nimport type { MultiSelectItem, MultiSelectLabels, MultiSelectProps, MultiSelectValue } from './multiSelect.types';\n\nimport styles from './multiSelect.module.pcss';\n\nconst EMPTY: MultiSelectValue[] = [];\n\nconst DEFAULT_LABELS: Required<MultiSelectLabels> = {\n clearSelection: 'Clear selection',\n create: (value) => `Add \"${value}\"`,\n searchPlaceholder: 'Search…',\n empty: 'No matches',\n};\n\nfunction MultiSelect(props: MultiSelectProps): ReactElement {\n const {\n items,\n value,\n defaultValue = [],\n onChange = () => {},\n label = '',\n alwaysFloatLabel = false,\n placeholder = '',\n searchable = false,\n allowCustomValues = false,\n maxSelected,\n clearable = true,\n disabled = false,\n readOnly = false,\n presentation,\n formValidator = null,\n errorMessage = '',\n isValid = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n onMouseEnter,\n onMouseLeave,\n onFocus,\n onBlur,\n labels,\n ...rest\n } = useComponentDefaults('MultiSelect', props);\n const text = useLabels('MultiSelect', labels, DEFAULT_LABELS);\n\n const rootRef = useRef<HTMLDivElement>(null);\n const controlRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLDivElement>(null);\n const optionsRef = useRef<HTMLDivElement>(null);\n const listboxId = useId();\n\n const [internalValue, setInternalValue] = useState<MultiSelectValue[]>(defaultValue);\n const [isOpen, setIsOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [activeIndex, setActiveIndex] = useState(0);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [panelWidth, setPanelWidth] = useState<number>();\n\n // A ref as well as the prop: react-hooks/immutability refuses a mutation of the prop itself in\n // an event handler, and marking the validator touched happens in one.\n const fvRef = useRef(formValidator);\n const [, forceUpdate] = useState(0);\n useEffect(() => {\n fvRef.current = formValidator;\n const fv = fvRef.current;\n if (!fv) return;\n fv.label = label;\n fv.validate();\n const listener = () => forceUpdate((n) => n + 1);\n fv.registerOnUpdateListener(listener);\n return () => {\n fv.removeOnUpdateListener(listener);\n };\n }, [formValidator, label]);\n\n const isControlled = value !== undefined && !formValidator;\n const rawValue = formValidator ? formValidator.value : isControlled ? value : internalValue;\n const selected = Array.isArray(rawValue) ? (rawValue as MultiSelectValue[]) : EMPTY;\n\n const hasError = formValidator ? formValidator.shouldShowError : !isValid || !!errorMessage;\n const errorText = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n const isInteractive = !disabled && !readOnly;\n const atMax = maxSelected !== undefined && selected.length >= maxSelected;\n\n const shouldFloatLabel = alwaysFloatLabel || isOpen || isFocused || selected.length > 0;\n\n const isSearchable = searchable || allowCustomValues;\n const typed = search.trim();\n const needle = isSearchable ? typed.toLowerCase() : '';\n const matches = needle ? items.filter((item) => item.label.toLowerCase().includes(needle)) : items;\n\n const canCreate =\n allowCustomValues &&\n typed.length > 0 &&\n !atMax &&\n !items.some((item) => item.label.toLowerCase() === needle || String(item.key).toLowerCase() === needle) &&\n !selected.some((key) => String(key).toLowerCase() === needle);\n\n const visibleItems: MultiSelectItem[] = canCreate ? [...matches, { key: typed, label: text.create(typed) }] : matches;\n\n // Mapped from `selected`, not filtered from `items`, to keep the chips in pick order. A custom\n // value has no entry in `items`, so it stands in for itself.\n const selectedItems = selected\n .map<MultiSelectItem | undefined>(\n (key) => items.find((item) => item.key === key) ?? (allowCustomValues ? { key, label: String(key) } : undefined),\n )\n .filter((item): item is MultiSelectItem => item !== undefined);\n\n function commit(next: MultiSelectValue[]) {\n if (formValidator) formValidator.set(next);\n else if (!isControlled) setInternalValue(next);\n onChange(next);\n }\n\n function toggle(item: MultiSelectItem) {\n if (!isInteractive || item.disabled) return;\n if (selected.includes(item.key)) commit(selected.filter((key) => key !== item.key));\n else if (!atMax) commit([...selected, item.key]);\n // The typed text has become a chip, so leaving it in the box would offer to add it again.\n if (canCreate && item.key === typed) setSearch('');\n }\n\n /**\n * A focused element that unmounts fires no blur, so removing the chip or clear button that was\n * just clicked would leave `isFocused` stuck true. Hand focus back so there is still something\n * left to blur.\n */\n function keepFocusInControl() {\n if (rootRef.current?.contains(document.activeElement)) controlRef.current?.focus();\n }\n\n function handleOpenChange(next: boolean) {\n if (next && !isInteractive) return;\n setIsOpen(next);\n if (next) {\n setPanelWidth(controlRef.current?.offsetWidth);\n setActiveIndex(0);\n } else {\n setSearch('');\n if (fvRef.current) fvRef.current.touched = true;\n }\n }\n\n useEffect(() => {\n if (!isOpen || !isSearchable) return;\n searchRef.current?.querySelector('input')?.focus();\n }, [isOpen, isSearchable]);\n\n useEffect(() => {\n if (!isOpen) return;\n scrollOptionIntoView(optionsRef.current, document.getElementById(`${listboxId}-${activeIndex}`));\n }, [isOpen, activeIndex, listboxId]);\n\n // Matches what the options render as `aria-disabled`, so the arrows cannot land on one.\n const navigation = {\n isDisabled: (index: number) => {\n const item = visibleItems[index];\n return !!item.disabled || (atMax && !selected.includes(item.key));\n },\n };\n\n function handleKeyDown(event: React.KeyboardEvent) {\n if (!isInteractive) return;\n\n if (!isOpen) {\n if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleOpenChange(true);\n } else if (event.key === 'Backspace' && selected.length > 0) {\n commit(selected.slice(0, -1));\n }\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n setActiveIndex(nextIndex(activeIndex, event.key === 'ArrowDown' ? 1 : -1, visibleItems.length, navigation) ?? 0);\n } else if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault();\n setActiveIndex(edgeIndex(event.key === 'Home' ? 'first' : 'last', visibleItems.length, navigation) ?? 0);\n } else if (event.key === 'Enter') {\n event.preventDefault();\n const item = visibleItems[activeIndex];\n if (item) toggle(item);\n } else if (event.key === 'Backspace' && !search && selected.length > 0) {\n commit(selected.slice(0, -1));\n }\n }\n\n const panel = (\n <div className={cx(styles.multiSelectPanel, classNames?.panel)} style={slotStyles?.panel}>\n {isSearchable && (\n <div ref={searchRef} className={cx(styles.multiSelectSearch, classNames?.search)} style={slotStyles?.search}>\n <SearchInput\n value={search}\n onChange={(value) => {\n setSearch(value);\n setActiveIndex(0);\n }}\n placeholder={text.searchPlaceholder}\n onKeyDown={handleKeyDown}\n />\n </div>\n )}\n <div\n ref={optionsRef}\n id={listboxId}\n role='listbox'\n aria-multiselectable='true'\n aria-label={label || undefined}\n className={cx(styles.multiSelectOptions, classNames?.options)}\n style={slotStyles?.options}\n >\n {visibleItems.length === 0 ? (\n <div className={cx(styles.multiSelectEmpty, classNames?.empty)} style={slotStyles?.empty}>\n {text.empty}\n </div>\n ) : (\n visibleItems.map((item, index) => {\n const isSelected = selected.includes(item.key);\n const isDisabled = item.disabled || (atMax && !isSelected);\n\n return (\n <div\n key={item.key}\n id={`${listboxId}-${index}`}\n role='option'\n aria-selected={isSelected}\n aria-disabled={isDisabled || undefined}\n className={cx(\n styles.multiSelectOption,\n index === activeIndex && styles.multiSelectOptionActive,\n isSelected && styles.multiSelectOptionSelected,\n isDisabled && styles.multiSelectOptionDisabled,\n classNames?.option,\n )}\n style={slotStyles?.option}\n onMouseDown={(event) => event.preventDefault()}\n onMouseEnter={() => setActiveIndex(index)}\n onClick={() => toggle(item)}\n >\n <span\n className={cx(styles.multiSelectOptionCheck, classNames?.optionCheck)}\n style={slotStyles?.optionCheck}\n >\n {isSelected && <FontAwesomeIcon icon={faCheck} />}\n </span>\n <span\n className={cx(styles.multiSelectOptionLabel, classNames?.optionLabel)}\n style={slotStyles?.optionLabel}\n >\n {item.label}\n </span>\n </div>\n );\n })\n )}\n </div>\n </div>\n );\n\n return (\n <div\n {...rest}\n ref={rootRef}\n className={cx(\n styles.multiSelect,\n disabled && styles.multiSelectDisabled,\n !!label && styles.multiSelectWithLabel,\n classNames?.root,\n className,\n )}\n style={{ ...style, ...slotStyles?.root }}\n onMouseEnter={(event) => {\n setIsHovered(true);\n onMouseEnter?.(event);\n }}\n onMouseLeave={(event) => {\n setIsHovered(false);\n onMouseLeave?.(event);\n }}\n onFocus={(event) => {\n setIsFocused(true);\n onFocus?.(event);\n }}\n onBlur={(event) => {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n onBlur?.(event);\n }}\n >\n <Popover\n isOpen={isOpen}\n onOpenChange={handleOpenChange}\n trigger={PopoverTrigger.Click}\n placement={PopoverPlacement.Bottom}\n align={PopoverAlign.Start}\n offset={4}\n withArrow={false}\n // Neither half of this is the keyboard-only default. A search field takes\n // the caret however the panel was opened, and without one the control\n // keeps the focus and points at the active option with\n // `aria-activedescendant`, so nothing may take it away.\n focusOnOpen={isSearchable}\n constrainWidth={false}\n width='trigger'\n disabled={!isInteractive}\n presentation={presentation}\n content={panel}\n styles={{ panel: panelWidth ? { minWidth: panelWidth } : undefined }}\n >\n <div\n ref={controlRef}\n role='combobox'\n tabIndex={disabled ? -1 : 0}\n aria-expanded={isOpen}\n aria-haspopup='listbox'\n aria-controls={listboxId}\n aria-activedescendant={isOpen && visibleItems.length > 0 ? `${listboxId}-${activeIndex}` : undefined}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n className={cx(styles.multiSelectControl, classNames?.control)}\n style={slotStyles?.control}\n onKeyDown={handleKeyDown}\n >\n {placeholder && selectedItems.length === 0 && (\n <span\n className={cx(\n styles.multiSelectPlaceholder,\n (!label || shouldFloatLabel) && styles.multiSelectPlaceholderShown,\n !label && styles.multiSelectPlaceholderInstant,\n classNames?.placeholder,\n )}\n style={slotStyles?.placeholder}\n >\n {placeholder}\n </span>\n )}\n\n <div className={cx(styles.multiSelectValues, classNames?.values)} style={slotStyles?.values}>\n {selectedItems.map((item) => (\n <Chip\n key={item.key}\n text={item.label}\n showRemoveIcon={isInteractive}\n labels={{ remove: `Remove ${item.label}` }}\n onMouseDown={(event) => event.preventDefault()}\n onRemove={() => {\n toggle(item);\n keepFocusInControl();\n }}\n className={cx(styles.multiSelectChip, classNames?.chip)}\n style={slotStyles?.chip}\n />\n ))}\n </div>\n\n {clearable && isInteractive && selected.length > 0 && (\n <button\n type='button'\n tabIndex={-1}\n aria-label={text.clearSelection}\n className={cx(styles.multiSelectClearButton, classNames?.clearButton)}\n style={slotStyles?.clearButton}\n onMouseDown={(event) => event.preventDefault()}\n onClick={(event) => {\n event.stopPropagation();\n commit([]);\n keepFocusInControl();\n }}\n >\n <FontAwesomeIcon icon={faXmark} />\n </button>\n )}\n\n <FontAwesomeIcon\n icon={faChevronDown}\n className={cx(styles.multiSelectIcon, isOpen && styles.multiSelectIconOpen, classNames?.icon)}\n style={slotStyles?.icon as React.CSSProperties & Record<`--fa-font-${string}`, string>}\n />\n\n {label && (\n <div\n className={cx(\n styles.multiSelectLabel,\n shouldFloatLabel && styles.multiSelectLabelFloating,\n classNames?.label,\n )}\n style={slotStyles?.label}\n >\n {label}\n </div>\n )}\n\n {/* The visible border lives here rather than on the control, because the legend is what\n cuts the notch the floating label sits in. Same construction as Input. */}\n <fieldset\n className={cx(\n styles.multiSelectFieldset,\n (isFocused || isOpen) && styles.multiSelectFieldsetFocused,\n hasError && styles.multiSelectFieldsetError,\n classNames?.fieldset,\n )}\n style={slotStyles?.fieldset}\n >\n <legend\n className={cx(\n styles.multiSelectFieldsetLegend,\n shouldFloatLabel && styles.multiSelectFieldsetLegendActive,\n )}\n >\n <span>{label}</span>\n </legend>\n </fieldset>\n </div>\n </Popover>\n\n <Tooltip variant='error' message={errorText} isVisible={hasError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default MultiSelect;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{ButtonSize as r}from"../button/button.types.mjs";import i from"../button/button.mjs";import a from"../hooks/useCoarsePointer.mjs";import ee from"../buttonGroup/buttonGroup.mjs";import o from"../input/input.mjs";import s from"./numberInput.module.mjs";import te from"./useStepRepeat.mjs";import c from"react";import l from"classnames";import{FontAwesomeIcon as u}from"@fortawesome/react-fontawesome";import{faChevronDown as ne,faChevronUp as re,faMinus as ie,faPlus as ae}from"@fortawesome/free-solid-svg-icons";import{jsx as d,jsxs as f}from"react/jsx-runtime";import{NumericFormat as p}from"react-number-format";var oe={increase:`Increase`,decrease:`Decrease`};function m({label:e,disabled:t,direction:n,onStart:r,onStop:i}){return{tabIndex:-1,disabled:t,"aria-label":e,onPointerDown:e=>{(e.button===0||e.pointerType!==`mouse`)&&(e.preventDefault(),r(n,e.shiftKey?10:1))},onPointerUp:i,onPointerCancel:i,onContextMenu:e=>e.preventDefault()}}function h(e){return d(`button`,{type:`button`,...m(e),className:l(s.arrowBtn,e.className),style:e.style,children:d(u,{icon:e.icon,"aria-hidden":`true`})})}function g(e){let{disabled:t,...a}=m(e);return d(i,{...a,className:l(s.splitBtn,e.className),style:e.style,color:n.Neutral,size:r.Icon,disabled:t,icon:e.icon})}function _(n){let r=c.useRef(void 0),{value:i,onChange:u,formValidator:m,label:_,error:v,className:y,style:b,classNames:x,styles:S,allowNegative:C=!1,decimalSeparator:se,thousandSeparator:ce,prefix:le,suffix:ue,decimalScale:w,fixedDecimalScale:de=!0,placeholder:fe,sign:T,showArrows:pe=!1,step:E=1,min:D,max:O,arrowsLayout:k=`auto`,labels:me,readOnly:A=!1,disabled:j=!1,isRequired:he=!1,onKeyDown:M,ref:ge,...N}=e(`NumberInput`,n),P=t(`NumberInput`,me,oe),[,F]=c.useState(0);c.useEffect(()=>{if(r.current=m,!m)return;let e=()=>F(e=>e+1);return m.registerOnUpdateListener(e),()=>{m.removeOnUpdateListener(e)}},[m]);let I=he||!!m?.validators.find(e=>e.validatorId===`required`),L=m==null?i:m.value,R=T!=null&&L!=null?Math.abs(Number(L)):L,z=R==null?``:String(R);function B(e){let t=e;t!==void 0&&(T===`-`&&(t=-Math.abs(t)),T===`+`&&(t=Math.abs(t))),m?.set(t??null),u?.(t)}function V(e){let t=e;return!C&&T!==`-`&&(t=Math.max(0,t)),T===`-`&&(t=Math.min(0,t)),D!==void 0&&(t=Math.max(D,t)),O!==void 0&&(t=Math.min(O,t)),t}let H=L!=null&&L!==``?Number(L):0;function U(){if(A||j||L==null||L===``)return;let e=Number(L);if(!Number.isFinite(e))return;let t=V(e);t!==e&&B(t)}function W(e){if(A||j)return!1;let t=V(H+e);return t!==H&&(B(t),!0)}let{start:_e,stop:ve}=te(e=>W(e*E));function ye(e){if(e.key===`Enter`){U(),M?.(e);return}if(!G){M?.(e);return}if(e.key===`ArrowUp`){e.preventDefault(),W(e.shiftKey?E*10:E);return}if(e.key===`ArrowDown`){e.preventDefault(),W(e.shiftKey?-E*10:-E);return}M?.(e)}let be=T===`-`?`-`:T===`+`?`+`:le,xe=a(),G=pe&&!A&&!j,K=k===`split`||k===`auto`&&xe,q=V(H+E)!==H,J=V(H-E)!==H,Y={onStart:_e,onStop:ve},X=G&&!K?f(`div`,{className:l(s.arrows,x?.arrows),style:S?.arrows,children:[d(h,{className:x?.increaseButton,style:S?.increaseButton,label:P.increase,icon:re,disabled:!q,direction:1,...Y}),d(h,{className:x?.decreaseButton,style:S?.decreaseButton,label:P.decrease,icon:ne,disabled:!J,direction:-1,...Y})]}):null,Se=G?{role:`spinbutton`,"aria-valuenow":Number.isFinite(H)?H:void 0,"aria-valuemin":D,"aria-valuemax":O,"aria-valuetext":z||void 0}:{},Z=m?!m.touched||!m.hasError():!v,Q=G&&K,$=d(o,{label:_,className:l(s.numberInput,{[s.numberInputSplit]:Q},Q?void 0:y,Q?void 0:x?.root,x?.input),style:{...Q?void 0:b,...Q?void 0:S?.root,...S?.input},value:z,onChange:()=>{},formValidator:null,isRequired:I,onBlur:()=>{U(),r.current&&(r.current.touched=!0)},isValid:Z,errorMessage:m?m.touched?m.getCurrentErrorMessage()??``:``:v??``,placeholder:fe,readOnly:A,disabled:j,suffix:X,classNames:X?{suffix:s.arrowSuffix}:void 0,inputMode:w===0?`numeric`:`decimal`,customInput:d(p,{onValueChange:e=>B(e.floatValue),allowNegative:T==null&&C,decimalSeparator:se,thousandSeparator:ce,decimalScale:w,fixedDecimalScale:de,prefix:be,suffix:ue,onKeyDown:ye,getInputRef:ge,...Se,...N})});return Q?f(ee,{className:l(s.split,{[s.splitInvalid]:!Z},y,x?.root),style:{...b,...S?.root},children:[d(g,{className:x?.decreaseButton,style:S?.decreaseButton,label:P.decrease,icon:ie,disabled:!J,direction:-1,...Y}),$,d(g,{className:x?.increaseButton,style:S?.increaseButton,label:P.increase,icon:ae,disabled:!q,direction:1,...Y})]}):$}export{_ as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{ButtonSize as r}from"../button/button.types.mjs";import i from"../button/button.mjs";import a from"../hooks/useCoarsePointer.mjs";import ee from"../buttonGroup/buttonGroup.mjs";import o from"../input/input.mjs";import s from"./numberInput.module.mjs";import te from"./useStepRepeat.mjs";import c from"react";import l from"classnames";import{FontAwesomeIcon as u}from"@fortawesome/react-fontawesome";import{faChevronDown as ne,faChevronUp as re,faMinus as ie,faPlus as ae}from"@fortawesome/free-solid-svg-icons";import{jsx as d,jsxs as f}from"react/jsx-runtime";import{NumericFormat as p}from"react-number-format";var oe={increase:`Increase`,decrease:`Decrease`};function m({label:e,disabled:t,direction:n,onStart:r,onStop:i}){return{tabIndex:-1,disabled:t,"aria-label":e,onPointerDown:e=>{(e.button===0||e.pointerType!==`mouse`)&&(e.preventDefault(),r(n,e.shiftKey?10:1))},onPointerUp:i,onPointerCancel:i,onContextMenu:e=>e.preventDefault()}}function h(e){return d(`button`,{type:`button`,...m(e),className:l(s.arrowBtn,e.className),style:e.style,children:d(u,{icon:e.icon,"aria-hidden":`true`})})}function g(e){let{disabled:t,...a}=m(e);return d(i,{...a,className:l(s.splitBtn,e.className),style:e.style,color:n.Neutral,size:r.Icon,disabled:t,icon:e.icon})}function _(n){let r=c.useRef(void 0),{value:i,onChange:u,formValidator:m,label:_,error:v,className:y,style:b,classNames:x,styles:S,allowNegative:C=!1,decimalSeparator:se,thousandSeparator:ce,prefix:le,suffix:ue,decimalScale:w,fixedDecimalScale:de=!0,placeholder:fe,sign:T,showArrows:pe=!1,step:E=1,min:D,max:O,arrowsLayout:k=`auto`,labels:me,readOnly:A=!1,disabled:j=!1,isRequired:he=!1,onKeyDown:M,ref:ge,...N}=e(`NumberInput`,n),P=t(`NumberInput`,me,oe),[,F]=c.useState(0);c.useEffect(()=>{if(r.current=m,!m)return;let e=()=>F(e=>e+1);return m.registerOnUpdateListener(e),()=>{m.removeOnUpdateListener(e)}},[m]);let I=he||!!m?.validators.find(e=>e.validatorId===`required`),L=m==null?i:m.value,R=T!=null&&L!=null?Math.abs(Number(L)):L,z=R==null?``:String(R);function B(e,t=!0){let n=e;n!==void 0&&(T===`-`&&(n=-Math.abs(n)),T===`+`&&(n=Math.abs(n))),m?.set(n??null,t),u?.(n)}function V(e){let t=e;return!C&&T!==`-`&&(t=Math.max(0,t)),T===`-`&&(t=Math.min(0,t)),D!==void 0&&(t=Math.max(D,t)),O!==void 0&&(t=Math.min(O,t)),t}let H=L!=null&&L!==``?Number(L):0;function U(){if(A||j||L==null||L===``)return;let e=Number(L);if(!Number.isFinite(e))return;let t=V(e);t!==e&&B(t)}function W(e){if(A||j)return!1;let t=V(H+e);return t!==H&&(B(t),!0)}let{start:_e,stop:ve}=te(e=>W(e*E));function ye(e){if(e.key===`Enter`){U(),M?.(e);return}if(!G){M?.(e);return}if(e.key===`ArrowUp`){e.preventDefault(),W(e.shiftKey?E*10:E);return}if(e.key===`ArrowDown`){e.preventDefault(),W(e.shiftKey?-E*10:-E);return}M?.(e)}let be=T===`-`?`-`:T===`+`?`+`:le,xe=a(),G=pe&&!A&&!j,K=k===`split`||k===`auto`&&xe,q=V(H+E)!==H,J=V(H-E)!==H,Y={onStart:_e,onStop:ve},X=G&&!K?f(`div`,{className:l(s.arrows,x?.arrows),style:S?.arrows,children:[d(h,{className:x?.increaseButton,style:S?.increaseButton,label:P.increase,icon:re,disabled:!q,direction:1,...Y}),d(h,{className:x?.decreaseButton,style:S?.decreaseButton,label:P.decrease,icon:ne,disabled:!J,direction:-1,...Y})]}):null,Se=G?{role:`spinbutton`,"aria-valuenow":Number.isFinite(H)?H:void 0,"aria-valuemin":D,"aria-valuemax":O,"aria-valuetext":z||void 0}:{},Z=m?!m.shouldShowError:!v,Q=G&&K,$=d(o,{label:_,className:l(s.numberInput,{[s.numberInputSplit]:Q},Q?void 0:y,Q?void 0:x?.root,x?.input),style:{...Q?void 0:b,...Q?void 0:S?.root,...S?.input},value:z,onChange:()=>{},formValidator:null,isRequired:I,onBlur:()=>{U(),r.current&&(r.current.touched=!0)},isValid:Z,errorMessage:m?m.shouldShowError?m.getCurrentErrorMessage()??``:``:v??``,placeholder:fe,readOnly:A,disabled:j,suffix:X,classNames:X?{suffix:s.arrowSuffix}:void 0,inputMode:w===0?`numeric`:`decimal`,customInput:d(p,{onValueChange:(e,{source:t})=>B(e.floatValue,t===`event`),allowNegative:T==null&&C,decimalSeparator:se,thousandSeparator:ce,decimalScale:w,fixedDecimalScale:de,prefix:be,suffix:ue,onKeyDown:ye,getInputRef:ge,...Se,...N})});return Q?f(ee,{className:l(s.split,{[s.splitInvalid]:!Z},y,x?.root),style:{...b,...S?.root},children:[d(g,{className:x?.decreaseButton,style:S?.decreaseButton,label:P.decrease,icon:ie,disabled:!J,direction:-1,...Y}),$,d(g,{className:x?.increaseButton,style:S?.increaseButton,label:P.increase,icon:ae,disabled:!q,direction:1,...Y})]}):$}export{_ as default};
2
2
  //# sourceMappingURL=numberInput.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"numberInput.mjs","names":[],"sources":["../../../../package/common/numberInput/numberInput.tsx"],"sourcesContent":["import type { CSSProperties, ReactElement } from 'react';\nimport React from 'react';\nimport cx from 'classnames';\nimport { NumericFormat } from 'react-number-format';\nimport type { FormValidator } from 'services/formValidation';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChevronUp, faChevronDown, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';\n\nimport styles from './numberInput.module.pcss';\nimport Input from '../input';\nimport Button from '../button';\nimport ButtonGroup from '../buttonGroup';\nimport { ButtonSize } from '../button/button.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport { useStepRepeat } from './useStepRepeat';\nimport type { NumberInputLabels, NumberInputProps } from './numberInput.types';\nimport { ThemeColor } from '../themeProvider/color.types';\n\nexport type { NumberInputProps, NumberInputArrowsLayout } from './numberInput.types';\n\ninterface StepButtonProps {\n label: string;\n className?: string;\n style?: CSSProperties;\n icon: typeof faPlus;\n disabled: boolean;\n direction: 1 | -1;\n onStart: (direction: 1 | -1, multiplier: number) => void;\n onStop: () => void;\n}\n\n/** Press-and-hold behaviour shared by both stepper layouts. */\nconst DEFAULT_LABELS: Required<NumberInputLabels> = {\n increase: 'Increase',\n decrease: 'Decrease',\n};\n\nfunction stepButtonProps({ label, disabled, direction, onStart, onStop }: StepButtonProps) {\n return {\n // The input keeps the tab stop — it carries role=\"spinbutton\", which announces the value\n // and the arrow-key affordance, so two more tab stops per field would be noise.\n tabIndex: -1,\n disabled,\n 'aria-label': label,\n // pointerdown (not click) keeps focus in the field and responds immediately on touch.\n // Deliberately no setPointerCapture: capturing retargets the following click to the captured\n // element, past the node Button's ripple listens on. useStepRepeat watches the window for\n // the release instead.\n onPointerDown: (e: React.PointerEvent<HTMLElement>) => {\n if (e.button !== 0 && e.pointerType === 'mouse') return;\n e.preventDefault();\n onStart(direction, e.shiftKey ? 10 : 1);\n },\n onPointerUp: onStop,\n onPointerCancel: onStop,\n onContextMenu: (e: React.SyntheticEvent) => e.preventDefault(),\n };\n}\n\n/** Stacked-layout chevron: borderless, sits inside the field's border. */\nfunction ChevronStepButton(props: StepButtonProps): ReactElement {\n return (\n <button\n type='button'\n {...stepButtonProps(props)}\n className={cx(styles.arrowBtn, props.className)}\n style={props.style}\n >\n <FontAwesomeIcon icon={props.icon} aria-hidden='true' />\n </button>\n );\n}\n\n/** Split-layout stepper: a standalone control, so a real Button. */\nfunction SplitStepButton(props: StepButtonProps): ReactElement {\n const { disabled, ...handlers } = stepButtonProps(props);\n return (\n <Button\n {...handlers}\n className={cx(styles.splitBtn, props.className)}\n style={props.style}\n color={ThemeColor.Neutral}\n size={ButtonSize.Icon}\n disabled={disabled}\n icon={props.icon}\n />\n );\n}\n\nfunction NumberInput(props: NumberInputProps): ReactElement {\n const fvRef = React.useRef<FormValidator | null | undefined>(undefined);\n const {\n value,\n onChange,\n formValidator,\n label,\n error,\n className,\n style,\n classNames,\n styles: slotStyles,\n allowNegative = false,\n decimalSeparator,\n thousandSeparator,\n prefix,\n suffix,\n decimalScale,\n fixedDecimalScale = true,\n placeholder,\n sign,\n showArrows = false,\n step = 1,\n min,\n max,\n arrowsLayout = 'auto',\n labels,\n readOnly = false,\n disabled = false,\n isRequired = false,\n onKeyDown: onKeyDownProp,\n ref,\n ...rest\n } = useComponentDefaults('NumberInput', props);\n const text = useLabels('NumberInput', labels, DEFAULT_LABELS);\n const [, forceUpdate] = React.useState(0);\n React.useEffect(() => {\n fvRef.current = formValidator;\n if (!formValidator) return;\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => {\n formValidator.removeOnUpdateListener(listener);\n };\n }, [formValidator]);\n const computedIsRequired =\n isRequired || !!formValidator?.validators.find((v) => (v as { validatorId?: string }).validatorId === 'required');\n const rawValue = formValidator != null ? (formValidator.value as number | string) : value;\n // When a sign is forced, display the absolute value — the prefix character carries the sign visually.\n const displayValue = sign != null && rawValue != null ? Math.abs(Number(rawValue)) : rawValue;\n const controlledValue = displayValue != null ? String(displayValue) : '';\n\n function handleValueChange(floatValue: number | undefined) {\n let out = floatValue;\n if (out !== undefined) {\n if (sign === '-') out = -Math.abs(out);\n if (sign === '+') out = Math.abs(out);\n }\n if (formValidator != null) formValidator.set(out ?? null);\n onChange?.(out);\n }\n\n function clamp(next: number): number {\n let out = next;\n if (!allowNegative && sign !== '-') out = Math.max(0, out);\n if (sign === '-') out = Math.min(0, out);\n if (min !== undefined) out = Math.max(min, out);\n if (max !== undefined) out = Math.min(max, out);\n return out;\n }\n\n const currentNumber = rawValue != null && rawValue !== '' ? Number(rawValue) : 0;\n\n /**\n * Called on blur and Enter, never per keystroke: rejecting an out-of-bounds edit as it is\n * typed swallows keystrokes on their way somewhere valid (with `max: 10`, typing `1` in front\n * of `8` to reach `18`). An empty field is skipped so a `min` is never typed in for the user.\n */\n function clampToBounds() {\n if (readOnly || disabled) return;\n if (rawValue == null || rawValue === '') return;\n const n = Number(rawValue);\n if (!Number.isFinite(n)) return;\n const next = clamp(n);\n if (next !== n) handleValueChange(next);\n }\n\n /** Returns whether the value moved, which is how useStepRepeat knows to end a hold at a bound. */\n function handleStep(delta: number): boolean {\n if (readOnly || disabled) return false;\n const next = clamp(currentNumber + delta);\n if (next === currentNumber) return false;\n handleValueChange(next);\n return true;\n }\n\n const { start: startRepeat, stop: stopRepeat } = useStepRepeat((delta) => handleStep(delta * step));\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n // Enter submits the surrounding form without ever blurring, so the blur clamp would not run.\n // Before the stepper guard, since the bounds apply with or without steppers.\n if (e.key === 'Enter') {\n clampToBounds();\n onKeyDownProp?.(e);\n return;\n }\n if (!showStepper) {\n onKeyDownProp?.(e);\n return;\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n handleStep(e.shiftKey ? step * 10 : step);\n return;\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n handleStep(e.shiftKey ? -step * 10 : -step);\n return;\n }\n onKeyDownProp?.(e);\n }\n\n const resolvedPrefix = sign === '-' ? '-' : sign === '+' ? '+' : prefix;\n\n const isCoarsePointer = useCoarsePointer();\n const showStepper = showArrows && !readOnly && !disabled;\n const isSplit = arrowsLayout === 'split' || (arrowsLayout === 'auto' && isCoarsePointer);\n\n const canIncrease = clamp(currentNumber + step) !== currentNumber;\n const canDecrease = clamp(currentNumber - step) !== currentNumber;\n\n const stepperProps = { onStart: startRepeat, onStop: stopRepeat };\n\n const stackedArrows =\n showStepper && !isSplit ? (\n <div className={cx(styles.arrows, classNames?.arrows)} style={slotStyles?.arrows}>\n <ChevronStepButton\n className={classNames?.increaseButton}\n style={slotStyles?.increaseButton}\n label={text.increase}\n icon={faChevronUp}\n disabled={!canIncrease}\n direction={1}\n {...stepperProps}\n />\n <ChevronStepButton\n className={classNames?.decreaseButton}\n style={slotStyles?.decreaseButton}\n label={text.decrease}\n icon={faChevronDown}\n disabled={!canDecrease}\n direction={-1}\n {...stepperProps}\n />\n </div>\n ) : null;\n\n // aria-* is spread onto NumericFormat, which forwards unknown props to the DOM input.\n // Input's own cloneElement doesn't set these keys, so they survive the clone.\n const spinButtonProps = showStepper\n ? {\n role: 'spinbutton',\n 'aria-valuenow': Number.isFinite(currentNumber) ? currentNumber : undefined,\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuetext': controlledValue || undefined,\n }\n : {};\n\n // Shared with the split group, whose buttons have to mirror the field's invalid state — they\n // form one continuous border.\n const isValid = formValidator ? (formValidator.touched ? !formValidator.hasError() : true) : !error;\n\n // In the split layout the ButtonGroup is the root, so the consumer's className and style\n // belong there rather than on the Input.\n const isSplitGroup = showStepper && isSplit;\n\n const input = (\n <Input\n label={label}\n className={cx(\n styles.numberInput,\n { [styles.numberInputSplit]: isSplitGroup },\n // In split mode the ButtonGroup around all three is the root, so the\n // consumer's root class belongs there rather than on the field.\n isSplitGroup ? undefined : className,\n isSplitGroup ? undefined : classNames?.root,\n classNames?.input,\n )}\n style={{\n ...(isSplitGroup ? undefined : style),\n ...(isSplitGroup ? undefined : slotStyles?.root),\n ...slotStyles?.input,\n }}\n value={controlledValue}\n onChange={() => {}}\n formValidator={null}\n isRequired={computedIsRequired}\n onBlur={() => {\n clampToBounds();\n if (fvRef.current) fvRef.current.touched = true;\n }}\n isValid={isValid}\n errorMessage={\n formValidator ? (formValidator.touched ? (formValidator.getCurrentErrorMessage() ?? '') : '') : (error ?? '')\n }\n placeholder={placeholder}\n readOnly={readOnly}\n disabled={disabled}\n suffix={stackedArrows}\n // Input centres its suffix wrapper; this lets the chevrons claim the field's full height.\n classNames={stackedArrows ? { suffix: styles.arrowSuffix } : undefined}\n inputMode={decimalScale === 0 ? 'numeric' : 'decimal'}\n customInput={\n <NumericFormat\n onValueChange={(vals) => handleValueChange(vals.floatValue)}\n allowNegative={sign != null ? false : allowNegative}\n decimalSeparator={decimalSeparator}\n thousandSeparator={thousandSeparator}\n decimalScale={decimalScale}\n fixedDecimalScale={fixedDecimalScale}\n prefix={resolvedPrefix}\n suffix={suffix}\n onKeyDown={handleKeyDown}\n // react-number-format owns the <input>, so its ref is reachable only through this prop.\n getInputRef={ref}\n {...spinButtonProps}\n {...rest}\n />\n }\n />\n );\n\n if (!isSplitGroup) return input;\n\n return (\n // ButtonGroup collapses the doubled borders and flattens the inner corners, so the three\n // read as one control.\n <ButtonGroup\n className={cx(styles.split, { [styles.splitInvalid]: !isValid }, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <SplitStepButton\n className={classNames?.decreaseButton}\n style={slotStyles?.decreaseButton}\n label={text.decrease}\n icon={faMinus}\n disabled={!canDecrease}\n direction={-1}\n {...stepperProps}\n />\n {input}\n <SplitStepButton\n className={classNames?.increaseButton}\n style={slotStyles?.increaseButton}\n label={text.increase}\n icon={faPlus}\n disabled={!canIncrease}\n direction={1}\n {...stepperProps}\n />\n </ButtonGroup>\n );\n}\n\nexport default NumberInput;\n"],"mappings":"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"}
1
+ {"version":3,"file":"numberInput.mjs","names":[],"sources":["../../../../package/common/numberInput/numberInput.tsx"],"sourcesContent":["import type { CSSProperties, ReactElement } from 'react';\nimport React from 'react';\nimport cx from 'classnames';\nimport { NumericFormat } from 'react-number-format';\nimport type { FormValidator } from 'services/formValidation';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faChevronUp, faChevronDown, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';\n\nimport styles from './numberInput.module.pcss';\nimport Input from '../input';\nimport Button from '../button';\nimport ButtonGroup from '../buttonGroup';\nimport { ButtonSize } from '../button/button.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport { useStepRepeat } from './useStepRepeat';\nimport type { NumberInputLabels, NumberInputProps } from './numberInput.types';\nimport { ThemeColor } from '../themeProvider/color.types';\n\nexport type { NumberInputProps, NumberInputArrowsLayout } from './numberInput.types';\n\ninterface StepButtonProps {\n label: string;\n className?: string;\n style?: CSSProperties;\n icon: typeof faPlus;\n disabled: boolean;\n direction: 1 | -1;\n onStart: (direction: 1 | -1, multiplier: number) => void;\n onStop: () => void;\n}\n\n/** Press-and-hold behaviour shared by both stepper layouts. */\nconst DEFAULT_LABELS: Required<NumberInputLabels> = {\n increase: 'Increase',\n decrease: 'Decrease',\n};\n\nfunction stepButtonProps({ label, disabled, direction, onStart, onStop }: StepButtonProps) {\n return {\n // The input keeps the tab stop — it carries role=\"spinbutton\", which announces the value\n // and the arrow-key affordance, so two more tab stops per field would be noise.\n tabIndex: -1,\n disabled,\n 'aria-label': label,\n // pointerdown (not click) keeps focus in the field and responds immediately on touch.\n // Deliberately no setPointerCapture: capturing retargets the following click to the captured\n // element, past the node Button's ripple listens on. useStepRepeat watches the window for\n // the release instead.\n onPointerDown: (e: React.PointerEvent<HTMLElement>) => {\n if (e.button !== 0 && e.pointerType === 'mouse') return;\n e.preventDefault();\n onStart(direction, e.shiftKey ? 10 : 1);\n },\n onPointerUp: onStop,\n onPointerCancel: onStop,\n onContextMenu: (e: React.SyntheticEvent) => e.preventDefault(),\n };\n}\n\n/** Stacked-layout chevron: borderless, sits inside the field's border. */\nfunction ChevronStepButton(props: StepButtonProps): ReactElement {\n return (\n <button\n type='button'\n {...stepButtonProps(props)}\n className={cx(styles.arrowBtn, props.className)}\n style={props.style}\n >\n <FontAwesomeIcon icon={props.icon} aria-hidden='true' />\n </button>\n );\n}\n\n/** Split-layout stepper: a standalone control, so a real Button. */\nfunction SplitStepButton(props: StepButtonProps): ReactElement {\n const { disabled, ...handlers } = stepButtonProps(props);\n return (\n <Button\n {...handlers}\n className={cx(styles.splitBtn, props.className)}\n style={props.style}\n color={ThemeColor.Neutral}\n size={ButtonSize.Icon}\n disabled={disabled}\n icon={props.icon}\n />\n );\n}\n\nfunction NumberInput(props: NumberInputProps): ReactElement {\n const fvRef = React.useRef<FormValidator | null | undefined>(undefined);\n const {\n value,\n onChange,\n formValidator,\n label,\n error,\n className,\n style,\n classNames,\n styles: slotStyles,\n allowNegative = false,\n decimalSeparator,\n thousandSeparator,\n prefix,\n suffix,\n decimalScale,\n fixedDecimalScale = true,\n placeholder,\n sign,\n showArrows = false,\n step = 1,\n min,\n max,\n arrowsLayout = 'auto',\n labels,\n readOnly = false,\n disabled = false,\n isRequired = false,\n onKeyDown: onKeyDownProp,\n ref,\n ...rest\n } = useComponentDefaults('NumberInput', props);\n const text = useLabels('NumberInput', labels, DEFAULT_LABELS);\n const [, forceUpdate] = React.useState(0);\n React.useEffect(() => {\n fvRef.current = formValidator;\n if (!formValidator) return;\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => {\n formValidator.removeOnUpdateListener(listener);\n };\n }, [formValidator]);\n const computedIsRequired =\n isRequired || !!formValidator?.validators.find((v) => (v as { validatorId?: string }).validatorId === 'required');\n const rawValue = formValidator != null ? (formValidator.value as number | string) : value;\n // When a sign is forced, display the absolute value — the prefix character carries the sign visually.\n const displayValue = sign != null && rawValue != null ? Math.abs(Number(rawValue)) : rawValue;\n const controlledValue = displayValue != null ? String(displayValue) : '';\n\n function handleValueChange(floatValue: number | undefined, wasUser = true) {\n let out = floatValue;\n if (out !== undefined) {\n if (sign === '-') out = -Math.abs(out);\n if (sign === '+') out = Math.abs(out);\n }\n if (formValidator != null) formValidator.set(out ?? null, wasUser);\n onChange?.(out);\n }\n\n function clamp(next: number): number {\n let out = next;\n if (!allowNegative && sign !== '-') out = Math.max(0, out);\n if (sign === '-') out = Math.min(0, out);\n if (min !== undefined) out = Math.max(min, out);\n if (max !== undefined) out = Math.min(max, out);\n return out;\n }\n\n const currentNumber = rawValue != null && rawValue !== '' ? Number(rawValue) : 0;\n\n /**\n * Called on blur and Enter, never per keystroke: rejecting an out-of-bounds edit as it is\n * typed swallows keystrokes on their way somewhere valid (with `max: 10`, typing `1` in front\n * of `8` to reach `18`). An empty field is skipped so a `min` is never typed in for the user.\n */\n function clampToBounds() {\n if (readOnly || disabled) return;\n if (rawValue == null || rawValue === '') return;\n const n = Number(rawValue);\n if (!Number.isFinite(n)) return;\n const next = clamp(n);\n if (next !== n) handleValueChange(next);\n }\n\n /** Returns whether the value moved, which is how useStepRepeat knows to end a hold at a bound. */\n function handleStep(delta: number): boolean {\n if (readOnly || disabled) return false;\n const next = clamp(currentNumber + delta);\n if (next === currentNumber) return false;\n handleValueChange(next);\n return true;\n }\n\n const { start: startRepeat, stop: stopRepeat } = useStepRepeat((delta) => handleStep(delta * step));\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n // Enter submits the surrounding form without ever blurring, so the blur clamp would not run.\n // Before the stepper guard, since the bounds apply with or without steppers.\n if (e.key === 'Enter') {\n clampToBounds();\n onKeyDownProp?.(e);\n return;\n }\n if (!showStepper) {\n onKeyDownProp?.(e);\n return;\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n handleStep(e.shiftKey ? step * 10 : step);\n return;\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n handleStep(e.shiftKey ? -step * 10 : -step);\n return;\n }\n onKeyDownProp?.(e);\n }\n\n const resolvedPrefix = sign === '-' ? '-' : sign === '+' ? '+' : prefix;\n\n const isCoarsePointer = useCoarsePointer();\n const showStepper = showArrows && !readOnly && !disabled;\n const isSplit = arrowsLayout === 'split' || (arrowsLayout === 'auto' && isCoarsePointer);\n\n const canIncrease = clamp(currentNumber + step) !== currentNumber;\n const canDecrease = clamp(currentNumber - step) !== currentNumber;\n\n const stepperProps = { onStart: startRepeat, onStop: stopRepeat };\n\n const stackedArrows =\n showStepper && !isSplit ? (\n <div className={cx(styles.arrows, classNames?.arrows)} style={slotStyles?.arrows}>\n <ChevronStepButton\n className={classNames?.increaseButton}\n style={slotStyles?.increaseButton}\n label={text.increase}\n icon={faChevronUp}\n disabled={!canIncrease}\n direction={1}\n {...stepperProps}\n />\n <ChevronStepButton\n className={classNames?.decreaseButton}\n style={slotStyles?.decreaseButton}\n label={text.decrease}\n icon={faChevronDown}\n disabled={!canDecrease}\n direction={-1}\n {...stepperProps}\n />\n </div>\n ) : null;\n\n // aria-* is spread onto NumericFormat, which forwards unknown props to the DOM input.\n // Input's own cloneElement doesn't set these keys, so they survive the clone.\n const spinButtonProps = showStepper\n ? {\n role: 'spinbutton',\n 'aria-valuenow': Number.isFinite(currentNumber) ? currentNumber : undefined,\n 'aria-valuemin': min,\n 'aria-valuemax': max,\n 'aria-valuetext': controlledValue || undefined,\n }\n : {};\n\n // Shared with the split group, whose buttons have to mirror the field's invalid state — they\n // form one continuous border.\n const isValid = formValidator ? !formValidator.shouldShowError : !error;\n\n // In the split layout the ButtonGroup is the root, so the consumer's className and style\n // belong there rather than on the Input.\n const isSplitGroup = showStepper && isSplit;\n\n const input = (\n <Input\n label={label}\n className={cx(\n styles.numberInput,\n { [styles.numberInputSplit]: isSplitGroup },\n // In split mode the ButtonGroup around all three is the root, so the\n // consumer's root class belongs there rather than on the field.\n isSplitGroup ? undefined : className,\n isSplitGroup ? undefined : classNames?.root,\n classNames?.input,\n )}\n style={{\n ...(isSplitGroup ? undefined : style),\n ...(isSplitGroup ? undefined : slotStyles?.root),\n ...slotStyles?.input,\n }}\n value={controlledValue}\n onChange={() => {}}\n formValidator={null}\n isRequired={computedIsRequired}\n onBlur={() => {\n clampToBounds();\n if (fvRef.current) fvRef.current.touched = true;\n }}\n isValid={isValid}\n errorMessage={\n formValidator\n ? formValidator.shouldShowError\n ? (formValidator.getCurrentErrorMessage() ?? '')\n : ''\n : (error ?? '')\n }\n placeholder={placeholder}\n readOnly={readOnly}\n disabled={disabled}\n suffix={stackedArrows}\n // Input centres its suffix wrapper; this lets the chevrons claim the field's full height.\n classNames={stackedArrows ? { suffix: styles.arrowSuffix } : undefined}\n inputMode={decimalScale === 0 ? 'numeric' : 'decimal'}\n customInput={\n <NumericFormat\n // NumericFormat also reports a value set from outside (fv.set(), group.reset()) once it has\n // reformatted it; that must not mark the field dirty. A string, because its `SourceType`\n // enum exists only in the type declarations and is undefined at runtime.\n onValueChange={(vals, { source }) => handleValueChange(vals.floatValue, source === 'event')}\n allowNegative={sign != null ? false : allowNegative}\n decimalSeparator={decimalSeparator}\n thousandSeparator={thousandSeparator}\n decimalScale={decimalScale}\n fixedDecimalScale={fixedDecimalScale}\n prefix={resolvedPrefix}\n suffix={suffix}\n onKeyDown={handleKeyDown}\n // react-number-format owns the <input>, so its ref is reachable only through this prop.\n getInputRef={ref}\n {...spinButtonProps}\n {...rest}\n />\n }\n />\n );\n\n if (!isSplitGroup) return input;\n\n return (\n // ButtonGroup collapses the doubled borders and flattens the inner corners, so the three\n // read as one control.\n <ButtonGroup\n className={cx(styles.split, { [styles.splitInvalid]: !isValid }, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <SplitStepButton\n className={classNames?.decreaseButton}\n style={slotStyles?.decreaseButton}\n label={text.decrease}\n icon={faMinus}\n disabled={!canDecrease}\n direction={-1}\n {...stepperProps}\n />\n {input}\n <SplitStepButton\n className={classNames?.increaseButton}\n style={slotStyles?.increaseButton}\n label={text.increase}\n icon={faPlus}\n disabled={!canIncrease}\n direction={1}\n {...stepperProps}\n />\n </ButtonGroup>\n );\n}\n\nexport default NumberInput;\n"],"mappings":"q0BAiCA,IAAM,GAA8C,CAClD,SAAU,WACV,SAAU,UACZ,EAEA,SAAS,EAAgB,CAAE,QAAO,WAAU,YAAW,UAAS,UAA2B,CACzF,MAAO,CAGL,SAAU,GACV,WACA,aAAc,EAKd,cAAgB,GAAuC,EACjD,EAAE,SAAW,GAAK,EAAE,cAAgB,WACxC,EAAE,eAAe,EACjB,EAAQ,EAAW,EAAE,SAAW,GAAK,CAAC,EACxC,EACA,YAAa,EACb,gBAAiB,EACjB,cAAgB,GAA4B,EAAE,eAAe,CAC/D,CACF,CAGA,SAAS,EAAkB,EAAsC,CAC/D,OACE,EAAC,SAAD,CACE,KAAK,SACL,GAAI,EAAgB,CAAK,EACzB,UAAW,EAAG,EAAO,SAAU,EAAM,SAAS,EAC9C,MAAO,EAAM,MAEb,SAAA,EAAC,EAAD,CAAiB,KAAM,EAAM,KAAM,cAAY,MAAQ,CAAA,CACjD,CAAA,CAEZ,CAGA,SAAS,EAAgB,EAAsC,CAC7D,GAAM,CAAE,WAAU,GAAG,GAAa,EAAgB,CAAK,EACvD,OACE,EAAC,EAAD,CACE,GAAI,EACJ,UAAW,EAAG,EAAO,SAAU,EAAM,SAAS,EAC9C,MAAO,EAAM,MACb,MAAO,EAAW,QAClB,KAAM,EAAW,KACP,WACV,KAAM,EAAM,IACb,CAAA,CAEL,CAEA,SAAS,EAAY,EAAuC,CAC1D,IAAM,EAAQ,EAAM,OAAyC,IAAA,EAAS,EAChE,CACJ,QACA,WACA,gBACA,QACA,QACA,YACA,QACA,aACA,OAAQ,EACR,gBAAgB,GAChB,oBACA,qBACA,UACA,UACA,eACA,qBAAoB,GACpB,eACA,OACA,cAAa,GACb,OAAO,EACP,MACA,MACA,eAAe,OACf,UACA,WAAW,GACX,WAAW,GACX,cAAa,GACb,UAAW,EACX,OACA,GAAG,GACD,EAAqB,cAAe,CAAK,EACvC,EAAO,EAAU,cAAe,GAAQ,EAAc,EACtD,EAAG,GAAe,EAAM,SAAS,CAAC,EACxC,EAAM,cAAgB,CAEpB,GADA,EAAM,QAAU,EACZ,CAAC,EAAe,OACpB,IAAM,MAAiB,EAAa,GAAM,EAAI,CAAC,EAE/C,OADA,EAAc,yBAAyB,CAAQ,MAClC,CACX,EAAc,uBAAuB,CAAQ,CAC/C,CACF,EAAG,CAAC,CAAa,CAAC,EAClB,IAAM,EACJ,IAAc,CAAC,CAAC,GAAe,WAAW,KAAM,GAAO,EAA+B,cAAgB,UAAU,EAC5G,EAAW,GAAiB,KAAkD,EAA1C,EAAc,MAElD,EAAe,GAAQ,MAAQ,GAAY,KAAO,KAAK,IAAI,OAAO,CAAQ,CAAC,EAAI,EAC/E,EAAkB,GAAgB,KAA8B,GAAvB,OAAO,CAAY,EAElE,SAAS,EAAkB,EAAgC,EAAU,GAAM,CACzE,IAAI,EAAM,EACN,IAAQ,IAAA,KACN,IAAS,MAAK,EAAM,CAAC,KAAK,IAAI,CAAG,GACjC,IAAS,MAAK,EAAM,KAAK,IAAI,CAAG,IAElC,GAAqC,IAAI,GAAO,KAAM,CAAO,EACjE,IAAW,CAAG,CAChB,CAEA,SAAS,EAAM,EAAsB,CACnC,IAAI,EAAM,EAKV,MAJI,CAAC,GAAiB,IAAS,MAAK,EAAM,KAAK,IAAI,EAAG,CAAG,GACrD,IAAS,MAAK,EAAM,KAAK,IAAI,EAAG,CAAG,GACnC,IAAQ,IAAA,KAAW,EAAM,KAAK,IAAI,EAAK,CAAG,GAC1C,IAAQ,IAAA,KAAW,EAAM,KAAK,IAAI,EAAK,CAAG,GACvC,CACT,CAEA,IAAM,EAAgB,GAAY,MAAQ,IAAa,GAAK,OAAO,CAAQ,EAAI,EAO/E,SAAS,GAAgB,CAEvB,GADI,GAAY,GACZ,GAAY,MAAQ,IAAa,GAAI,OACzC,IAAM,EAAI,OAAO,CAAQ,EACzB,GAAI,CAAC,OAAO,SAAS,CAAC,EAAG,OACzB,IAAM,EAAO,EAAM,CAAC,EAChB,IAAS,GAAG,EAAkB,CAAI,CACxC,CAGA,SAAS,EAAW,EAAwB,CAC1C,GAAI,GAAY,EAAU,MAAO,GACjC,IAAM,EAAO,EAAM,EAAgB,CAAK,EAGxC,OAFI,IAAS,IACb,EAAkB,CAAI,EACf,GACT,CAEA,GAAM,CAAE,MAAO,GAAa,KAAM,IAAe,GAAe,GAAU,EAAW,EAAQ,CAAI,CAAC,EAElG,SAAS,GAAc,EAA0C,CAG/D,GAAI,EAAE,MAAQ,QAAS,CACrB,EAAc,EACd,IAAgB,CAAC,EACjB,MACF,CACA,GAAI,CAAC,EAAa,CAChB,IAAgB,CAAC,EACjB,MACF,CACA,GAAI,EAAE,MAAQ,UAAW,CACvB,EAAE,eAAe,EACjB,EAAW,EAAE,SAAW,EAAO,GAAK,CAAI,EACxC,MACF,CACA,GAAI,EAAE,MAAQ,YAAa,CACzB,EAAE,eAAe,EACjB,EAAW,EAAE,SAAW,CAAC,EAAO,GAAK,CAAC,CAAI,EAC1C,MACF,CACA,IAAgB,CAAC,CACnB,CAEA,IAAM,GAAiB,IAAS,IAAM,IAAM,IAAS,IAAM,IAAM,GAE3D,GAAkB,EAAiB,EACnC,EAAc,IAAc,CAAC,GAAY,CAAC,EAC1C,EAAU,IAAiB,SAAY,IAAiB,QAAU,GAElE,EAAc,EAAM,EAAgB,CAAI,IAAM,EAC9C,EAAc,EAAM,EAAgB,CAAI,IAAM,EAE9C,EAAe,CAAE,QAAS,GAAa,OAAQ,EAAW,EAE1D,EACJ,GAAe,CAAC,EACd,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,OAAQ,GAAY,MAAM,EAAG,MAAO,GAAY,OAA1E,SAAA,CACE,EAAC,EAAD,CACE,UAAW,GAAY,eACvB,MAAO,GAAY,eACnB,MAAO,EAAK,SACZ,KAAM,GACN,SAAU,CAAC,EACX,UAAW,EACX,GAAI,CACL,CAAA,EACD,EAAC,EAAD,CACE,UAAW,GAAY,eACvB,MAAO,GAAY,eACnB,MAAO,EAAK,SACZ,KAAM,GACN,SAAU,CAAC,EACX,UAAW,GACX,GAAI,CACL,CAAA,CACE,CACH,CAAA,EAAA,KAIA,GAAkB,EACpB,CACE,KAAM,aACN,gBAAiB,OAAO,SAAS,CAAa,EAAI,EAAgB,IAAA,GAClE,gBAAiB,EACjB,gBAAiB,EACjB,iBAAkB,GAAmB,IAAA,EACvC,EACA,CAAC,EAIC,EAAU,EAAgB,CAAC,EAAc,gBAAkB,CAAC,EAI5D,EAAe,GAAe,EAE9B,EACJ,EAAC,EAAD,CACS,QACP,UAAW,EACT,EAAO,YACP,EAAG,EAAO,kBAAmB,CAAa,EAG1C,EAAe,IAAA,GAAY,EAC3B,EAAe,IAAA,GAAY,GAAY,KACvC,GAAY,KACd,EACA,MAAO,CACL,GAAI,EAAe,IAAA,GAAY,EAC/B,GAAI,EAAe,IAAA,GAAY,GAAY,KAC3C,GAAG,GAAY,KACjB,EACA,MAAO,EACP,aAAgB,CAAC,EACjB,cAAe,KACf,WAAY,EACZ,WAAc,CACZ,EAAc,EACV,EAAM,UAAS,EAAM,QAAQ,QAAU,GAC7C,EACS,UACT,aACE,EACI,EAAc,gBACX,EAAc,uBAAuB,GAAK,GAC3C,GACD,GAAS,GAEH,eACH,WACA,WACV,OAAQ,EAER,WAAY,EAAgB,CAAE,OAAQ,EAAO,WAAY,EAAI,IAAA,GAC7D,UAAW,IAAiB,EAAI,UAAY,UAC5C,YACE,EAAC,EAAD,CAIE,eAAgB,EAAM,CAAE,YAAa,EAAkB,EAAK,WAAY,IAAW,OAAO,EAC1F,cAAe,GAAQ,MAAe,EACpB,oBACC,qBACL,eACK,qBACnB,OAAQ,GACA,UACR,UAAW,GAEX,YAAa,GACb,GAAI,GACJ,GAAI,CACL,CAAA,CAEJ,CAAA,EAKH,OAFK,EAKH,EAAC,GAAD,CACE,UAAW,EAAG,EAAO,MAAO,EAAG,EAAO,cAAe,CAAC,CAAQ,EAAG,EAAW,GAAY,IAAI,EAC5F,MAAO,CAAE,GAAG,EAAO,GAAG,GAAY,IAAK,EAFzC,SAAA,CAIE,EAAC,EAAD,CACE,UAAW,GAAY,eACvB,MAAO,GAAY,eACnB,MAAO,EAAK,SACZ,KAAM,GACN,SAAU,CAAC,EACX,UAAW,GACX,GAAI,CACL,CAAA,EACA,EACD,EAAC,EAAD,CACE,UAAW,GAAY,eACvB,MAAO,GAAY,eACnB,MAAO,EAAK,SACZ,KAAM,GACN,SAAU,CAAC,EACX,UAAW,EACX,GAAI,CACL,CAAA,CACU,IA5BW,CA8B5B"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import n from"../tooltip/tooltip.mjs";import{OtpInputCharset as r}from"./otpInput.types.mjs";import i from"./otpInput.module.mjs";import{useEffect as a,useRef as o,useState as s}from"react";import c from"classnames";import{jsx as l,jsxs as u}from"react/jsx-runtime";var d=/[0-9]/g,f=/[a-zA-Z0-9]/g,ee={field:`One-time code`,digit:(e,t)=>`Digit ${e} of ${t}`};function p(e,t){let n=t===r.Alphanumeric?f:d;return(e.match(n)||[]).join(``)}function m(d){let{length:f=6,value:m,onChange:h=()=>{},onComplete:g=()=>{},formValidator:_=null,charset:v=r.Numeric,masked:y=!1,groupAfter:b=[],label:x=``,isValid:S=!0,errorMessage:C=``,isRequired:w=!1,clearOnError:T=!1,clearErrorDelay:E=900,disabled:D=!1,autoFocus:O=!1,className:k=``,style:A,classNames:j,styles:M,labels:N,...P}=e(`OtpInput`,d),F=t(`OtpInput`,N,ee),I=(_?_.value:m)??``,[,L]=s(0),[R,z]=s(!1),[B,V]=s(!1),[H,U]=s(!1),[W,G]=s(!1),[K,q]=s(!1),J=o([]);a(()=>{if(!_)return;_.validate();let e=()=>L(e=>e+1);return _.registerOnUpdateListener(e),()=>_.removeOnUpdateListener(e)},[_]),a(()=>{if(!O)return;let e=Math.min(I.length,f-1);J.current[e]?.focus()},[]);let Y=_?_.touched&&_.hasError():!S||!!C,X=_?_.getCurrentErrorMessage():C||``;Y!==K&&(q(Y),Y&&z(!0),G(Y&&T)),a(()=>{if(!R)return;let e=window.setTimeout(()=>z(!1),400);return()=>window.clearTimeout(e)},[R]),a(()=>{if(!W||!T)return;let e=window.setTimeout(()=>{G(!1),Z(``),J.current[0]?.focus()},E);return()=>window.clearTimeout(e)},[W,T,E]);function Z(e){let t=I.length===f,n=p(e,v).slice(0,f);_&&_.set(n),h?.(n),n.length===f&&!t&&g?.(n)}function Q(e,t){let n=I.split(``);n[e]=t,Z(n.join(``))}function te(e,t){if(D)return;if(t===``){Q(e,``);return}let n=p(t,v);if(!n)return;let r=Math.min(e,I.length);if(t.length>1){$(r,n);return}Q(r,n),r<f-1?J.current[r+1]?.focus():J.current[r]?.blur()}function $(e,t){let n=p(t,v);if(!n)return;let r=I.padEnd(f,` `).split(``),i=e;for(let e of n){if(i>=f)break;r[i]=e,i+=1}Z(r.join(``).trimEnd());let a=Math.min(i,f-1);J.current[a]?.focus(),i>=f&&J.current[f-1]?.blur()}function ne(e,t){if(D)return;let n=t.clipboardData.getData(`text`);n&&(t.preventDefault(),$(Math.min(e,I.length),n))}function re(e,t){if(!D){if(t.key.length===1&&!t.ctrlKey&&!t.metaKey&&!t.altKey){let n=p(t.key,v);if(n&&I[e]===n){t.preventDefault(),Q(e,n),e<f-1?J.current[e+1]?.focus():J.current[e]?.blur();return}}if(t.key===`Backspace`){if(I[e]){Q(e,``);return}e>0&&(t.preventDefault(),Q(e-1,``),J.current[e-1]?.focus());return}if(t.key===`Delete`){I[e]&&Q(e,``);return}if(t.key===`ArrowLeft`&&e>0){t.preventDefault(),J.current[e-1]?.focus();return}t.key===`ArrowRight`&&e<f-1&&(t.preventDefault(),J.current[e+1]?.focus())}}function ie(e){e.target.select()}function ae(){V(!0)}function oe(e){e.currentTarget.contains(e.relatedTarget)||(V(!1),_&&(_.touched=!0))}return u(`div`,{...P,className:c(i.otpInput,D&&i.otpInputDisabled,k),style:A,children:[x&&u(`div`,{className:c(i.otpInputLabel,j?.label),style:M?.label,children:[x,w&&l(`span`,{className:i.otpInputRequiredMark,children:` *`})]}),l(`div`,{role:`group`,"aria-label":x||F.field,className:c(i.otpInputBoxes,R&&i.otpInputBoxesShake,j?.inputs),style:M?.inputs,onFocus:ae,onBlur:oe,onMouseEnter:()=>U(!0),onMouseLeave:()=>U(!1),children:Array.from({length:f},(e,t)=>{let n=I[t]||``;return u(`div`,{className:i.otpInputBoxWrapper,children:[l(`input`,{ref:e=>{J.current[t]=e},type:y?`password`:`text`,inputMode:v===r.Numeric?`numeric`:`text`,pattern:v===r.Numeric?`[0-9]*`:void 0,autoComplete:t===0?`one-time-code`:`off`,maxLength:1,disabled:D,value:n,"aria-label":F.digit(t+1,f),"aria-invalid":Y||void 0,className:c(i.otpInputBox,Y&&i.otpInputBoxError,n&&i.otpInputBoxFilled,j?.input),style:M?.input,onChange:e=>te(t,e.target.value),onKeyDown:e=>re(t,e),onPaste:e=>ne(t,e),onFocus:ie}),b.includes(t+1)&&t<f-1&&l(`span`,{className:c(i.otpInputSeparator,j?.separator),style:M?.separator})]},t)})}),l(n,{variant:`error`,message:X,isVisible:Y&&(B||H)})]})}export{m as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import n from"../tooltip/tooltip.mjs";import{OtpInputCharset as r}from"./otpInput.types.mjs";import i from"./otpInput.module.mjs";import{useEffect as a,useRef as o,useState as s}from"react";import c from"classnames";import{jsx as l,jsxs as u}from"react/jsx-runtime";var d=/[0-9]/g,f=/[a-zA-Z0-9]/g,ee={field:`One-time code`,digit:(e,t)=>`Digit ${e} of ${t}`};function p(e,t){let n=t===r.Alphanumeric?f:d;return(e.match(n)||[]).join(``)}function m(d){let{length:f=6,value:m,onChange:h=()=>{},onComplete:g=()=>{},formValidator:_=null,charset:v=r.Numeric,masked:y=!1,groupAfter:b=[],label:x=``,isValid:S=!0,errorMessage:C=``,isRequired:w=!1,clearOnError:T=!1,clearErrorDelay:E=900,disabled:D=!1,autoFocus:O=!1,className:k=``,style:A,classNames:j,styles:M,labels:N,...P}=e(`OtpInput`,d),F=t(`OtpInput`,N,ee),I=(_?_.value:m)??``,[,L]=s(0),[R,z]=s(!1),[B,V]=s(!1),[H,U]=s(!1),[W,G]=s(!1),[K,q]=s(!1),J=o([]);a(()=>{if(!_)return;_.validate();let e=()=>L(e=>e+1);return _.registerOnUpdateListener(e),()=>_.removeOnUpdateListener(e)},[_]),a(()=>{if(!O)return;let e=Math.min(I.length,f-1);J.current[e]?.focus()},[]);let Y=_?_.shouldShowError:!S||!!C,X=_?_.getCurrentErrorMessage():C||``;Y!==K&&(q(Y),Y&&z(!0),G(Y&&T)),a(()=>{if(!R)return;let e=window.setTimeout(()=>z(!1),400);return()=>window.clearTimeout(e)},[R]),a(()=>{if(!W||!T)return;let e=window.setTimeout(()=>{G(!1),Z(``),J.current[0]?.focus()},E);return()=>window.clearTimeout(e)},[W,T,E]);function Z(e){let t=I.length===f,n=p(e,v).slice(0,f);_&&_.set(n),h?.(n),n.length===f&&!t&&g?.(n)}function Q(e,t){let n=I.split(``);n[e]=t,Z(n.join(``))}function te(e,t){if(D)return;if(t===``){Q(e,``);return}let n=p(t,v);if(!n)return;let r=Math.min(e,I.length);if(t.length>1){$(r,n);return}Q(r,n),r<f-1?J.current[r+1]?.focus():J.current[r]?.blur()}function $(e,t){let n=p(t,v);if(!n)return;let r=I.padEnd(f,` `).split(``),i=e;for(let e of n){if(i>=f)break;r[i]=e,i+=1}Z(r.join(``).trimEnd());let a=Math.min(i,f-1);J.current[a]?.focus(),i>=f&&J.current[f-1]?.blur()}function ne(e,t){if(D)return;let n=t.clipboardData.getData(`text`);n&&(t.preventDefault(),$(Math.min(e,I.length),n))}function re(e,t){if(!D){if(t.key.length===1&&!t.ctrlKey&&!t.metaKey&&!t.altKey){let n=p(t.key,v);if(n&&I[e]===n){t.preventDefault(),Q(e,n),e<f-1?J.current[e+1]?.focus():J.current[e]?.blur();return}}if(t.key===`Backspace`){if(I[e]){Q(e,``);return}e>0&&(t.preventDefault(),Q(e-1,``),J.current[e-1]?.focus());return}if(t.key===`Delete`){I[e]&&Q(e,``);return}if(t.key===`ArrowLeft`&&e>0){t.preventDefault(),J.current[e-1]?.focus();return}t.key===`ArrowRight`&&e<f-1&&(t.preventDefault(),J.current[e+1]?.focus())}}function ie(e){e.target.select()}function ae(){V(!0)}function oe(e){e.currentTarget.contains(e.relatedTarget)||(V(!1),_&&(_.touched=!0))}return u(`div`,{...P,className:c(i.otpInput,D&&i.otpInputDisabled,k),style:A,children:[x&&u(`div`,{className:c(i.otpInputLabel,j?.label),style:M?.label,children:[x,w&&l(`span`,{className:i.otpInputRequiredMark,children:` *`})]}),l(`div`,{role:`group`,"aria-label":x||F.field,className:c(i.otpInputBoxes,R&&i.otpInputBoxesShake,j?.inputs),style:M?.inputs,onFocus:ae,onBlur:oe,onMouseEnter:()=>U(!0),onMouseLeave:()=>U(!1),children:Array.from({length:f},(e,t)=>{let n=I[t]||``;return u(`div`,{className:i.otpInputBoxWrapper,children:[l(`input`,{ref:e=>{J.current[t]=e},type:y?`password`:`text`,inputMode:v===r.Numeric?`numeric`:`text`,pattern:v===r.Numeric?`[0-9]*`:void 0,autoComplete:t===0?`one-time-code`:`off`,maxLength:1,disabled:D,value:n,"aria-label":F.digit(t+1,f),"aria-invalid":Y||void 0,className:c(i.otpInputBox,Y&&i.otpInputBoxError,n&&i.otpInputBoxFilled,j?.input),style:M?.input,onChange:e=>te(t,e.target.value),onKeyDown:e=>re(t,e),onPaste:e=>ne(t,e),onFocus:ie}),b.includes(t+1)&&t<f-1&&l(`span`,{className:c(i.otpInputSeparator,j?.separator),style:M?.separator})]},t)})}),l(n,{variant:`error`,message:X,isVisible:Y&&(B||H)})]})}export{m as default};
2
2
  //# sourceMappingURL=otpInput.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"otpInput.mjs","names":[],"sources":["../../../../package/common/otpInput/otpInput.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport Tooltip from 'components/common/tooltip';\nimport { useComponentDefaults, useLabels } from '../configProvider';\n\nimport { OtpInputCharset, type OtpInputLabels, type OtpInputProps } from './otpInput.types';\nimport styles from './otpInput.module.pcss';\n\nconst NUMERIC_RE = /[0-9]/g;\nconst ALPHANUMERIC_RE = /[a-zA-Z0-9]/g;\n\nconst DEFAULT_LABELS: Required<OtpInputLabels> = {\n field: 'One-time code',\n digit: (position, length) => `Digit ${position} of ${length}`,\n};\n\nfunction filterChars(raw: string, charset: OtpInputCharset): string {\n const re = charset === OtpInputCharset.Alphanumeric ? ALPHANUMERIC_RE : NUMERIC_RE;\n return (raw.match(re) || []).join('');\n}\n\nfunction OtpInput(props: OtpInputProps): ReactElement {\n const {\n length = 6,\n value,\n onChange = () => {},\n onComplete = () => {},\n formValidator = null,\n charset = OtpInputCharset.Numeric,\n masked = false,\n groupAfter = [],\n label = '',\n isValid = true,\n errorMessage = '',\n isRequired = false,\n clearOnError = false,\n clearErrorDelay = 900,\n disabled = false,\n autoFocus = false,\n className = '',\n style,\n classNames,\n styles: slotStyles,\n labels,\n ...rest\n } = useComponentDefaults('OtpInput', props);\n const text = useLabels('OtpInput', labels, DEFAULT_LABELS);\n\n const currentValue = (formValidator ? (formValidator.value as string) : value) ?? '';\n const [, forceUpdate] = useState(0);\n const [isShaking, setIsShaking] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [pendingClear, setPendingClear] = useState(false);\n const [wasError, setWasError] = useState(false);\n const inputRefs = useRef<Array<HTMLInputElement | null>>([]);\n\n useEffect(() => {\n if (!formValidator) return;\n formValidator.validate();\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => formValidator.removeOnUpdateListener(listener);\n }, [formValidator]);\n\n useEffect(() => {\n if (!autoFocus) return;\n const firstEmpty = Math.min(currentValue.length, length - 1);\n inputRefs.current[firstEmpty]?.focus();\n // Only run once, on mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const showError = formValidator ? formValidator.touched && formValidator.hasError() : !isValid || !!errorMessage;\n const displayedErrorMessage = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n // State set during render, not a ref read in an effect: the ref survives a remount (StrictMode,\n // <Activity>), so the re-run saw no new error and dropped the pending clear.\n if (showError !== wasError) {\n setWasError(showError);\n if (showError) setIsShaking(true);\n setPendingClear(showError && clearOnError);\n }\n\n useEffect(() => {\n if (!isShaking) return;\n const id = window.setTimeout(() => setIsShaking(false), 400);\n return () => window.clearTimeout(id);\n }, [isShaking]);\n\n useEffect(() => {\n if (!pendingClear || !clearOnError) return;\n const id = window.setTimeout(() => {\n setPendingClear(false);\n commit('');\n inputRefs.current[0]?.focus();\n }, clearErrorDelay);\n return () => window.clearTimeout(id);\n // commit is recreated every render; listing it would restart the delay on each keystroke.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [pendingClear, clearOnError, clearErrorDelay]);\n\n function commit(next: string) {\n const wasComplete = currentValue.length === length;\n const clipped = filterChars(next, charset).slice(0, length);\n if (formValidator) {\n formValidator.set(clipped);\n }\n onChange?.(clipped);\n // Only fire once per genuinely new completion (going from partial to full), not on every\n // keystroke made while correcting an already-full code.\n if (clipped.length === length && !wasComplete) {\n onComplete?.(clipped);\n }\n }\n\n function setCharAt(index: number, char: string) {\n const chars = currentValue.split('');\n chars[index] = char;\n commit(chars.join(''));\n }\n\n function handleChange(index: number, rawInput: string) {\n if (disabled) return;\n if (rawInput === '') {\n // Box was cleared (e.g. select + delete) — Backspace itself is handled in onKeyDown.\n setCharAt(index, '');\n return;\n }\n\n const filtered = filterChars(rawInput, charset);\n if (!filtered) {\n // Typed an invalid/disallowed character — the controlled `value` re-render discards it.\n return;\n }\n\n // Clicking straight into a box past the contiguous filled prefix (skipping ahead of\n // empty boxes) fills the next expected slot instead, rather than leaving a gap before it\n // that setCharAt/distributeFrom can't represent in a plain string and would silently collapse.\n const targetIndex = Math.min(index, currentValue.length);\n\n if (rawInput.length > 1) {\n // Mobile autofill / SMS suggestions can drop the whole code into one box, bypassing maxLength.\n distributeFrom(targetIndex, filtered);\n return;\n }\n\n setCharAt(targetIndex, filtered);\n if (targetIndex < length - 1) {\n inputRefs.current[targetIndex + 1]?.focus();\n } else {\n inputRefs.current[targetIndex]?.blur();\n }\n }\n\n function distributeFrom(startIndex: number, chars: string) {\n const clean = filterChars(chars, charset);\n if (!clean) return;\n const existing = currentValue.padEnd(length, ' ').split('');\n let cursor = startIndex;\n for (const char of clean) {\n if (cursor >= length) break;\n existing[cursor] = char;\n cursor += 1;\n }\n commit(existing.join('').trimEnd());\n const nextFocus = Math.min(cursor, length - 1);\n inputRefs.current[nextFocus]?.focus();\n if (cursor >= length) {\n inputRefs.current[length - 1]?.blur();\n }\n }\n\n function handlePaste(index: number, event: React.ClipboardEvent<HTMLInputElement>) {\n if (disabled) return;\n const pasted = event.clipboardData.getData('text');\n if (!pasted) return;\n event.preventDefault();\n // Same clamp as handleChange — pasting into a box past the filled prefix starts at the\n // next expected slot instead of leaving an unrepresentable gap before it.\n distributeFrom(Math.min(index, currentValue.length), pasted);\n }\n\n function handleKeyDown(index: number, event: React.KeyboardEvent<HTMLInputElement>) {\n if (disabled) return;\n\n // React's controlled-input onChange never fires when the resulting value is identical to\n // what's already rendered (its value tracker sees no change) — so retyping the exact digit\n // already in a box would otherwise silently do nothing. keydown always fires, so handle that\n // one case here and skip the native edit entirely.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {\n const typed = filterChars(event.key, charset);\n if (typed && currentValue[index] === typed) {\n event.preventDefault();\n setCharAt(index, typed);\n if (index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n } else {\n inputRefs.current[index]?.blur();\n }\n return;\n }\n }\n\n if (event.key === 'Backspace') {\n if (currentValue[index]) {\n setCharAt(index, '');\n return;\n }\n if (index > 0) {\n event.preventDefault();\n setCharAt(index - 1, '');\n inputRefs.current[index - 1]?.focus();\n }\n return;\n }\n if (event.key === 'Delete') {\n if (currentValue[index]) setCharAt(index, '');\n return;\n }\n if (event.key === 'ArrowLeft' && index > 0) {\n event.preventDefault();\n inputRefs.current[index - 1]?.focus();\n return;\n }\n if (event.key === 'ArrowRight' && index < length - 1) {\n event.preventDefault();\n inputRefs.current[index + 1]?.focus();\n }\n }\n\n function handleFocus(event: React.FocusEvent<HTMLInputElement>) {\n event.target.select();\n }\n\n function handleGroupFocus() {\n setIsFocused(true);\n }\n\n function handleGroupBlur(event: React.FocusEvent<HTMLDivElement>) {\n // Auto-advance moves focus between boxes constantly while typing — only treat this as\n // leaving the group once focus actually lands outside it, not on the next box.\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n if (formValidator) formValidator.touched = true;\n }\n\n return (\n <div {...rest} className={cx(styles.otpInput, disabled && styles.otpInputDisabled, className)} style={style}>\n {label && (\n <div className={cx(styles.otpInputLabel, classNames?.label)} style={slotStyles?.label}>\n {label}\n {isRequired && <span className={styles.otpInputRequiredMark}> *</span>}\n </div>\n )}\n <div\n role='group'\n aria-label={label || text.field}\n className={cx(styles.otpInputBoxes, isShaking && styles.otpInputBoxesShake, classNames?.inputs)}\n style={slotStyles?.inputs}\n onFocus={handleGroupFocus}\n onBlur={handleGroupBlur}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {Array.from({ length }, (_, index) => {\n const char = currentValue[index] || '';\n return (\n <div key={index} className={styles.otpInputBoxWrapper}>\n <input\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type={masked ? 'password' : 'text'}\n inputMode={charset === OtpInputCharset.Numeric ? 'numeric' : 'text'}\n pattern={charset === OtpInputCharset.Numeric ? '[0-9]*' : undefined}\n autoComplete={index === 0 ? 'one-time-code' : 'off'}\n maxLength={1}\n disabled={disabled}\n value={char}\n aria-label={text.digit(index + 1, length)}\n aria-invalid={showError || undefined}\n className={cx(\n styles.otpInputBox,\n showError && styles.otpInputBoxError,\n char && styles.otpInputBoxFilled,\n classNames?.input,\n )}\n style={slotStyles?.input}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={(e) => handlePaste(index, e)}\n onFocus={handleFocus}\n />\n {groupAfter.includes(index + 1) && index < length - 1 && (\n <span className={cx(styles.otpInputSeparator, classNames?.separator)} style={slotStyles?.separator} />\n )}\n </div>\n );\n })}\n </div>\n <Tooltip variant='error' message={displayedErrorMessage} isVisible={showError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default OtpInput;\n"],"mappings":"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"}
1
+ {"version":3,"file":"otpInput.mjs","names":[],"sources":["../../../../package/common/otpInput/otpInput.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport Tooltip from 'components/common/tooltip';\nimport { useComponentDefaults, useLabels } from '../configProvider';\n\nimport { OtpInputCharset, type OtpInputLabels, type OtpInputProps } from './otpInput.types';\nimport styles from './otpInput.module.pcss';\n\nconst NUMERIC_RE = /[0-9]/g;\nconst ALPHANUMERIC_RE = /[a-zA-Z0-9]/g;\n\nconst DEFAULT_LABELS: Required<OtpInputLabels> = {\n field: 'One-time code',\n digit: (position, length) => `Digit ${position} of ${length}`,\n};\n\nfunction filterChars(raw: string, charset: OtpInputCharset): string {\n const re = charset === OtpInputCharset.Alphanumeric ? ALPHANUMERIC_RE : NUMERIC_RE;\n return (raw.match(re) || []).join('');\n}\n\nfunction OtpInput(props: OtpInputProps): ReactElement {\n const {\n length = 6,\n value,\n onChange = () => {},\n onComplete = () => {},\n formValidator = null,\n charset = OtpInputCharset.Numeric,\n masked = false,\n groupAfter = [],\n label = '',\n isValid = true,\n errorMessage = '',\n isRequired = false,\n clearOnError = false,\n clearErrorDelay = 900,\n disabled = false,\n autoFocus = false,\n className = '',\n style,\n classNames,\n styles: slotStyles,\n labels,\n ...rest\n } = useComponentDefaults('OtpInput', props);\n const text = useLabels('OtpInput', labels, DEFAULT_LABELS);\n\n const currentValue = (formValidator ? (formValidator.value as string) : value) ?? '';\n const [, forceUpdate] = useState(0);\n const [isShaking, setIsShaking] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [pendingClear, setPendingClear] = useState(false);\n const [wasError, setWasError] = useState(false);\n const inputRefs = useRef<Array<HTMLInputElement | null>>([]);\n\n useEffect(() => {\n if (!formValidator) return;\n formValidator.validate();\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => formValidator.removeOnUpdateListener(listener);\n }, [formValidator]);\n\n useEffect(() => {\n if (!autoFocus) return;\n const firstEmpty = Math.min(currentValue.length, length - 1);\n inputRefs.current[firstEmpty]?.focus();\n // Only run once, on mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const showError = formValidator ? formValidator.shouldShowError : !isValid || !!errorMessage;\n const displayedErrorMessage = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n // State set during render, not a ref read in an effect: the ref survives a remount (StrictMode,\n // <Activity>), so the re-run saw no new error and dropped the pending clear.\n if (showError !== wasError) {\n setWasError(showError);\n if (showError) setIsShaking(true);\n setPendingClear(showError && clearOnError);\n }\n\n useEffect(() => {\n if (!isShaking) return;\n const id = window.setTimeout(() => setIsShaking(false), 400);\n return () => window.clearTimeout(id);\n }, [isShaking]);\n\n useEffect(() => {\n if (!pendingClear || !clearOnError) return;\n const id = window.setTimeout(() => {\n setPendingClear(false);\n commit('');\n inputRefs.current[0]?.focus();\n }, clearErrorDelay);\n return () => window.clearTimeout(id);\n // commit is recreated every render; listing it would restart the delay on each keystroke.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [pendingClear, clearOnError, clearErrorDelay]);\n\n function commit(next: string) {\n const wasComplete = currentValue.length === length;\n const clipped = filterChars(next, charset).slice(0, length);\n if (formValidator) {\n formValidator.set(clipped);\n }\n onChange?.(clipped);\n // Only fire once per genuinely new completion (going from partial to full), not on every\n // keystroke made while correcting an already-full code.\n if (clipped.length === length && !wasComplete) {\n onComplete?.(clipped);\n }\n }\n\n function setCharAt(index: number, char: string) {\n const chars = currentValue.split('');\n chars[index] = char;\n commit(chars.join(''));\n }\n\n function handleChange(index: number, rawInput: string) {\n if (disabled) return;\n if (rawInput === '') {\n // Box was cleared (e.g. select + delete) — Backspace itself is handled in onKeyDown.\n setCharAt(index, '');\n return;\n }\n\n const filtered = filterChars(rawInput, charset);\n if (!filtered) {\n // Typed an invalid/disallowed character — the controlled `value` re-render discards it.\n return;\n }\n\n // Clicking straight into a box past the contiguous filled prefix (skipping ahead of\n // empty boxes) fills the next expected slot instead, rather than leaving a gap before it\n // that setCharAt/distributeFrom can't represent in a plain string and would silently collapse.\n const targetIndex = Math.min(index, currentValue.length);\n\n if (rawInput.length > 1) {\n // Mobile autofill / SMS suggestions can drop the whole code into one box, bypassing maxLength.\n distributeFrom(targetIndex, filtered);\n return;\n }\n\n setCharAt(targetIndex, filtered);\n if (targetIndex < length - 1) {\n inputRefs.current[targetIndex + 1]?.focus();\n } else {\n inputRefs.current[targetIndex]?.blur();\n }\n }\n\n function distributeFrom(startIndex: number, chars: string) {\n const clean = filterChars(chars, charset);\n if (!clean) return;\n const existing = currentValue.padEnd(length, ' ').split('');\n let cursor = startIndex;\n for (const char of clean) {\n if (cursor >= length) break;\n existing[cursor] = char;\n cursor += 1;\n }\n commit(existing.join('').trimEnd());\n const nextFocus = Math.min(cursor, length - 1);\n inputRefs.current[nextFocus]?.focus();\n if (cursor >= length) {\n inputRefs.current[length - 1]?.blur();\n }\n }\n\n function handlePaste(index: number, event: React.ClipboardEvent<HTMLInputElement>) {\n if (disabled) return;\n const pasted = event.clipboardData.getData('text');\n if (!pasted) return;\n event.preventDefault();\n // Same clamp as handleChange — pasting into a box past the filled prefix starts at the\n // next expected slot instead of leaving an unrepresentable gap before it.\n distributeFrom(Math.min(index, currentValue.length), pasted);\n }\n\n function handleKeyDown(index: number, event: React.KeyboardEvent<HTMLInputElement>) {\n if (disabled) return;\n\n // React's controlled-input onChange never fires when the resulting value is identical to\n // what's already rendered (its value tracker sees no change) — so retyping the exact digit\n // already in a box would otherwise silently do nothing. keydown always fires, so handle that\n // one case here and skip the native edit entirely.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {\n const typed = filterChars(event.key, charset);\n if (typed && currentValue[index] === typed) {\n event.preventDefault();\n setCharAt(index, typed);\n if (index < length - 1) {\n inputRefs.current[index + 1]?.focus();\n } else {\n inputRefs.current[index]?.blur();\n }\n return;\n }\n }\n\n if (event.key === 'Backspace') {\n if (currentValue[index]) {\n setCharAt(index, '');\n return;\n }\n if (index > 0) {\n event.preventDefault();\n setCharAt(index - 1, '');\n inputRefs.current[index - 1]?.focus();\n }\n return;\n }\n if (event.key === 'Delete') {\n if (currentValue[index]) setCharAt(index, '');\n return;\n }\n if (event.key === 'ArrowLeft' && index > 0) {\n event.preventDefault();\n inputRefs.current[index - 1]?.focus();\n return;\n }\n if (event.key === 'ArrowRight' && index < length - 1) {\n event.preventDefault();\n inputRefs.current[index + 1]?.focus();\n }\n }\n\n function handleFocus(event: React.FocusEvent<HTMLInputElement>) {\n event.target.select();\n }\n\n function handleGroupFocus() {\n setIsFocused(true);\n }\n\n function handleGroupBlur(event: React.FocusEvent<HTMLDivElement>) {\n // Auto-advance moves focus between boxes constantly while typing — only treat this as\n // leaving the group once focus actually lands outside it, not on the next box.\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n if (formValidator) formValidator.touched = true;\n }\n\n return (\n <div {...rest} className={cx(styles.otpInput, disabled && styles.otpInputDisabled, className)} style={style}>\n {label && (\n <div className={cx(styles.otpInputLabel, classNames?.label)} style={slotStyles?.label}>\n {label}\n {isRequired && <span className={styles.otpInputRequiredMark}> *</span>}\n </div>\n )}\n <div\n role='group'\n aria-label={label || text.field}\n className={cx(styles.otpInputBoxes, isShaking && styles.otpInputBoxesShake, classNames?.inputs)}\n style={slotStyles?.inputs}\n onFocus={handleGroupFocus}\n onBlur={handleGroupBlur}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {Array.from({ length }, (_, index) => {\n const char = currentValue[index] || '';\n return (\n <div key={index} className={styles.otpInputBoxWrapper}>\n <input\n ref={(el) => {\n inputRefs.current[index] = el;\n }}\n type={masked ? 'password' : 'text'}\n inputMode={charset === OtpInputCharset.Numeric ? 'numeric' : 'text'}\n pattern={charset === OtpInputCharset.Numeric ? '[0-9]*' : undefined}\n autoComplete={index === 0 ? 'one-time-code' : 'off'}\n maxLength={1}\n disabled={disabled}\n value={char}\n aria-label={text.digit(index + 1, length)}\n aria-invalid={showError || undefined}\n className={cx(\n styles.otpInputBox,\n showError && styles.otpInputBoxError,\n char && styles.otpInputBoxFilled,\n classNames?.input,\n )}\n style={slotStyles?.input}\n onChange={(e) => handleChange(index, e.target.value)}\n onKeyDown={(e) => handleKeyDown(index, e)}\n onPaste={(e) => handlePaste(index, e)}\n onFocus={handleFocus}\n />\n {groupAfter.includes(index + 1) && index < length - 1 && (\n <span className={cx(styles.otpInputSeparator, classNames?.separator)} style={slotStyles?.separator} />\n )}\n </div>\n );\n })}\n </div>\n <Tooltip variant='error' message={displayedErrorMessage} isVisible={showError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default OtpInput;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"otpInput.types.mjs","names":[],"sources":["../../../../package/common/otpInput/otpInput.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { FormValidator } from 'services/formValidation';\nimport type { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';\n\nexport type OtpInputSlots = 'root' | 'label' | 'inputs' | 'input' | 'separator';\n\nexport enum OtpInputCharset {\n /** Digits only (0-9). Shows a numeric mobile keyboard. */\n Numeric = 'numeric',\n /** Letters and digits. */\n Alphanumeric = 'alphanumeric',\n}\n\n/** Text OtpInput renders of its own, for translating it. */\nexport interface OtpInputLabels {\n /** The group of boxes, used when no `label` is given. */\n field?: string;\n /** One box, given its 1-based position and how many there are. */\n digit?: (position: number, length: number) => string;\n}\n\nexport interface OtpInputProps extends Omit<HtmlProps, 'onChange'> {\n /** Number of character boxes. Default 6. */\n length?: number;\n /** Current code as a plain string, e.g. \"123456\". */\n value?: string;\n /** Fired on every change with the full current code (may be shorter than `length`). */\n onChange?: ((value: string) => void) | null;\n /** Fired once, when the code reaches `length` characters. */\n onComplete?: ((value: string) => void) | null;\n formValidator?: FormValidator | null;\n\n /** Restricts which characters can be typed and picks the mobile keyboard. Default `numeric`. */\n charset?: OtpInputCharset;\n /** Render each box's value as a dot, like a password field. */\n masked?: boolean;\n /** Draw a small gap after these 1-based box indices, e.g. `[3]` to split 6 boxes into 3+3. */\n groupAfter?: number[];\n\n label?: string;\n isValid?: boolean;\n /** Shown in an error Tooltip on focus/hover once `isValid` is `false` (or, with a\n * `formValidator`, once it's touched and has an error). Persists until the value changes\n * `isValid`/`formValidator`'s error state itself — see `clearOnError` to auto-dismiss it. */\n errorMessage?: string;\n isRequired?: boolean;\n /** When an error newly appears (`isValid`/`formValidator` turning invalid), clear every box and\n * refocus the first one after `clearErrorDelay`, so the next attempt starts clean instead of the\n * user editing individual wrong digits (and re-triggering `onComplete` on every keystroke while\n * the code stays at full length). Default `false`. */\n clearOnError?: boolean;\n /** Delay in ms before `clearOnError` clears the boxes, so the wrong code and error message are\n * still visible for a moment first. Default `900`. */\n clearErrorDelay?: number;\n disabled?: boolean;\n /** Focuses the first empty box (or the first box) on mount. */\n autoFocus?: boolean;\n\n className?: string;\n style?: React.CSSProperties;\n /** Ref to the root element. */\n ref?: React.Ref<HTMLDivElement>;\n classNames?: SlotClassNames<OtpInputSlots>;\n styles?: SlotStyles<OtpInputSlots>;\n /** Overrides the text OtpInput renders of its own. Merged per key. */\n labels?: OtpInputLabels;\n}\n"],"mappings":"aAMA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,QAAA,UAEA,EAAA,aAAA,gBACF,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"otpInput.types.mjs","names":[],"sources":["../../../../package/common/otpInput/otpInput.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { FormValidator } from 'services/formValidation';\nimport type { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';\n\nexport type OtpInputSlots = 'root' | 'label' | 'inputs' | 'input' | 'separator';\n\nexport enum OtpInputCharset {\n /** Digits only (0-9). Shows a numeric mobile keyboard. */\n Numeric = 'numeric',\n /** Letters and digits. */\n Alphanumeric = 'alphanumeric',\n}\n\n/** Text OtpInput renders of its own, for translating it. */\nexport interface OtpInputLabels {\n /** The group of boxes, used when no `label` is given. */\n field?: string;\n /** One box, given its 1-based position and how many there are. */\n digit?: (position: number, length: number) => string;\n}\n\nexport interface OtpInputProps extends Omit<HtmlProps, 'onChange'> {\n /** Number of character boxes. Default 6. */\n length?: number;\n /** Current code as a plain string, e.g. \"123456\". */\n value?: string;\n /** Fired on every change with the full current code (may be shorter than `length`). */\n onChange?: ((value: string) => void) | null;\n /** Fired once, when the code reaches `length` characters. */\n onComplete?: ((value: string) => void) | null;\n formValidator?: FormValidator | null;\n\n /** Restricts which characters can be typed and picks the mobile keyboard. Default `numeric`. */\n charset?: OtpInputCharset;\n /** Render each box's value as a dot, like a password field. */\n masked?: boolean;\n /** Draw a small gap after these 1-based box indices, e.g. `[3]` to split 6 boxes into 3+3. */\n groupAfter?: number[];\n\n label?: string;\n isValid?: boolean;\n /** Shown in an error Tooltip on focus/hover once `isValid` is `false` (or, with a\n * `formValidator`, once its `shouldShowError` is true). Persists until the value changes\n * `isValid`/`formValidator`'s error state itself — see `clearOnError` to auto-dismiss it. */\n errorMessage?: string;\n isRequired?: boolean;\n /** When an error newly appears (`isValid`/`formValidator` turning invalid), clear every box and\n * refocus the first one after `clearErrorDelay`, so the next attempt starts clean instead of the\n * user editing individual wrong digits (and re-triggering `onComplete` on every keystroke while\n * the code stays at full length). Default `false`. */\n clearOnError?: boolean;\n /** Delay in ms before `clearOnError` clears the boxes, so the wrong code and error message are\n * still visible for a moment first. Default `900`. */\n clearErrorDelay?: number;\n disabled?: boolean;\n /** Focuses the first empty box (or the first box) on mount. */\n autoFocus?: boolean;\n\n className?: string;\n style?: React.CSSProperties;\n /** Ref to the root element. */\n ref?: React.Ref<HTMLDivElement>;\n classNames?: SlotClassNames<OtpInputSlots>;\n styles?: SlotStyles<OtpInputSlots>;\n /** Overrides the text OtpInput renders of its own. Merged per key. */\n labels?: OtpInputLabels;\n}\n"],"mappings":"aAMA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,QAAA,UAEA,EAAA,aAAA,gBACF,EAAA,CAAA,CAAA"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import t from"../tooltip/tooltip.mjs";import{RadioGroupOrientation as n}from"./radioGroup.types.mjs";import r from"./radioGroup.module.mjs";import{useEffect as i,useRef as a,useState as o}from"react";import s from"classnames";import{jsx as c,jsxs as l}from"react/jsx-runtime";function u(u){let{options:d=[],value:f,defaultValue:p,onChange:m=()=>{},orientation:h=n.Vertical,disabled:g=!1,size:_,tabIndex:v=0,formValidator:y=null,errorMessage:b=``,isValid:x=!0,className:S,style:C,classNames:w,styles:T,...E}=e(`RadioGroup`,u),D=a([]),O=a(y);i(()=>{O.current=y});let[,k]=o(0),[A,j]=o(p),[M,N]=o(!1),[P,F]=o(!1);i(()=>{if(!y)return;y.validate();let e=()=>k(e=>e+1);return y.registerOnUpdateListener(e),()=>y.removeOnUpdateListener(e)},[y]);let I=y?y.touched&&y.hasError():!x||!!b,L=y?y.getCurrentErrorMessage():b||``,R=f!==void 0&&!y,z=y?y.value:R?f:A;function B(e,t){y?y.set(e):R||j(e),m(e,t)}function V(e){e.currentTarget.contains(e.relatedTarget)||(N(!1),O.current&&(O.current.touched=!0))}let H=d.findIndex(e=>e.value===z),U=d.findIndex(e=>!e.disabled),W=H>=0?H:U;function G(e){D.current[e]?.focus()}function K(e,t,n){if(g||d.length===0)return;let r=t;for(let t=0;t<d.length;t++)if(r=(r+e+d.length)%d.length,!d[r].disabled){G(r),B(d[r].value,n);return}}function q(e,t){let n=d[t];switch(e.key){case`ArrowDown`:case`ArrowRight`:e.preventDefault(),K(1,t,e);break;case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),K(-1,t,e);break;case` `:case`Enter`:e.preventDefault(),!g&&!n.disabled&&B(n.value,e)}}return l(`div`,{...E,role:`radiogroup`,"aria-orientation":h,"aria-disabled":g||void 0,"aria-invalid":I||void 0,className:s(r.radioGroup,h===n.Horizontal&&r.radioGroupHorizontal,g&&r.radioGroupDisabled,I&&r.radioGroupError,S,w?.root),style:{..._?{"--radio-size":_}:null,...C,...T?.root},onFocus:()=>N(!0),onBlur:V,onMouseEnter:()=>F(!0),onMouseLeave:()=>F(!1),children:[d.map((e,t)=>{let n=e.value===z,i=g||!!e.disabled;return l(`div`,{ref:e=>{D.current[t]=e},role:`radio`,"aria-checked":n,"aria-disabled":i||void 0,tabIndex:i?-1:t===W?v:-1,className:s(r.radioGroupOption,n&&r.radioGroupOptionChecked,i&&r.radioGroupOptionDisabled,w?.option),style:T?.option,onClick:()=>{i||B(e.value)},onKeyDown:e=>q(e,t),children:[c(`div`,{className:s(r.radioGroupRadio,n&&r.radioGroupRadioChecked,w?.radio),style:T?.radio,children:c(`div`,{className:s(r.radioGroupDot,n&&r.radioGroupDotChecked,w?.dot),style:T?.dot})}),(e.label??e.value)!==``&&l(`div`,{className:r.radioGroupText,children:[c(`div`,{className:s(r.radioGroupLabel,w?.label),style:T?.label,children:e.label??e.value}),e.description!=null&&c(`div`,{className:s(r.radioGroupDescription,w?.description),style:T?.description,children:e.description})]})]},String(e.value))}),c(t,{variant:`error`,message:L,isVisible:I&&(M||P)})]})}export{u as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import t from"../tooltip/tooltip.mjs";import{RadioGroupOrientation as n}from"./radioGroup.types.mjs";import r from"./radioGroup.module.mjs";import{useEffect as i,useRef as a,useState as o}from"react";import s from"classnames";import{jsx as c,jsxs as l}from"react/jsx-runtime";function u(u){let{options:d=[],value:f,defaultValue:p,onChange:m=()=>{},orientation:h=n.Vertical,disabled:g=!1,size:_,tabIndex:v=0,formValidator:y=null,errorMessage:b=``,isValid:x=!0,className:S,style:C,classNames:w,styles:T,...E}=e(`RadioGroup`,u),D=a([]),O=a(y);i(()=>{O.current=y});let[,k]=o(0),[A,j]=o(p),[M,N]=o(!1),[P,F]=o(!1);i(()=>{if(!y)return;y.validate();let e=()=>k(e=>e+1);return y.registerOnUpdateListener(e),()=>y.removeOnUpdateListener(e)},[y]);let I=y?y.shouldShowError:!x||!!b,L=y?y.getCurrentErrorMessage():b||``,R=f!==void 0&&!y,z=y?y.value:R?f:A;function B(e,t){y?y.set(e):R||j(e),m(e,t)}function V(e){e.currentTarget.contains(e.relatedTarget)||(N(!1),O.current&&(O.current.touched=!0))}let H=d.findIndex(e=>e.value===z),U=d.findIndex(e=>!e.disabled),W=H>=0?H:U;function G(e){D.current[e]?.focus()}function K(e,t,n){if(g||d.length===0)return;let r=t;for(let t=0;t<d.length;t++)if(r=(r+e+d.length)%d.length,!d[r].disabled){G(r),B(d[r].value,n);return}}function q(e,t){let n=d[t];switch(e.key){case`ArrowDown`:case`ArrowRight`:e.preventDefault(),K(1,t,e);break;case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),K(-1,t,e);break;case` `:case`Enter`:e.preventDefault(),!g&&!n.disabled&&B(n.value,e)}}return l(`div`,{...E,role:`radiogroup`,"aria-orientation":h,"aria-disabled":g||void 0,"aria-invalid":I||void 0,className:s(r.radioGroup,h===n.Horizontal&&r.radioGroupHorizontal,g&&r.radioGroupDisabled,I&&r.radioGroupError,S,w?.root),style:{..._?{"--radio-size":_}:null,...C,...T?.root},onFocus:()=>N(!0),onBlur:V,onMouseEnter:()=>F(!0),onMouseLeave:()=>F(!1),children:[d.map((e,t)=>{let n=e.value===z,i=g||!!e.disabled;return l(`div`,{ref:e=>{D.current[t]=e},role:`radio`,"aria-checked":n,"aria-disabled":i||void 0,tabIndex:i?-1:t===W?v:-1,className:s(r.radioGroupOption,n&&r.radioGroupOptionChecked,i&&r.radioGroupOptionDisabled,w?.option),style:T?.option,onClick:()=>{i||B(e.value)},onKeyDown:e=>q(e,t),children:[c(`div`,{className:s(r.radioGroupRadio,n&&r.radioGroupRadioChecked,w?.radio),style:T?.radio,children:c(`div`,{className:s(r.radioGroupDot,n&&r.radioGroupDotChecked,w?.dot),style:T?.dot})}),(e.label??e.value)!==``&&l(`div`,{className:r.radioGroupText,children:[c(`div`,{className:s(r.radioGroupLabel,w?.label),style:T?.label,children:e.label??e.value}),e.description!=null&&c(`div`,{className:s(r.radioGroupDescription,w?.description),style:T?.description,children:e.description})]})]},String(e.value))}),c(t,{variant:`error`,message:L,isVisible:I&&(M||P)})]})}export{u as default};
2
2
  //# sourceMappingURL=radioGroup.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"radioGroup.mjs","names":[],"sources":["../../../../package/common/radioGroup/radioGroup.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport Tooltip from 'components/common/tooltip';\n\nimport { useComponentDefaults } from '../configProvider';\nimport { RadioGroupOrientation, type RadioGroupProps, type RadioValue } from './radioGroup.types';\nimport styles from './radioGroup.module.pcss';\n\nfunction RadioGroup(props: RadioGroupProps): ReactElement {\n const {\n options = [],\n value,\n defaultValue,\n onChange = () => {},\n orientation = RadioGroupOrientation.Vertical,\n disabled = false,\n size,\n tabIndex = 0,\n formValidator = null,\n errorMessage = '',\n isValid = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('RadioGroup', props);\n\n const optionRefs = useRef<(HTMLDivElement | null)[]>([]);\n const fvRef = useRef(formValidator);\n useEffect(() => {\n fvRef.current = formValidator;\n });\n const [, forceUpdate] = useState(0);\n const [internalValue, setInternalValue] = useState<RadioValue | undefined>(defaultValue);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n\n useEffect(() => {\n if (!formValidator) return;\n formValidator.validate();\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => formValidator.removeOnUpdateListener(listener);\n }, [formValidator]);\n\n const hasError = formValidator ? formValidator.touched && formValidator.hasError() : !isValid || !!errorMessage;\n const errorText = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n const isControlled = value !== undefined && !formValidator;\n const currentValue = (formValidator ? formValidator.value : isControlled ? value : internalValue) as\n | RadioValue\n | undefined;\n\n function select(next: RadioValue, event?: React.MouseEvent | React.KeyboardEvent) {\n if (formValidator) formValidator.set(next);\n else if (!isControlled) setInternalValue(next);\n onChange(next, event);\n }\n\n function handleBlur(event: React.FocusEvent<HTMLDivElement>) {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n if (fvRef.current) fvRef.current.touched = true;\n }\n\n const selectedIndex = options.findIndex((o) => o.value === currentValue);\n const firstEnabledIndex = options.findIndex((o) => !o.disabled);\n const rovingIndex = selectedIndex >= 0 ? selectedIndex : firstEnabledIndex;\n\n function focusOption(index: number) {\n optionRefs.current[index]?.focus();\n }\n\n function moveTo(step: 1 | -1, fromIndex: number, event: React.KeyboardEvent) {\n if (disabled || options.length === 0) return;\n let i = fromIndex;\n for (let count = 0; count < options.length; count++) {\n i = (i + step + options.length) % options.length;\n if (!options[i].disabled) {\n focusOption(i);\n select(options[i].value, event);\n return;\n }\n }\n }\n\n function handleKeyDown(event: React.KeyboardEvent, index: number) {\n const option = options[index];\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n event.preventDefault();\n moveTo(1, index, event);\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n event.preventDefault();\n moveTo(-1, index, event);\n break;\n case ' ':\n case 'Enter':\n event.preventDefault();\n if (!disabled && !option.disabled) select(option.value, event);\n break;\n default:\n break;\n }\n }\n\n return (\n <div\n {...rest}\n role='radiogroup'\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-invalid={hasError || undefined}\n className={cx(\n styles.radioGroup,\n orientation === RadioGroupOrientation.Horizontal && styles.radioGroupHorizontal,\n disabled && styles.radioGroupDisabled,\n hasError && styles.radioGroupError,\n className,\n classNames?.root,\n )}\n style={{ ...(size ? { '--radio-size': size } : null), ...style, ...slotStyles?.root } as React.CSSProperties}\n onFocus={() => setIsFocused(true)}\n onBlur={handleBlur}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {options.map((option, index) => {\n const checked = option.value === currentValue;\n const optionDisabled = disabled || !!option.disabled;\n return (\n <div\n key={String(option.value)}\n ref={(el) => {\n optionRefs.current[index] = el;\n }}\n role='radio'\n aria-checked={checked}\n aria-disabled={optionDisabled || undefined}\n tabIndex={optionDisabled ? -1 : index === rovingIndex ? tabIndex : -1}\n className={cx(\n styles.radioGroupOption,\n checked && styles.radioGroupOptionChecked,\n optionDisabled && styles.radioGroupOptionDisabled,\n classNames?.option,\n )}\n style={slotStyles?.option}\n onClick={() => {\n if (!optionDisabled) select(option.value);\n }}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n <div\n className={cx(styles.radioGroupRadio, checked && styles.radioGroupRadioChecked, classNames?.radio)}\n style={slotStyles?.radio}\n >\n <div\n className={cx(styles.radioGroupDot, checked && styles.radioGroupDotChecked, classNames?.dot)}\n style={slotStyles?.dot}\n />\n </div>\n {(option.label ?? option.value) !== '' && (\n <div className={styles.radioGroupText}>\n <div className={cx(styles.radioGroupLabel, classNames?.label)} style={slotStyles?.label}>\n {option.label ?? option.value}\n </div>\n {option.description != null && (\n <div\n className={cx(styles.radioGroupDescription, classNames?.description)}\n style={slotStyles?.description}\n >\n {option.description}\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n <Tooltip variant='error' message={errorText} isVisible={hasError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default RadioGroup;\n"],"mappings":"mXAQA,SAAS,EAAW,EAAsC,CACxD,GAAM,CACJ,UAAU,CAAC,EACX,QACA,eACA,eAAiB,CAAC,EAClB,cAAc,EAAsB,SACpC,WAAW,GACX,OACA,WAAW,EACX,gBAAgB,KAChB,eAAe,GACf,UAAU,GACV,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,aAAc,CAAK,EAEtC,EAAa,EAAkC,CAAC,CAAC,EACjD,EAAQ,EAAO,CAAa,EAClC,MAAgB,CACd,EAAM,QAAU,CAClB,CAAC,EACD,GAAM,EAAG,GAAe,EAAS,CAAC,EAC5B,CAAC,EAAe,GAAoB,EAAiC,CAAY,EACjF,CAAC,EAAW,GAAgB,EAAS,EAAK,EAC1C,CAAC,EAAW,GAAgB,EAAS,EAAK,EAEhD,MAAgB,CACd,GAAI,CAAC,EAAe,OACpB,EAAc,SAAS,EACvB,IAAM,MAAiB,EAAa,GAAM,EAAI,CAAC,EAE/C,OADA,EAAc,yBAAyB,CAAQ,MAClC,EAAc,uBAAuB,CAAQ,CAC5D,EAAG,CAAC,CAAa,CAAC,EAElB,IAAM,EAAW,EAAgB,EAAc,SAAW,EAAc,SAAS,EAAI,CAAC,GAAW,CAAC,CAAC,EAC7F,EAAY,EAAgB,EAAc,uBAAuB,EAAI,GAAgB,GAErF,EAAe,IAAU,IAAA,IAAa,CAAC,EACvC,EAAgB,EAAgB,EAAc,MAAQ,EAAe,EAAQ,EAInF,SAAS,EAAO,EAAkB,EAAgD,CAC5E,EAAe,EAAc,IAAI,CAAI,EAC/B,GAAc,EAAiB,CAAI,EAC7C,EAAS,EAAM,CAAK,CACtB,CAEA,SAAS,EAAW,EAAyC,CACvD,EAAM,cAAc,SAAS,EAAM,aAA4B,IACnE,EAAa,EAAK,EACd,EAAM,UAAS,EAAM,QAAQ,QAAU,IAC7C,CAEA,IAAM,EAAgB,EAAQ,UAAW,GAAM,EAAE,QAAU,CAAY,EACjE,EAAoB,EAAQ,UAAW,GAAM,CAAC,EAAE,QAAQ,EACxD,EAAc,GAAiB,EAAI,EAAgB,EAEzD,SAAS,EAAY,EAAe,CAClC,EAAW,QAAQ,EAAM,EAAE,MAAM,CACnC,CAEA,SAAS,EAAO,EAAc,EAAmB,EAA4B,CAC3E,GAAI,GAAY,EAAQ,SAAW,EAAG,OACtC,IAAI,EAAI,EACR,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,OAAQ,IAE1C,GADA,GAAK,EAAI,EAAO,EAAQ,QAAU,EAAQ,OACtC,CAAC,EAAQ,EAAE,CAAC,SAAU,CACxB,EAAY,CAAC,EACb,EAAO,EAAQ,EAAE,CAAC,MAAO,CAAK,EAC9B,MACF,CAEJ,CAEA,SAAS,EAAc,EAA4B,EAAe,CAChE,IAAM,EAAS,EAAQ,GACvB,OAAQ,EAAM,IAAd,CACE,IAAK,YACL,IAAK,aACH,EAAM,eAAe,EACrB,EAAO,EAAG,EAAO,CAAK,EACtB,MACF,IAAK,UACL,IAAK,YACH,EAAM,eAAe,EACrB,EAAO,GAAI,EAAO,CAAK,EACvB,MACF,IAAK,IACL,IAAK,QACH,EAAM,eAAe,EACjB,CAAC,GAAY,CAAC,EAAO,UAAU,EAAO,EAAO,MAAO,CAAK,CAIjE,CACF,CAEA,OACE,EAAC,MAAD,CACE,GAAI,EACJ,KAAK,aACL,mBAAkB,EAClB,gBAAe,GAAY,IAAA,GAC3B,eAAc,GAAY,IAAA,GAC1B,UAAW,EACT,EAAO,WACP,IAAgB,EAAsB,YAAc,EAAO,qBAC3D,GAAY,EAAO,mBACnB,GAAY,EAAO,gBACnB,EACA,GAAY,IACd,EACA,MAAO,CAAE,GAAI,EAAO,CAAE,eAAgB,CAAK,EAAI,KAAO,GAAG,EAAO,GAAG,GAAY,IAAK,EACpF,YAAe,EAAa,EAAI,EAChC,OAAQ,EACR,iBAAoB,EAAa,EAAI,EACrC,iBAAoB,EAAa,EAAK,EAlBxC,SAAA,CAoBG,EAAQ,KAAK,EAAQ,IAAU,CAC9B,IAAM,EAAU,EAAO,QAAU,EAC3B,EAAiB,GAAY,CAAC,CAAC,EAAO,SAC5C,OACE,EAAC,MAAD,CAEE,IAAM,GAAO,CACX,EAAW,QAAQ,GAAS,CAC9B,EACA,KAAK,QACL,eAAc,EACd,gBAAe,GAAkB,IAAA,GACjC,SAAU,EAAiB,GAAK,IAAU,EAAc,EAAW,GACnE,UAAW,EACT,EAAO,iBACP,GAAW,EAAO,wBAClB,GAAkB,EAAO,yBACzB,GAAY,MACd,EACA,MAAO,GAAY,OACnB,YAAe,CACR,GAAgB,EAAO,EAAO,KAAK,CAC1C,EACA,UAAY,GAAM,EAAc,EAAG,CAAK,EAnB1C,SAAA,CAqBE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,gBAAiB,GAAW,EAAO,uBAAwB,GAAY,KAAK,EACjG,MAAO,GAAY,MAEnB,SAAA,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,cAAe,GAAW,EAAO,qBAAsB,GAAY,GAAG,EAC3F,MAAO,GAAY,GACpB,CAAA,CACE,CAAA,GACH,EAAO,OAAS,EAAO,SAAW,IAClC,EAAC,MAAD,CAAK,UAAW,EAAO,eAAvB,SAAA,CACE,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,gBAAiB,GAAY,KAAK,EAAG,MAAO,GAAY,MAC/E,SAAA,EAAO,OAAS,EAAO,KACrB,CAAA,EACJ,EAAO,aAAe,MACrB,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,sBAAuB,GAAY,WAAW,EACnE,MAAO,GAAY,YAElB,SAAA,EAAO,WACL,CAAA,CAEJ,CAEJ,CAAA,CAAA,CA5CE,EAAA,OAAO,EAAO,KAAK,CA4CrB,CAET,CAAC,EACD,EAAC,EAAD,CAAS,QAAQ,QAAQ,QAAS,EAAW,UAAW,IAAa,GAAa,EAAa,CAAA,CAC5F,GAET"}
1
+ {"version":3,"file":"radioGroup.mjs","names":[],"sources":["../../../../package/common/radioGroup/radioGroup.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport Tooltip from 'components/common/tooltip';\n\nimport { useComponentDefaults } from '../configProvider';\nimport { RadioGroupOrientation, type RadioGroupProps, type RadioValue } from './radioGroup.types';\nimport styles from './radioGroup.module.pcss';\n\nfunction RadioGroup(props: RadioGroupProps): ReactElement {\n const {\n options = [],\n value,\n defaultValue,\n onChange = () => {},\n orientation = RadioGroupOrientation.Vertical,\n disabled = false,\n size,\n tabIndex = 0,\n formValidator = null,\n errorMessage = '',\n isValid = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('RadioGroup', props);\n\n const optionRefs = useRef<(HTMLDivElement | null)[]>([]);\n const fvRef = useRef(formValidator);\n useEffect(() => {\n fvRef.current = formValidator;\n });\n const [, forceUpdate] = useState(0);\n const [internalValue, setInternalValue] = useState<RadioValue | undefined>(defaultValue);\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n\n useEffect(() => {\n if (!formValidator) return;\n formValidator.validate();\n const listener = () => forceUpdate((n) => n + 1);\n formValidator.registerOnUpdateListener(listener);\n return () => formValidator.removeOnUpdateListener(listener);\n }, [formValidator]);\n\n const hasError = formValidator ? formValidator.shouldShowError : !isValid || !!errorMessage;\n const errorText = formValidator ? formValidator.getCurrentErrorMessage() : errorMessage || '';\n\n const isControlled = value !== undefined && !formValidator;\n const currentValue = (formValidator ? formValidator.value : isControlled ? value : internalValue) as\n | RadioValue\n | undefined;\n\n function select(next: RadioValue, event?: React.MouseEvent | React.KeyboardEvent) {\n if (formValidator) formValidator.set(next);\n else if (!isControlled) setInternalValue(next);\n onChange(next, event);\n }\n\n function handleBlur(event: React.FocusEvent<HTMLDivElement>) {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n setIsFocused(false);\n if (fvRef.current) fvRef.current.touched = true;\n }\n\n const selectedIndex = options.findIndex((o) => o.value === currentValue);\n const firstEnabledIndex = options.findIndex((o) => !o.disabled);\n const rovingIndex = selectedIndex >= 0 ? selectedIndex : firstEnabledIndex;\n\n function focusOption(index: number) {\n optionRefs.current[index]?.focus();\n }\n\n function moveTo(step: 1 | -1, fromIndex: number, event: React.KeyboardEvent) {\n if (disabled || options.length === 0) return;\n let i = fromIndex;\n for (let count = 0; count < options.length; count++) {\n i = (i + step + options.length) % options.length;\n if (!options[i].disabled) {\n focusOption(i);\n select(options[i].value, event);\n return;\n }\n }\n }\n\n function handleKeyDown(event: React.KeyboardEvent, index: number) {\n const option = options[index];\n switch (event.key) {\n case 'ArrowDown':\n case 'ArrowRight':\n event.preventDefault();\n moveTo(1, index, event);\n break;\n case 'ArrowUp':\n case 'ArrowLeft':\n event.preventDefault();\n moveTo(-1, index, event);\n break;\n case ' ':\n case 'Enter':\n event.preventDefault();\n if (!disabled && !option.disabled) select(option.value, event);\n break;\n default:\n break;\n }\n }\n\n return (\n <div\n {...rest}\n role='radiogroup'\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-invalid={hasError || undefined}\n className={cx(\n styles.radioGroup,\n orientation === RadioGroupOrientation.Horizontal && styles.radioGroupHorizontal,\n disabled && styles.radioGroupDisabled,\n hasError && styles.radioGroupError,\n className,\n classNames?.root,\n )}\n style={{ ...(size ? { '--radio-size': size } : null), ...style, ...slotStyles?.root } as React.CSSProperties}\n onFocus={() => setIsFocused(true)}\n onBlur={handleBlur}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {options.map((option, index) => {\n const checked = option.value === currentValue;\n const optionDisabled = disabled || !!option.disabled;\n return (\n <div\n key={String(option.value)}\n ref={(el) => {\n optionRefs.current[index] = el;\n }}\n role='radio'\n aria-checked={checked}\n aria-disabled={optionDisabled || undefined}\n tabIndex={optionDisabled ? -1 : index === rovingIndex ? tabIndex : -1}\n className={cx(\n styles.radioGroupOption,\n checked && styles.radioGroupOptionChecked,\n optionDisabled && styles.radioGroupOptionDisabled,\n classNames?.option,\n )}\n style={slotStyles?.option}\n onClick={() => {\n if (!optionDisabled) select(option.value);\n }}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n <div\n className={cx(styles.radioGroupRadio, checked && styles.radioGroupRadioChecked, classNames?.radio)}\n style={slotStyles?.radio}\n >\n <div\n className={cx(styles.radioGroupDot, checked && styles.radioGroupDotChecked, classNames?.dot)}\n style={slotStyles?.dot}\n />\n </div>\n {(option.label ?? option.value) !== '' && (\n <div className={styles.radioGroupText}>\n <div className={cx(styles.radioGroupLabel, classNames?.label)} style={slotStyles?.label}>\n {option.label ?? option.value}\n </div>\n {option.description != null && (\n <div\n className={cx(styles.radioGroupDescription, classNames?.description)}\n style={slotStyles?.description}\n >\n {option.description}\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n <Tooltip variant='error' message={errorText} isVisible={hasError && (isFocused || isHovered)} />\n </div>\n );\n}\n\nexport default RadioGroup;\n"],"mappings":"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"}