UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

1,456 lines (1,455 loc) 52.8 kB
"use client"; var _IconPrimary, _AlignmentHelper; import _pushInstanceProperty from "core-js-pure/stable/instance/push.js"; import React, { useState, useRef, useEffect, useContext, useCallback } from 'react'; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import clsx from 'clsx'; import { warn, extendPropsWithContext, validateDOMAttributes, dispatchCustomElementEvent, getStatusState, combineDescribedBy, convertJsxToString, escapeRegexChars, getClosestParent } from "../../shared/component-helper.js"; import { IS_MAC, debounce, hasSelectedText } from "../../shared/helpers.js"; import useId from "../../shared/helpers/useId.js"; import useMountEffect from "../../shared/helpers/useMountEffect.js"; import { useIsomorphicLayoutEffect } from "../../shared/helpers/useIsomorphicLayoutEffect.js"; import { applySpacing } from "../space/SpacingUtils.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import Suffix from "../../shared/helpers/Suffix.js"; import AriaLive from "../aria-live/AriaLive.js"; import FormLabel from "../form-label/FormLabel.js"; import FormStatus from "../form-status/FormStatus.js"; import IconPrimary from "../icon-primary/IconPrimary.js"; import Input, { SubmitButton } from "../input/Input.js"; import ProgressIndicator from "../progress-indicator/ProgressIndicator.js"; import DrawerList from "../../fragments/drawer-list/DrawerList.js"; import { ItemContent } from "../../fragments/drawer-list/DrawerListItem.js"; import DrawerListContext from "../../fragments/drawer-list/DrawerListContext.js"; import DrawerListProvider from "../../fragments/drawer-list/DrawerListProvider.js"; import { parseContentTitle, getCurrentData, getCurrentIndex, normalizeData } from "../../fragments/drawer-list/DrawerListHelpers.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const autocompleteDefaultProps = { id: null, mode: 'sync', title: 'Option Menu', placeholder: null, noOptions: null, showAll: null, ariaLiveOptions: null, indicatorLabel: null, showOptionsSr: null, selectedSr: null, submitButtonTitle: null, submitButtonIcon: 'chevron_down', inputRef: null, icon: 'loupe', iconSize: null, iconPosition: 'left', arrowPosition: null, label: null, labelDirection: 'vertical', labelSrOnly: null, keepValue: null, keepSelection: null, keepValueAndSelection: null, showClearButton: null, status: null, statusState: 'error', statusProps: null, statusNoAnimation: null, globalStatus: null, suffix: null, disableFilter: false, disableReorder: false, scrollable: true, focusable: false, disableHighlighting: false, maxHeight: null, direction: 'auto', skipPortal: null, noAnimation: false, noScrollAnimation: false, showSubmitButton: false, submitElement: null, preventSelection: false, size: 'default', align: null, optionsRender: null, data: null, searchInWordIndex: null, searchNumbers: null, defaultValue: null, value: 'initval', inputValue: 'initval', autoComplete: 'off', openOnFocus: false, preventClose: false, keepOpen: false, open: null, disabled: null, stretch: null, skeleton: null, portalClass: null, drawerClass: null, pageOffset: null, observerElement: null, enableBodyLock: false, className: null, children: null, onOpen: null, onClose: null, onType: null, onFocus: null, onBlur: null, onChange: null, onSelect: null, onClear: null, onSubmit: null, inputElement: null }; function Autocomplete(props) { const _id = useId(props.id); const providerProps = { ...props, id: _id, data: props.data || props.children, open: null, tagName: 'dnb-autocomplete', ignoreEvents: false, preventFocus: true, skipKeysearch: true }; return _jsx(DrawerListProvider, { ...providerProps, children: _jsx(AutocompleteInstance, { ...props, id: _id }) }); } function parseDataItem(dataItem) { const searchWord = parseContentTitle(dataItem.searchContent || dataItem, { separator: ' ' }); if (typeof searchWord !== 'string' && Array.isArray(searchWord)) { return parseDataItem(searchWord); } return searchWord; } function createSearchIndex(data) { return data.map(dataItem => { const contentChunk = parseDataItem(dataItem); return { dataItem, contentChunk }; }); } function getCurrentDataTitle(selectedItem, data) { const currentData = getCurrentData(selectedItem, data); return parseContentTitle(currentData, { separator: ' ', preferSelectedValue: true }); } function AutocompleteInstance(ownProps) { var _context$getTranslati, _Fragment2; const context = useContext(DrawerListContext); const drawerList = context.drawerList; const filteredOwnProps = Object.fromEntries(Object.entries(ownProps).filter(([, v]) => v !== undefined)); const props = extendPropsWithContext(filteredOwnProps, autocompleteDefaultProps, (_context$getTranslati = context.getTranslation) === null || _context$getTranslati === void 0 || (_context$getTranslati = _context$getTranslati.call(context, ownProps)) === null || _context$getTranslati === void 0 ? void 0 : _context$getTranslati.Autocomplete, pickFormElementProps(context === null || context === void 0 ? void 0 : context.formElement), context === null || context === void 0 ? void 0 : context.Autocomplete); const { title, placeholder, label, labelDirection, labelSrOnly, icon, iconSize, size, align, fixedPosition, status, statusState, statusProps, statusNoAnimation, globalStatus, suffix, scrollable, focusable, keepOpen, keepValue, keepValueAndSelection, keepSelection, showClearButton, preventClose, noAnimation, noScrollAnimation, showSubmitButton, submitElement, inputElement: CustomInput, optionsRender, preventSelection, maxHeight, defaultValue, searchNumbers, searchInWordIndex, searchMatch, showOptionsSr, selectedSr, submitButtonTitle, submitButtonIcon, portalClass, drawerClass, inputRef, className, disabled, stretch, skeleton, arrowPosition, iconPosition, skipPortal, independentWidth, autoComplete, openOnFocus, disableFilter, disableReorder, onClear, selectAll, mode: _mode, data: _data, children: _children, direction: _direction, pageOffset: _pageOffset, observerElement: _observerElement, id: _id, open: _open, value: _value, inputValue: _inputValue, enableBodyLock: _enableBodyLock, listClass: _listClass, indicatorLabel: _indicatorLabel, noOptions: _noOptions, showAll: _showAll, ariaLiveOptions: _ariaLiveOptions, disableHighlighting: _disableHighlighting, onOpen: _onOpen, onType: _onType, onFocus: _onFocus, onBlur: _onBlur, onClose: _onClose, onChange: _onChange, onSelect: _onSelect, onSubmit: _onSubmit, onItemMouseEnter, ...attributes } = props; const [inputValue, setInputValueState] = useState(() => { if (props.inputValue !== 'initval' && props.inputValue != null) { return props.inputValue; } if (drawerList && drawerList.currentTitle) { return drawerList.currentTitle; } return null; }); const [typedInputValue, setTypedInputValue] = useState(null); const [mode, setModeState] = useState(props.mode); const [hasFocus, setHasFocus] = useState(false); const hasBlurRef = useRef(false); const setHasBlur = useCallback(v => { hasBlurRef.current = v; }, []); const [showAllNextTime, setShowAllNextTime] = useState(false); const [skipFocusDuringChange, setSkipFocusDuringChange] = useState(false); const [disableHighlightingState, setDisableHighlighting] = useState(props.disableHighlighting); const [visibleIndicator, setVisibleIndicator] = useState(false); const [searchIndex, setSearchIndexState] = useState(null); const _ref = useRef(null); const _refShell = useRef(null); const _refInput = useRef(null); const _selectTimeout = useRef(null); const _blurTimeout = useRef(null); const _focusTimeout = useRef(null); const showAllTimeoutRef = useRef(null); const preventFiringBlurEvent = useRef(null); const closingFromChangeRef = useRef(false); const suppressFocusHandlerRef = useRef(false); const selectAllActiveRef = useRef(false); const debouncedEventFnsRef = useRef({}); const cacheMemoryRef = useRef({}); const attributesRef = useRef({}); const wasVisibleRef = useRef(false); const skipFilterRef = useRef(disableFilter); const skipReorderRef = useRef(disableReorder); const searchIndexRef = useRef(searchIndex); const prevDataRef = useRef(props.data); const dataChangedRef = useRef(false); const lastUpdateDataRef = useRef(null); const prevValueRef = useRef(props.value); const prevInputValuePropRef = useRef(props.inputValue); const prevDisableHighlightingRef = useRef(props.disableHighlighting); const inputValueRef = useRef(inputValue); const typedInputValueRef = useRef(typedInputValue); const modeRef = useRef(mode); const hasFocusRef = useRef(hasFocus); searchIndexRef.current = searchIndex; inputValueRef.current = inputValue; typedInputValueRef.current = typedInputValue; modeRef.current = mode; hasFocusRef.current = hasFocus; const drawerListRef = useRef(drawerList); drawerListRef.current = drawerList; const propsRef = useRef(props); propsRef.current = props; const hasInjectedDataItem = useCallback((data = drawerList.data) => { const lastItem = data.slice(-1)[0]; return lastItem ? lastItem.showAll || String(lastItem.__id) === 'noOptions' : false; }, [drawerList.data]); const countData = useCallback((data = drawerList.data) => { const count = data.length; return count > 0 && hasInjectedDataItem(data) ? count - 1 : count; }, [drawerList.data, hasInjectedDataItem]); const hasValidData = useCallback((data = drawerList.data) => { if (countData(data) > 0) { const first = data[0]; if (!first.showAll && !['noOptions', 'indicator'].includes(String(first.__id))) { return true; } } return false; }, [drawerList.data, countData]); const hasSelectedItem = useCallback(() => { return parseFloat(String(drawerListRef.current.selectedItem)) > -1; }, []); const hasActiveItem = useCallback(() => { return parseFloat(String(drawerList.activeItem)) > -1; }, [drawerList.activeItem]); const hasFilterActive = useCallback((data = drawerListRef.current.data) => { const originalData = drawerListRef.current.originalData; return !(originalData && originalData.length === countData(data)); }, [countData]); const focusDrawerList = useCallback(() => { try { drawerList._refUl.current.focus({ preventScroll: true }); } catch (e) {} }, [drawerList._refUl]); const focusInput = useCallback(() => { try { if (_refInput.current) { _refInput.current.focus({ preventScroll: true }); } } catch (e) { warn(e); } }, []); const setVisible = useCallback((args = null, onStateComplete = null) => { wasVisibleRef.current = true; drawerListRef.current.setWrapperElement(_ref.current).setVisible(args, onStateComplete); }, []); const setHidden = useCallback((args = null, onStateComplete = null) => { drawerListRef.current.setHidden(args, onStateComplete); setHasFocus(false); setHasBlur(false); }, [setHasBlur]); const resetActiveItem = useCallback(() => { drawerListRef.current.setState({ activeItem: null }); }, []); const resetFilter = useCallback(() => { drawerListRef.current.setData(drawerListRef.current.originalData); }, []); const setInputValue = useCallback(val => { setInputValueState(val); }, []); const clearInputValue = useCallback(() => { setInputValueState(''); setTypedInputValue(null); }, []); const ignoreEvents = useCallback(() => { clearTimeout(showAllTimeoutRef.current); drawerListRef.current.setState({ ignoreEvents: true }, () => { showAllTimeoutRef.current = setTimeout(() => { drawerListRef.current.setState({ ignoreEvents: false }); }, 10); }); }, []); const showAllItems = useCallback(() => { var _getCurrentIndex; resetFilter(); const selectedItem = (_getCurrentIndex = getCurrentIndex(propsRef.current.value, drawerListRef.current.originalData)) !== null && _getCurrentIndex !== void 0 ? _getCurrentIndex : drawerListRef.current.selectedItem; drawerListRef.current.setState({ cacheHash: 'all' }); drawerListRef.current.setActiveItemAndScrollToIt(selectedItem, { scrollTo: false }); }, [resetFilter]); const setSearchIndex = useCallback(({ overwriteSearchIndex = false, data = drawerList.originalData } = {}, cb = null) => { cacheMemoryRef.current = {}; if (!overwriteSearchIndex && searchIndexRef.current) { return searchIndexRef.current; } const newSearchIndex = createSearchIndex(data); setSearchIndexState(newSearchIndex); searchIndexRef.current = newSearchIndex; if (cb) { cb(); } return newSearchIndex; }, [drawerList.originalData]); const revalidateSelectedItem = useCallback(() => { const selectedItem = getCurrentIndex(props.value, drawerListRef.current.originalData); drawerListRef.current.setState({ selectedItem }); }, [props.value]); const revalidateInputValue = useCallback(() => { if (props.inputValue && props.inputValue !== 'initval') { return undefined; } const selectedItem = getCurrentIndex(props.value, drawerListRef.current.originalData); const usedInputValue = getCurrentDataTitle(selectedItem, drawerListRef.current.originalData); setInputValue(usedInputValue); }, [props.inputValue, props.value, setInputValue]); const resetSelectedItem = useCallback(() => { const hadValue = hasSelectedItem(); drawerListRef.current.setState({ selectedItem: null }, () => { if (hadValue) { dispatchCustomElementEvent(propsRef.current, 'onChange', { ...getEventObjects('onChange') }); } }); }, [hasSelectedItem]); const totalReset = useCallback(() => { setInputValueState(null); setTypedInputValue(null); resetActiveItem(); resetSelectedItem(); }, [resetActiveItem, resetSelectedItem]); const resetInputValue = useCallback(() => { var _props$inputValue; if (keepValue || keepValueAndSelection || props.inputValue !== 'initval' && ((_props$inputValue = props.inputValue) === null || _props$inputValue === void 0 ? void 0 : _props$inputValue.length) > 0) { return undefined; } clearTimeout(_selectTimeout.current); _selectTimeout.current = setTimeout(() => { if (hasSelectedItem()) { const val = getCurrentDataTitle(drawerListRef.current.selectedItem, drawerListRef.current.originalData); setInputValue(val); } else { clearInputValue(); } }, 1); }, [keepValue, keepValueAndSelection, props.inputValue, hasSelectedItem, setInputValue, clearInputValue]); const showNoOptionsItem = useCallback(() => { resetActiveItem(); ignoreEvents(); drawerListRef.current.setData(props.noOptions === false ? [] : [{ className: 'dnb-autocomplete__no-options', content: props.noOptions, ignoreEvents: true, __id: 'noOptions' }]); drawerListRef.current.setState({ cacheHash: 'noOptions' }); setVisible(); }, [resetActiveItem, ignoreEvents, props.noOptions, setVisible]); const showIndicatorItem = useCallback(() => { resetActiveItem(); ignoreEvents(); drawerListRef.current.setData([{ className: 'dnb-autocomplete__indicator', content: _jsx(ProgressIndicator, { label: props.indicatorLabel, labelDirection: "horizontal" }), ignoreEvents: true, __id: 'indicator' }]); drawerListRef.current.setState({ cacheHash: 'indicator' }); setVisible(); }, [resetActiveItem, ignoreEvents, props.indicatorLabel, setVisible]); const showIndicator = useCallback(() => { setVisibleIndicator(true); }, []); const hideIndicator = useCallback(() => { setVisibleIndicator(false); }, []); const setMode = useCallback(newMode => { setModeState(newMode); }, []); const wrapWithShowAll = useCallback(data => { if (!data || !hasFilterActive(data)) { return data; } const lastItem = drawerListRef.current.originalData.slice(-1)[0]; if (lastItem && !lastItem.showAll) { const lastActiveItem = data.slice(-1)[0]; if (lastActiveItem) { _pushInstanceProperty(data).call(data, { __id: lastItem.__id + 1, lastActiveItem: lastActiveItem.__id, className: 'dnb-autocomplete__show-all', showAll: true, activeItem: false, selectedItem: false, content: _jsxs(_Fragment, { children: [_IconPrimary || (_IconPrimary = _jsx(IconPrimary, { icon: "arrow_down" })), props.showAll] }) }); } } return data; }, [hasFilterActive, props.showAll]); const runFilter = useCallback((value, { data = null, searchIndex: siParam = searchIndexRef.current, searchNumbers: snParam = props.searchNumbers, inWordIndex = parseFloat(String((_props$searchInWordIn => (_props$searchInWordIn = props.searchInWordIndex) !== null && _props$searchInWordIn !== void 0 ? _props$searchInWordIn : skipFilterRef.current ? 1 : 3)())) - 1, disableHighlighting: disableHL = false, skipFilter = false, skipReorder = false } = {}) => { let currentSearchIndex = siParam; if (data) { currentSearchIndex = setSearchIndex({ data }); } else if (!currentSearchIndex) { currentSearchIndex = setSearchIndex(); } if (typeof currentSearchIndex === 'undefined') { return []; } const startsWithMatch = props.searchMatch === 'starts-with'; const rawValue = value !== null && value !== void 0 ? value : ''; let searchWords = rawValue.split(/\s+/g).filter(Boolean); if (startsWithMatch) { const hasLetters = /[\p{L}]/u.test(rawValue); const hasNumbers = /[\p{N}]/u.test(rawValue); if (startsWithMatch && snParam && hasNumbers && !hasLetters) { const normalizedNumeric = rawValue.replace(/[^\p{N}]+/gu, ''); searchWords = normalizedNumeric ? [normalizedNumeric] : []; } } const getWordBoundary = wordIndex => startsWithMatch && wordIndex === 0 ? '^' : snParam ? '' : '^|\\s'; const searchWordsData = searchWords.map((word, wordIndex) => { const processedWord = snParam ? word.replace(/[^\p{L}\p{N}]+/gu, '') : escapeRegexChars(word); const wordBoundary = getWordBoundary(wordIndex); return { originalWord: word, processedWord, wordIndex, filterRegex: new RegExp(wordIndex >= inWordIndex ? `${processedWord}` : `(${wordBoundary})${processedWord}`, 'i'), scoreRegex: new RegExp(`(${wordBoundary})${escapeRegexChars(word)}`, 'ig') }; }); const firstWordRegex = searchWords.length > 0 ? new RegExp(`^${escapeRegexChars(searchWords[0])}`, 'i') : null; const findSearchWords = contentChunk => { if (typeof contentChunk !== 'string') { return []; } return searchWordsData.filter(({ filterRegex }) => { if (filterRegex.test(contentChunk)) { return true; } if (snParam && filterRegex.test(contentChunk.replace(/[^0-9]/g, ''))) { return true; } return false; }).map(({ originalWord, wordIndex, scoreRegex }) => { let wordScore = 0; wordScore += (contentChunk.match(scoreRegex) || []).length; if (wordIndex === 0 && firstWordRegex) { const isFirstWord = firstWordRegex.test(contentChunk.split(' ')[0]); if (isFirstWord) { wordScore += searchWords.length + 1; } } return { word: originalWord, wordIndex, wordScore }; }); }; const strS = '\uFFFE'; const strE = '\uFFFF'; const mappedIndex = currentSearchIndex.map(item => { const listOfFoundWords = findSearchWords(item.contentChunk); const allWordsAreNumeric = snParam ? searchWords.every(word => /^[\p{N}\s.,]+$/u.test(word)) : false; const hasMultipleNumericTerms = snParam && searchWords && searchWords.length > 1 && allWordsAreNumeric; if (hasMultipleNumericTerms && listOfFoundWords.length !== searchWords.length) { return { totalScore: 0, item }; } if (typeof item.dataItem === 'string') { item.dataItem = { content: item.dataItem }; } if (!item.dataItem.render) { item.dataItem = { ...item.dataItem }; } item.dataItem.render = (children, id) => { var _props; if (disableHL || disableHighlightingState) { return children; } const cacheHash = id + value; cacheMemoryRef.current = cacheMemoryRef.current || {}; if (cacheMemoryRef.current[cacheHash]) { return cacheMemoryRef.current[cacheHash]; } const isComponent = typeof children !== 'string' && React.isValidElement(children); let childArray; if (isComponent && Array.isArray((_props = children.props) === null || _props === void 0 ? void 0 : _props.children)) { childArray = children.props.children; } else if (!Array.isArray(children)) { childArray = [children]; } else { childArray = children; } const segments = childArray.map(originalChild => ({ originalChild, segment: convertJsxToString(originalChild, ' ') })); const processed = segments.map(({ originalChild, segment }, idx) => { searchWords.forEach((word, wordIndex) => { if (segment) { word = escapeRegexChars(word); if (snParam) { const cleanedWord = word.replace(/[^\p{L}\p{N}]+/gu, ''); if (cleanedWord) { const escapedWord = escapeRegexChars(cleanedWord); segment = segment.replace(new RegExp(`(${escapedWord})`, 'gi'), match => { if (match.includes(strS)) { return match; } return `${strS}${match}${strE}`; }); } } else { if (wordIndex >= inWordIndex) { segment = segment.replace(new RegExp(`(${word})`, 'gi'), `${strS}$1${strE}`); } else { segment = segment.replace(new RegExp(`(${getWordBoundary(wordIndex)})(${word})`, 'gi'), `$1${strS}$2${strE}`); } } } }); let result = segment; if (segment.includes(strS)) { const startRepeatRegex = new RegExp(`(${strS})+`, 'g'); const endRepeatRegex = new RegExp(`(${strE})+`, 'g'); const adjacentRegex = new RegExp(`(${strE}${strS})`, 'g'); const splitRegex = new RegExp(`(${strS}|${strE})`, 'g'); const normalized = segment.replace(startRepeatRegex, strS).replace(endRepeatRegex, strE).replace(adjacentRegex, ''); const tokens = normalized.split(splitRegex).filter(Boolean); let isHighlighted = false; let highlightIndex = 0; const parts = tokens.map(token => { if (token === strS) { isHighlighted = true; return null; } if (token === strE) { isHighlighted = false; return null; } if (isHighlighted) { const key = `highlight-${cacheHash}-${idx}-${highlightIndex++}`; return _jsx("span", { className: "dnb-drawer-list__option__item--highlight", children: token }, key); } return token; }); result = _jsx("span", { children: parts }, cacheHash + idx); } else { result = _jsx("span", { children: segment }, cacheHash + idx); } if (isComponent) { var _element$props; const element = originalChild; if (Array.isArray(element === null || element === void 0 || (_element$props = element.props) === null || _element$props === void 0 ? void 0 : _element$props.children)) { result = element.props.children.map(Comp => { const compEl = Comp; return Comp === originalChild || compEl.props && compEl.props.children === originalChild ? result : Comp; }); } else if (typeof originalChild === 'string') { result = originalChild; } if (React.isValidElement(originalChild)) { result = React.createElement(originalChild.type, { ...originalChild.props, key: 'clone' + cacheHash + idx }, result); } } return result; }); return cacheMemoryRef.current[cacheHash] = processed; }; if (skipFilterRef.current || skipFilter) { return item.dataItem; } let totalScore = listOfFoundWords.length; for (const { wordScore } of listOfFoundWords) { totalScore += wordScore; } return { totalScore, item }; }); if (!skipFilterRef.current && !skipFilter) { const scored = mappedIndex.filter(({ totalScore }) => totalScore); if (!skipReorderRef.current && !skipReorder) { scored.sort(({ totalScore: a }, { totalScore: b }) => b - a); } return scored.map(({ item }) => item.dataItem); } return mappedIndex; }, [setSearchIndex, props.searchMatch, props.searchNumbers, props.searchInWordIndex, disableHighlightingState]); const runFilterToHighlight = useCallback(({ fillDataIfEmpty = false, ...options } = {}, value = inputValueRef.current) => { const possibleTitle = getCurrentDataTitle(drawerListRef.current.selectedItem, drawerListRef.current.originalData); if (value === possibleTitle) { return undefined; } value = String(value || '').trim(); setDisableHighlighting(false); let data = runFilter(value, options); if (fillDataIfEmpty && data.length === 0 && value === '') { data = drawerListRef.current.originalData; } drawerListRef.current.setData(wrapWithShowAll(data)); drawerListRef.current.setState({ cacheHash: value + countData(data) }); return data; }, [runFilter, wrapWithShowAll, countData]); const runFilterWithSideEffects = useCallback((value, options = {}) => { const data = runFilter(value, options); const count = countData(data); if ((value === null || value === void 0 ? void 0 : value.length) > 0) { if (count === 0) { if (modeRef.current !== 'async') { showNoOptionsItem(); } } else if (count > 0) { drawerListRef.current.setData(wrapWithShowAll(data)); drawerListRef.current.setState({ cacheHash: value + count }); if (count === 1) { drawerListRef.current.setState({ activeItem: data[0].__id }); } } } else { var _inputValueRef$curren; if (!keepValue && !keepSelection && !keepValueAndSelection) { totalReset(); } else if (keepValue) { resetSelectedItem(); } showAllItems(); if (((_inputValueRef$curren = inputValueRef.current) === null || _inputValueRef$curren === void 0 ? void 0 : _inputValueRef$curren.length) > 0) { setVisible(); } } if (hasFocusRef.current) { setVisible(); } return data; }, [runFilter, countData, showNoOptionsItem, wrapWithShowAll, keepValue, keepSelection, keepValueAndSelection, totalReset, resetSelectedItem, showAllItems, setVisible]); const showAll = useCallback(() => { resetFilter(); drawerListRef.current.setState({ cacheHash: 'all' }); runFilterToHighlight({ skipFilter: true, fillDataIfEmpty: true }); }, [resetFilter, runFilterToHighlight]); const setVisibleByContext = useCallback((options = {}, onStateComplete = null) => { const skipFilter = showAllNextTime; if (skipFilter) { setShowAllNextTime(false); } runFilterToHighlight({ fillDataIfEmpty: true, skipFilter, ...options }); setVisible(null, onStateComplete); }, [showAllNextTime, runFilterToHighlight, setVisible]); const toggleVisible = useCallback((args = null, onStateComplete = null) => { args = args || {}; if (typeof args.hasFilter === 'undefined') { args.hasFilter = false; } if (disabled) { return undefined; } if (!args.hasFilter && !preventClose && !drawerList.hidden && drawerList.isOpen) { setHidden(null, onStateComplete); } else { setVisibleByContext(null, onStateComplete); } }, [disabled, preventClose, drawerList.hidden, drawerList.isOpen, setHidden, setVisibleByContext]); const toggleVisibleAndFocusOptions = useCallback(() => { drawerListRef.current.toggleVisible(null, isVisible => { if (isVisible) { focusDrawerList(); } }); }, [focusDrawerList]); const hasDatasetChanged = useCallback(rawData => { const { selectedItem } = drawerListRef.current; if (parseFloat(String(selectedItem)) > -1) { const newItem = rawData === null || rawData === void 0 ? void 0 : rawData[selectedItem]; const oldItem = drawerListRef.current.originalData[selectedItem]; if ((newItem === null || newItem === void 0 ? void 0 : newItem.selectedKey) !== (oldItem === null || oldItem === void 0 ? void 0 : oldItem.selectedKey)) { return true; } } return false; }, []); const emptyData = useCallback(() => { cacheMemoryRef.current = {}; clearInputValue(); drawerListRef.current.setData(() => [], () => { setSearchIndex({ overwriteSearchIndex: true }, null); resetActiveItem(); totalReset(); }, { overwriteOriginalData: true }); }, [clearInputValue, setSearchIndex, resetActiveItem, totalReset]); const updateData = useCallback(rawData => { if (rawData === lastUpdateDataRef.current) { return; } lastUpdateDataRef.current = rawData; const hasChanged = hasDatasetChanged(rawData); drawerListRef.current.setState({ cacheHash: 'updateData' }, () => { if (hasChanged) { if (propsRef.current.value && propsRef.current.value !== 'initval') { revalidateSelectedItem(); revalidateInputValue(); } else { resetSelectedItem(); } } }); drawerListRef.current.setData(() => rawData, newData => { setSearchIndex({ overwriteSearchIndex: true, data: newData }, () => { const typed = typedInputValueRef.current; if ((typed === null || typed === void 0 ? void 0 : typed.length) > 0) { const filteredData = runFilterWithSideEffects(typed); if (countData(filteredData) === 0) { if (modeRef.current !== 'async') { showNoOptionsItem(); } } } else { resetActiveItem(); if (drawerListRef.current.open) { showAllItems(); } } }); }, { overwriteOriginalData: true }); }, [hasDatasetChanged, revalidateSelectedItem, revalidateInputValue, resetSelectedItem, setSearchIndex, runFilterWithSideEffects, countData, showNoOptionsItem, resetActiveItem, showAllItems]); const eventMethodsRef = useRef(null); eventMethodsRef.current = { updateData, revalidateSelectedItem, revalidateInputValue, resetSelectedItem, clearInputValue, showAllItems, setVisible, resetInputValue, setHidden, emptyData, focusInput, setInputValue, showNoOptionsItem, showIndicatorItem, showIndicator, hideIndicator, setMode }; function getEventObjects(key) { return { attributes: attributesRef.current, dataList: drawerListRef.current.data, ...eventMethodsRef.current, debounce: (func, cbProps = {}, wait = 250) => { const existingDebouncedFn = debouncedEventFnsRef.current[key]; if (existingDebouncedFn) { return existingDebouncedFn(cbProps); } const newDebouncedFn = debounce(func, wait); debouncedEventFnsRef.current[key] = newDebouncedFn; return newDebouncedFn(cbProps); } }; } const setFocusOnInput = useCallback(() => { suppressFocusHandlerRef.current = true; focusInput(); suppressFocusHandlerRef.current = false; }, [focusInput]); const setVisibleAndFocusOnInput = useCallback(() => { if (!hasFocusRef.current && !hasSelectedText()) { setFocusOnInput(); setVisible(); } }, [setFocusOnInput, setVisible]); const onInputChangeHandler = useCallback(({ value: val, event }) => { selectAllActiveRef.current = false; setTypedInputValue(val); setInputValueState(val); dispatchCustomElementEvent(propsRef.current, 'onType', { value: val, event, ...getEventObjects('onType') }); const trimmed = String(val).trim(); if (trimmed !== inputValueRef.current) { runFilterWithSideEffects(trimmed); } }, [runFilterWithSideEffects]); const onInputKeyDownHandler = useCallback(({ event: e }) => { const key = e.key; switch (key) { case 'PageUp': case 'PageDown': case 'Home': case 'End': e.preventDefault(); break; case 'ArrowUp': case 'ArrowDown': e.preventDefault(); if (!drawerList.open) { setVisible(); } break; case 'Escape': setShowAllNextTime(true); break; case 'Enter': if (e.defaultPrevented) { break; } e.preventDefault(); if (!drawerList.open && hasFilterActive()) { ignoreEvents(); showAll(); } if ((!hasValidData() || !hasSelectedItem()) && !hasActiveItem()) { var _inputValueRef$curren2; dispatchCustomElementEvent(propsRef.current, 'onSubmit', { value: (_inputValueRef$curren2 = inputValueRef.current) !== null && _inputValueRef$curren2 !== void 0 ? _inputValueRef$curren2 : '', event: e, ...getEventObjects('onSubmit') }); toggleVisible(); } else if (!drawerList.open) { setVisible(); } break; } }, [drawerList.open, setVisible, hasFilterActive, ignoreEvents, showAll, hasValidData, hasSelectedItem, hasActiveItem, toggleVisible]); const onInputClickHandler = useCallback(e => { if (!drawerList.open && hasFilterActive()) { ignoreEvents(); showAll(); } const { value } = e.target; setVisibleByContext({ value }); }, [drawerList.open, hasFilterActive, ignoreEvents, showAll, setVisibleByContext]); const onInputFocusHandler = useCallback(event => { if (skipFocusDuringChange) { return undefined; } if (suppressFocusHandlerRef.current) { return undefined; } if (!hasFocusRef.current) { if (openOnFocus && hasValidData()) { const { value } = event.target; setVisibleByContext({ value }); } else { setSearchIndex({}, null); } if (keepValueAndSelection) { showAll(); } if (selectAll) { selectAllActiveRef.current = true; } setHasFocus(true); setHasBlur(false); dispatchCustomElementEvent(propsRef.current, 'onFocus', { event, ...getEventObjects('onFocus') }); } }, [skipFocusDuringChange, openOnFocus, hasValidData, setVisibleByContext, setSearchIndex, keepValueAndSelection, showAll, selectAll, setHasBlur]); const reserveActivityHandler = useCallback(event => { preventFiringBlurEvent.current = Boolean('key' in event && event.key === 'Enter' || (event !== null && event !== void 0 && event.currentTarget ? getClosestParent('dnb-drawer-list', event.currentTarget) || getClosestParent('dnb-input__submit-button__button', event.currentTarget) : false)); if (preventFiringBlurEvent.current) { setTimeout(() => { preventFiringBlurEvent.current = false; }, noAnimation ? 1 : DrawerList.blurDelay); } }, [noAnimation]); const onBlurHandler = useCallback(event => { if (preventFiringBlurEvent.current || drawerList.hasFocusOnElement || hasBlurRef.current) { preventFiringBlurEvent.current = null; return false; } selectAllActiveRef.current = false; setHasBlur(true); setHasFocus(false); if (!keepValue && !keepValueAndSelection) { setTypedInputValue(null); } if (!preventSelection) { const existingValue = inputValueRef.current; resetInputValue(); const resetAfterClose = () => { if (!keepValue || !existingValue || hasSelectedItem()) { resetActiveItem(); } resetFilter(); }; if (noAnimation) { resetAfterClose(); } else { clearTimeout(_blurTimeout.current); _blurTimeout.current = setTimeout(resetAfterClose, DrawerList.blurDelay); } } if (openOnFocus) { setHidden(); } dispatchCustomElementEvent(propsRef.current, 'onBlur', { event, ...getEventObjects('onBlur') }); return undefined; }, [drawerList.hasFocusOnElement, keepValue, keepValueAndSelection, preventSelection, noAnimation, openOnFocus, resetInputValue, hasSelectedItem, resetActiveItem, resetFilter, setHidden, setHasBlur]); const onTriggerKeyDownHandler = useCallback(e => { const key = e.key; switch (key) { case ' ': case 'Enter': { setVisible(); } break; } switch (key) { case ' ': case 'Enter': case 'PageUp': case 'PageDown': case 'ArrowDown': case 'ArrowUp': { e.preventDefault(); focusInput(); } break; } }, [setVisible, focusInput]); const onCloseHandler = useCallback((args = {}) => { const res = dispatchCustomElementEvent(propsRef.current, 'onClose', { ...args, ...getEventObjects('onClose') }); if (res !== false && !closingFromChangeRef.current) { setFocusOnInput(); } return res; }, [setFocusOnInput]); const onSelectHandler = useCallback(args => { if (parseFloat(String(args.activeItem)) > -1) { dispatchCustomElementEvent(propsRef.current, 'onSelect', { ...args, ...getEventObjects('onSelect') }); } }, []); const onPreChangeHandler = useCallback(({ data }) => { if (data && data.showAll) { showAll(); const activeItem = data.lastActiveItem; if (parseFloat(String(activeItem)) > -1) { drawerListRef.current.setActiveItemAndScrollToIt(activeItem, { scrollTo: false }); } setFocusOnInput(); return false; } return undefined; }, [showAll, setFocusOnInput]); const onChangeHandler = useCallback(args => { var _args$data; const selectedItem = args.selectedItem; if (!preventSelection) { if (!keepOpen) { setSkipFocusDuringChange(true); setDisableHighlighting(true); closingFromChangeRef.current = true; setHidden(); focusDrawerList(); closingFromChangeRef.current = false; setSkipFocusDuringChange(false); _focusTimeout.current = setTimeout(() => { setFocusOnInput(); setHasFocus(true); setHasBlur(false); }, 0); } const val = getCurrentDataTitle(selectedItem, drawerListRef.current.data); setInputValue(val); } if (typeof ((_args$data = args.data) === null || _args$data === void 0 ? void 0 : _args$data.render) === 'function') { delete args.data.render; } dispatchCustomElementEvent(propsRef.current, 'onChange', { ...args, ...getEventObjects('onChange') }); }, [preventSelection, keepOpen, setHidden, focusDrawerList, setFocusOnInput, setInputValue, setHasBlur]); if (props.disableHighlighting !== prevDisableHighlightingRef.current) { prevDisableHighlightingRef.current = props.disableHighlighting; setDisableHighlighting(props.disableHighlighting); } if (props.inputValue !== 'initval' && props.inputValue !== prevInputValuePropRef.current) { prevInputValuePropRef.current = props.inputValue; setInputValueState(props.inputValue); } if (props.data !== prevDataRef.current) { var _props$data, _prevDataRef$current; if (((_props$data = props.data) === null || _props$data === void 0 ? void 0 : _props$data.length) > 0 && ((_prevDataRef$current = prevDataRef.current) === null || _prevDataRef$current === void 0 ? void 0 : _prevDataRef$current.length) === 0) { let selectedItem = drawerList.selectedItem; if (props.defaultValue) { selectedItem = props.defaultValue; } if (!props.defaultValue && props.value && props.value !== 'initval') { selectedItem = props.value; } const currentData = getCurrentData(selectedItem, normalizeData(props.data)); const newInputValue = parseContentTitle(currentData, { separator: ' ', preferSelectedValue: true }); setInputValueState(newInputValue); } prevDataRef.current = props.data; dataChangedRef.current = true; } useEffect(() => { if (inputRef && _refInput.current) { if (typeof inputRef === 'function') { inputRef(_refInput.current); } else { inputRef.current = _refInput.current; } } }, [inputRef]); useMountEffect(() => { if (props.open) { runFilterToHighlight({ fillDataIfEmpty: true }); setVisible(); } }); useEffect(() => { if (dataChangedRef.current) { dataChangedRef.current = false; lastUpdateDataRef.current = null; updateData(props.data); if (drawerList.open || hasFocus) { setSearchIndex({ overwriteSearchIndex: true }, () => { runFilterWithSideEffects(inputValueRef.current); }); } } }, [props.data]); useEffect(() => { if (props.value !== prevValueRef.current) { prevValueRef.current = props.value; revalidateSelectedItem(); revalidateInputValue(); } }, [props.value, revalidateSelectedItem, revalidateInputValue]); useMountEffect(() => { return () => { clearTimeout(_selectTimeout.current); clearTimeout(_blurTimeout.current); clearTimeout(_focusTimeout.current); clearTimeout(showAllTimeoutRef.current); }; }); useIsomorphicLayoutEffect(() => { if (selectAllActiveRef.current && _refInput.current) { try { _refInput.current.select(); } catch (e) {} } }); const showStatus = getStatusState(status); const { id, hidden, selectedItem, direction, open } = drawerList; const isExpanded = Boolean(open) && hasValidData(); attributesRef.current = validateDOMAttributes(null, attributes); Object.assign(drawerList.attributes, attributesRef.current); const mainParams = applySpacing(props, { className: clsx("dnb-autocomplete dnb-form-component", className, direction && `dnb-autocomplete--${direction}`, disabled && 'dnb-autocomplete--disabled', open && 'dnb-autocomplete--open', labelDirection && `dnb-autocomplete--${labelDirection}`, iconPosition && `dnb-autocomplete--icon-position-${iconPosition}`, align && `dnb-autocomplete--${align}`, visibleIndicator && 'dnb-autocomplete--show-indicator', size && `dnb-autocomplete--${size}`, stretch && `dnb-autocomplete--stretch`, status && `dnb-autocomplete__status--${statusState}`, showStatus && 'dnb-autocomplete__form-status') }); const shellParams = { className: 'dnb-autocomplete__shell dnb-no-focus', ref: _refShell }; const inputParams = { className: 'dnb-autocomplete__input', id, value: inputValue !== null && inputValue !== void 0 ? inputValue : '', placeholder: undefined, autoCapitalize: 'none', spellCheck: false, autoCorrect: 'off', autoComplete, role: 'combobox', 'aria-autocomplete': 'both', 'aria-controls': isExpanded ? `${id}-ul` : undefined, 'aria-haspopup': 'listbox', 'aria-expanded': isExpanded, onMouseDown: onInputClickHandler, onKeyDown: onInputKeyDownHandler, onChange: onInputChangeHandler, onFocus: onInputFocusHandler, onBlur: onBlurHandler, iconPosition: iconPosition, disabled, skeleton, ...attributes }; if (!(parseFloat(String(selectedItem)) > -1)) { inputParams.placeholder = placeholder || title; } inputParams['aria-placeholder'] = undefined; if (isExpanded) { inputParams['aria-activedescendant'] = drawerList.ariaActiveDescendant; } if (showStatus || suffix) { inputParams['aria-describedby'] = combineDescribedBy(inputParams, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null); } const { iconPosition: _iconPosition, ...customInputParams } = inputParams; let submitButton = false; const triggerParams = { id: id + '-submit-button', disabled, status: status ? statusState : null, onKeyDown: onTriggerKeyDownHandler, onSubmit: toggleVisible, onMouseDown: reserveActivityHandler, 'aria-haspopup': 'listbox', 'aria-expanded': isExpanded, 'aria-label': !hidden ? submitButtonTitle : undefined, tooltip: showSubmitButton ? submitButtonTitle : null }; if (submitElement && React.isValidElement(submitElement)) { submitButton = React.createElement(submitElement.type, { ...submitElement.props, ...triggerParams }); } else if (showSubmitButton) { submitButton = _jsx(SubmitButton, { icon: submitButtonIcon, iconSize: iconSize || (size === 'large' ? 'medium' : 'default'), variant: "secondary", size: size === 'default' ? 'medium' : size, type: "button", status: status, statusState: statusState, statusProps: statusProps, ...triggerParams }); } const currentDataItem = getCurrentData(selectedItem, drawerList.originalData); const innerId = showStatus || suffix || currentDataItem !== null && currentDataItem !== void 0 && currentDataItem.suffixValue ? `${id}-inner` : null; validateDOMAttributes(null, mainParams); validateDOMAttributes(null, shellParams); const voiceOverActiveItem = (() => { const { activeItem, selectedItem } = drawerList; const currentDataItemVO = getCurrentData(activeItem, drawerList.data); return _jsx(AriaLive, { hidden: !IS_MAC, priority: "high", delay: 0, children: currentDataItemVO && _jsxs(_Fragment, { children: [activeItem === selectedItem ? _Fragment2 || (_Fragment2 = _jsxs(_Fragment, { children: [selectedSr, " "] })) : null, _jsx(ItemContent, { children: currentDataItemVO })] }) }); })(); const ariaLiveUpdate = (() => { if (open) { const count = countData(); let newString = null; if (count > 0) { newString = String(props.ariaLiveOptions).replace('%s', String(count)); } else { newString = props.noOptions; } return newString; } return ''; })(); return _jsxs("span", { ...mainParams, children: [label && _jsx(FormLabel, { id: id + '-label', forId: id, text: label, labelDirection: labelDirection, srOnly: labelSrOnly, disabled: disabled, skeleton: skeleton, onClick: toggleVisible }), _jsxs("span", { className: "dnb-autocomplete__inner", ref: _ref, id: innerId, children: [_AlignmentHelper || (_AlignmentHelper = _jsx(AlignmentHelper, {})), _jsx(FormStatus, { show: showStatus, id: id + '-form-status', globalStatus: globalStatus, label: label, textId: id + '-status', text: status, state: statusState, noAnimation: statusNoAnimation, skeleton: skeleton, widthSelector: innerId, ...statusProps }), _jsxs("span", { className: "dnb-autocomplete__row", children: [_jsxs("span", { ...shellParams, children: [CustomInput ? (React.createElement(CustomInput, customInputParams)) : _jsx(Input, { icon: visibleIndicator ? _jsx(ProgressIndicator, { size: size === 'large' ? 'medium' : 'small' }) : icon, iconSize: iconSize || (size === 'large' ? 'medium' : 'default'), size: size, status: status ? statusState : null, statusState: statusState, type: null, innerElement: (currentData