wix-style-react
Version:
wix-style-react
496 lines (494 loc) • 18.5 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _DropdownBaseSt = require("./DropdownBase.st.css");
var _Popover = _interopRequireWildcard(require("../Popover"));
var _DropdownLayout = _interopRequireDefault(require("../DropdownLayout"));
var _excluded = ["onShow", "onHide"];
var _jsxFileName = "/home/builduser/work/a9c1ac8876d5057c/packages/wix-style-react/dist/cjs/DropdownBase/DropdownBase.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); }
class DropdownBase extends _react.default.PureComponent {
constructor(_props) {
var _ref, _this$props$selectedI, _this;
super(_props);
_this = this;
this._dropdownLayoutRef = null;
this._shouldCloseOnMouseLeave = false;
this.state = {
open: this.props.open,
selectedId: (_ref = (_this$props$selectedI = this.props.selectedId) !== null && _this$props$selectedI !== void 0 ? _this$props$selectedI : this.props.initialSelectedId) !== null && _ref !== void 0 ? _ref : -1,
listAutoFocus: false
};
/**
* Return `true` if the `open` prop is being controlled
*/
this._isControllingOpen = function () {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props;
return typeof props.open !== 'undefined';
};
/**
* Return `true` if the selection behaviour is being controlled
*/
this._isControllingSelection = function () {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.props;
return typeof props.selectedId !== 'undefined' && typeof props.onSelect !== 'undefined';
};
this._open = () => {
if (this.state.open) {
this._dropdownLayoutRef && this._dropdownLayoutRef._focusFirstOption();
return;
}
if (!this._isControllingOpen()) {
this.setState({
open: true
});
this.props.onShow();
}
};
this._close = e => {
if (this._isControllingOpen()) {
return;
}
// If called within a `mouseleave` event on the target element, we would like to close the
// popover only on the popover's `mouseleave` event
if (e && e.type === 'mouseleave') {
// We're not using `setState` since we don't want to wait for the next render
this._shouldCloseOnMouseLeave = true;
} else {
this.setState({
open: false,
listAutoFocus: false
});
}
this.props.onHide();
};
this._toggle = () => {
!this._isControllingOpen() && this.setState(_ref2 => {
var {
open
} = _ref2;
if (open) {
this.props.onHide();
} else {
this.props.onShow();
}
return {
open: !open,
listAutoFocus: false
};
});
};
this._handleClickOutside = () => {
var {
onClickOutside
} = this.props;
this._close();
onClickOutside && onClickOutside();
};
this._handlePopoverMouseEnter = () => {
var {
onMouseEnter
} = this.props;
onMouseEnter && onMouseEnter();
};
this._handlePopoverMouseLeave = () => {
var {
onMouseLeave
} = this.props;
if (this._shouldCloseOnMouseLeave) {
this._shouldCloseOnMouseLeave = false;
this.setState({
open: false
});
}
onMouseLeave && onMouseLeave();
};
this._handleSelect = selectedOption => {
var newState = {};
if (!this._isControllingOpen()) {
newState.open = false;
this.props.onHide();
}
if (!this._isControllingSelection()) {
newState.selectedId = selectedOption.id;
}
this.setState(newState, () => {
var {
onSelect
} = this.props;
onSelect && onSelect(selectedOption);
});
};
this._handleClose = () => {
if (this.state.open) {
this._close();
}
if (this.triggerElementRef && this.triggerElementRef.current && this.triggerElementRef.current.focus) {
this.triggerElementRef.current.focus();
}
};
this._getSelectedOption = selectedId => {
return this.props.options.find(_ref3 => {
var {
id
} = _ref3;
return id === selectedId;
});
};
/**
* Determine if a certain key should open the DropdownLayout
*/
this._isOpenKey = key => {
return ['Enter', ' ', 'ArrowDown'].includes(key);
};
this._isClosingKey = key => {
return ['Tab', 'Escape'].includes(key);
};
/**
* A common `keydown` event that can be used for the target elements. It will automatically
* delegate the event to the underlying <DropdownLayout/>, and will determine when to open the
* dropdown depending on the pressed key.
*/
this._handleKeyDown = e => {
if (this._isControllingOpen()) {
return;
}
var isHandledByDropdownLayout = this._delegateKeyDown(e);
if (!isHandledByDropdownLayout) {
if (this._isOpenKey(e.key)) {
this.setState({
listAutoFocus: true
});
this._open();
e.preventDefault();
} else if (this._isClosingKey(e.key)) {
this._close();
}
}
// prevent toggle button onClick when pressing enter and dropdown is open
else if (this.state.open && e.key === 'Enter') {
e.preventDefault();
}
};
/*
* Delegate the event to the DropdownLayout. It'll handle the navigation, option selection and
* closing of the dropdown.
*/
this._delegateKeyDown = e => {
if (!this._dropdownLayoutRef) {
return false;
}
return this._dropdownLayoutRef._onSelectListKeyDown(e);
};
this.triggerElementRef = /*#__PURE__*/_react.default.createRef();
}
UNSAFE_componentWillReceiveProps(nextProps) {
// Keep internal state updated if needed
if (this._isControllingOpen(nextProps) && this.props.open !== nextProps.open) {
this.setState({
open: nextProps.open
});
}
if (this._isControllingSelection(nextProps) && this.props.selectedId !== nextProps.selectedId) {
this.setState({
selectedId: nextProps.selectedId
});
}
}
_renderChildren() {
var {
children
} = this.props;
var {
selectedId,
open
} = this.state;
if (!children) {
return null;
}
return /*#__PURE__*/_react.default.isValidElement(children) ? children // Returning the children as is when using in controlled mode
: children({
open: this._open,
close: this._close,
toggle: this._toggle,
isOpen: Boolean(open),
ref: this.triggerElementRef,
delegateKeyDown: this._delegateKeyDown,
selectedOption: this._getSelectedOption(selectedId)
});
}
render() {
var {
dataHook,
placement,
appendTo,
showArrow,
zIndex,
moveBy,
options,
minWidth,
maxWidth,
fixed,
flip,
tabIndex,
overflow,
dynamicWidth,
maxHeight,
fluid,
animate,
className,
focusOnSelectedOption,
infiniteScroll,
loadMore,
hasMore,
focusOnOption,
scrollToOption,
markedOption,
onMouseDown,
listType,
fixedHeader,
fixedFooter,
autoFocus
} = this.props;
var {
open,
selectedId
} = this.state;
var _this$props = this.props,
{
onShow,
onHide
} = _this$props,
popoverProps = (0, _objectWithoutProperties2.default)(_this$props, _excluded);
return /*#__PURE__*/_react.default.createElement(_Popover.default, (0, _extends2.default)({
"data-list-type": listType
}, popoverProps, {
// backward compatible for migration stylable 1 to stylable 3
animate: animate,
dataHook: dataHook,
shown: open,
placement: placement,
dynamicWidth: dynamicWidth,
appendTo: appendTo,
showArrow: showArrow,
zIndex: zIndex,
moveBy: moveBy,
onKeyDown: this._handleKeyDown,
onMouseEnter: this._handlePopoverMouseEnter,
onMouseLeave: this._handlePopoverMouseLeave,
onClickOutside: this._handleClickOutside,
fixed: fixed,
flip: flip,
fluid: fluid,
className: (0, _DropdownBaseSt.st)(_DropdownBaseSt.classes.root, {
withWidth: Boolean(minWidth || maxWidth)
}, className),
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 445,
columnNumber: 7
}
}), /*#__PURE__*/_react.default.createElement(_Popover.default.Element, {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 472,
columnNumber: 9
}
}, this._renderChildren()), /*#__PURE__*/_react.default.createElement(_Popover.default.Content, {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 474,
columnNumber: 9
}
}, /*#__PURE__*/_react.default.createElement("div", {
style: {
minWidth,
maxWidth
},
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 475,
columnNumber: 11
}
}, /*#__PURE__*/_react.default.createElement(_DropdownLayout.default, {
dataHook: "dropdown-base-dropdownlayout",
className: _DropdownBaseSt.classes.list,
ref: r => this._dropdownLayoutRef = r,
selectedId: selectedId,
options: options,
maxHeightPixels: maxHeight,
onSelect: this._handleSelect,
onClose: this._handleClose,
tabIndex: tabIndex,
inContainer: true,
visible: true,
overflow: overflow,
focusOnSelectedOption: focusOnSelectedOption,
infiniteScroll: infiniteScroll,
loadMore: loadMore,
hasMore: hasMore,
focusOnOption: focusOnOption,
markedOption: markedOption,
scrollToOption: scrollToOption,
onMouseDown: onMouseDown,
listType: listType,
autoFocus: autoFocus || this.state.listAutoFocus,
fixedHeader: fixedHeader,
fixedFooter: fixedFooter,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 481,
columnNumber: 13
}
}))));
}
}
DropdownBase.displayName = 'DropdownBase';
DropdownBase.propTypes = {
/** Applies a data-hook HTML attribute that can be used in the tests */
dataHook: _propTypes.default.string,
/** Specifies a CSS class name to be appended to the component’s root element */
className: _propTypes.default.string,
/** Control whether the <Popover/> should be opened */
open: _propTypes.default.bool,
/** Control popover placement */
placement: _propTypes.default.oneOf(_Popover.placements),
/** Specifies where popover should be inserted as a last child - whether `parent` or `window` containers */
appendTo: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),
/** Specifies whether popover arrow should be shown */
showArrow: _propTypes.default.bool,
/** Defines a callback function which is called when user clicks outside of a dropdown */
onClickOutside: _propTypes.default.func,
/** Defines a callback function which is called on `onMouseEnter` event on the entire component */
onMouseEnter: _propTypes.default.func,
/** Defines a callback function which is called on `onMouseLeave` event on the entire component */
onMouseLeave: _propTypes.default.func,
/** Defines a callback function which is called when dropdown is opened */
onShow: _propTypes.default.func,
/** Defines a callback function which is called when dropdown is closed */
onHide: _propTypes.default.func,
/** Defines a callback function which is called whenever user selects a different option in the list */
onSelect: _propTypes.default.func,
/**
* Set popover's content width to a minimum width of a trigger element,
* but it can expand up to the defined value of `maxWidth`
*/
dynamicWidth: _propTypes.default.bool,
/** Controls the minimum width of dropdown layout */
minWidth: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
/** Controls the maximum width of dropdown layout */
maxWidth: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
/** Controls the maximum height of dropdown layout */
maxHeight: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
/**
* Specifies a target component to be rendered. If a regular node is passed, it'll be rendered as-is.
* If a function is passed, it's expected to return a React element.
* The function accepts an object containing the following properties:
*
* * `open` - will open the Popover
* * `close` - will close the Popover
* * `toggle` - will toggle the Popover
* * `isOpen` - indicates whether the items list is currently open
* * `delegateKeyDown` - the underlying DropdownLayout's keydown handler. It can be called
* inside another keyDown event in order to delegate it.
* * `selectedOption` - the currently selected option
*
* Check inserted component documentation for more information on available properties.
*/
children: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.func]),
/**
* Specifies an array of options for a dropdown list. Objects must have an id and can include string value or node.
* If value is '-', a divider will be rendered instead (dividers do not require and id).
*/
options: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.shape({
id: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]).isRequired,
value: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.string, _propTypes.default.func]).isRequired,
disabled: _propTypes.default.bool,
overrideStyle: _propTypes.default.bool
}),
// A divider option without an id
_propTypes.default.shape({
value: _propTypes.default.oneOf(['-'])
})])),
/** Sets the initial marking of an option in the list when opened:
* - `false` - no initially hovered list item
* - `true` - hover first selectable option
* - any `number/string` specify the id of an option to be hovered
*/
markedOption: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.string, _propTypes.default.number]),
/** Define the selected option in the list */
selectedId: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
/** Handles container overflow behaviour */
overflow: _propTypes.default.string,
/** Indicates that element can be focused and where it participates in sequential keyboard navigation */
tabIndex: _propTypes.default.number,
/**
* Sets the initially selected option in the list. Used when selection
* behaviour is being controlled.
*/
initialSelectedId: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
/** Specifies the stack order (`z-index`) of a dropdown layout */
zIndex: _propTypes.default.number,
/** Moves dropdown content relative to the parent on X or Y axis by a defined amount of pixels */
moveBy: _propTypes.default.shape({
x: _propTypes.default.number,
y: _propTypes.default.number
}),
/**
* Specifies whether to flip the <Popover/> placement
* when it starts to overlap the target element (<Popover.Element/>)
*/
flip: _propTypes.default.bool,
/**
* Specifies whether to enable the fixed behaviour. If enabled, <Popover/> keep its
* original placement even when it's being positioned outside the boundary.
*/
fixed: _propTypes.default.bool,
/** Stretches trigger element to fill its parent container width */
fluid: _propTypes.default.bool,
/** Adds enter and exit animation */
animate: _propTypes.default.bool,
/** Focus to the selected option when dropdown is opened */
focusOnSelectedOption: _propTypes.default.bool,
/** Specifies whether lazy loading of the dropdown items is enabled */
infiniteScroll: _propTypes.default.bool,
/** Defines a callback function which is called on a request to render more list items */
loadMore: _propTypes.default.func,
/** Specifies whether there are more items to load */
hasMore: _propTypes.default.bool,
/** Focus to the specified option when dropdown is opened */
focusOnOption: _propTypes.default.number,
/** Scrolls to the specified option when dropdown is opened without marking it */
scrollToOption: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
/** Defines type of behavior applied in list */
listType: _propTypes.default.oneOf(['action', 'select']),
/** A fixed header to the list */
fixedHeader: _propTypes.default.node,
/** A fixed footer to the list */
fixedFooter: _propTypes.default.node,
/** Specifies whether first list item should be focused */
autoFocus: _propTypes.default.bool
};
DropdownBase.defaultProps = {
placement: 'bottom',
appendTo: 'parent',
showArrow: false,
maxHeight: '260px',
fluid: false,
animate: false,
listType: 'select',
onShow: () => {},
onHide: () => {}
};
var _default = exports.default = DropdownBase;
//# sourceMappingURL=DropdownBase.js.map