UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

1,384 lines 57.2 kB
import _extends from "@babel/runtime-corejs3/helpers/esm/extends"; import _defineProperty from "@babel/runtime-corejs3/helpers/esm/defineProperty"; var _IconPrimary, _AlignmentHelper; import _pushInstanceProperty from "core-js-pure/stable/instance/push.js"; import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { warn, isTrue, isTouchDevice, makeUniqueId, extendPropsWithContextInClassComponent, validateDOMAttributes, dispatchCustomElementEvent, getStatusState, combineDescribedBy, convertJsxToString, escapeRegexChars, getClosestParent, keycode } from "../../shared/component-helper.js"; import { IS_MAC, IS_WIN, IS_EDGE, debounce, hasSelectedText } from "../../shared/helpers.js"; import AlignmentHelper from "../../shared/AlignmentHelper.js"; import { spacingPropTypes, createSpacingClasses } from "../space/SpacingHelper.js"; import { pickFormElementProps } from "../../shared/helpers/filterValidProps.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 { drawerListPropTypes, parseContentTitle, getCurrentData, getCurrentIndex, normalizeData } from "../../fragments/drawer-list/DrawerListHelpers.js"; export default class Autocomplete extends React.PureComponent { constructor(props) { super(props); this._id = props.id || makeUniqueId(); } render() { return React.createElement(DrawerListProvider, _extends({}, this.props, { id: this._id, data: this.props.data || this.props.children, opened: null, tagName: "dnb-autocomplete", ignore_events: false, prevent_focus: true, skip_keysearch: true }), React.createElement(AutocompleteInstance, _extends({}, this.props, { id: this._id }))); } } _defineProperty(Autocomplete, "defaultProps", { id: null, mode: 'sync', title: 'Option Menu', placeholder: null, no_options: null, show_all: null, aria_live_options: null, indicator_label: null, show_options_sr: null, selected_sr: null, submit_button_title: null, submit_button_icon: 'chevron_down', input_ref: null, icon: null, icon_size: null, icon_position: 'left', triangle_position: null, input_icon: 'loupe', label: null, label_direction: null, label_sr_only: null, keep_value: null, keep_selection: null, keep_value_and_selection: null, show_clear_button: null, status: null, status_state: 'error', status_props: null, status_no_animation: null, globalStatus: null, suffix: null, disable_filter: false, disable_reorder: false, scrollable: true, focusable: false, disable_highlighting: false, max_height: null, direction: 'auto', skip_portal: null, no_animation: false, no_scroll_animation: false, show_submit_button: false, submit_element: null, prevent_selection: false, size: 'default', align_autocomplete: null, options_render: null, data: null, search_in_word_index: null, search_numbers: null, default_value: null, value: 'initval', input_value: 'initval', autoComplete: 'off', open_on_focus: false, prevent_close: false, keep_open: false, opened: null, disabled: null, stretch: null, skeleton: null, portal_class: null, drawer_class: null, page_offset: null, observer_element: null, enable_body_lock: false, className: null, children: null, on_show: null, on_hide: null, on_type: null, on_focus: null, on_blur: null, on_change: null, on_select: null, on_state_update: null, input_element: null }); process.env.NODE_ENV !== "production" ? Autocomplete.propTypes = { ...spacingPropTypes, ...drawerListPropTypes, id: PropTypes.string, mode: PropTypes.oneOf(['sync', 'async']), title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), no_options: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), show_all: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), aria_live_options: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), indicator_label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), show_options_sr: PropTypes.string, selected_sr: PropTypes.string, submit_button_title: PropTypes.string, submit_button_icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), input_ref: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), icon_size: PropTypes.string, icon_position: PropTypes.oneOf(['left', 'right']), triangle_position: PropTypes.oneOf(['left', 'right']), input_icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]), label: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), label_direction: PropTypes.oneOf(['horizontal', 'vertical']), label_sr_only: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), keep_value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), keep_selection: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), keep_value_and_selection: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), show_clear_button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), status: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.func, PropTypes.node]), status_state: PropTypes.string, status_props: PropTypes.object, status_no_animation: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), globalStatus: PropTypes.shape({ id: PropTypes.string, message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]) }), suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), disable_filter: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), disable_reorder: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), scrollable: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), focusable: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), disable_highlighting: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), direction: PropTypes.oneOf(['auto', 'top', 'bottom']), max_height: PropTypes.number, skip_portal: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), no_animation: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), no_scroll_animation: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), show_submit_button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), submit_element: PropTypes.node, prevent_selection: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), size: PropTypes.oneOf(['default', 'small', 'medium', 'large']), align_autocomplete: PropTypes.oneOf(['left', 'right']), options_render: PropTypes.oneOfType([PropTypes.object, PropTypes.func, PropTypes.node]), input_element: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), data: PropTypes.oneOfType([PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node, PropTypes.object]), PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.oneOfType([PropTypes.string, PropTypes.node]), PropTypes.shape({ selectedKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), selected_key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), selected_value: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), suffix_value: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), content: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.arrayOf(PropTypes.string)]) })]))]), search_in_word_index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), searchMatch: PropTypes.oneOf(['word', 'starts-with']), search_numbers: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), default_value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), input_value: PropTypes.string, open_on_focus: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), prevent_close: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), keep_open: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), opened: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), disabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), stretch: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), portal_class: PropTypes.string, drawer_class: PropTypes.string, page_offset: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), observer_element: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), enable_body_lock: PropTypes.bool, class: PropTypes.string, className: PropTypes.string, children: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node, PropTypes.object, PropTypes.array]), on_show: PropTypes.func, on_type: PropTypes.func, on_focus: PropTypes.func, on_blur: PropTypes.func, on_hide: PropTypes.func, on_change: PropTypes.func, on_select: PropTypes.func, on_state_update: PropTypes.func, onClear: PropTypes.func } : void 0; class AutocompleteInstance extends React.PureComponent { static parseDataItem(dataItem) { const searchWord = parseContentTitle(dataItem.search_content || dataItem, { separator: ' ' }); if (typeof searchWord !== 'string' && Array.isArray(searchWord)) { return AutocompleteInstance.parseDataItem(searchWord); } return searchWord; } static createSearchIndex(data) { return data.map(dataItem => { const contentChunk = AutocompleteInstance.parseDataItem(dataItem); return { dataItem, contentChunk }; }); } static getCurrentDataTitle(selected_item, data) { const currentData = getCurrentData(selected_item, data); return parseContentTitle(currentData, { separator: ' ', preferSelectedValue: true }); } static getDerivedStateFromProps(props, state) { if (state._listenForPropChanges) { var _props$data, _state$prevData; state.disableHighlighting = isTrue(props.disable_highlighting); if (props.input_value !== 'initval') { state.inputValue = props.input_value; } if ((props === null || props === void 0 || (_props$data = props.data) === null || _props$data === void 0 ? void 0 : _props$data.length) > 0 && (state === null || state === void 0 || (_state$prevData = state.prevData) === null || _state$prevData === void 0 ? void 0 : _state$prevData.length) === 0) { let selectedItem = state.selected_item; if (props.default_value) { selectedItem = props.default_value; } if (!props.default_value && props.value && props.value !== 'initval') { selectedItem = props.value; } const currentData = getCurrentData(selectedItem, normalizeData(props.data)); state.inputValue = parseContentTitle(currentData, { separator: ' ', preferSelectedValue: true }); } if (props.data !== state.prevData) { state.updateData(props.data); state.prevData = props.data; } } state._listenForPropChanges = true; return state; } constructor(_props, context) { super(_props); _defineProperty(this, "setVisible", (args = null, onStateComplete = null) => { this.wasVisible = true; this.context.drawerList.setWrapperElement(this._ref.current).setVisible(args, onStateComplete); }); _defineProperty(this, "setHidden", (args = null, onStateComplete = null) => { this.context.drawerList.setHidden(args, onStateComplete); this.setState({ hasFocus: false, hasBlur: false }); }); _defineProperty(this, "toggleVisible", (args = null, onStateComplete = null) => { args = args || {}; if (typeof args.hasFilter === 'undefined') { args.hasFilter = false; } if (isTrue(this.props.disabled)) { return; } if (!args.hasFilter && !isTrue(this.props.prevent_close) && !this.context.drawerList.hidden && this.context.drawerList.isOpen) { this.setHidden(null, onStateComplete); } else { this.setVisibleByContext(null, onStateComplete); } }); _defineProperty(this, "toggleVisibleAndFocusOptions", () => { this.context.drawerList.toggleVisible(null, isVisible => { if (isVisible) { this.focusDrawerList(); } }); }); _defineProperty(this, "setVisibleByContext", (options = {}, onStateComplete = null) => { const skipFilter = this.state.showAllNextTime; if (skipFilter) { this.setState({ showAllNextTime: false, _listenForPropChanges: false }); } this.runFilterToHighlight({ fillDataIfEmpty: true, skipFilter, ...options }); this.setVisible(null, onStateComplete); }); _defineProperty(this, "onInputChangeHandler", ({ value, event }) => { this.setState({ typedInputValue: value, inputValue: value, _listenForPropChanges: false }); dispatchCustomElementEvent(this, 'on_type', { value, event, ...this.getEventObjects('on_type') }); value = String(value).trim(); if (value !== this.state.inputValue) { this.runFilterWithSideEffects(value); } }); _defineProperty(this, "runFilterWithSideEffects", (value, options = {}) => { const data = this.runFilter(value, options); const count = this.countData(data); const { keep_value, keep_selection, keep_value_and_selection } = this.props; if ((value === null || value === void 0 ? void 0 : value.length) > 0) { if (count === 0) { if (this.state.mode !== 'async') { this.showNoOptionsItem(); } } else if (count > 0) { this.context.drawerList.setData(this.wrapWithShowAll(data)); this.context.drawerList.setState({ cache_hash: value + count }); if (count === 1) { this.context.drawerList.setState({ active_item: data[0].__id }); } } } else { var _this$state$inputValu; if (!isTrue(keep_value) && !isTrue(keep_selection) && !isTrue(keep_value_and_selection)) { this.totalReset(); } else if (isTrue(keep_value)) { this.resetSelectedItem(); } this.showAllItems(); if (((_this$state$inputValu = this.state.inputValue) === null || _this$state$inputValu === void 0 ? void 0 : _this$state$inputValu.length) > 0) { this.setVisible(); } } if (this.state.hasFocus) { this.setVisible(); } return data; }); _defineProperty(this, "runFilterToHighlight", ({ fillDataIfEmpty = false, ...options } = {}, value = this.state.inputValue) => { const possibleTitle = AutocompleteInstance.getCurrentDataTitle(this.context.drawerList.selected_item, this.context.drawerList.original_data); if (value === possibleTitle) { return; } value = String(value || '').trim(); this.setState({ disableHighlighting: false, _listenForPropChanges: false }); let data = this.runFilter(value, options); if (fillDataIfEmpty && data.length === 0 && value === '') { data = this.context.drawerList.original_data; } this.context.drawerList.setData(this.wrapWithShowAll(data)); this.context.drawerList.setState({ cache_hash: value + this.countData(data) }); return data; }); _defineProperty(this, "wrapWithShowAll", data => { if (!data || !this.hasFilterActive(data)) { return data; } const lastItem = this.context.drawerList.original_data.slice(-1)[0]; if (lastItem && !lastItem.show_all) { const lastActiveItem = data.slice(-1)[0]; if (lastActiveItem) { _pushInstanceProperty(data).call(data, { __id: lastItem.__id + 1, lastActiveItem: lastActiveItem.__id, class_name: 'dnb-autocomplete__show-all', show_all: true, active_item: false, selected_item: false, content: React.createElement(React.Fragment, null, _IconPrimary || (_IconPrimary = React.createElement(IconPrimary, { icon: "arrow_down" })), this._props.show_all) }); } } return data; }); _defineProperty(this, "setInputValue", inputValue => { this.setState({ inputValue, _listenForPropChanges: false }); }); _defineProperty(this, "emptyData", () => { this._cacheMemory = {}; this.clearInputValue(); this.context.drawerList.setData(() => [], () => { this.setSearchIndex({ overwriteSearchIndex: true }); this.resetActiveItem(); this.totalReset(); }, { overwriteOriginalData: true }); }); _defineProperty(this, "clearInputValue", () => { this.setState({ inputValue: '', typedInputValue: null, _listenForPropChanges: false }); }); _defineProperty(this, "resetInputValue", () => { const { input_value, keep_value, keep_value_and_selection } = this.props; if (isTrue(keep_value) || isTrue(keep_value_and_selection) || input_value !== 'initval' && input_value.length > 0) { return; } clearTimeout(this._selectTimeout); this._selectTimeout = setTimeout(() => { if (this.hasSelectedItem()) { const inputValue = AutocompleteInstance.getCurrentDataTitle(this.context.drawerList.selected_item, this.context.drawerList.original_data); this.setInputValue(inputValue); } else { this.clearInputValue(); } }, 1); }); _defineProperty(this, "showNoOptionsItem", () => { this.resetActiveItem(); this.ignoreEvents(); this.context.drawerList.setData(this.props.no_options === false ? [] : [{ class_name: 'dnb-autocomplete__no-options', content: this._props.no_options, ignore_events: true, __id: 'no_options' }]); this.context.drawerList.setState({ cache_hash: 'no_options' }); this.setVisible(); }); _defineProperty(this, "showIndicatorItem", () => { this.resetActiveItem(); this.ignoreEvents(); this.context.drawerList.setData([{ class_name: 'dnb-autocomplete__indicator', content: React.createElement(ProgressIndicator, { label: this._props.indicator_label }), ignore_events: true, __id: 'indicator' }]); this.context.drawerList.setState({ cache_hash: 'indicator' }); this.setVisible(); }); _defineProperty(this, "showIndicator", () => { if (!this.state.visibleIndicator) { this.setState({ visibleIndicator: true, _listenForPropChanges: false }); } }); _defineProperty(this, "hideIndicator", () => { this.setState({ visibleIndicator: false, _listenForPropChanges: false }); }); _defineProperty(this, "setMode", mode => { this.setState({ mode, _listenForPropChanges: false }); }); _defineProperty(this, "revalidateInputValue", () => { const { input_value, value } = this.props; if (input_value && input_value !== 'initval') { return; } const selected_item = getCurrentIndex(value, this.context.drawerList.original_data); const inputValue = AutocompleteInstance.getCurrentDataTitle(selected_item, this.context.drawerList.original_data); this.setInputValue(inputValue); }); _defineProperty(this, "revalidateSelectedItem", () => { const selected_item = getCurrentIndex(this.props.value, this.context.drawerList.original_data); this.context.drawerList.setState({ selected_item }); }); _defineProperty(this, "hasDatasetChanged", rawData => { const { selected_item } = this.context.drawerList; if (parseFloat(selected_item) > -1) { const newItem = rawData === null || rawData === void 0 ? void 0 : rawData[selected_item]; const oldItem = this.context.drawerList.original_data[selected_item]; if (typeof (newItem === null || newItem === void 0 ? void 0 : newItem.selectedKey) !== 'undefined' ? (newItem === null || newItem === void 0 ? void 0 : newItem.selectedKey) !== (oldItem === null || oldItem === void 0 ? void 0 : oldItem.selectedKey) : (newItem === null || newItem === void 0 ? void 0 : newItem.selected_key) !== (oldItem === null || oldItem === void 0 ? void 0 : oldItem.selected_key)) { return true; } } return false; }); _defineProperty(this, "updateData", rawData => { const hasChanged = this.hasDatasetChanged(rawData); this.context.drawerList.setState({ cache_hash: 'updateData' }, () => { if (hasChanged) { const { value } = this.props; if (value && value !== 'initval') { this.revalidateSelectedItem(); this.revalidateInputValue(); } else { this.resetSelectedItem(); } } }); this.context.drawerList.setData(() => rawData, newData => { this.setSearchIndex({ overwriteSearchIndex: true, data: newData }, () => { const { typedInputValue } = this.state; if ((typedInputValue === null || typedInputValue === void 0 ? void 0 : typedInputValue.length) > 0) { const filteredData = this.runFilterWithSideEffects(typedInputValue); if (this.countData(filteredData) === 0) { if (this.state.mode !== 'async') { this.showNoOptionsItem(); } } } else { this.resetActiveItem(); if (this.context.drawerList.opened) { this.showAllItems(); } } }); }, { overwriteOriginalData: true }); return this; }); _defineProperty(this, "onInputKeyDownHandler", ({ event: e }) => { const key = keycode(e); switch (key) { case 'page up': case 'page down': case 'home': case 'end': case 'down': case 'up': e.preventDefault(); break; } switch (key) { case 'up': case 'down': if (!this.context.drawerList.opened) { this.setVisible(); } break; case 'escape': case 'esc': this.setState({ showAllNextTime: true, _listenForPropChanges: false }); break; case 'enter': e.preventDefault(); if (!this.context.drawerList.opened && this.hasFilterActive()) { this.ignoreEvents(); this.showAll(); } if ((!this.hasValidData() || !this.hasSelectedItem()) && !this.hasActiveItem()) { this.toggleVisible(); } else { this.setVisible(); } break; } }); _defineProperty(this, "onInputClickHandler", e => { if (!this.context.drawerList.opened && this.hasFilterActive()) { this.ignoreEvents(); this.showAll(); } const { value } = e.target; this.setVisibleByContext({ value }); }); _defineProperty(this, "onInputFocusHandler", event => { if (this.state.skipFocusDuringChange) { return; } const { open_on_focus, keep_value_and_selection } = this.props; if (!this.state.hasFocus) { if (isTrue(open_on_focus) && this.hasValidData()) { const { value } = event.target; this.setVisibleByContext({ value }); } else { this.setSearchIndex(); } if (isTrue(keep_value_and_selection)) { this.showAll(); } this.setState({ hasFocus: true, hasBlur: false }); dispatchCustomElementEvent(this, 'on_focus', { event, ...this.getEventObjects('on_focus') }); } }); _defineProperty(this, "reserveActivityHandler", (event = null) => { this.__preventFiringBlurEvent = Boolean(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 (this.__preventFiringBlurEvent) { setTimeout(() => { this.__preventFiringBlurEvent = false; }, isTrue(this.props.no_animation) ? 1 : DrawerList.blurDelay); } }); _defineProperty(this, "onBlurHandler", event => { if (this.__preventFiringBlurEvent || this.context.drawerList.hasFocusOnElement || this.state.hasBlur) { this.__preventFiringBlurEvent = null; return false; } const { open_on_focus, keep_value, keep_value_and_selection, prevent_selection, no_animation } = this.props; this.setState({ hasBlur: true, hasFocus: false }); if (!isTrue(keep_value) && !isTrue(keep_value_and_selection)) { this.setState({ typedInputValue: null, _listenForPropChanges: false }); } if (!isTrue(prevent_selection)) { const existingValue = this.state.inputValue; this.resetInputValue(); const resetAfterClose = () => { if (!isTrue(keep_value) || !existingValue || this.hasSelectedItem()) { this.resetActiveItem(); } this.resetFilter(); }; if (isTrue(no_animation)) { resetAfterClose(); } else { clearTimeout(this._blurTimeout); this._blurTimeout = setTimeout(resetAfterClose, DrawerList.blurDelay); } } if (isTrue(open_on_focus)) { this.setHidden(); } dispatchCustomElementEvent(this, 'on_blur', { event, ...this.getEventObjects('on_blur') }); }); _defineProperty(this, "onTriggerKeyDownHandler", e => { const key = keycode(e); switch (key) { case 'space': case 'enter': { this.setVisible(); } break; } switch (key) { case 'space': case 'enter': case 'page up': case 'page down': case 'down': case 'up': { e.preventDefault(); this.focusInput(); } break; } }); _defineProperty(this, "focusDrawerList", () => { try { this.context.drawerList._refUl.current.focus({ preventScroll: true }); } catch (e) {} }); _defineProperty(this, "focusInput", () => { try { this._refInput.current._ref.current.focus({ preventScroll: true }); } catch (e) { warn(e); } }); _defineProperty(this, "getEventObjects", key => { const attributes = this.attributes; return { attributes, dataList: this.context.drawerList.data, updateData: this.updateData, revalidateSelectedItem: this.revalidateSelectedItem, revalidateInputValue: this.revalidateInputValue, resetSelectedItem: this.resetSelectedItem, clearInputValue: this.clearInputValue, showAllItems: this.showAllItems, setVisible: this.setVisible, resetInputValue: this.resetInputValue, setHidden: this.setHidden, emptyData: this.emptyData, focusInput: this.focusInput, setInputValue: this.setInputValue, showNoOptionsItem: this.showNoOptionsItem, showIndicatorItem: this.showIndicatorItem, showIndicator: this.showIndicator, hideIndicator: this.hideIndicator, setMode: this.setMode, debounce: (func, props = {}, wait = 250) => { this.dbf = this.dbf || {}; return (this.dbf[key] || (this.dbf[key] = debounce(func, wait, { context: this })))(props); } }; }); _defineProperty(this, "hasInjectedDataItem", (data = this.context.drawerList.data) => { const lastItem = data.slice(-1)[0]; return lastItem ? lastItem.show_all || lastItem.__id === 'no_options' : false; }); _defineProperty(this, "countData", (data = this.context.drawerList.data) => { const count = data.length; return count > 0 && this.hasInjectedDataItem(data) ? count - 1 : count; }); _defineProperty(this, "hasValidData", (data = this.context.drawerList.data) => { if (this.countData(data) > 0) { const first = data[0]; if (!first.show_all && !['no_options', 'indicator'].includes(first.__id)) { return true; } } return false; }); _defineProperty(this, "hasSelectedItem", () => { return parseFloat(this.context.drawerList.selected_item) > -1; }); _defineProperty(this, "hasActiveItem", () => { return parseFloat(this.context.drawerList.active_item) > -1; }); _defineProperty(this, "hasFilterActive", (data = this.context.drawerList.data) => { return !(this.context.drawerList.original_data && this.context.drawerList.original_data.length === this.countData(data)); }); _defineProperty(this, "ignoreEvents", () => { clearTimeout(this.showAllTimeout); this.context.drawerList.setState({ ignore_events: true }, () => { this.showAllTimeout = setTimeout(() => { this.context && this.context.drawerList && this.context.drawerList.setState({ ignore_events: false }); }, 10); }); }); _defineProperty(this, "showAll", () => { this.resetFilter(); this.context.drawerList.setState({ cache_hash: 'all' }); this.runFilterToHighlight({ skipFilter: true, fillDataIfEmpty: true }); }); _defineProperty(this, "showAllItems", () => { this.resetFilter(); this.context.drawerList.setState({ cache_hash: 'all' }); this.context.drawerList.setActiveItemAndScrollToIt(this.context.drawerList.selected_item, { scrollTo: false }); }); _defineProperty(this, "totalReset", () => { this.setState({ inputValue: null, typedInputValue: null, _listenForPropChanges: false }); this.resetActiveItem(); this.resetSelectedItem(); }); _defineProperty(this, "resetActiveItem", () => { this.context.drawerList.setState({ active_item: null }); }); _defineProperty(this, "resetSelectedItem", () => { const hasHadValue = this.hasSelectedItem(); this.context.drawerList.setState({ selected_item: null }, () => { if (hasHadValue) { dispatchCustomElementEvent(this, 'on_change', { ...this.getEventObjects('on_change') }); } }); }); _defineProperty(this, "resetFilter", () => { this.context.drawerList.setData(this.context.drawerList.original_data); }); _defineProperty(this, "runFilter", (value, { data = null, searchIndex = this.state.searchIndex, searchNumbers = isTrue(this.props.search_numbers), inWordIndex = parseFloat((_this$props$search_in => (_this$props$search_in = this.props.search_in_word_index) !== null && _this$props$search_in !== void 0 ? _this$props$search_in : this.skipFilter ? 1 : 3)()) - 1, disableHighlighting = false, skipFilter = false, skipReorder = false } = {}) => { if (data) { searchIndex = this.setSearchIndex({ data }); } else if (!searchIndex) { searchIndex = this.setSearchIndex(); } if (typeof searchIndex === 'undefined') { return []; } const startsWithMatch = this.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 && searchNumbers && hasNumbers && !hasLetters) { const normalizedNumeric = rawValue.replace(/[^\p{N}]+/gu, ''); searchWords = normalizedNumeric ? [normalizedNumeric] : []; } } const getWordBoundary = wordIndex => startsWithMatch && wordIndex === 0 ? '^' : searchNumbers ? '' : '^|\\s'; const searchWordsData = searchWords.map((word, wordIndex) => { const processedWord = searchNumbers ? 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, processedWord }) => { if (filterRegex.test(contentChunk)) { return true; } if (searchNumbers && 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'; searchIndex = searchIndex.map((item, i) => { const listOfFoundWords = findSearchWords(item.contentChunk, i); const allWordsAreNumeric = searchNumbers ? searchWords.every(word => /^[\p{N}\s.,]+$/u.test(word)) : false; const hasMultipleNumericTerms = searchNumbers && 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 _children2; if (disableHighlighting || this.state.disableHighlighting) { return children; } const cacheHash = id + value; this._cacheMemory = this._cacheMemory || {}; if (this._cacheMemory[cacheHash]) { return this._cacheMemory[cacheHash]; } const isComponent = typeof children !== 'string' && React.isValidElement(children); if (isComponent && Array.isArray((_children2 = children) === null || _children2 === void 0 || (_children2 = _children2.props) === null || _children2 === void 0 ? void 0 : _children2.children)) { children = children.props.children; } else if (!Array.isArray(children)) { children = [children]; } children = children.map(originalChild => ({ originalChild, segment: convertJsxToString(originalChild, ' ') })); children = children.map(({ originalChild, segment }, idx) => { searchWords.forEach((word, wordIndex) => { if (segment) { word = escapeRegexChars(word); if (searchNumbers) { 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 React.createElement("span", { key: key, className: "dnb-drawer-list__option__item--highlight" }, token); } return token; }); result = React.createElement("span", { key: cacheHash + idx }, parts); } else { result = React.createElement("span", { key: cacheHash + idx }, segment); } if (isComponent) { var _originalChild$props; if (Array.isArray(originalChild === null || originalChild === void 0 || (_originalChild$props = originalChild.props) === null || _originalChild$props === void 0 ? void 0 : _originalChild$props.children)) { result = originalChild.props.children.map(Comp => { return Comp === originalChild || Comp.props && Comp.props.children === originalChild ? result : Comp; }); } else if (typeof originalChild === 'string') { result = originalChild; } if (React.isValidElement(originalChild)) { result = React.cloneElement(originalChild, { key: 'clone' + cacheHash + idx }, result); } } return result; }); return this._cacheMemory[cacheHash] = children; }; if (this.skipFilter || skipFilter) { return item.dataItem; } let totalScore = listOfFoundWords.length; for (const { wordScore } of listOfFoundWords) { totalScore += wordScore; } return { totalScore, item }; }); if (!this.skipFilter && !skipFilter) { searchIndex = searchIndex.filter(({ totalScore }) => totalScore); if (!this.skipReorder && !skipReorder) { searchIndex = searchIndex.sort(({ totalScore: a }, { totalScore: b }) => b - a); } searchIndex = searchIndex.map(({ item }) => item.dataItem); } return searchIndex; }); _defineProperty(this, "onHideHandler", (args = {}) => { const res = dispatchCustomElementEvent(this, 'on_hide', { ...args, ...this.getEventObjects('on_hide') }); if (res !== false) { this.setFocusOnInput(); } return res; }); _defineProperty(this, "setVisibleAndFocusOnInput", () => { if (!this.state.hasFocus && !hasSelectedText()) { this.setFocusOnInput(); this.setVisible(); } }); _defineProperty(this, "onSelectHandler", args => { if (parseFloat(args.active_item) > -1) { dispatchCustomElementEvent(this, 'on_select', { ...args, ...this.getEventObjects('on_select') }); } }); _defineProperty(this, "onPreChangeHandler", ({ data }) => { if (data && data.show_all) { this.showAll(); const active_item = data.lastActiveItem; if (parseFloat(active_item) > -1) { this.context.drawerList.setActiveItemAndScrollToIt(active_item, { scrollTo: false }); } this.setFocusOnInput(); return false; } }); _defineProperty(this, "onChangeHandler", args => { var _args$data; const selected_item = args.selected_item; const { prevent_selection, keep_open } = this.props; if (!isTrue(prevent_selection)) { if (!isTrue(keep_open)) { this.setState({ skipFocusDuringChange: true, disableHighlighting: true, _listenForPropChanges: false }); this.setHidden(); this.focusDrawerList(); this.setState({ skipFocusDuringChange: false, _listenForPropChanges: false }, () => this.setFocusOnInput()); } const inputValue = AutocompleteInstance.getCurrentDataTitle(selected_item, this.context.drawerList.data); this.setInputValue(inputValue); } if (typeof ((_args$data = args.data) === null || _args$data === void 0 ? void 0 : _args$data.render) === 'function') { delete args.data.render; } dispatchCustomElementEvent(this, 'on_change', { ...args, ...this.getEventObjects('on_change') }); }); this.attributes = {}; this.state = this.state || {}; this.state._listenForPropChanges = true; this.state.mode = _props.mode; this.state.prevData = _props.data; this.state.updateData = this.updateData; if (context.drawerList && context.drawerList.current_title) { this.state.inputValue = context.drawerList.current_title; } this._ref = React.createRef(); this._refShell = React.createRef(); this._refInput = React.createRef(); this.isTouchDevice = isTouchDevice(); this.skipFilter = isTrue(_props.disable_filter); this.skipReorder = isTrue(_props.disable_reorder); this.wasVisible = false; } componentDidMount() { if (isTrue(this.props.opened)) { this.runFilterToHighlight({ fillDataIfEmpty: true }); this.setVisible(); } } componentDidUpdate(prevProps) { if ((this.context.drawerList.opened || this.state.hasFocus) && prevProps.data !== this.props.data) { this.setSearchIndex({ overwriteSearchIndex: true }, () => { this.runFilterWithSideEffects(this.state.inputValue); }); } if (prevProps.value !== this.props.value) { this.revalidateSelectedItem(); this.revalidateInputValue(); } } componentWillUnmount() { clearTimeout(this._selectTimeout); clearTimeout(this._focusTimeout); clearTimeout(this._blurTimeout); } setSearchIndex({ overwriteSearchIndex = false, data = this.context.drawerList.original_data } = {}, cb) { this._cacheMemory = {}; if (!overwriteSearchIndex && this.state.searchIndex) { return this.state.searchIndex; } const searchIndex = AutocompleteInstance.createSearchIndex(data); this.setState({ searchIndex, _listenForPropChanges: false }, cb); return searchIndex; } setFocusOnInput() { this.setState({ hasFocus: true }, () => { this.focusInput(); this.setState({ hasFocus: false }); }); } getAriaLiveUpdate() { const { opened } = this.context.drawerList; if (opened) { const { aria_live_options, no_options } = this._props; const count = this.countData(); let newString = null; if (count > 0) { newString = String(aria_live_options).replace('%s', count); } else { newString = no_options; } return newString; } return ''; } getVoiceOverActiveItem(selected_sr) { const { active_item, selected_item } = this.context.drawerList; const currentDataItem = getCurrentData(active_item, this.context.drawerList.data); return React.createElement(AriaLive, { hidden: !IS_MAC, priority: "high", delay: 0 }, currentDataItem && React.createElement(React.Fragment, null, active_item === selected_item ? React.createElement(React.Fragment, null, selected_sr, " ") : null, React.createElement(ItemContent, null, currentDataItem))); } render() { var _this$context, _this$context2; const props = this._props = extendPropsWithContextInClassComponent(this.props, Autocomplete.defaultProps, this.context.getTranslation(this.props).Autocomplete, pickFormElementProps((_this$context = this.context) === null || _this$context === void 0 ? void 0 : _this$context.FormRow), pickFormElementProps((_this$context2 = this.context) === null || _this$context2 === void 0 ? void 0 : _this$context2.formElement), this.context.Autocomplete); const { title, placeholder, label, label_direction, label_sr_only, icon, icon_size, input_icon, size, align_autocomplete, fixed_position, status, status_state, status_props, status_no_animation, globalStatus, suffix, scrollable, focusable, keep_open, keep_value, keep_value_and_selection, show_clear_button, prevent_close, no_animation, no_scroll_animation, show_submit_button, submit_element, input_element: CustomInput, options_render, prevent_selection, max_height, default_value, search_numbers, search_in_word_index, searchMatch, show_options_sr, selected_sr, submit_button_title, submit_button_icon, portal_class, drawer_class, input_ref, className, disabled, stretch, skeleton, triangle_position, icon_position, skip_portal, independent_width, autoComplete, mode: _mode, data: _data, children: _children, direction: _direction, id: _id, opened: _opened, value: _value, input_value: _input_value, indicator_label, no_options, show_all, aria_live_options, disable_highlighting, onClear, ...attributes } = props; const showStatus = getStatusState(status); const { inputValue, visibleIndicator } = this.state; const { id, hidden, selected_item, direction, opened } = this.context.drawerList; const isExpanded = Boolean(opened) && this.hasValidData(); delete attributes.onBlur; this.attributes = validateDOMAttributes(null, attributes); Object.assign(this.context.drawerList.attributes, this.attributes); const mainParams = { className: classnames("dnb-autocomplete dnb-form-component", createSpacingClasses(props), className, direction && `dnb-autocomplete--${direction}`, disabled && 'dnb-autocomplete--disabled', opened && 'dnb-autocomplete--opened', label_direction && `dnb-autocomplete--${label_direction}`, icon_position && `dnb-autocomplete--icon-position-${icon_position}`, align_autocomplete && `dnb-autocomplete--${align_autocomplete}`, visibleInd