You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
8.7 KiB
243 lines
8.7 KiB
"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; |