"use strict"; exports.__esModule = true; exports.default = void 0; var _querySelectorAll = _interopRequireDefault(require("dom-helpers/querySelectorAll")); var _addEventListener = _interopRequireDefault(require("dom-helpers/addEventListener")); var React = _interopRequireWildcard(require("react")); var _uncontrollable = require("uncontrollable"); var _usePrevious = _interopRequireDefault(require("@restart/hooks/usePrevious")); var _useForceUpdate = _interopRequireDefault(require("@restart/hooks/useForceUpdate")); var _useEventListener = _interopRequireDefault(require("@restart/hooks/useEventListener")); var _useEventCallback = _interopRequireDefault(require("@restart/hooks/useEventCallback")); var _DropdownContext = _interopRequireDefault(require("./DropdownContext")); var _DropdownMenu = _interopRequireDefault(require("./DropdownMenu")); var _DropdownToggle = _interopRequireWildcard(require("./DropdownToggle")); var _DropdownItem = _interopRequireDefault(require("./DropdownItem")); var _SelectableContext = _interopRequireDefault(require("./SelectableContext")); var _DataKey = require("./DataKey"); var _useWindow = _interopRequireDefault(require("./useWindow")); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (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 _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function useRefWithUpdate() { const forceUpdate = (0, _useForceUpdate.default)(); const ref = (0, React.useRef)(null); const attachRef = (0, React.useCallback)(element => { ref.current = element; // ensure that a menu set triggers an update for consumers forceUpdate(); }, [forceUpdate]); return [ref, attachRef]; } /** * @displayName Dropdown * @public */ function Dropdown({ defaultShow, show: rawShow, onSelect, onToggle: rawOnToggle, itemSelector = `* [${(0, _DataKey.dataAttr)('dropdown-item')}]`, focusFirstItemOnShow, placement = 'bottom-start', children }) { const window = (0, _useWindow.default)(); const [show, onToggle] = (0, _uncontrollable.useUncontrolledProp)(rawShow, defaultShow, rawOnToggle); // We use normal refs instead of useCallbackRef in order to populate the // the value as quickly as possible, otherwise the effect to focus the element // may run before the state value is set const [menuRef, setMenu] = useRefWithUpdate(); const menuElement = menuRef.current; const [toggleRef, setToggle] = useRefWithUpdate(); const toggleElement = toggleRef.current; const lastShow = (0, _usePrevious.default)(show); const lastSourceEvent = (0, React.useRef)(null); const focusInDropdown = (0, React.useRef)(false); const onSelectCtx = (0, React.useContext)(_SelectableContext.default); const toggle = (0, React.useCallback)((nextShow, event, source = event == null ? void 0 : event.type) => { onToggle(nextShow, { originalEvent: event, source }); }, [onToggle]); const handleSelect = (0, _useEventCallback.default)((key, event) => { onSelect == null ? void 0 : onSelect(key, event); toggle(false, event, 'select'); if (!event.isPropagationStopped()) { onSelectCtx == null ? void 0 : onSelectCtx(key, event); } }); const context = (0, React.useMemo)(() => ({ toggle, placement, show, menuElement, toggleElement, setMenu, setToggle }), [toggle, placement, show, menuElement, toggleElement, setMenu, setToggle]); if (menuElement && lastShow && !show) { focusInDropdown.current = menuElement.contains(menuElement.ownerDocument.activeElement); } const focusToggle = (0, _useEventCallback.default)(() => { if (toggleElement && toggleElement.focus) { toggleElement.focus(); } }); const maybeFocusFirst = (0, _useEventCallback.default)(() => { const type = lastSourceEvent.current; let focusType = focusFirstItemOnShow; if (focusType == null) { focusType = menuRef.current && (0, _DropdownToggle.isRoleMenu)(menuRef.current) ? 'keyboard' : false; } if (focusType === false || focusType === 'keyboard' && !/^key.+$/.test(type)) { return; } const first = (0, _querySelectorAll.default)(menuRef.current, itemSelector)[0]; if (first && first.focus) first.focus(); }); (0, React.useEffect)(() => { if (show) maybeFocusFirst();else if (focusInDropdown.current) { focusInDropdown.current = false; focusToggle(); } // only `show` should be changing }, [show, focusInDropdown, focusToggle, maybeFocusFirst]); (0, React.useEffect)(() => { lastSourceEvent.current = null; }); const getNextFocusedChild = (current, offset) => { if (!menuRef.current) return null; const items = (0, _querySelectorAll.default)(menuRef.current, itemSelector); let index = items.indexOf(current) + offset; index = Math.max(0, Math.min(index, items.length)); return items[index]; }; (0, _useEventListener.default)((0, React.useCallback)(() => window.document, [window]), 'keydown', event => { var _menuRef$current, _toggleRef$current; const { key } = event; const target = event.target; const fromMenu = (_menuRef$current = menuRef.current) == null ? void 0 : _menuRef$current.contains(target); const fromToggle = (_toggleRef$current = toggleRef.current) == null ? void 0 : _toggleRef$current.contains(target); // Second only to https://github.com/twbs/bootstrap/blob/8cfbf6933b8a0146ac3fbc369f19e520bd1ebdac/js/src/dropdown.js#L400 // in inscrutability const isInput = /input|textarea/i.test(target.tagName); if (isInput && (key === ' ' || key !== 'Escape' && fromMenu || key === 'Escape' && target.type === 'search')) { return; } if (!fromMenu && !fromToggle) { return; } if (key === 'Tab' && (!menuRef.current || !show)) { return; } lastSourceEvent.current = event.type; const meta = { originalEvent: event, source: event.type }; switch (key) { case 'ArrowUp': { const next = getNextFocusedChild(target, -1); if (next && next.focus) next.focus(); event.preventDefault(); return; } case 'ArrowDown': event.preventDefault(); if (!show) { onToggle(true, meta); } else { const next = getNextFocusedChild(target, 1); if (next && next.focus) next.focus(); } return; case 'Tab': // on keydown the target is the element being tabbed FROM, we need that // to know if this event is relevant to this dropdown (e.g. in this menu). // On `keyup` the target is the element being tagged TO which we use to check // if focus has left the menu (0, _addEventListener.default)(target.ownerDocument, 'keyup', e => { var _menuRef$current2; if (e.key === 'Tab' && !e.target || !((_menuRef$current2 = menuRef.current) != null && _menuRef$current2.contains(e.target))) { onToggle(false, meta); } }, { once: true }); break; case 'Escape': if (key === 'Escape') { event.preventDefault(); event.stopPropagation(); } onToggle(false, meta); break; default: } }); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SelectableContext.default.Provider, { value: handleSelect, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DropdownContext.default.Provider, { value: context, children: children }) }); } Dropdown.displayName = 'Dropdown'; Dropdown.Menu = _DropdownMenu.default; Dropdown.Toggle = _DropdownToggle.default; Dropdown.Item = _DropdownItem.default; var _default = Dropdown; exports.default = _default;