UNPKG

@wix/design-system

Version:

@wix/design-system

1,064 lines (1,052 loc) 42.4 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.DropdownLayout = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _react = _interopRequireWildcard(require("react")); var _Loader = _interopRequireDefault(require("../Loader/Loader")); var _InfiniteScroll = _interopRequireDefault(require("../utils/InfiniteScroll")); var _scrollIntoView = _interopRequireDefault(require("../utils/scrollIntoView")); var _DataAttr = require("./DataAttr"); var _DropdownLayoutSt = require("./DropdownLayout.st.css.js"); var _filterObject = require("../utils/filterObject"); var _ListItemSection = require("../ListItemSection"); var _ListItemSelect = require("../ListItemSelect"); var _ListItemAction = require("../ListItemAction"); var _StringUtils = require("../utils/StringUtils"); var _withOverlayScrollbar = require("./withOverlayScrollbar"); var _DropdownLayout2 = require("./DropdownLayout.constants"); var _forkRef = require("../utils/forkRef"); var _useHover = require("../common/useHover"); var _excluded = ["id", "value", "disabled", "disabledDescription", "optionTitle", "title"]; var _jsxFileName = "/home/builduser/work/57e038ea7326c1ec/packages/wix-design-system/dist/cjs/DropdownLayout/DropdownLayout.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; } var modulus = (n, m) => (n % m + m) % m; var getUnit = value => (0, _StringUtils.isString)(value) ? value : "".concat(value, "px"); class DropdownLayout extends _react.PureComponent { constructor(props) { super(props); this.setScrollElement = node => { this.setState({ scrollElement: node }); }; this.containerRef = /*#__PURE__*/_react.default.createRef(); this.optionsRef = /*#__PURE__*/_react.default.createRef(); this.loadedWithUndefinedOptions = false; this._boundEvents = []; this.focusableItemsIdsList = []; this.savedOnClicks = []; this.children = {}; this.selectedOption = null; 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 _this$props = this.props, fixedHeader = _this$props.fixedHeader, fixedFooter = _this$props.fixedFooter; return Boolean(fixedHeader || fixedFooter); }; /** @deprecated */ this._onMouseEventsHandler = e => { if (!this._checkIfEventOnElements(e, [this.containerRef.current])) { this._onClickOutside(e); } }; this._onClickOutside = event => { var _this$props2 = this.props, visible = _this$props2.visible, onClickOutside = _this$props2.onClickOutside; if (visible && onClickOutside) { onClickOutside(event); } }; this._onSelect = (index, e) => { var _this$props3 = this.props, onSelect = _this$props3.onSelect, listType = _this$props3.listType; var options = this.props.options || []; if (listType !== _DropdownLayout2.ListType.select) { e.stopPropagation(); this._onClose(); return; } var chosenOption = options[index]; if (chosenOption) { var sameOptionWasPicked = chosenOption.id === this.state.selectedId; if (onSelect) { e.stopPropagation(); onSelect( // casting to avoid breaking changes after TS conversion chosenOption, sameOptionWasPicked); } } if (!this._isControlled()) { this.setState({ selectedId: chosenOption && chosenOption.id }); } return !!onSelect && chosenOption; }; this._onActionClick = e => { var _this$savedOnClicks$f; var onClick = (_this$savedOnClicks$f = this.savedOnClicks.find(_ref => { var id = _ref.id; return id === e.id; })) == null ? void 0 : _this$savedOnClicks$f.onClick; onClick && onClick(e); }; this._saveOnClicks = () => { var options = this.props.options || []; this.savedOnClicks = options.map(_ref2 => { var id = _ref2.id, onClick = _ref2.onClick; return { id, onClick }; }); }; this._onOptionHoverStart = index => { var _this$props$options = this.props.options, options = _this$props$options === void 0 ? [] : _this$props$options; if (this._isSelectableOption(options[index])) { var _this$props$onOptions, _this$props4; var markedOption = options[index]; (_this$props$onOptions = (_this$props4 = this.props).onOptionsNavigate) == null || _this$props$onOptions.call(_this$props4, markedOption); this._markOption(index); } }; this._onOptionHoverEnd = () => { this._markOption(_DropdownLayout2.NOT_HOVERED_INDEX); }; this._onContainerMouseLeave = e => { var _this$props$onOptions2, _this$props5; (_this$props$onOptions2 = (_this$props5 = this.props).onOptionsNavigate) == null || _this$props$onOptions2.call(_this$props5, null); this._markOption(_DropdownLayout2.NOT_HOVERED_INDEX); if (this.props.onMouseLeave) { this.props.onMouseLeave(e); } }; this._focusOnOption = () => { var focusOnOption = this.props.focusOnOption; var options = this.props.options || []; var markedIndex = options.findIndex(option => option.id === focusOnOption); if (markedIndex !== -1) { this._markOptionAtIndex(markedIndex); } else { // Remove focus this._markOption(markedIndex); } }; this._markOptionAtIndex = markedIndex => { this._markOption(markedIndex); this._scrollIntoViewByIndex(markedIndex); }; this._scrollIntoViewByIndex = index => { var _this$optionsRef$curr, _this$optionsRef$curr2; var infiniteScroll = this.props.infiniteScroll; var menuElement = this.optionsRef.current; var hoveredElement = infiniteScroll ? (_this$optionsRef$curr = this.optionsRef.current) == null ? void 0 : _this$optionsRef$curr.childNodes[0].childNodes[index] : (_this$optionsRef$curr2 = this.optionsRef.current) == null ? void 0 : _this$optionsRef$curr2.childNodes[index]; (0, _scrollIntoView.default)(menuElement, hoveredElement); }; /** * Handle keydown events for the DropdownLayout, mostly for accessibility * * @param event - The keydown event triggered by React * @returns Whether the event was handled by the component */ this._onSelectListKeyDown = event => { if (!this.props.visible || // such prop does not exist but let's avoid potential breaking change // @ts-expect-error this.props.isComposing || this.props.listType !== _DropdownLayout2.ListType.select) { return false; } switch (event.key) { case _DropdownLayout2.KEY.arrowDown: { if (this._isArrowNavigationDisabled(event)) { return false; } this._markNextStep(1); event.preventDefault(); event.stopPropagation(); return true; } case _DropdownLayout2.KEY.arrowUp: { if (this._isArrowNavigationDisabled(event)) { return false; } this._markNextStep(-1); event.preventDefault(); event.stopPropagation(); return true; } case _DropdownLayout2.KEY.space: case _DropdownLayout2.KEY.enter: { // If event is from fixedHeader/fixedFooter, let it bubble naturally var isFromFixedRegion = this._isEventFromFixedRegion(event); if (isFromFixedRegion) { // Don't preventDefault, don't stopPropagation - let button handle it completely return false; } // Otherwise, handle as option selection (when options container is focused) if (this.state.hovered !== _DropdownLayout2.NOT_HOVERED_INDEX) { this._onSelect(this.state.hovered, event); if (this.props.closeOnSelect) { this._onClose(); } return true; } // If no option is hovered, return false to allow manual input handling return false; } case _DropdownLayout2.KEY.tab: { var _event$target; // Dialog mode: let natural Tab navigation work within dialog if (this._isDialogMode()) { return false; } // Menu mode: handle Tab key behavior // If an option is hovered, select it first (similar to Enter/Space) if (this.state.hovered !== _DropdownLayout2.NOT_HOVERED_INDEX) { event.preventDefault(); this._onSelect(this.state.hovered, event); if (this.props.closeOnSelect) { this._onClose(); } return true; } // No hovered option: close dropdown and determine if parent should handle Tab this._onClose(); // Check if Tab originated from an input field with text // If so, return false to allow parent component (e.g., InputWithOptions) // to handle Tab and trigger onManuallyInput callback // Otherwise, return true to indicate we handled it (prevents DropdownBase double-closing) var inputValue = (_event$target = event.target) == null || (_event$target = _event$target.value) == null ? void 0 : _event$target.trim(); var hasInputText = Boolean(inputValue); // Return false to allow parent handling, true to indicate we handled it return !hasInputText; } case _DropdownLayout2.KEY.arrowRight: { // Delegate to the component-level onDrillIn callback so the consumer // can move focus into an interactive child element (e.g. an actions menu). if (this.state.hovered === _DropdownLayout2.NOT_HOVERED_INDEX) { return false; } var onDrillIn = this.props.onDrillIn; if (typeof onDrillIn === 'function') { var _this$props$options2; var options = (_this$props$options2 = this.props.options) !== null && _this$props$options2 !== void 0 ? _this$props$options2 : []; var hoveredOption = options[this.state.hovered]; onDrillIn(hoveredOption); event.preventDefault(); event.stopPropagation(); return true; } return false; } case _DropdownLayout2.KEY.escape: { this._onClose(); event.stopPropagation(); return true; } default: { return false; } } }; this._focus = (focusedItemId, e) => { e && e.preventDefault(); var element = this.children[focusedItemId]; if (!element) { return; } var native = element.focus; var focusableHOC = element.wrappedComponentRef; var callback = native ? element.focus : focusableHOC ? focusableHOC.innerComponentRef.focus : () => ({}); this.setState({ focusedItemId }, () => callback == null ? void 0 : callback()); }; this._handleActionListNavigation = (event, id) => { var length = this.focusableItemsIdsList.length; var focusedItemId = this.state.focusedItemId; var key = event.key; var currentMenuItemIndex = this.focusableItemsIdsList.indexOf(id); var firstMenuItem = this.focusableItemsIdsList[0]; var lastMenuItem = this.focusableItemsIdsList[length - 1]; if (key === _DropdownLayout2.KEY.arrowLeft || key === _DropdownLayout2.KEY.arrowUp) { focusedItemId = id === 0 ? lastMenuItem : this.focusableItemsIdsList[currentMenuItemIndex - 1]; } if (key === _DropdownLayout2.KEY.arrowRight || key === _DropdownLayout2.KEY.arrowDown) { focusedItemId = currentMenuItemIndex === length - 1 ? firstMenuItem : this.focusableItemsIdsList[currentMenuItemIndex + 1]; } if (key === _DropdownLayout2.KEY.home) { focusedItemId = firstMenuItem; } if (key === _DropdownLayout2.KEY.end) { focusedItemId = lastMenuItem; } if (focusedItemId !== this.state.focusedItemId) { this._focus(focusedItemId, event); this._scrollIntoViewByIndex(focusedItemId); } }; this._onActionListKeyDown = (event, id) => { // Action lists use direct focus on each item, so they have their own // keyboard handling that must run first. if (this.props.listType === _DropdownLayout2.ListType.action) { var _key = event.key; if (_key === _DropdownLayout2.KEY.space || _key === _DropdownLayout2.KEY.enter) { event.preventDefault(); this._onActionClick({ id: this.state.focusedItemId }); this._onClose(); } else if (_key === _DropdownLayout2.KEY.escape || _key === _DropdownLayout2.KEY.tab) { this._onClose(); } else { this._handleActionListNavigation(event, id); } event.stopPropagation(); return; } // For non-action lists: when focus is on an interactive child element // (e.g. a button the user drilled into via ArrowRight), handle keyboard // navigation back to the options container. var optionEl = event.currentTarget; var activeEl = document.activeElement; var containsFocusedElement = optionEl.contains(activeEl) && activeEl !== optionEl; if (!containsFocusedElement) { return; } var key = event.key; var restoreFocusToList = () => { if (this.props.onDrillOut) { this.props.onDrillOut(); } else if (this.optionsRef.current) { this.optionsRef.current.focus(); } }; if (key === _DropdownLayout2.KEY.escape || key === _DropdownLayout2.KEY.arrowLeft) { event.preventDefault(); event.stopPropagation(); restoreFocusToList(); } if (key === _DropdownLayout2.KEY.enter || key === _DropdownLayout2.KEY.space) { event.stopPropagation(); } if (key === _DropdownLayout2.KEY.arrowDown || key === _DropdownLayout2.KEY.arrowUp) { event.preventDefault(); event.stopPropagation(); } }; this._onClose = () => { var _this$props$onOptions3, _this$props6; (_this$props$onOptions3 = (_this$props6 = this.props).onOptionsNavigate) == null || _this$props$onOptions3.call(_this$props6, null); this._markOption(_DropdownLayout2.NOT_HOVERED_INDEX); if (this.props.onClose) { this.props.onClose(); } }; this._onFixedRegionFocus = () => { // Clear the highlighted/active list item when focus moves to header/footer button if (this.state.hovered !== _DropdownLayout2.NOT_HOVERED_INDEX) { this._markOption(_DropdownLayout2.NOT_HOVERED_INDEX); } }; this._wrapWithInfiniteScroll = scrollableElement => { if (!this.optionsRef.current) { this.loadedWithUndefinedOptions = true; } return /*#__PURE__*/_react.default.createElement(_InfiniteScroll.default, { useWindow: true, dataHook: _DataAttr.DATA_HOOKS.INFINITE_SCROLL_CONTAINER, scrollElement: this.state.scrollElement, loadMore: this.props.loadMore, hasMore: this.props.hasMore, data: this.props.options, loader: /*#__PURE__*/_react.default.createElement("div", { className: _DropdownLayoutSt.classes.loader, __self: this, __source: { fileName: _jsxFileName, lineNumber: 858, columnNumber: 11 } }, /*#__PURE__*/_react.default.createElement(_Loader.default, { dataHook: _DataAttr.DROPDOWN_LAYOUT_LOADER, size: "small", __self: this, __source: { fileName: _jsxFileName, lineNumber: 859, columnNumber: 13 } })), __self: this, __source: { fileName: _jsxFileName, lineNumber: 850, columnNumber: 7 } }, scrollableElement); }; /** for testing purposes only */ this._getDataAttributes = () => { var _this$props7 = this.props, visible = _this$props7.visible, dropDirectionUp = _this$props7.dropDirectionUp; var selectedId = this.state.selectedId; return (0, _filterObject.filterObject)({ 'data-hook': _DataAttr.DATA_HOOKS.CONTENT_CONTAINER, [_DataAttr.DATA_SHOWN]: visible, [_DataAttr.DATA_SELECTED_OPTION_ID]: selectedId === 0 ? "".concat(selectedId) : selectedId, [_DataAttr.DATA_DIRECTION]: dropDirectionUp ? _DataAttr.DROPDOWN_LAYOUT_DIRECTIONS.UP : _DataAttr.DROPDOWN_LAYOUT_DIRECTIONS.DOWN }, (_, value) => !!value); }; // For testing purposes only this._getItemDataAttr = _ref3 => { var hovered = _ref3.hovered, selected = _ref3.selected, disabled = _ref3.disabled; var _this$props8 = this.props, itemHeight = _this$props8.itemHeight, selectedHighlight = _this$props8.selectedHighlight; return (0, _filterObject.filterObject)({ [_DataAttr.DATA_OPTION.DISABLED]: disabled, [_DataAttr.DATA_OPTION.SELECTED]: selected && selectedHighlight, [_DataAttr.DATA_OPTION.HOVERED]: hovered, /* deprecated */ [_DataAttr.DATA_OPTION.SIZE]: itemHeight }, (_, value) => !!value); }; this._isSelectableOption = option => { if (!option) { return false; } if (this._isDivider(option)) { return false; } // only DropdownLayoutValueOption type has title prop, which is deprecated // therefore expecting error instead of dealing with it // @ts-expect-error var hasTitle = option.title; return !this._isDivider(option) && !option.disabled && !hasTitle; }; this.state = { hovered: _DropdownLayout2.NOT_HOVERED_INDEX, selectedId: props.selectedId, scrollElement: null, focusedItemId: null }; } componentDidMount() { if (this.loadedWithUndefinedOptions && this.optionsRef.current) { this.forceUpdate(); } var _this$props9 = this.props, focusOnSelectedOption = _this$props9.focusOnSelectedOption, scrollToOption = _this$props9.scrollToOption, autoFocus = _this$props9.autoFocus; if (focusOnSelectedOption) { this._focusOnSelectedOption(); } else if (this.props.hasOwnProperty('focusOnOption')) { this._focusOnOption(); } if (scrollToOption) { this._scrollToOption(); } this._markOptionByProperty(this.props); /** @deprecated */ _DropdownLayout2.MOUSE_EVENTS_SUPPORTED.forEach(eventName => { document.addEventListener(eventName, this._onMouseEventsHandler, true); }); this._boundEvents = _DropdownLayout2.MOUSE_EVENTS_SUPPORTED; if (!this._isDialogMode() && autoFocus) { this._focusFirstOption(); } } componentWillUnmount() { if (this._boundEvents && typeof document !== 'undefined') { this._boundEvents.forEach(eventName => { document.removeEventListener(eventName, this._onMouseEventsHandler, true); }); } } componentDidUpdate(prevProps) { var focusOnOption = this.props.focusOnOption; if (prevProps.focusOnOption !== focusOnOption) { this._focusOnOption(); } } UNSAFE_componentWillReceiveProps(nextProps) { var options = this.props.options || []; var nextOptions = nextProps.options || []; if (this.props.visible !== nextProps.visible) { this._markOption(_DropdownLayout2.NOT_HOVERED_INDEX); } if (this.props.selectedId !== nextProps.selectedId) { this.setState({ selectedId: nextProps.selectedId }); } // make sure the same item is hovered if options changed if (this.state.hovered !== _DropdownLayout2.NOT_HOVERED_INDEX && (!nextOptions[this.state.hovered] || options[this.state.hovered].id !== nextOptions[this.state.hovered].id)) { var _nextProps$options; this._markOption(this._findIndex((_nextProps$options = nextProps.options) !== null && _nextProps$options !== void 0 ? _nextProps$options : [], item => item.id === options[this.state.hovered].id)); } this._markOptionByProperty(nextProps); } _getOptionElementId(optionIndex) { return "dropdown-item-".concat(optionIndex); } _focusFirstOption() { this._focus(this.focusableItemsIdsList[0]); } /** @deprecated */ _checkIfEventOnElements(e, elem) { var current = e.target; while (current.parentNode) { if (elem.indexOf(current) > -1) { return true; } current = current.parentNode; } return current !== document; } /** @deprecated */ _renderTopArrow() { var _this$props0 = this.props, withArrow = _this$props0.withArrow, visible = _this$props0.visible; return withArrow && visible ? /*#__PURE__*/_react.default.createElement("div", { "data-hook": _DataAttr.DATA_HOOKS.TOP_ARROW, className: _DropdownLayoutSt.classes.arrow, __self: this, __source: { fileName: _jsxFileName, lineNumber: 258, columnNumber: 7 } }) : null; } _convertOptionToListItemSectionBuilder(_ref4) { var option = _ref4.option, idx = _ref4.idx; if (this._isDivider(option)) { return (0, _ListItemSection.listItemSectionBuilder)({ dataHook: _DataAttr.OPTION_DATA_HOOKS.DIVIDER, id: option.id || idx, type: 'divider' }); } else if (option.title) { return (0, _ListItemSection.listItemSectionBuilder)({ dataHook: _DataAttr.OPTION_DATA_HOOKS.TITLE, id: option.id, type: 'subheader', title: option.value }); } else { // This should never happen but let's make TS happy return undefined; } } _convertOptionToListItemActionBuilder(_ref5) { var option = _ref5.option, idx = _ref5.idx; var id = option.id, value = option.value, disabled = option.disabled, disabledDescription = option.disabledDescription, optionTitle = option.optionTitle, title = option.title, rest = (0, _objectWithoutProperties2.default)(option, _excluded); var size = this.props.size; return (0, _ListItemAction.listItemActionBuilder)(_objectSpread({ id: id !== undefined ? id : idx, ref: _ref6 => this.children[id] = _ref6, tabIndex: id === this.state.focusedItemId && !disabled ? '0' : '-1', disabled, disabledDescription, title: optionTitle, role: 'menuitem', size }, rest)); } _isControlled() { return typeof this.props.selectedId !== 'undefined' && typeof this.props.onSelect !== 'undefined'; } _focusOnSelectedOption() { if (this.selectedOption && this.optionsRef.current) { this.optionsRef.current.scrollTop = Math.max(this.selectedOption.offsetTop - this.selectedOption.offsetHeight, 0); } } _setSelectedOptionNode(optionNode, option) { if (option.id === this.state.selectedId) { this.selectedOption = optionNode; } } _markOption(index, options) { var onOptionMarked = this.props.onOptionMarked; options = options || this.props.options || []; this.setState({ hovered: index }); if (onOptionMarked) { var markedOption = options[index] || null; var markedOptionElementId = index === _DropdownLayout2.NOT_HOVERED_INDEX ? undefined : this._getOptionElementId(index); // the type declaration was incorrect, casting to avoid breaking changes after TS conversion onOptionMarked(markedOption, markedOptionElementId); } } _getMarkedIndex() { var options = this.props.options || []; var useHoverIndex = this.state.hovered > _DropdownLayout2.NOT_HOVERED_INDEX; var useSelectedIdIndex = typeof this.state.selectedId !== 'undefined'; var markedIndex; if (useHoverIndex) { markedIndex = this.state.hovered; } else if (useSelectedIdIndex) { markedIndex = options.findIndex(option => option.id === this.state.selectedId); } else { markedIndex = _DropdownLayout2.NOT_HOVERED_INDEX; } return markedIndex; } getActiveDescendentElementId() { var options = this.props.options || []; var markedIndex = this._getMarkedIndex(); if (markedIndex === _DropdownLayout2.NOT_HOVERED_INDEX) { markedIndex = options.findIndex(this._isSelectableOption); } return markedIndex === _DropdownLayout2.NOT_HOVERED_INDEX ? undefined : this._getOptionElementId(markedIndex); } _markNextStep(step) { var _this$props1 = this.props, onOptionsNavigate = _this$props1.onOptionsNavigate, _this$props1$options = _this$props1.options, options = _this$props1$options === void 0 ? [] : _this$props1$options; if (!options.some(this._isSelectableOption)) { return; } var markedIndex = this._getMarkedIndex(); do { markedIndex = modulus(Math.max(markedIndex + step, -1), options.length); } while (!this._isSelectableOption(options[markedIndex])); var markedOption = options[markedIndex]; onOptionsNavigate == null || onOptionsNavigate(markedOption); this._markOptionAtIndex(markedIndex); } _scrollToOption() { var _this$optionsRef$curr3; var scrollToOption = this.props.scrollToOption; var options = this.props.options || []; var optionIndex = options.findIndex(option => option.id === scrollToOption); var optionNode = (_this$optionsRef$curr3 = this.optionsRef.current) == null ? void 0 : _this$optionsRef$curr3.childNodes[optionIndex]; if (!optionNode || !this.optionsRef.current) { return; } this.optionsRef.current.scrollTop = Math.max(optionNode.offsetTop - optionNode.offsetHeight, 0); } _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'; } _isArrowNavigationDisabled(event) { // Disable arrow navigation only when event originates from fixed header/footer region return this._isEventFromFixedRegion(event); } _renderNode(node, region) { if (!node) return null; return /*#__PURE__*/_react.default.createElement("div", { "data-dropdown-region": region, onFocus: this._onFixedRegionFocus, __self: this, __source: { fileName: _jsxFileName, lineNumber: 831, columnNumber: 7 } }, node); } _convertCustomOptionToBuilder(_ref7) { var option = _ref7.option; var _value = option.value, id = option.id, disabled = option.disabled, disabledDescription = option.disabledDescription, overrideOptionStyle = option.overrideOptionStyle, overrideStyle = option.overrideStyle; if (overrideStyle) { return { id, disabled, disabledDescription, overrideStyle, value: () => /*#__PURE__*/_react.default.createElement("div", { className: _DropdownLayoutSt.classes.dropdownOption, "data-hook": _DataAttr.DATA_HOOKS.OPTION, __self: this, __source: { fileName: _jsxFileName, lineNumber: 908, columnNumber: 11 } }, _value) }; } if (overrideOptionStyle) { return { id, disabled, disabledDescription, overrideOptionStyle, value: () => /*#__PURE__*/_react.default.createElement("div", { className: _DropdownLayoutSt.classes.dropdownOption, "data-hook": _DataAttr.DATA_HOOKS.OPTION, __self: this, __source: { fileName: _jsxFileName, lineNumber: 922, columnNumber: 11 } }, _value) }; } else { // This should never happen but let's make TS happy return undefined; } } _convertOptionToListItemSelectBuilder(_ref8) { var option = _ref8.option; var value = option.value, id = option.id, disabled = option.disabled, disabledDescription = option.disabledDescription; var selectedId = this.state.selectedId; var _this$props10 = this.props, itemHeight = _this$props10.itemHeight, selectedHighlight = _this$props10.selectedHighlight, size = _this$props10.size; return (0, _ListItemSelect.listItemSelectBuilder)({ id, title: /*#__PURE__*/_react.default.createElement("div", { "data-hook": _DataAttr.DATA_HOOKS.OPTION, __self: this, __source: { fileName: _jsxFileName, lineNumber: 944, columnNumber: 9 } }, value), disabled, disabledDescription, size, selected: id === selectedId && selectedHighlight, className: (0, _DropdownLayoutSt.st)(_DropdownLayoutSt.classes.selectableOption, { itemHeight }) }); } _isBuilderOption(option) { var value = option.value; return typeof value === 'function'; } _isCustomOption(option) { var overrideOptionStyle = option.overrideOptionStyle, overrideStyle = option.overrideStyle; return overrideOptionStyle || overrideStyle; } _isActionOption(option) { return 'value' in option && option.value === _DropdownLayout2.ListType.action; } _isItemSection(option) { // only DropdownLayoutValueOption type has title prop, which is deprecated // therefore expecting error instead of dealing with it // @ts-expect-error var isTitle = option.title; return this._isDivider(option) || isTitle; } _isDivider(opt) { return opt.value === _DropdownLayout2.DIVIDER_OPTION_VALUE; } _convertOptionToBuilder(option, idx) { if (this._isBuilderOption(option)) { return option; } else if (this._isActionOption(option)) { return this._convertOptionToListItemActionBuilder({ option, idx }); } else if (this._isItemSection(option)) { return this._convertOptionToListItemSectionBuilder({ option, idx }); } else if (this._isCustomOption(option)) { return this._convertCustomOptionToBuilder({ option }); } else { return this._convertOptionToListItemSelectBuilder({ option }); } } _renderOption(_ref9) { var option = _ref9.option, idx = _ref9.idx; var isValueOption = opt => 'id' in opt && 'value' in opt && typeof opt.value !== 'function'; var builderOption = this._convertOptionToBuilder(option, idx); var hasLink = isValueOption(option) && !!option.linkTo; var content = this._renderOptionContent({ option: builderOption, idx, hasLink }); var isActionItem = this.props.listType === _DropdownLayout2.ListType.action; return hasLink ? /*#__PURE__*/_react.default.createElement("a", { className: _DropdownLayoutSt.classes.linkItem, key: idx, "data-hook": _DataAttr.DATA_HOOKS.LINK_ITEM, href: option.linkTo, role: isActionItem ? undefined : 'option', "aria-selected": isActionItem || option.disabled ? undefined : option.id === this.state.selectedId, "aria-hidden": option.disabled, __self: this, __source: { fileName: _jsxFileName, lineNumber: 1026, columnNumber: 7 } }, content) : content; } _renderOptionContent(_ref0) { var option = _ref0.option, idx = _ref0.idx, hasLink = _ref0.hasLink; if (!option) { return undefined; } var _this$props11 = this.props, itemHeight = _this$props11.itemHeight, selectedHighlight = _this$props11.selectedHighlight, listType = _this$props11.listType; var _this$state = this.state, selectedId = _this$state.selectedId, hovered = _this$state.hovered; var id = option.id, disabled = option.disabled, disabledDescription = option.disabledDescription, overrideStyle = option.overrideStyle, overrideOptionStyle = option.overrideOptionStyle; var optionState = { selected: id === selectedId, hovered: idx === hovered, disabled, size: this.props.size }; var isDisabledWithTooltip = disabled && !!disabledDescription; if (!disabled || isDisabledWithTooltip) { this.focusableItemsIdsList = [...this.focusableItemsIdsList, id]; } var isActionItem = listType === _DropdownLayout2.ListType.action; return /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({ "aria-selected": hasLink || isActionItem || disabled ? undefined : optionState.selected, "aria-hidden": disabled }, this._getItemDataAttr(_objectSpread({}, optionState)), { role: hasLink || isActionItem ? undefined : 'option', className: overrideOptionStyle ? null : (0, _DropdownLayoutSt.st)(_DropdownLayoutSt.classes.option, _objectSpread(_objectSpread({}, optionState), {}, { selected: optionState.selected && selectedHighlight, itemHeight, overrideStyle })), id: this._getOptionElementId(idx), ref: node => this._setSelectedOptionNode(node, option), onClick: !disabled ? e => this._onSelect(idx, e) : undefined, key: idx }, (0, _useHover.createHoverHandlers)({ onHoverStart: () => this._onOptionHoverStart(idx), onHoverEnd: () => this._onOptionHoverEnd() }), { "data-hook": "dropdown-item-".concat(id), onKeyDown: e => this._onActionListKeyDown(e, id), __self: this, __source: { fileName: _jsxFileName, lineNumber: 1113, columnNumber: 7 } }), option == null ? void 0 : option.value(optionState)); } _markOptionByProperty(props) { var options = props.options || []; if (this.state.hovered === _DropdownLayout2.NOT_HOVERED_INDEX && props.markedOption) { var selectableOptions = options.filter(this._isSelectableOption); if (selectableOptions.length) { var idToMark = props.markedOption === true ? selectableOptions[0].id : props.markedOption; var foundIndex = this._findIndex(options, item => item.id === idToMark); if (foundIndex !== -1) { this._markOption(foundIndex, props.options); } } } } _findIndex(arr, predicate) { return (Array.isArray(arr) ? arr : []).findIndex(predicate); } _renderOptions() { this.focusableItemsIdsList = []; this._saveOnClicks(); var options = this.props.options || []; return options.map((option, idx) => this._renderOption({ option, idx })); } render() { var _this$props$options3; var _this$props12 = this.props, className = _this$props12.className, visible = _this$props12.visible, dropDirectionUp = _this$props12.dropDirectionUp, onMouseEnter = _this$props12.onMouseEnter, onMouseDown = _this$props12.onMouseDown, fixedHeader = _this$props12.fixedHeader, withArrow = _this$props12.withArrow, fixedFooter = _this$props12.fixedFooter, inContainer = _this$props12.inContainer, overflow = _this$props12.overflow, _this$props12$maxHeig = _this$props12.maxHeightPixels, maxHeightPixels = _this$props12$maxHeig === void 0 ? 260 : _this$props12$maxHeig, minWidthPixels = _this$props12.minWidthPixels, infiniteScroll = _this$props12.infiniteScroll, dataHook = _this$props12.dataHook, listType = _this$props12.listType, overlayScrollbarProps = _this$props12.overlayScrollbarProps, listboxId = _this$props12.listboxId; var renderedOptions = this._renderOptions(); var OverlayScrollbarHostElement = overlayScrollbarProps == null ? void 0 : overlayScrollbarProps.OverlayScrollbarHostElement; var OverlayScrollbarContentElement = overlayScrollbarProps == null ? void 0 : overlayScrollbarProps.OverlayScrollbarContentElement; var optionsContainerProps = { style: { maxHeight: getUnit(parseInt(String(maxHeightPixels), 10) - 35), overflow }, 'data-hook': _DataAttr.DATA_HOOKS.DROPDOWN_LAYOUT_OPTIONS, role: listType === _DropdownLayout2.ListType.select ? 'listbox' : 'menu', id: listboxId, // Make options container focusable as a focus stop in Modal Dialog pattern tabIndex: this._isDialogMode() ? 0 : -1, // Use aria-activedescendant pattern (W3C standard) 'aria-activedescendant': this.state.hovered !== _DropdownLayout2.NOT_HOVERED_INDEX ? this._getOptionElementId(this.state.hovered) : undefined }; var options = infiniteScroll ? this._wrapWithInfiniteScroll(renderedOptions) : renderedOptions; var optionsContainer = this.props.scrollbar === _DropdownLayout2.Scrollbar.OVERLAY && OverlayScrollbarHostElement && OverlayScrollbarContentElement ? /*#__PURE__*/_react.default.createElement(OverlayScrollbarHostElement, (0, _extends2.default)({ className: _DropdownLayoutSt.classes.options }, optionsContainerProps, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 1253, columnNumber: 9 } }), /*#__PURE__*/_react.default.createElement(OverlayScrollbarContentElement, { className: _DropdownLayoutSt.classes.optionsContent, ref: (0, _forkRef.forkRef)(this.optionsRef, this.setScrollElement), __self: this, __source: { fileName: _jsxFileName, lineNumber: 1257, columnNumber: 11 } }, options)) : /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({ className: (0, _DropdownLayoutSt.st)(_DropdownLayoutSt.classes.options, _DropdownLayoutSt.classes.optionsContent), ref: (0, _forkRef.forkRef)(this.optionsRef, this.setScrollElement) }, optionsContainerProps, { __self: this, __source: { fileName: _jsxFileName, lineNumber: 1268, columnNumber: 9 } }), options); var isEmpty = !fixedHeader && !fixedFooter && !((_this$props$options3 = this.props.options) != null && _this$props$options3.length) && (!this.props.infiniteScroll || !this.props.hasMore); return /*#__PURE__*/_react.default.createElement("div", { "data-list-type": listType, "data-hook": dataHook, className: (0, _DropdownLayoutSt.st)(_DropdownLayoutSt.classes.root, { hidden: isEmpty, visible, withArrow, direction: dropDirectionUp ? _DataAttr.DROPDOWN_LAYOUT_DIRECTIONS.UP : _DataAttr.DROPDOWN_LAYOUT_DIRECTIONS.DOWN, containerStyles: !inContainer }, className), onKeyDown: this._onSelectListKeyDown, onMouseEnter: onMouseEnter, onMouseLeave: this._onContainerMouseLeave, onMouseDown: onMouseDown, ref: this.containerRef, __self: this, __source: { fileName: _jsxFileName, lineNumber: 1284, columnNumber: 7 } }, /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({}, this._getDataAttributes(), { className: _DropdownLayoutSt.classes.contentContainer, style: { overflow, maxHeight: maxHeightPixels ? getUnit(maxHeightPixels) : undefined, minWidth: minWidthPixels ? getUnit(minWidthPixels) : undefined }, __self: this, __source: { fileName: _jsxFileName, lineNumber: 1306, columnNumber: 9 } }), this._renderNode(fixedHeader, 'header'), optionsContainer, this._renderNode(fixedFooter, 'footer')), this._renderTopArrow()); } } exports.DropdownLayout = DropdownLayout; DropdownLayout.defaultProps = { options: [], tabIndex: 0, maxHeightPixels: 260, closeOnSelect: true, itemHeight: 'small', selectedHighlight: true, inContainer: false, infiniteScroll: false, loadMore: undefined, hasMore: false, markedOption: false, overflow: 'auto', listType: _DropdownLayout2.ListType.select, scrollbar: 'fixed' }; DropdownLayout.displayName = 'DropdownLayout'; DropdownLayout.NONE_SELECTED_ID = _DropdownLayout2.NOT_HOVERED_INDEX; var _default = exports.default = (0, _withOverlayScrollbar.withOverlayScrollbar)(DropdownLayout); //# sourceMappingURL=DropdownLayout.js.map