@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
JavaScript
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