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

275 lines 12.7 kB
"use strict"; 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