suomifi-ui-components
Version:
Suomi.fi UI component library
486 lines (483 loc) • 21.2 kB
JavaScript
import { __extends, __rest, __assign, __makeTemplateObject } from 'tslib';
import React, { createRef, Component, forwardRef } from 'react';
import { styled } from 'styled-components';
import classnames from 'classnames';
import { AutoId } from '../../utils/AutoId/AutoId.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 { getConditionalAriaProp } from '../../../utils/aria/aria.js';
import { Debounce } from '../../utils/Debounce/Debounce.js';
import '../../../reset/HtmlA/HtmlA.js';
import '../../../reset/HtmlButton/HtmlButton.js';
import { HtmlDiv, HtmlDivWithRef } from '../../../reset/HtmlDiv/HtmlDiv.js';
import '../../../reset/HtmlFieldSet/HtmlFieldSet.js';
import '../../../reset/HtmlH/HtmlH.js';
import { HtmlInput } from '../../../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 { HtmlSpan } from '../../../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 { VisuallyHidden } from '../../VisuallyHidden/VisuallyHidden.js';
import { StatusText } from '../StatusText/StatusText.js';
import { Label } from '../Label/Label.js';
import { IconSearch } from 'suomifi-icons';
import { baseStyles } from './SearchInput.baseStyles.js';
import { InputClearButton } from '../InputClearButton/InputClearButton.js';
import { filterDuplicateKeys, getOwnerDocument } from '../../../utils/common/common.js';
import { Popover, PopoverConsumer } from '../../Popover/Popover.js';
import { SelectItem } from '../Select/BaseSelect/SelectItem/SelectItem.js';
import { SuggestionList } from './SuggestionList/SuggestionList.js';
import { Button } from '../../Button/Button.js';
var baseClassName = 'fi-search-input';
var searchInputClassNames = {
fullWidth: "".concat(baseClassName, "--full-width"),
error: "".concat(baseClassName, "--error"),
notEmpty: "".concat(baseClassName, "--not-empty"),
noSearchButton: "".concat(baseClassName, "--no-search-button"),
labelIsVisible: "".concat(baseClassName, "_label--visible"),
styleWrapper: "".concat(baseClassName, "_wrapper"),
inputElement: "".concat(baseClassName, "_input"),
inputElementContainer: "".concat(baseClassName, "_input-element-container"),
functionalityContainer: "".concat(baseClassName, "_functionality-container"),
statusTextHasContent: "".concat(baseClassName, "_statusText--has-content"),
button: "".concat(baseClassName, "_button"),
searchButton: "".concat(baseClassName, "_button-search"),
searchIcon: "".concat(baseClassName, "_search-icon"),
clearButton: "".concat(baseClassName, "_button-clear"),
clearIcon: "".concat(baseClassName, "_button-clear-icon"),
suggestions: "".concat(baseClassName, "_suggestions"),
suggestionsOpen: "".concat(baseClassName, "--suggestions-open")
};
var BaseSearchInput = function (_super) {
__extends(BaseSearchInput, _super);
function BaseSearchInput() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.state = {
value: _this.props.value || _this.props.defaultValue || '',
displayValue: _this.props.value || _this.props.defaultValue || '',
showPopover: false,
focusedDescendantId: null,
popoverPlacement: 'bottom'
};
_this.inputRef = _this.props.forwardedRef || /*#__PURE__*/createRef();
_this.functionalityWrapperRef = /*#__PURE__*/createRef();
_this.popoverListRef = /*#__PURE__*/createRef();
_this.conditionalSetState = function (newValue) {
if (!('value' in _this.props)) {
_this.setState({
value: newValue,
displayValue: newValue
});
} else {
_this.setState({
displayValue: newValue
});
}
};
_this.updatePopoverPlacement = function (placement) {
if (!placement) return;
if (placement !== _this.state.popoverPlacement) {
requestAnimationFrame(function () {
return _this.setState({
popoverPlacement: placement
});
});
}
};
return _this;
}
BaseSearchInput.prototype.componentDidMount = function () {
var _a;
if (this.props.autosuggest && this.props.suggestions && ((_a = this.props.suggestions) === null || _a === void 0 ? void 0 : _a.length) > 0) {
this.setState({
showPopover: true
});
}
};
BaseSearchInput.prototype.componentDidUpdate = function (prevProps, prevState) {
if (!this.props.autosuggest) {
return;
}
var newDisplayValue = '';
if (this.props.value) {
newDisplayValue = this.props.value;
} else if (this.state.displayValue !== this.state.value) {
newDisplayValue = this.state.displayValue;
} else {
newDisplayValue = this.state.value || '';
}
var hasSuggestions = this.props.suggestions && this.props.suggestions.length > 0;
var hadSuggestions = prevProps.suggestions && prevProps.suggestions.length > 0;
var focusInInput = this.inputRef.current && this.inputRef.current === document.activeElement;
var suggestionsChanged = (hasSuggestions || hadSuggestions) && JSON.stringify(prevProps.suggestions) !== JSON.stringify(this.props.suggestions);
var valueChanged = this.props.value !== prevProps.value || this.state.value !== prevState.value && this.state.displayValue !== prevState.displayValue;
var suggestionsPresentButHidden = hasSuggestions && !this.state.showPopover;
if (prevProps.autosuggest !== this.props.autosuggest || suggestionsChanged) {
this.setState({
showPopover: focusInInput && (hasSuggestions || suggestionsPresentButHidden && valueChanged),
displayValue: newDisplayValue,
focusedDescendantId: null
});
} else if (valueChanged && suggestionsPresentButHidden && focusInInput) {
this.setState({
showPopover: true,
focusedDescendantId: null
});
}
};
BaseSearchInput.getDerivedStateFromProps = function (nextProps, prevState) {
var value = nextProps.value;
if ('value' in nextProps && value !== prevState.value) {
return {
value: value,
displayValue: value
};
}
return null;
};
BaseSearchInput.prototype.render = function () {
var _a, _b, _c;
var _this = this;
var _d = this.props;
_d.value;
_d.defaultValue;
var className = _d.className,
labelText = _d.labelText,
labelMode = _d.labelMode,
clearButtonLabel = _d.clearButtonLabel,
searchButtonLabel = _d.searchButtonLabel,
searchButtonProps = _d.searchButtonProps,
style = _d.style,
propOnChange = _d.onChange,
propOnSearch = _d.onSearch;
_d.children;
var status = _d.status,
statusText = _d.statusText,
visualPlaceholder = _d.visualPlaceholder,
id = _d.id,
fullWidth = _d.fullWidth,
debounce = _d.debounce;
_d.theme;
_d.forwardedRef;
var ariaDescribedBy = _d["aria-describedby"],
_e = _d.statusTextAriaLiveMode,
statusTextAriaLiveMode = _e === void 0 ? 'assertive' : _e,
_f = _d.autosuggest,
autosuggest = _f === void 0 ? false : _f,
suggestions = _d.suggestions,
suggestionHintText = _d.suggestionHintText,
ariaOptionsAvailableText = _d.ariaOptionsAvailableText,
onSuggestionSelected = _d.onSuggestionSelected,
popoverClassName = _d.popoverClassName,
_g = _d.showSearchButtonLabel,
showSearchButtonLabel = _g === void 0 ? false : _g,
_h = _d.showSearchIcon,
showSearchIcon = _h === void 0 ? true : _h,
rest = __rest(_d, ["value", "defaultValue", "className", "labelText", "labelMode", "clearButtonLabel", "searchButtonLabel", "searchButtonProps", "style", "onChange", "onSearch", "children", "status", "statusText", "visualPlaceholder", "id", "fullWidth", "debounce", "theme", "forwardedRef", 'aria-describedby', "statusTextAriaLiveMode", "autosuggest", "suggestions", "suggestionHintText", "ariaOptionsAvailableText", "onSuggestionSelected", "popoverClassName", "showSearchButtonLabel", "showSearchIcon"]);
var _j = separateMarginProps(rest),
passProps = _j[1];
var statusTextId = "".concat(id, "-statusText");
var suggestionHintId = "".concat(id, "-suggestionHintText");
var onSearch = function onSearch(searchValue) {
if (!!propOnSearch) {
propOnSearch(searchValue);
}
};
var onClear = function onClear() {
_this.conditionalSetState('');
if (propOnChange) {
propOnChange('');
}
setTimeout(function () {
if (_this.inputRef.current) {
_this.inputRef.current.focus();
}
}, 100);
};
var handleOnBlur = function handleOnBlur(event) {
var currentValue = _this.state.displayValue;
if (_this.props.onBlur) {
_this.props.onBlur(event, currentValue);
}
var ownerDocument = getOwnerDocument(_this.popoverListRef);
if (!ownerDocument) {
return;
}
requestAnimationFrame(function () {
var _a;
var focusInPopover = (_a = _this.popoverListRef.current) === null || _a === void 0 ? void 0 : _a.contains(ownerDocument.activeElement);
var focusInInput = ownerDocument.activeElement === _this.inputRef.current;
var focusInComponent = focusInPopover || focusInInput;
if (!focusInComponent) {
_this.setState(function (prevState) {
var inputValue = _this.props.value || prevState.displayValue;
return {
value: inputValue,
showPopover: false,
focusedDescendantId: null
};
});
}
});
};
var onKeyDown = function onKeyDown(event) {
var focusedDescendantId = _this.state.focusedDescendantId;
var popoverItems = suggestions || [];
var index = focusedDescendantId ? popoverItems.findIndex(function (_a) {
var uniqueId = _a.uniqueId;
return uniqueId === focusedDescendantId;
}) : -1;
var getNextIndex = function getNextIndex() {
return (index + 1) % popoverItems.length;
};
var getPreviousIndex = function getPreviousIndex() {
return (index - 1 + popoverItems.length) % popoverItems.length;
};
if (event.key === 'Escape') {
event.preventDefault();
_this.setState(function (prevState) {
return {
showPopover: false,
focusedDescendantId: null,
displayValue: prevState.value
};
});
setTimeout(function () {
if (_this.inputRef.current) {
_this.inputRef.current.focus();
}
}, 100);
}
if (event.key === 'Enter') {
if (autosuggest && suggestions && suggestions.length > 0 && focusedDescendantId) {
event.preventDefault();
var selectedItem = popoverItems.find(function (_a) {
var uniqueId = _a.uniqueId;
return uniqueId === focusedDescendantId;
});
if (selectedItem) {
_this.setState({
showPopover: false,
value: selectedItem.label,
displayValue: selectedItem.label,
focusedDescendantId: null
});
if (onSuggestionSelected) {
onSuggestionSelected(selectedItem.uniqueId);
}
}
} else if (!!_this.state.value && propOnSearch) {
event.preventDefault();
onSearch(_this.state.displayValue);
}
}
if (event.key === 'ArrowDown') {
event.preventDefault();
if (autosuggest && popoverItems.length > 0) {
var nextIndex = getNextIndex();
_this.setState({
focusedDescendantId: popoverItems[nextIndex].uniqueId,
showPopover: true,
displayValue: _this.props.value || popoverItems[nextIndex].label
});
}
}
if (event.key === 'ArrowUp') {
event.preventDefault();
if (autosuggest && popoverItems.length > 0) {
var previousIndex = getPreviousIndex();
_this.setState({
focusedDescendantId: popoverItems[previousIndex].uniqueId,
showPopover: true,
displayValue: _this.props.value || popoverItems[previousIndex].label
});
}
}
};
var getInputContainerProps = function getInputContainerProps() {
if (autosuggest) {
return {
role: 'combobox',
'aria-owns': "".concat(id, "-itemlist"),
'aria-expanded': _this.state.showPopover,
'aria-haspopup': 'listbox'
};
}
return {};
};
var searchButtonDerivedProps = __assign(__assign({}, searchButtonProps), {
className: classnames(searchInputClassNames.button, searchInputClassNames.searchButton, searchButtonProps === null || searchButtonProps === void 0 ? void 0 : searchButtonProps.className),
onClick: this.state.value ? function () {
return onSearch(_this.state.displayValue);
} : undefined,
'aria-label': showSearchButtonLabel ? undefined : searchButtonLabel
});
var clearButtonProps = __assign({
className: classnames(searchInputClassNames.button, searchInputClassNames.clearButton)
}, !!this.state.value ? {
onClick: onClear
} : {
tabIndex: -1,
hidden: true
});
return /*#__PURE__*/React.createElement(HtmlDiv, {
className: classnames(className, baseClassName, (_a = {}, _a[searchInputClassNames.error] = status === 'error', _a[searchInputClassNames.notEmpty] = !!this.state.value, _a[searchInputClassNames.fullWidth] = fullWidth, _a[searchInputClassNames.suggestionsOpen] = this.state.showPopover, _a[searchInputClassNames.noSearchButton] = !propOnSearch, _a)),
style: style
}, /*#__PURE__*/React.createElement(HtmlSpan, {
className: searchInputClassNames.styleWrapper
}, /*#__PURE__*/React.createElement(Label, {
htmlFor: id,
labelMode: labelMode,
className: classnames((_b = {}, _b[searchInputClassNames.labelIsVisible] = labelMode !== 'hidden', _b))
}, labelText), autosuggest && ( /*#__PURE__*/React.createElement(VisuallyHidden, {
id: suggestionHintId
}, suggestionHintText)), /*#__PURE__*/React.createElement(Debounce, {
waitFor: debounce
}, function (debouncer, cancelDebounce) {
return /*#__PURE__*/React.createElement(HtmlDivWithRef, {
className: searchInputClassNames.functionalityContainer,
forwardedRef: _this.functionalityWrapperRef,
"data-floating-ui-placement": _this.state.popoverPlacement
}, /*#__PURE__*/React.createElement(HtmlDiv, __assign({
className: searchInputClassNames.inputElementContainer
}, getInputContainerProps(), {
"aria-haspopup": autosuggest ? 'listbox' : undefined
}), /*#__PURE__*/React.createElement(HtmlInput, __assign({}, passProps, getConditionalAriaProp('aria-describedby', [!!statusText ? statusTextId : undefined, !!autosuggest ? suggestionHintId : undefined, ariaDescribedBy]), {
forwardedRef: _this.inputRef,
"aria-invalid": status === 'error',
id: id,
onBlur: handleOnBlur,
className: searchInputClassNames.inputElement,
type: "search",
"aria-activedescendant": _this.state.focusedDescendantId,
"aria-controls": _this.state.showPopover ? "".concat(id, "-itemlist") : undefined,
autoComplete: "off",
value: _this.state.displayValue,
placeholder: visualPlaceholder,
onChange: function onChange(event) {
var eventValue = event.currentTarget.value;
_this.conditionalSetState(eventValue);
if (propOnChange) {
debouncer(propOnChange, eventValue);
}
},
onKeyDown: onKeyDown
})), /*#__PURE__*/React.createElement(InputClearButton, __assign({}, clearButtonProps, {
label: clearButtonLabel,
onClick: function onClick() {
onClear();
cancelDebounce();
}
})), !_this.props.onSearch && showSearchIcon && ( /*#__PURE__*/React.createElement(IconSearch, {
className: searchInputClassNames.searchIcon
}))), _this.props.onSearch && ( /*#__PURE__*/React.createElement(Button, __assign({}, __assign(__assign({}, searchButtonDerivedProps), {
forwardedRef: undefined
}), {
"aria-disabled": !!searchButtonDerivedProps.disabled,
icon: /*#__PURE__*/React.createElement(IconSearch, null)
}), showSearchButtonLabel && searchButtonLabel)));
}), /*#__PURE__*/React.createElement(StatusText, {
id: statusTextId,
className: classnames((_c = {}, _c[searchInputClassNames.statusTextHasContent] = !!statusText, _c)),
status: status,
ariaLiveMode: statusTextAriaLiveMode
}, statusText), autosuggest && ( /*#__PURE__*/React.createElement(VisuallyHidden, {
"aria-live": "polite",
"aria-atomic": "true"
}, this.state.showPopover ? ariaOptionsAvailableText : ''))), this.state.showPopover && ( /*#__PURE__*/React.createElement(Popover, {
sourceRef: this.functionalityWrapperRef,
matchWidth: true,
onKeyDown: onKeyDown,
onClickOutside: function onClickOutside() {
_this.setState({
showPopover: false,
focusedDescendantId: null
});
},
className: popoverClassName
}, /*#__PURE__*/React.createElement(PopoverConsumer, null, function (consumer) {
_this.updatePopoverPlacement(consumer === null || consumer === void 0 ? void 0 : consumer.popoverPlacement);
if (suggestions && suggestions.length > 0) {
return /*#__PURE__*/React.createElement(SuggestionList, {
className: searchInputClassNames.suggestions,
focusedDescendantId: _this.state.focusedDescendantId || '',
id: "".concat(id, "-itemlist"),
ref: _this.popoverListRef,
popoverPlacement: consumer === null || consumer === void 0 ? void 0 : consumer.popoverPlacement
}, suggestions.map(function (item) {
return /*#__PURE__*/React.createElement(SelectItem, {
key: item.uniqueId,
id: item.uniqueId,
hasKeyboardFocus: _this.state.focusedDescendantId === item.uniqueId,
hightlightQuery: !!_this.state.value ? String(_this.state.value) : undefined,
checked: false,
onClick: function onClick() {
_this.setState({
showPopover: false,
value: item.label,
displayValue: item.label
});
if (onSuggestionSelected) {
onSuggestionSelected(item.uniqueId);
}
}
}, item.label);
}));
}
return null;
}))));
};
return BaseSearchInput;
}(Component);
var StyledSearchInput = styled(function (_a) {
_a.globalMargins;
var passProps = __rest(_a, ["globalMargins"]);
return /*#__PURE__*/React.createElement(BaseSearchInput, __assign({}, passProps));
}).withConfig({
componentId: "sc-15b5myw-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.searchInput, marginProps);
return baseStyles(theme, cleanedGlobalMargins, marginProps);
});
var SearchInput = /*#__PURE__*/forwardRef(function (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(StyledSearchInput, __assign({
theme: suomifiTheme,
id: id,
globalMargins: margins,
forwardedRef: ref
}, passProps));
});
});
});
});
SearchInput.displayName = 'SearchInput';
var templateObject_1;
export { SearchInput };
//# sourceMappingURL=SearchInput.js.map