UNPKG

wix-style-react

Version:
622 lines (529 loc) • 24.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = exports.DEFAULT_POPOVER_PROPS = exports.DEFAULT_VALUE_PARSER = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); 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")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } var DEFAULT_VALUE_PARSER = function DEFAULT_VALUE_PARSER(option) { return option.value; }; exports.DEFAULT_VALUE_PARSER = DEFAULT_VALUE_PARSER; var DEFAULT_POPOVER_PROPS = { appendTo: 'parent', flip: false, fixed: true, placement: 'bottom' }; exports.DEFAULT_POPOVER_PROPS = DEFAULT_POPOVER_PROPS; var InputWithOptions = /*#__PURE__*/function (_Component) { (0, _inherits2["default"])(InputWithOptions, _Component); var _super = _createSuper(InputWithOptions); function InputWithOptions(props) { var _this; (0, _classCallCheck2["default"])(this, InputWithOptions); _this = _super.call(this, props); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "onClickOutside", function () { // Hide the popover _this.hideOptions(); // Trigger the ClickOutside callback if (_this.props.onClickOutside) { _this.props.onClickOutside(); } }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "input", /*#__PURE__*/_react["default"].createRef()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "isDropdownLayoutVisible", function () { return _this.state.showOptions && (_this.props.showOptionsIfEmptyInput || _this.state.inputValue.length > 0); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_didSelectOption", function (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; }); _this.state = { inputValue: props.value || '', showOptions: false, lastOptionsShow: 0, isEditing: false }; _this.uniqueId = (0, _uniqueId["default"])('InputWithOptions'); _this._onSelect = _this._onSelect.bind((0, _assertThisInitialized2["default"])(_this)); _this._onFocus = _this._onFocus.bind((0, _assertThisInitialized2["default"])(_this)); _this._onBlur = _this._onBlur.bind((0, _assertThisInitialized2["default"])(_this)); _this._onChange = _this._onChange.bind((0, _assertThisInitialized2["default"])(_this)); _this._onKeyDown = _this._onKeyDown.bind((0, _assertThisInitialized2["default"])(_this)); _this.focus = _this.focus.bind((0, _assertThisInitialized2["default"])(_this)); _this.blur = _this.blur.bind((0, _assertThisInitialized2["default"])(_this)); _this.select = _this.select.bind((0, _assertThisInitialized2["default"])(_this)); _this.hideOptions = _this.hideOptions.bind((0, _assertThisInitialized2["default"])(_this)); _this.showOptions = _this.showOptions.bind((0, _assertThisInitialized2["default"])(_this)); _this._onManuallyInput = _this._onManuallyInput.bind((0, _assertThisInitialized2["default"])(_this)); _this._renderDropdownLayout = _this._renderDropdownLayout.bind((0, _assertThisInitialized2["default"])(_this)); _this._onInputClicked = _this._onInputClicked.bind((0, _assertThisInitialized2["default"])(_this)); _this.closeOnSelect = _this.closeOnSelect.bind((0, _assertThisInitialized2["default"])(_this)); _this.onCompositionChange = _this.onCompositionChange.bind((0, _assertThisInitialized2["default"])(_this)); return _this; } (0, _createClass2["default"])(InputWithOptions, [{ key: "inputClasses", value: // Abstraction function inputClasses() {} }, { key: "dropdownClasses", value: function dropdownClasses() {} }, { key: "dropdownAdditionalProps", value: function dropdownAdditionalProps() {} }, { key: "inputAdditionalProps", value: function inputAdditionalProps() {} /** * An array of key codes that act as manual submit. Will be used within * onKeyDown(event). * * @returns {KeyboardEvent.key[]} */ }, { key: "getManualSubmitKeys", value: function getManualSubmitKeys() { return ['Enter', 'Tab']; } }, { key: "componentDidUpdate", value: function 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: '' }); } } }, { key: "onCompositionChange", value: function onCompositionChange(isComposing) { this.setState({ isComposing: isComposing }); } }, { key: "renderInput", value: function 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'], this.props), inputAdditionalProps); var inputElement = inputProps.inputElement; 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 })); } }, { key: "_renderDropdownLayout", value: function _renderDropdownLayout() { var _this2 = this; var _this$props = this.props, highlight = _this$props.highlight, value = _this$props.value; 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" }, /*#__PURE__*/_react["default"].createElement(_HighlightContext["default"].Provider, { value: { highlight: highlight, match: value } }, /*#__PURE__*/_react["default"].createElement(_DropdownLayout["default"], (0, _extends2["default"])({ ref: function ref(dropdownLayout) { return _this2.dropdownLayout = dropdownLayout; } }, dropdownProps, { dataHook: "inputwithoptions-dropdownlayout", visible: true, onClose: this.hideOptions, onSelect: this._onSelect, isComposing: this.state.isComposing, inContainer: true, tabIndex: -1 })))); } }, { key: "_renderNativeSelect", value: function _renderNativeSelect() { var _this3 = this; var _this$props2 = this.props, options = _this$props2.options, onSelect = _this$props2.onSelect, disabled = _this$props2.disabled; return /*#__PURE__*/_react["default"].createElement("div", { className: _InputWithOptionsSt.classes.nativeSelectWrapper }, this.renderInput(), /*#__PURE__*/_react["default"].createElement("select", { disabled: disabled, "data-hook": "native-select", className: _InputWithOptionsSt.classes.nativeSelect, onChange: function onChange(event) { _this3._onChange(event); // In this case we don't use DropdownLayout so we need to invoke `onSelect` manually onSelect(options[event.target.selectedIndex]); } }, options.map(function (option, index) { return /*#__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 }, option.value); }))); } }, { key: "render", value: function render() { var _this$props3 = this.props, _native = _this$props3["native"], dataHook = _this$props3.dataHook, popoverProps = _this$props3.popoverProps, dropDirectionUp = _this$props3.dropDirectionUp, dropdownWidth = _this$props3.dropdownWidth; var placement = dropDirectionUp ? 'top' : popoverProps.placement; var body = popoverProps.appendTo === 'window'; return !_native ? /*#__PURE__*/_react["default"].createElement(_Popover["default"], (0, _extends2["default"])({ className: _InputWithOptionsSt.classes.root }, DEFAULT_POPOVER_PROPS, { dynamicWidth: body, excludeClass: this.uniqueId }, popoverProps, { width: dropdownWidth, placement: placement, dataHook: dataHook, onKeyDown: this._onKeyDown, onClickOutside: this.onClickOutside, shown: this.isDropdownLayoutVisible() }), /*#__PURE__*/_react["default"].createElement(_Popover["default"].Element, null, /*#__PURE__*/_react["default"].createElement("div", { "data-input-parent": true, className: this.inputClasses() }, this.renderInput())), /*#__PURE__*/_react["default"].createElement(_Popover["default"].Content, null, this._renderDropdownLayout())) : this._renderNativeSelect(); } /** * Shows dropdown options */ }, { key: "showOptions", value: function showOptions() { if (!this.state.showOptions) { this.setState({ showOptions: true, lastOptionsShow: Date.now() }); this.props.onOptionsShow && this.props.onOptionsShow(); } } /** * Hides dropdown options */ }, { key: "hideOptions", value: function hideOptions() { if (this.state.showOptions) { this.setState({ showOptions: false }); this.props.onOptionsHide && this.props.onOptionsHide(); this.props.onClose && this.props.onClose(); } } }, { key: "closeOnSelect", value: function closeOnSelect() { return this.props.closeOnSelect; } }, { key: "isReadOnly", get: function get() { var _ref = this.inputAdditionalProps() || {}, readOnly = _ref.readOnly; return readOnly; } /** * Determine if the provided key should cause the dropdown to be opened. * * @param {KeyboardEvent.key} * @returns {boolean} */ }, { key: "shouldOpenDropdown", value: function 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} */ }, { key: "shouldDelegateKeyDown", value: function shouldDelegateKeyDown(key) { return this.isReadOnly || !['Spacebar', ' '].includes(key); } /** * Determine if the provided key should cause manual submit. * * @param {KeyboardEvent.key} * @returns {boolean} */ }, { key: "shouldPerformManualSubmit", value: function shouldPerformManualSubmit(key) { return this.getManualSubmitKeys().includes(key); } }, { key: "_onManuallyInput", value: function _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(function (element) { return element.value === inputValue; }); if (this.props.onManuallyInput) { this.props.onManuallyInput(inputValue, suggestedOption); } } }, { key: "_onSelect", value: function _onSelect(option, isSelectedOption) { var onSelect = this.props.onSelect; if (this.closeOnSelect() || isSelectedOption) { this.hideOptions(); } if (onSelect) { onSelect(this.props.highlight ? this.props.options.find(function (opt) { return opt.id === option.id; }) : option); } } }, { key: "_onChange", value: function _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(); } } }, { key: "_onInputClicked", value: function _onInputClicked(event) { if (this.state.showOptions) { if (Date.now() - this.state.lastOptionsShow > 2000) { this.hideOptions(); } } else { this.showOptions(); } if (this.props.onInputClicked) { this.props.onInputClicked(event); } } }, { key: "_onFocus", value: function _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); } } /** Checks if focus event is related to selecting an option */ }, { key: "_onBlur", value: function _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); } } }, { key: "_onKeyDown", value: function _onKeyDown(event) { if (this.props.disabled) { return; } var key = event.key; /* 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._onKeyDown(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 */ }, { key: "focus", value: function 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 */ }, { key: "blur", value: function blur() { this.input.current && this.input.current.blur(); } /** * Selects all text in the input element */ }, { key: "select", value: function select() { this.input.current && this.input.current.select(); } }]); return InputWithOptions; }(_react.Component); InputWithOptions.defaultProps = _objectSpread(_objectSpread(_objectSpread({}, _Input["default"].defaultProps), _DropdownLayout["default"].defaultProps), {}, { onSelect: function onSelect() {}, options: [], closeOnSelect: true, inputElement: /*#__PURE__*/_react["default"].createElement(_Input["default"], null), 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-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 = InputWithOptions; exports["default"] = _default;