UNPKG

suomifi-ui-components

Version:
566 lines (563 loc) 24.6 kB
import { __extends, __rest, __assign, __makeTemplateObject } from 'tslib'; import React, { Component, forwardRef } from 'react'; import { styled } from 'styled-components'; import classnames from 'classnames'; import '../../../../reset/HtmlA/HtmlA.js'; import '../../../../reset/HtmlButton/HtmlButton.js'; import { HtmlDiv } from '../../../../reset/HtmlDiv/HtmlDiv.js'; import '../../../../reset/HtmlFieldSet/HtmlFieldSet.js'; import '../../../../reset/HtmlH/HtmlH.js'; import '../../../../reset/HtmlInput/HtmlInput.js'; import '../../../../reset/HtmlLabel/HtmlLabel.js'; import '../../../../reset/HtmlLegend/HtmlLegend.js'; import '../../../../reset/HtmlLi/HtmlLi.js'; import '../../../../reset/HtmlNav/HtmlNav.js'; import '../../../../reset/HtmlOl/HtmlOl.js'; import '../../../../reset/HtmlSpan/HtmlSpan.js'; import '../../../../reset/HtmlTextarea/HtmlTextarea.js'; import '../../../../reset/HtmlUl/HtmlUl.js'; import '../../../../reset/HtmlTable/HtmlTable.js'; import '../../../../reset/HtmlTable/HtmlTableCaption.js'; import '../../../../reset/HtmlTable/HtmlTableHeader.js'; import '../../../../reset/HtmlTable/HtmlTableRow.js'; import '../../../../reset/HtmlTable/HtmlTableBody.js'; import '../../../../reset/HtmlTable/HtmlTableHeaderCell.js'; import '../../../../reset/HtmlTable/HtmlTableCell.js'; import { getOwnerDocument, escapeStringRegexp, filterDuplicateKeys } from '../../../../utils/common/common.js'; import { AutoId } from '../../../utils/AutoId/AutoId.js'; import { Debounce } from '../../../utils/Debounce/Debounce.js'; import { Popover } from '../../../Popover/Popover.js'; import { SuomifiThemeConsumer } from '../../../theme/SuomifiThemeProvider/SuomifiThemeProvider.js'; import '../../../theme/SuomifiTheme/SuomifiTheme.js'; import { SpacingConsumer } from '../../../theme/SpacingProvider/SpacingProvider.js'; import { separateMarginProps } from '../../../theme/utils/spacing.js'; import { FilterInput } from '../../FilterInput/FilterInput.js'; import { LoadingSpinner } from '../../../LoadingSpinner/LoadingSpinner.js'; import { VisuallyHidden } from '../../../VisuallyHidden/VisuallyHidden.js'; import { InputClearButton } from '../../InputClearButton/InputClearButton.js'; import { SelectItemList } from '../BaseSelect/SelectItemList/SelectItemList.js'; import { SelectItem } from '../BaseSelect/SelectItem/SelectItem.js'; import { SelectEmptyItem } from '../BaseSelect/SelectEmptyItem/SelectEmptyItem.js'; import { InputToggleButton } from '../../InputToggleButton/InputToggleButton.js'; import { baseStyles } from './SingleSelect.baseStyles.js'; import { SelectItemAddition } from '../BaseSelect/SelectItemAddition/SelectItemAddition.js'; var baseClassName = 'fi-single-select'; var singleSelectClassNames = { valueSelected: "".concat(baseClassName, "--value-selected"), clearButtonWrapper: "".concat(baseClassName, "_clear-button_wrapper"), open: "".concat(baseClassName, "--open"), error: "".concat(baseClassName, "--error"), queryHighlight: "".concat(baseClassName, "-item--query_highlight"), fullWidth: "".concat(baseClassName, "--full-width") }; var BaseSingleSelect = function (_super) { __extends(BaseSingleSelect, _super); function BaseSingleSelect(props) { var _this = this; var _a, _b; _this = _super.call(this, props) || this; _this.preventShowPopoverOnInputFocus = false; _this.state = { filterInputValue: ((_a = _this.props.selectedItem) === null || _a === void 0 ? void 0 : _a.labelText) ? _this.props.selectedItem.labelText : ((_b = _this.props.defaultSelectedItem) === null || _b === void 0 ? void 0 : _b.labelText) || '', filteredItems: _this.props.items, filterMode: false, showPopover: false, focusedDescendantId: null, selectedItem: _this.props.selectedItem ? _this.props.selectedItem : _this.props.defaultSelectedItem || null, initialItems: _this.props.items, computedItems: _this.props.items }; _this.filter = function (data, query) { return data.labelText.toLowerCase().includes(query.toLowerCase()); }; _this.handleBlur = function () { if (!!_this.props.onBlur) { _this.props.onBlur(); } var ownerDocument = getOwnerDocument(_this.popoverListRef); if (!ownerDocument) { return; } requestAnimationFrame(function () { var _a, _b; var focusInPopover = (_a = _this.popoverListRef.current) === null || _a === void 0 ? void 0 : _a.contains(ownerDocument.activeElement); var focusInToggleButton = (_b = _this.toggleButtonRef.current) === null || _b === void 0 ? void 0 : _b.contains(ownerDocument.activeElement); var focusInInput = ownerDocument.activeElement === _this.filterInputRef.current; var focusInSingleSelect = focusInPopover || focusInInput || focusInToggleButton; if (!focusInSingleSelect) { _this.setState(function (prevState) { var _a; return { filterInputValue: ((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.labelText) || '', filterMode: false, showPopover: focusInSingleSelect, focusedDescendantId: null }; }); } }); }; _this.handleOnChange = function (value) { _this.setState(function (prevState) { var _a; var newValue = prevState.filterMode || !prevState.selectedItem ? value : value.replace(new RegExp("^".concat(escapeStringRegexp(((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.labelText) || ''))), ''); return { filterInputValue: newValue, showPopover: true, filterMode: true }; }); }; _this.focusToInputAndSelectText = function () { if (!!_this.filterInputRef && _this.filterInputRef.current) { _this.filterInputRef.current.focus(); setTimeout(function () { var _a; return (_a = _this.filterInputRef.current) === null || _a === void 0 ? void 0 : _a.select(); }, 100); } }; _this.focusToInputAndCloseMenu = function () { _this.focusToInputAndSelectText(); _this.setState({ showPopover: false, filterMode: false, focusedDescendantId: null }); }; _this.handleItemSelection = function (item) { if (item !== null && item.disabled) return; var _a = _this.props, onItemSelect = _a.onItemSelect, onItemSelectionChange = _a.onItemSelectionChange, controlledItem = _a.selectedItem; if (!controlledItem) { var userAddedSelectedItem = []; if (item !== null) { var itemIsFromPropItems = _this.props.items.some(function (propItem) { return propItem.uniqueItemId === item.uniqueItemId; }); if (!itemIsFromPropItems) { userAddedSelectedItem.push(item); } } _this.setState({ selectedItem: item || null, filterInputValue: (item === null || item === void 0 ? void 0 : item.labelText) || '', focusedDescendantId: null, computedItems: _this.props.items.concat(userAddedSelectedItem) }); } else { _this.setState(function (prevState) { var _a; return { filterInputValue: ((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.labelText) || '' }; }); } if (!!onItemSelect) { onItemSelect((item === null || item === void 0 ? void 0 : item.uniqueItemId) || null); } if (!!onItemSelectionChange) { onItemSelectionChange(item); } _this.focusToInputAndCloseMenu(); }; _this.handleKeyDown = function (event) { var _a = _this.state, filteredItems = _a.filteredItems, focusedDescendantId = _a.focusedDescendantId, filterMode = _a.filterMode, filterInputValue = _a.filterInputValue; var popoverItems = !!filterMode ? filteredItems : _this.state.computedItems; var index = !!focusedDescendantId ? popoverItems.findIndex(function (_a) { var uniqueItemId = _a.uniqueItemId; return uniqueItemId === focusedDescendantId; }) : null; var getNextIndex = function getNextIndex() { return index !== null ? (index + 1) % popoverItems.length : 0; }; var getPreviousIndex = function getPreviousIndex() { return index !== null && index !== -1 ? (index - 1 + popoverItems.length) % popoverItems.length : popoverItems.length - 1; }; var getNextItem = function getNextItem() { return popoverItems[getNextIndex()]; }; var getPreviousItem = function getPreviousItem() { return popoverItems[getPreviousIndex()]; }; switch (event.key) { case 'ArrowDown': { event.preventDefault(); if (!_this.state.showPopover) { _this.setState({ showPopover: true }); } var nextItem = _this.props.allowItemAddition && (index === popoverItems.length - 1 || popoverItems.length === 0) && filterInputValue !== '' && !_this.inputValueInItems() ? { uniqueItemId: filterInputValue.toLowerCase(), labelText: filterInputValue } : getNextItem(); if (nextItem) { _this.setState({ focusedDescendantId: nextItem.uniqueItemId }); } break; } case 'ArrowUp': { event.preventDefault(); if (!_this.state.showPopover) { _this.setState({ showPopover: true }); } var previousItem = _this.props.allowItemAddition && (index === null || index === 0) && filterInputValue !== '' && !_this.inputValueInItems() ? { uniqueItemId: filterInputValue.toLowerCase(), labelText: filterInputValue } : getPreviousItem(); if (previousItem) { _this.setState({ focusedDescendantId: previousItem.uniqueItemId }); } break; } case 'Enter': { event.preventDefault(); if (focusedDescendantId) { var focusedItem = popoverItems.find(function (_a) { var uniqueItemId = _a.uniqueItemId; return uniqueItemId === focusedDescendantId; }); if (focusedItem) { _this.handleItemSelection(focusedItem); } else { var userAddedItem = { uniqueItemId: filterInputValue.toLowerCase(), labelText: filterInputValue }; _this.handleItemSelection(userAddedItem); } } break; } case 'Escape': { if (_this.state.showPopover) { event.stopPropagation(); } if (!_this.state.selectedItem) { _this.setState({ filterInputValue: '' }); } _this.focusToInputAndCloseMenu(); break; } } }; _this.inputValueInItems = function () { return !!_this.state.computedItems.find(function (ci) { return ci.uniqueItemId === _this.state.filterInputValue.toLowerCase() || ci.labelText.toLowerCase() === _this.state.filterInputValue.toLowerCase(); }); }; _this.popoverListRef = /*#__PURE__*/React.createRef(); if (_this.props.forwardedRef) { _this.filterInputRef = _this.props.forwardedRef; } else { _this.filterInputRef = /*#__PURE__*/React.createRef(); } _this.toggleButtonRef = /*#__PURE__*/React.createRef(); _this.clearButtonRef = /*#__PURE__*/React.createRef(); return _this; } BaseSingleSelect.getDerivedStateFromProps = function (nextProps, prevState) { var _a; var propItems = nextProps.items, selectedItem = nextProps.selectedItem; var selectedItemChanged = 'selectedItem' in nextProps && (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.uniqueItemId) !== ((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.uniqueItemId); if (selectedItemChanged || propItems !== prevState.initialItems) { var resolvedSelectedItem = 'selectedItem' in nextProps ? selectedItem : propItems.find(function (item) { var _a; return item.uniqueItemId === ((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.uniqueItemId); }); var resolvedInputValue = prevState.filterInputValue; if (selectedItemChanged) { resolvedInputValue = selectedItem ? selectedItem.labelText || prevState.filterInputValue : ''; } else { var matchingItem = propItems.find(function (item) { var _a; return item.uniqueItemId === ((_a = prevState.selectedItem) === null || _a === void 0 ? void 0 : _a.uniqueItemId); }); if (matchingItem) { resolvedInputValue = matchingItem.labelText && !prevState.filterMode ? matchingItem.labelText : prevState.filterInputValue; } } return { selectedItem: resolvedSelectedItem, filteredItems: propItems, filterInputValue: resolvedInputValue, filterMode: prevState.filterMode, initialItems: propItems }; } return null; }; BaseSingleSelect.prototype.componentDidUpdate = function (prevProps) { if (JSON.stringify(this.props.items) !== JSON.stringify(prevProps.items)) { this.setState({ computedItems: this.props.items }); } }; BaseSingleSelect.prototype.isOutsideClick = function (event) { return !!this.toggleButtonRef && this.toggleButtonRef.current.contains(event.target); }; BaseSingleSelect.prototype.render = function () { var _a; var _this = this; var _b = this.state, filteredItems = _b.filteredItems, filterMode = _b.filterMode, showPopover = _b.showPopover, focusedDescendantId = _b.focusedDescendantId, filterInputValue = _b.filterInputValue, selectedItem = _b.selectedItem, computedItems = _b.computedItems; var _c = this.props, id = _c.id, className = _c.className; _c.theme; var labelText = _c.labelText, optionalText = _c.optionalText, hintText = _c.hintText; _c.onItemSelectionChange; var visualPlaceholder = _c.visualPlaceholder, noItemsText = _c.noItemsText; _c.defaultSelectedItem; var propOnChange = _c.onChange, onChangeWithoutDebounce = _c.onChangeWithoutDebounce; _c.onBlur; var debounce = _c.debounce, loading = _c.loading, loadingText = _c.loadingText, status = _c.status, statusText = _c.statusText; _c.selectedItem; var clearButtonLabel = _c.clearButtonLabel, ariaOptionsAvailableText = _c.ariaOptionsAvailableText, ariaOptionsAvailableTextFunction = _c.ariaOptionsAvailableTextFunction; _c.onItemSelect; var disabled = _c.disabled, allowItemAddition = _c.allowItemAddition, itemAdditionHelpText = _c.itemAdditionHelpText, tooltipComponent = _c.tooltipComponent; _c.items; _c.forwardedRef; var listProps = _c.listProps, style = _c.style, fullWidth = _c.fullWidth, rest = __rest(_c, ["id", "className", "theme", "labelText", "optionalText", "hintText", "onItemSelectionChange", "visualPlaceholder", "noItemsText", "defaultSelectedItem", "onChange", "onChangeWithoutDebounce", "onBlur", "debounce", "loading", "loadingText", "status", "statusText", "selectedItem", "clearButtonLabel", "ariaOptionsAvailableText", "ariaOptionsAvailableTextFunction", "onItemSelect", "disabled", "allowItemAddition", "itemAdditionHelpText", "tooltipComponent", "items", "forwardedRef", "listProps", "style", "fullWidth"]); var _d = separateMarginProps(rest), passProps = _d[1]; var ariaActiveDescendant = focusedDescendantId ? "".concat(id, "-").concat(focusedDescendantId) : ''; var popoverItemListId = "".concat(id, "-popover"); var popoverItems = filterMode ? filteredItems : computedItems; return /*#__PURE__*/React.createElement(HtmlDiv, __assign({}, passProps, { className: classnames(baseClassName, className, (_a = {}, _a[singleSelectClassNames.valueSelected] = !!selectedItem, _a[singleSelectClassNames.open] = showPopover, _a[singleSelectClassNames.error] = status === 'error', _a[singleSelectClassNames.fullWidth] = fullWidth, _a)), style: style }), /*#__PURE__*/React.createElement(Debounce, { waitFor: debounce }, function (debouncer) { return /*#__PURE__*/React.createElement(FilterInput, { inputElementContainerProps: { role: 'combobox', 'aria-haspopup': 'listbox', 'aria-owns': popoverItemListId, 'aria-expanded': showPopover }, "aria-activedescendant": ariaActiveDescendant, id: id, "aria-controls": popoverItemListId, labelText: labelText, optionalText: optionalText, hintText: hintText, items: computedItems, onFilter: function onFilter(filtered) { if (_this.state.filterMode) { _this.setState(function (prevState) { var newFocusedDescendandId = prevState.focusedDescendantId; if (!filtered.some(function (f) { return f.uniqueItemId === newFocusedDescendandId; })) { newFocusedDescendandId = null; } return { filteredItems: filtered, focusedDescendantId: newFocusedDescendandId }; }); } }, filterFunc: _this.filter, forwardedRef: _this.filterInputRef, onFocus: function onFocus() { if (!_this.preventShowPopoverOnInputFocus) { _this.setState({ showPopover: true }); } _this.preventShowPopoverOnInputFocus = false; }, onClick: function onClick() { _this.focusToInputAndSelectText(); _this.setState({ showPopover: true }); }, onKeyDown: _this.handleKeyDown, onBlur: _this.handleBlur, value: filterInputValue, onChange: function onChange(value) { if (propOnChange) { debouncer(propOnChange, value); } if (onChangeWithoutDebounce) { onChangeWithoutDebounce(value); } _this.handleOnChange(value); }, visualPlaceholder: !selectedItem ? visualPlaceholder : '', status: status, statusText: statusText, disabled: disabled, tooltipComponent: tooltipComponent }, !!selectedItem && ( /*#__PURE__*/React.createElement(HtmlDiv, { className: singleSelectClassNames.clearButtonWrapper }, /*#__PURE__*/React.createElement(InputClearButton, { forwardedRef: _this.clearButtonRef, onClick: function onClick() { return _this.handleItemSelection(null); }, onBlur: _this.handleBlur, label: clearButtonLabel, disabled: disabled }))), /*#__PURE__*/React.createElement(InputToggleButton, { open: showPopover, ref: _this.toggleButtonRef, onClick: function onClick(event) { event.preventDefault(); _this.setState(function (prevState) { return { showPopover: !prevState.showPopover }; }); _this.preventShowPopoverOnInputFocus = true; _this.focusToInputAndSelectText(); }, "aria-hidden": true, tabIndex: -1, disabled: disabled })); }), showPopover && ( /*#__PURE__*/React.createElement(Popover, { sourceRef: this.filterInputRef, matchWidth: true, onKeyDown: this.handleKeyDown, onClickOutside: function onClickOutside(event) { if (!_this.isOutsideClick(event)) { _this.setState({ showPopover: false }); } } }, /*#__PURE__*/React.createElement(SelectItemList, __assign({ id: popoverItemListId, ref: this.popoverListRef, focusedDescendantId: ariaActiveDescendant }, listProps), /*#__PURE__*/React.createElement(React.Fragment, null, popoverItems.length > 0 && !loading && popoverItems.map(function (item) { var _a; var isCurrentlySelected = item.uniqueItemId === focusedDescendantId; return /*#__PURE__*/React.createElement(SelectItem, __assign({ hasKeyboardFocus: isCurrentlySelected, key: "".concat(item.uniqueItemId, "_").concat(item.uniqueItemId === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.uniqueItemId)), id: "".concat(id, "-").concat(item.uniqueItemId), checked: item.uniqueItemId === (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.uniqueItemId), disabled: item.disabled, onClick: function onClick() { _this.handleItemSelection(item); }, hightlightQuery: filterMode ? (_a = _this.filterInputRef.current) === null || _a === void 0 ? void 0 : _a.value : '' }, item.listItemProps), item.labelText); }), popoverItems.length === 0 && !allowItemAddition && !loading && /*#__PURE__*/React.createElement(SelectEmptyItem, null, noItemsText), loading && ( /*#__PURE__*/React.createElement(SelectEmptyItem, { className: "loading" }, /*#__PURE__*/React.createElement(LoadingSpinner, { status: "loading", variant: "small", textAlign: "right", text: loadingText }))), filterInputValue !== '' && !this.inputValueInItems() && allowItemAddition && !loading && ( /*#__PURE__*/React.createElement(SelectItemAddition, { hintText: itemAdditionHelpText, hasKeyboardFocus: filterInputValue === focusedDescendantId, id: "".concat(id, "-").concat(filterInputValue.toLowerCase()), onClick: function onClick() { var item = { labelText: filterInputValue, uniqueItemId: filterInputValue.toLowerCase() }; _this.handleItemSelection(item); } }, filterInputValue)))))), /*#__PURE__*/React.createElement(VisuallyHidden, { "aria-live": "polite", "aria-atomic": "true" }, this.state.filterMode && !loading ? ariaOptionsAvailableTextFunction ? ariaOptionsAvailableTextFunction(popoverItems.length) : "".concat(popoverItems.length, " ").concat(ariaOptionsAvailableText) : ''), /*#__PURE__*/React.createElement(VisuallyHidden, { "aria-live": "polite", "aria-atomic": "true", id: "".concat(id, "-loading-announce") }, loading ? loadingText : '')); }; return BaseSingleSelect; }(Component); var StyledSingleSelect = styled(function (_a) { _a.globalMargins; var passProps = __rest(_a, ["globalMargins"]); return /*#__PURE__*/React.createElement(BaseSingleSelect, __assign({}, passProps)); }).withConfig({ componentId: "sc-t1n3a2-0" })(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n ", "\n"], ["\n ", "\n"])), function (_a) { var theme = _a.theme, globalMargins = _a.globalMargins, rest = __rest(_a, ["theme", "globalMargins"]); var _b = separateMarginProps(rest), marginProps = _b[0]; var cleanedGlobalMargins = filterDuplicateKeys(globalMargins.singleSelect, marginProps); return baseStyles(theme, cleanedGlobalMargins, marginProps); }); function SingleSelectInner(props, ref) { var propId = props.id, passProps = __rest(props, ["id"]); return /*#__PURE__*/React.createElement(SpacingConsumer, null, function (_a) { var margins = _a.margins; return /*#__PURE__*/React.createElement(SuomifiThemeConsumer, null, function (_a) { var suomifiTheme = _a.suomifiTheme; return /*#__PURE__*/React.createElement(AutoId, { id: propId }, function (id) { return /*#__PURE__*/React.createElement(StyledSingleSelect, __assign({ theme: suomifiTheme, id: id, globalMargins: margins, forwardedRef: ref }, passProps)); }); }); }); } var SingleSelect = /*#__PURE__*/forwardRef(SingleSelectInner); SingleSelect.displayName = 'SingleSelect'; var templateObject_1; export { SingleSelect }; //# sourceMappingURL=SingleSelect.js.map