@allxsmith/bestax-bulma 5.34.1 → 5.34.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/Dropdown.js +8 -3
- package/dist/esm/components/Dropdown.js.map +1 -1
- package/dist/esm/components/Loading.js +5 -4
- package/dist/esm/components/Loading.js.map +1 -1
- package/dist/esm/components/Modal.js +11 -4
- package/dist/esm/components/Modal.js.map +1 -1
- package/dist/esm/components/Sidebar.js +5 -4
- package/dist/esm/components/Sidebar.js.map +1 -1
- package/dist/esm/components/Toast.js +7 -5
- package/dist/esm/components/Toast.js.map +1 -1
- package/dist/esm/form/Autocomplete.js +60 -10
- package/dist/esm/form/Autocomplete.js.map +1 -1
- package/dist/esm/form/Taginput.js +4 -0
- package/dist/esm/form/Taginput.js.map +1 -1
- package/dist/esm/form/_pickerInternals/PickerPopover.js +11 -3
- package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
- package/dist/index.cjs +110 -33
- package/dist/index.cjs.map +1 -1
- package/dist/types/components/Loading.d.ts +5 -1
- package/dist/types/components/Modal.d.ts +4 -1
- package/dist/types/components/Sidebar.d.ts +5 -1
- package/dist/types/components/Toast.d.ts +4 -1
- package/dist/types/form/Autocomplete.d.ts +19 -3
- package/dist/types-cjs/components/Loading.d.ts +5 -1
- package/dist/types-cjs/components/Modal.d.ts +4 -1
- package/dist/types-cjs/components/Sidebar.d.ts +5 -1
- package/dist/types-cjs/components/Toast.d.ts +4 -1
- package/dist/types-cjs/form/Autocomplete.d.ts +19 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Taginput.js","sources":["../../../src/form/Taginput.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useState,\n useRef,\n useEffect,\n useCallback,\n useId,\n} from 'react';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { useConfig, useIconLibrary } from '../helpers/Config';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { isEventInside } from '../helpers/shadowDom';\nimport {\n useInsideField,\n useInsideControl,\n rendersOwnField,\n} from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\nimport { Icon } from '../elements/Icon';\n\n/**\n * Represents a tag item object with a value and optional label.\n */\nexport interface TaginputItem {\n /** The tag value. */\n value: string;\n /** Display label (optional). */\n label?: string;\n [key: string]: unknown;\n}\n\n/** A tag can be either a TaginputItem object or a plain string. */\nexport type TaginputTag = TaginputItem | string;\n\ntype IconLibrary = 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n\n/**\n * Props for the Taginput component.\n * Inside an outer `Field`, or a `Control` with no `Field` around it, it\n * renders no `Field` of its own. The exception is `label`, `message`,\n * `horizontal` or `fieldClassName` in that bare `Control`: it keeps a `Field`\n * for them, nested in the `.control`, and warns in development. Wrap the\n * `Control` in a `Field` instead, and set the `label`, `horizontal` and class\n * name on that `Field`.\n * @extraProp {string} [className] - Additional CSS classes.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the input element.\n */\nexport interface TaginputProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label. Automatically associated with the text input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired when the tag limit is reached (the input is not rendered). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here; it then replaces the `aria-label=\"Add tag\"` fallback too. With `data`, the label naming the input also names the open suggestion list, through `aria-labelledby`, when it has an id: this label always does, and an outer `Field`'s does unless you wired it by hand without an `id` in its `labelProps`. Otherwise the list is named \"Suggestions\". */\n label?: React.ReactNode;\n /** Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then). With `data`, while the label names the input, the suggestion list's `aria-labelledby` points at it, through an `id` here when you give one and a generated one otherwise. */\n labelProps?: React.LabelHTMLAttributes<HTMLLabelElement> & {\n [key: string]: unknown;\n };\n /** Applied to the inner text input (the labellable control), not the wrapper div. */\n id?: string;\n /** The current tags (controlled). */\n value?: TaginputTag[];\n /** Default tags (uncontrolled). */\n defaultValue?: TaginputTag[];\n /**\n * Autocomplete suggestions. With `data` set, even to an empty array, the\n * text input is a `role=\"combobox\"` that controls the suggestion list and\n * points `aria-activedescendant` at the highlighted suggestion. Without it\n * there's no list, so the input stays a plain text field.\n */\n data?: string[];\n /** Placeholder text when no tags. */\n placeholder?: string;\n /** Object property to use as display field. */\n field?: string;\n /** Allow creating new tags not in suggestions. */\n allowNew?: boolean;\n /** Allow duplicate tags. */\n allowDuplicates?: boolean;\n /** Open autocomplete dropdown on focus. */\n openOnFocus?: boolean;\n /** Remove tag on backspace. Default: true. */\n removeOnKeys?: boolean;\n /** Keys to confirm tag creation. Default: ['Enter', ',']. */\n confirmKeys?: string[];\n /** Show close button on tags. Default: true. */\n closable?: boolean;\n /** Attach tags visually. */\n attached?: boolean;\n /** Maximum number of tags allowed. */\n maxTags?: number;\n /** Maximum length of input. */\n maxlength?: number;\n /** Whether the input is disabled. */\n disabled?: boolean;\n /** Whether the input is read-only. */\n readonly?: boolean;\n /** Applies rounded corners to the tags. */\n rounded?: boolean;\n /** Truncates long tag text with ellipsis. */\n ellipsis?: boolean;\n /** Shows a counter of the number of tags. */\n hasCounter?: boolean;\n /** Characters that split pasted text into tags. */\n onPasteSeparators?: string[];\n /** Validation function called before adding a tag. */\n beforeAdding?: (tag: string) => boolean;\n /** Custom function for creating tag objects from input. */\n createTag?: (input: string) => TaginputTag;\n /** Keeps the first autocomplete suggestion highlighted. */\n keepFirst?: boolean;\n /** Keeps the autocomplete dropdown open after selection. */\n keepOpen?: boolean;\n /**\n * Shows a loading spinner in the input.\n * Under `prefers-reduced-motion: reduce` the spinner stops and stays\n * drawn (with bestax's CSS loaded).\n */\n loading?: boolean;\n /** ARIA label for tag close buttons. */\n ariaCloseLabel?: string;\n /** Icon name for the input field. */\n icon?: string;\n /** Icon library to use. */\n iconLibrary?: IconLibrary;\n /** Icon style variant (e.g., 'solid', 'outlined'). */\n iconVariant?: string;\n /** Additional icon modifiers. */\n iconFeatures?: string | string[];\n /** Input color variant. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Tag color variant. */\n tagColor?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'dark'\n | 'light';\n /** Size variant. */\n size?: 'small' | 'medium' | 'large';\n /** Form field name. When set, one hidden input per tag is rendered. */\n name?: string;\n /** Optional id of the form the hidden inputs belong to. */\n form?: string;\n /** Callback when tags change. */\n onChange?: (tags: TaginputTag[]) => void;\n /** Callback when tag is added. */\n onAdd?: (tag: TaginputTag) => void;\n /** Callback when tag is removed. */\n onRemove?: (tag: TaginputTag, index: number) => void;\n /** Callback when typing in input. */\n onTyping?: (value: string) => void;\n /** Custom render for tags. */\n tagTemplate?: (tag: TaginputTag) => React.ReactNode;\n}\n\n/**\n * The `Taginput` component provides a tag/chip input field for managing multiple tags.\n *\n * @function\n * @param {TaginputProps} props - Props for the Taginput component.\n * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the input element.\n * @returns {JSX.Element} The rendered taginput component.\n *\n * @example\n * // Basic usage\n * <Taginput\n * placeholder=\"Add a tag...\"\n * onChange={(tags) => console.log(tags)}\n * />\n *\n * @example\n * // With autocomplete\n * <Taginput\n * data={['React', 'Vue', 'Angular', 'Svelte']}\n * allowNew={false}\n * placeholder=\"Select frameworks...\"\n * />\n */\nexport const Taginput = forwardRef<HTMLInputElement, TaginputProps>(\n (\n {\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n value: controlledValue,\n defaultValue = [],\n data: dataProp,\n placeholder,\n field = 'label',\n allowNew = true,\n allowDuplicates = false,\n openOnFocus = false,\n removeOnKeys = true,\n confirmKeys = ['Enter', ','],\n closable = true,\n attached = false,\n maxTags,\n maxlength,\n disabled = false,\n readonly = false,\n rounded = false,\n ellipsis = false,\n hasCounter = true,\n onPasteSeparators = [','],\n beforeAdding,\n createTag,\n keepFirst = false,\n keepOpen = true,\n loading = false,\n ariaCloseLabel,\n icon,\n iconLibrary: iconLibraryProp,\n iconVariant,\n iconFeatures,\n color,\n tagColor,\n size,\n onChange,\n onAdd,\n onRemove,\n onTyping,\n tagTemplate,\n name,\n form,\n id,\n className,\n ...props\n },\n ref\n ) => {\n const insideField = useInsideField();\n const insideControl = useInsideControl();\n const ownField = rendersOwnField('Taginput', {\n insideField,\n insideControl,\n label,\n message,\n horizontal,\n fieldClassName,\n });\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const { classPrefix } = useConfig();\n const defaultIconLibrary = useIconLibrary();\n const resolvedLibrary = iconLibraryProp || defaultIconLibrary || 'fa';\n const pcn = (\n ...args: (\n | string\n | number\n | undefined\n | null\n | false\n | Record<string, unknown>\n | unknown[]\n )[]\n ) => prefixedClassNames(classPrefix, ...args);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const [internalTags, setInternalTags] =\n useState<TaginputTag[]>(defaultValue);\n const [inputValue, setInputValue] = useState('');\n const [isActive, setIsActive] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const listboxId = `${useId()}-listbox`;\n\n // With `data` the input offers suggestions, so it's a combobox, even\n // while none match. Without it there's never a list, so it stays a plain\n // text field.\n const hasSuggestions = dataProp !== undefined;\n const data = dataProp ?? [];\n\n // Use controlled or internal tags\n const tags = controlledValue !== undefined ? controlledValue : internalTags;\n\n const isMaxReached = maxTags !== undefined && tags.length >= maxTags;\n // At the tag limit the text input is not rendered, so there is nothing to\n // wire a label to, its own or a surrounding Field's. The suggestion list\n // points `aria-labelledby` at the label that names the input, so with\n // suggestions it needs that label's id.\n const {\n controlId,\n fieldLabelProps,\n labelled,\n labelId: listLabelId,\n } = useAutoLabelId({\n label,\n id,\n labelProps,\n rendersLabel: ownField && !isMaxReached,\n hasInput: !isMaxReached,\n needsLabelId: hasSuggestions,\n });\n\n // Get display value from tag\n const getDisplayValue = (tag: TaginputTag): string => {\n if (typeof tag === 'string') return tag;\n const fieldValue = tag[field];\n if (typeof fieldValue === 'string') return fieldValue;\n return tag.value || '';\n };\n\n // Filter autocomplete data\n const filteredData = data.filter(item => {\n const displayValue = item.toLowerCase();\n const matchesInput = displayValue.includes(inputValue.toLowerCase());\n const notAlreadyAdded =\n allowDuplicates ||\n !tags.some(tag => getDisplayValue(tag).toLowerCase() === displayValue);\n const hasInput = inputValue || (openOnFocus && isActive);\n return matchesInput && notAlreadyAdded && hasInput;\n });\n const listOpen = isActive && filteredData.length > 0;\n\n // keepFirst: auto-highlight first item when dropdown opens\n useEffect(() => {\n if (keepFirst && isActive && filteredData.length > 0) {\n setHighlightedIndex(0);\n }\n }, [keepFirst, isActive, filteredData.length]);\n\n // Update tags\n const updateTags = useCallback(\n (newTags: TaginputTag[]) => {\n if (controlledValue === undefined) {\n setInternalTags(newTags);\n }\n onChange?.(newTags);\n },\n [controlledValue, onChange]\n );\n\n // Add tag\n const addTag = useCallback(\n (value: string | TaginputTag) => {\n if (disabled || readonly) return;\n\n const tagValue = typeof value === 'string' ? value.trim() : value;\n const displayValue =\n typeof tagValue === 'string' ? tagValue : getDisplayValue(tagValue);\n\n if (!displayValue) return;\n\n // Check max tags\n if (maxTags && tags.length >= maxTags) return;\n\n // Check duplicates\n if (!allowDuplicates) {\n const exists = tags.some(\n tag =>\n getDisplayValue(tag).toLowerCase() === displayValue.toLowerCase()\n );\n if (exists) return;\n }\n\n // Check if allowed (must be in data if allowNew is false). Compare\n // case-insensitively, consistent with the dropdown filter and the\n // duplicate check above.\n if (\n !allowNew &&\n !data.some(d => d.toLowerCase() === displayValue.toLowerCase())\n )\n return;\n\n // beforeAdding validation\n if (beforeAdding && !beforeAdding(displayValue)) return;\n\n // createTag transformation\n const finalTag = createTag ? createTag(displayValue) : tagValue;\n\n const newTags = [...tags, finalTag];\n updateTags(newTags);\n onAdd?.(finalTag);\n setInputValue('');\n if (!keepOpen) {\n setIsActive(false);\n }\n setHighlightedIndex(-1);\n },\n [\n disabled,\n readonly,\n tags,\n maxTags,\n allowDuplicates,\n allowNew,\n data,\n field,\n updateTags,\n onAdd,\n beforeAdding,\n createTag,\n keepOpen,\n ]\n );\n\n // Remove tag\n const removeTag = useCallback(\n (index: number) => {\n /* istanbul ignore next: both triggers (delete button click, Backspace handler) are already gated on disabled/readonly */\n if (disabled || readonly) return;\n\n const removedTag = tags[index];\n const newTags = tags.filter((_, i) => i !== index);\n updateTags(newTags);\n onRemove?.(removedTag, index);\n },\n [disabled, readonly, tags, updateTags, onRemove]\n );\n\n // Handle input change\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n\n // Check if a confirm key was typed\n const lastChar = newValue.slice(-1);\n if (confirmKeys.includes(lastChar) && lastChar !== 'Enter') {\n const valueWithoutKey = newValue.slice(0, -1).trim();\n if (valueWithoutKey) {\n addTag(valueWithoutKey);\n }\n return;\n }\n\n setInputValue(newValue);\n onTyping?.(newValue);\n\n if (newValue && data.length > 0) {\n setIsActive(true);\n }\n },\n [confirmKeys, addTag, onTyping, data.length]\n );\n\n // Handle paste\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLInputElement>) => {\n if (onPasteSeparators.length === 0) return;\n\n const pastedText = e.clipboardData.getData('text');\n // Check if any separator exists in the pasted text\n const hasSeparator = onPasteSeparators.some(sep =>\n pastedText.includes(sep)\n );\n\n if (!hasSeparator) return;\n\n e.preventDefault();\n\n // Build a regex from all separators\n const escapedSeparators = onPasteSeparators.map(s =>\n s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n );\n const regex = new RegExp(`[${escapedSeparators.join('')}]`);\n\n const parts = pastedText\n .split(regex)\n .map(s => s.trim())\n .filter(Boolean);\n\n for (const part of parts) {\n addTag(part);\n }\n },\n [onPasteSeparators, addTag]\n );\n\n // Handle focus\n const handleFocus = useCallback(() => {\n if (openOnFocus && data.length > 0 && !disabled) {\n setIsActive(true);\n }\n }, [openOnFocus, data.length, disabled]);\n\n // Handle click outside\n useEffect(() => {\n if (!isActive) return undefined;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (containerRef.current && !isEventInside(e, containerRef.current)) {\n setIsActive(false);\n setHighlightedIndex(-1);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () =>\n document.removeEventListener('mousedown', handleClickOutside);\n }, [isActive]);\n\n // Handle keyboard\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled || readonly) return;\n\n // Handle confirm keys\n if (confirmKeys.includes(e.key)) {\n e.preventDefault();\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n addTag(filteredData[highlightedIndex]);\n } else if (inputValue.trim()) {\n addTag(inputValue);\n }\n return;\n }\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!isActive && data.length > 0) {\n setIsActive(true);\n } else {\n setHighlightedIndex(prev =>\n prev < filteredData.length - 1 ? prev + 1 : prev\n );\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n setHighlightedIndex(prev => (prev > 0 ? prev - 1 : prev));\n break;\n case 'Backspace':\n if (removeOnKeys && !inputValue && tags.length > 0) {\n e.preventDefault();\n removeTag(tags.length - 1);\n }\n break;\n case 'Escape':\n setIsActive(false);\n setHighlightedIndex(-1);\n break;\n }\n },\n [\n disabled,\n readonly,\n confirmKeys,\n highlightedIndex,\n filteredData,\n addTag,\n inputValue,\n isActive,\n data.length,\n removeOnKeys,\n tags.length,\n removeTag,\n ]\n );\n\n // Combined ref\n const combinedRef = useCallback(\n (node: HTMLInputElement | null) => {\n (inputRef as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n }\n },\n [ref]\n );\n\n // Focus input when container is clicked\n const handleContainerClick = useCallback(() => {\n inputRef.current?.focus();\n }, []);\n\n // Generate classes\n const taginputClasses = pcn('taginput', {\n 'is-active': isActive,\n [`is-${size}`]: !!size,\n [`is-${color}`]: !!color,\n 'is-disabled': disabled,\n 'is-rounded': rounded,\n 'is-ellipsis': ellipsis,\n });\n\n const containerClasses = pcn('taginput-container', {\n 'is-attached': attached,\n 'has-icons-left': !!icon,\n });\n\n const inputClasses = pcn('input', {\n [`is-${size}`]: !!size,\n });\n\n const tagClasses = pcn('tag', {\n [`is-${tagColor}`]: !!tagColor,\n [`is-${size}`]: !!size,\n });\n\n const combinedClasses = classNames(\n taginputClasses,\n bulmaHelperClasses,\n className\n );\n\n // Counter text\n const showCounter =\n hasCounter && (maxTags !== undefined || maxlength !== undefined);\n const counterText =\n maxTags !== undefined\n ? `${tags.length} / ${maxTags}`\n : maxlength !== undefined\n ? `${inputValue.length} / ${maxlength}`\n : '';\n\n const counterClasses = pcn('help', 'counter');\n const tagsClasses = pcn('tags');\n const dropdownMenuClasses = pcn('dropdown-menu', 'is-active');\n const dropdownContentClasses = pcn('dropdown-content');\n const iconRightClass = pcn('icon', 'is-right');\n const loaderClass = pcn('loader', 'is-loading');\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n // The combobox controls its listbox and points at the highlighted option,\n // the way Autocomplete's does. The listbox takes its name from the label\n // that names the combobox, the one this renders or a surrounding Field's,\n // and keeps a fallback name when no label names it or the one that does\n // has no id to point at. While no suggestion matches there's no listbox,\n // so the combobox is collapsed and points at nothing. It skips a\n // highlighted suggestion that's blank once trimmed, since that one can't\n // become a tag, and the option it points at is the one marked selected.\n const optionId = (index: number) => `${listboxId}-${index}`;\n const activeOptionId =\n listOpen && filteredData[highlightedIndex]?.trim()\n ? optionId(highlightedIndex)\n : undefined;\n const comboboxProps: React.InputHTMLAttributes<HTMLInputElement> = {\n role: 'combobox',\n 'aria-expanded': listOpen,\n 'aria-controls': listOpen ? listboxId : undefined,\n 'aria-activedescendant': activeOptionId,\n 'aria-haspopup': 'listbox',\n 'aria-autocomplete': 'list',\n };\n\n const taginputElement = (\n <div\n ref={containerRef}\n className={combinedClasses}\n onClick={handleContainerClick}\n {...rest}\n >\n <div className={containerClasses}>\n {icon && (\n <Icon\n name={icon}\n library={resolvedLibrary}\n variant={iconVariant}\n features={iconFeatures}\n containerClassName={pcn('icon', 'is-left')}\n />\n )}\n <div className={tagsClasses}>\n {tags.map((tag, index) => {\n const displayVal = getDisplayValue(tag);\n return (\n <span key={index} className={tagClasses}>\n {tagTemplate ? (\n tagTemplate(tag)\n ) : ellipsis ? (\n <span title={displayVal}>{displayVal}</span>\n ) : (\n displayVal\n )}\n {closable && !disabled && !readonly && (\n <button\n type=\"button\"\n className={pcn('delete', 'is-small')}\n onClick={e => {\n e.stopPropagation();\n removeTag(index);\n }}\n aria-label={ariaCloseLabel || `Remove ${displayVal}`}\n />\n )}\n </span>\n );\n })}\n {!isMaxReached && (\n <input\n ref={combinedRef}\n type=\"text\"\n className={inputClasses}\n id={controlId}\n value={inputValue}\n placeholder={tags.length === 0 ? placeholder : undefined}\n disabled={disabled}\n readOnly={readonly}\n maxLength={maxlength}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n {...(hasSuggestions && comboboxProps)}\n // Fallback name, dropped only when a rendered label actually\n // targets this input, its own or a surrounding Field's\n // (aria-label would win otherwise). A label pointing elsewhere\n // keeps the fallback so the input is never nameless.\n aria-label={labelled ? undefined : 'Add tag'}\n />\n )}\n </div>\n {loading && (\n <span className={iconRightClass}>\n <span className={loaderClass} />\n </span>\n )}\n </div>\n\n {listOpen && (\n <div className={dropdownMenuClasses}>\n <div\n ref={dropdownRef}\n id={listboxId}\n className={dropdownContentClasses}\n role=\"listbox\"\n aria-labelledby={listLabelId}\n aria-label={listLabelId ? undefined : 'Suggestions'}\n >\n {filteredData.map((item, index) => (\n <a\n key={index}\n id={optionId(index)}\n className={pcn('dropdown-item', {\n 'is-active': index === highlightedIndex,\n })}\n onClick={() => addTag(item)}\n onMouseEnter={() => setHighlightedIndex(index)}\n role=\"option\"\n aria-selected={optionId(index) === activeOptionId}\n >\n {item}\n </a>\n ))}\n </div>\n </div>\n )}\n\n {showCounter && <small className={counterClasses}>{counterText}</small>}\n\n {name &&\n tags.map((tag, i) => (\n <input\n key={`tag-input-${i}`}\n type=\"hidden\"\n name={name}\n value={getDisplayValue(tag)}\n form={form}\n />\n ))}\n </div>\n );\n\n if (ownField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {taginputElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {taginputElement}\n {messageEl}\n </>\n );\n }\n);\n\nTaginput.displayName = 'Taginput';\n\n/** @deprecated Use `Taginput` — same component. */\nexport const TagInput = Taginput;\n\n/** @deprecated Use `TaginputProps`. */\nexport type TagInputProps = TaginputProps;\n\nexport default Taginput;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":";;;;;;;;;;;;AAqKA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI,MAAM,QAAQ,GAAG,UAAU,CAChC,CACE;AACE;AACA,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,OAAO,EACP,YAAY,EACZ,cAAc,EACd,KAAK,EAAE,eAAe,EACtB,YAAY,GAAG,EAAE,EACjB,IAAI,EAAE,QAAQ,EACd,WAAW,EACX,KAAK,GAAG,OAAO,EACf,QAAQ,GAAG,IAAI,EACf,eAAe,GAAG,KAAK,EACvB,WAAW,GAAG,KAAK,EACnB,YAAY,GAAG,IAAI,EACnB,WAAW,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAC5B,QAAQ,GAAG,IAAI,EACf,QAAQ,GAAG,KAAK,EAChB,OAAO,EACP,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,OAAO,GAAG,KAAK,EACf,QAAQ,GAAG,KAAK,EAChB,UAAU,GAAG,IAAI,EACjB,iBAAiB,GAAG,CAAC,GAAG,CAAC,EACzB,YAAY,EACZ,SAAS,EACT,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,IAAI,EACf,OAAO,GAAG,KAAK,EACf,cAAc,EACd,IAAI,EACJ,WAAW,EAAE,eAAe,EAC5B,WAAW,EACX,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,IAAI,EACJ,IAAI,EACJ,EAAE,EACF,SAAS,EACT,GAAG,KAAK,EACT,EACD,GAAG,KACD;AACF,IAAA,MAAM,WAAW,GAAG,cAAc,EAAE;AACpC,IAAA,MAAM,aAAa,GAAG,gBAAgB,EAAE;AACxC,IAAA,MAAM,QAAQ,GAAG,eAAe,CAAC,UAAU,EAAE;QAC3C,WAAW;QACX,aAAa;QACb,KAAK;QACL,OAAO;QACP,UAAU;QACV,cAAc;AACf,KAAA,CAAC;IACF,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC;AAC3D,IAAA,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE;AACnC,IAAA,MAAM,kBAAkB,GAAG,cAAc,EAAE;AAC3C,IAAA,MAAM,eAAe,GAAG,eAAe,IAAI,kBAAkB,IAAI,IAAI;AACrE,IAAA,MAAM,GAAG,GAAG,CACV,GAAG,IAQA,KACA,kBAAkB,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC;AAC7C,IAAA,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC;AACjD,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAmB,IAAI,CAAC;AAC/C,IAAA,MAAM,WAAW,GAAG,MAAM,CAAiB,IAAI,CAAC;IAEhD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GACnC,QAAQ,CAAgB,YAAY,CAAC;IACvC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;IAChD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC/C,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;AAC5D,IAAA,MAAM,SAAS,GAAG,CAAA,EAAG,KAAK,EAAE,UAAU;;;;AAKtC,IAAA,MAAM,cAAc,GAAG,QAAQ,KAAK,SAAS;AAC7C,IAAA,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE;;AAG3B,IAAA,MAAM,IAAI,GAAG,eAAe,KAAK,SAAS,GAAG,eAAe,GAAG,YAAY;IAE3E,MAAM,YAAY,GAAG,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO;;;;;AAKpE,IAAA,MAAM,EACJ,SAAS,EACT,eAAe,EACf,QAAQ,EACR,OAAO,EAAE,WAAW,GACrB,GAAG,cAAc,CAAC;QACjB,KAAK;QACL,EAAE;QACF,UAAU;AACV,QAAA,YAAY,EAAE,QAAQ,IAAI,CAAC,YAAY;QACvC,QAAQ,EAAE,CAAC,YAAY;AACvB,QAAA,YAAY,EAAE,cAAc;AAC7B,KAAA,CAAC;;AAGF,IAAA,MAAM,eAAe,GAAG,CAAC,GAAgB,KAAY;QACnD,IAAI,OAAO,GAAG,KAAK,QAAQ;AAAE,YAAA,OAAO,GAAG;AACvC,QAAA,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC;QAC7B,IAAI,OAAO,UAAU,KAAK,QAAQ;AAAE,YAAA,OAAO,UAAU;AACrD,QAAA,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;AACxB,IAAA,CAAC;;IAGD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAG;AACtC,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE;QACvC,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;QACpE,MAAM,eAAe,GACnB,eAAe;AACf,YAAA,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC;QACxE,MAAM,QAAQ,GAAG,UAAU,KAAK,WAAW,IAAI,QAAQ,CAAC;AACxD,QAAA,OAAO,YAAY,IAAI,eAAe,IAAI,QAAQ;AACpD,IAAA,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;;IAGpD,SAAS,CAAC,MAAK;QACb,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;YACpD,mBAAmB,CAAC,CAAC,CAAC;QACxB;IACF,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;;AAG9C,IAAA,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,OAAsB,KAAI;AACzB,QAAA,IAAI,eAAe,KAAK,SAAS,EAAE;YACjC,eAAe,CAAC,OAAO,CAAC;QAC1B;AACA,QAAA,QAAQ,GAAG,OAAO,CAAC;AACrB,IAAA,CAAC,EACD,CAAC,eAAe,EAAE,QAAQ,CAAC,CAC5B;;AAGD,IAAA,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,KAA2B,KAAI;QAC9B,IAAI,QAAQ,IAAI,QAAQ;YAAE;AAE1B,QAAA,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,KAAK;AACjE,QAAA,MAAM,YAAY,GAChB,OAAO,QAAQ,KAAK,QAAQ,GAAG,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC;AAErE,QAAA,IAAI,CAAC,YAAY;YAAE;;AAGnB,QAAA,IAAI,OAAO,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO;YAAE;;QAGvC,IAAI,CAAC,eAAe,EAAE;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CACtB,GAAG,IACD,eAAe,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC,WAAW,EAAE,CACpE;AACD,YAAA,IAAI,MAAM;gBAAE;QACd;;;;AAKA,QAAA,IACE,CAAC,QAAQ;AACT,YAAA,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC,WAAW,EAAE,CAAC;YAE/D;;AAGF,QAAA,IAAI,YAAY,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;YAAE;;AAGjD,QAAA,MAAM,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,QAAQ;QAE/D,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC;QACnC,UAAU,CAAC,OAAO,CAAC;AACnB,QAAA,KAAK,GAAG,QAAQ,CAAC;QACjB,aAAa,CAAC,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,EAAE;YACb,WAAW,CAAC,KAAK,CAAC;QACpB;AACA,QAAA,mBAAmB,CAAC,EAAE,CAAC;AACzB,IAAA,CAAC,EACD;QACE,QAAQ;QACR,QAAQ;QACR,IAAI;QACJ,OAAO;QACP,eAAe;QACf,QAAQ;QACR,IAAI;QACJ,KAAK;QACL,UAAU;QACV,KAAK;QACL,YAAY;QACZ,SAAS;QACT,QAAQ;AACT,KAAA,CACF;;AAGD,IAAA,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,KAAa,KAAI;;QAEhB,IAAI,QAAQ,IAAI,QAAQ;YAAE;AAE1B,QAAA,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC;AAC9B,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC;QAClD,UAAU,CAAC,OAAO,CAAC;AACnB,QAAA,QAAQ,GAAG,UAAU,EAAE,KAAK,CAAC;AAC/B,IAAA,CAAC,EACD,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CACjD;;AAGD,IAAA,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,CAAsC,KAAI;AACzC,QAAA,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK;;QAG/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnC,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,OAAO,EAAE;AAC1D,YAAA,MAAM,eAAe,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;YACpD,IAAI,eAAe,EAAE;gBACnB,MAAM,CAAC,eAAe,CAAC;YACzB;YACA;QACF;QAEA,aAAa,CAAC,QAAQ,CAAC;AACvB,QAAA,QAAQ,GAAG,QAAQ,CAAC;QAEpB,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;YAC/B,WAAW,CAAC,IAAI,CAAC;QACnB;AACF,IAAA,CAAC,EACD,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAC7C;;AAGD,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,CAAyC,KAAI;AAC5C,QAAA,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;YAAE;QAEpC,MAAM,UAAU,GAAG,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC;;AAElD,QAAA,MAAM,YAAY,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,IAC7C,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CACzB;AAED,QAAA,IAAI,CAAC,YAAY;YAAE;QAEnB,CAAC,CAAC,cAAc,EAAE;;AAGlB,QAAA,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,IAC/C,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CACzC;AACD,QAAA,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,CAAA,CAAA,EAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA,CAAA,CAAG,CAAC;QAE3D,MAAM,KAAK,GAAG;aACX,KAAK,CAAC,KAAK;aACX,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;aACjB,MAAM,CAAC,OAAO,CAAC;AAElB,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,MAAM,CAAC,IAAI,CAAC;QACd;AACF,IAAA,CAAC,EACD,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAC5B;;AAGD,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,MAAK;QACnC,IAAI,WAAW,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE;YAC/C,WAAW,CAAC,IAAI,CAAC;QACnB;IACF,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;;IAGxC,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,SAAS;AAE/B,QAAA,MAAM,kBAAkB,GAAG,CAAC,CAAa,KAAI;AAC3C,YAAA,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE;gBACnE,WAAW,CAAC,KAAK,CAAC;AAClB,gBAAA,mBAAmB,CAAC,EAAE,CAAC;YACzB;AACF,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,kBAAkB,CAAC;QAC1D,OAAO,MACL,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,kBAAkB,CAAC;AACjE,IAAA,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;;AAGd,IAAA,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAsB,KAAI;QACzB,IAAI,QAAQ,IAAI,QAAQ;YAAE;;QAG1B,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;YAC/B,CAAC,CAAC,cAAc,EAAE;YAClB,IAAI,gBAAgB,IAAI,CAAC,IAAI,YAAY,CAAC,gBAAgB,CAAC,EAAE;AAC3D,gBAAA,MAAM,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;YACxC;AAAO,iBAAA,IAAI,UAAU,CAAC,IAAI,EAAE,EAAE;gBAC5B,MAAM,CAAC,UAAU,CAAC;YACpB;YACA;QACF;AAEA,QAAA,QAAQ,CAAC,CAAC,GAAG;AACX,YAAA,KAAK,WAAW;gBACd,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;oBAChC,WAAW,CAAC,IAAI,CAAC;gBACnB;qBAAO;oBACL,mBAAmB,CAAC,IAAI,IACtB,IAAI,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CACjD;gBACH;gBACA;AACF,YAAA,KAAK,SAAS;gBACZ,CAAC,CAAC,cAAc,EAAE;gBAClB,mBAAmB,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;gBACzD;AACF,YAAA,KAAK,WAAW;gBACd,IAAI,YAAY,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;oBAClD,CAAC,CAAC,cAAc,EAAE;AAClB,oBAAA,SAAS,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC5B;gBACA;AACF,YAAA,KAAK,QAAQ;gBACX,WAAW,CAAC,KAAK,CAAC;AAClB,gBAAA,mBAAmB,CAAC,EAAE,CAAC;gBACvB;;AAEN,IAAA,CAAC,EACD;QACE,QAAQ;QACR,QAAQ;QACR,WAAW;QACX,gBAAgB;QAChB,YAAY;QACZ,MAAM;QACN,UAAU;QACV,QAAQ;AACR,QAAA,IAAI,CAAC,MAAM;QACX,YAAY;AACZ,QAAA,IAAI,CAAC,MAAM;QACX,SAAS;AACV,KAAA,CACF;;AAGD,IAAA,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,IAA6B,KAAI;AAC/B,QAAA,QAA4D,CAAC,OAAO;AACnE,YAAA,IAAI;AACN,QAAA,IAAI,OAAO,GAAG,KAAK,UAAU,EAAE;YAC7B,GAAG,CAAC,IAAI,CAAC;QACX;aAAO,IAAI,GAAG,EAAE;AACb,YAAA,GAAuD,CAAC,OAAO;AAC9D,gBAAA,IAAI;QACR;AACF,IAAA,CAAC,EACD,CAAC,GAAG,CAAC,CACN;;AAGD,IAAA,MAAM,oBAAoB,GAAG,WAAW,CAAC,MAAK;AAC5C,QAAA,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE;IAC3B,CAAC,EAAE,EAAE,CAAC;;AAGN,IAAA,MAAM,eAAe,GAAG,GAAG,CAAC,UAAU,EAAE;AACtC,QAAA,WAAW,EAAE,QAAQ;AACrB,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACtB,QAAA,CAAC,MAAM,KAAK,CAAA,CAAE,GAAG,CAAC,CAAC,KAAK;AACxB,QAAA,aAAa,EAAE,QAAQ;AACvB,QAAA,YAAY,EAAE,OAAO;AACrB,QAAA,aAAa,EAAE,QAAQ;AACxB,KAAA,CAAC;AAEF,IAAA,MAAM,gBAAgB,GAAG,GAAG,CAAC,oBAAoB,EAAE;AACjD,QAAA,aAAa,EAAE,QAAQ;QACvB,gBAAgB,EAAE,CAAC,CAAC,IAAI;AACzB,KAAA,CAAC;AAEF,IAAA,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,EAAE;AAChC,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACvB,KAAA,CAAC;AAEF,IAAA,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,EAAE;AAC5B,QAAA,CAAC,MAAM,QAAQ,CAAA,CAAE,GAAG,CAAC,CAAC,QAAQ;AAC9B,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACvB,KAAA,CAAC;IAEF,MAAM,eAAe,GAAG,UAAU,CAChC,eAAe,EACf,kBAAkB,EAClB,SAAS,CACV;;AAGD,IAAA,MAAM,WAAW,GACf,UAAU,KAAK,OAAO,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,CAAC;AAClE,IAAA,MAAM,WAAW,GACf,OAAO,KAAK;AACV,UAAE,CAAA,EAAG,IAAI,CAAC,MAAM,CAAA,GAAA,EAAM,OAAO,CAAA;UAC3B,SAAS,KAAK;AACd,cAAE,CAAA,EAAG,UAAU,CAAC,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA;cACnC,EAAE;IAEV,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7C,IAAA,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC;IAC/B,MAAM,mBAAmB,GAAG,GAAG,CAAC,eAAe,EAAE,WAAW,CAAC;AAC7D,IAAA,MAAM,sBAAsB,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACtD,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAC9C,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC;AAE/C,IAAA,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,EAAE;AAC9C,QAAA,CAAC,MAAM,YAAY,CAAA,CAAE,GAAG,CAAC,CAAC,YAAY;AACvC,KAAA,CAAC;AACF,IAAA,MAAM,SAAS,GAAG,OAAO,GAAGA,GAAA,CAAA,GAAA,EAAA,EAAG,SAAS,EAAE,SAAS,YAAG,OAAO,EAAA,CAAK,GAAG,IAAI;;;;;;;;;AAUzE,IAAA,MAAM,QAAQ,GAAG,CAAC,KAAa,KAAK,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE;IAC3D,MAAM,cAAc,GAClB,QAAQ,IAAI,YAAY,CAAC,gBAAgB,CAAC,EAAE,IAAI;AAC9C,UAAE,QAAQ,CAAC,gBAAgB;UACzB,SAAS;AACf,IAAA,MAAM,aAAa,GAAgD;AACjE,QAAA,IAAI,EAAE,UAAU;AAChB,QAAA,eAAe,EAAE,QAAQ;QACzB,eAAe,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS;AACjD,QAAA,uBAAuB,EAAE,cAAc;AACvC,QAAA,eAAe,EAAE,SAAS;AAC1B,QAAA,mBAAmB,EAAE,MAAM;KAC5B;AAED,IAAA,MAAM,eAAe,IACnBC,IAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,oBAAoB,EAAA,GACzB,IAAI,EAAA,QAAA,EAAA,CAERA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,gBAAgB,aAC7B,IAAI,KACHD,GAAA,CAAC,IAAI,EAAA,EACH,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAAA,CAC1C,CACH,EACDC,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,WAAW,EAAA,QAAA,EAAA,CACxB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAI;AACvB,gCAAA,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC;AACvC,gCAAA,QACEA,IAAA,CAAA,MAAA,EAAA,EAAkB,SAAS,EAAE,UAAU,EAAA,QAAA,EAAA,CACpC,WAAW,IACV,WAAW,CAAC,GAAG,CAAC,IACd,QAAQ,IACVD,GAAA,CAAA,MAAA,EAAA,EAAM,KAAK,EAAE,UAAU,EAAA,QAAA,EAAG,UAAU,GAAQ,KAE5C,UAAU,CACX,EACA,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,KACjCA,GAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,EACpC,OAAO,EAAE,CAAC,IAAG;gDACX,CAAC,CAAC,eAAe,EAAE;gDACnB,SAAS,CAAC,KAAK,CAAC;AAClB,4CAAA,CAAC,EAAA,YAAA,EACW,cAAc,IAAI,CAAA,OAAA,EAAU,UAAU,CAAA,CAAE,EAAA,CACpD,CACH,CAAA,EAAA,EAlBQ,KAAK,CAmBT;AAEX,4BAAA,CAAC,CAAC,EACD,CAAC,YAAY,KACZA,GAAA,CAAA,OAAA,EAAA,EACE,GAAG,EAAE,WAAW,EAChB,IAAI,EAAC,MAAM,EACX,SAAS,EAAE,YAAY,EACvB,EAAE,EAAE,SAAS,EACb,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,GAAG,WAAW,GAAG,SAAS,EACxD,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,MACf,cAAc,IAAI,aAAa,CAAC,EAAA,YAAA,EAKzB,QAAQ,GAAG,SAAS,GAAG,SAAS,GAC5C,CACH,CAAA,EAAA,CACG,EACL,OAAO,KACNA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,cAAc,EAAA,QAAA,EAC7BA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,WAAW,GAAI,EAAA,CAC3B,CACR,CAAA,EAAA,CACG,EAEL,QAAQ,KACPA,aAAK,SAAS,EAAE,mBAAmB,EAAA,QAAA,EACjCA,GAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,WAAW,EAChB,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,sBAAsB,EACjC,IAAI,EAAC,SAAS,EAAA,iBAAA,EACG,WAAW,EAAA,YAAA,EAChB,WAAW,GAAG,SAAS,GAAG,aAAa,YAElD,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,MAC5BA,GAAA,CAAA,GAAA,EAAA,EAEE,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,EACnB,SAAS,EAAE,GAAG,CAAC,eAAe,EAAE;4BAC9B,WAAW,EAAE,KAAK,KAAK,gBAAgB;yBACxC,CAAC,EACF,OAAO,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,EAC3B,YAAY,EAAE,MAAM,mBAAmB,CAAC,KAAK,CAAC,EAC9C,IAAI,EAAC,QAAQ,EAAA,eAAA,EACE,QAAQ,CAAC,KAAK,CAAC,KAAK,cAAc,EAAA,QAAA,EAEhD,IAAI,IAVA,KAAK,CAWR,CACL,CAAC,EAAA,CACE,EAAA,CACF,CACP,EAEA,WAAW,IAAIA,GAAA,CAAA,OAAA,EAAA,EAAO,SAAS,EAAE,cAAc,EAAA,QAAA,EAAG,WAAW,EAAA,CAAS,EAEtE,IAAI;AACH,gBAAA,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,MACdA,GAAA,CAAA,OAAA,EAAA,EAEE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAC3B,IAAI,EAAE,IAAI,EAAA,EAJL,CAAA,UAAA,EAAa,CAAC,CAAA,CAAE,CAKrB,CACH,CAAC,CAAA,EAAA,CACA,CACP;IAED,IAAI,QAAQ,EAAE;AACZ,QAAA,QACEC,IAAA,CAAC,KAAK,EAAA,EACJ,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,eAAe,EAC3B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,cAAc,EAAA,QAAA,EAAA,CAExB,eAAe,EACf,SAAS,CAAA,EAAA,CACJ;IAEZ;AAEA,IAAA,QACEA,IAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAA,CACG,eAAe,EACf,SAAS,CAAA,EAAA,CACT;AAEP,CAAC;AAGH,QAAQ,CAAC,WAAW,GAAG,UAAU;AAEjC;AACO,MAAM,QAAQ,GAAG;;"}
|
|
1
|
+
{"version":3,"file":"Taginput.js","sources":["../../../src/form/Taginput.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useState,\n useRef,\n useEffect,\n useCallback,\n useId,\n} from 'react';\nimport {\n classNames,\n usePrefixedClassNames,\n prefixedClassNames,\n} from '../helpers/classNames';\nimport { useConfig, useIconLibrary } from '../helpers/Config';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { isEventInside } from '../helpers/shadowDom';\nimport {\n useInsideField,\n useInsideControl,\n rendersOwnField,\n} from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\nimport { Icon } from '../elements/Icon';\n\n/**\n * Represents a tag item object with a value and optional label.\n */\nexport interface TaginputItem {\n /** The tag value. */\n value: string;\n /** Display label (optional). */\n label?: string;\n [key: string]: unknown;\n}\n\n/** A tag can be either a TaginputItem object or a plain string. */\nexport type TaginputTag = TaginputItem | string;\n\ntype IconLibrary = 'fa' | 'mdi' | 'ion' | 'material-icons' | 'material-symbols';\n\n/**\n * Props for the Taginput component.\n * Inside an outer `Field`, or a `Control` with no `Field` around it, it\n * renders no `Field` of its own. The exception is `label`, `message`,\n * `horizontal` or `fieldClassName` in that bare `Control`: it keeps a `Field`\n * for them, nested in the `.control`, and warns in development. Wrap the\n * `Control` in a `Field` instead, and set the `label`, `horizontal` and class\n * name on that `Field`.\n * @extraProp {string} [className] - Additional CSS classes.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the input element.\n */\nexport interface TaginputProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label. Automatically associated with the text input via `htmlFor` — uses your `id` when provided, otherwise a generated one. Not wired when the tag limit is reached (the input is not rendered). Dropped inside an outer `Field`, whose own label associates instead when that `Field` generates a target id (not `grouped`/`hasAddons`, no explicit `labelProps.htmlFor`) and you set no `id` here; it then replaces the `aria-label=\"Add tag\"` fallback too. With `data`, the label naming the input also names the open suggestion list, through `aria-labelledby`, when it has an id: this label always does, and an outer `Field`'s does unless you wired it by hand without an `id` in its `labelProps`. Otherwise the list is named \"Suggestions\". */\n label?: React.ReactNode;\n /** Props for the label element. An explicit `htmlFor` here overrides the automatic association (no id is generated then). With `data`, while the label names the input, the suggestion list's `aria-labelledby` points at it, through an `id` here when you give one and a generated one otherwise. */\n labelProps?: React.LabelHTMLAttributes<HTMLLabelElement> & {\n [key: string]: unknown;\n };\n /** Applied to the inner text input (the labellable control), not the wrapper div. */\n id?: string;\n /** The current tags (controlled). */\n value?: TaginputTag[];\n /** Default tags (uncontrolled). */\n defaultValue?: TaginputTag[];\n /**\n * Autocomplete suggestions. With `data` set, even to an empty array, the\n * text input is a `role=\"combobox\"` that controls the suggestion list and\n * points `aria-activedescendant` at the highlighted suggestion. Without it\n * there's no list, so the input stays a plain text field.\n */\n data?: string[];\n /** Placeholder text when no tags. */\n placeholder?: string;\n /** Object property to use as display field. */\n field?: string;\n /** Allow creating new tags not in suggestions. */\n allowNew?: boolean;\n /** Allow duplicate tags. */\n allowDuplicates?: boolean;\n /** Open autocomplete dropdown on focus. */\n openOnFocus?: boolean;\n /** Remove tag on backspace. Default: true. */\n removeOnKeys?: boolean;\n /** Keys to confirm tag creation. Default: ['Enter', ',']. */\n confirmKeys?: string[];\n /** Show close button on tags. Default: true. */\n closable?: boolean;\n /** Attach tags visually. */\n attached?: boolean;\n /** Maximum number of tags allowed. */\n maxTags?: number;\n /** Maximum length of input. */\n maxlength?: number;\n /** Whether the input is disabled. */\n disabled?: boolean;\n /** Whether the input is read-only. */\n readonly?: boolean;\n /** Applies rounded corners to the tags. */\n rounded?: boolean;\n /** Truncates long tag text with ellipsis. */\n ellipsis?: boolean;\n /** Shows a counter of the number of tags. */\n hasCounter?: boolean;\n /** Characters that split pasted text into tags. */\n onPasteSeparators?: string[];\n /** Validation function called before adding a tag. */\n beforeAdding?: (tag: string) => boolean;\n /** Custom function for creating tag objects from input. */\n createTag?: (input: string) => TaginputTag;\n /** Keeps the first autocomplete suggestion highlighted. */\n keepFirst?: boolean;\n /** Keeps the autocomplete dropdown open after selection. */\n keepOpen?: boolean;\n /**\n * Shows a loading spinner in the input.\n * Under `prefers-reduced-motion: reduce` the spinner stops and stays\n * drawn (with bestax's CSS loaded).\n */\n loading?: boolean;\n /** ARIA label for tag close buttons. */\n ariaCloseLabel?: string;\n /** Icon name for the input field. */\n icon?: string;\n /** Icon library to use. */\n iconLibrary?: IconLibrary;\n /** Icon style variant (e.g., 'solid', 'outlined'). */\n iconVariant?: string;\n /** Additional icon modifiers. */\n iconFeatures?: string | string[];\n /** Input color variant. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Tag color variant. */\n tagColor?:\n | 'primary'\n | 'link'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger'\n | 'dark'\n | 'light';\n /** Size variant. */\n size?: 'small' | 'medium' | 'large';\n /** Form field name. When set, one hidden input per tag is rendered. */\n name?: string;\n /** Optional id of the form the hidden inputs belong to. */\n form?: string;\n /** Callback when tags change. */\n onChange?: (tags: TaginputTag[]) => void;\n /** Callback when tag is added. */\n onAdd?: (tag: TaginputTag) => void;\n /** Callback when tag is removed. */\n onRemove?: (tag: TaginputTag, index: number) => void;\n /** Callback when typing in input. */\n onTyping?: (value: string) => void;\n /** Custom render for tags. */\n tagTemplate?: (tag: TaginputTag) => React.ReactNode;\n}\n\n/**\n * The `Taginput` component provides a tag/chip input field for managing multiple tags.\n *\n * @function\n * @param {TaginputProps} props - Props for the Taginput component.\n * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the input element.\n * @returns {JSX.Element} The rendered taginput component.\n *\n * @example\n * // Basic usage\n * <Taginput\n * placeholder=\"Add a tag...\"\n * onChange={(tags) => console.log(tags)}\n * />\n *\n * @example\n * // With autocomplete\n * <Taginput\n * data={['React', 'Vue', 'Angular', 'Svelte']}\n * allowNew={false}\n * placeholder=\"Select frameworks...\"\n * />\n */\nexport const Taginput = forwardRef<HTMLInputElement, TaginputProps>(\n (\n {\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n value: controlledValue,\n defaultValue = [],\n data: dataProp,\n placeholder,\n field = 'label',\n allowNew = true,\n allowDuplicates = false,\n openOnFocus = false,\n removeOnKeys = true,\n confirmKeys = ['Enter', ','],\n closable = true,\n attached = false,\n maxTags,\n maxlength,\n disabled = false,\n readonly = false,\n rounded = false,\n ellipsis = false,\n hasCounter = true,\n onPasteSeparators = [','],\n beforeAdding,\n createTag,\n keepFirst = false,\n keepOpen = true,\n loading = false,\n ariaCloseLabel,\n icon,\n iconLibrary: iconLibraryProp,\n iconVariant,\n iconFeatures,\n color,\n tagColor,\n size,\n onChange,\n onAdd,\n onRemove,\n onTyping,\n tagTemplate,\n name,\n form,\n id,\n className,\n ...props\n },\n ref\n ) => {\n const insideField = useInsideField();\n const insideControl = useInsideControl();\n const ownField = rendersOwnField('Taginput', {\n insideField,\n insideControl,\n label,\n message,\n horizontal,\n fieldClassName,\n });\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const { classPrefix } = useConfig();\n const defaultIconLibrary = useIconLibrary();\n const resolvedLibrary = iconLibraryProp || defaultIconLibrary || 'fa';\n const pcn = (\n ...args: (\n | string\n | number\n | undefined\n | null\n | false\n | Record<string, unknown>\n | unknown[]\n )[]\n ) => prefixedClassNames(classPrefix, ...args);\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const [internalTags, setInternalTags] =\n useState<TaginputTag[]>(defaultValue);\n const [inputValue, setInputValue] = useState('');\n const [isActive, setIsActive] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const listboxId = `${useId()}-listbox`;\n\n // With `data` the input offers suggestions, so it's a combobox, even\n // while none match. Without it there's never a list, so it stays a plain\n // text field.\n const hasSuggestions = dataProp !== undefined;\n const data = dataProp ?? [];\n\n // Use controlled or internal tags\n const tags = controlledValue !== undefined ? controlledValue : internalTags;\n\n const isMaxReached = maxTags !== undefined && tags.length >= maxTags;\n // At the tag limit the text input is not rendered, so there is nothing to\n // wire a label to, its own or a surrounding Field's. The suggestion list\n // points `aria-labelledby` at the label that names the input, so with\n // suggestions it needs that label's id.\n const {\n controlId,\n fieldLabelProps,\n labelled,\n labelId: listLabelId,\n } = useAutoLabelId({\n label,\n id,\n labelProps,\n rendersLabel: ownField && !isMaxReached,\n hasInput: !isMaxReached,\n needsLabelId: hasSuggestions,\n });\n\n // Get display value from tag\n const getDisplayValue = (tag: TaginputTag): string => {\n if (typeof tag === 'string') return tag;\n const fieldValue = tag[field];\n if (typeof fieldValue === 'string') return fieldValue;\n return tag.value || '';\n };\n\n // Filter autocomplete data\n const filteredData = data.filter(item => {\n const displayValue = item.toLowerCase();\n const matchesInput = displayValue.includes(inputValue.toLowerCase());\n const notAlreadyAdded =\n allowDuplicates ||\n !tags.some(tag => getDisplayValue(tag).toLowerCase() === displayValue);\n const hasInput = inputValue || (openOnFocus && isActive);\n return matchesInput && notAlreadyAdded && hasInput;\n });\n const listOpen = isActive && filteredData.length > 0;\n\n // keepFirst: auto-highlight first item when dropdown opens\n useEffect(() => {\n if (keepFirst && isActive && filteredData.length > 0) {\n setHighlightedIndex(0);\n }\n }, [keepFirst, isActive, filteredData.length]);\n\n // Update tags\n const updateTags = useCallback(\n (newTags: TaginputTag[]) => {\n if (controlledValue === undefined) {\n setInternalTags(newTags);\n }\n onChange?.(newTags);\n },\n [controlledValue, onChange]\n );\n\n // Add tag\n const addTag = useCallback(\n (value: string | TaginputTag) => {\n if (disabled || readonly) return;\n\n const tagValue = typeof value === 'string' ? value.trim() : value;\n const displayValue =\n typeof tagValue === 'string' ? tagValue : getDisplayValue(tagValue);\n\n if (!displayValue) return;\n\n // Check max tags\n if (maxTags && tags.length >= maxTags) return;\n\n // Check duplicates\n if (!allowDuplicates) {\n const exists = tags.some(\n tag =>\n getDisplayValue(tag).toLowerCase() === displayValue.toLowerCase()\n );\n if (exists) return;\n }\n\n // Check if allowed (must be in data if allowNew is false). Compare\n // case-insensitively, consistent with the dropdown filter and the\n // duplicate check above.\n if (\n !allowNew &&\n !data.some(d => d.toLowerCase() === displayValue.toLowerCase())\n )\n return;\n\n // beforeAdding validation\n if (beforeAdding && !beforeAdding(displayValue)) return;\n\n // createTag transformation\n const finalTag = createTag ? createTag(displayValue) : tagValue;\n\n const newTags = [...tags, finalTag];\n updateTags(newTags);\n onAdd?.(finalTag);\n setInputValue('');\n if (!keepOpen) {\n setIsActive(false);\n }\n setHighlightedIndex(-1);\n },\n [\n disabled,\n readonly,\n tags,\n maxTags,\n allowDuplicates,\n allowNew,\n data,\n field,\n updateTags,\n onAdd,\n beforeAdding,\n createTag,\n keepOpen,\n ]\n );\n\n // Remove tag\n const removeTag = useCallback(\n (index: number) => {\n /* istanbul ignore next: both triggers (delete button click, Backspace handler) are already gated on disabled/readonly */\n if (disabled || readonly) return;\n\n const removedTag = tags[index];\n const newTags = tags.filter((_, i) => i !== index);\n updateTags(newTags);\n onRemove?.(removedTag, index);\n },\n [disabled, readonly, tags, updateTags, onRemove]\n );\n\n // Handle input change\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n\n // Check if a confirm key was typed\n const lastChar = newValue.slice(-1);\n if (confirmKeys.includes(lastChar) && lastChar !== 'Enter') {\n const valueWithoutKey = newValue.slice(0, -1).trim();\n if (valueWithoutKey) {\n addTag(valueWithoutKey);\n }\n return;\n }\n\n setInputValue(newValue);\n onTyping?.(newValue);\n\n if (newValue && data.length > 0) {\n setIsActive(true);\n }\n },\n [confirmKeys, addTag, onTyping, data.length]\n );\n\n // Handle paste\n const handlePaste = useCallback(\n (e: React.ClipboardEvent<HTMLInputElement>) => {\n if (onPasteSeparators.length === 0) return;\n\n const pastedText = e.clipboardData.getData('text');\n // Check if any separator exists in the pasted text\n const hasSeparator = onPasteSeparators.some(sep =>\n pastedText.includes(sep)\n );\n\n if (!hasSeparator) return;\n\n e.preventDefault();\n\n // Build a regex from all separators\n const escapedSeparators = onPasteSeparators.map(s =>\n s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n );\n const regex = new RegExp(`[${escapedSeparators.join('')}]`);\n\n const parts = pastedText\n .split(regex)\n .map(s => s.trim())\n .filter(Boolean);\n\n for (const part of parts) {\n addTag(part);\n }\n },\n [onPasteSeparators, addTag]\n );\n\n // Handle focus\n const handleFocus = useCallback(() => {\n if (openOnFocus && data.length > 0 && !disabled) {\n setIsActive(true);\n }\n }, [openOnFocus, data.length, disabled]);\n\n // Handle click outside\n useEffect(() => {\n if (!isActive) return undefined;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (containerRef.current && !isEventInside(e, containerRef.current)) {\n setIsActive(false);\n setHighlightedIndex(-1);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () =>\n document.removeEventListener('mousedown', handleClickOutside);\n }, [isActive]);\n\n // Handle keyboard\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled || readonly) return;\n\n // Handle confirm keys\n if (confirmKeys.includes(e.key)) {\n e.preventDefault();\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n addTag(filteredData[highlightedIndex]);\n } else if (inputValue.trim()) {\n addTag(inputValue);\n }\n return;\n }\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!isActive && data.length > 0) {\n setIsActive(true);\n } else {\n setHighlightedIndex(prev =>\n prev < filteredData.length - 1 ? prev + 1 : prev\n );\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n setHighlightedIndex(prev => (prev > 0 ? prev - 1 : prev));\n break;\n case 'Backspace':\n if (removeOnKeys && !inputValue && tags.length > 0) {\n e.preventDefault();\n removeTag(tags.length - 1);\n }\n break;\n case 'Escape':\n // Closing the suggestions claims the key, so a Modal around the\n // field stays open. With none showing, the key goes on to it.\n if (isActive && filteredData.length > 0) e.preventDefault();\n setIsActive(false);\n setHighlightedIndex(-1);\n break;\n }\n },\n [\n disabled,\n readonly,\n confirmKeys,\n highlightedIndex,\n filteredData,\n addTag,\n inputValue,\n isActive,\n data.length,\n removeOnKeys,\n tags.length,\n removeTag,\n ]\n );\n\n // Combined ref\n const combinedRef = useCallback(\n (node: HTMLInputElement | null) => {\n (inputRef as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLInputElement | null>).current =\n node;\n }\n },\n [ref]\n );\n\n // Focus input when container is clicked\n const handleContainerClick = useCallback(() => {\n inputRef.current?.focus();\n }, []);\n\n // Generate classes\n const taginputClasses = pcn('taginput', {\n 'is-active': isActive,\n [`is-${size}`]: !!size,\n [`is-${color}`]: !!color,\n 'is-disabled': disabled,\n 'is-rounded': rounded,\n 'is-ellipsis': ellipsis,\n });\n\n const containerClasses = pcn('taginput-container', {\n 'is-attached': attached,\n 'has-icons-left': !!icon,\n });\n\n const inputClasses = pcn('input', {\n [`is-${size}`]: !!size,\n });\n\n const tagClasses = pcn('tag', {\n [`is-${tagColor}`]: !!tagColor,\n [`is-${size}`]: !!size,\n });\n\n const combinedClasses = classNames(\n taginputClasses,\n bulmaHelperClasses,\n className\n );\n\n // Counter text\n const showCounter =\n hasCounter && (maxTags !== undefined || maxlength !== undefined);\n const counterText =\n maxTags !== undefined\n ? `${tags.length} / ${maxTags}`\n : maxlength !== undefined\n ? `${inputValue.length} / ${maxlength}`\n : '';\n\n const counterClasses = pcn('help', 'counter');\n const tagsClasses = pcn('tags');\n const dropdownMenuClasses = pcn('dropdown-menu', 'is-active');\n const dropdownContentClasses = pcn('dropdown-content');\n const iconRightClass = pcn('icon', 'is-right');\n const loaderClass = pcn('loader', 'is-loading');\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n // The combobox controls its listbox and points at the highlighted option,\n // the way Autocomplete's does. The listbox takes its name from the label\n // that names the combobox, the one this renders or a surrounding Field's,\n // and keeps a fallback name when no label names it or the one that does\n // has no id to point at. While no suggestion matches there's no listbox,\n // so the combobox is collapsed and points at nothing. It skips a\n // highlighted suggestion that's blank once trimmed, since that one can't\n // become a tag, and the option it points at is the one marked selected.\n const optionId = (index: number) => `${listboxId}-${index}`;\n const activeOptionId =\n listOpen && filteredData[highlightedIndex]?.trim()\n ? optionId(highlightedIndex)\n : undefined;\n const comboboxProps: React.InputHTMLAttributes<HTMLInputElement> = {\n role: 'combobox',\n 'aria-expanded': listOpen,\n 'aria-controls': listOpen ? listboxId : undefined,\n 'aria-activedescendant': activeOptionId,\n 'aria-haspopup': 'listbox',\n 'aria-autocomplete': 'list',\n };\n\n const taginputElement = (\n <div\n ref={containerRef}\n className={combinedClasses}\n onClick={handleContainerClick}\n {...rest}\n >\n <div className={containerClasses}>\n {icon && (\n <Icon\n name={icon}\n library={resolvedLibrary}\n variant={iconVariant}\n features={iconFeatures}\n containerClassName={pcn('icon', 'is-left')}\n />\n )}\n <div className={tagsClasses}>\n {tags.map((tag, index) => {\n const displayVal = getDisplayValue(tag);\n return (\n <span key={index} className={tagClasses}>\n {tagTemplate ? (\n tagTemplate(tag)\n ) : ellipsis ? (\n <span title={displayVal}>{displayVal}</span>\n ) : (\n displayVal\n )}\n {closable && !disabled && !readonly && (\n <button\n type=\"button\"\n className={pcn('delete', 'is-small')}\n onClick={e => {\n e.stopPropagation();\n removeTag(index);\n }}\n aria-label={ariaCloseLabel || `Remove ${displayVal}`}\n />\n )}\n </span>\n );\n })}\n {!isMaxReached && (\n <input\n ref={combinedRef}\n type=\"text\"\n className={inputClasses}\n id={controlId}\n value={inputValue}\n placeholder={tags.length === 0 ? placeholder : undefined}\n disabled={disabled}\n readOnly={readonly}\n maxLength={maxlength}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n {...(hasSuggestions && comboboxProps)}\n // Fallback name, dropped only when a rendered label actually\n // targets this input, its own or a surrounding Field's\n // (aria-label would win otherwise). A label pointing elsewhere\n // keeps the fallback so the input is never nameless.\n aria-label={labelled ? undefined : 'Add tag'}\n />\n )}\n </div>\n {loading && (\n <span className={iconRightClass}>\n <span className={loaderClass} />\n </span>\n )}\n </div>\n\n {listOpen && (\n <div className={dropdownMenuClasses}>\n <div\n ref={dropdownRef}\n id={listboxId}\n className={dropdownContentClasses}\n role=\"listbox\"\n aria-labelledby={listLabelId}\n aria-label={listLabelId ? undefined : 'Suggestions'}\n >\n {filteredData.map((item, index) => (\n <a\n key={index}\n id={optionId(index)}\n className={pcn('dropdown-item', {\n 'is-active': index === highlightedIndex,\n })}\n onClick={() => addTag(item)}\n onMouseEnter={() => setHighlightedIndex(index)}\n role=\"option\"\n aria-selected={optionId(index) === activeOptionId}\n >\n {item}\n </a>\n ))}\n </div>\n </div>\n )}\n\n {showCounter && <small className={counterClasses}>{counterText}</small>}\n\n {name &&\n tags.map((tag, i) => (\n <input\n key={`tag-input-${i}`}\n type=\"hidden\"\n name={name}\n value={getDisplayValue(tag)}\n form={form}\n />\n ))}\n </div>\n );\n\n if (ownField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {taginputElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {taginputElement}\n {messageEl}\n </>\n );\n }\n);\n\nTaginput.displayName = 'Taginput';\n\n/** @deprecated Use `Taginput` — same component. */\nexport const TagInput = Taginput;\n\n/** @deprecated Use `TaginputProps`. */\nexport type TagInputProps = TaginputProps;\n\nexport default Taginput;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { createPortal } from 'react-dom';
|
|
|
5
5
|
import { usePrefixedClassNames, classNames } from '../../helpers/classNames.js';
|
|
6
6
|
import { useFocusTrap } from '../../helpers/useFocusTrap.js';
|
|
7
7
|
import { usePortalContainer } from '../../helpers/portal.js';
|
|
8
|
+
import { isEventInside } from '../../helpers/shadowDom.js';
|
|
8
9
|
import { useAnchoredPosition, marginGap } from '../../helpers/useAnchoredPosition.js';
|
|
9
10
|
|
|
10
11
|
const isBrowser = typeof window !== 'undefined';
|
|
@@ -48,13 +49,20 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
48
49
|
return () => document.removeEventListener('pointerdown', handler);
|
|
49
50
|
}, [isOpen, closeOnClickOutside, anchorRef, onClose]);
|
|
50
51
|
// Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
|
|
51
|
-
// which the calendar and the time wheels inside leave to it.
|
|
52
|
+
// which the calendar and the time wheels inside leave to it. Escape closes
|
|
53
|
+
// it from anywhere, but the key is only the picker's when it was pressed in
|
|
54
|
+
// the panel or the field: then it's marked used, so a Modal or Sidebar
|
|
55
|
+
// around the picker stays open. From anywhere else, such as a dialog opened
|
|
56
|
+
// over the picker, the key goes on to whatever has focus.
|
|
52
57
|
useEffect(() => {
|
|
53
58
|
if (!isOpen || !closeOnEscape || !isBrowser)
|
|
54
59
|
return undefined;
|
|
55
60
|
const handler = (e) => {
|
|
56
61
|
if (e.key === 'Escape') {
|
|
57
|
-
e.
|
|
62
|
+
if (isEventInside(e, panelRef.current) ||
|
|
63
|
+
isEventInside(e, anchorRef.current)) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
}
|
|
58
66
|
onClose();
|
|
59
67
|
}
|
|
60
68
|
else if (e.altKey && e.key === 'ArrowUp') {
|
|
@@ -65,7 +73,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
65
73
|
};
|
|
66
74
|
document.addEventListener('keydown', handler);
|
|
67
75
|
return () => document.removeEventListener('keydown', handler);
|
|
68
|
-
}, [isOpen, closeOnEscape, onClose]);
|
|
76
|
+
}, [isOpen, closeOnEscape, anchorRef, onClose]);
|
|
69
77
|
useFocusTrap(panelRef, {
|
|
70
78
|
active: isOpen && trapFocus,
|
|
71
79
|
initialFocusRef,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PickerPopover.js","sources":["../../../../src/form/_pickerInternals/PickerPopover.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { classNames, usePrefixedClassNames } from '../../helpers/classNames';\nimport { useFocusTrap } from '../../helpers/useFocusTrap';\nimport { usePortalContainer } from '../../helpers/portal';\nimport {\n marginGap,\n useAnchoredPosition,\n} from '../../helpers/useAnchoredPosition';\nimport { PickerPosition } from './pickerTypes';\n\nexport interface PickerPopoverProps {\n isOpen: boolean;\n onClose: () => void;\n anchorRef: React.RefObject<HTMLElement | null>;\n children: React.ReactNode;\n position?: PickerPosition;\n appendToBody?: boolean;\n className?: string;\n trapFocus?: boolean;\n closeOnClickOutside?: boolean;\n /** Close on Escape and on Alt+ArrowUp. Default `true`. */\n closeOnEscape?: boolean;\n ariaLabel?: string;\n ariaLabelledBy?: string;\n role?: 'dialog' | 'group';\n id?: string;\n /**\n * Where focus goes when the popover closes. Without it, focus goes back to\n * whatever had it when the focus trap turned on, which is read after the\n * panel's content mounts: a calendar that focuses a cell as it mounts\n * would be recorded instead, and that cell leaves with the panel.\n */\n restoreFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Selects the element in the panel that takes focus as it opens, such as a\n * calendar's focused date rather than the first button in its header. When\n * nothing matches, or the match can't take focus, the first tab stop does.\n */\n initialFocusSelector?: string;\n}\n\nconst isBrowser = typeof window !== 'undefined';\n\nexport const PickerPopover: React.FC<PickerPopoverProps> = ({\n isOpen,\n onClose,\n anchorRef,\n children,\n position = 'bottom-left',\n appendToBody = false,\n className,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n ariaLabelledBy,\n role = 'dialog',\n id,\n restoreFocusRef,\n initialFocusSelector,\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const resolveContainer = usePortalContainer();\n // Read as the trap turns on, once the panel's content has rendered.\n const initialFocusRef = useMemo(\n () =>\n initialFocusSelector === undefined\n ? undefined\n : {\n get current() {\n return (\n panelRef.current?.querySelector<HTMLElement>(\n initialFocusSelector\n ) ?? null\n );\n },\n },\n [initialFocusSelector]\n );\n const resolved = useAnchoredPosition(anchorRef, panelRef, {\n active: isOpen,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-picker-popover-offset` as a margin, in place\n // or portaled, so the coordinates leave it out and `auto` reads it off\n // the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n useEffect(() => {\n if (!isOpen || !closeOnClickOutside || !isBrowser) return undefined;\n // Listen on `pointerdown` (rather than `mousedown`) so touch starts\n // outside the popover dismiss it on mobile — including swipe-from-outside\n // gestures, where the synthetic mouse event would otherwise lag a few\n // hundred ms behind the actual touch.\n const handler = (e: PointerEvent) => {\n const path = e.composedPath();\n if (panelRef.current && path.includes(panelRef.current)) return;\n if (anchorRef.current && path.includes(anchorRef.current)) return;\n onClose();\n };\n document.addEventListener('pointerdown', handler);\n return () => document.removeEventListener('pointerdown', handler);\n }, [isOpen, closeOnClickOutside, anchorRef, onClose]);\n\n // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,\n // which the calendar and the time wheels inside leave to it.\n useEffect(() => {\n if (!isOpen || !closeOnEscape || !isBrowser) return undefined;\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.
|
|
1
|
+
{"version":3,"file":"PickerPopover.js","sources":["../../../../src/form/_pickerInternals/PickerPopover.tsx"],"sourcesContent":["import React, { useEffect, useMemo, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { classNames, usePrefixedClassNames } from '../../helpers/classNames';\nimport { useFocusTrap } from '../../helpers/useFocusTrap';\nimport { usePortalContainer } from '../../helpers/portal';\nimport { isEventInside } from '../../helpers/shadowDom';\nimport {\n marginGap,\n useAnchoredPosition,\n} from '../../helpers/useAnchoredPosition';\nimport { PickerPosition } from './pickerTypes';\n\nexport interface PickerPopoverProps {\n isOpen: boolean;\n onClose: () => void;\n anchorRef: React.RefObject<HTMLElement | null>;\n children: React.ReactNode;\n position?: PickerPosition;\n appendToBody?: boolean;\n className?: string;\n trapFocus?: boolean;\n closeOnClickOutside?: boolean;\n /** Close on Escape and on Alt+ArrowUp. Default `true`. */\n closeOnEscape?: boolean;\n ariaLabel?: string;\n ariaLabelledBy?: string;\n role?: 'dialog' | 'group';\n id?: string;\n /**\n * Where focus goes when the popover closes. Without it, focus goes back to\n * whatever had it when the focus trap turned on, which is read after the\n * panel's content mounts: a calendar that focuses a cell as it mounts\n * would be recorded instead, and that cell leaves with the panel.\n */\n restoreFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Selects the element in the panel that takes focus as it opens, such as a\n * calendar's focused date rather than the first button in its header. When\n * nothing matches, or the match can't take focus, the first tab stop does.\n */\n initialFocusSelector?: string;\n}\n\nconst isBrowser = typeof window !== 'undefined';\n\nexport const PickerPopover: React.FC<PickerPopoverProps> = ({\n isOpen,\n onClose,\n anchorRef,\n children,\n position = 'bottom-left',\n appendToBody = false,\n className,\n trapFocus = true,\n closeOnClickOutside = true,\n closeOnEscape = true,\n ariaLabel,\n ariaLabelledBy,\n role = 'dialog',\n id,\n restoreFocusRef,\n initialFocusSelector,\n}) => {\n const panelRef = useRef<HTMLDivElement>(null);\n const resolveContainer = usePortalContainer();\n // Read as the trap turns on, once the panel's content has rendered.\n const initialFocusRef = useMemo(\n () =>\n initialFocusSelector === undefined\n ? undefined\n : {\n get current() {\n return (\n panelRef.current?.querySelector<HTMLElement>(\n initialFocusSelector\n ) ?? null\n );\n },\n },\n [initialFocusSelector]\n );\n const resolved = useAnchoredPosition(anchorRef, panelRef, {\n active: isOpen,\n position,\n fixed: appendToBody,\n // The gap comes from `--bulma-picker-popover-offset` as a margin, in place\n // or portaled, so the coordinates leave it out and `auto` reads it off\n // the panel.\n offset: 0,\n styledGap: marginGap,\n });\n\n useEffect(() => {\n if (!isOpen || !closeOnClickOutside || !isBrowser) return undefined;\n // Listen on `pointerdown` (rather than `mousedown`) so touch starts\n // outside the popover dismiss it on mobile — including swipe-from-outside\n // gestures, where the synthetic mouse event would otherwise lag a few\n // hundred ms behind the actual touch.\n const handler = (e: PointerEvent) => {\n const path = e.composedPath();\n if (panelRef.current && path.includes(panelRef.current)) return;\n if (anchorRef.current && path.includes(anchorRef.current)) return;\n onClose();\n };\n document.addEventListener('pointerdown', handler);\n return () => document.removeEventListener('pointerdown', handler);\n }, [isOpen, closeOnClickOutside, anchorRef, onClose]);\n\n // Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,\n // which the calendar and the time wheels inside leave to it. Escape closes\n // it from anywhere, but the key is only the picker's when it was pressed in\n // the panel or the field: then it's marked used, so a Modal or Sidebar\n // around the picker stays open. From anywhere else, such as a dialog opened\n // over the picker, the key goes on to whatever has focus.\n useEffect(() => {\n if (!isOpen || !closeOnEscape || !isBrowser) return undefined;\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n if (\n isEventInside(e, panelRef.current) ||\n isEventInside(e, anchorRef.current)\n ) {\n e.preventDefault();\n }\n onClose();\n } else if (e.altKey && e.key === 'ArrowUp') {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n };\n document.addEventListener('keydown', handler);\n return () => document.removeEventListener('keydown', handler);\n }, [isOpen, closeOnEscape, anchorRef, onClose]);\n\n useFocusTrap(panelRef, {\n active: isOpen && trapFocus,\n initialFocusRef,\n restoreFocus: restoreFocusRef ?? true,\n });\n\n const panelClass = usePrefixedClassNames('picker-popover', {\n 'is-active': isOpen,\n [`is-${resolved.position}`]: true,\n 'is-portal': appendToBody,\n });\n\n if (!isOpen) return null;\n\n const style: React.CSSProperties = appendToBody\n ? {\n position: 'fixed',\n top: resolved.top,\n left: resolved.left,\n }\n : {};\n\n const panel = (\n <div\n ref={panelRef}\n id={id}\n role={role}\n aria-modal={role === 'dialog' ? 'false' : undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n tabIndex={-1}\n className={classNames(panelClass, className)}\n style={style}\n >\n {children}\n </div>\n );\n\n if (appendToBody && isBrowser) {\n return createPortal(panel, resolveContainer());\n }\n return panel;\n};\n\nPickerPopover.displayName = 'PickerPopover';\n\nexport default PickerPopover;\n"],"names":["_jsx"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -2563,8 +2563,9 @@ const isBrowser$1 = (win, doc) => typeof win !== 'undefined' && typeof doc !== '
|
|
|
2563
2563
|
* the menu, so a letter typed on the trigger does nothing. Escape closes the
|
|
2564
2564
|
* menu and focuses the trigger, and Tab closes it and lets focus move on.
|
|
2565
2565
|
* Anything focusable in the menu that isn't an item, such as a field, keeps
|
|
2566
|
-
* its own keys apart from Escape and Tab
|
|
2567
|
-
*
|
|
2566
|
+
* its own keys apart from Escape and Tab, though an Escape it calls
|
|
2567
|
+
* `preventDefault()` on in its own `onKeyDown` stays its own and leaves the
|
|
2568
|
+
* menu open. For a panel built around fields, use `Popover`.
|
|
2568
2569
|
*
|
|
2569
2570
|
* @function
|
|
2570
2571
|
* @param {DropdownProps} props - Props for the Dropdown component.
|
|
@@ -2741,8 +2742,12 @@ const DropdownComponent = React.forwardRef(function DropdownComponent({ label, c
|
|
|
2741
2742
|
// Closing is Dropdown's: Escape and Tab close the menu from anywhere in
|
|
2742
2743
|
// it, a field included, and an empty or all-disabled menu too. Moving
|
|
2743
2744
|
// between the items, running them and typeahead are the hook's, which
|
|
2744
|
-
// acts only while focus is on the menu or one of its items.
|
|
2745
|
+
// acts only while focus is on the menu or one of its items. An Escape
|
|
2746
|
+
// something in the menu already used, such as a field's own list
|
|
2747
|
+
// closing on it, leaves the menu open for the next one.
|
|
2745
2748
|
if (e.key === 'Escape') {
|
|
2749
|
+
if (e.defaultPrevented)
|
|
2750
|
+
return;
|
|
2746
2751
|
e.preventDefault();
|
|
2747
2752
|
setActive(false);
|
|
2748
2753
|
onActiveChange?.(false);
|
|
@@ -4056,6 +4061,9 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4056
4061
|
};
|
|
4057
4062
|
}, [isModalActive]);
|
|
4058
4063
|
// Escape closes; Tab cycles inside the modal. Both only for the topmost one.
|
|
4064
|
+
// The listener is on `window`, where a keydown arrives last, so everything
|
|
4065
|
+
// inside the modal has had the key first: React's handlers, and `document`
|
|
4066
|
+
// listeners added after this one, such as a date picker's.
|
|
4059
4067
|
React.useEffect(() => {
|
|
4060
4068
|
if (!isModalActive)
|
|
4061
4069
|
return undefined;
|
|
@@ -4065,11 +4073,15 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4065
4073
|
return;
|
|
4066
4074
|
}
|
|
4067
4075
|
if (e.key === 'Escape') {
|
|
4068
|
-
|
|
4076
|
+
// Something inside already used this Escape, such as a popup that
|
|
4077
|
+
// closed on it, so the modal waits for the next one.
|
|
4078
|
+
if (closeOnEscape && !e.defaultPrevented)
|
|
4069
4079
|
onClose?.();
|
|
4070
4080
|
return;
|
|
4071
4081
|
}
|
|
4072
|
-
|
|
4082
|
+
// A trap inside the modal, such as a portaled popup's, already moved
|
|
4083
|
+
// focus on this Tab, so the modal leaves it where it went.
|
|
4084
|
+
if (e.key !== 'Tab' || !node || e.defaultPrevented)
|
|
4073
4085
|
return;
|
|
4074
4086
|
// Keep Tab within the modal — `aria-modal` hides the rest of the page
|
|
4075
4087
|
// from assistive technology, so the keyboard order has to agree.
|
|
@@ -4094,8 +4106,8 @@ const ModalRoot = React.forwardRef(function ModalRoot({ active, isActive, onClos
|
|
|
4094
4106
|
first.focus();
|
|
4095
4107
|
}
|
|
4096
4108
|
};
|
|
4097
|
-
|
|
4098
|
-
return () =>
|
|
4109
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
4110
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
4099
4111
|
}, [isModalActive, closeOnEscape, onClose]);
|
|
4100
4112
|
// Move focus into the modal on open, restore it on close
|
|
4101
4113
|
React.useEffect(() => {
|
|
@@ -5375,17 +5387,18 @@ const Loading = ({ active = false, isFullPage = false, size, color, canCancel =
|
|
|
5375
5387
|
onCancel();
|
|
5376
5388
|
}
|
|
5377
5389
|
};
|
|
5378
|
-
// Handle escape key
|
|
5390
|
+
// Handle escape key. On `window`, as in `Modal`, so a popup on the page has
|
|
5391
|
+
// had the key first, and an Escape it closed on doesn't cancel as well.
|
|
5379
5392
|
React.useEffect(() => {
|
|
5380
5393
|
if (!active || !canCancel || !onCancel)
|
|
5381
5394
|
return;
|
|
5382
5395
|
const handleKeyDown = (e) => {
|
|
5383
|
-
if (e.key === 'Escape') {
|
|
5396
|
+
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
5384
5397
|
onCancel();
|
|
5385
5398
|
}
|
|
5386
5399
|
};
|
|
5387
|
-
|
|
5388
|
-
return () =>
|
|
5400
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
5401
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
5389
5402
|
}, [active, canCancel, onCancel]);
|
|
5390
5403
|
// Prevent body scroll when full page loading is active. Ref-counted through
|
|
5391
5404
|
// the shared helper so an overlapping Modal/Dialog/Sidebar doesn't have its
|
|
@@ -7795,17 +7808,18 @@ const SidebarComponent = React.forwardRef(({ isOpen, onClose, position = 'left',
|
|
|
7795
7808
|
handleClose();
|
|
7796
7809
|
}
|
|
7797
7810
|
}, [overlayClose, handleClose]);
|
|
7798
|
-
// Handle escape key
|
|
7811
|
+
// Handle escape key. On `window`, as in `Modal`, so everything inside the
|
|
7812
|
+
// sidebar has had the key first, and an Escape it used is left to it.
|
|
7799
7813
|
React.useEffect(() => {
|
|
7800
7814
|
if (!isOpen || !escapeClose)
|
|
7801
7815
|
return;
|
|
7802
7816
|
const handleKeyDown = (e) => {
|
|
7803
|
-
if (e.key === 'Escape') {
|
|
7817
|
+
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
7804
7818
|
handleClose();
|
|
7805
7819
|
}
|
|
7806
7820
|
};
|
|
7807
|
-
|
|
7808
|
-
return () =>
|
|
7821
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
7822
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
7809
7823
|
}, [isOpen, escapeClose, handleClose]);
|
|
7810
7824
|
// Prevent body scroll when sidebar is open. Ref-counted through the shared
|
|
7811
7825
|
// helper so an overlapping Modal/Dialog/Loading doesn't unlock underneath.
|
|
@@ -8060,17 +8074,19 @@ const Toast = React.forwardRef(({ message, type = 'default', actionType, positio
|
|
|
8060
8074
|
document.removeEventListener('click', handleDocumentClick);
|
|
8061
8075
|
};
|
|
8062
8076
|
}, [dismissible, isVisible, handleClose]);
|
|
8077
|
+
// On `window`, as in `Modal`, so a popup on the page has had the key
|
|
8078
|
+
// first, and an Escape it closed on doesn't dismiss the toast as well.
|
|
8063
8079
|
React.useEffect(() => {
|
|
8064
|
-
if (!cancelable)
|
|
8080
|
+
if (!cancelable || !isVisible)
|
|
8065
8081
|
return undefined;
|
|
8066
8082
|
const handleKeyDown = (e) => {
|
|
8067
|
-
if (e.key === 'Escape') {
|
|
8083
|
+
if (e.key === 'Escape' && !e.defaultPrevented) {
|
|
8068
8084
|
handleClose();
|
|
8069
8085
|
}
|
|
8070
8086
|
};
|
|
8071
|
-
|
|
8072
|
-
return () =>
|
|
8073
|
-
}, [cancelable, handleClose]);
|
|
8087
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
8088
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
8089
|
+
}, [cancelable, isVisible, handleClose]);
|
|
8074
8090
|
const containerClasses = usePrefixedClassNames('toast-container', {
|
|
8075
8091
|
[`is-${position}`]: true,
|
|
8076
8092
|
});
|
|
@@ -12630,6 +12646,18 @@ Rate.displayName = 'Rate';
|
|
|
12630
12646
|
// it's handed while that content is on screen because of the user's typing.
|
|
12631
12647
|
const typedNoMatches = [{ id: 'empty' }];
|
|
12632
12648
|
const nothingToAnnounce = [];
|
|
12649
|
+
// A press on an option would take focus from the field, since an option
|
|
12650
|
+
// can't hold it, and nothing would put it back. Cancelling the press keeps
|
|
12651
|
+
// it there, as the ARIA Authoring Practices' combobox does, and the click
|
|
12652
|
+
// still picks the option.
|
|
12653
|
+
const keepFocus = (e) => e.preventDefault();
|
|
12654
|
+
// An IME that's composing owns every key: Enter confirms what it composed,
|
|
12655
|
+
// and the arrows move through its candidates. Safari can say so with
|
|
12656
|
+
// `keyCode` 229 rather than `isComposing`.
|
|
12657
|
+
const isComposing = (e) => e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
|
|
12658
|
+
// Whether an option can be picked. The arrows still reach a disabled one, so
|
|
12659
|
+
// a screen reader user finds it.
|
|
12660
|
+
const isEnabled = (item) => typeof item === 'string' || !item.disabled;
|
|
12633
12661
|
/**
|
|
12634
12662
|
* The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
|
|
12635
12663
|
*
|
|
@@ -12655,6 +12683,11 @@ const nothingToAnnounce = [];
|
|
|
12655
12683
|
* />
|
|
12656
12684
|
*/
|
|
12657
12685
|
const Autocomplete = React.forwardRef(({ data = [], value: controlledValue, placeholder, field = 'label', clearable = false, openOnFocus = false, keepFirst = false, keepOpen = false, selectOnClickOutside = false, maxHeight = 200, loading = false, disabled = false, checkInfiniteScroll = false, infiniteScrollDistance = 50, color, size, onInput, onSelect, onActiveChange, onInfiniteScroll, itemTemplate, header, footer, empty, name, form, required, id,
|
|
12686
|
+
// Deprecated no-ops, taken here so they stay off the wrapper.
|
|
12687
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op
|
|
12688
|
+
selected,
|
|
12689
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- deprecated no-op
|
|
12690
|
+
dropdown,
|
|
12658
12691
|
// Field props
|
|
12659
12692
|
label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName, className, ...props }, ref) => {
|
|
12660
12693
|
const insideField = useInsideField();
|
|
@@ -12784,13 +12817,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12784
12817
|
}, [keepFirst, isActive, filteredData.length, inputValue]);
|
|
12785
12818
|
// Handle keyboard navigation
|
|
12786
12819
|
const handleKeyDown = React.useCallback((e) => {
|
|
12787
|
-
if (disabled)
|
|
12820
|
+
if (disabled || isComposing(e))
|
|
12788
12821
|
return;
|
|
12789
12822
|
switch (e.key) {
|
|
12790
12823
|
case 'ArrowDown':
|
|
12791
12824
|
e.preventDefault();
|
|
12792
12825
|
if (!isActive) {
|
|
12826
|
+
// A closed list opens on its first enabled option, as the ARIA
|
|
12827
|
+
// Authoring Practices' combobox does. Alt+Down Arrow opens it
|
|
12828
|
+
// without a highlight.
|
|
12793
12829
|
setIsActive(true);
|
|
12830
|
+
setHighlightedIndex(e.altKey ? -1 : filteredData.map(isEnabled).indexOf(true));
|
|
12794
12831
|
}
|
|
12795
12832
|
else {
|
|
12796
12833
|
setHighlightedIndex(prev => prev < filteredData.length - 1 ? prev + 1 : prev);
|
|
@@ -12798,27 +12835,56 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12798
12835
|
break;
|
|
12799
12836
|
case 'ArrowUp':
|
|
12800
12837
|
e.preventDefault();
|
|
12801
|
-
|
|
12838
|
+
if (!isActive) {
|
|
12839
|
+
// And Up Arrow opens it on its last enabled option.
|
|
12840
|
+
setIsActive(true);
|
|
12841
|
+
setHighlightedIndex(filteredData.map(isEnabled).lastIndexOf(true));
|
|
12842
|
+
}
|
|
12843
|
+
else {
|
|
12844
|
+
setHighlightedIndex(prev => (prev > 0 ? prev - 1 : prev));
|
|
12845
|
+
}
|
|
12802
12846
|
break;
|
|
12803
12847
|
case 'Enter':
|
|
12804
|
-
|
|
12805
|
-
|
|
12848
|
+
// With the list open on a highlighted option it can pick, Enter
|
|
12849
|
+
// is that option's. Otherwise it closes the list and goes on, so
|
|
12850
|
+
// a form around the field submits, as it would from any text
|
|
12851
|
+
// field.
|
|
12852
|
+
if (isActive &&
|
|
12853
|
+
highlightedIndex >= 0 &&
|
|
12854
|
+
filteredData[highlightedIndex] &&
|
|
12855
|
+
isEnabled(filteredData[highlightedIndex])) {
|
|
12856
|
+
e.preventDefault();
|
|
12806
12857
|
handleSelect(filteredData[highlightedIndex]);
|
|
12807
12858
|
}
|
|
12859
|
+
else {
|
|
12860
|
+
setIsActive(false);
|
|
12861
|
+
}
|
|
12808
12862
|
break;
|
|
12809
12863
|
case 'Escape':
|
|
12810
|
-
|
|
12864
|
+
// Only closing the dropdown claims the key. With it closed, the
|
|
12865
|
+
// key goes on to a Modal around the field.
|
|
12866
|
+
if (menuOpen)
|
|
12867
|
+
e.preventDefault();
|
|
12811
12868
|
setIsActive(false);
|
|
12812
12869
|
setHighlightedIndex(-1);
|
|
12813
12870
|
break;
|
|
12814
12871
|
case 'Tab':
|
|
12815
|
-
if (
|
|
12872
|
+
if (isActive &&
|
|
12873
|
+
highlightedIndex >= 0 &&
|
|
12874
|
+
filteredData[highlightedIndex]) {
|
|
12816
12875
|
handleSelect(filteredData[highlightedIndex]);
|
|
12817
12876
|
}
|
|
12818
12877
|
setIsActive(false);
|
|
12819
12878
|
break;
|
|
12820
12879
|
}
|
|
12821
|
-
}, [
|
|
12880
|
+
}, [
|
|
12881
|
+
disabled,
|
|
12882
|
+
isActive,
|
|
12883
|
+
menuOpen,
|
|
12884
|
+
highlightedIndex,
|
|
12885
|
+
filteredData,
|
|
12886
|
+
handleSelect,
|
|
12887
|
+
]);
|
|
12822
12888
|
// Handle dropdown scroll for infinite scroll
|
|
12823
12889
|
const handleDropdownScroll = React.useCallback(() => {
|
|
12824
12890
|
if (!checkInfiniteScroll || !dropdownRef.current)
|
|
@@ -12918,17 +12984,17 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
12918
12984
|
return null;
|
|
12919
12985
|
return spokenText(node);
|
|
12920
12986
|
}, []);
|
|
12921
|
-
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsxRuntime.jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => {
|
|
12987
|
+
const autocompleteElement = (jsxRuntime.jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxRuntime.jsxs("div", { className: controlClasses, children: [jsxRuntime.jsx("input", { ref: combinedRef, type: "text", className: inputClasses, id: controlId, value: inputValue, placeholder: placeholder, disabled: disabled, name: name, form: form, required: required, onChange: handleInputChange, onFocus: handleFocus, onKeyDown: handleKeyDown, role: "combobox", "aria-expanded": listOpen, "aria-controls": listOpen ? listboxId : undefined, "aria-activedescendant": activeOptionId, "aria-haspopup": "listbox", "aria-autocomplete": "list", autoComplete: "off" }), showClear && (jsxRuntime.jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "16", height: "16", "aria-hidden": "true", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsxRuntime.jsx("span", { className: iconRightClass, children: jsxRuntime.jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsxRuntime.jsx("div", { className: dropdownMenuClasses, children: jsxRuntime.jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsxRuntime.jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsxRuntime.jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', onMouseDown: keepFocus, children: filteredData.map((item, index) => {
|
|
12922
12988
|
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
12923
12989
|
const isHighlighted = index === highlightedIndex;
|
|
12924
12990
|
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
12925
12991
|
'is-active': isHighlighted,
|
|
12926
12992
|
'is-disabled': isDisabled,
|
|
12927
12993
|
});
|
|
12928
|
-
return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted, "aria-disabled": isDisabled, children: itemTemplate
|
|
12994
|
+
return (jsxRuntime.jsx("a", { id: `${listboxId}-${index}`, "data-index": index, className: itemClasses, onClick: () => !isDisabled && handleSelect(item), onMouseEnter: () => !isDisabled && setHighlightedIndex(index), role: "option", "aria-selected": isHighlighted && !isDisabled, "aria-disabled": isDisabled, children: itemTemplate
|
|
12929
12995
|
? itemTemplate(item)
|
|
12930
12996
|
: getDisplayValue(item) }, index));
|
|
12931
|
-
}) })) : (jsxRuntime.jsx("div", { ref: emptyRef, className: emptyItemClasses, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsxRuntime.jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
|
|
12997
|
+
}) })) : (jsxRuntime.jsx("div", { ref: emptyRef, className: emptyItemClasses, onMouseDown: keepFocus, children: empty })), footer && jsxRuntime.jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsxRuntime.jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
|
|
12932
12998
|
if (!insideField) {
|
|
12933
12999
|
return (jsxRuntime.jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
|
|
12934
13000
|
}
|
|
@@ -13192,6 +13258,10 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
13192
13258
|
}
|
|
13193
13259
|
break;
|
|
13194
13260
|
case 'Escape':
|
|
13261
|
+
// Closing the suggestions claims the key, so a Modal around the
|
|
13262
|
+
// field stays open. With none showing, the key goes on to it.
|
|
13263
|
+
if (isActive && filteredData.length > 0)
|
|
13264
|
+
e.preventDefault();
|
|
13195
13265
|
setIsActive(false);
|
|
13196
13266
|
setHighlightedIndex(-1);
|
|
13197
13267
|
break;
|
|
@@ -15053,13 +15123,20 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
15053
15123
|
return () => document.removeEventListener('pointerdown', handler);
|
|
15054
15124
|
}, [isOpen, closeOnClickOutside, anchorRef, onClose]);
|
|
15055
15125
|
// Escape closes it, and so does Alt+ArrowUp, the combobox's key for it,
|
|
15056
|
-
// which the calendar and the time wheels inside leave to it.
|
|
15126
|
+
// which the calendar and the time wheels inside leave to it. Escape closes
|
|
15127
|
+
// it from anywhere, but the key is only the picker's when it was pressed in
|
|
15128
|
+
// the panel or the field: then it's marked used, so a Modal or Sidebar
|
|
15129
|
+
// around the picker stays open. From anywhere else, such as a dialog opened
|
|
15130
|
+
// over the picker, the key goes on to whatever has focus.
|
|
15057
15131
|
React.useEffect(() => {
|
|
15058
15132
|
if (!isOpen || !closeOnEscape || !isBrowser)
|
|
15059
15133
|
return undefined;
|
|
15060
15134
|
const handler = (e) => {
|
|
15061
15135
|
if (e.key === 'Escape') {
|
|
15062
|
-
e.
|
|
15136
|
+
if (isEventInside(e, panelRef.current) ||
|
|
15137
|
+
isEventInside(e, anchorRef.current)) {
|
|
15138
|
+
e.preventDefault();
|
|
15139
|
+
}
|
|
15063
15140
|
onClose();
|
|
15064
15141
|
}
|
|
15065
15142
|
else if (e.altKey && e.key === 'ArrowUp') {
|
|
@@ -15070,7 +15147,7 @@ const PickerPopover = ({ isOpen, onClose, anchorRef, children, position = 'botto
|
|
|
15070
15147
|
};
|
|
15071
15148
|
document.addEventListener('keydown', handler);
|
|
15072
15149
|
return () => document.removeEventListener('keydown', handler);
|
|
15073
|
-
}, [isOpen, closeOnEscape, onClose]);
|
|
15150
|
+
}, [isOpen, closeOnEscape, anchorRef, onClose]);
|
|
15074
15151
|
useFocusTrap(panelRef, {
|
|
15075
15152
|
active: isOpen && trapFocus,
|
|
15076
15153
|
initialFocusRef,
|