UNPKG

@wix/design-system

Version:

@wix/design-system

601 lines (584 loc) 22.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.DEFAULT_VALUE_PARSER = exports.DEFAULT_POPOVER_PROPS = void 0; var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _Input = _interopRequireDefault(require("../Input")); var _omit = _interopRequireDefault(require("omit")); var _DropdownLayout = _interopRequireDefault(require("../DropdownLayout/DropdownLayout")); var _InputWithOptionsSt = require("./InputWithOptions.st.css.js"); var _uniqueId = _interopRequireDefault(require("lodash/uniqueId")); var _HighlightContext = _interopRequireDefault(require("./HighlightContext")); var _PopoverNext = _interopRequireDefault(require("../PopoverNext/PopoverNext")); var _excluded = ["appendTo", "fixed", "flip", "placement"]; var _jsxFileName = "/home/builduser/work/57e038ea7326c1ec/packages/wix-design-system/dist/cjs/InputWithOptions/InputWithOptions.js"; 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 ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var DEFAULT_VALUE_PARSER = option => option.value; exports.DEFAULT_VALUE_PARSER = DEFAULT_VALUE_PARSER; var INPUT_WITH_OPTIONS_DOUBLE_CLICK_THRESHOLD = 2000; var DEFAULT_POPOVER_PROPS = exports.DEFAULT_POPOVER_PROPS = { appendTo: 'parent', flip: false, fixed: true, placement: 'bottom', width: '100%', minWidth: 192 }; class InputWithOptions extends _react.Component { // Abstraction inputClasses() {} dropdownClasses() {} dropdownAdditionalProps() {} inputAdditionalProps() {} rootAdditionalProps() { return {}; } /** * An array of key codes that act as manual submit. Will be used within * onKeyDown(event). * * @returns {KeyboardEvent.key[]} */ getManualSubmitKeys() { return ['Enter', 'Tab']; } constructor(props) { super(props); this.input = /*#__PURE__*/_react.default.createRef(); this._onOptionMarked = (option, optionElementId) => { var { onOptionMarked } = this.props; this.setState({ activeDescendentId: optionElementId }); if (onOptionMarked) { onOptionMarked(option, optionElementId); } }; /** Checks if focus event is related to selecting an option */ this._didSelectOption = event => { var focusedElement = event && event.relatedTarget; var dropdownContainer = this.dropdownLayout && this.dropdownLayout.containerRef.current; // Check if user has focused other input component var isInput = focusedElement instanceof HTMLInputElement; if (!focusedElement || !dropdownContainer || isInput) { return false; } var isInDropdown = dropdownContainer.contains(focusedElement); // Returns true if element is the dropdown container or is inside of it return isInDropdown; }; /** * Clears the input. * * @param event delegated to the onClear call */ this.clear = event => { this.input.current && this.input.current.clear(event); }; this.state = { inputValue: props.value || '', showOptions: false, lastOptionsShow: 0, isEditing: false }; this.uniqueId = (0, _uniqueId.default)('InputWithOptions'); this._onSelect = this._onSelect.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); this._onChange = this._onChange.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this.focus = this.focus.bind(this); this.blur = this.blur.bind(this); this.select = this.select.bind(this); this.hideOptions = this.hideOptions.bind(this); this.showOptions = this.showOptions.bind(this); this._onManuallyInput = this._onManuallyInput.bind(this); this._renderDropdownLayout = this._renderDropdownLayout.bind(this); this.isDropdownLayoutVisible = this.isDropdownLayoutVisible.bind(this); this._onInputClicked = this._onInputClicked.bind(this); this._onOpenChange = this._onOpenChange.bind(this); this.closeOnSelect = this.closeOnSelect.bind(this); this.onCompositionChange = this.onCompositionChange.bind(this); } componentDidUpdate(prevProps, prevState) { if (!this.props.showOptionsIfEmptyInput && (!prevProps.value && this.props.value || !prevState.inputValue && this.state.inputValue)) { this.showOptions(); } // Clear value in controlled mode if (prevProps.value !== this.props.value && this.props.value === '') { this.setState({ inputValue: '' }); } } onCompositionChange(isComposing) { this.setState({ isComposing }); } renderInput() { var inputAdditionalProps = this.inputAdditionalProps(); var inputProps = Object.assign((0, _omit.default)(['onChange', 'dataHook', 'dropDirectionUp', 'focusOnSelectedOption', 'onClose', 'onSelect', 'onOptionMarked', 'overflow', 'visible', 'options', 'selectedId', 'tabIndex', 'fixedHeader', 'fixedFooter', 'maxHeightPixels', 'minWidthPixels', 'withArrow', 'closeOnSelect', 'onMouseEnter', 'onMouseLeave', 'itemHeight', 'selectedHighlight', 'inContainer', 'infiniteScroll', 'loadMore', 'hasMore', 'markedOption', 'className'], this.props), inputAdditionalProps); var { inputElement } = inputProps; return /*#__PURE__*/_react.default.cloneElement(inputElement, _objectSpread(_objectSpread({ menuArrow: true, ref: this.input, ariaExpanded: this.state.showOptions, ariaControls: "".concat(this.uniqueId, "-listbox"), ariaActivedescendant: this.state.activeDescendentId }, inputProps), {}, { onChange: this._onChange, onInputClicked: this._onInputClicked, onFocus: this._onFocus, onBlur: this._onBlur, onCompositionChange: this.onCompositionChange, width: inputElement.props.width, textOverflow: this.props.textOverflow || inputElement.props.textOverflow, tabIndex: this.props.native ? -1 : 0 })); } isDropdownLayoutVisible() { return this.state.showOptions && (this.props.showOptionsIfEmptyInput || this.state.inputValue.length > 0); } _renderDropdownLayout() { var { highlight, value } = this.props; var inputOnlyProps = (0, _omit.default)(['tabIndex'], _Input.default.propTypes); var dropdownProps = Object.assign((0, _omit.default)(Object.keys(inputOnlyProps).concat(['dataHook', 'onClickOutside']), this.props), this.dropdownAdditionalProps()); var customStyle = { marginLeft: this.props.dropdownOffsetLeft, width: 'inherit' }; return /*#__PURE__*/_react.default.createElement("div", { className: "".concat(this.uniqueId, " ").concat(this.dropdownClasses()), style: customStyle, "data-hook": "dropdown-layout-wrapper", __self: this, __source: { fileName: _jsxFileName, lineNumber: 185, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement(_HighlightContext.default.Provider, { value: { highlight, match: value }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 190, columnNumber: 9 } }, /*#__PURE__*/_react.default.createElement(_DropdownLayout.default, (0, _extends2.default)({ ref: dropdownLayout => this.dropdownLayout = dropdownLayout }, dropdownProps, { dataHook: "inputwithoptions-dropdownlayout", visible: true, className: _InputWithOptionsSt.classes.dropdownLayout, onClose: this.hideOptions, onSelect: this._onSelect, onOptionMarked: this._onOptionMarked, isComposing: this.state.isComposing, listboxId: "".concat(this.uniqueId, "-listbox"), inContainer: true, tabIndex: -1, __self: this, __source: { fileName: _jsxFileName, lineNumber: 191, columnNumber: 11 } })))); } _renderNativeSelect() { var { options, onSelect, disabled } = this.props; return /*#__PURE__*/_react.default.createElement("div", { className: _InputWithOptionsSt.classes.nativeSelectWrapper, __self: this, __source: { fileName: _jsxFileName, lineNumber: 213, columnNumber: 7 } }, this.renderInput(), /*#__PURE__*/_react.default.createElement("select", { disabled: disabled, "data-hook": "native-select", className: _InputWithOptionsSt.classes.nativeSelect, onChange: event => { this._onChange(event); // In this case we don't use DropdownLayout so we need to invoke `onSelect` manually onSelect(options[event.target.selectedIndex]); }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 215, columnNumber: 9 } }, options.map((option, index) => /*#__PURE__*/_react.default.createElement("option", { "data-hook": "native-option-".concat(option.id), "data-index": index, key: option.id, value: option.value, __self: this, __source: { fileName: _jsxFileName, lineNumber: 227, columnNumber: 13 } }, option.value)))); } render() { var _rootProps$className; if (!!this.props.native) { return this._renderNativeSelect(); } var _this$props = this.props, { dataHook, popoverProps: { appendTo: popoverAppendTo, fixed: popoverFixed, flip: popoverFlip, placement: popoverPlacement }, dropDirectionUp, dropdownWidth, className } = _this$props, restPopoverFields = (0, _objectWithoutProperties2.default)(_this$props.popoverProps, _excluded); var placement = dropDirectionUp ? 'top' : popoverPlacement; var dynamicWidth = popoverAppendTo === 'window'; var rootProps = this.rootAdditionalProps(); return /*#__PURE__*/_react.default.createElement(_PopoverNext.default, (0, _extends2.default)({ className: (0, _InputWithOptionsSt.st)(_InputWithOptionsSt.classes.root, { size: 'medium' }, (_rootProps$className = rootProps.className) !== null && _rootProps$className !== void 0 ? _rootProps$className : className), open: this.isDropdownLayoutVisible(), onOpenChange: this._onOpenChange, appendTo: popoverAppendTo !== null && popoverAppendTo !== void 0 ? popoverAppendTo : DEFAULT_POPOVER_PROPS.appendTo, flip: popoverFlip !== null && popoverFlip !== void 0 ? popoverFlip : DEFAULT_POPOVER_PROPS.flip, fixed: popoverFixed !== null && popoverFixed !== void 0 ? popoverFixed : DEFAULT_POPOVER_PROPS.fixed, placement: placement !== null && placement !== void 0 ? placement : DEFAULT_POPOVER_PROPS.placement, dynamicWidth: dynamicWidth, excludeClass: this.uniqueId, focusManagerEnabled: false, onClickOutside: this.props.onClickOutside }, restPopoverFields, { minWidth: dynamicWidth ? 'fit-content' : DEFAULT_POPOVER_PROPS.minWidth, width: dropdownWidth !== null && dropdownWidth !== void 0 ? dropdownWidth : dynamicWidth ? null : DEFAULT_POPOVER_PROPS.width, dataHook: dataHook, onKeyDown: this._onKeyDown, __self: this, __source: { fileName: _jsxFileName, lineNumber: 264, columnNumber: 7 } }), /*#__PURE__*/_react.default.createElement(_PopoverNext.default.Trigger, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 288, columnNumber: 9 } }, /*#__PURE__*/_react.default.createElement("div", { "data-input-parent": true, className: this.inputClasses(), __self: this, __source: { fileName: _jsxFileName, lineNumber: 289, columnNumber: 11 } }, this.renderInput())), /*#__PURE__*/_react.default.createElement(_PopoverNext.default.Content, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 293, columnNumber: 9 } }, this.props.customDropdownContent || this._renderDropdownLayout())); } /** * Shows dropdown options */ showOptions() { if (!this.state.showOptions) { this.setState({ showOptions: true, lastOptionsShow: Date.now() }); this.props.onOptionsShow && this.props.onOptionsShow(); } } /** * Hides dropdown options */ hideOptions() { if (this.state.showOptions) { this.setState({ showOptions: false, activeDescendentId: undefined }); this.props.onOptionsHide && this.props.onOptionsHide(); this.props.onClose && this.props.onClose(); } } closeOnSelect() { return this.props.closeOnSelect; } get isReadOnly() { var { readOnly } = this.inputAdditionalProps() || { readOnly: this.props.readOnly }; return readOnly; } /** * Determine if the provided key should cause the dropdown to be opened. * * @param {KeyboardEvent.key} * @returns {boolean} */ shouldOpenDropdown(key) { var openKeys = this.isReadOnly ? ['Enter', 'Spacebar', ' ', 'ArrowDown'] : ['ArrowDown']; return openKeys.includes(key); } /** * Determine if the provided key should delegate the keydown event to the * DropdownLayout. * * @param {KeyboardEvent.key} * @returns {boolean} */ shouldDelegateKeyDown(key) { return this.isReadOnly || !['Spacebar', ' '].includes(key) || this.shouldPerformManualSubmit(' '); } /** * Determine if the provided key should cause manual submit. * * @param {KeyboardEvent.key} * @returns {boolean} */ shouldPerformManualSubmit(key) { return this.getManualSubmitKeys().includes(key); } _onManuallyInput() { var inputValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; if (this.state.isComposing) { return; } inputValue = inputValue.trim(); var suggestedOption = this.props.options.find(element => element.value === inputValue); if (this.props.onManuallyInput) { this.props.onManuallyInput(inputValue, suggestedOption); } } _onSelect(option, isSelectedOption) { var { onSelect } = this.props; if (this.closeOnSelect() || isSelectedOption) { this._onOpenChange(false, 'select-option'); } if (onSelect) { onSelect(this.props.highlight ? this.props.options.find(opt => opt.id === option.id) : option); } } _onChange(event) { this.setState({ inputValue: event.target.value }); if (this.props.onChange) { this.props.onChange(event); } // If the input value is not empty, should show the options if (event.target.value.trim() && !this.props.native) { this.showOptions(); } } _onInputClicked(event) { var _this$props$onInputCl, _this$props2; (_this$props$onInputCl = (_this$props2 = this.props).onInputClicked) == null || _this$props$onInputCl.call(_this$props2, event); } _onOpenChange(open, reason) { var doubleClickThreshold = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INPUT_WITH_OPTIONS_DOUBLE_CLICK_THRESHOLD; if (this.props.disabled || this.isReadOnly) { return; } if (open) { this.showOptions(); } else if (reason === 'outside-press' || reason === 'select-option') { this.hideOptions(); } else if (Date.now() - this.state.lastOptionsShow > doubleClickThreshold) { this.hideOptions(); } } _onFocus(e) { /** Don't call onFocus if input is already focused or is disabled * can occur when input is re-focused after selecting an option */ if (this._focused || this.props.disabled) { return; } this._focused = true; this.setState({ isEditing: false }); if (this.props.onFocus) { this.props.onFocus(e); } } _onBlur(event) { // Don't blur input if selected an option var stopBlur = this._didSelectOption(event); if (stopBlur) { // Restore focus to input element this.focus(); return; } this._focused = false; if (this.props.onBlur) { this.props.onBlur(event); } } _onKeyDown(event) { if (this.props.disabled || this.isReadOnly) { return; } var { key } = event; /* Enter - prevent a wrapping form from submitting when hitting Enter */ /* ArrowUp - prevent input's native behaviour from moving the text cursor to the beginning */ if (key === 'Enter' || key === 'ArrowUp') { event.preventDefault(); } if (key !== 'ArrowDown' && key !== 'ArrowUp') { this.setState({ isEditing: true }); } if (this.shouldOpenDropdown(key)) { this.showOptions(); event.preventDefault(); } if (this.shouldDelegateKeyDown(key)) { // Delegate event and get result if (this.dropdownLayout) { var eventWasHandled = this.dropdownLayout._onSelectListKeyDown(event); if (eventWasHandled || this.isReadOnly) { return; } } // For editing mode, we want to *submit* only for specific keys. if (this.shouldPerformManualSubmit(key)) { this._onManuallyInput(this.state.inputValue, event); var inputIsEmpty = !event.target.value; if (this.closeOnSelect() || key === 'Tab' && inputIsEmpty) { this.hideOptions(); } } } } /** * Sets focus on the input element * @param {FocusOptions} options */ focus() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; this.input.current && this.input.current.focus(options); } /** * Removes focus on the input element */ blur() { this.input.current && this.input.current.blur(); } /** * Selects all text in the input element */ select() { this.input.current && this.input.current.select(); } } InputWithOptions.defaultProps = _objectSpread(_objectSpread(_objectSpread({}, _Input.default.defaultProps), _DropdownLayout.default.defaultProps), {}, { onSelect: () => {}, options: [], closeOnSelect: true, inputElement: /*#__PURE__*/_react.default.createElement(_Input.default, { __self: void 0, __source: { fileName: _jsxFileName, lineNumber: 565, columnNumber: 17 } }), valueParser: DEFAULT_VALUE_PARSER, dropdownWidth: null, popoverProps: DEFAULT_POPOVER_PROPS, dropdownOffsetLeft: '0', showOptionsIfEmptyInput: true, autocomplete: 'off', native: false }); InputWithOptions.propTypes = _objectSpread(_objectSpread(_objectSpread({}, _Input.default.propTypes), _DropdownLayout.default.propTypes), {}, { /** Use a customized input component instead of the default @wix/design-system `<Input/>` component */ inputElement: _propTypes.default.element, /** Closes DropdownLayout on option selection */ closeOnSelect: _propTypes.default.bool, /** Node which is displayed in dropdown */ customDropdown: _propTypes.default.node, /** A callback which is called when the user performs a Submit-Action. * Submit-Action triggers are: "Enter", "Tab", [typing any defined delimiters], Paste action. * `onManuallyInput(values: Array<string>): void - The array of strings is the result of splitting the input value by the given delimiters */ onManuallyInput: _propTypes.default.func, /** A callback which is called when options dropdown is shown */ onOptionsShow: _propTypes.default.func, /** A callback which is called when options dropdown is hidden */ onOptionsHide: _propTypes.default.func, /** Function that receives an option, and should return the value to be displayed. */ valueParser: _propTypes.default.func, /** Sets the width of the dropdown */ dropdownWidth: _propTypes.default.string, /** Sets the offset of the dropdown from the left */ dropdownOffsetLeft: _propTypes.default.string, /** Controls whether to show options if input is empty */ showOptionsIfEmptyInput: _propTypes.default.bool, /** Mark in bold word parts based on search pattern */ highlight: _propTypes.default.bool, /** Indicates whether to render using the native select element */ native: _propTypes.default.bool, /** common popover props */ popoverProps: _propTypes.default.shape({ appendTo: _propTypes.default.oneOf(['window', 'scrollParent', 'parent', 'viewport']), maxWidth: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), minWidth: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), flip: _propTypes.default.bool, fixed: _propTypes.default.bool, placement: _propTypes.default.oneOf(['auto-start', 'auto', 'auto-end', 'top-start', 'top', 'top-end', 'right-start', 'right', 'right-end', 'bottom-end', 'bottom', 'bottom-start', 'left-end', 'left', 'left-start']), dynamicWidth: _propTypes.default.bool }) }); InputWithOptions.displayName = 'InputWithOptions'; var _default = exports.default = InputWithOptions; //# sourceMappingURL=InputWithOptions.js.map