@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
275 lines • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useInputDropdown = void 0;
const react_1 = require("react");
const useCustomHeightFromChildren_1 = require("../../../hooks/useCustomHeightFromChildrens/useCustomHeightFromChildren");
const useInput_1 = require("../../../hooks/useInput/useInput");
const useMediaDevice_1 = require("../../../hooks/useMediaDevice/useMediaDevice");
const role_1 = require("../../../types/role/role");
const keyboard_utility_1 = require("../../../utils/keyboard/keyboard.utility");
const syntheticSelect_1 = require("../../../utils/syntheticComponents/syntheticSelect/syntheticSelect");
const useInternalValidations_1 = require("../../input/hooks/useInternalValidations");
const internalErrors_1 = require("../../input/types/internalErrors");
// helpers
const filterOptions_1 = require("../helpers/filterOptions");
const useInputDropdown = (props) => {
// States
const [openOptions, setOpenOptions] = (0, react_1.useState)(props.open);
const [valueSearchSelected, setValueSearchSelected] = (0, react_1.useState)(props.value);
// if the input loses focus and there is no valid option in the input value, show an error message
const [labelSearchSelected, setLabelSearchSelected] = (0, react_1.useState)(props.value);
// It is used to control that if an option is selected and the dropdown is opened, all the options are shown again
const [showAllOptions, setShowAllOptions] = (0, react_1.useState)(true);
const [searchText, setSearchText] = (0, react_1.useState)((0, filterOptions_1.findOptionByValue)(props.value, props.optionList.options)?.label ?? '');
const [inputPopoverText, setInputPopoverText] = (0, react_1.useState)((0, filterOptions_1.findOptionByValue)(props.value, props.optionList.options)?.label ?? '');
// ghost prototype
const { getRef, clickOption } = (0, react_1.useMemo)(() => {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
return (0, syntheticSelect_1.syntheticSelect)(props.optionList.options, props.value, props.name);
}
return { getRef: () => null, clickOption: () => null };
}, [props.optionList.options]);
(0, react_1.useImperativeHandle)(props.ref, () => {
return getRef();
}, []);
// Variables
const device = (0, useMediaDevice_1.useMediaDevice)();
const { internalErrors, addInternalError, removeInternalError } = (0, useInternalValidations_1.useInternalValidations)(props.type, undefined, props.onInternalErrors);
const handleInputBlur = event => {
props.onBlur?.(event);
// if the input loses focus and there is no valid option in the input value, show an error message
if (!labelSearchSelected) {
addInternalError(internalErrors_1.InternalErrorType.INVALID_OPTION);
}
};
// Input Basic hook
const { state, inputRef, handleFocusInternal, handlePasteInternal } = (0, useInput_1.useInput)({
internalErrorExecution: props.internalErrorExecution,
disabled: props.disabled,
error: props.error || internalErrors.length > 0,
// need for update the state
currentValue: searchText,
informationAssociated: props.informationAssociated,
disabledCopyAndPaste: props.disabledCopyAndPaste,
onFocus: props.onFocus,
onBlur: handleInputBlur,
onInternalErrors: props.onInternalErrors,
onPaste: props.onPaste,
});
const useActionBottomSheet = (0, react_1.useMemo)(() => props.styles?.[state]?.useActionBottomSheet?.[device], [state, device]);
const { optionsListRefCollection, optionsListCollectionRef, height: listOptionsHeight, } = (0, useCustomHeightFromChildren_1.useCustomHeightFromChildrens)({
limit: props.elementsToShow,
observer: [props.optionList.options, searchText, inputPopoverText],
shouldCalculateHeight: !useActionBottomSheet,
});
const showAllOptionsTimeout = (0, react_1.useRef)(null);
// Delete showAllOptionsTimeout when the component is unmounted
(0, react_1.useEffect)(() => {
return () => {
if (showAllOptionsTimeout.current) {
clearTimeout(showAllOptionsTimeout.current);
}
};
}, []);
const handleChangeShowAllOptions = ({ value, timeout = 300, }) => {
if (showAllOptionsTimeout.current) {
clearTimeout(showAllOptionsTimeout.current);
}
showAllOptionsTimeout.current = setTimeout(() => {
setShowAllOptions(value);
}, timeout);
};
// Auxiliar method
// Set the value suggested and call onChange
const commonChangeActions = (valueSearchSelected, valueSuggest) => {
setValueSearchSelected(valueSearchSelected);
if (valueSuggest?.label) {
setLabelSearchSelected(valueSuggest.label);
handleChangeShowAllOptions({ value: true });
}
const event = clickOption(valueSearchSelected);
props.onChange?.(event);
};
// Methods
const handleValueSelected = (value) => {
const optionFound = (0, filterOptions_1.findOptionByValue)(value, props.optionList.options);
const searchText = optionFound?.customLabel ?? optionFound?.label ?? value;
removeInternalError(internalErrors_1.InternalErrorType.INVALID_OPTION);
setSearchText(searchText);
setInputPopoverText(searchText);
commonChangeActions(value, optionFound ?? { label: value, value });
};
const handleOpenOptions = (open) => {
setOpenOptions(open);
props.onOpenCloseOptions?.(open);
// If closing the options, focus the input
if (!open) {
inputRef?.current?.focus();
}
};
// Input dropdown handlers
const handleChangeInputDropdown = event => {
handleOpenOptions(true);
const searchText = event.target.value;
const optionFound = (0, filterOptions_1.findOptionByLabel)(searchText, props.optionList.options);
setSearchText(searchText);
setInputPopoverText(searchText);
removeInternalError(internalErrors_1.InternalErrorType.INVALID_OPTION);
if (labelSearchSelected) {
setLabelSearchSelected('');
}
handleChangeShowAllOptions({ value: false, timeout: 0 });
if (!optionFound && valueSearchSelected !== undefined) {
commonChangeActions(undefined, null);
}
};
const handleClickInputDropdown = event => {
handleOpenOptions(!openOptions);
props.onClick?.(event);
};
const handleClickIconInputDropdown = event => {
handleOpenOptions(!openOptions);
props.onIconClick?.(event);
props.onRightIconClick?.(event);
};
const handleInputKeyDown = event => {
if ((0, keyboard_utility_1.isKeyEnterPressed)(event.key) ||
(!(props.allowSearch ?? props.styles[state]?.allowSearch) && (0, keyboard_utility_1.isKeySpacePressed)(event.key))) {
handleOpenOptions(!openOptions);
props.onClick?.(event);
}
if ((0, keyboard_utility_1.isKeyEscapePressed)(event.key)) {
handleOpenOptions(false);
}
// Focus first element of the list
if ((0, keyboard_utility_1.isArrowDownPressed)(event.key)) {
optionsListCollectionRef?.current?.[0]?.firstElementChild?.focus();
event.preventDefault();
}
};
// Input popover handlers
const handleInputPopoverChange = event => {
setInputPopoverText(event.target.value);
if (labelSearchSelected) {
setLabelSearchSelected('');
}
handleChangeShowAllOptions({ value: false, timeout: 0 });
};
const handleInputPopoverIconClick = () => {
props.onInputPopoverIconClick?.();
if (props.clearTextInputPopoverIconClick) {
setInputPopoverText('');
}
};
// Uses effects
// Update value when value or optionList options changes
(0, react_1.useEffect)(() => {
setValueSearchSelected(props.value);
const optionFound = (0, filterOptions_1.findOptionByValue)(props.value, props.optionList.options);
const searchText = optionFound?.customLabel ?? optionFound?.label ?? '';
handleChangeShowAllOptions({ value: true });
setLabelSearchSelected(searchText);
setSearchText(searchText);
setInputPopoverText(searchText);
}, [props.value, props.optionList.options]);
/**
* Action bottom sheet keydown handler, when arrow down is pressed over a non option element, it should focus the first option list element
*/
const handleActionBottomSheetKeydown = (0, react_1.useCallback)((event) => {
// if target is option do nothing, this will handle internally by the OPTIONS
if (event.target instanceof Element && event.target.getAttribute('role') === role_1.ROLES.OPTION) {
return;
}
// Focus first element of the list
if ((0, keyboard_utility_1.isArrowDownPressed)(event.key)) {
optionsListCollectionRef?.current?.[0]?.firstElementChild?.focus();
event.preventDefault();
}
}, []);
const actionBottomSheetRef = (0, react_1.useRef)(null);
const actionBottomSheetRefCb = (0, react_1.useCallback)(node => {
// Add event listeners to the action bottom sheet when it is mounted
if (!node) {
actionBottomSheetRef.current?.removeEventListener('keydown', handleActionBottomSheetKeydown);
}
actionBottomSheetRef.current = node;
if (actionBottomSheetRef.current) {
actionBottomSheetRef.current.addEventListener('keydown', handleActionBottomSheetKeydown);
}
// Focus in the input popover if exists
const inputInPopover = node?.querySelector('input');
if (inputInPopover) {
return inputInPopover.focus();
}
// Focus in the first enable option
const firstEnabledOption = node?.querySelector('[role="option"]:not([aria-disabled="true"])');
firstEnabledOption?.focus();
}, [handleActionBottomSheetKeydown]);
// iconButton skip to list handler
const iconRef = (0, react_1.useRef)();
const handleIconButtonPress = event => {
event.stopPropagation();
if ((0, keyboard_utility_1.isKeyEnterPressed)(event.key) || (0, keyboard_utility_1.isKeySpacePressed)(event.key)) {
return;
}
if ((0, keyboard_utility_1.isKeyEscapePressed)(event.key)) {
handleOpenOptions(false);
}
// Focus first element of the list
if ((0, keyboard_utility_1.isArrowDownPressed)(event.key)) {
optionsListCollectionRef?.current?.[0]?.firstElementChild?.focus();
event.preventDefault();
}
};
(0, react_1.useEffect)(() => {
const iconButton = iconRef.current?.children[0];
if (!iconButton || iconButton.tagName !== 'BUTTON') {
return;
}
iconButton.addEventListener('keydown', handleIconButtonPress);
return () => {
iconButton?.removeEventListener('keydown', handleIconButtonPress);
};
}, []);
// references
const ref = {
refInput: inputRef,
refList: optionsListRefCollection,
refActionBottomSheet: actionBottomSheetRefCb,
refIcon: iconRef,
};
// Filter options attending to
// 1. Allow search && !showAllOptions due to there is already one selected
// 2. If useActionBottomSheet && hasInputInSearchList -> popover input, else normal input
let optionsFiltered = { ...props.optionList };
if ((props.allowSearch ?? props.styles[state]?.allowSearch) && !showAllOptions) {
const filter = useActionBottomSheet && props.hasInputInSearchList ? inputPopoverText : searchText;
optionsFiltered = {
...props.optionList,
options: (0, filterOptions_1.filterOptions)(filter, props.optionList.options),
};
}
return {
openOptions,
valueSearchSelected,
searchText,
inputPopoverText,
optionsFiltered,
handleOpenOptions,
handleClickInputDropdown,
handleClickIconInputDropdown,
handleInputPopoverIconClick,
handleValueSelected,
handleChangeInputDropdown,
handleInputKeyDown,
handleInputBlur,
state,
ref,
listOptionsHeight,
handleFocusInternal,
handleInputPopoverChange,
handlePasteInternal,
};
};
exports.useInputDropdown = useInputDropdown;
//# sourceMappingURL=useInputDropdown.js.map