@wix/design-system
Version:
@wix/design-system
485 lines (484 loc) • 17.7 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 _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _uniqueId = _interopRequireDefault(require("lodash/uniqueId"));
var _DropdownBaseSt = require("./DropdownBase.st.css.js");
var _DropdownLayout = _interopRequireDefault(require("../DropdownLayout"));
var _PopoverNext = _interopRequireDefault(require("../PopoverNext"));
var _context = require("../WixDesignSystemProvider/context");
var _Drawer = _interopRequireDefault(require("../Drawer"));
var _excluded = ["onShow", "onHide"];
var _jsxFileName = "/home/builduser/work/57e038ea7326c1ec/packages/wix-design-system/dist/cjs/DropdownBase/DropdownBase.tsx";
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; }
class DropdownBase extends _react.PureComponent {
constructor(_props) {
var _ref, _this$props$selectedI, _this;
super(_props);
_this = this;
this.triggerElementRef = void 0;
this.uniqueId = (0, _uniqueId.default)('DropdownBase');
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,
activeDescendantId: undefined,
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._setIsOpen = (isOpen, _, newState) => {
if (!this._isControllingOpen()) {
this.setState(_objectSpread(_objectSpread({}, newState), {}, {
open: isOpen,
listAutoFocus: false
}));
}
};
this._handleClickOutside = () => {
var {
onClickOutside
} = this.props;
onClickOutside && onClickOutside();
};
this._handlePopoverMouseEnter = renderedTrigger => {
var {
onMouseEnter
} = this.props;
if (/*#__PURE__*/_react.default.isValidElement(renderedTrigger) && renderedTrigger.props.onMouseEnter) {
renderedTrigger.props.onMouseEnter();
}
onMouseEnter && onMouseEnter();
};
this._handlePopoverMouseLeave = renderedTrigger => {
var {
onMouseLeave
} = this.props;
if (this._shouldCloseOnMouseLeave) {
this._shouldCloseOnMouseLeave = false;
this.setState({
open: false,
activeDescendantId: undefined
});
}
if (/*#__PURE__*/_react.default.isValidElement(renderedTrigger) && renderedTrigger.props.onMouseLeave) {
renderedTrigger.props.onMouseLeave();
}
onMouseLeave && onMouseLeave();
};
this._handleSelect = selectedOption => {
var newState = {};
this._close();
if (!this._isControllingSelection()) {
newState.selectedId = selectedOption.id;
}
this.setState(newState, () => {
var {
onSelect
} = this.props;
onSelect && onSelect(selectedOption);
});
};
this._handleOptionMarked = (_option, optionElementId) => {
this.setState({
activeDescendantId: optionElementId
});
};
this._open = () => {
this._setIsOpen(true);
};
this._close = () => {
this._setIsOpen(false);
};
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 => {
var _this$props$options;
return (_this$props$options = this.props.options) == null ? void 0 : _this$props$options.find(_ref2 => {
var {
id
} = _ref2;
return id === selectedId;
});
};
/**
* Determine if a certain key should open the DropdownLayout
*/
this._isOpenKey = key => {
return ['Enter', ' ', 'ArrowDown'].includes(key);
};
this._isClosingKey = key => {
if (this._isDialogMode()) {
return ['Escape'].includes(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.state.open) {
this.setState({
listAutoFocus: true
});
this._open();
e.preventDefault();
} else if (this._isClosingKey(e.key) && this.state.open) {
// Fallback: handle closing keys when DropdownLayout doesn't process them
// This happens in action list mode where DropdownLayout returns false immediately
// for container-level keydown events (listType !== ListType.select check)
this._close();
}
}
// Prevent trigger element events when dropdown is open.
if (this.state.open && (e.key === 'Enter' || e.key === ' ') && !this._isEventFromFixedRegion(e)) {
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._setDropdownLayoutRef = ref => {
var _this$_dropdownLayout;
this._dropdownLayoutRef = ref;
this.setState({
activeDescendantId: (_this$_dropdownLayout = this._dropdownLayoutRef) == null ? void 0 : _this$_dropdownLayout.getActiveDescendentElementId()
});
};
this._handleOutsideClick = (_, reason) => {
if (reason === 'outside-press') {
this._setIsOpen(false, reason);
return;
}
};
this._isDialogMode = () => {
/** This component has two modes:
* 1. Menu mode: when fixedHeader or fixedFooter is not present - https://www.w3.org/WAI/ARIA/apg/patterns/menubar/
* 2. Dialog mode: when fixedHeader or fixedFooter is present - https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
*/
var {
fixedHeader,
fixedFooter
} = this.props;
return Boolean(fixedHeader || fixedFooter);
};
this._isEventFromFixedRegion = event => {
var target = event.target;
var region = target.closest('[data-dropdown-region]');
if (!region) {
return false;
}
var regionValue = region.getAttribute('data-dropdown-region');
return regionValue === 'header' || regionValue === 'footer';
};
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(renderedTrigger) {
var {
children
} = this.props;
if (!children) {
return null;
}
return /*#__PURE__*/_react.default.isValidElement(children) ? children : /*#__PURE__*/_react.default.isValidElement(renderedTrigger) ? /*#__PURE__*/_react.default.cloneElement(renderedTrigger, _objectSpread(_objectSpread({}, renderedTrigger.props.onMouseEnter ? {
onMouseEnter: () => {}
} : {}), renderedTrigger.props.onMouseLeave ? {
onMouseLeave: () => {}
} : {})) : renderedTrigger;
}
_renderDropdownLayout() {
var {
options,
maxHeight,
overflow,
focusOnSelectedOption,
infiniteScroll,
loadMore,
hasMore,
focusOnOption,
markedOption,
scrollToOption,
onMouseDown,
listType,
autoFocus,
fixedHeader,
fixedFooter
} = this.props;
var {
selectedId
} = this.state;
return /*#__PURE__*/_react.default.createElement(_DropdownLayout.default, {
dataHook: "dropdown-base-dropdownlayout",
className: _DropdownBaseSt.classes.list,
ref: this._setDropdownLayoutRef,
selectedId: selectedId,
options: options,
maxHeightPixels: maxHeight,
onSelect: this._handleSelect,
onOptionMarked: this._handleOptionMarked,
onClose: this._handleClose,
inContainer: true,
visible: true,
overflow: overflow,
focusOnSelectedOption: focusOnSelectedOption,
infiniteScroll: infiniteScroll,
loadMore: loadMore,
hasMore: hasMore,
focusOnOption: focusOnOption,
markedOption: markedOption,
scrollToOption: scrollToOption,
onMouseDown: onMouseDown,
listType: listType
// In dialog mode, FloatingFocusManager handles initial focus
// In menu mode, DropdownLayout focuses the first option
,
autoFocus: this._isDialogMode() ? false : autoFocus || this.state.listAutoFocus,
fixedHeader: fixedHeader,
fixedFooter: fixedFooter,
listboxId: "".concat(this.uniqueId, "-listbox"),
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 326,
columnNumber: 7
}
});
}
render() {
var {
dataHook,
placement,
appendTo,
showArrow,
zIndex,
moveBy,
minWidth,
maxWidth,
width,
fixed,
flip,
dynamicWidth,
fluid,
animate,
className,
listType,
popoverContentClassName,
onMouseEnter,
onMouseLeave,
selectedId,
children
} = this.props;
var {
open,
activeDescendantId
} = this.state;
var _this$props = this.props,
{
onShow,
onHide
} = _this$props,
popoverProps = (0, _objectWithoutProperties2.default)(_this$props, _excluded);
var renderedTrigger = typeof children === 'function' ? children({
toggle: Boolean(open) ? this._close : this._open,
open: this._open,
close: this._close,
isOpen: Boolean(open),
ref: this.triggerElementRef,
delegateKeyDown: this._delegateKeyDown,
selectedOption: this._getSelectedOption(selectedId),
listboxId: "".concat(this.uniqueId, "-listbox"),
activeDescendantId
}) : children;
return /*#__PURE__*/_react.default.createElement(_context.WixDesignSystemContext.Consumer, {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 403,
columnNumber: 7
}
}, _ref3 => {
var {
mobile
} = _ref3;
return mobile ? /*#__PURE__*/_react.default.createElement("div", {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 406,
columnNumber: 13
}
}, /*#__PURE__*/_react.default.createElement("div", {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 407,
columnNumber: 15
}
}, this._renderChildren(renderedTrigger)), /*#__PURE__*/_react.default.createElement(_Drawer.default, {
dataHook: dataHook,
open: Boolean(open),
onClose: isOpen => this._setIsOpen(isOpen),
zIndex: zIndex,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 408,
columnNumber: 15
}
}, /*#__PURE__*/_react.default.createElement("div", {
className: _DropdownBaseSt.classes.drawerContent,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 414,
columnNumber: 17
}
}, this._renderDropdownLayout()))) : /*#__PURE__*/_react.default.createElement(_PopoverNext.default, (0, _extends2.default)({
dataListType: listType,
onOpenChange: this._handleOutsideClick,
animate: animate,
dataHook: dataHook,
open: open,
autoUpdateOptions: {
animationFrame: true
}
}, popoverProps, {
// backward compatible for migration stylable 1 to stylable 3
placement: placement,
dynamicWidth: dynamicWidth,
appendTo: appendTo,
showArrow: showArrow,
zIndex: zIndex,
moveBy: moveBy,
onKeyDown: this._handleKeyDown,
onMouseEnter: onMouseEnter || !!(/*#__PURE__*/_react.default.isValidElement(renderedTrigger) && renderedTrigger.props.onMouseEnter) ? () => this._handlePopoverMouseEnter(renderedTrigger) : undefined,
onMouseLeave: onMouseLeave || !!(/*#__PURE__*/_react.default.isValidElement(renderedTrigger) && renderedTrigger.props.onMouseLeave) ? () => this._handlePopoverMouseLeave(renderedTrigger) : undefined,
onClickOutside: this._handleClickOutside,
fixed: fixed,
flip: flip,
fluid: fluid,
onHide: onHide,
onShow: onShow,
focusManagerEnabled: this._isDialogMode(),
className: (0, _DropdownBaseSt.st)(_DropdownBaseSt.classes.root, {
withWidth: Boolean(minWidth || maxWidth)
}, className),
minWidth: minWidth,
maxWidth: maxWidth,
width: width,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 420,
columnNumber: 13
}
}), /*#__PURE__*/_react.default.createElement(_PopoverNext.default.Trigger, {
className: _DropdownBaseSt.classes.trigger,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 471,
columnNumber: 15
}
}, /*#__PURE__*/_react.default.createElement("div", {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 472,
columnNumber: 17
}
}, this._renderChildren(renderedTrigger))), /*#__PURE__*/_react.default.createElement(_PopoverNext.default.Content, {
className: (0, _DropdownBaseSt.st)(_DropdownBaseSt.classes.content, popoverContentClassName, {
withWidth: Boolean(minWidth || maxWidth)
}),
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 475,
columnNumber: 15
}
}, /*#__PURE__*/_react.default.createElement("div", {
style: {
minWidth,
maxWidth
},
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 480,
columnNumber: 17
}
}, this._renderDropdownLayout())));
});
}
}
DropdownBase.displayName = 'DropdownBase';
DropdownBase.defaultProps = {
placement: 'bottom',
appendTo: 'parent',
showArrow: false,
maxHeight: '260px',
dynamicWidth: true,
minWidth: 192,
fluid: false,
animate: false,
listType: 'select',
onShow: () => {},
onHide: () => {},
onMouseEnter: () => {},
onMouseLeave: () => {}
};
var _default = exports.default = DropdownBase;
//# sourceMappingURL=DropdownBase.js.map