suomifi-ui-components
Version:
Suomi.fi UI component library
566 lines (563 loc) • 24.6 kB
JavaScript
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