@allxsmith/bestax-bulma 5.33.0 → 5.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/form/Autocomplete.js +59 -14
- package/dist/esm/form/Autocomplete.js.map +1 -1
- package/dist/esm/helpers/statusRegion.js +19 -12
- package/dist/esm/helpers/statusRegion.js.map +1 -1
- package/dist/index.cjs +77 -26
- package/dist/index.cjs.map +1 -1
- package/dist/types/form/Autocomplete.d.ts +18 -3
- package/dist/types/helpers/statusRegion.d.ts +4 -4
- package/dist/types-cjs/form/Autocomplete.d.ts +18 -3
- package/dist/types-cjs/helpers/statusRegion.d.ts +4 -4
- package/package.json +1 -1
|
@@ -8,7 +8,12 @@ import { useInsideField } from './FormContext.js';
|
|
|
8
8
|
import { Field } from './Field.js';
|
|
9
9
|
import { useAutoLabelId } from './useAutoLabelId.js';
|
|
10
10
|
import { useControlLoading } from './controlLoading.js';
|
|
11
|
+
import { spokenText, StatusRegion } from '../helpers/statusRegion.js';
|
|
11
12
|
|
|
13
|
+
// What the status region announces: the `empty` content, as the one item
|
|
14
|
+
// it's handed while that content is on screen because of the user's typing.
|
|
15
|
+
const typedNoMatches = [{ id: 'empty' }];
|
|
16
|
+
const nothingToAnnounce = [];
|
|
12
17
|
/**
|
|
13
18
|
* The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.
|
|
14
19
|
*
|
|
@@ -53,6 +58,13 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
53
58
|
const dropdownRef = useRef(null);
|
|
54
59
|
const [internalValue, setInternalValue] = useState('');
|
|
55
60
|
const [isActive, setIsActive] = useState(false);
|
|
61
|
+
// Whether the user has typed since the list opened, since only typing
|
|
62
|
+
// gets the `empty` content announced. Closing the list resets it (during
|
|
63
|
+
// render, React's pattern for adjusting state when other state changes),
|
|
64
|
+
// so opening the list again without typing stays quiet.
|
|
65
|
+
const [typed, setTyped] = useState(false);
|
|
66
|
+
if (typed && !isActive)
|
|
67
|
+
setTyped(false);
|
|
56
68
|
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
57
69
|
const listboxId = `${useId()}-listbox`;
|
|
58
70
|
// Use controlled or internal value
|
|
@@ -71,6 +83,11 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
71
83
|
const displayValue = getDisplayValue(item);
|
|
72
84
|
return displayValue.toLowerCase().includes(inputValue.toLowerCase());
|
|
73
85
|
});
|
|
86
|
+
// The dropdown shows the options or, when nothing matches, the `empty`
|
|
87
|
+
// content. The listbox holds only options, so it's on screen only while
|
|
88
|
+
// there are some.
|
|
89
|
+
const menuOpen = isActive && (filteredData.length > 0 || !!empty);
|
|
90
|
+
const listOpen = isActive && filteredData.length > 0;
|
|
74
91
|
// Handle input change
|
|
75
92
|
const handleInputChange = useCallback((e) => {
|
|
76
93
|
const newValue = e.target.value;
|
|
@@ -78,6 +95,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
78
95
|
setInternalValue(newValue);
|
|
79
96
|
}
|
|
80
97
|
onInput?.(newValue);
|
|
98
|
+
setTyped(true);
|
|
81
99
|
if (!isActive && newValue) {
|
|
82
100
|
setIsActive(true);
|
|
83
101
|
}
|
|
@@ -228,7 +246,7 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
228
246
|
});
|
|
229
247
|
const controlClasses = usePrefixedClassNames('control', 'is-expanded', 'has-icons-right');
|
|
230
248
|
const dropdownMenuClasses = usePrefixedClassNames('dropdown-menu', {
|
|
231
|
-
'is-active':
|
|
249
|
+
'is-active': menuOpen,
|
|
232
250
|
});
|
|
233
251
|
const dropdownContentClass = usePrefixedClassNames('dropdown-content');
|
|
234
252
|
const dropdownHeaderClass = usePrefixedClassNames('dropdown-header');
|
|
@@ -250,24 +268,51 @@ label, labelSize, labelProps, horizontal, message, messageColor, fieldClassName,
|
|
|
250
268
|
// The listbox takes its name from the label that names the combobox, the
|
|
251
269
|
// one this renders or a surrounding Field's, so the two say the same
|
|
252
270
|
// thing. When no label names the combobox, or the one that does has no
|
|
253
|
-
// id to point at, the list keeps a fallback name.
|
|
254
|
-
|
|
271
|
+
// id to point at, the list keeps a fallback name. While the dropdown
|
|
272
|
+
// shows only the `empty` content there's no listbox, so the combobox is
|
|
273
|
+
// collapsed and points at nothing.
|
|
255
274
|
const activeOptionId = listOpen &&
|
|
256
275
|
highlightedIndex >= 0 &&
|
|
257
276
|
highlightedIndex < filteredData.length
|
|
258
277
|
? `${listboxId}-${highlightedIndex}`
|
|
259
278
|
: undefined;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
279
|
+
// A polite status region, in the page from the start so it's there
|
|
280
|
+
// before anything is written into it, announces the `empty` content
|
|
281
|
+
// when the user's typing leaves nothing that matches what's in the
|
|
282
|
+
// field. It follows what's on screen, so results that come in after the
|
|
283
|
+
// typing count too. It's said once each time that happens, not again on
|
|
284
|
+
// each keystroke while nothing matches. An empty field isn't a search,
|
|
285
|
+
// so it stays quiet then, even with no data. While `loading`, or a
|
|
286
|
+
// loading Control around it, says results are on their way, it waits,
|
|
287
|
+
// and it's said once loading ends with nothing matching.
|
|
288
|
+
const emptyItems = typed &&
|
|
289
|
+
menuOpen &&
|
|
290
|
+
!listOpen &&
|
|
291
|
+
!!inputValue &&
|
|
292
|
+
!loading &&
|
|
293
|
+
!controlLoading
|
|
294
|
+
? typedNoMatches
|
|
295
|
+
: nothingToAnnounce;
|
|
296
|
+
// The region reads the text on screen, whatever `empty` renders.
|
|
297
|
+
const emptyRef = useRef(null);
|
|
298
|
+
const describeEmpty = useCallback(() => {
|
|
299
|
+
const node = emptyRef.current;
|
|
300
|
+
/* istanbul ignore if: it's only announced while it's on screen */
|
|
301
|
+
if (!node)
|
|
302
|
+
return null;
|
|
303
|
+
return spokenText(node);
|
|
304
|
+
}, []);
|
|
305
|
+
const autocompleteElement = (jsxs("div", { ref: containerRef, className: combinedClasses, ...rest, children: [jsxs("div", { className: controlClasses, children: [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 && (jsx("button", { type: "button", className: iconRightClickableClass, onClick: handleClear, "aria-label": "Clear", children: 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: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })), loading && (jsx("span", { className: iconRightClass, children: jsx("span", { className: loaderClass }) }))] }), menuOpen && (jsx("div", { className: dropdownMenuClasses, children: jsxs("div", { ref: dropdownRef, className: dropdownContentClass, style: { maxHeight: `${maxHeight}px`, overflowY: 'auto' }, onScroll: handleDropdownScroll, children: [header && jsx("div", { className: dropdownHeaderClass, children: header }), listOpen ? (jsx("div", { id: listboxId, role: "listbox", "aria-labelledby": listLabelId, "aria-label": listLabelId ? undefined : 'Suggestions', children: filteredData.map((item, index) => {
|
|
306
|
+
const isDisabled = typeof item !== 'string' && item.disabled;
|
|
307
|
+
const isHighlighted = index === highlightedIndex;
|
|
308
|
+
const itemClasses = prefixedClassNames(classPrefix, 'dropdown-item', {
|
|
309
|
+
'is-active': isHighlighted,
|
|
310
|
+
'is-disabled': isDisabled,
|
|
311
|
+
});
|
|
312
|
+
return (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
|
|
313
|
+
? itemTemplate(item)
|
|
314
|
+
: getDisplayValue(item) }, index));
|
|
315
|
+
}) })) : (jsx("div", { ref: emptyRef, className: emptyItemClasses, children: empty })), footer && jsx("div", { className: dropdownFooterClass, children: footer })] }) })), jsx(StatusRegion, { items: emptyItems, describe: describeEmpty })] }));
|
|
271
316
|
if (!insideField) {
|
|
272
317
|
return (jsxs(Field, { label: label, labelSize: labelSize, labelProps: fieldLabelProps, horizontal: horizontal, className: fieldClassName, children: [autocompleteElement, messageEl] }));
|
|
273
318
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.js","sources":["../../../src/form/Autocomplete.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 } from '../helpers/Config';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { useInsideField } from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\nimport { useControlLoading } from './controlLoading';\n\n/**\n * An item in the Autocomplete dropdown list.\n */\nexport interface AutocompleteItem {\n /** The value used for filtering and selection. */\n value: string;\n /** Display label (optional). */\n label?: string;\n /** Whether the item is disabled and unselectable. */\n disabled?: boolean;\n [key: string]: unknown;\n}\n\n/**\n * Props for the Autocomplete component.\n * @extraProp {string} [className] - Additional CSS classes.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the input element.\n */\nexport interface AutocompleteProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect' | 'onInput'>,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label, associated with the text input via `htmlFor`: through your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose label names the input instead when it points at it. The `Field` points its label there itself when the Autocomplete has no `id` and no inner `Field` sits between them (and the `Field` is not `grouped` or `hasAddons`); a label you wire by hand with `labelProps.htmlFor` points at the input with that `id`. 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 (the input then gets no generated id). 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 options data to display (required). */\n data: AutocompleteItem[] | string[];\n /** The current input value (controlled). */\n value?: string;\n /** The selected item (controlled). */\n selected?: AutocompleteItem | string | null;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** Object property to use as the display field. */\n field?: string;\n /**\n * Whether to show a clear button while the input holds a value.\n * It hides while the input is disabled, and gives way to a loading\n * spinner at the same right edge: the one `loading` draws, or that of a\n * `Control` with `isLoading` around the Autocomplete.\n */\n clearable?: boolean;\n /** Open dropdown when input is focused. */\n openOnFocus?: boolean;\n /** Keep first option highlighted. */\n keepFirst?: boolean;\n /** Keep dropdown open after selection. */\n keepOpen?: boolean;\n /** Select highlighted item on click outside. */\n selectOnClickOutside?: boolean;\n /** Maximum dropdown height in pixels. */\n maxHeight?: number;\n /** Render as dropdown style. */\n dropdown?: boolean;\n /**\n * Show 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 /** Whether the input is disabled. */\n disabled?: boolean;\n /** Enables infinite scroll detection in the dropdown. */\n checkInfiniteScroll?: boolean;\n /** Distance in pixels from the bottom to trigger `onInfiniteScroll`. */\n infiniteScrollDistance?: number;\n /** Input color variant. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Size variant. */\n size?: 'small' | 'medium' | 'large';\n /** Form field name. Forwarded to the inner `<input>`. */\n name?: string;\n /** Optional id of the form the input belongs to. */\n form?: string;\n /** Marks the field as required for native HTML form validation. */\n required?: boolean;\n /** Callback when input value changes. */\n onInput?: (value: string) => void;\n /** Callback when item is selected. */\n onSelect?: (item: AutocompleteItem | string | null) => void;\n /** Callback when dropdown active state changes. */\n onActiveChange?: (active: boolean) => void;\n /** Callback when scrolled to bottom (infinite scroll). */\n onInfiniteScroll?: () => void;\n /** Custom render for items. */\n itemTemplate?: (item: AutocompleteItem | string) => React.ReactNode;\n /** Custom header in dropdown. */\n header?: React.ReactNode;\n /** Custom footer in dropdown. */\n footer?: React.ReactNode;\n /** Content to show when no results. */\n empty?: React.ReactNode;\n}\n\n/**\n * The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.\n *\n * @function\n * @param {AutocompleteProps} props - Props for the Autocomplete component.\n * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the input element.\n * @returns {JSX.Element} The rendered autocomplete component.\n *\n * @example\n * // Basic usage with string array\n * <Autocomplete\n * data={['Apple', 'Banana', 'Cherry']}\n * placeholder=\"Search fruit...\"\n * onSelect={(item) => console.log(item)}\n * />\n *\n * @example\n * // With object data\n * <Autocomplete\n * data={[{ value: '1', label: 'Option 1' }, { value: '2', label: 'Option 2' }]}\n * field=\"label\"\n * onSelect={(item) => console.log(item)}\n * />\n */\nexport const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(\n (\n {\n data = [],\n value: controlledValue,\n placeholder,\n field = 'label',\n clearable = false,\n openOnFocus = false,\n keepFirst = false,\n keepOpen = false,\n selectOnClickOutside = false,\n maxHeight = 200,\n loading = false,\n disabled = false,\n checkInfiniteScroll = false,\n infiniteScrollDistance = 50,\n color,\n size,\n onInput,\n onSelect,\n onActiveChange,\n onInfiniteScroll,\n itemTemplate,\n header,\n footer,\n empty,\n name,\n form,\n required,\n id,\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n className,\n ...props\n },\n ref\n ) => {\n const insideField = useInsideField();\n // The suggestion list points `aria-labelledby` at the label that names\n // the input, so it needs that label's id.\n const {\n controlId,\n fieldLabelProps,\n labelId: listLabelId,\n } = useAutoLabelId({\n label,\n id,\n labelProps,\n rendersLabel: !insideField,\n needsLabelId: true,\n });\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const { classPrefix } = useConfig();\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const [internalValue, setInternalValue] = useState('');\n const [isActive, setIsActive] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const listboxId = `${useId()}-listbox`;\n\n // Use controlled or internal value\n const inputValue =\n controlledValue !== undefined ? controlledValue : internalValue;\n\n // Get display value from item\n const getDisplayValue = (item: AutocompleteItem | string): string => {\n if (typeof item === 'string') return item;\n const fieldValue = item[field];\n if (typeof fieldValue === 'string') return fieldValue;\n return item.value || '';\n };\n\n // Filter data based on input\n const filteredData = data.filter(item => {\n const displayValue = getDisplayValue(item);\n return displayValue.toLowerCase().includes(inputValue.toLowerCase());\n });\n\n // Handle input change\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n onInput?.(newValue);\n if (!isActive && newValue) {\n setIsActive(true);\n }\n },\n [controlledValue, isActive, onInput]\n );\n\n // Handle item selection\n const handleSelect = useCallback(\n (item: AutocompleteItem | string) => {\n if (typeof item !== 'string' && item.disabled) return;\n\n const displayValue = getDisplayValue(item);\n if (controlledValue === undefined) {\n setInternalValue(displayValue);\n }\n onInput?.(displayValue);\n onSelect?.(item);\n\n if (!keepOpen) {\n setIsActive(false);\n }\n setHighlightedIndex(-1);\n },\n [controlledValue, field, keepOpen, onInput, onSelect]\n );\n\n // Handle clear\n const handleClear = useCallback(() => {\n if (controlledValue === undefined) {\n setInternalValue('');\n }\n onInput?.('');\n onSelect?.(null);\n inputRef.current?.focus();\n }, [controlledValue, onInput, onSelect]);\n\n // Handle focus\n const handleFocus = useCallback(() => {\n if (openOnFocus && !disabled) {\n setIsActive(true);\n }\n }, [openOnFocus, disabled]);\n\n // Handle blur (click outside)\n useEffect(() => {\n if (!isActive) return undefined;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (!containerRef.current) return;\n\n // Use composedPath() to correctly detect clicks inside Shadow DOM\n const path = e.composedPath();\n const isInside = path.includes(containerRef.current);\n\n if (!isInside) {\n if (selectOnClickOutside && highlightedIndex >= 0) {\n handleSelect(filteredData[highlightedIndex]);\n }\n setIsActive(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () =>\n document.removeEventListener('mousedown', handleClickOutside);\n }, [\n isActive,\n selectOnClickOutside,\n highlightedIndex,\n filteredData,\n handleSelect,\n ]);\n\n // Notify active state change\n useEffect(() => {\n onActiveChange?.(isActive);\n }, [isActive, onActiveChange]);\n\n // Keep first highlighted\n useEffect(() => {\n if (keepFirst && isActive && filteredData.length > 0) {\n setHighlightedIndex(0);\n }\n }, [keepFirst, isActive, filteredData.length, inputValue]);\n\n // Handle keyboard navigation\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!isActive) {\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 'Enter':\n e.preventDefault();\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n handleSelect(filteredData[highlightedIndex]);\n }\n break;\n case 'Escape':\n e.preventDefault();\n setIsActive(false);\n setHighlightedIndex(-1);\n break;\n case 'Tab':\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n handleSelect(filteredData[highlightedIndex]);\n }\n setIsActive(false);\n break;\n }\n },\n [disabled, isActive, highlightedIndex, filteredData, handleSelect]\n );\n\n // Handle dropdown scroll for infinite scroll\n const handleDropdownScroll = useCallback(() => {\n if (!checkInfiniteScroll || !dropdownRef.current) return;\n\n const { scrollTop, scrollHeight, clientHeight } = dropdownRef.current;\n if (scrollHeight - scrollTop - clientHeight <= infiniteScrollDistance) {\n onInfiniteScroll?.();\n }\n }, [checkInfiniteScroll, infiniteScrollDistance, onInfiniteScroll]);\n\n // Scroll highlighted item into view\n useEffect(() => {\n if (highlightedIndex >= 0 && dropdownRef.current) {\n const highlightedEl = dropdownRef.current.querySelector(\n `[data-index=\"${highlightedIndex}\"]`\n );\n if (\n highlightedEl &&\n typeof highlightedEl.scrollIntoView === 'function'\n ) {\n highlightedEl.scrollIntoView({ block: 'nearest' });\n }\n }\n }, [highlightedIndex]);\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 // Generate classes\n const autocompleteClasses = usePrefixedClassNames('autocomplete', {\n 'is-active': isActive,\n [`is-${size}`]: !!size,\n });\n\n const inputClasses = usePrefixedClassNames('input', {\n [`is-${color}`]: !!color,\n [`is-${size}`]: !!size,\n 'is-loading': loading,\n });\n\n const controlClasses = usePrefixedClassNames(\n 'control',\n 'is-expanded',\n 'has-icons-right'\n );\n\n const dropdownMenuClasses = usePrefixedClassNames('dropdown-menu', {\n 'is-active': isActive && (filteredData.length > 0 || !!empty),\n });\n\n const dropdownContentClass = usePrefixedClassNames('dropdown-content');\n const dropdownHeaderClass = usePrefixedClassNames('dropdown-header');\n const dropdownFooterClass = usePrefixedClassNames('dropdown-footer');\n const emptyItemClasses = usePrefixedClassNames(\n 'dropdown-item',\n 'has-text-grey'\n );\n\n const iconRightClickableClass = usePrefixedClassNames(\n 'icon',\n 'is-right',\n 'is-clickable'\n );\n const iconRightClass = usePrefixedClassNames('icon', 'is-right');\n const loaderClass = usePrefixedClassNames('loader', 'is-loading');\n\n const combinedClasses = classNames(\n autocompleteClasses,\n bulmaHelperClasses,\n className\n );\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n // The clear button gives way to a spinner at its spot: the `loading` one,\n // or that of a loading Control this sits in.\n const controlLoading = useControlLoading();\n const showClear =\n clearable && !!inputValue && !disabled && !loading && !controlLoading;\n\n // The combobox controls its listbox and points at the highlighted option.\n // The listbox takes its name from the label that names the combobox, the\n // one this renders or a surrounding Field's, so the two say the same\n // thing. When no label names the combobox, or the one that does has no\n // id to point at, the list keeps a fallback name.\n const listOpen = isActive && (filteredData.length > 0 || !!empty);\n const activeOptionId =\n listOpen &&\n highlightedIndex >= 0 &&\n highlightedIndex < filteredData.length\n ? `${listboxId}-${highlightedIndex}`\n : undefined;\n\n const autocompleteElement = (\n <div ref={containerRef} className={combinedClasses} {...rest}>\n <div className={controlClasses}>\n <input\n ref={combinedRef}\n type=\"text\"\n className={inputClasses}\n id={controlId}\n value={inputValue}\n placeholder={placeholder}\n disabled={disabled}\n name={name}\n form={form}\n required={required}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\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 autoComplete=\"off\"\n />\n {showClear && (\n <button\n type=\"button\"\n className={iconRightClickableClass}\n onClick={handleClear}\n aria-label=\"Clear\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n )}\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 className={dropdownContentClass}\n style={{ maxHeight: `${maxHeight}px`, overflowY: 'auto' }}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={listLabelId}\n aria-label={listLabelId ? undefined : 'Suggestions'}\n onScroll={handleDropdownScroll}\n >\n {header && <div className={dropdownHeaderClass}>{header}</div>}\n\n {filteredData.length > 0 ? (\n filteredData.map((item, index) => {\n const isDisabled = typeof item !== 'string' && item.disabled;\n const isHighlighted = index === highlightedIndex;\n\n const itemClasses = prefixedClassNames(\n classPrefix,\n 'dropdown-item',\n {\n 'is-active': isHighlighted,\n 'is-disabled': isDisabled,\n }\n );\n\n return (\n <a\n key={index}\n id={`${listboxId}-${index}`}\n data-index={index}\n className={itemClasses}\n onClick={() => !isDisabled && handleSelect(item)}\n onMouseEnter={() =>\n !isDisabled && setHighlightedIndex(index)\n }\n role=\"option\"\n aria-selected={isHighlighted}\n aria-disabled={isDisabled}\n >\n {itemTemplate\n ? itemTemplate(item)\n : getDisplayValue(item)}\n </a>\n );\n })\n ) : (\n <div className={emptyItemClasses}>{empty}</div>\n )}\n\n {footer && <div className={dropdownFooterClass}>{footer}</div>}\n </div>\n </div>\n )}\n </div>\n );\n\n if (!insideField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {autocompleteElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {autocompleteElement}\n {messageEl}\n </>\n );\n }\n);\n\nAutocomplete.displayName = 'Autocomplete';\n\nexport default Autocomplete;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Autocomplete.js","sources":["../../../src/form/Autocomplete.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 } from '../helpers/Config';\nimport { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';\nimport { useInsideField } from './FormContext';\nimport { Field } from './Field';\nimport { FormFieldProps } from './fieldProps';\nimport { useAutoLabelId } from './useAutoLabelId';\nimport { useControlLoading } from './controlLoading';\nimport { StatusRegion, spokenText } from '../helpers/statusRegion';\n\n/**\n * An item in the Autocomplete dropdown list.\n */\nexport interface AutocompleteItem {\n /** The value used for filtering and selection. */\n value: string;\n /** Display label (optional). */\n label?: string;\n /** Whether the item is disabled and unselectable. */\n disabled?: boolean;\n [key: string]: unknown;\n}\n\n// What the status region announces: the `empty` content, as the one item\n// it's handed while that content is on screen because of the user's typing.\nconst typedNoMatches = [{ id: 'empty' }];\nconst nothingToAnnounce: Array<{ id: string }> = [];\n\n/**\n * Props for the Autocomplete component.\n * @extraProp {string} [className] - Additional CSS classes.\n * @extraProp {React.Ref<HTMLInputElement>} [ref] - Ref forwarded to the input element.\n */\nexport interface AutocompleteProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect' | 'onInput'>,\n Omit<BulmaClassesProps, 'color'>,\n FormFieldProps {\n /** Field label, associated with the text input via `htmlFor`: through your `id` when provided, otherwise a generated one. Dropped inside an outer `Field`, whose label names the input instead when it points at it. The `Field` points its label there itself when the Autocomplete has no `id` and no inner `Field` sits between them (and the `Field` is not `grouped` or `hasAddons`); a label you wire by hand with `labelProps.htmlFor` points at the input with that `id`. 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 (the input then gets no generated id). 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 options data to display (required). */\n data: AutocompleteItem[] | string[];\n /** The current input value (controlled). */\n value?: string;\n /** The selected item (controlled). */\n selected?: AutocompleteItem | string | null;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** Object property to use as the display field. */\n field?: string;\n /**\n * Whether to show a clear button while the input holds a value.\n * It hides while the input is disabled, and gives way to a loading\n * spinner at the same right edge: the one `loading` draws, or that of a\n * `Control` with `isLoading` around the Autocomplete.\n */\n clearable?: boolean;\n /** Open dropdown when input is focused. */\n openOnFocus?: boolean;\n /** Keep first option highlighted. */\n keepFirst?: boolean;\n /** Keep dropdown open after selection. */\n keepOpen?: boolean;\n /** Select highlighted item on click outside. */\n selectOnClickOutside?: boolean;\n /** Maximum dropdown height in pixels. */\n maxHeight?: number;\n /** Render as dropdown style. */\n dropdown?: boolean;\n /**\n * Show 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 /** Whether the input is disabled. */\n disabled?: boolean;\n /** Enables infinite scroll detection in the dropdown. */\n checkInfiniteScroll?: boolean;\n /** Distance in pixels from the bottom to trigger `onInfiniteScroll`. */\n infiniteScrollDistance?: number;\n /** Input color variant. */\n color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';\n /** Size variant. */\n size?: 'small' | 'medium' | 'large';\n /** Form field name. Forwarded to the inner `<input>`. */\n name?: string;\n /** Optional id of the form the input belongs to. */\n form?: string;\n /** Marks the field as required for native HTML form validation. */\n required?: boolean;\n /** Callback when input value changes. */\n onInput?: (value: string) => void;\n /** Callback when item is selected. */\n onSelect?: (item: AutocompleteItem | string | null) => void;\n /** Callback when dropdown active state changes. */\n onActiveChange?: (active: boolean) => void;\n /** Callback when scrolled to bottom (infinite scroll). */\n onInfiniteScroll?: () => void;\n /** Custom render for items. */\n itemTemplate?: (item: AutocompleteItem | string) => React.ReactNode;\n /** Custom header at the top of the dropdown. It sits above the list of options rather than in it, so assistive technology doesn't count it as one. */\n header?: React.ReactNode;\n /** Custom footer at the bottom of the dropdown. It sits below the list of options rather than in it, so assistive technology doesn't count it as one. */\n footer?: React.ReactNode;\n /**\n * A message the dropdown shows in place of the options when nothing\n * matches. Keep it to text: `Tab` closes the list, so a keyboard user\n * can't reach a link or button in it. It sits beside the list of options\n * rather than in it, so the input's `aria-expanded` stays false while it\n * shows. When the user's typing leaves nothing that matches, a polite\n * status region announces its text: once each time the matches run out.\n * The text is read when they first run out, and isn't read again as it\n * changes while they stay out, so a message that names the query is heard\n * for that first query only. Keep it static.\n * Opening the list without typing, or emptying the field, announces\n * nothing. While `loading`, or a `Control` with `isLoading` around the\n * Autocomplete, is on, it waits, and it's announced once loading ends\n * with nothing matching. Without `empty`, the dropdown closes when nothing\n * matches and nothing is announced.\n */\n empty?: React.ReactNode;\n}\n\n/**\n * The `Autocomplete` component provides an input field with dropdown suggestions that filter based on user input.\n *\n * @function\n * @param {AutocompleteProps} props - Props for the Autocomplete component.\n * @param {React.Ref<HTMLInputElement>} ref - Forwarded ref to the input element.\n * @returns {JSX.Element} The rendered autocomplete component.\n *\n * @example\n * // Basic usage with string array\n * <Autocomplete\n * data={['Apple', 'Banana', 'Cherry']}\n * placeholder=\"Search fruit...\"\n * onSelect={(item) => console.log(item)}\n * />\n *\n * @example\n * // With object data\n * <Autocomplete\n * data={[{ value: '1', label: 'Option 1' }, { value: '2', label: 'Option 2' }]}\n * field=\"label\"\n * onSelect={(item) => console.log(item)}\n * />\n */\nexport const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(\n (\n {\n data = [],\n value: controlledValue,\n placeholder,\n field = 'label',\n clearable = false,\n openOnFocus = false,\n keepFirst = false,\n keepOpen = false,\n selectOnClickOutside = false,\n maxHeight = 200,\n loading = false,\n disabled = false,\n checkInfiniteScroll = false,\n infiniteScrollDistance = 50,\n color,\n size,\n onInput,\n onSelect,\n onActiveChange,\n onInfiniteScroll,\n itemTemplate,\n header,\n footer,\n empty,\n name,\n form,\n required,\n id,\n // Field props\n label,\n labelSize,\n labelProps,\n horizontal,\n message,\n messageColor,\n fieldClassName,\n className,\n ...props\n },\n ref\n ) => {\n const insideField = useInsideField();\n // The suggestion list points `aria-labelledby` at the label that names\n // the input, so it needs that label's id.\n const {\n controlId,\n fieldLabelProps,\n labelId: listLabelId,\n } = useAutoLabelId({\n label,\n id,\n labelProps,\n rendersLabel: !insideField,\n needsLabelId: true,\n });\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n const { classPrefix } = useConfig();\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n const [internalValue, setInternalValue] = useState('');\n const [isActive, setIsActive] = useState(false);\n // Whether the user has typed since the list opened, since only typing\n // gets the `empty` content announced. Closing the list resets it (during\n // render, React's pattern for adjusting state when other state changes),\n // so opening the list again without typing stays quiet.\n const [typed, setTyped] = useState(false);\n if (typed && !isActive) setTyped(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const listboxId = `${useId()}-listbox`;\n\n // Use controlled or internal value\n const inputValue =\n controlledValue !== undefined ? controlledValue : internalValue;\n\n // Get display value from item\n const getDisplayValue = (item: AutocompleteItem | string): string => {\n if (typeof item === 'string') return item;\n const fieldValue = item[field];\n if (typeof fieldValue === 'string') return fieldValue;\n return item.value || '';\n };\n\n // Filter data based on input\n const filteredData = data.filter(item => {\n const displayValue = getDisplayValue(item);\n return displayValue.toLowerCase().includes(inputValue.toLowerCase());\n });\n\n // The dropdown shows the options or, when nothing matches, the `empty`\n // content. The listbox holds only options, so it's on screen only while\n // there are some.\n const menuOpen = isActive && (filteredData.length > 0 || !!empty);\n const listOpen = isActive && filteredData.length > 0;\n\n // Handle input change\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = e.target.value;\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n onInput?.(newValue);\n setTyped(true);\n if (!isActive && newValue) {\n setIsActive(true);\n }\n },\n [controlledValue, isActive, onInput]\n );\n\n // Handle item selection\n const handleSelect = useCallback(\n (item: AutocompleteItem | string) => {\n if (typeof item !== 'string' && item.disabled) return;\n\n const displayValue = getDisplayValue(item);\n if (controlledValue === undefined) {\n setInternalValue(displayValue);\n }\n onInput?.(displayValue);\n onSelect?.(item);\n\n if (!keepOpen) {\n setIsActive(false);\n }\n setHighlightedIndex(-1);\n },\n [controlledValue, field, keepOpen, onInput, onSelect]\n );\n\n // Handle clear\n const handleClear = useCallback(() => {\n if (controlledValue === undefined) {\n setInternalValue('');\n }\n onInput?.('');\n onSelect?.(null);\n inputRef.current?.focus();\n }, [controlledValue, onInput, onSelect]);\n\n // Handle focus\n const handleFocus = useCallback(() => {\n if (openOnFocus && !disabled) {\n setIsActive(true);\n }\n }, [openOnFocus, disabled]);\n\n // Handle blur (click outside)\n useEffect(() => {\n if (!isActive) return undefined;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (!containerRef.current) return;\n\n // Use composedPath() to correctly detect clicks inside Shadow DOM\n const path = e.composedPath();\n const isInside = path.includes(containerRef.current);\n\n if (!isInside) {\n if (selectOnClickOutside && highlightedIndex >= 0) {\n handleSelect(filteredData[highlightedIndex]);\n }\n setIsActive(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () =>\n document.removeEventListener('mousedown', handleClickOutside);\n }, [\n isActive,\n selectOnClickOutside,\n highlightedIndex,\n filteredData,\n handleSelect,\n ]);\n\n // Notify active state change\n useEffect(() => {\n onActiveChange?.(isActive);\n }, [isActive, onActiveChange]);\n\n // Keep first highlighted\n useEffect(() => {\n if (keepFirst && isActive && filteredData.length > 0) {\n setHighlightedIndex(0);\n }\n }, [keepFirst, isActive, filteredData.length, inputValue]);\n\n // Handle keyboard navigation\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!isActive) {\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 'Enter':\n e.preventDefault();\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n handleSelect(filteredData[highlightedIndex]);\n }\n break;\n case 'Escape':\n e.preventDefault();\n setIsActive(false);\n setHighlightedIndex(-1);\n break;\n case 'Tab':\n if (highlightedIndex >= 0 && filteredData[highlightedIndex]) {\n handleSelect(filteredData[highlightedIndex]);\n }\n setIsActive(false);\n break;\n }\n },\n [disabled, isActive, highlightedIndex, filteredData, handleSelect]\n );\n\n // Handle dropdown scroll for infinite scroll\n const handleDropdownScroll = useCallback(() => {\n if (!checkInfiniteScroll || !dropdownRef.current) return;\n\n const { scrollTop, scrollHeight, clientHeight } = dropdownRef.current;\n if (scrollHeight - scrollTop - clientHeight <= infiniteScrollDistance) {\n onInfiniteScroll?.();\n }\n }, [checkInfiniteScroll, infiniteScrollDistance, onInfiniteScroll]);\n\n // Scroll highlighted item into view\n useEffect(() => {\n if (highlightedIndex >= 0 && dropdownRef.current) {\n const highlightedEl = dropdownRef.current.querySelector(\n `[data-index=\"${highlightedIndex}\"]`\n );\n if (\n highlightedEl &&\n typeof highlightedEl.scrollIntoView === 'function'\n ) {\n highlightedEl.scrollIntoView({ block: 'nearest' });\n }\n }\n }, [highlightedIndex]);\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 // Generate classes\n const autocompleteClasses = usePrefixedClassNames('autocomplete', {\n 'is-active': isActive,\n [`is-${size}`]: !!size,\n });\n\n const inputClasses = usePrefixedClassNames('input', {\n [`is-${color}`]: !!color,\n [`is-${size}`]: !!size,\n 'is-loading': loading,\n });\n\n const controlClasses = usePrefixedClassNames(\n 'control',\n 'is-expanded',\n 'has-icons-right'\n );\n\n const dropdownMenuClasses = usePrefixedClassNames('dropdown-menu', {\n 'is-active': menuOpen,\n });\n\n const dropdownContentClass = usePrefixedClassNames('dropdown-content');\n const dropdownHeaderClass = usePrefixedClassNames('dropdown-header');\n const dropdownFooterClass = usePrefixedClassNames('dropdown-footer');\n const emptyItemClasses = usePrefixedClassNames(\n 'dropdown-item',\n 'has-text-grey'\n );\n\n const iconRightClickableClass = usePrefixedClassNames(\n 'icon',\n 'is-right',\n 'is-clickable'\n );\n const iconRightClass = usePrefixedClassNames('icon', 'is-right');\n const loaderClass = usePrefixedClassNames('loader', 'is-loading');\n\n const combinedClasses = classNames(\n autocompleteClasses,\n bulmaHelperClasses,\n className\n );\n\n const helpClass = usePrefixedClassNames('help', {\n [`is-${messageColor}`]: !!messageColor,\n });\n const messageEl = message ? <p className={helpClass}>{message}</p> : null;\n\n // The clear button gives way to a spinner at its spot: the `loading` one,\n // or that of a loading Control this sits in.\n const controlLoading = useControlLoading();\n const showClear =\n clearable && !!inputValue && !disabled && !loading && !controlLoading;\n\n // The combobox controls its listbox and points at the highlighted option.\n // The listbox takes its name from the label that names the combobox, the\n // one this renders or a surrounding Field's, so the two say the same\n // thing. When no label names the combobox, or the one that does has no\n // id to point at, the list keeps a fallback name. While the dropdown\n // shows only the `empty` content there's no listbox, so the combobox is\n // collapsed and points at nothing.\n const activeOptionId =\n listOpen &&\n highlightedIndex >= 0 &&\n highlightedIndex < filteredData.length\n ? `${listboxId}-${highlightedIndex}`\n : undefined;\n\n // A polite status region, in the page from the start so it's there\n // before anything is written into it, announces the `empty` content\n // when the user's typing leaves nothing that matches what's in the\n // field. It follows what's on screen, so results that come in after the\n // typing count too. It's said once each time that happens, not again on\n // each keystroke while nothing matches. An empty field isn't a search,\n // so it stays quiet then, even with no data. While `loading`, or a\n // loading Control around it, says results are on their way, it waits,\n // and it's said once loading ends with nothing matching.\n const emptyItems =\n typed &&\n menuOpen &&\n !listOpen &&\n !!inputValue &&\n !loading &&\n !controlLoading\n ? typedNoMatches\n : nothingToAnnounce;\n // The region reads the text on screen, whatever `empty` renders.\n const emptyRef = useRef<HTMLDivElement>(null);\n const describeEmpty = useCallback(() => {\n const node = emptyRef.current;\n /* istanbul ignore if: it's only announced while it's on screen */\n if (!node) return null;\n return spokenText(node);\n }, []);\n\n const autocompleteElement = (\n <div ref={containerRef} className={combinedClasses} {...rest}>\n <div className={controlClasses}>\n <input\n ref={combinedRef}\n type=\"text\"\n className={inputClasses}\n id={controlId}\n value={inputValue}\n placeholder={placeholder}\n disabled={disabled}\n name={name}\n form={form}\n required={required}\n onChange={handleInputChange}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\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 autoComplete=\"off\"\n />\n {showClear && (\n <button\n type=\"button\"\n className={iconRightClickableClass}\n onClick={handleClear}\n aria-label=\"Clear\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n width=\"16\"\n height=\"16\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n )}\n {loading && (\n <span className={iconRightClass}>\n <span className={loaderClass} />\n </span>\n )}\n </div>\n\n {menuOpen && (\n <div className={dropdownMenuClasses}>\n <div\n ref={dropdownRef}\n className={dropdownContentClass}\n style={{ maxHeight: `${maxHeight}px`, overflowY: 'auto' }}\n onScroll={handleDropdownScroll}\n >\n {header && <div className={dropdownHeaderClass}>{header}</div>}\n\n {listOpen ? (\n <div\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={listLabelId}\n aria-label={listLabelId ? undefined : 'Suggestions'}\n >\n {filteredData.map((item, index) => {\n const isDisabled =\n typeof item !== 'string' && item.disabled;\n const isHighlighted = index === highlightedIndex;\n\n const itemClasses = prefixedClassNames(\n classPrefix,\n 'dropdown-item',\n {\n 'is-active': isHighlighted,\n 'is-disabled': isDisabled,\n }\n );\n\n return (\n <a\n key={index}\n id={`${listboxId}-${index}`}\n data-index={index}\n className={itemClasses}\n onClick={() => !isDisabled && handleSelect(item)}\n onMouseEnter={() =>\n !isDisabled && setHighlightedIndex(index)\n }\n role=\"option\"\n aria-selected={isHighlighted}\n aria-disabled={isDisabled}\n >\n {itemTemplate\n ? itemTemplate(item)\n : getDisplayValue(item)}\n </a>\n );\n })}\n </div>\n ) : (\n <div ref={emptyRef} className={emptyItemClasses}>\n {empty}\n </div>\n )}\n\n {footer && <div className={dropdownFooterClass}>{footer}</div>}\n </div>\n </div>\n )}\n\n <StatusRegion items={emptyItems} describe={describeEmpty} />\n </div>\n );\n\n if (!insideField) {\n return (\n <Field\n label={label}\n labelSize={labelSize}\n labelProps={fieldLabelProps}\n horizontal={horizontal}\n className={fieldClassName}\n >\n {autocompleteElement}\n {messageEl}\n </Field>\n );\n }\n\n return (\n <>\n {autocompleteElement}\n {messageEl}\n </>\n );\n }\n);\n\nAutocomplete.displayName = 'Autocomplete';\n\nexport default Autocomplete;\n"],"names":["_jsx","_jsxs","_Fragment"],"mappings":";;;;;;;;;;;;AAmCA;AACA;AACA,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;AACxC,MAAM,iBAAiB,GAA0B,EAAE;AAwGnD;;;;;;;;;;;;;;;;;;;;;;;AAuBG;AACI,MAAM,YAAY,GAAG,UAAU,CACpC,CACE,EACE,IAAI,GAAG,EAAE,EACT,KAAK,EAAE,eAAe,EACtB,WAAW,EACX,KAAK,GAAG,OAAO,EACf,SAAS,GAAG,KAAK,EACjB,WAAW,GAAG,KAAK,EACnB,SAAS,GAAG,KAAK,EACjB,QAAQ,GAAG,KAAK,EAChB,oBAAoB,GAAG,KAAK,EAC5B,SAAS,GAAG,GAAG,EACf,OAAO,GAAG,KAAK,EACf,QAAQ,GAAG,KAAK,EAChB,mBAAmB,GAAG,KAAK,EAC3B,sBAAsB,GAAG,EAAE,EAC3B,KAAK,EACL,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,MAAM,EACN,MAAM,EACN,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,EAAE;AACF;AACA,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,OAAO,EACP,YAAY,EACZ,cAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EACD,GAAG,KACD;AACF,IAAA,MAAM,WAAW,GAAG,cAAc,EAAE;;;IAGpC,MAAM,EACJ,SAAS,EACT,eAAe,EACf,OAAO,EAAE,WAAW,GACrB,GAAG,cAAc,CAAC;QACjB,KAAK;QACL,EAAE;QACF,UAAU;QACV,YAAY,EAAE,CAAC,WAAW;AAC1B,QAAA,YAAY,EAAE,IAAI;AACnB,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,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,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;IACtD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;;;;;IAK/C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IACzC,IAAI,KAAK,IAAI,CAAC,QAAQ;QAAE,QAAQ,CAAC,KAAK,CAAC;IACvC,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC;AAC5D,IAAA,MAAM,SAAS,GAAG,CAAA,EAAG,KAAK,EAAE,UAAU;;AAGtC,IAAA,MAAM,UAAU,GACd,eAAe,KAAK,SAAS,GAAG,eAAe,GAAG,aAAa;;AAGjE,IAAA,MAAM,eAAe,GAAG,CAAC,IAA+B,KAAY;QAClE,IAAI,OAAO,IAAI,KAAK,QAAQ;AAAE,YAAA,OAAO,IAAI;AACzC,QAAA,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC;QAC9B,IAAI,OAAO,UAAU,KAAK,QAAQ;AAAE,YAAA,OAAO,UAAU;AACrD,QAAA,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE;AACzB,IAAA,CAAC;;IAGD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAG;AACtC,QAAA,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC;AAC1C,QAAA,OAAO,YAAY,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;AACtE,IAAA,CAAC,CAAC;;;;AAKF,IAAA,MAAM,QAAQ,GAAG,QAAQ,KAAK,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;IACjE,MAAM,QAAQ,GAAG,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;;AAGpD,IAAA,MAAM,iBAAiB,GAAG,WAAW,CACnC,CAAC,CAAsC,KAAI;AACzC,QAAA,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK;AAC/B,QAAA,IAAI,eAAe,KAAK,SAAS,EAAE;YACjC,gBAAgB,CAAC,QAAQ,CAAC;QAC5B;AACA,QAAA,OAAO,GAAG,QAAQ,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC;AACd,QAAA,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE;YACzB,WAAW,CAAC,IAAI,CAAC;QACnB;IACF,CAAC,EACD,CAAC,eAAe,EAAE,QAAQ,EAAE,OAAO,CAAC,CACrC;;AAGD,IAAA,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,IAA+B,KAAI;AAClC,QAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ;YAAE;AAE/C,QAAA,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC;AAC1C,QAAA,IAAI,eAAe,KAAK,SAAS,EAAE;YACjC,gBAAgB,CAAC,YAAY,CAAC;QAChC;AACA,QAAA,OAAO,GAAG,YAAY,CAAC;AACvB,QAAA,QAAQ,GAAG,IAAI,CAAC;QAEhB,IAAI,CAAC,QAAQ,EAAE;YACb,WAAW,CAAC,KAAK,CAAC;QACpB;AACA,QAAA,mBAAmB,CAAC,EAAE,CAAC;AACzB,IAAA,CAAC,EACD,CAAC,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CACtD;;AAGD,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,MAAK;AACnC,QAAA,IAAI,eAAe,KAAK,SAAS,EAAE;YACjC,gBAAgB,CAAC,EAAE,CAAC;QACtB;AACA,QAAA,OAAO,GAAG,EAAE,CAAC;AACb,QAAA,QAAQ,GAAG,IAAI,CAAC;AAChB,QAAA,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE;IAC3B,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;;AAGxC,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,MAAK;AACnC,QAAA,IAAI,WAAW,IAAI,CAAC,QAAQ,EAAE;YAC5B,WAAW,CAAC,IAAI,CAAC;QACnB;AACF,IAAA,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;;IAG3B,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,SAAS;AAE/B,QAAA,MAAM,kBAAkB,GAAG,CAAC,CAAa,KAAI;YAC3C,IAAI,CAAC,YAAY,CAAC,OAAO;gBAAE;;AAG3B,YAAA,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC;YAEpD,IAAI,CAAC,QAAQ,EAAE;AACb,gBAAA,IAAI,oBAAoB,IAAI,gBAAgB,IAAI,CAAC,EAAE;AACjD,oBAAA,YAAY,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;gBAC9C;gBACA,WAAW,CAAC,KAAK,CAAC;YACpB;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;QACD,QAAQ;QACR,oBAAoB;QACpB,gBAAgB;QAChB,YAAY;QACZ,YAAY;AACb,KAAA,CAAC;;IAGF,SAAS,CAAC,MAAK;AACb,QAAA,cAAc,GAAG,QAAQ,CAAC;AAC5B,IAAA,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;;IAG9B,SAAS,CAAC,MAAK;QACb,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;YACpD,mBAAmB,CAAC,CAAC,CAAC;QACxB;AACF,IAAA,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;;AAG1D,IAAA,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,CAAsB,KAAI;AACzB,QAAA,IAAI,QAAQ;YAAE;AAEd,QAAA,QAAQ,CAAC,CAAC,GAAG;AACX,YAAA,KAAK,WAAW;gBACd,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,CAAC,QAAQ,EAAE;oBACb,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,OAAO;gBACV,CAAC,CAAC,cAAc,EAAE;gBAClB,IAAI,gBAAgB,IAAI,CAAC,IAAI,YAAY,CAAC,gBAAgB,CAAC,EAAE;AAC3D,oBAAA,YAAY,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;gBAC9C;gBACA;AACF,YAAA,KAAK,QAAQ;gBACX,CAAC,CAAC,cAAc,EAAE;gBAClB,WAAW,CAAC,KAAK,CAAC;AAClB,gBAAA,mBAAmB,CAAC,EAAE,CAAC;gBACvB;AACF,YAAA,KAAK,KAAK;gBACR,IAAI,gBAAgB,IAAI,CAAC,IAAI,YAAY,CAAC,gBAAgB,CAAC,EAAE;AAC3D,oBAAA,YAAY,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;gBAC9C;gBACA,WAAW,CAAC,KAAK,CAAC;gBAClB;;AAEN,IAAA,CAAC,EACD,CAAC,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,CAAC,CACnE;;AAGD,IAAA,MAAM,oBAAoB,GAAG,WAAW,CAAC,MAAK;AAC5C,QAAA,IAAI,CAAC,mBAAmB,IAAI,CAAC,WAAW,CAAC,OAAO;YAAE;QAElD,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,OAAO;QACrE,IAAI,YAAY,GAAG,SAAS,GAAG,YAAY,IAAI,sBAAsB,EAAE;YACrE,gBAAgB,IAAI;QACtB;IACF,CAAC,EAAE,CAAC,mBAAmB,EAAE,sBAAsB,EAAE,gBAAgB,CAAC,CAAC;;IAGnE,SAAS,CAAC,MAAK;QACb,IAAI,gBAAgB,IAAI,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE;AAChD,YAAA,MAAM,aAAa,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,CACrD,CAAA,aAAA,EAAgB,gBAAgB,CAAA,EAAA,CAAI,CACrC;AACD,YAAA,IACE,aAAa;AACb,gBAAA,OAAO,aAAa,CAAC,cAAc,KAAK,UAAU,EAClD;gBACA,aAAa,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YACpD;QACF;AACF,IAAA,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;;AAGtB,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,mBAAmB,GAAG,qBAAqB,CAAC,cAAc,EAAE;AAChE,QAAA,WAAW,EAAE,QAAQ;AACrB,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACvB,KAAA,CAAC;AAEF,IAAA,MAAM,YAAY,GAAG,qBAAqB,CAAC,OAAO,EAAE;AAClD,QAAA,CAAC,MAAM,KAAK,CAAA,CAAE,GAAG,CAAC,CAAC,KAAK;AACxB,QAAA,CAAC,MAAM,IAAI,CAAA,CAAE,GAAG,CAAC,CAAC,IAAI;AACtB,QAAA,YAAY,EAAE,OAAO;AACtB,KAAA,CAAC;IAEF,MAAM,cAAc,GAAG,qBAAqB,CAC1C,SAAS,EACT,aAAa,EACb,iBAAiB,CAClB;AAED,IAAA,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,eAAe,EAAE;AACjE,QAAA,WAAW,EAAE,QAAQ;AACtB,KAAA,CAAC;AAEF,IAAA,MAAM,oBAAoB,GAAG,qBAAqB,CAAC,kBAAkB,CAAC;AACtE,IAAA,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,iBAAiB,CAAC;AACpE,IAAA,MAAM,mBAAmB,GAAG,qBAAqB,CAAC,iBAAiB,CAAC;IACpE,MAAM,gBAAgB,GAAG,qBAAqB,CAC5C,eAAe,EACf,eAAe,CAChB;IAED,MAAM,uBAAuB,GAAG,qBAAqB,CACnD,MAAM,EACN,UAAU,EACV,cAAc,CACf;IACD,MAAM,cAAc,GAAG,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC;IAChE,MAAM,WAAW,GAAG,qBAAqB,CAAC,QAAQ,EAAE,YAAY,CAAC;IAEjE,MAAM,eAAe,GAAG,UAAU,CAChC,mBAAmB,EACnB,kBAAkB,EAClB,SAAS,CACV;AAED,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;;;AAIzE,IAAA,MAAM,cAAc,GAAG,iBAAiB,EAAE;AAC1C,IAAA,MAAM,SAAS,GACb,SAAS,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,IAAI,CAAC,cAAc;;;;;;;;IASvE,MAAM,cAAc,GAClB,QAAQ;AACR,QAAA,gBAAgB,IAAI,CAAC;QACrB,gBAAgB,GAAG,YAAY,CAAC;AAC9B,UAAE,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,gBAAgB,CAAA;UAChC,SAAS;;;;;;;;;;IAWf,MAAM,UAAU,GACd,KAAK;QACL,QAAQ;AACR,QAAA,CAAC,QAAQ;AACT,QAAA,CAAC,CAAC,UAAU;AACZ,QAAA,CAAC,OAAO;AACR,QAAA,CAAC;AACC,UAAE;UACA,iBAAiB;;AAEvB,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC;AAC7C,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,MAAK;AACrC,QAAA,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO;;AAE7B,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,IAAI;AACtB,QAAA,OAAO,UAAU,CAAC,IAAI,CAAC;IACzB,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,mBAAmB,IACvBC,IAAA,CAAA,KAAA,EAAA,EAAK,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAA,GAAM,IAAI,aAC1DA,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,cAAc,EAAA,QAAA,EAAA,CAC5BD,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,WAAW,EACxB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAC,UAAU,EAAA,eAAA,EACA,QAAQ,EAAA,eAAA,EACR,QAAQ,GAAG,SAAS,GAAG,SAAS,EAAA,uBAAA,EACxB,cAAc,EAAA,eAAA,EACvB,SAAS,EAAA,mBAAA,EACL,MAAM,EACxB,YAAY,EAAC,KAAK,EAAA,CAClB,EACD,SAAS,KACRA,GAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,uBAAuB,EAClC,OAAO,EAAE,WAAW,EAAA,YAAA,EACT,OAAO,EAAA,QAAA,EAElBC,IAAA,CAAA,KAAA,EAAA,EACE,KAAK,EAAC,4BAA4B,EAClC,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EAAA,aAAA,EACC,MAAM,EAAA,QAAA,EAAA,CAElBD,cAAM,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAA,CAAG,EACtCA,cAAM,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,GAAG,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAA,CAAG,CAAA,EAAA,CAClC,GACC,CACV,EACA,OAAO,KACNA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,cAAc,EAAA,QAAA,EAC7BA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,WAAW,EAAA,CAAI,EAAA,CAC3B,CACR,IACG,EAEL,QAAQ,KACPA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,mBAAmB,YACjCC,IAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,WAAW,EAChB,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,EAAE,SAAS,EAAE,CAAA,EAAG,SAAS,CAAA,EAAA,CAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EACzD,QAAQ,EAAE,oBAAoB,aAE7B,MAAM,IAAID,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,mBAAmB,EAAA,QAAA,EAAG,MAAM,GAAO,EAE7D,QAAQ,IACPA,GAAA,CAAA,KAAA,EAAA,EACE,EAAE,EAAE,SAAS,EACb,IAAI,EAAC,SAAS,EAAA,iBAAA,EACG,WAAW,EAAA,YAAA,EAChB,WAAW,GAAG,SAAS,GAAG,aAAa,YAElD,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,KAAI;gCAChC,MAAM,UAAU,GACd,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ;AAC3C,gCAAA,MAAM,aAAa,GAAG,KAAK,KAAK,gBAAgB;AAEhD,gCAAA,MAAM,WAAW,GAAG,kBAAkB,CACpC,WAAW,EACX,eAAe,EACf;AACE,oCAAA,WAAW,EAAE,aAAa;AAC1B,oCAAA,aAAa,EAAE,UAAU;AAC1B,iCAAA,CACF;gCAED,QACEA,WAEE,EAAE,EAAE,GAAG,SAAS,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,EAAA,YAAA,EACf,KAAK,EACjB,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,MAAM,CAAC,UAAU,IAAI,YAAY,CAAC,IAAI,CAAC,EAChD,YAAY,EAAE,MACZ,CAAC,UAAU,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAE3C,IAAI,EAAC,QAAQ,mBACE,aAAa,EAAA,eAAA,EACb,UAAU,EAAA,QAAA,EAExB;AACC,0CAAE,YAAY,CAAC,IAAI;0CACjB,eAAe,CAAC,IAAI,CAAC,EAAA,EAdpB,KAAK,CAeR;4BAER,CAAC,CAAC,GACE,KAENA,aAAK,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAA,QAAA,EAC5C,KAAK,EAAA,CACF,CACP,EAEA,MAAM,IAAIA,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAE,mBAAmB,YAAG,MAAM,EAAA,CAAO,IAC1D,EAAA,CACF,CACP,EAEDA,GAAA,CAAC,YAAY,IAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAA,CAAI,CAAA,EAAA,CACxD,CACP;IAED,IAAI,CAAC,WAAW,EAAE;AAChB,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,mBAAmB,EACnB,SAAS,CAAA,EAAA,CACJ;IAEZ;AAEA,IAAA,QACEA,IAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EAAA,CACG,mBAAmB,EACnB,SAAS,CAAA,EAAA,CACT;AAEP,CAAC;AAGH,YAAY,CAAC,WAAW,GAAG,cAAc;;"}
|
|
@@ -44,7 +44,8 @@ const visuallyHidden = {
|
|
|
44
44
|
border: 0,
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* Works out what a status region says. Each item is announced once
|
|
47
|
+
* Works out what a status region says. Each item is announced once each time
|
|
48
|
+
* it appears, so one that goes and comes back is announced again. Its
|
|
48
49
|
* announcement is written `announceDelay` after it appears, or after the last
|
|
49
50
|
* item to join it when several appear in quick succession, alongside whatever
|
|
50
51
|
* the region still says. Once written, it stays for `announcementLifetime`,
|
|
@@ -79,8 +80,8 @@ function useAnnouncements(items, describe) {
|
|
|
79
80
|
useEffect(() => {
|
|
80
81
|
const announced = onScreenIdsRef.current;
|
|
81
82
|
const appeared = items.filter(item => !announced.has(item.id));
|
|
82
|
-
// Items that have gone drop out here too
|
|
83
|
-
//
|
|
83
|
+
// Items that have gone drop out here too, so one that comes back has
|
|
84
|
+
// appeared again.
|
|
84
85
|
onScreenIdsRef.current = new Set(items.map(item => item.id));
|
|
85
86
|
const next = [];
|
|
86
87
|
for (const item of appeared) {
|
|
@@ -93,9 +94,13 @@ function useAnnouncements(items, describe) {
|
|
|
93
94
|
setAnnouncements(current => ({
|
|
94
95
|
...current,
|
|
95
96
|
key: current.key + 1,
|
|
96
|
-
// Announcements still waiting go out with the new ones
|
|
97
|
-
//
|
|
98
|
-
|
|
97
|
+
// Announcements still waiting go out with the new ones, apart from
|
|
98
|
+
// one whose item came back before it was written, which the new one
|
|
99
|
+
// replaces. What's already written stays put.
|
|
100
|
+
waiting: [
|
|
101
|
+
...current.waiting.filter(waiting => !next.some(({ id }) => id === waiting.id)),
|
|
102
|
+
...next,
|
|
103
|
+
],
|
|
99
104
|
}));
|
|
100
105
|
}
|
|
101
106
|
}, [items, describe]);
|
|
@@ -254,10 +259,10 @@ function readAloud(element) {
|
|
|
254
259
|
return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;
|
|
255
260
|
}
|
|
256
261
|
/**
|
|
257
|
-
* A visually hidden, polite `status` live region that a
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
262
|
+
* A visually hidden, polite `status` live region that a container announces
|
|
263
|
+
* its items through. The container renders it from the moment it mounts,
|
|
264
|
+
* whether or not it has anything to show, so the region is already in the
|
|
265
|
+
* page when an announcement is written into it.
|
|
261
266
|
*
|
|
262
267
|
* The announcements are this component's own state, so writing and clearing
|
|
263
268
|
* them re-renders the region and leaves the container's items alone. Each one
|
|
@@ -274,8 +279,10 @@ function StatusRegion({ items, describe, }) {
|
|
|
274
279
|
// reads the whole region on every change. Each announcement is its own
|
|
275
280
|
// node, and only additions are relevant by default, so with atomic off a
|
|
276
281
|
// screen reader reads just what was written, and removing one announcement
|
|
277
|
-
// doesn't read out the ones still there.
|
|
278
|
-
|
|
282
|
+
// doesn't read out the ones still there. A node is keyed by its batch as
|
|
283
|
+
// well as its item, since an item that comes back can be written again
|
|
284
|
+
// while what it said before is still there.
|
|
285
|
+
return (jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", style: visuallyHidden, children: announcements.map(announcement => (jsx("div", { children: announcement.text }, `${announcement.batch}:${announcement.id}`))) }));
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
export { StatusRegion, announceDelay, announcementLifetime, spokenText, visuallyHidden };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusRegion.js","sources":["../../../src/helpers/statusRegion.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\n\n/**\n * What a status region says for one of a container's items.\n */\ninterface Announcement {\n /** The id of the item it announces. */\n id: string;\n /** The text a screen reader reads out. */\n text: string;\n}\n\n/**\n * An announcement in the region.\n */\ninterface WrittenAnnouncement extends Announcement {\n /** The batch it was written with, which is what its clear removes. */\n batch: number;\n}\n\n/**\n * How long after an item appears its announcement is written, in ms. The wait\n * lets a screen reader register a region the container has only just mounted,\n * and items shown in quick succession are written, and read out, together.\n */\nexport const announceDelay = 100;\n\n/**\n * How long an announcement stays in the region once written, in ms. That's\n * long enough for a screen reader to pick it up. Clearing it after keeps a\n * second copy of the item's text from sitting in the page, where someone\n * reading through the page, or a test looking the text up, would find it\n * again.\n */\nexport const announcementLifetime = 1000;\n\n/**\n * What the region says, and what it's about to say.\n */\ninterface Announcements {\n /**\n * Changes whenever announcements join the wait, which restarts it. It also\n * names the batch they're written with.\n */\n key: number;\n /** Waiting to be written, in the order their items were shown. */\n waiting: Announcement[];\n /** In the region, in the order they were written. */\n written: WrittenAnnouncement[];\n}\n\nconst noAnnouncements: Announcement[] = [];\nconst nothingWritten: WrittenAnnouncement[] = [];\nconst noAnnouncementsYet: Announcements = {\n key: 0,\n waiting: noAnnouncements,\n written: nothingWritten,\n};\n\n// Hides the region with inline styles rather than a class, so it needs no\n// stylesheet, including the helper classes a modular Bulma build can leave\n// out. Clipping it, rather than using `display` or `visibility`, keeps it in\n// the accessibility tree. The declarations match the `extras-sr-only` mixin,\n// plus `clip-path`, which replaces the deprecated `clip`. The negative margin\n// pulls the 1px box back inside the page, so it doesn't add scrollable\n// overflow. Exported for other live regions that must work without a\n// stylesheet, such as a range calendar's.\nexport const visuallyHidden: React.CSSProperties = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * Works out what a status region says. Each item is announced once. Its\n * announcement is written `announceDelay` after it appears, or after the last\n * item to join it when several appear in quick succession, alongside whatever\n * the region still says. Once written, it stays for `announcementLifetime`,\n * whatever is written after it and whether or not its item goes in the\n * meantime. An item that goes before its announcement is written isn't\n * announced.\n *\n * Nothing is written in the commit that mounts the region or the item. A\n * screen reader reliably announces a polite live region whose content changes\n * after the region is already in the page, and not always one that arrives\n * with its content. By the time `describe` runs, an item's rendered text can\n * be read.\n *\n * @function useAnnouncements\n * @param items - The items to announce, in the order they were shown.\n * @param describe - The text to announce for an item, or nothing to skip it.\n * It runs once the item is on screen, and it must keep its identity between\n * renders (a module-level function or a `useCallback`), or every render\n * re-checks the items.\n * @returns The announcements to render, in the order they were written.\n */\nfunction useAnnouncements<Item extends { id: string }>(\n items: readonly Item[],\n describe: (item: Item) => string | null\n): WrittenAnnouncement[] {\n // The ids of the items on screen as of the last change, every one of which\n // has been looked at for an announcement already.\n const onScreenIdsRef = useRef(new Set<string>());\n // Clears that haven't run yet. They outlive the batch that started them, so\n // they're only stopped when the region unmounts.\n const clearTimersRef = useRef(new Set<ReturnType<typeof setTimeout>>());\n const [announcements, setAnnouncements] = useState(noAnnouncementsYet);\n\n // Runs after the commit that puts an item on screen, which is the first\n // point its rendered text can be read.\n useEffect(() => {\n const announced = onScreenIdsRef.current;\n const appeared = items.filter(item => !announced.has(item.id));\n // Items that have gone drop out here too. Ids are never reused, so\n // forgetting them can't bring an old item back.\n onScreenIdsRef.current = new Set(items.map(item => item.id));\n\n const next: Announcement[] = [];\n for (const item of appeared) {\n const text = describe(item);\n if (text) next.push({ id: item.id, text });\n }\n if (next.length > 0) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen\n setAnnouncements(current => ({\n ...current,\n key: current.key + 1,\n // Announcements still waiting go out with the new ones. What's\n // already written stays put.\n waiting: [...current.waiting, ...next],\n }));\n }\n }, [items, describe]);\n\n // The write only touches the batch it was started for: a newer batch can\n // be set before the render that cancels this timer. Its clear is started\n // by the write itself, so it's timed from the write without waiting on a\n // render, and a newer batch arriving doesn't cut short what's written.\n const { key } = announcements;\n useEffect(() => {\n if (key === noAnnouncementsYet.key) return undefined;\n const clearTimers = clearTimersRef.current;\n const write = setTimeout(() => {\n // Whether an item is on screen is decided here, once. An item that\n // went before its announcement was due isn't announced, and one that\n // goes after keeps its announcement for the rest of its lifetime, when\n // the region holds the only copy of its text rather than a second one.\n const onScreen = onScreenIdsRef.current;\n setAnnouncements(current =>\n current.key === key\n ? {\n ...current,\n waiting: noAnnouncements,\n written: [\n ...current.written,\n ...current.waiting\n .filter(announcement => onScreen.has(announcement.id))\n .map(announcement => ({ ...announcement, batch: key })),\n ],\n }\n : current\n );\n // eslint-disable-next-line @eslint-react/web-api-no-leaked-timeout -- it has to outlive this effect when a newer batch arrives, so the unmount effect below clears it\n const clear = setTimeout(() => {\n clearTimers.delete(clear);\n setAnnouncements(current => {\n const written = current.written.filter(\n announcement => announcement.batch !== key\n );\n return written.length === current.written.length\n ? current\n : { ...current, written };\n });\n }, announcementLifetime);\n clearTimers.add(clear);\n }, announceDelay);\n return () => clearTimeout(write);\n }, [key]);\n\n useEffect(() => {\n const clearTimers = clearTimersRef.current;\n return () => {\n clearTimers.forEach(clearTimeout);\n clearTimers.clear();\n };\n }, []);\n\n return announcements.written;\n}\n\n/**\n * Reads an element out roughly the way a screen reader would: its text, with\n * an element's `aria-label`, or an image's `alt`, standing in for what's\n * inside it, a break between block-level elements, and parts that are\n * `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't\n * the full accessible name computation, so `aria-labelledby`, CSS-generated\n * content, content hidden by a stylesheet and other ways of naming or hiding\n * content aren't followed.\n *\n * @function spokenText\n * @param element - The element to read.\n * @returns Its text, with runs of whitespace collapsed and the ends trimmed.\n */\nexport function spokenText(element: Element): string {\n return readAloud(element).replace(/\\s+/g, ' ').trim();\n}\n\n// Elements a browser displays as blocks by default, and `br`. A screen reader\n// pauses between them, so their text shouldn't run into the text beside them.\n// They're known by tag rather than by computed `display`, which would depend\n// on a stylesheet and on layout.\nconst blockLevelTags = new Set([\n 'ADDRESS',\n 'ARTICLE',\n 'ASIDE',\n 'BLOCKQUOTE',\n 'BR',\n 'CAPTION',\n 'DD',\n 'DETAILS',\n 'DIALOG',\n 'DIV',\n 'DL',\n 'DT',\n 'FIELDSET',\n 'FIGCAPTION',\n 'FIGURE',\n 'FOOTER',\n 'FORM',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'HEADER',\n 'HGROUP',\n 'HR',\n 'LEGEND',\n 'LI',\n 'MAIN',\n 'MENU',\n 'NAV',\n 'OL',\n 'P',\n 'PRE',\n 'SEARCH',\n 'SECTION',\n 'SUMMARY',\n 'TABLE',\n 'TBODY',\n 'TD',\n 'TFOOT',\n 'TH',\n 'THEAD',\n 'TR',\n 'UL',\n]);\n\n/**\n * Whether an element is left out of what a screen reader reads.\n *\n * @function isHidden\n * @param element - The element to check.\n * @returns True for `aria-hidden=\"true\"`, the `hidden` attribute, and an\n * inline `display: none`, on an HTML, SVG or any other element with an\n * inline style.\n */\nconst isHidden = (element: Element): boolean =>\n element.getAttribute('aria-hidden') === 'true' ||\n element.hasAttribute('hidden') ||\n (element as Partial<ElementCSSInlineStyle>).style?.display === 'none';\n\n/**\n * The text `spokenText` reads for an element, before its whitespace is\n * tidied.\n *\n * @function readAloud\n * @param element - The element to read.\n * @returns Its text.\n */\nfunction readAloud(element: Element): string {\n if (isHidden(element)) return '';\n const name =\n element.getAttribute('aria-label') ||\n (element.tagName === 'IMG' ? element.getAttribute('alt') : null);\n const text =\n name ||\n Array.from(element.childNodes, child => {\n if (child instanceof Element) return readAloud(child);\n return child.nodeType === Node.TEXT_NODE ? child.textContent : '';\n }).join('');\n // A name stands in for the element's content, and a block's content stands\n // apart from what's around it, so both are padded to keep them from running\n // into the neighboring text. An inline run, like `<span>Up</span>` then\n // `<span>loaded</span>`, still reads as one word.\n return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;\n}\n\n/**\n * Props for StatusRegion.\n */\nexport interface StatusRegionProps<Item extends { id: string }> {\n /** The items to announce politely, in the order they were shown. */\n items: readonly Item[];\n /**\n * The text to announce for an item, or nothing to skip it, as\n * `useAnnouncements` takes it.\n */\n describe: (item: Item) => string | null;\n}\n\n/**\n * A visually hidden, polite `status` live region that a programmatic\n * container announces its items through. The container renders it from the\n * moment it mounts, whether or not it has anything to show, so the region is\n * already in the page when an announcement is written into it.\n *\n * The announcements are this component's own state, so writing and clearing\n * them re-renders the region and leaves the container's items alone. Each one\n * is its own keyed node, so a re-render that leaves the announcements alone\n * leaves the region's content alone too, and nothing is read out twice.\n *\n * @function StatusRegion\n * @param props - The items to announce, and the text to announce for each.\n * @returns The region.\n */\nexport function StatusRegion<Item extends { id: string }>({\n items,\n describe,\n}: StatusRegionProps<Item>): React.ReactElement {\n const announcements = useAnnouncements(items, describe);\n\n // `role=\"status\"` makes a region atomic unless it says otherwise, which\n // reads the whole region on every change. Each announcement is its own\n // node, and only additions are relevant by default, so with atomic off a\n // screen reader reads just what was written, and removing one announcement\n // doesn't read out the ones still there.\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\n style={visuallyHidden}\n >\n {announcements.map(announcement => (\n <div key={announcement.id}>{announcement.text}</div>\n ))}\n </div>\n );\n}\n"],"names":["_jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"statusRegion.js","sources":["../../../src/helpers/statusRegion.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\n\n/**\n * What a status region says for one of a container's items.\n */\ninterface Announcement {\n /** The id of the item it announces. */\n id: string;\n /** The text a screen reader reads out. */\n text: string;\n}\n\n/**\n * An announcement in the region.\n */\ninterface WrittenAnnouncement extends Announcement {\n /** The batch it was written with, which is what its clear removes. */\n batch: number;\n}\n\n/**\n * How long after an item appears its announcement is written, in ms. The wait\n * lets a screen reader register a region the container has only just mounted,\n * and items shown in quick succession are written, and read out, together.\n */\nexport const announceDelay = 100;\n\n/**\n * How long an announcement stays in the region once written, in ms. That's\n * long enough for a screen reader to pick it up. Clearing it after keeps a\n * second copy of the item's text from sitting in the page, where someone\n * reading through the page, or a test looking the text up, would find it\n * again.\n */\nexport const announcementLifetime = 1000;\n\n/**\n * What the region says, and what it's about to say.\n */\ninterface Announcements {\n /**\n * Changes whenever announcements join the wait, which restarts it. It also\n * names the batch they're written with.\n */\n key: number;\n /** Waiting to be written, in the order their items were shown. */\n waiting: Announcement[];\n /** In the region, in the order they were written. */\n written: WrittenAnnouncement[];\n}\n\nconst noAnnouncements: Announcement[] = [];\nconst nothingWritten: WrittenAnnouncement[] = [];\nconst noAnnouncementsYet: Announcements = {\n key: 0,\n waiting: noAnnouncements,\n written: nothingWritten,\n};\n\n// Hides the region with inline styles rather than a class, so it needs no\n// stylesheet, including the helper classes a modular Bulma build can leave\n// out. Clipping it, rather than using `display` or `visibility`, keeps it in\n// the accessibility tree. The declarations match the `extras-sr-only` mixin,\n// plus `clip-path`, which replaces the deprecated `clip`. The negative margin\n// pulls the 1px box back inside the page, so it doesn't add scrollable\n// overflow. Exported for other live regions that must work without a\n// stylesheet, such as a range calendar's.\nexport const visuallyHidden: React.CSSProperties = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: 0,\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * Works out what a status region says. Each item is announced once each time\n * it appears, so one that goes and comes back is announced again. Its\n * announcement is written `announceDelay` after it appears, or after the last\n * item to join it when several appear in quick succession, alongside whatever\n * the region still says. Once written, it stays for `announcementLifetime`,\n * whatever is written after it and whether or not its item goes in the\n * meantime. An item that goes before its announcement is written isn't\n * announced.\n *\n * Nothing is written in the commit that mounts the region or the item. A\n * screen reader reliably announces a polite live region whose content changes\n * after the region is already in the page, and not always one that arrives\n * with its content. By the time `describe` runs, an item's rendered text can\n * be read.\n *\n * @function useAnnouncements\n * @param items - The items to announce, in the order they were shown.\n * @param describe - The text to announce for an item, or nothing to skip it.\n * It runs once the item is on screen, and it must keep its identity between\n * renders (a module-level function or a `useCallback`), or every render\n * re-checks the items.\n * @returns The announcements to render, in the order they were written.\n */\nfunction useAnnouncements<Item extends { id: string }>(\n items: readonly Item[],\n describe: (item: Item) => string | null\n): WrittenAnnouncement[] {\n // The ids of the items on screen as of the last change, every one of which\n // has been looked at for an announcement already.\n const onScreenIdsRef = useRef(new Set<string>());\n // Clears that haven't run yet. They outlive the batch that started them, so\n // they're only stopped when the region unmounts.\n const clearTimersRef = useRef(new Set<ReturnType<typeof setTimeout>>());\n const [announcements, setAnnouncements] = useState(noAnnouncementsYet);\n\n // Runs after the commit that puts an item on screen, which is the first\n // point its rendered text can be read.\n useEffect(() => {\n const announced = onScreenIdsRef.current;\n const appeared = items.filter(item => !announced.has(item.id));\n // Items that have gone drop out here too, so one that comes back has\n // appeared again.\n onScreenIdsRef.current = new Set(items.map(item => item.id));\n\n const next: Announcement[] = [];\n for (const item of appeared) {\n const text = describe(item);\n if (text) next.push({ id: item.id, text });\n }\n if (next.length > 0) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- an item's rendered text can only be read once it's on screen\n setAnnouncements(current => ({\n ...current,\n key: current.key + 1,\n // Announcements still waiting go out with the new ones, apart from\n // one whose item came back before it was written, which the new one\n // replaces. What's already written stays put.\n waiting: [\n ...current.waiting.filter(\n waiting => !next.some(({ id }) => id === waiting.id)\n ),\n ...next,\n ],\n }));\n }\n }, [items, describe]);\n\n // The write only touches the batch it was started for: a newer batch can\n // be set before the render that cancels this timer. Its clear is started\n // by the write itself, so it's timed from the write without waiting on a\n // render, and a newer batch arriving doesn't cut short what's written.\n const { key } = announcements;\n useEffect(() => {\n if (key === noAnnouncementsYet.key) return undefined;\n const clearTimers = clearTimersRef.current;\n const write = setTimeout(() => {\n // Whether an item is on screen is decided here, once. An item that\n // went before its announcement was due isn't announced, and one that\n // goes after keeps its announcement for the rest of its lifetime, when\n // the region holds the only copy of its text rather than a second one.\n const onScreen = onScreenIdsRef.current;\n setAnnouncements(current =>\n current.key === key\n ? {\n ...current,\n waiting: noAnnouncements,\n written: [\n ...current.written,\n ...current.waiting\n .filter(announcement => onScreen.has(announcement.id))\n .map(announcement => ({ ...announcement, batch: key })),\n ],\n }\n : current\n );\n // eslint-disable-next-line @eslint-react/web-api-no-leaked-timeout -- it has to outlive this effect when a newer batch arrives, so the unmount effect below clears it\n const clear = setTimeout(() => {\n clearTimers.delete(clear);\n setAnnouncements(current => {\n const written = current.written.filter(\n announcement => announcement.batch !== key\n );\n return written.length === current.written.length\n ? current\n : { ...current, written };\n });\n }, announcementLifetime);\n clearTimers.add(clear);\n }, announceDelay);\n return () => clearTimeout(write);\n }, [key]);\n\n useEffect(() => {\n const clearTimers = clearTimersRef.current;\n return () => {\n clearTimers.forEach(clearTimeout);\n clearTimers.clear();\n };\n }, []);\n\n return announcements.written;\n}\n\n/**\n * Reads an element out roughly the way a screen reader would: its text, with\n * an element's `aria-label`, or an image's `alt`, standing in for what's\n * inside it, a break between block-level elements, and parts that are\n * `aria-hidden`, `hidden` or inline-styled `display: none` left out. It isn't\n * the full accessible name computation, so `aria-labelledby`, CSS-generated\n * content, content hidden by a stylesheet and other ways of naming or hiding\n * content aren't followed.\n *\n * @function spokenText\n * @param element - The element to read.\n * @returns Its text, with runs of whitespace collapsed and the ends trimmed.\n */\nexport function spokenText(element: Element): string {\n return readAloud(element).replace(/\\s+/g, ' ').trim();\n}\n\n// Elements a browser displays as blocks by default, and `br`. A screen reader\n// pauses between them, so their text shouldn't run into the text beside them.\n// They're known by tag rather than by computed `display`, which would depend\n// on a stylesheet and on layout.\nconst blockLevelTags = new Set([\n 'ADDRESS',\n 'ARTICLE',\n 'ASIDE',\n 'BLOCKQUOTE',\n 'BR',\n 'CAPTION',\n 'DD',\n 'DETAILS',\n 'DIALOG',\n 'DIV',\n 'DL',\n 'DT',\n 'FIELDSET',\n 'FIGCAPTION',\n 'FIGURE',\n 'FOOTER',\n 'FORM',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'HEADER',\n 'HGROUP',\n 'HR',\n 'LEGEND',\n 'LI',\n 'MAIN',\n 'MENU',\n 'NAV',\n 'OL',\n 'P',\n 'PRE',\n 'SEARCH',\n 'SECTION',\n 'SUMMARY',\n 'TABLE',\n 'TBODY',\n 'TD',\n 'TFOOT',\n 'TH',\n 'THEAD',\n 'TR',\n 'UL',\n]);\n\n/**\n * Whether an element is left out of what a screen reader reads.\n *\n * @function isHidden\n * @param element - The element to check.\n * @returns True for `aria-hidden=\"true\"`, the `hidden` attribute, and an\n * inline `display: none`, on an HTML, SVG or any other element with an\n * inline style.\n */\nconst isHidden = (element: Element): boolean =>\n element.getAttribute('aria-hidden') === 'true' ||\n element.hasAttribute('hidden') ||\n (element as Partial<ElementCSSInlineStyle>).style?.display === 'none';\n\n/**\n * The text `spokenText` reads for an element, before its whitespace is\n * tidied.\n *\n * @function readAloud\n * @param element - The element to read.\n * @returns Its text.\n */\nfunction readAloud(element: Element): string {\n if (isHidden(element)) return '';\n const name =\n element.getAttribute('aria-label') ||\n (element.tagName === 'IMG' ? element.getAttribute('alt') : null);\n const text =\n name ||\n Array.from(element.childNodes, child => {\n if (child instanceof Element) return readAloud(child);\n return child.nodeType === Node.TEXT_NODE ? child.textContent : '';\n }).join('');\n // A name stands in for the element's content, and a block's content stands\n // apart from what's around it, so both are padded to keep them from running\n // into the neighboring text. An inline run, like `<span>Up</span>` then\n // `<span>loaded</span>`, still reads as one word.\n return name || blockLevelTags.has(element.tagName) ? ` ${text} ` : text;\n}\n\n/**\n * Props for StatusRegion.\n */\nexport interface StatusRegionProps<Item extends { id: string }> {\n /** The items to announce politely, in the order they were shown. */\n items: readonly Item[];\n /**\n * The text to announce for an item, or nothing to skip it, as\n * `useAnnouncements` takes it.\n */\n describe: (item: Item) => string | null;\n}\n\n/**\n * A visually hidden, polite `status` live region that a container announces\n * its items through. The container renders it from the moment it mounts,\n * whether or not it has anything to show, so the region is already in the\n * page when an announcement is written into it.\n *\n * The announcements are this component's own state, so writing and clearing\n * them re-renders the region and leaves the container's items alone. Each one\n * is its own keyed node, so a re-render that leaves the announcements alone\n * leaves the region's content alone too, and nothing is read out twice.\n *\n * @function StatusRegion\n * @param props - The items to announce, and the text to announce for each.\n * @returns The region.\n */\nexport function StatusRegion<Item extends { id: string }>({\n items,\n describe,\n}: StatusRegionProps<Item>): React.ReactElement {\n const announcements = useAnnouncements(items, describe);\n\n // `role=\"status\"` makes a region atomic unless it says otherwise, which\n // reads the whole region on every change. Each announcement is its own\n // node, and only additions are relevant by default, so with atomic off a\n // screen reader reads just what was written, and removing one announcement\n // doesn't read out the ones still there. A node is keyed by its batch as\n // well as its item, since an item that comes back can be written again\n // while what it said before is still there.\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\n style={visuallyHidden}\n >\n {announcements.map(announcement => (\n <div key={`${announcement.batch}:${announcement.id}`}>\n {announcement.text}\n </div>\n ))}\n </div>\n );\n}\n"],"names":["_jsx"],"mappings":"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"}
|