wix-style-react
Version:
wix-style-react
558 lines (540 loc) • 20.3 kB
JavaScript
"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