UNPKG

wix-style-react

Version:
558 lines (540 loc) • 20.3 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 _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"); var _uniqueId = _interopRequireDefault(require("lodash/uniqueId")); var _Popover = _interopRequireDefault(require("../Popover")); var _HighlightContext = _interopRequireDefault(require("./HighlightContext")); var _jsxFileName = "/home/builduser/work/a9c1ac8876d5057c/packages/wix-style-react/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 DEFAULT_POPOVER_PROPS = exports.DEFAULT_POPOVER_PROPS = { appendTo: 'parent', flip: false, fixed: true, placement: 'bottom' }; 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.onClickOutside = () => { // Hide the popover this.hideOptions(); // Trigger the ClickOutside callback if (this.props.onClickOutside) { this.props.onClickOutside(); } }; this.input = /*#__PURE__*/_react.default.createRef(); this.isDropdownLayoutVisible = () => this.state.showOptions && (this.props.showOptionsIfEmptyInput || this.state.inputValue.length > 0); /** 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._onInputClicked = this._onInputClicked.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', 'onClickOutside', '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 }, 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 })); } _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 }; 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: 172, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement(_HighlightContext.default.Provider, { value: { highlight, match: value }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 177, columnNumber: 9 } }, /*#__PURE__*/_react.default.createElement(_DropdownLayout.default, (0, _extends2.default)({ ref: dropdownLayout => this.dropdownLayout = dropdownLayout }, dropdownProps, { dataHook: "inputwithoptions-dropdownlayout", visible: true, onClose: this.hideOptions, onSelect: this._onSelect, isComposing: this.state.isComposing, inContainer: true, tabIndex: -1, __self: this, __source: { fileName: _jsxFileName, lineNumber: 178, 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: 197, 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: 199, 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, className: _InputWithOptionsSt.classes.nativeOption, __self: this, __source: { fileName: _jsxFileName, lineNumber: 211, columnNumber: 13 } }, option.value)))); } render() { var _rootProps$className; var { native, dataHook, popoverProps, dropDirectionUp, dropdownWidth, className } = this.props; var placement = dropDirectionUp ? 'top' : popoverProps.placement; var body = popoverProps.appendTo === 'window'; var rootProps = this.rootAdditionalProps(); return !native ? /*#__PURE__*/_react.default.createElement(_Popover.default, (0, _extends2.default)({ className: (0, _InputWithOptionsSt.st)(_InputWithOptionsSt.classes.root, (_rootProps$className = rootProps.className) !== null && _rootProps$className !== void 0 ? _rootProps$className : className) }, DEFAULT_POPOVER_PROPS, { dynamicWidth: body, excludeClass: this.uniqueId }, popoverProps, { width: dropdownWidth, placement: placement, dataHook: dataHook, onKeyDown: this._onKeyDown, onClickOutside: this.onClickOutside, shown: this.isDropdownLayoutVisible(), __self: this, __source: { fileName: _jsxFileName, lineNumber: 240, columnNumber: 7 } }), /*#__PURE__*/_react.default.createElement(_Popover.default.Element, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 253, columnNumber: 9 } }, /*#__PURE__*/_react.default.createElement("div", { "data-input-parent": true, className: this.inputClasses(), __self: this, __source: { fileName: _jsxFileName, lineNumber: 254, columnNumber: 11 } }, this.renderInput())), /*#__PURE__*/_react.default.createElement(_Popover.default.Content, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 258, columnNumber: 9 } }, this._renderDropdownLayout())) : this._renderNativeSelect(); } /** * 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 }); this.props.onOptionsHide && this.props.onOptionsHide(); this.props.onClose && this.props.onClose(); } } closeOnSelect() { return this.props.closeOnSelect; } get isReadOnly() { var { readOnly } = this.inputAdditionalProps() || {}; 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.hideOptions(); } 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) { if (this.state.showOptions) { if (Date.now() - this.state.lastOptionsShow > 2000) { this.hideOptions(); } } else if (!this.props.readOnly) { this.showOptions(); } if (this.props.onInputClicked) { this.props.onInputClicked(event); } } _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.props.readOnly) { 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: 520, columnNumber: 17 } }), valueParser: DEFAULT_VALUE_PARSER, dropdownWidth: null, popoverProps: DEFAULT_POPOVER_PROPS, dropdownOffsetLeft: '0', showOptionsIfEmptyInput: true, autocomplete: 'off,chrome-off', native: false }); InputWithOptions.propTypes = _objectSpread(_objectSpread(_objectSpread({}, _Input.default.propTypes), _DropdownLayout.default.propTypes), {}, { /** Use a customized input component instead of the default wix-style-react `<Input/>` component */ inputElement: _propTypes.default.element, /** Closes DropdownLayout on option selection */ closeOnSelect: _propTypes.default.bool, /** 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