@s-ui/react-molecule-select
Version:
`MoleculeSelect` is a customized `select` created from a combination of `AtomInput`, `MoleculeInputTags`, `MoleculeDropdownList` and `MoleculeDropdownOption`
315 lines • 11.9 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
var _excluded = ["className", "onBlur", "onFocus", "isOpen", "onToggle", "children", "errorState", "state", "disabled", "keysSelection", "multiselection", "hasSearch", "iconCloseTag", "tagSize", "searchIcon", "searchPlaceholder", "noResults", "refMoleculeSelect", "aria-label", "onSearch", "readOnly", "onChange", "selectSize", "size", "isBorderless", "value", "defaultValue"];
import { Children, cloneElement, createRef, forwardRef, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { getTarget } from '@s-ui/js/lib/react';
import { useControlledState } from '@s-ui/react-hooks';
import useMergeRefs from '@s-ui/react-hooks/lib/useMergeRefs';
import MoleculeSelectMultipleSelection from './components/MultipleSelection.js';
import MoleculeSelectSingleSelection from './components/SingleSelection.js';
import { DropdownContext, ENABLED_KEYS, getClassName, getOptionData, SELECT_DROPDOWN_LIST_SIZES, SELECT_INPUT_SIZES, SELECT_STATES, SELECT_TAG_SIZES, SELECTION_KEYS } from './config.js';
import { jsx as _jsx } from "react/jsx-runtime";
var useFunctionalRef = function useFunctionalRef() {
return useReducer(function (_s, node) {
return node;
}, null);
};
var MoleculeSelect = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
var classNameFromProps = _ref.className,
onBlur = _ref.onBlur,
onFocus = _ref.onFocus,
isOpen = _ref.isOpen,
onToggle = _ref.onToggle,
children = _ref.children,
errorState = _ref.errorState,
state = _ref.state,
_ref$disabled = _ref.disabled,
disabled = _ref$disabled === void 0 ? false : _ref$disabled,
_ref$keysSelection = _ref.keysSelection,
keysSelection = _ref$keysSelection === void 0 ? SELECTION_KEYS : _ref$keysSelection,
_ref$multiselection = _ref.multiselection,
multiselection = _ref$multiselection === void 0 ? false : _ref$multiselection,
_ref$hasSearch = _ref.hasSearch,
hasSearch = _ref$hasSearch === void 0 ? false : _ref$hasSearch,
iconCloseTag = _ref.iconCloseTag,
tagSize = _ref.tagSize,
searchIcon = _ref.searchIcon,
searchPlaceholder = _ref.searchPlaceholder,
noResults = _ref.noResults,
refMoleculeSelectFromProps = _ref.refMoleculeSelect,
ariaLabel = _ref['aria-label'],
onSearch = _ref.onSearch,
_ref$readOnly = _ref.readOnly,
readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly,
_ref$onChange = _ref.onChange,
onChange = _ref$onChange === void 0 ? function () {} : _ref$onChange,
_ref$selectSize = _ref.selectSize,
selectSize = _ref$selectSize === void 0 ? SELECT_INPUT_SIZES.MEDIUM : _ref$selectSize,
dropdownListSize = _ref.size,
isBorderless = _ref.isBorderless,
value = _ref.value,
defaultValue = _ref.defaultValue,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useState = useState(false),
focusedFirstOption = _useState[0],
setFocusedFirstOption = _useState[1];
var refOptions = useRef({});
var _useControlledState = useControlledState(value, defaultValue),
innerValue = _useControlledState[0],
setInnerValue = _useControlledState[1];
var innerRef = useRef();
var triggerRef = useRef();
var searchRef = useRef();
var refMoleculeSelect = useRef(refMoleculeSelectFromProps);
var refsMoleculeSelectOptions = useRef([]);
var ref = useMergeRefs(forwardedRef, refMoleculeSelect, innerRef);
var _useFunctionalRef = useFunctionalRef(),
inputSearch = _useFunctionalRef[0],
setInputRef = _useFunctionalRef[1];
var _useState2 = useState(isOpen || false),
isOpenState = _useState2[0],
setIsOpenState = _useState2[1];
useEffect(function () {
return setIsOpenState(isOpen);
}, [isOpen, setIsOpenState]);
Object.assign(refOptions.current, getOptionData(children));
var isFocused = function isFocused() {
var el = innerRef == null ? void 0 : innerRef.current;
return el && (el.matches(':focus-within') || el.contains(document.activeElement));
};
var isTriggerFocused = function isTriggerFocused() {
var el = triggerRef == null ? void 0 : triggerRef.current;
return el && el.matches(':focus');
};
var isSearchFocused = function isSearchFocused() {
var el = searchRef == null ? void 0 : searchRef.current;
return el && el.matches(':focus');
};
var extendedChildren = useMemo(function () {
return Children.toArray(children).filter(Boolean).map(function (child, index) {
refsMoleculeSelectOptions.current[index] = /*#__PURE__*/createRef();
return /*#__PURE__*/cloneElement(child, {
ref: refsMoleculeSelectOptions.current[index],
selectKey: keysSelection
});
});
}, [children, keysSelection]);
var numOptions = Children.toArray(extendedChildren).length;
var className = getClassName({
state: state,
errorState: errorState,
disabled: disabled,
className: classNameFromProps,
isBorderless: isBorderless
});
var handleToggle = useCallback(function (ev, _temp) {
var _ref2 = _temp === void 0 ? {
isOutsideEvent: false
} : _temp,
isOpen = _ref2.isOpen,
isOutsideEvent = _ref2.isOutsideEvent;
setIsOpenState(isOpen !== undefined ? isOpen : !isOpenState);
typeof onToggle === 'function' && onToggle(ev, {
isOpen: isOpen
});
if (!isOutsideEvent) {
ev.preventDefault();
ev.stopPropagation();
}
}, [isOpenState, onToggle]);
var closeList = useCallback(function (ev, _ref3) {
var _ref3$isOutsideEvent = _ref3.isOutsideEvent,
isOutsideEvent = _ref3$isOutsideEvent === void 0 ? false : _ref3$isOutsideEvent;
handleToggle(ev, {
isOpen: false,
isOutsideEvent: isOutsideEvent
});
}, [handleToggle]);
var handleTriggerClick = function handleTriggerClick(event) {
handleToggle(event, {
isOpen: true
});
if (!isOpenState) {
setTimeout(function () {
if (hasSearch) {
focusSearchInput(event);
} else {
focusFirstOption(event);
}
});
}
};
var handleOutsideClick = useCallback(function (ev) {
if (disabled) return;
if (refMoleculeSelect.current && !refMoleculeSelect.current.contains(ev.target)) {
// outside click
closeList(ev, {
isOutsideEvent: true
});
}
}, [closeList, disabled]);
var focusFirstOption = useCallback(function (ev) {
var options = refsMoleculeSelectOptions.current.map(function (option) {
return getTarget(option.current);
});
options[0] && options[0].focus();
ev.preventDefault();
ev.stopPropagation();
}, [refsMoleculeSelectOptions]);
var focusSearchInput = useCallback(function (ev) {
var isEnabledKey = ENABLED_KEYS.includes(ev == null ? void 0 : ev.key);
isEnabledKey ? focusFirstOption(ev) : inputSearch == null ? void 0 : inputSearch.focus();
ev == null ? void 0 : ev.preventDefault();
ev == null ? void 0 : ev.stopPropagation();
}, [inputSearch, focusFirstOption]);
var isFirstOptionFocused = useCallback(function () {
var options = refsMoleculeSelectOptions.current.map(function (option) {
return getTarget(option.current);
});
return document.activeElement === options[0];
}, [refsMoleculeSelectOptions]);
useEffect(function () {
Object.assign(refOptions.current, getOptionData(children));
document.addEventListener('touchend', handleOutsideClick);
document.addEventListener('mousedown', handleOutsideClick);
return function () {
document.removeEventListener('touchend', handleOutsideClick);
document.removeEventListener('mousedown', handleOutsideClick);
};
}, [children, handleOutsideClick]);
useEffect(function () {
isOpenState && setTimeout(function () {
return focusSearchInput();
});
}, [isOpenState, focusSearchInput]);
var handleKeyDown = function handleKeyDown(ev) {
var _triggerRef$current;
ev.persist();
switch (ev.key) {
case 'Tab':
if (isOpenState) {
handleToggle(ev, {
isOpen: !isOpenState
});
}
break;
case 'Escape':
closeList(ev, {
isOutsideEvent: true
});
triggerRef == null ? void 0 : (_triggerRef$current = triggerRef.current) == null ? void 0 : _triggerRef$current.focus();
break;
case 'ArrowDown':
case 'ArrowUp':
if (!isOpenState) {
handleToggle(ev, {
isOpen: !isOpenState
});
}
if (isTriggerFocused()) {
isOpenState && setTimeout(function () {
return focusSearchInput(ev);
});
!hasSearch && setTimeout(function () {
return focusFirstOption(ev);
});
}
if (isSearchFocused() && ev.key === 'ArrowDown') {
focusFirstOption(ev);
}
if (focusedFirstOption && ev.key === 'ArrowUp') {
inputSearch == null ? void 0 : inputSearch.focus();
}
break;
default:
// isOpenState && setTimeout(() => focusSearchInput(ev))
!hasSearch && setTimeout(function () {
return focusFirstOption(ev);
});
break;
}
};
var handleFocusOut = function handleFocusOut(event) {
typeof onBlur === 'function' && onBlur(event);
};
var handleFocusIn = function handleFocusIn() {
return !disabled && !hasSearch && onFocus && onFocus();
};
var handleClick = function handleClick(ev) {
ev.persist();
if (isFocused()) {
if (hasSearch) {
setTimeout(function () {
return focusSearchInput(ev);
});
} else {
setTimeout(function () {
return focusFirstOption(ev);
});
}
}
};
var handleChange = function handleChange(ev, _ref4) {
var value = _ref4.value;
setInnerValue(value);
onChange && onChange(ev, {
value: value
});
};
var Select = multiselection ? MoleculeSelectMultipleSelection : MoleculeSelectSingleSelection;
var context = {
hasSearch: hasSearch,
setInputRef: setInputRef,
isOpen: isOpenState,
inputSearch: inputSearch,
onSearch: onSearch,
isFirstOptionFocused: isFirstOptionFocused,
searchPlaceholder: searchPlaceholder,
searchIcon: searchIcon,
focusedFirstOption: focusedFirstOption,
setFocusedFirstOption: setFocusedFirstOption
};
return /*#__PURE__*/_jsx(DropdownContext.Provider, {
value: context,
children: /*#__PURE__*/_jsx("div", {
ref: ref,
className: className,
onKeyDown: handleKeyDown,
onFocus: handleFocusIn,
onBlur: handleFocusOut,
onClick: handleClick,
"aria-label": ariaLabel,
children: /*#__PURE__*/_jsx(Select, _extends({
ref: triggerRef,
refMoleculeSelect: refMoleculeSelect,
refSearch: searchRef,
optionsData: refOptions.current,
isOpen: isOpenState
}, props, {
value: innerValue,
state: state,
disabled: disabled,
readOnly: readOnly,
onTriggerClick: handleTriggerClick,
onToggle: handleToggle,
onChange: handleChange,
size: dropdownListSize,
selectSize: selectSize,
tagSize: tagSize,
multiselection: multiselection
}, multiselection && {
iconCloseTag: iconCloseTag,
keysSelection: keysSelection
}, {
children: numOptions > 0 ? extendedChildren : noResults
}))
})
});
});
MoleculeSelect.displayName = 'MoleculeSelect';
export default MoleculeSelect;
export { SELECT_DROPDOWN_LIST_SIZES as moleculeSelectDropdownListSizes };
export { SELECT_INPUT_SIZES as moleculeSelectSizes };
export { SELECT_TAG_SIZES as moleculeSelectTagSizes };
export { SELECT_STATES as moleculeSelectStates };