wix-style-react
Version:
622 lines (529 loc) • 24.5 kB
JavaScript
"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;