UNPKG

grommet

Version:

focus on the essential experience

503 lines (495 loc) 21.4 kB
"use strict"; exports.__esModule = true; exports.SelectMultiple = void 0; var _react = _interopRequireWildcard(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _utils = require("../../utils"); var _Box = require("../Box"); var _DropButton = require("../DropButton"); var _Keyboard = require("../Keyboard"); var _FormContext = require("../Form/FormContext"); var _SelectMultipleValue = require("./SelectMultipleValue"); var _SelectMultipleContainer = require("./SelectMultipleContainer"); var _StyledSelect = require("../Select/StyledSelect"); var _utils2 = require("../Select/utils"); var _DefaultSelectTextInput = require("../Select/DefaultSelectTextInput"); var _MessageContext = require("../../contexts/MessageContext"); var _propTypes = require("./propTypes"); var _useThemeValue2 = require("../../utils/useThemeValue"); var _excluded = ["a11yTitle", "aria-label", "aria-labelledby", "alignSelf", "children", "defaultValue", "disabled", "disabledKey", "dropAlign", "dropHeight", "dropProps", "dropTarget", "emptySearchMessage", "focusIndicator", "gridArea", "help", "id", "icon", "labelKey", "limit", "margin", "messages", "name", "onBlur", "onChange", "onClick", "onClose", "onFocus", "onKeyDown", "onMore", "onOpen", "onSearch", "open", "options", "placeholder", "plain", "replace", "searchPlaceholder", "size", "sortSelectedOnClose", "value", "valueKey", "valueLabel", "showSelectedInline", "width"]; 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; } var StyledSelectBox = (0, _styledComponents["default"])(_Box.Box).withConfig({ displayName: "SelectMultiple__StyledSelectBox", componentId: "sc-18zwyth-0" })(["", ";", ";", ";"], function (props) { return !props.plainSelect && _utils.controlBorderStyle; }, function (props) { var _props$theme$select; return (_props$theme$select = props.theme.select) == null || (_props$theme$select = _props$theme$select.control) == null ? void 0 : _props$theme$select.extend; }, function (props) { var _props$theme$select$c; return props.open && ((_props$theme$select$c = props.theme.select.control) == null ? void 0 : _props$theme$select$c.open); }); var SelectMultiple = exports.SelectMultiple = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) { var a11yTitle = _ref.a11yTitle, ariaLabel = _ref['aria-label'], ariaLabelledByProp = _ref['aria-labelledby'], alignSelf = _ref.alignSelf, children = _ref.children, defaultValue = _ref.defaultValue, disabled = _ref.disabled, disabledKey = _ref.disabledKey, dropAlignProp = _ref.dropAlign, dropHeight = _ref.dropHeight, dropProps = _ref.dropProps, dropTarget = _ref.dropTarget, emptySearchMessage = _ref.emptySearchMessage, focusIndicator = _ref.focusIndicator, gridArea = _ref.gridArea, help = _ref.help, id = _ref.id, icon = _ref.icon, labelKey = _ref.labelKey, limit = _ref.limit, margin = _ref.margin, messages = _ref.messages, name = _ref.name, onBlur = _ref.onBlur, onChange = _ref.onChange, onClick = _ref.onClick, onClose = _ref.onClose, onFocus = _ref.onFocus, onKeyDown = _ref.onKeyDown, onMore = _ref.onMore, onOpen = _ref.onOpen, onSearch = _ref.onSearch, openProp = _ref.open, optionsProp = _ref.options, placeholder = _ref.placeholder, plain = _ref.plain, replace = _ref.replace, searchPlaceholder = _ref.searchPlaceholder, size = _ref.size, _ref$sortSelectedOnCl = _ref.sortSelectedOnClose, sortSelectedOnClose = _ref$sortSelectedOnCl === void 0 ? true : _ref$sortSelectedOnCl, valueProp = _ref.value, valueKey = _ref.valueKey, valueLabel = _ref.valueLabel, _ref$showSelectedInli = _ref.showSelectedInline, showSelectedInline = _ref$showSelectedInli === void 0 ? false : _ref$showSelectedInli, width = _ref.width, rest = _objectWithoutPropertiesLoose(_ref, _excluded); var _useThemeValue = (0, _useThemeValue2.useThemeValue)(), theme = _useThemeValue.theme, passThemeFlag = _useThemeValue.passThemeFlag; var inputRef = (0, _react.useRef)(); var formContext = (0, _react.useContext)(_FormContext.FormContext); var _useContext = (0, _react.useContext)(_MessageContext.MessageContext), format = _useContext.format; var selectBoxRef = (0, _react.useRef)(); var dropButtonRef = (0, _utils.useForwardedRef)(ref); var usingKeyboard = (0, _utils.useKeyboard)(); var formFieldData = formContext == null ? void 0 : formContext.useFormField({}); var dropAlign = (0, _react.useMemo)(function () { return dropAlignProp || (showSelectedInline ? { top: 'top', right: 'right', left: 'left' } : { top: 'bottom', left: 'left' }); }, [dropAlignProp, showSelectedInline]); // value is used for what we receive in valueProp and the basis for // what we send with onChange // When 'valueKey' sets 'reduce', the value(s) here should match // what the 'valueKey' would return for the corresponding // selected option object. // Otherwise, the value(s) should match the selected options. var _formContext$useFormI = formContext.useFormInput({ name: name, value: valueProp, initialValue: defaultValue || '' }), value = _formContext$useFormI[0], setValue = _formContext$useFormI[1]; var _useState = (0, _react.useState)(), ariaLabelledBy = _useState[0], setAriaLabelledBy = _useState[1]; (0, _react.useEffect)(function () { if (formFieldData != null && formFieldData.inForm && id && !ariaLabel && !placeholder) { var labelElement = document.getElementById("grommet-" + id + "__input__label"); if (labelElement) { setAriaLabelledBy("grommet-" + id + "__input__label " + id); } } }, [formFieldData == null ? void 0 : formFieldData.inForm, id, ariaLabel, placeholder]); // normalizedValue is the value mapped with any valueKey applied // When the options array contains objects, this property indicates how // to retrieve the value of each option. // If a string is provided, it is used as the key to retrieve a // property of an option object. // If a function is provided, it is called with the option and should // return the value. // If reduce is true, this value will be used for the 'value' // delivered via 'onChange'. var normalizedValue = (0, _react.useMemo)(function () { return (0, _utils2.getNormalizedValue)(value, valueKey); }, [value, valueKey]); // search input value var _useState2 = (0, _react.useState)(), search = _useState2[0], setSearch = _useState2[1]; // All select option indices and values var _useState3 = (0, _react.useState)(optionsProp), allOptions = _useState3[0], setAllOptions = _useState3[1]; var _useState4 = (0, _react.useState)(), orderedOptions = _useState4[0], setOrderedOptions = _useState4[1]; // Track changes to options property, except when options are being // updated due to search activity. Allows option's initial index value // to be referenced when filtered by search. (0, _react.useEffect)(function () { if (!search) setAllOptions(optionsProp); }, [optionsProp, search]); (0, _react.useEffect)(function () { if (search && optionsProp && optionsProp.length > 0) { var additionalOptions = [].concat(allOptions); optionsProp.forEach(function (i) { return !additionalOptions.some(function (j) { return typeof i === 'object' ? (0, _utils2.applyKey)(i, valueKey) === (0, _utils2.applyKey)(j, valueKey) : i === j; }) && additionalOptions.push(i); }); if (allOptions.length !== additionalOptions.length) setAllOptions(additionalOptions); } }, [allOptions, optionsProp, search, valueKey]); (0, _react.useEffect)(function () { if (sortSelectedOnClose) setOrderedOptions(optionsProp); }, [optionsProp, sortSelectedOnClose]); // the option indexes present in the value var optionIndexesInValue = (0, _react.useMemo)(function () { var result = []; if (!Array.isArray(normalizedValue)) { return result; } normalizedValue.forEach(function (v) { var index = allOptions.map(function (option) { return (0, _utils2.applyKey)(option, valueKey); }).indexOf(v); if (index !== -1) result.push(index); }); return result; }, [allOptions, valueKey, normalizedValue]); var _useState5 = (0, _react.useState)(openProp), open = _useState5[0], setOpen = _useState5[1]; (0, _react.useEffect)(function () { return setOpen(openProp); }, [openProp]); var onRequestOpen = (0, _react.useCallback)(function () { if (open) return; setOpen(true); if (onOpen) onOpen(); }, [onOpen, open]); // On drop close if sortSelectedOnClose is true, sort options so that // selected options appear first, followed by unselected options. (0, _react.useEffect)(function () { if (sortSelectedOnClose && value && !open) { var selectedOptions = optionsProp.filter(function (option) { return (0, _utils2.arrayIncludes)(value, valueKey && valueKey.reduce ? (0, _utils2.applyKey)(option, valueKey) : option, valueKey || labelKey); }); var unselectedOptions = optionsProp.filter(function (i) { return !(0, _utils2.arrayIncludes)(selectedOptions, i, valueKey || labelKey); }); var nextOrderedOptions = selectedOptions.concat(unselectedOptions); setOrderedOptions(nextOrderedOptions); } }, [labelKey, open, sortSelectedOnClose, optionsProp, value, valueKey]); var onRequestClose = (0, _react.useCallback)(function () { setOpen(false); if (onClose) onClose(); setSearch(); }, [onClose]); var triggerChangeEvent = (0, _react.useCallback)(function (nextValue) { return (0, _utils2.changeEvent)(inputRef, nextValue); }, []); var onSelectChange = (0, _react.useCallback)(function (event, _ref2) { var option = _ref2.option, nextValue = _ref2.value; // nextValue must not be of type object to set value directly on the // input. if it is an object, then the user has not provided necessary // props to reduce object option if (typeof nextValue !== 'object' && nextValue !== event.target.value && inputRef.current) { // select registers changing option as a click event or keydown. // when in a form, we need to programatically trigger a change // event in order for the change event to be registered upstream // necessary for change validation in form triggerChangeEvent(nextValue); } setValue(nextValue); if (onChange) { event.persist(); var adjustedEvent; // support for native event used by Preact if (event instanceof Event) { adjustedEvent = new event.constructor(event.type, event); Object.defineProperties(adjustedEvent, { target: { value: inputRef.current }, value: { value: nextValue }, option: { value: option } }); } else { adjustedEvent = event; adjustedEvent.target = inputRef.current; adjustedEvent.value = nextValue; adjustedEvent.option = option; } onChange(adjustedEvent); } }, [onChange, setValue, triggerChangeEvent]); var SelectIcon = (0, _utils2.getSelectIcon)(icon, theme, open); // element to show, trumps inputValue var selectValue = (0, _react.useMemo)(function () { var result; if (valueLabel) { result = value && valueLabel instanceof Function ? valueLabel(value) : valueLabel; } else if ((value == null ? void 0 : value.length) > 0 && showSelectedInline) { result = /*#__PURE__*/_react["default"].createElement(_SelectMultipleValue.SelectMultipleValue, { allOptions: allOptions, disabled: disabled, disabledKey: disabledKey, dropButtonRef: dropButtonRef, labelKey: labelKey, messages: messages, onRequestOpen: onRequestOpen, onSelectChange: onSelectChange, theme: theme, value: value, valueKey: valueKey }, children); } return result; }, [allOptions, children, disabled, disabledKey, dropButtonRef, labelKey, messages, onRequestOpen, onSelectChange, showSelectedInline, theme, value, valueKey, valueLabel]); var displayLabelKey = (0, _react.useMemo)(function () { return (0, _utils2.getDisplayLabelKey)(labelKey, allOptions, optionIndexesInValue, selectValue); }, [labelKey, allOptions, optionIndexesInValue, selectValue]); // text to show // When the options array contains objects, this property indicates how // to retrieve the value of each option. // If a string is provided, it is used as the key to retrieve a // property of an option object. // If a function is provided, it is called with the option and should // return the value. // If reduce is true, this value will be used for the 'value' // delivered via 'onChange'. var inputValue = (0, _react.useMemo)(function () { if (!selectValue) { if (optionIndexesInValue.length === 0) return ''; if (optionIndexesInValue.length === 1) return (0, _utils2.applyKey)(allOptions[optionIndexesInValue[0]], labelKey); // keeping messages.multiple for backwards compatibility if (messages != null && messages.multiple && !messages.summarizedValue) { return format({ id: 'select.multiple', messages: messages }); } return format({ id: 'selectMultiple.summarizedValue', messages: messages, values: { selected: optionIndexesInValue.length, total: allOptions.length } }); } return undefined; }, [selectValue, optionIndexesInValue, allOptions, labelKey, format, messages]); var iconColor = (0, _utils2.getIconColor)(theme); var displaySelectIcon = SelectIcon && /*#__PURE__*/_react["default"].createElement(_Box.Box, { alignSelf: "center", margin: theme.select.icons.margin, width: { min: 'auto' } }, /*#__PURE__*/(0, _react.isValidElement)(SelectIcon) ? SelectIcon : /*#__PURE__*/_react["default"].createElement(SelectIcon, { color: iconColor, size: size })); var dropContent = /*#__PURE__*/_react["default"].createElement(_SelectMultipleContainer.SelectMultipleContainer, { allOptions: allOptions, disabled: disabled, disabledKey: disabledKey, dropHeight: dropHeight, emptySearchMessage: emptySearchMessage, help: help, icon: displaySelectIcon, id: id, labelKey: labelKey, limit: limit, messages: messages, onChange: onSelectChange, onClose: onRequestClose, onKeyDown: onKeyDown, onMore: onMore, onSearch: onSearch, options: orderedOptions || optionsProp, optionIndexesInValue: optionIndexesInValue, replace: replace, searchPlaceholder: searchPlaceholder, search: search, setSearch: setSearch, usingKeyboard: usingKeyboard, value: value, valueKey: valueKey, showSelectedInline: showSelectedInline }, children); var dropButtonProps = { ref: dropButtonRef, a11yTitle: (ariaLabel || a11yTitle || placeholder || format({ id: 'selectMultiple.open', messages: messages })) + ". " + format({ id: 'selectMultiple.selected', values: { selected: (value == null ? void 0 : value.length) || 0, total: allOptions.length } }), 'aria-expanded': Boolean(open), 'aria-haspopup': 'listbox', id: id, disabled: disabled === true || undefined, open: open, focusIndicator: focusIndicator, onFocus: onFocus, onBlur: onBlur, gridArea: gridArea, margin: margin, onOpen: onRequestOpen, onClose: onRequestClose, onClick: onClick, plainSelect: plain, plain: plain, // Button should be plain dropProps: dropProps, dropContent: dropContent, theme: theme }; return /*#__PURE__*/_react["default"].createElement(_Keyboard.Keyboard, { onDown: onRequestOpen, onUp: onRequestOpen }, showSelectedInline ? /*#__PURE__*/_react["default"].createElement(StyledSelectBox, _extends({ disabled: disabled === true || undefined, alignSelf: alignSelf, direction: "row", alignContent: "start", background: theme.select.background, ref: selectBoxRef, flex: false, plainSelect: plain, width: width }, passThemeFlag), /*#__PURE__*/_react["default"].createElement(_Box.Box, { width: "100%" }, /*#__PURE__*/_react["default"].createElement(_DropButton.DropButton, _extends({ fill: "horizontal", alignSelf: "start" }, dropButtonProps, { dropAlign: dropAlign, dropTarget: dropTarget || selectBoxRef.current }), selectValue || displayLabelKey ? /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement(_Box.Box, { direction: "row" }, /*#__PURE__*/_react["default"].createElement(_StyledSelect.SelectTextInput, _extends({ a11yTitle: ariaLabel || a11yTitle, defaultCursor: disabled === true || undefined, focusIndicator: false, id: id ? (0, _utils2.selectInputId)(id) : undefined, inert: _utils2.inertTrueValue, name: name, width: "100%" }, rest, { tabIndex: "-1", type: "text", placeholder: !value || (value == null ? void 0 : value.length) === 0 ? placeholder || selectValue || displayLabelKey : format({ id: onMore ? 'selectMultiple.selected' : 'selectMultiple.selectedOfTotal', messages: messages, values: _extends({ selected: (value == null ? void 0 : value.length) || 0 }, !onMore ? { total: allOptions.length } : {}) }), plain: true, readOnly: true, value: "", theme: theme })), displaySelectIcon), /*#__PURE__*/_react["default"].createElement(_StyledSelect.HiddenInput, { type: "text", name: name, id: id ? (0, _utils2.selectInputId)(id) : undefined, inert: _utils2.inertTrueValue, value: inputValue, ref: inputRef, readOnly: true })) : /*#__PURE__*/_react["default"].createElement(_Box.Box, { direction: "row" }, /*#__PURE__*/_react["default"].createElement(_DefaultSelectTextInput.DefaultSelectTextInput, _extends({ a11yTitle: ariaLabel || a11yTitle, disabled: disabled, id: id, inert: _utils2.inertTrueValue, name: name, ref: inputRef, placeholder: placeholder || 'Select', value: inputValue, size: size, theme: theme }, rest)), displaySelectIcon)), !open && (value == null ? void 0 : value.length) > 0 && (selectValue || displayLabelKey))) : /*#__PURE__*/_react["default"].createElement(_Box.Box, { width: width }, /*#__PURE__*/_react["default"].createElement(_StyledSelect.StyledSelectDropButton, _extends({}, dropButtonProps, { dropAlign: dropAlign, dropTarget: dropTarget, alignSelf: alignSelf, tabIndex: "0", "aria-labelledby": ariaLabelledByProp || ariaLabelledBy }), /*#__PURE__*/_react["default"].createElement(_Box.Box, { align: "center", direction: "row", justify: "between", background: theme.select.background }, /*#__PURE__*/_react["default"].createElement(_Box.Box, { direction: "row", flex: true, basis: "auto" }, selectValue || displayLabelKey ? /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, selectValue || displayLabelKey, /*#__PURE__*/_react["default"].createElement(_StyledSelect.HiddenInput, { type: "text", name: name, id: id ? (0, _utils2.selectInputId)(id) : undefined, inert: _utils2.inertTrueValue, value: inputValue, ref: inputRef, readOnly: true })) : /*#__PURE__*/_react["default"].createElement(_DefaultSelectTextInput.DefaultSelectTextInput, _extends({ a11yTitle: ariaLabel || a11yTitle, disabled: disabled, id: id, inert: _utils2.inertTrueValue, name: name, ref: inputRef, placeholder: placeholder, value: inputValue, size: size, theme: theme }, rest))), displaySelectIcon)))); }); SelectMultiple.displayName = 'SelectMultiple'; SelectMultiple.propTypes = _propTypes.SelectMultiplePropTypes;