@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
1,456 lines (1,455 loc) • 52.8 kB
JavaScript
"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