UNPKG

@kubit-ui-web/react-components

Version:

Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience

208 lines 8.78 kB
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { useCustomHeightFromChildrens } from '../../../hooks/useCustomHeightFromChildrens/useCustomHeightFromChildren'; import { useInput } from '../../../hooks/useInput/useInput'; import { useMediaDevice } from '../../../hooks/useMediaDevice/useMediaDevice'; import { isArrowDownPressed, isArrowUpPressed, isKeyEnterPressed, isKeyEscapePressed, } from '../../../utils/keyboard/keyboard.utility'; import { matchInputValue } from '../../../utils/maskUtility/mask.utility'; import { dispatchSyntheticEvent } from '../../../utils/syntheticComponents/syntheticEvent/syntheticEvent'; import { useInternalValidations } from '../../input/hooks/useInternalValidations'; import { InternalErrorType } from '../../input/types/internalErrors'; // helpers import { filterOptions, hasMatchWithOptions } from '../helpers/filterOptions'; export const useInputSearch = ({ executeInternalOpenOptions = true, ...props }) => { const device = useMediaDevice(); const [openOptions, setOpenOptions] = useState(props.open); const [searchText, setSearchText] = useState(props.value ?? ''); const [inputPopoverText, setInputPopoverText] = useState(''); const [showHighlightedOption, setShowHighlightedOption] = useState(!!props.highlightedOption); // References const iconRef = useRef(); const { internalErrors, addInternalError, removeInternalError } = useInternalValidations(props.type, undefined, props.onInternalErrors); // Methods const handleValueSelected = (value) => { setSearchText(value); setInputPopoverText(value); props.onOptionClick?.(value); removeInternalError(InternalErrorType.INVALID_OPTION); showHighlightedOption && setShowHighlightedOption(false); const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; if (props.onChange && inputRef?.current && isBrowser) { // Need to have the current input value into dispatch synthetic event inputRef.current.value = value; const event = dispatchSyntheticEvent({ element: inputRef.current, eventType: 'change', }); props.onChange(event); } }; const handleOpenOptions = (open) => { setOpenOptions(prevOpen => { if (prevOpen !== open) { props.onPopoverOpen?.(open); } return open; }); }; // Input search handlers const handleChangeInputSearch = event => { handleOpenOptions(true); if (props.regex) { const newMaskedValue = matchInputValue(searchText, event.target.value, props.regex); event.target.value = newMaskedValue; } const newSearchText = event.target.value; setSearchText(newSearchText); setInputPopoverText(newSearchText); showHighlightedOption && setShowHighlightedOption(false); removeInternalError(InternalErrorType.INVALID_OPTION); props.onChange?.(event); }; const handleClickInputSearch = event => { handleOpenOptions(!openOptions); props.onClick?.(event); }; const handleIconClick = (e) => { executeInternalOpenOptions && handleOpenOptions(!openOptions); props.onIconClick?.(e); }; const handleRightIconClick = (e) => { executeInternalOpenOptions && handleOpenOptions(!openOptions); props.onRightIconClick?.(e); }; const handleInputKeyDown = event => { if (isKeyEscapePressed(event.key)) { handleOpenOptions(false); } // Focus first element of the list if (isArrowDownPressed(event.key)) { optionsListCollectionRef?.current?.[0]?.firstElementChild?.focus(); event.preventDefault(); } props.onKeyDown?.(event); }; const handleInputBlur = event => { props.onBlur?.(event); const allOptions = props.options.map(option => option.options).flat(); const hasMatch = hasMatchWithOptions(searchText, allOptions, props.caseSensitive); // if the input loses focus and there is no valid option in the input value, show an error message if (!hasMatch && !props.hasResultTextWrittenByUser && !props.disableErrorInvalidOption) { addInternalError(InternalErrorType.INVALID_OPTION); } }; // Input popover handlers const handleInputPopoverChange = event => { props.onChange?.(event); setInputPopoverText(event.target.value); }; const handleInputPopoverKeyDown = event => { if (isKeyEnterPressed(event.key) && props.onInputPopoverEnterKeyDown) { props.onInputPopoverEnterKeyDown(event); } // Focus first element of the list if (isArrowDownPressed(event.key)) { optionsListCollectionRef?.current?.[0]?.firstElementChild?.focus(); event.preventDefault(); } }; const handleInputPopoverIconClick = () => { props.onInputPopoverIconClick?.(); if (props.clearTextInputPopoverIconClick) { setInputPopoverText(''); } }; // OnKeyDown list const handleOptionsListKeyDown = event => { let selectedOption = 0; const options = document.querySelectorAll('[role="option"]'); const getSelectedIndex = (operator) => { event.preventDefault(); for (let i = 0; i < options.length; i++) { if (options[i] === document.activeElement) { selectedOption = i + operator; } } // Focus the item options[selectedOption]?.focus(); }; // When arrow down if (isArrowDownPressed(event.key)) { getSelectedIndex(1); } else if (isArrowUpPressed(event.key)) { // When arrow up getSelectedIndex(-1); } }; // Input Basic hook const { state, inputRef, handleBlurInternal, handleFocusInternal, handlePasteInternal } = useInput({ internalErrorExecution: props.internalErrorExecution, ref: props.ref, disabled: props.disabled, error: props.error || internalErrors.length > 0, maxLength: props.maxLength, // need for update the state currentValue: searchText, informationAssociated: props.informationAssociated, disabledCopyAndPaste: props.disabledCopyAndPaste, onBlur: handleInputBlur, onFocus: props.onFocus, onInternalErrors: props.onInternalErrors, onPaste: props.onPaste, }); const useActionBottomSheet = useMemo(() => props.styles?.[state]?.useActionBottomSheet?.[device], [state, device]); // Returns references (OptionsList Collection, ActionBottomSheet and InputPopover) and OptionsList height const { optionsListRefCollection, optionsListCollectionRef, height: listOptionsHeight, } = useCustomHeightFromChildrens({ limit: props.elementsToShow, observer: [props.options, searchText, inputPopoverText], shouldCalculateHeight: !useActionBottomSheet, }); // Uses effects // Update value when new prop value useEffect(() => { setSearchText(props.value ?? ''); setInputPopoverText(props.value ?? ''); }, [props.value]); // Open or close the popover from external props useEffect(() => { handleOpenOptions(props.open); }, [props.open]); // Set focus on popover input when show const actionBottomSheetRefCb = useCallback(node => { node?.querySelector('input')?.focus(); }, []); // references const ref = { refInput: inputRef, refList: optionsListRefCollection, refIcon: iconRef, refActionBottomSheet: actionBottomSheetRefCb, }; // Filter options const { optionsFiltered } = filterOptions(useActionBottomSheet ? inputPopoverText : searchText, props.options, props.searchFilterConfig?.wordSeparator, props.searchFilterConfig?.suggestInit); return { openOptions, searchText, inputPopoverText, optionsFiltered, showHighlightedOption, handleOpenOptions, handleClickInputSearch, handleIconClick, handleRightIconClick, handleInputPopoverIconClick, handleValueSelected, handleChangeInputSearch, handleInputKeyDown, handleInputPopoverKeyDown, state, ref, listOptionsHeight, handleBlurInternal, handleFocusInternal, handleInputPopoverChange, handleOptionsListKeyDown, handlePasteInternal, }; }; //# sourceMappingURL=useInputSearch.js.map