UNPKG

grommet

Version:

focus on the essential experience

428 lines (422 loc) 19 kB
"use strict"; exports.__esModule = true; exports.SelectContainer = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _utils = require("../../utils"); var _Box = require("../Box"); var _Button = require("../Button"); var _InfiniteScroll = require("../InfiniteScroll"); var _Keyboard = require("../Keyboard"); var _Text = require("../Text"); var _TextInput = require("../TextInput"); var _StyledSelect = require("./StyledSelect"); var _utils2 = require("./utils"); var _EmptySearchOption = require("./EmptySearchOption"); var _useThemeValue3 = require("../../utils/useThemeValue"); var _excluded = ["clear", "onClear", "name", "theme"]; function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } // ensure ClearButton receives visual indication of keyboard var StyledButton = (0, _styledComponents["default"])(_Button.Button).withConfig({ displayName: "SelectContainer__StyledButton", componentId: "sc-1wi0ul8-0" })(["&:focus{", "}"], function (props) { return (0, _utils.getHoverIndicatorStyle)('background', props.theme); }); var ClearButton = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var _theme$select$clear, _theme$select$clear2, _theme$select$clear3; var clear = _ref.clear, onClear = _ref.onClear, name = _ref.name, theme = _ref.theme, rest = _objectWithoutPropertiesLoose(_ref, _excluded); var label = clear.label, position = clear.position; var align = position !== 'bottom' ? 'start' : 'center'; var buttonLabel = label || "Clear " + (name || 'selection'); var _useThemeValue = (0, _useThemeValue3.useThemeValue)(), passThemeFlag = _useThemeValue.passThemeFlag; var buttonKind = (_theme$select$clear = theme.select.clear) == null ? void 0 : _theme$select$clear.button; var containerProps = ((_theme$select$clear2 = theme.select.clear) == null ? void 0 : _theme$select$clear2.container) || {}; var textProps = ((_theme$select$clear3 = theme.select.clear) == null ? void 0 : _theme$select$clear3.text) || {}; var buttonProps = _extends({ a11yTitle: buttonLabel + ". Or, press " + (position === 'bottom' ? 'shift tab' : 'down arrow') + " to move to select options", align: align, fill: 'horizontal', onClick: onClear, ref: ref }, passThemeFlag, rest); if (buttonKind) { // new structure when `theme.select.clear.button` is defined return /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({ flex: "grow" }, containerProps), /*#__PURE__*/_react["default"].createElement(_Button.Button, _extends({ kind: buttonKind, label: textProps ? /*#__PURE__*/_react["default"].createElement(_Text.Text, textProps, buttonLabel) : buttonLabel }, buttonProps))); } // default structure when `theme.select.clear.button` is not defined return /*#__PURE__*/_react["default"].createElement(StyledButton, _extends({ focusIndicator: false, plain: true }, buttonProps), function (_ref2) { var _theme$select$clear4; var hover = _ref2.hover; var boxProps = _extends({}, theme.select.clear.container); delete boxProps.hover; // avoid passing hover object to Box return /*#__PURE__*/_react["default"].createElement(_Box.Box, _extends({}, boxProps, hover ? (_theme$select$clear4 = theme.select.clear) == null || (_theme$select$clear4 = _theme$select$clear4.container) == null ? void 0 : _theme$select$clear4.hover : {}, { align: align }), /*#__PURE__*/_react["default"].createElement(_Text.Text, theme.select.clear.text, buttonLabel)); }); }); var SelectContainer = exports.SelectContainer = /*#__PURE__*/(0, _react.forwardRef)(function (_ref3, ref) { var _theme$select; var clear = _ref3.clear, _ref3$children = _ref3.children, children = _ref3$children === void 0 ? null : _ref3$children, disabled = _ref3.disabled, disabledKey = _ref3.disabledKey, dropHeight = _ref3.dropHeight, _ref3$emptySearchMess = _ref3.emptySearchMessage, emptySearchMessage = _ref3$emptySearchMess === void 0 ? 'No matches found' : _ref3$emptySearchMess, id = _ref3.id, labelKey = _ref3.labelKey, multiple = _ref3.multiple, name = _ref3.name, onChange = _ref3.onChange, onKeyDown = _ref3.onKeyDown, onMore = _ref3.onMore, onSearch = _ref3.onSearch, optionIndexesInValue = _ref3.optionIndexesInValue, options = _ref3.options, allOptions = _ref3.allOptions, searchPlaceholder = _ref3.searchPlaceholder, search = _ref3.search, setSearch = _ref3.setSearch, selected = _ref3.selected, usingKeyboard = _ref3.usingKeyboard, _ref3$value = _ref3.value, value = _ref3$value === void 0 ? '' : _ref3$value, valueKey = _ref3.valueKey, _ref3$replace = _ref3.replace, replace = _ref3$replace === void 0 ? true : _ref3$replace; var _useThemeValue2 = (0, _useThemeValue3.useThemeValue)(), theme = _useThemeValue2.theme, passThemeFlag = _useThemeValue2.passThemeFlag; var shouldShowClearButton = (0, _react.useCallback)(function (position) { var hasValue = Boolean(multiple && value ? value.length : value); var showAtPosition = position === 'bottom' ? (clear == null ? void 0 : clear.position) === 'bottom' : (clear == null ? void 0 : clear.position) !== 'bottom'; return clear && hasValue && showAtPosition; }, [clear, multiple, value]); var isDisabled = (0, _utils2.useDisabled)(disabled, disabledKey, options, valueKey || labelKey); var _useState = (0, _react.useState)(usingKeyboard && !shouldShowClearButton('top') && !onSearch ? 0 : -1), activeIndex = _useState[0], setActiveIndex = _useState[1]; var _useState2 = (0, _react.useState)(usingKeyboard), keyboardNavigation = _useState2[0], setKeyboardNavigation = _useState2[1]; var searchRef = (0, _react.useRef)(); var optionsRef = (0, _react.useRef)(); var clearRef = (0, _react.useRef)(); var activeRef = (0, _react.useRef)(); // for keyboard/screenreader, keep the active option in focus (0, _react.useEffect)(function () { var _activeRef$current; if (activeIndex >= 0) (_activeRef$current = activeRef.current) == null || _activeRef$current.focus(); }, [activeIndex]); // set initial focus (0, _react.useEffect)(function () { // need to wait for Drop to be ready var timer = setTimeout(function () { var optionsNode = optionsRef.current; var clearButton = clearRef.current; if (onSearch) { var searchInput = searchRef.current; if (searchInput && searchInput.focus && !activeRef.current) { (0, _utils.setFocusWithoutScroll)(searchInput); } } else if (clear && clearButton && clearButton.focus && clear.position !== 'bottom') { (0, _utils.setFocusWithoutScroll)(clearButton); } else if (usingKeyboard && activeRef.current) { (0, _utils.setFocusWithoutScroll)(activeRef.current); } else if (optionsNode) { (0, _utils.setFocusWithoutScroll)(optionsNode); } }, 100); return function () { return clearTimeout(timer); }; }, [onSearch, usingKeyboard, clear]); var isSelected = (0, _react.useCallback)(function (index) { var result; if (selected) { // deprecated in favor of value result = selected.indexOf(index) !== -1; } else { var optionVal = (0, _utils2.getOptionValue)(index, options, valueKey); if (Array.isArray(value)) { if (value.length === 0) { result = false; } else if (typeof value[0] !== 'object') { result = value.indexOf(optionVal) !== -1; } else if (valueKey) { result = value.some(function (valueItem) { var valueValue = typeof valueKey === 'function' ? valueKey(valueItem) : valueItem[valueKey] || valueItem[valueKey.key]; return valueValue === optionVal; }); } } else if (valueKey && value !== null && typeof value === 'object') { var valueValue = typeof valueKey === 'function' ? valueKey(value) : value[valueKey]; result = valueValue === optionVal; } else { result = value === optionVal; } } return result; }, [selected, value, valueKey, options]); var selectOption = (0, _react.useCallback)(function (index) { return function (event) { if (onChange) { var nextValue; var nextSelected; if (multiple) { var nextOptionIndexesInValue = optionIndexesInValue.slice(0); var allOptionsIndex = allOptions.indexOf(options[index]); var valueIndex = optionIndexesInValue.indexOf(allOptionsIndex); if (valueIndex === -1) { nextOptionIndexesInValue.push(allOptionsIndex); } else { nextOptionIndexesInValue.splice(valueIndex, 1); } nextValue = nextOptionIndexesInValue.map(function (i) { return valueKey && valueKey.reduce ? (0, _utils2.applyKey)(allOptions[i], valueKey) : allOptions[i]; }); nextSelected = nextOptionIndexesInValue; } else { nextValue = valueKey && valueKey.reduce ? (0, _utils2.applyKey)(options[index], valueKey) : options[index]; nextSelected = index; } onChange(event, { option: options[index], value: nextValue, selected: nextSelected }); } }; }, [multiple, onChange, optionIndexesInValue, options, allOptions, valueKey]); var onClear = (0, _react.useCallback)(function (event) { onChange(event, { option: undefined, value: '', selected: '' }); }, [onChange]); var onNextOption = (0, _react.useCallback)(function (event) { event.preventDefault(); var nextActiveIndex = activeIndex + 1; while (nextActiveIndex < options.length && isDisabled(nextActiveIndex)) { nextActiveIndex += 1; } if (nextActiveIndex !== options.length) { setActiveIndex(nextActiveIndex); setKeyboardNavigation(true); } }, [activeIndex, options, isDisabled]); var onPreviousOption = (0, _react.useCallback)(function (event) { event.preventDefault(); var nextActiveIndex = activeIndex - 1; if (nextActiveIndex === -1) { var searchInput = searchRef.current; var clearButton = clearRef.current; if (clearButton && clearButton.focus && shouldShowClearButton('top')) { setActiveIndex(nextActiveIndex); (0, _utils.setFocusWithoutScroll)(clearButton); } else if (searchInput && searchInput.focus) { setActiveIndex(nextActiveIndex); (0, _utils.setFocusWithoutScroll)(searchInput); } } while (nextActiveIndex >= 0 && isDisabled(nextActiveIndex)) { nextActiveIndex -= 1; } if (nextActiveIndex >= 0) { setActiveIndex(nextActiveIndex); setKeyboardNavigation(true); } }, [activeIndex, isDisabled, shouldShowClearButton]); var onKeyDownOption = (0, _react.useCallback)(function (event) { if (!onSearch) { var nextActiveIndex = options.findIndex(function (e, index) { var label; if (typeof e === 'object') { label = e.label || (0, _utils2.applyKey)(e, labelKey); } else { label = e; } return typeof label === 'string' && label.charAt(0).toLowerCase() === event.key.toLowerCase() && !isDisabled(index); }); if (nextActiveIndex >= 0) { event.preventDefault(); setActiveIndex(nextActiveIndex); setKeyboardNavigation(true); } } if (onKeyDown) { onKeyDown(event); } }, [isDisabled, labelKey, onKeyDown, options, onSearch]); var onActiveOption = (0, _react.useCallback)(function (index) { return function () { if (!keyboardNavigation) setActiveIndex(index); }; }, [keyboardNavigation]); var onSelectOption = (0, _react.useCallback)(function (event) { if ((shouldShowClearButton('bottom') || shouldShowClearButton('top')) && (0, _utils.containsFocus)(clearRef.current)) { onChange(event, { option: undefined, value: '', selected: '' }); } else if (activeIndex >= 0 && activeIndex < options.length) { event.preventDefault(); // prevent submitting forms selectOption(activeIndex)(event); } }, [activeIndex, selectOption, options, onChange, shouldShowClearButton]); var customSearchInput = theme.select.searchInput; var SelectTextInput = customSearchInput || _TextInput.TextInput; var selectOptionsStyle = theme.select.options ? _extends({}, theme.select.options.box, theme.select.options.container) : {}; return /*#__PURE__*/_react["default"].createElement(_Keyboard.Keyboard, { onEnter: onSelectOption, onSpace: onSelectOption, onUp: onPreviousOption, onDown: onNextOption, onKeyDown: onKeyDownOption }, /*#__PURE__*/_react["default"].createElement(_StyledSelect.StyledContainer, _extends({ ref: ref, id: id ? id + "__select-drop" : undefined, dropHeight: dropHeight }, passThemeFlag), onSearch && /*#__PURE__*/_react["default"].createElement(_Box.Box, { pad: !customSearchInput ? (_theme$select = theme.select) == null || (_theme$select = _theme$select.search) == null ? void 0 : _theme$select.pad : undefined, flex: false }, /*#__PURE__*/_react["default"].createElement(SelectTextInput, { focusIndicator: !customSearchInput, size: "small", ref: searchRef, type: "search", value: search || '', placeholder: searchPlaceholder, onChange: function onChange(event) { var nextSearch = event.target.value; setSearch(nextSearch); setActiveIndex(-1); onSearch(nextSearch); }, onFocus: function onFocus() { return setActiveIndex(-1); } })), shouldShowClearButton('top') && /*#__PURE__*/_react["default"].createElement(ClearButton, { ref: clearRef, clear: clear, name: name, onClear: onClear, onFocus: function onFocus() { return setActiveIndex(-1); }, onMouseOver: function onMouseOver() { return setActiveIndex(-1); }, theme: theme }), options.length > 0 ? /*#__PURE__*/_react["default"].createElement(_StyledSelect.OptionsContainer, { role: "listbox", tabIndex: "-1", ref: optionsRef, "aria-multiselectable": multiple, onMouseMove: function onMouseMove() { return setKeyboardNavigation(false); } }, /*#__PURE__*/_react["default"].createElement(_InfiniteScroll.InfiniteScroll, { items: options, step: theme.select.step, onMore: onMore, replace: replace, show: activeIndex !== -1 ? activeIndex : undefined }, function (option, index, optionRef) { var optionDisabled = isDisabled(index); var optionSelected = isSelected(index); var optionActive = activeIndex === index; // Determine whether the label is done as a child or // as an option Button kind property. var child; var textComponent = false; if (children) { child = children(option, index, options, { active: optionActive, disabled: optionDisabled, selected: optionSelected }); if (typeof child === 'string' || child.props && child.props.children && typeof child.props.children === 'string') textComponent = true; } else if (theme.select.options) { child = /*#__PURE__*/_react["default"].createElement(_Box.Box, selectOptionsStyle, /*#__PURE__*/_react["default"].createElement(_Text.Text, theme.select.options.text, (0, _utils2.getOptionLabel)(index, options, labelKey))); textComponent = true; } // if we have a child, turn on plain, and hoverIndicator return /*#__PURE__*/_react["default"].createElement(_StyledSelect.SelectOption // lint isn't flagging this but we shouldn't use index // as a key see no-array-index-key lint rule , _extends({ key: index // merge optionRef and activeRef , ref: function ref(node) { // eslint-disable-next-line no-param-reassign if (optionRef) optionRef.current = node; if (optionActive) activeRef.current = node; }, tabIndex: optionSelected || activeIndex === index || // when nothing is selected and entering listbox // first option should be focused (!value || Array.isArray(value) && value.length === 0) && activeIndex === -1 && index === 0 ? '0' : '-1', role: "option", "aria-setsize": options.length, "aria-posinset": index + 1, "aria-selected": optionSelected, "aria-disabled": optionDisabled || undefined, plain: !child ? undefined : true, align: "start", kind: !child ? 'option' : undefined, label: !child ? (0, _utils2.getOptionLabel)(index, options, labelKey || valueKey) : undefined, disabled: optionDisabled || undefined, active: optionActive, selected: optionSelected // allow keyboard navigation to start from // selected option after tabbing to it , onFocus: function onFocus() { return setActiveIndex(index); }, onMouseOver: !optionDisabled ? onActiveOption(index) : undefined, onMouseOut: !optionDisabled ? onActiveOption(-1) : undefined, onClick: !optionDisabled ? selectOption(index) : undefined, textComponent: textComponent }, passThemeFlag), child); })) : /*#__PURE__*/_react["default"].createElement(_EmptySearchOption.EmptySearchOption, { emptySearchMessage: emptySearchMessage, selectOptionsStyle: selectOptionsStyle, theme: theme }), shouldShowClearButton('bottom') && /*#__PURE__*/_react["default"].createElement(ClearButton, { ref: clearRef, clear: clear, name: name, onClear: onClear, onFocus: function onFocus() { return setActiveIndex(-1); }, onMouseOver: function onMouseOver() { return setActiveIndex(-1); }, theme: theme }))); });