blob: 1adc8a4fcab859e267cbc2ce1cec2230faca745b [file] [edit]
/*!
* Bootstrap menu.js v6.0.0-alpha1 (https://getbootstrap.com/)
* Copyright 2011-2026 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom";
import BaseComponent from "./base-component.js";
import EventHandler from "./dom/event-handler.js";
import Manipulator from "./dom/manipulator.js";
import SelectorEngine from "./dom/selector-engine.js";
import { execute, getElement, getNextActiveElement, getTransitionDurationFromElement, isDisabled, isElement, isRTL, isVisible, noop } from "./util/index.js";
import { createBreakpointListeners, disposeBreakpointListeners, getResponsivePlacement, parseResponsivePlacement, toFloatingOffset } from "./util/floating-ui.js";
//#region js/src/menu.ts
/**
* --------------------------------------------------------------------------
* Bootstrap menu.ts
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = "menu";
const EVENT_KEY = `.bs.menu`;
const DATA_API_KEY = ".data-api";
const ESCAPE_KEY = "Escape";
const TAB_KEY = "Tab";
const ARROW_UP_KEY = "ArrowUp";
const ARROW_DOWN_KEY = "ArrowDown";
const ARROW_LEFT_KEY = "ArrowLeft";
const ARROW_RIGHT_KEY = "ArrowRight";
const HOME_KEY = "Home";
const END_KEY = "End";
const ENTER_KEY = "Enter";
const SPACE_KEY = " ";
const RIGHT_MOUSE_BUTTON = 2;
const SUBMENU_CLOSE_DELAY = 100;
const EVENT_HIDE = `hide${EVENT_KEY}`;
const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
const EVENT_SHOW = `show${EVENT_KEY}`;
const EVENT_SHOWN = `shown${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_SHOW = "show";
const SELECTOR_DATA_TOGGLE = "[data-bs-toggle=\"menu\"]:not(.disabled):not(:disabled)";
const SELECTOR_MENU = ".menu";
const SELECTOR_SUBMENU = ".submenu";
const SELECTOR_SUBMENU_TOGGLE = ".submenu > .menu-item";
const SELECTOR_NAVBAR_NAV = ".navbar-nav";
const SELECTOR_VISIBLE_ITEMS = ".menu-item:not(.disabled):not(:disabled)";
const DEFAULT_PLACEMENT = "bottom-start";
const SUBMENU_PLACEMENT = "end-start";
const resolveLogicalPlacement = (placement) => {
if (isRTL()) return placement.replace(/^start(?=-|$)/, "right").replace(/^end(?=-|$)/, "left");
return placement.replace(/^start(?=-|$)/, "left").replace(/^end(?=-|$)/, "right");
};
const triangleSign = (p1, p2, p3) => (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
const Default = {
autoClose: true,
boundary: "clippingParents",
container: false,
display: "dynamic",
offset: [0, 2],
floatingConfig: null,
menu: null,
placement: DEFAULT_PLACEMENT,
reference: "toggle",
strategy: "absolute",
submenuTrigger: "both",
submenuDelay: SUBMENU_CLOSE_DELAY
};
const DefaultType = {
autoClose: "(boolean|string)",
boundary: "(string|element)",
container: "(string|element|boolean)",
display: "string",
offset: "(array|string|function)",
floatingConfig: "(null|object|function)",
menu: "(null|element)",
placement: "string",
reference: "(string|element|object)",
strategy: "string",
submenuTrigger: "string",
submenuDelay: "number"
};
/**
* Class definition
*/
var Menu = class Menu extends BaseComponent {
static _openInstances = /* @__PURE__ */ new Set();
constructor(element, config) {
if (typeof computePosition === "undefined") throw new TypeError("Bootstrap's menus require Floating UI (https://floating-ui.com)");
super(element, config);
this._floatingCleanup = null;
this._mediaQueryListeners = [];
this._responsivePlacements = null;
this._parent = this._element.parentNode;
this._openSubmenus = /* @__PURE__ */ new Map();
this._submenuCloseTimeouts = /* @__PURE__ */ new Map();
this._hoverIntentData = null;
this._menu = this._config.menu || this._findMenu();
if (!this._config.menu && this._menu) this._parent = this._findWrapper(this._menu);
this._isSubmenu = this._parent.classList?.contains("submenu");
this._menuOriginalParent = this._menu?.parentNode;
this._parseResponsivePlacements();
this._setupSubmenuListeners();
this._initSubmenuTriggers();
}
static get Default() {
return Default;
}
static get DefaultType() {
return DefaultType;
}
static get NAME() {
return NAME;
}
toggle() {
return this._isShown() ? this.hide() : this.show();
}
async show() {
if (isDisabled(this._element) || this._isShown()) return;
const relatedTarget = { relatedTarget: this._element };
if (EventHandler.trigger(this._element, EVENT_SHOW, relatedTarget).defaultPrevented) return;
this._moveMenuToContainer();
this._createFloating();
if ("ontouchstart" in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) for (const element of document.body.children) EventHandler.on(element, "mouseover", noop);
this._element.focus({ focusVisible: false });
this._element.setAttribute("aria-expanded", "true");
this._menu.classList.add(CLASS_NAME_SHOW);
this._element.classList.add(CLASS_NAME_SHOW);
if (this._parent) this._parent.classList.add(CLASS_NAME_SHOW);
Menu._openInstances.add(this);
await this._queueCallback(() => {
if (this._isShown()) EventHandler.trigger(this._element, EVENT_SHOWN, relatedTarget);
}, this._menu, this._isAnimated());
}
async hide() {
if (isDisabled(this._element) || !this._isShown()) return;
const relatedTarget = { relatedTarget: this._element };
await this._completeHide(relatedTarget);
}
dispose() {
this._disposeFloating();
this._restoreMenuToOriginalParent();
this._disposeMediaQueryListeners();
this._closeAllSubmenus();
this._clearAllSubmenuTimeouts();
Menu._openInstances.delete(this);
super.dispose();
}
update() {
if (this._floatingCleanup) this._updateFloatingPosition();
}
_findMenu() {
const wrapper = SelectorEngine.closest(this._element, `:has(${SELECTOR_MENU})`);
return SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, wrapper || this._parent);
}
_findWrapper(menu) {
let wrapper = this._element.parentNode;
while (wrapper instanceof Element && !wrapper.contains(menu)) wrapper = wrapper.parentNode;
return wrapper instanceof Element ? wrapper : this._element.parentNode;
}
async _completeHide(relatedTarget) {
if (EventHandler.trigger(this._element, EVENT_HIDE, relatedTarget).defaultPrevented) return;
this._closeAllSubmenus();
if ("ontouchstart" in document.documentElement) for (const element of document.body.children) EventHandler.off(element, "mouseover", noop);
this._menu.classList.remove(CLASS_NAME_SHOW);
this._element.classList.remove(CLASS_NAME_SHOW);
if (this._parent) this._parent.classList.remove(CLASS_NAME_SHOW);
this._element.setAttribute("aria-expanded", "false");
Menu._openInstances.delete(this);
await this._queueCallback(() => {
if (this._isShown()) return;
this._disposeFloating();
this._restoreMenuToOriginalParent();
Manipulator.removeDataAttribute(this._menu, "placement");
Manipulator.removeDataAttribute(this._menu, "display");
EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget);
}, this._menu, this._isAnimated());
}
_getConfig(config) {
config = super._getConfig(config);
if (typeof config.reference === "object" && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== "function") throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);
return config;
}
_createFloating() {
if (this._config.display === "static") {
Manipulator.setDataAttribute(this._menu, "display", "static");
return;
}
let referenceElement = this._element;
if (this._config.reference === "parent") referenceElement = this._parent;
else if (isElement(this._config.reference)) referenceElement = getElement(this._config.reference);
else if (typeof this._config.reference === "object") referenceElement = this._config.reference;
this._updateFloatingPosition(referenceElement);
this._floatingCleanup = autoUpdate(referenceElement, this._menu, () => this._updateFloatingPosition(referenceElement));
}
async _updateFloatingPosition(referenceElement = null) {
if (!this._menu) return;
if (!referenceElement) if (this._config.reference === "parent") referenceElement = this._parent;
else if (isElement(this._config.reference)) referenceElement = getElement(this._config.reference);
else if (typeof this._config.reference === "object") referenceElement = this._config.reference;
else referenceElement = this._element;
const placement = this._getPlacement();
const middleware = this._getFloatingMiddleware();
const floatingConfig = this._getFloatingConfig(placement, middleware);
await this._applyFloatingPosition(referenceElement, this._menu, floatingConfig.placement, floatingConfig.middleware, floatingConfig.strategy);
}
_isShown() {
return this._menu.classList.contains(CLASS_NAME_SHOW);
}
_isAnimated() {
return getTransitionDurationFromElement(this._menu) > 0;
}
_getPlacement() {
const placement = this._responsivePlacements ? getResponsivePlacement(this._responsivePlacements, DEFAULT_PLACEMENT) : this._config.placement;
return resolveLogicalPlacement(placement);
}
_parseResponsivePlacements() {
this._responsivePlacements = parseResponsivePlacement(this._config.placement, DEFAULT_PLACEMENT);
if (this._responsivePlacements) this._setupMediaQueryListeners();
}
_setupMediaQueryListeners() {
this._disposeMediaQueryListeners();
this._mediaQueryListeners = createBreakpointListeners(() => {
if (this._isShown()) this._updateFloatingPosition();
});
}
_disposeMediaQueryListeners() {
disposeBreakpointListeners(this._mediaQueryListeners);
this._mediaQueryListeners = [];
}
_getOffset() {
const { offset: offsetConfig } = this._config;
if (typeof offsetConfig === "string") return offsetConfig.split(",").map((value) => Number.parseInt(value, 10));
if (typeof offsetConfig === "function") return ({ placement, rects }) => {
return toFloatingOffset(offsetConfig({
placement,
reference: rects.reference,
floating: rects.floating
}, this._element));
};
return offsetConfig;
}
_getFloatingMiddleware() {
const offsetValue = this._getOffset();
return [
offset(typeof offsetValue === "function" ? offsetValue : toFloatingOffset(offsetValue)),
flip({
fallbackPlacements: this._getFallbackPlacements(),
fallbackStrategy: "initialPlacement"
}),
shift({ boundary: this._config.boundary === "clippingParents" ? "clippingAncestors" : this._config.boundary })
];
}
_getFallbackPlacements() {
return {
bottom: [
"top",
"bottom-start",
"bottom-end",
"top-start",
"top-end"
],
"bottom-start": [
"top-start",
"bottom-end",
"top-end"
],
"bottom-end": [
"top-end",
"bottom-start",
"top-start"
],
top: [
"bottom",
"top-start",
"top-end",
"bottom-start",
"bottom-end"
],
"top-start": [
"bottom-start",
"top-end",
"bottom-end"
],
"top-end": [
"bottom-end",
"top-start",
"bottom-start"
],
right: [
"left",
"right-start",
"right-end",
"left-start",
"left-end"
],
"right-start": [
"left-start",
"right-end",
"left-end",
"top-start",
"bottom-start"
],
"right-end": [
"left-end",
"right-start",
"left-start",
"top-end",
"bottom-end"
],
left: [
"right",
"left-start",
"left-end",
"right-start",
"right-end"
],
"left-start": [
"right-start",
"left-end",
"right-end",
"top-start",
"bottom-start"
],
"left-end": [
"right-end",
"left-start",
"right-start",
"top-end",
"bottom-end"
]
}[this._getPlacement()] || [
"top",
"bottom",
"right",
"left"
];
}
_getFloatingConfig(placement, middleware) {
const defaultConfig = {
placement,
middleware,
strategy: this._config.strategy
};
return {
...defaultConfig,
...execute(this._config.floatingConfig, [void 0, defaultConfig])
};
}
_disposeFloating() {
if (this._floatingCleanup) {
this._floatingCleanup();
this._floatingCleanup = null;
}
}
_getContainer() {
const { container } = this._config;
if (container === false) return null;
return container === true ? document.body : getElement(container);
}
_moveMenuToContainer() {
const container = this._getContainer();
if (!container || !this._menu) return;
if (this._menu.parentNode !== container) container.append(this._menu);
}
_restoreMenuToOriginalParent() {
if (!this._menuOriginalParent || !this._menu) return;
if (this._menu.parentNode !== this._menuOriginalParent) this._menuOriginalParent.append(this._menu);
}
async _applyFloatingPosition(reference, floating, placement, middleware, strategy = "absolute") {
if (!floating.isConnected) return null;
const { x, y, placement: finalPlacement } = await computePosition(reference, floating, {
placement,
middleware,
strategy
});
if (!floating.isConnected) return null;
Object.assign(floating.style, {
position: strategy,
left: `${x}px`,
top: `${y}px`,
margin: "0"
});
Manipulator.setDataAttribute(floating, "placement", finalPlacement);
return finalPlacement;
}
_setupSubmenuListeners() {
if (this._config.submenuTrigger === "hover" || this._config.submenuTrigger === "both") {
EventHandler.on(this._menu, "mouseenter", SELECTOR_SUBMENU_TOGGLE, (event) => {
this._onSubmenuTriggerEnter(event);
});
EventHandler.on(this._menu, "mouseleave", SELECTOR_SUBMENU, (event) => {
this._onSubmenuLeave(event);
});
EventHandler.on(this._menu, "mousemove", (event) => {
this._trackMousePosition(event);
});
}
if (this._config.submenuTrigger === "click" || this._config.submenuTrigger === "both") EventHandler.on(this._menu, "click", SELECTOR_SUBMENU_TOGGLE, (event) => {
this._onSubmenuTriggerClick(event);
});
}
_onSubmenuTriggerEnter(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) return;
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) return;
this._cancelSubmenuCloseTimeout(submenu);
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(trigger, submenu, submenuWrapper);
}
_onSubmenuLeave(event) {
const submenuWrapper = event.target.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu || !this._openSubmenus.has(submenu)) return;
if (this._isMovingTowardSubmenu(event, submenu)) return;
this._scheduleSubmenuClose(submenu, submenuWrapper);
}
_onSubmenuTriggerClick(event) {
const trigger = event.target.closest(SELECTOR_SUBMENU_TOGGLE);
if (!trigger) return;
event.preventDefault();
event.stopPropagation();
const submenuWrapper = trigger.closest(SELECTOR_SUBMENU);
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (!submenu) return;
if (this._openSubmenus.has(submenu)) this._closeSubmenu(submenu, submenuWrapper);
else {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(trigger, submenu, submenuWrapper);
}
}
_initSubmenuTriggers() {
if (!this._menu) return;
for (const trigger of SelectorEngine.find(SELECTOR_SUBMENU_TOGGLE, this._menu)) {
if (!SelectorEngine.findOne(SELECTOR_MENU, trigger.parentElement)) continue;
trigger.setAttribute("aria-haspopup", "true");
if (!trigger.hasAttribute("aria-expanded")) trigger.setAttribute("aria-expanded", "false");
}
}
_openSubmenu(trigger, submenu, submenuWrapper) {
if (this._openSubmenus.has(submenu)) return;
trigger.setAttribute("aria-expanded", "true");
trigger.setAttribute("aria-haspopup", "true");
submenu.style.opacity = "0";
submenu.classList.add(CLASS_NAME_SHOW);
submenuWrapper.classList.add(CLASS_NAME_SHOW);
const cleanup = this._createSubmenuFloating(trigger, submenu, submenuWrapper);
this._openSubmenus.set(submenu, cleanup);
EventHandler.on(submenu, "mouseenter", () => {
this._cancelSubmenuCloseTimeout(submenu);
});
}
_closeSubmenu(submenu, submenuWrapper) {
if (!this._openSubmenus.has(submenu)) return;
const nestedSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, submenu);
for (const nested of nestedSubmenus) {
const nestedWrapper = nested.closest(SELECTOR_SUBMENU);
this._closeSubmenu(nested, nestedWrapper);
}
const trigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, submenuWrapper);
const cleanup = this._openSubmenus.get(submenu);
if (cleanup) cleanup();
this._openSubmenus.delete(submenu);
EventHandler.off(submenu, "mouseenter");
if (trigger) trigger.setAttribute("aria-expanded", "false");
submenu.classList.remove(CLASS_NAME_SHOW);
submenuWrapper.classList.remove(CLASS_NAME_SHOW);
submenu.style.opacity = "";
}
_closeAllSubmenus() {
for (const [submenu] of this._openSubmenus) {
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
this._closeSubmenu(submenu, submenuWrapper);
}
}
_closeSiblingSubmenus(currentSubmenuWrapper) {
const parent = currentSubmenuWrapper.parentNode;
const siblingSubmenus = SelectorEngine.find(`${SELECTOR_SUBMENU} > ${SELECTOR_MENU}.${CLASS_NAME_SHOW}`, parent);
for (const siblingMenu of siblingSubmenus) {
const siblingWrapper = siblingMenu.closest(SELECTOR_SUBMENU);
if (siblingWrapper !== currentSubmenuWrapper) this._closeSubmenu(siblingMenu, siblingWrapper);
}
}
_createSubmenuFloating(trigger, submenu, submenuWrapper) {
const referenceElement = submenuWrapper;
const placement = resolveLogicalPlacement(SUBMENU_PLACEMENT);
const middleware = [
offset({
mainAxis: 0,
crossAxis: -4
}),
flip({ fallbackPlacements: [
resolveLogicalPlacement("start-start"),
resolveLogicalPlacement("end-end"),
resolveLogicalPlacement("start-end")
] }),
shift({ padding: 8 })
];
const updatePosition = () => this._applyFloatingPosition(referenceElement, submenu, placement, middleware).then((finalPlacement) => {
submenu.style.opacity = "";
return finalPlacement;
});
updatePosition();
return autoUpdate(referenceElement, submenu, updatePosition);
}
_scheduleSubmenuClose(submenu, submenuWrapper) {
this._cancelSubmenuCloseTimeout(submenu);
const timeoutId = setTimeout(() => {
this._closeSubmenu(submenu, submenuWrapper);
this._submenuCloseTimeouts.delete(submenu);
}, this._config.submenuDelay);
this._submenuCloseTimeouts.set(submenu, timeoutId);
}
_cancelSubmenuCloseTimeout(submenu) {
const timeoutId = this._submenuCloseTimeouts.get(submenu);
if (timeoutId) {
clearTimeout(timeoutId);
this._submenuCloseTimeouts.delete(submenu);
}
}
_clearAllSubmenuTimeouts() {
for (const timeoutId of this._submenuCloseTimeouts.values()) clearTimeout(timeoutId);
this._submenuCloseTimeouts.clear();
}
_trackMousePosition(event) {
this._hoverIntentData = {
x: event.clientX,
y: event.clientY,
timestamp: Date.now()
};
}
_isMovingTowardSubmenu(event, submenu) {
if (!this._hoverIntentData) return false;
const submenuRect = submenu.getBoundingClientRect();
const currentPos = {
x: event.clientX,
y: event.clientY
};
const lastPos = {
x: this._hoverIntentData.x,
y: this._hoverIntentData.y
};
const targetX = isRTL() ? submenuRect.right : submenuRect.left;
const topCorner = {
x: targetX,
y: submenuRect.top
};
const bottomCorner = {
x: targetX,
y: submenuRect.bottom
};
return this._pointInTriangle(currentPos, lastPos, topCorner, bottomCorner);
}
_pointInTriangle(point, v1, v2, v3) {
const d1 = triangleSign(point, v1, v2);
const d2 = triangleSign(point, v2, v3);
const d3 = triangleSign(point, v3, v1);
return !((d1 < 0 || d2 < 0 || d3 < 0) && (d1 > 0 || d2 > 0 || d3 > 0));
}
_getItemsInMenu(menu) {
return SelectorEngine.find(`:scope > ${SELECTOR_VISIBLE_ITEMS}, :scope > ${SELECTOR_SUBMENU} > ${SELECTOR_VISIBLE_ITEMS}`, menu).filter((element) => isVisible(element));
}
_selectMenuItem({ key, target }) {
const currentMenu = target.closest(SELECTOR_MENU) || this._menu;
const items = this._getItemsInMenu(currentMenu);
if (!items.length) return;
const nextItem = getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target));
nextItem.focus();
this._closeUnrelatedSubmenus(currentMenu, nextItem);
}
_closeUnrelatedSubmenus(currentMenu, focusedElement) {
for (const [submenu] of this._openSubmenus) {
const submenuWrapper = submenu.closest(SELECTOR_SUBMENU);
if (!submenuWrapper || submenuWrapper.parentElement !== currentMenu || submenuWrapper.contains(focusedElement)) continue;
this._closeSubmenu(submenu, submenuWrapper);
}
}
_handleSubmenuKeydown(event) {
const { key, target } = event;
const isRtl = isRTL();
const enterKey = isRtl ? ARROW_LEFT_KEY : ARROW_RIGHT_KEY;
const exitKey = isRtl ? ARROW_RIGHT_KEY : ARROW_LEFT_KEY;
const submenuWrapper = target.closest(SELECTOR_SUBMENU);
const isSubmenuToggle = target.matches(SELECTOR_SUBMENU_TOGGLE);
if ((key === ENTER_KEY || key === SPACE_KEY || key === enterKey) && submenuWrapper && isSubmenuToggle) {
event.preventDefault();
event.stopPropagation();
const submenu = SelectorEngine.findOne(SELECTOR_MENU, submenuWrapper);
if (submenu) {
this._closeSiblingSubmenus(submenuWrapper);
this._openSubmenu(target, submenu, submenuWrapper);
requestAnimationFrame(() => {
const firstItem = SelectorEngine.findOne(SELECTOR_VISIBLE_ITEMS, submenu);
if (firstItem) firstItem.focus();
});
}
return true;
}
if (key === exitKey) {
const currentMenu = target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper) {
event.preventDefault();
event.stopPropagation();
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
this._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) parentTrigger.focus();
return true;
}
}
if (key === HOME_KEY || key === END_KEY) {
event.preventDefault();
event.stopPropagation();
const currentMenu = target.closest(SELECTOR_MENU);
const items = this._getItemsInMenu(currentMenu);
if (items.length) {
const targetItem = key === HOME_KEY ? items[0] : items.at(-1);
targetItem.focus();
this._closeUnrelatedSubmenus(currentMenu, targetItem);
}
return true;
}
return false;
}
static clearMenus(event) {
if (event.button === RIGHT_MOUSE_BUTTON || event.type === "keyup" && event.key !== TAB_KEY) return;
for (const instance of Menu._openInstances) {
if (instance._config.autoClose === false) continue;
const composedPath = event.composedPath();
const isMenuTarget = composedPath.includes(instance._menu);
if (composedPath.includes(instance._element) || instance._config.autoClose === "inside" && !isMenuTarget || instance._config.autoClose === "outside" && isMenuTarget) continue;
const formAncestor = event.target.closest?.("form");
const isInsideMenuForm = Boolean(formAncestor) && instance._menu.contains(formAncestor);
if (instance._menu.contains(event.target) && (event.type === "keyup" && event.key === TAB_KEY || /input|select|option|textarea|form/i.test(event.target.tagName) || isInsideMenuForm)) continue;
const relatedTarget = { relatedTarget: instance._element };
if (event.type === "click") relatedTarget.clickEvent = event;
instance._completeHide(relatedTarget);
}
}
static _getToggleFromKeydownContext(element, event) {
if (element.matches(SELECTOR_DATA_TOGGLE)) return element;
for (const instance of Menu._openInstances) if (instance._element === event.target || instance._menu?.contains(event.target)) return instance._element;
return SelectorEngine.prev(element, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(element, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, event.delegateTarget.parentNode);
}
static dataApiKeydownHandler(event) {
const isInput = /input|textarea/i.test(event.target.tagName) || event.target.isContentEditable;
const isEscapeEvent = event.key === ESCAPE_KEY;
const isUpOrDownEvent = [ARROW_UP_KEY, ARROW_DOWN_KEY].includes(event.key);
const isLeftOrRightEvent = [ARROW_LEFT_KEY, ARROW_RIGHT_KEY].includes(event.key);
const isHomeOrEndEvent = [HOME_KEY, END_KEY].includes(event.key);
const isEnterOrSpaceEvent = [ENTER_KEY, SPACE_KEY].includes(event.key);
const isSubmenuTrigger = event.target.matches(SELECTOR_SUBMENU_TOGGLE);
if (!isUpOrDownEvent && !isEscapeEvent && !isLeftOrRightEvent && !isHomeOrEndEvent && !(isEnterOrSpaceEvent && isSubmenuTrigger)) return;
if (isInput && !isEscapeEvent) return;
const getToggleButton = Menu._getToggleFromKeydownContext(this, event);
if (!getToggleButton) return;
const instance = Menu.getOrCreateInstance(getToggleButton);
if ((isLeftOrRightEvent || isHomeOrEndEvent || isEnterOrSpaceEvent && isSubmenuTrigger) && instance._handleSubmenuKeydown(event)) return;
if (isUpOrDownEvent) {
event.preventDefault();
event.stopPropagation();
instance.show();
instance._selectMenuItem(event);
return;
}
if (isEscapeEvent && instance._isShown()) {
event.preventDefault();
event.stopPropagation();
const currentMenu = event.target.closest(SELECTOR_MENU);
const parentSubmenuWrapper = currentMenu?.closest(SELECTOR_SUBMENU);
if (parentSubmenuWrapper && instance._openSubmenus.size > 0) {
const parentTrigger = SelectorEngine.findOne(SELECTOR_SUBMENU_TOGGLE, parentSubmenuWrapper);
instance._closeSubmenu(currentMenu, parentSubmenuWrapper);
if (parentTrigger) parentTrigger.focus();
return;
}
instance.hide();
getToggleButton.focus();
}
}
};
/**
* Data API implementation
*/
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE, Menu.dataApiKeydownHandler);
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Menu.dataApiKeydownHandler);
EventHandler.on(document, EVENT_CLICK_DATA_API, Menu.clearMenus);
EventHandler.on(document, EVENT_KEYUP_DATA_API, Menu.clearMenus);
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function(event) {
event.preventDefault();
Menu.getOrCreateInstance(this).toggle();
});
//#endregion
export { Menu as default };
//# sourceMappingURL=menu.js.map