baseui
Version:
A React Component library implementing the Base design language
993 lines (980 loc) • 37.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
exports.isInteractive = isInteractive;
var React = _interopRequireWildcard(require("react"));
var _overrides = require("../helpers/overrides");
var _deleteAlt = _interopRequireDefault(require("../icon/delete-alt"));
var _triangleDown = _interopRequireDefault(require("../icon/triangle-down"));
var _search = _interopRequireDefault(require("../icon/search"));
var _locale = require("../locale");
var _popover = require("../popover");
var _reactUid = require("react-uid");
var _autosizeInput = _interopRequireDefault(require("./autosize-input"));
var _constants = require("./constants");
var _defaultProps = _interopRequireDefault(require("./default-props"));
var _dropdown = _interopRequireDefault(require("./dropdown"));
var _styledComponents = require("./styled-components");
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
Copyright (c) Uber Technologies, Inc.
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
// @ts-ignore
function Noop() {
return null;
}
// @ts-ignore
const isClick = event => event.type === 'click';
// @ts-ignore
const isLeftClick = event => event.button !== null && event.button !== undefined && event.button === 0;
// @ts-ignore
const containsNode = (parent, child) => {
if (typeof document !== 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return child && parent && parent.contains(child);
}
};
function isInteractive(rootTarget, rootElement) {
if (rootTarget instanceof Element) {
let target = rootTarget;
while (target && target !== rootElement) {
const role = target.getAttribute('role');
if (role === 'button' || role === 'link') {
return true;
}
if (target.tagName) target = target.parentElement;
}
}
return false;
}
class Select extends React.Component {
constructor(props) {
super(props);
// anchor is a ref that refers to the outermost element rendered when the dropdown menu is not
// open. This is required so that we can check if clicks are on/off the anchor element.
_defineProperty(this, "anchor", /*#__PURE__*/React.createRef());
// dropdown is a ref that refers to the popover element. This is required so that we can check if
// clicks are on/off the dropdown element.
_defineProperty(this, "dropdown", /*#__PURE__*/React.createRef());
_defineProperty(this, "input", void 0);
// dragging is a flag to track whether a mobile device is currently scrolling versus clicking.
// @ts-ignore
_defineProperty(this, "dragging", void 0);
// focusAfterClear is a flag to indicate that the dropdowm menu should open after a selected
// option has been cleared.
// @ts-ignore
_defineProperty(this, "focusAfterClear", void 0);
// openAfterFocus is a flag to indicate that the dropdown menu should open when the component is
// focused. Developers have the option to disable initial clicks opening the dropdown menu. If not
// disabled, clicks will set this flag to true. Upon focusing, look to this to see if the menu should
// be opened, or only focus.
// @ts-ignore
_defineProperty(this, "openAfterFocus", void 0);
// When an item is selected, it also triggers handleClickOutside and since the selected item is
// already out of the menu (DOM), it will not recognize it as a subnode and triggers handleBlur
// that sets isOpen to false. That's a faulty logic causing visible problems when
// closeOnSelect is false. This flag helps to detect that selection was just made.
// @ts-ignore
_defineProperty(this, "justSelected", void 0);
// the select components can accept an array of options or an object where properties are optgroups
// and values are arrays of options. this class property is constructed and updated in a normalized
// shape where optgroup titles are stored on the option in the __optgroup field.
_defineProperty(this, "options", []);
// @ts-ignore
_defineProperty(this, "state", {
// @ts-ignore
activeDescendant: null,
inputValue: '',
isFocused: false,
isOpen: this.props.startOpen,
isPseudoFocused: false
});
_defineProperty(this, "isItMounted", false);
// Handle touch outside on mobile to dismiss menu, ensures that the
// touch target is not within the anchor DOM node.
_defineProperty(this, "handleTouchOutside", event => {
if (containsNode(this.dropdown.current, event.target)) return;
if (!containsNode(this.anchor.current, event.target)) {
this.closeMenu();
}
});
// Track dragging state to filter false-positive actions where a user
// intends to drag/scroll the page.
_defineProperty(this, "handleTouchMove", () => this.dragging = true);
_defineProperty(this, "handleTouchStart", () => this.dragging = false);
_defineProperty(this, "handleTouchEnd", event => {
if (this.dragging) return;
this.handleClick(event);
});
_defineProperty(this, "handleClick", event => {
if (this.props.disabled || !isClick(event) && !isLeftClick(event)) {
return;
}
// Case comes up when text has been typed into the input field. If no text provided,
// the 'input' element will have essentially 0 width therefore will not be clickable.
// When click outside does not reset input, text provided will stay rendered after clicks away
// from the select component. Upon subsequent clicks on the provided text, open the dropdown
// menu, in addition to text edit operations.
if (event.target === this.input) {
// CHASE: not sure why this condition is here. I cannot replicate a situation where clicks
// on provided text break into here.
if (!this.state.isFocused) {
// @ts-ignore
this.openAfterFocus = this.props.openOnClick;
this.focus();
}
if (!this.state.isOpen) {
this.setState({
isOpen: true,
isFocused: true,
isPseudoFocused: false
});
}
return;
}
// Ensures that interactive elements within the Select component do not trigger the outer click
// handler. For example, after an option is selected clicks on the 'clear' icon call here. We
// should ignore those events. This comes after case where click is on input element, so that
// those are handled on their own.
// @ts-ignore
if (this.input && isInteractive(event.target, this.input)) {
return;
}
// For the simple case where clicking on the Select does not allow for providing
// text input to filter the dropdown options.
if (!this.props.searchable) {
this.focus();
if (this.state.isOpen) {
this.setState({
isOpen: false,
isFocused: false
});
} else {
this.setState({
isOpen: true,
isFocused: true
});
}
return;
}
// Cases below only apply to searchable Select component.
if (this.state.isFocused) {
// iOS ignores programmatic calls to input.focus() that were not triggered by a click event.
// This component can get into a state where isFocused is true, but the DOM node is not
// focused. Call focus here again to ensure.
this.focus();
// Case comes up when click outside does not reset input - once text has been provided to
// the input, and the user closes the dropdown menu the provided text is maintained. After
// this, if the user focuses back into the select component then clicks on the component,
// the provided text highlights rather than position's the cursor at the end of the input.
if (this.input) this.input.value = '';
this.setState(prev => ({
isOpen: !this.focusAfterClear && !prev.isOpen,
isPseudoFocused: false
}));
this.focusAfterClear = false;
} else {
// When clear button is clicked, need to click twice to open control container - https://github.com/uber/baseweb/issues/4285
// Setting focusAfterClear to false, resolves the issue
this.focusAfterClear = false;
// @ts-ignore
this.openAfterFocus = this.props.openOnClick;
this.focus();
}
});
_defineProperty(this, "handleInputFocus", event => {
if (this.props.disabled) return;
if (this.props.onFocus) this.props.onFocus(event);
let toOpen = this.state.isOpen || this.openAfterFocus;
// if focus happens after clear values, don't open dropdown yet.
toOpen = !this.focusAfterClear && toOpen;
this.setState({
isFocused: true,
isOpen: !!toOpen
});
this.focusAfterClear = false;
this.openAfterFocus = false;
});
_defineProperty(this, "handleBlur", event => {
if (event.relatedTarget) {
if (containsNode(this.anchor.current, event.relatedTarget) || containsNode(this.dropdown.current, event.relatedTarget)) {
return;
}
} else if (containsNode(this.anchor.current, event.target)) {
return;
}
if (this.props.onBlur) {
this.props.onBlur(event);
}
if (this.isItMounted) {
this.setState({
isFocused: false,
isOpen: false,
isPseudoFocused: false,
inputValue: this.props.onBlurResetsInput ? '' : this.state.inputValue
});
}
});
_defineProperty(this, "handleClickOutside", event => {
if (this.justSelected) {
this.justSelected = false;
return;
}
if (containsNode(this.dropdown.current, event.target)) return;
const isFocused = this.state.isFocused || this.state.isPseudoFocused;
if (isFocused && !containsNode(this.anchor.current, event.target)) {
this.handleBlur(event);
}
});
_defineProperty(this, "handleInputChange", event => {
let newInputValue = event.target.value;
this.setState({
inputValue: newInputValue,
isOpen: true,
isPseudoFocused: false
});
if (this.props.onInputChange) {
this.props.onInputChange(event);
}
});
_defineProperty(this, "handleKeyDown", event => {
if (this.props.disabled) return;
switch (event.keyCode) {
case 8:
// backspace
if (!this.state.inputValue && this.props.clearable && this.props.backspaceRemoves) {
event.preventDefault();
this.backspaceValue();
}
break;
case 9:
// tab
this.setState(prevState => ({
isPseudoFocused: false,
isFocused: false,
isOpen: false,
inputValue: !this.props.onCloseResetsInput || !this.props.onBlurResetsInput ? prevState.inputValue : ''
}));
break;
case 27:
// escape
if (!this.state.isOpen && this.props.clearable && this.props.escapeClearsValue) {
this.clearValue(event);
this.setState({
isFocused: false,
isPseudoFocused: false
});
}
break;
case 32:
// space
if (this.props.searchable) {
break;
}
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 38:
// up
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 40:
// down
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 33:
// page up
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 34:
// page down
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 35:
// end key
if (event.shiftKey) {
break;
}
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 36:
// home key
if (event.shiftKey) {
break;
}
event.preventDefault();
if (!this.state.isOpen) {
this.setState({
isOpen: true
});
}
break;
case 46:
// delete
if (!this.state.inputValue && this.props.deleteRemoves) {
event.preventDefault();
this.popValue();
}
break;
}
});
_defineProperty(this, "getOptionLabel", (locale, {
option
}) => option.isCreatable ?
// @ts-ignore
`${locale.select.create} “${option[this.props.labelKey]}”` :
// @ts-ignore
option[this.props.labelKey]);
_defineProperty(this, "getValueLabel", ({
option
}) => {
// @ts-ignore
return option[this.props.labelKey];
});
_defineProperty(this, "handleActiveDescendantChange", id => {
if (id) {
this.setState({
activeDescendant: id
});
} else {
this.setState({
activeDescendant: null
});
}
});
_defineProperty(this, "handleInputRef", input => {
this.input = input;
if (typeof this.props.inputRef === 'function') {
this.props.inputRef(input);
} else if (this.props.inputRef) {
// @ts-expect-error todo(flow->ts) MutableRefObject
this.props.inputRef.current = input;
}
if (this.props.controlRef && typeof this.props.controlRef === 'function') {
// @ts-expect-error todo(flow->ts) according to types this code is not reachable
this.props.controlRef(input);
}
});
_defineProperty(this, "selectValue", ({
item
}) => {
if (item.disabled) {
return;
}
this.justSelected = true;
// NOTE: we add/set the value in a callback to make sure the
// input value is empty to avoid styling issues in Chrome
const updatedValue = this.props.onSelectResetsInput ? '' : this.state.inputValue;
if (this.props.multi) {
this.setState({
inputValue: updatedValue,
isOpen: !this.props.closeOnSelect
}, () => {
const valueArray = this.props.value;
// @ts-ignore
if (valueArray.some(i => i[this.props.valueKey] === item[this.props.valueKey])) {
this.removeValue(item);
} else {
this.addValue(item);
}
});
} else {
this.focus();
this.setState({
inputValue: updatedValue,
isOpen: !this.props.closeOnSelect,
isFocused: true,
isPseudoFocused: false
}, () => {
this.setValue([item], item, _constants.STATE_CHANGE_TYPE.select);
});
}
});
_defineProperty(this, "addValue", item => {
const valueArray = [...this.props.value];
this.setValue(valueArray.concat(item), item, _constants.STATE_CHANGE_TYPE.select);
});
_defineProperty(this, "backspaceValue", () => {
const item = this.popValue();
if (!item) {
return;
}
// @ts-ignore
const valueLength = this.props.value.length;
const renderLabel = this.props.getValueLabel || this.getValueLabel;
const labelForInput = renderLabel({
option: item,
index: valueLength - 1
});
// label might not be a string, it might be a Node of another kind.
if (!this.props.backspaceClearsInputValue && typeof labelForInput === 'string') {
const remainingInput = labelForInput.slice(0, -1);
this.setState({
inputValue: remainingInput,
isOpen: true
});
}
});
_defineProperty(this, "popValue", () => {
// @ts-ignore
const valueArray = [...this.props.value];
const valueLength = valueArray.length;
if (!valueLength) return;
if (valueArray[valueLength - 1].clearableValue === false) return;
const item = valueArray.pop();
this.setValue(valueArray, item, _constants.STATE_CHANGE_TYPE.remove);
return item;
});
_defineProperty(this, "removeValue", item => {
const valueArray = [...this.props.value];
this.setValue(
// @ts-ignore
valueArray.filter(i => i[this.props.valueKey] !== item[this.props.valueKey]), item, _constants.STATE_CHANGE_TYPE.remove);
this.focus();
});
_defineProperty(this, "clearValue", event => {
if (isClick(event) && !isLeftClick(event)) return;
if (this.props.value) {
const resetValue = this.props.value.filter(item => item.clearableValue === false);
this.setValue(resetValue, null, _constants.STATE_CHANGE_TYPE.clear);
}
this.setState({
inputValue: '',
isOpen: false
});
this.focus();
this.focusAfterClear = true;
});
_defineProperty(this, "shouldShowPlaceholder", () => {
return !(this.state.inputValue || this.props.value && this.props.value.length);
});
_defineProperty(this, "shouldShowValue", () => {
return !this.state.inputValue;
});
this.options = (0, _utils.normalizeOptions)(props.options);
}
componentDidMount() {
if (this.props.autoFocus) {
this.focus();
}
this.isItMounted = true;
const {
controlRef
} = this.props;
if (controlRef && typeof controlRef !== 'function') {
controlRef.current = {
setDropdownOpen: this.handleDropdownOpen.bind(this),
setInputValue: this.handleSetInputValue.bind(this),
setInputFocus: this.handleSetInputFocus.bind(this),
setInputBlur: this.handleSetInputBlur.bind(this),
// `focus` & `blur` below are for backwards compatibility and may be removed. Use setInputFocus and setInputBlur instead.
focus: this.handleSetInputFocus.bind(this),
blur: this.handleSetInputBlur.bind(this)
};
}
}
componentDidUpdate(prevProps, prevState) {
if (typeof document !== 'undefined') {
if (prevState.isOpen !== this.state.isOpen) {
if (this.state.isOpen) {
this.props.onOpen && this.props.onOpen();
document.addEventListener('touchstart', this.handleTouchOutside);
} else {
this.props.onClose && this.props.onClose();
document.removeEventListener('touchstart', this.handleTouchOutside);
}
}
if (!prevState.isFocused && this.state.isFocused) {
setTimeout(() => document.addEventListener('click', this.handleClickOutside), 0);
}
}
}
componentWillUnmount() {
if (typeof document !== 'undefined') {
document.removeEventListener('touchstart', this.handleTouchOutside);
document.removeEventListener('click', this.handleClickOutside);
}
this.isItMounted = false;
}
focus() {
if (!this.input) return;
this.input.focus();
}
handleDropdownOpen(nextOpenState) {
this.setState({
isOpen: nextOpenState
});
}
handleSetInputValue(newInputValue) {
this.setState({
inputValue: newInputValue
});
}
handleSetInputFocus() {
// @ts-ignore
this.input.focus();
}
handleSetInputBlur() {
// @ts-ignore
this.input.blur();
}
closeMenu() {
if (this.props.onCloseResetsInput) {
this.setState({
inputValue: '',
isOpen: false,
isPseudoFocused: this.state.isFocused && !this.props.multi
});
} else {
this.setState({
isOpen: false,
isPseudoFocused: this.state.isFocused && !this.props.multi
});
}
}
/**
* Extends the value into an array from the given options
*/
getValueArray(value) {
if (!Array.isArray(value)) {
if (value === null || value === undefined) return [];
value = [value];
}
return value.map(value => (0, _utils.expandValue)(value, this.props));
}
setValue(value, option, type) {
if (this.props.onChange) {
this.props.onChange({
value,
option,
type
});
}
}
renderLoading() {
if (!this.props.isLoading) return;
const {
overrides = {}
} = this.props;
const [LoadingIndicator, loadingIndicatorProps] = (0, _overrides.getOverrides)(overrides.LoadingIndicator, _styledComponents.StyledLoadingIndicator);
return /*#__PURE__*/React.createElement(LoadingIndicator, _extends({
role: "status"
}, loadingIndicatorProps), /*#__PURE__*/React.createElement("span", {
style: {
position: 'absolute',
width: '1px',
height: '1px',
padding: 0,
margin: '-1px',
overflow: 'hidden',
clip: 'rect(0,0,0,0)',
whiteSpace: 'nowrap',
border: 0
}
}, "Loading"));
}
renderValue(valueArray) {
const {
overrides = {}
} = this.props;
const sharedProps = this.getSharedProps();
const renderLabel = this.props.getValueLabel || this.getValueLabel;
const Value = this.props.valueComponent || Noop;
if (!valueArray.length) {
return null;
}
if (this.props.multi) {
return valueArray.map((value, i) => {
const disabled = sharedProps.$disabled || value.clearableValue === false;
return /*#__PURE__*/React.createElement(Value, _extends({
value: value
// @ts-ignore
,
key: `value-${i}-${value[this.props.valueKey]}`,
removeValue: () => this.removeValue(value),
disabled: disabled,
overrides: {
Tag: overrides.Tag,
MultiValue: overrides.MultiValue
}
}, sharedProps, {
$disabled: disabled
}), renderLabel({
option: value,
index: i
}));
});
} else if (this.shouldShowValue()) {
return /*#__PURE__*/React.createElement(Value
// @ts-ignore
, _extends({
value: valueArray[0][this.props.valueKey],
disabled: this.props.disabled,
overrides: {
SingleValue: overrides.SingleValue
}
}, sharedProps), renderLabel({
option: valueArray[0]
}));
}
}
renderInput(listboxId) {
const {
overrides = {}
} = this.props;
const [InputContainer, inputContainerProps] = (0, _overrides.getOverrides)(overrides.InputContainer, _styledComponents.StyledInputContainer);
const sharedProps = this.getSharedProps();
const isOpen = this.state.isOpen;
// @ts-ignore
const selected = this.getValueArray(this.props.value)
// @ts-ignore
.map(v => v[this.props.labelKey]).join(', ');
const selectedLabel = selected.length ? `Selected ${selected}. ` : '';
const label = `${selectedLabel}${this.props['aria-label'] || ''}`;
if (!this.props.searchable) {
return /*#__PURE__*/React.createElement(InputContainer, _extends({
"aria-activedescendant": this.state.activeDescendant,
"aria-describedby": this.props['aria-describedby'],
"aria-errormessage": this.props['aria-errormessage'],
"aria-disabled": this.props.disabled,
"aria-labelledby": this.props['aria-labelledby'],
"aria-label": label,
"aria-owns": this.state.isOpen ? listboxId : null,
"aria-required": this.props.required || null,
onFocus: this.handleInputFocus,
tabIndex: 0
}, sharedProps, inputContainerProps), /*#__PURE__*/React.createElement("input", _extends({
"aria-hidden": true
// @ts-ignore
,
id: this.props.id || null,
ref: this.handleInputRef,
style: {
opacity: 0,
width: 0,
overflow: 'hidden',
border: 'none',
padding: 0
},
tabIndex: -1
}, overrides.Input ? overrides.Input.props ?
// $FlowExpectedError[not-an-object]
overrides.Input.props : {} : {})));
}
return /*#__PURE__*/React.createElement(InputContainer, _extends({}, sharedProps, inputContainerProps), /*#__PURE__*/React.createElement(_autosizeInput.default, _extends({
"aria-activedescendant": this.state.activeDescendant,
"aria-autocomplete": "list",
"aria-controls": this.state.isOpen ? listboxId : null,
"aria-describedby": this.props['aria-describedby'],
"aria-errormessage": this.props['aria-errormessage'],
"aria-expanded": isOpen,
"aria-haspopup": "listbox",
"aria-label": label,
"aria-labelledby": this.props['aria-labelledby'],
"aria-required": this.props.required || null,
disabled: this.props.disabled || null,
id: this.props.id || null,
inputRef: this.handleInputRef,
onChange: this.handleInputChange,
onFocus: this.handleInputFocus,
overrides: {
Input: overrides.Input
}
// @ts-ignore
,
required: this.props.required && !this.props.value.length || null,
role: "combobox",
value: this.state.inputValue,
tabIndex: 0
}, sharedProps)));
}
renderClear() {
const isValueEntered = Boolean(this.props.value && this.props.value.length || this.state.inputValue);
if (!this.props.clearable || this.props.disabled || this.props.isLoading || !isValueEntered) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
$size,
...sharedProps
} = this.getSharedProps();
const {
overrides = {}
} = this.props;
const [ClearIcon, clearIconProps] = (0, _overrides.getOverrides)(overrides.ClearIcon, _deleteAlt.default);
const ariaLabel = this.props.multi ? 'Clear all' : 'Clear value';
const sizes = {
[_constants.SIZE.mini]: 15,
[_constants.SIZE.compact]: 15,
[_constants.SIZE.default]: 18,
[_constants.SIZE.large]: 22
};
return /*#__PURE__*/React.createElement(ClearIcon, _extends({
title: ariaLabel,
"aria-label": ariaLabel,
onClick: this.clearValue,
role: "button"
// @ts-ignore
,
size: sizes[this.props.size] || sizes[_constants.SIZE.default]
}, sharedProps, clearIconProps));
}
renderArrow() {
if (this.props.type !== _constants.TYPE.select) {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
$size,
...sharedProps
} = this.getSharedProps();
const {
overrides = {}
} = this.props;
const [SelectArrow, selectArrowProps] = (0, _overrides.getOverrides)(overrides.SelectArrow, _triangleDown.default);
selectArrowProps.overrides = (0, _overrides.mergeOverrides)({
Svg: {
style: ({
$theme,
$disabled
}) => {
return {
color: $disabled ? $theme.colors.inputTextDisabled : $theme.colors.contentPrimary
};
}
}
}, selectArrowProps.overrides);
const sizes = {
[_constants.SIZE.mini]: 16,
[_constants.SIZE.compact]: 16,
[_constants.SIZE.default]: 20,
[_constants.SIZE.large]: 24
};
return /*#__PURE__*/React.createElement(SelectArrow
// @ts-ignore
, _extends({
size: sizes[this.props.size] || sizes[_constants.SIZE.default],
title: 'open'
}, sharedProps, selectArrowProps));
}
renderSearch() {
if (this.props.type !== _constants.TYPE.search) {
return null;
}
const {
overrides = {}
} = this.props;
const [SearchIconContainer, searchIconContainerProps] = (0, _overrides.getOverrides)(overrides.SearchIconContainer, _styledComponents.StyledSearchIconContainer);
const [SearchIcon, searchIconProps] = (0, _overrides.getOverrides)(overrides.SearchIcon, _search.default);
const sharedProps = this.getSharedProps();
return /*#__PURE__*/React.createElement(SearchIconContainer, _extends({}, sharedProps, searchIconContainerProps), /*#__PURE__*/React.createElement(SearchIcon, _extends({
size: 16,
title: 'search'
}, sharedProps, searchIconProps)));
}
filterOptions(excludeOptions) {
const filterValue = this.state.inputValue.trim();
// apply filter function
if (this.props.filterOptions) {
this.options = this.props.filterOptions(this.options, filterValue, excludeOptions, {
// @ts-ignore
valueKey: this.props.valueKey,
// @ts-ignore
labelKey: this.props.labelKey
});
}
// can user create a new option + there's no exact match already
const filterDoesNotMatchOption = this.props.ignoreCase ?
// @ts-ignore
opt => opt[this.props.labelKey].toLowerCase() !== filterValue.toLowerCase().trim() :
// @ts-ignore
opt => opt[this.props.labelKey] !== filterValue.trim();
if (filterValue && this.props.creatable &&
// @ts-ignore
this.options.concat(this.props.value).every(filterDoesNotMatchOption)) {
// @ts-expect-error todo(flow->ts) this.options is typed as a read-only array
this.options.push({
id: filterValue,
// @ts-ignore
[this.props.labelKey]: filterValue,
// @ts-ignore
[this.props.valueKey]: filterValue,
isCreatable: true
});
}
return this.options;
}
getSharedProps() {
const {
clearable,
creatable,
disabled,
error,
positive,
isLoading,
multi,
required,
size,
searchable,
type,
value
} = this.props;
const {
isOpen,
isFocused,
isPseudoFocused
} = this.state;
return {
$clearable: clearable,
$creatable: creatable,
$disabled: disabled,
$error: error,
$positive: positive,
$isFocused: isFocused,
$isLoading: isLoading,
$isOpen: isOpen,
$isPseudoFocused: isPseudoFocused,
$multi: multi,
$required: required,
$searchable: searchable,
$size: size,
$type: type,
// @ts-ignore
$isEmpty: !this.getValueArray(value).length
};
}
render() {
this.options = (0, _utils.normalizeOptions)(this.props.options);
const {
overrides = {},
type,
multi,
noResultsMsg,
value,
filterOutSelected
} = this.props;
if (process.env.NODE_ENV !== "production") {
// value may be nullish, only warn if value is defined
if (value && !Array.isArray(value)) {
console.warn('The Select component expects an array as the value prop. For more information, please visit the docs at https://baseweb.design/components/select/');
}
}
const [Root, rootProps] = (0, _overrides.getOverrides)(overrides.Root, _styledComponents.StyledRoot);
const [ControlContainer, controlContainerProps] = (0, _overrides.getOverrides)(overrides.ControlContainer, _styledComponents.StyledControlContainer);
const [ValueContainer, valueContainerProps] = (0, _overrides.getOverrides)(overrides.ValueContainer, _styledComponents.StyledValueContainer);
const [IconsContainer, iconsContainerProps] = (0, _overrides.getOverrides)(overrides.IconsContainer, _styledComponents.StyledIconsContainer);
const [PopoverOverride, popoverProps] = (0, _overrides.getOverrides)(overrides.Popover, _popover.Popover);
const [Placeholder, placeholderProps] = (0, _overrides.getOverrides)(overrides.Placeholder, _styledComponents.StyledPlaceholder);
const sharedProps = this.getSharedProps();
// @ts-ignore
const valueArray = this.getValueArray(value);
const options = this.filterOptions(multi && filterOutSelected ? valueArray : null);
const isOpen = this.state.isOpen;
sharedProps.$isOpen = isOpen;
if (process.env.NODE_ENV !== "production") {
if (this.props.error && this.props.positive) {
// eslint-disable-next-line no-console
console.warn(`[Select] \`error\` and \`positive\` are both set to \`true\`. \`error\` will take precedence but this may not be what you want.`);
}
}
return /*#__PURE__*/React.createElement(_reactUid.UIDConsumer, null, listboxId => /*#__PURE__*/React.createElement(_locale.LocaleContext.Consumer, null, locale => /*#__PURE__*/React.createElement(PopoverOverride
// Popover does not provide ability to forward refs through, and if we were to simply
// apply the ref to the Root component below it would be overwritten before the popover
// renders it. Using this strategy, we will get a ref to the popover, then reuse its
// anchorRef so we can check if clicks are on the select component or not.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
, _extends({
innerRef: ref => {
if (!ref) return;
this.anchor = ref.anchorRef;
},
accessibilityType: _popover.ACCESSIBILITY_TYPE.none,
autoFocus: false,
focusLock: false,
mountNode: this.props.mountNode,
onEsc: () => this.closeMenu(),
isOpen: isOpen,
popoverMargin: 0,
content: () => {
const dropdownProps = {
error: this.props.error,
positive: this.props.positive,
getOptionLabel: this.props.getOptionLabel || this.getOptionLabel.bind(this, locale),
id: listboxId,
isLoading: this.props.isLoading,
labelKey: this.props.labelKey,
maxDropdownHeight: this.props.maxDropdownHeight,
multi,
noResultsMsg,
onActiveDescendantChange: this.handleActiveDescendantChange,
onItemSelect: this.selectValue,
options,
overrides,
required: this.props.required,
searchable: this.props.searchable,
size: this.props.size,
type,
value: valueArray,
valueKey: this.props.valueKey,
width: this.anchor.current ? this.anchor.current.clientWidth : null,
keyboardControlNode: this.anchor
};
// @ts-ignore
return /*#__PURE__*/React.createElement(_dropdown.default, _extends({
innerRef: this.dropdown
}, dropdownProps));
},
placement: _popover.PLACEMENT.bottom
}, popoverProps), /*#__PURE__*/React.createElement(Root, _extends({
onBlur: this.handleBlur,
"data-baseweb": "select"
}, sharedProps, rootProps), /*#__PURE__*/React.createElement(ControlContainer, _extends({
onKeyDown: this.handleKeyDown,
onClick: this.handleClick,
onTouchEnd: this.handleTouchEnd,
onTouchMove: this.handleTouchMove,
onTouchStart: this.handleTouchStart
}, sharedProps, controlContainerProps), type === _constants.TYPE.search ? this.renderSearch() : null, /*#__PURE__*/React.createElement(ValueContainer, _extends({}, sharedProps, valueContainerProps), this.renderValue(valueArray), this.renderInput(listboxId), this.shouldShowPlaceholder() ? /*#__PURE__*/React.createElement(Placeholder, _extends({}, sharedProps, placeholderProps), typeof this.props.placeholder !== 'undefined' ? this.props.placeholder : locale.select.placeholder) : null), /*#__PURE__*/React.createElement(IconsContainer, _extends({}, sharedProps, iconsContainerProps), this.renderLoading(), this.renderClear(), type === _constants.TYPE.select ? this.renderArrow() : null))))));
}
}
_defineProperty(Select, "defaultProps", _defaultProps.default);
var _default = exports.default = Select;