@spectrum-web-components/menu 1.12.0-testing.20260223092154 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Menu.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["Menu.ts"],
4
- "sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n CSSResultArray,\n html,\n PropertyValues,\n SizedMixin,\n SpectrumElement,\n TemplateResult,\n} from '@spectrum-web-components/base';\nimport {\n property,\n query,\n} from '@spectrum-web-components/base/src/decorators.js';\nimport type { Overlay } from '@spectrum-web-components/overlay';\nimport { RovingTabindexController } from '@spectrum-web-components/reactive-controllers/src/RovingTabindex.js';\n\nimport menuStyles from './menu.css.js';\nimport type {\n MenuItemAddedOrUpdatedEvent,\n MenuItemKeydownEvent,\n} from './MenuItem.js';\nimport { MenuItem } from './MenuItem.js';\n\nexport interface MenuChildItem {\n menuItem: MenuItem;\n managed: boolean;\n active: boolean;\n focusable: boolean;\n focusRoot: Menu;\n}\n\ntype SelectsType = 'none' | 'ignore' | 'inherit' | 'multiple' | 'single';\ntype RoleType = 'group' | 'menu' | 'listbox' | 'none';\n\n/**\n * Spectrum Menu Component\n *\n * @element sp-menu\n *\n * @slot - menu items to be listed in the menu\n * @fires change - Announces that the `value` of the element has changed\n * @attr selects - whether the element has a specific selection algorithm that it applies\n * to its item descendants. `single` allows only one descendent to be selected at a time.\n * `multiple` allows many descendants to be selected. `inherit` will be applied dynamically\n * when an ancestor of this element is actively managing the selection of its descendents.\n * When the `selects` attribute is not present a `value` will not be maintained and the Menu\n * Item children of this Menu will not have their `selected` state managed.\n */\nexport class Menu extends SizedMixin(SpectrumElement, { noDefaultSize: true }) {\n public static override get styles(): CSSResultArray {\n return [menuStyles];\n }\n\n static override shadowRootOptions = {\n ...SpectrumElement.shadowRootOptions,\n delegatesFocus: true,\n };\n\n private get isSubmenu(): boolean {\n return this.slot === 'submenu';\n }\n\n protected rovingTabindexController?: RovingTabindexController<MenuItem>;\n\n /**\n * iPad scroll detection properties\n *\n * This feature prevents menu item selection during iPad scrolling to avoid\n * accidental selections when users are trying to scroll through a long menu.\n *\n * How it works:\n * 1. On touchstart: Record initial Y position and timestamp\n * 2. On touchmove: Calculate vertical movement and time elapsed\n * 3. If movement > threshold AND time < threshold: Mark as scrolling\n * 4. On touchend: Reset scrolling state after a delay\n * 5. During selection: Prevent selection if scrolling is detected\n *\n * This prevents the common iPad issue where users accidentally select menu\n * items while trying to scroll through the menu content.\n *\n * Threshold Values:\n * - Movement threshold: 10px (consistent with Card component click vs. drag detection)\n * - Time threshold: 300ms (consistent with longpress duration across the design system)\n * - Reset delay: 100ms (allows final touch events to be processed)\n *\n * These values are carefully chosen to balance preventing accidental triggers\n * while allowing intentional scroll gestures. They represent a common UX pattern\n * in mobile interfaces and are consistent with other components in the design system.\n */\n private touchStartY: number | undefined = undefined;\n private touchStartTime: number | undefined = undefined;\n private isCurrentlyScrolling = false;\n\n /**\n * Minimum vertical movement (in pixels) required to trigger scrolling detection.\n *\n * This threshold is consistent with other components in the design system:\n * - Card component uses 10px for click vs. drag detection\n * - Menu component uses 10px for scroll vs. selection detection\n *\n * The 10px threshold is carefully chosen to:\n * - Allow for natural finger tremor and accidental touches\n * - Distinguish between intentional scroll gestures and taps\n * - Provide consistent behavior across the platform\n *\n * @see {@link packages/card/src/Card.ts} for similar threshold usage\n */\n private scrollThreshold = 10; // pixels\n\n /**\n * Maximum time (in milliseconds) for a movement to be considered scrolling.\n *\n * This threshold is consistent with other timing values in the design system:\n * - Longpress duration: 300ms (ActionButton, LongpressController)\n * - Scroll detection: 300ms (Menu component)\n *\n * Quick movements within this timeframe are likely intentional scrolls,\n * while slower movements are more likely taps or selections.\n *\n * @see {@link packages/action-button/src/ActionButton.ts} for longpress duration\n * @see {@link packages/overlay/src/LongpressController.ts} for longpress duration\n */\n private scrollTimeThreshold = 300; // milliseconds\n\n /**\n * Public getter for scrolling state\n * Returns true if the component is currently in a scrolling state\n */\n public get isScrolling(): boolean {\n return this.isCurrentlyScrolling;\n }\n\n public set isScrolling(value: boolean) {\n // For testing purposes, allow setting the scrolling state\n this.isCurrentlyScrolling = value;\n }\n\n /**\n * label of the menu\n */\n @property({ type: String, reflect: true })\n public label = '';\n\n /**\n * whether menu should be ignored by roving tabindex controller\n */\n @property({ type: Boolean, reflect: true })\n public ignore = false;\n\n /**\n * how the menu allows selection of its items:\n * - `undefined` (default): no selection is allowed\n * - `\"inherit\"`: the selection behavior is managed from an ancestor\n * - `\"single\"`: only one item can be selected at a time\n * - `\"multiple\"`: multiple items can be selected\n */\n @property({ type: String, reflect: true })\n public selects: undefined | 'inherit' | 'single' | 'multiple';\n\n /**\n * value of the selected item(s)\n */\n @property({ type: String })\n public value = '';\n\n // For the multiple select case, we'll join the value strings together\n // for the value property with this separator\n @property({ type: String, attribute: 'value-separator' })\n public valueSeparator = ',';\n\n /**\n * selected items values as string\n */\n @property({ attribute: false })\n public get selected(): string[] {\n return !this.selects ? [] : this._selected;\n }\n\n public set selected(selected: string[]) {\n if (selected === this.selected) {\n return;\n }\n const old = this.selected;\n this._selected = selected;\n this.selectedItems = [];\n this.selectedItemsMap.clear();\n this.childItems.forEach((item) => {\n if (this !== item.menuData.selectionRoot) {\n return;\n }\n item.selected = this.selected.includes(item.value);\n if (item.selected) {\n this.selectedItems.push(item);\n this.selectedItemsMap.set(item, true);\n }\n });\n this.requestUpdate('selected', old);\n }\n\n protected _selected = [] as string[];\n\n /**\n * array of selected menu items\n */\n @property({ attribute: false })\n public selectedItems = [] as MenuItem[];\n\n @query('slot:not([name])')\n public menuSlot!: HTMLSlotElement;\n\n private childItemSet = new Set<MenuItem>();\n public focusedItemIndex = 0;\n public focusInItemIndex = 0;\n\n /**\n * Whether to support the pointerdown-drag-pointerup selection strategy.\n * Defaults to false to prevent click/touch events from being captured\n * behind the menu tray in mobile environments (since the menu closes\n * immediately on pointerup).\n */\n\n public shouldSupportDragAndSelect = false;\n\n public get focusInItem(): MenuItem | undefined {\n return this.rovingTabindexController?.focusInElement;\n }\n\n protected get controlsRovingTabindex(): boolean {\n return true;\n }\n\n private selectedItemsMap = new Map<MenuItem, boolean>();\n\n /**\n * child items managed by menu\n */\n public get childItems(): MenuItem[] {\n if (!this.cachedChildItems) {\n this.cachedChildItems = this.updateCachedMenuItems();\n }\n return this.cachedChildItems;\n }\n\n private cachedChildItems: MenuItem[] | undefined;\n\n private updateCachedMenuItems(): MenuItem[] {\n if (!this.menuSlot) {\n return [];\n }\n const itemsList = [];\n const slottedElements = this.menuSlot.assignedElements({\n flatten: true,\n }) as HTMLElement[];\n // Recursively flatten <slot> and non-<sp-menu-item> elements assigned to the menu into a single array.\n for (const [i, slottedElement] of slottedElements.entries()) {\n if (this.childItemSet.has(slottedElement as MenuItem)) {\n // Assign <sp-menu-item> members of the array that are in this.childItemSet to this.chachedChildItems.\n itemsList.push(slottedElement as MenuItem);\n continue;\n }\n const isHTMLSlotElement = slottedElement.localName === 'slot';\n const flattenedChildren = isHTMLSlotElement\n ? (slottedElement as HTMLSlotElement).assignedElements({\n flatten: true,\n })\n : [...slottedElement.querySelectorAll(`:scope > *`)];\n slottedElements.splice(\n i,\n 1,\n slottedElement,\n ...(flattenedChildren as HTMLElement[])\n );\n }\n\n this.cachedChildItems = [...itemsList];\n this.rovingTabindexController?.clearElementCache();\n\n return this.cachedChildItems;\n }\n\n /**\n * Hide this getter from web-component-analyzer until\n * https://github.com/runem/web-component-analyzer/issues/131\n * has been addressed.\n *\n * @private\n */\n public get childRole(): string {\n if (this.resolvedRole === 'listbox') {\n return 'option';\n }\n switch (this.resolvedSelects) {\n case 'single':\n return 'menuitemradio';\n case 'multiple':\n return 'menuitemcheckbox';\n default:\n return 'menuitem';\n }\n }\n\n protected get ownRole(): string {\n return 'menu';\n }\n\n /**\n * menuitem role based on selection type\n */\n private resolvedSelects?: SelectsType;\n\n /**\n * menu role based on selection type\n */\n private resolvedRole?: RoleType;\n\n /**\n * When a descendant `<sp-menu-item>` element is added or updated it will dispatch\n * this event to announce its presence in the DOM. During the CAPTURE phase the first\n * Menu based element that the event encounters will manage the focus state of the\n * dispatching `<sp-menu-item>` element.\n *\n * @param event\n */\n private onFocusableItemAddedOrUpdated(\n event: MenuItemAddedOrUpdatedEvent\n ): void {\n event.menuCascade.set(this, {\n hadFocusRoot: !!event.item.menuData.focusRoot,\n ancestorWithSelects: event.currentAncestorWithSelects,\n });\n if (this.selects) {\n event.currentAncestorWithSelects = this;\n }\n event.item.menuData.focusRoot = event.item.menuData.focusRoot || this;\n }\n\n /**\n * When a descendant `<sp-menu-item>` element is added or updated it will dispatch\n * this event to announce its presence in the DOM. During the BUBBLE phase the first\n * Menu based element that the event encounters that does not inherit selection will\n * manage the selection state of the dispatching `<sp-menu-item>` element.\n *\n * @param event\n */\n private onSelectableItemAddedOrUpdated(\n event: MenuItemAddedOrUpdatedEvent\n ): void {\n const cascadeData = event.menuCascade.get(this);\n /* c8 ignore next 1 */\n if (!cascadeData) {\n return;\n }\n\n event.item.menuData.parentMenu = event.item.menuData.parentMenu || this;\n this.addChildItem(event.item);\n\n if (this.selects === 'inherit') {\n this.resolvedSelects = 'inherit';\n const ignoreMenu = event.currentAncestorWithSelects?.ignore;\n this.resolvedRole = ignoreMenu\n ? 'none'\n : ((event.currentAncestorWithSelects?.getAttribute('role') ||\n this.getAttribute('role') ||\n undefined) as RoleType);\n } else if (this.selects) {\n this.resolvedRole = this.ignore\n ? 'none'\n : ((this.getAttribute('role') || undefined) as RoleType);\n this.resolvedSelects = this.selects;\n } else {\n this.resolvedRole = this.ignore\n ? 'none'\n : ((this.getAttribute('role') || undefined) as RoleType);\n this.resolvedSelects = this.resolvedRole === 'none' ? 'ignore' : 'none';\n }\n\n if (this.resolvedRole === 'none') {\n return;\n }\n\n const selects =\n this.resolvedSelects === 'single' || this.resolvedSelects === 'multiple';\n event.item.menuData.cleanupSteps.push((item: MenuItem) =>\n this.removeChildItem(item)\n );\n if (\n (selects || (!this.selects && this.resolvedSelects !== 'ignore')) &&\n !event.item.menuData.selectionRoot\n ) {\n event.item.setRole(this.childRole);\n event.item.menuData.selectionRoot =\n event.item.menuData.selectionRoot || this;\n if (event.item.selected) {\n this.selectedItemsMap.set(event.item, true);\n this.selectedItems = [...this.selectedItems, event.item];\n this._selected = [...this.selected, event.item.value];\n this.value = this.selected.join(this.valueSeparator);\n }\n }\n }\n\n private addChildItem(item: MenuItem): void {\n this.childItemSet.add(item);\n this.handleItemsChanged();\n }\n\n private async removeChildItem(item: MenuItem): Promise<void> {\n if (item.focused || item.hasAttribute('focused') || item.active) {\n this._updateFocus = this.getNeighboringFocusableElement(item);\n }\n this.childItemSet.delete(item);\n this.cachedChildItems = undefined;\n }\n\n public constructor() {\n super();\n\n /**\n * only create an RTI if menu controls keyboard navigation and one does not already exist\n */\n if (!this.rovingTabindexController && this.controlsRovingTabindex) {\n this.rovingTabindexController = new RovingTabindexController<MenuItem>(\n this,\n {\n direction: 'vertical',\n focusInIndex: (elements: MenuItem[] | undefined) => {\n let firstEnabledIndex = -1;\n const firstSelectedIndex = elements?.findIndex((el, index) => {\n if (!elements[firstEnabledIndex] && !el.disabled) {\n firstEnabledIndex = index;\n }\n return el.selected && !el.disabled;\n });\n return elements &&\n firstSelectedIndex &&\n elements[firstSelectedIndex]\n ? firstSelectedIndex\n : firstEnabledIndex;\n },\n elements: () => this.childItems,\n isFocusableElement: this.isFocusableElement.bind(this),\n hostDelegatesFocus: true,\n stopKeyEventPropagation: true,\n }\n );\n }\n\n this.addEventListener(\n 'sp-menu-item-added-or-updated',\n this.onSelectableItemAddedOrUpdated\n );\n this.addEventListener(\n 'sp-menu-item-added-or-updated',\n this.onFocusableItemAddedOrUpdated,\n {\n capture: true,\n }\n );\n this.addEventListener('click', this.handleClick);\n this.addEventListener('touchend', this.handlePointerup);\n this.addEventListener('focusout', this.handleFocusout);\n this.addEventListener('sp-menu-item-keydown', this.handleKeydown);\n this.addEventListener('pointerup', this.handlePointerup);\n this.addEventListener('sp-opened', this.handleSubmenuOpened);\n this.addEventListener('sp-closed', this.handleSubmenuClosed);\n\n // Add touch event listeners for iPad scroll detection\n this.addEventListener('touchstart', this.handleTouchStart, {\n passive: true,\n });\n this.addEventListener('touchmove', this.handleTouchMove, {\n passive: true,\n });\n }\n\n /**\n * for picker elements, will set focus on first selected item\n */\n public focusOnFirstSelectedItem({ preventScroll }: FocusOptions = {}): void {\n if (!this.rovingTabindexController) {\n return;\n }\n const selectedItem = this.selectedItems.find((el) =>\n this.isFocusableElement(el)\n );\n if (!selectedItem) {\n this.focus({ preventScroll });\n return;\n }\n\n if (selectedItem && !preventScroll) {\n selectedItem.scrollIntoView({ block: 'nearest' });\n }\n this.rovingTabindexController?.focusOnItem(selectedItem);\n }\n\n public override focus({ preventScroll }: FocusOptions = {}): void {\n if (this.rovingTabindexController) {\n if (\n !this.childItems.length ||\n this.childItems.every((childItem) => childItem.disabled)\n ) {\n return;\n }\n if (\n this.childItems.some(\n (childItem) => childItem.menuData.focusRoot !== this\n )\n ) {\n super.focus({ preventScroll });\n return;\n }\n this.rovingTabindexController.focus({ preventScroll });\n }\n }\n\n /**\n * Handles touchstart events for iPad scroll detection.\n *\n * Records the initial touch position and timestamp to establish a baseline\n * for detecting scroll gestures. Only processes single-touch events to\n * avoid interference with multi-touch gestures.\n *\n * @param event - The TouchEvent from the touchstart event\n */\n private handleTouchStart(event: TouchEvent): void {\n if (event.touches.length === 1) {\n this.touchStartY = event.touches[0].clientY;\n this.touchStartTime = Date.now();\n this.isCurrentlyScrolling = false;\n }\n }\n\n /**\n * Handles touchmove events for iPad scroll detection.\n *\n * Calculates the vertical movement distance and time elapsed since touchstart.\n * If the movement exceeds the threshold (10px) and happens within the time\n * threshold (300ms), it marks the interaction as scrolling. This helps\n * distinguish between intentional scroll gestures and accidental touches.\n *\n * @param event - The TouchEvent from the touchmove event\n */\n private handleTouchMove(event: TouchEvent): void {\n if (\n event.touches.length === 1 &&\n this.touchStartY !== undefined &&\n this.touchStartTime !== undefined\n ) {\n const currentY = event.touches[0].clientY;\n const deltaY = Math.abs(currentY - this.touchStartY);\n const deltaTime = Date.now() - this.touchStartTime;\n\n if (\n deltaY > this.scrollThreshold &&\n deltaTime < this.scrollTimeThreshold\n ) {\n this.isCurrentlyScrolling = true;\n }\n }\n }\n\n /**\n * Handles touchend events for iPad scroll detection.\n *\n * Resets the scrolling state after a short delay (100ms) to allow for\n * any final touch events to be processed. This delay prevents immediate\n * state changes that could interfere with the selection logic.\n *\n * The 100ms delay is consistent with the design system's approach to\n * touch event handling and ensures that any final touch events or\n * gesture recognition can complete before the scrolling state is reset.\n */\n private handleTouchEnd(): void {\n // Reset scrolling state after a short delay\n setTimeout(() => {\n this.isCurrentlyScrolling = false;\n this.touchStartY = undefined;\n this.touchStartTime = undefined;\n }, 100);\n }\n\n // if the click and pointerup events are on the same target, we should not\n // handle the click event.\n private pointerUpTarget = null as EventTarget | null;\n\n private handleFocusout(): void {\n if (!this.matches(':focus-within')) {\n this.rovingTabindexController?.reset();\n }\n }\n\n private handleClick(event: Event): void {\n if (this.pointerUpTarget === event.target) {\n this.pointerUpTarget = null;\n return;\n }\n this.handlePointerBasedSelection(event);\n }\n\n private handlePointerup(event: Event): void {\n // Reset scrolling state for iPad scroll detection\n // This ensures the scrolling state is properly reset for both touch\n // and pointer events, maintaining consistency across different input methods.\n this.handleTouchEnd();\n\n /*\n * early return if drag and select is not supported\n * in this case, selection will be handled by the click event\n */\n if (!this.shouldSupportDragAndSelect) {\n return;\n }\n this.pointerUpTarget = event.target;\n this.handlePointerBasedSelection(event);\n }\n\n private async handlePointerBasedSelection(event: Event): Promise<void> {\n // Only handle left clicks\n if (event instanceof MouseEvent && event.button !== 0) {\n return;\n }\n\n // Prevent selection if we're currently scrolling (iPad fix)\n // This prevents accidental menu item selection when users are trying\n // to scroll through a long menu on iPad devices.\n if (this.isScrolling) {\n return;\n }\n\n const path = event.composedPath();\n const target = path.find((el) => {\n /* c8 ignore next 3 */\n if (!(el instanceof Element)) {\n return false;\n }\n return el.getAttribute('role') === this.childRole;\n }) as MenuItem;\n if (event.defaultPrevented) {\n const index = this.childItems.indexOf(target);\n if (target?.menuData?.focusRoot === this && index > -1) {\n this.focusedItemIndex = index;\n }\n return;\n }\n if (target?.href && target.href.length) {\n // This event will NOT ALLOW CANCELATION as link action\n // cancelation should occur on the `<sp-menu-item>` itself.\n this.dispatchEvent(\n new Event('change', {\n bubbles: true,\n composed: true,\n })\n );\n return;\n } else if (\n target?.menuData?.selectionRoot === this &&\n this.childItems.length\n ) {\n event.preventDefault();\n if (target.hasSubmenu || target.open) {\n return;\n }\n this.selectOrToggleItem(target);\n } else {\n return;\n }\n this.prepareToCleanUp();\n }\n\n private descendentOverlays = new Map<Overlay, Overlay>();\n\n protected handleDescendentOverlayOpened(event: Event): void {\n const target = event.composedPath()[0] as MenuItem;\n /* c8 ignore next 1 */\n if (!target.overlayElement) {\n return;\n }\n this.descendentOverlays.set(target.overlayElement, target.overlayElement);\n }\n\n protected handleDescendentOverlayClosed(event: Event): void {\n const target = event.composedPath()[0] as MenuItem;\n /* c8 ignore next 1 */\n if (!target.overlayElement) {\n return;\n }\n this.descendentOverlays.delete(target.overlayElement);\n }\n\n public handleSubmenuClosed = (event: Event): void => {\n event.stopPropagation();\n const target = event.composedPath()[0] as Overlay;\n target.dispatchEvent(\n new Event('sp-menu-submenu-closed', {\n bubbles: true,\n composed: true,\n })\n );\n };\n\n /**\n * given a menu item, returns the next focusable menu item before or after it;\n * if no menu item is provided, returns the first focusable menu item\n *\n * @param menuItem {MenuItem}\n * @param before {boolean} return the item before; default is false\n * @returns {MenuItem}\n */\n public getNeighboringFocusableElement(\n menuItem?: MenuItem,\n before = false\n ): MenuItem {\n const diff = before ? -1 : 1;\n const elements = this.rovingTabindexController?.elements || [];\n const index = menuItem ? elements.indexOf(menuItem) : -1;\n let newIndex = Math.min(Math.max(0, index + diff), elements.length - 1);\n while (\n !this.isFocusableElement(elements[newIndex]) &&\n 0 < newIndex &&\n newIndex < elements.length - 1\n ) {\n newIndex += diff;\n }\n return this.isFocusableElement(elements[newIndex])\n ? (elements[newIndex] as MenuItem)\n : menuItem || elements[0];\n }\n\n public handleSubmenuOpened = (event: Event): void => {\n event.stopPropagation();\n const target = event.composedPath()[0] as Overlay;\n target.dispatchEvent(\n new Event('sp-menu-submenu-opened', {\n bubbles: true,\n composed: true,\n })\n );\n\n const openedItem = event\n .composedPath()\n .find((el) => this.childItemSet.has(el as MenuItem));\n /* c8 ignore next 1 */\n if (!openedItem) {\n return;\n }\n };\n\n public async selectOrToggleItem(targetItem: MenuItem): Promise<void> {\n const resolvedSelects = this.resolvedSelects;\n const oldSelectedItemsMap = new Map(this.selectedItemsMap);\n const oldSelected = this.selected.slice();\n const oldSelectedItems = this.selectedItems.slice();\n const oldValue = this.value;\n\n if (targetItem.menuData.selectionRoot !== this) {\n return;\n }\n\n if (resolvedSelects === 'multiple') {\n if (this.selectedItemsMap.has(targetItem)) {\n this.selectedItemsMap.delete(targetItem);\n } else {\n this.selectedItemsMap.set(targetItem, true);\n }\n\n // Match HTML select and set the first selected\n // item as the value. Also set the selected array\n // in the order of the menu items.\n const selected: string[] = [];\n const selectedItems: MenuItem[] = [];\n\n this.childItemSet.forEach((childItem) => {\n if (childItem.menuData.selectionRoot !== this) {\n return;\n }\n\n if (this.selectedItemsMap.has(childItem)) {\n selected.push(childItem.value);\n selectedItems.push(childItem);\n }\n });\n this._selected = selected;\n this.selectedItems = selectedItems;\n this.value = this.selected.join(this.valueSeparator);\n } else {\n this.selectedItemsMap.clear();\n this.selectedItemsMap.set(targetItem, true);\n this.value = targetItem.value;\n this._selected = [targetItem.value];\n this.selectedItems = [targetItem];\n }\n\n const applyDefault = this.dispatchEvent(\n new Event('change', {\n cancelable: true,\n bubbles: true,\n composed: true,\n })\n );\n\n if (!applyDefault) {\n // Cancel the event & don't apply the selection\n this._selected = oldSelected;\n this.selectedItems = oldSelectedItems;\n this.selectedItemsMap = oldSelectedItemsMap;\n this.value = oldValue;\n return;\n }\n // Apply the selection changes to the menu items\n if (resolvedSelects === 'single') {\n for (const oldItem of oldSelectedItemsMap.keys()) {\n if (oldItem !== targetItem) {\n oldItem.selected = false;\n }\n }\n targetItem.selected = true;\n } else if (resolvedSelects === 'multiple') {\n targetItem.selected = !targetItem.selected;\n } else if (\n !targetItem.hasSubmenu &&\n targetItem?.menuData?.focusRoot === this\n ) {\n this.dispatchEvent(new Event('close', { bubbles: true }));\n }\n }\n\n protected navigateBetweenRelatedMenus(event: MenuItemKeydownEvent): void {\n const { key, root } = event;\n const dir = this.dir;\n const shouldOpenSubmenu =\n (dir === 'ltr' && key === 'ArrowRight') ||\n (dir === 'rtl' && key === 'ArrowLeft');\n const shouldCloseSelfAsSubmenu =\n (dir === 'ltr' && key === 'ArrowLeft') ||\n (dir === 'rtl' && key === 'ArrowRight') ||\n key === 'Escape';\n const lastFocusedItem = root as MenuItem;\n if (shouldOpenSubmenu) {\n if (lastFocusedItem?.hasSubmenu) {\n //open submenu and set focus\n event.stopPropagation();\n lastFocusedItem.openOverlay(true);\n }\n } else if (shouldCloseSelfAsSubmenu && this.isSubmenu) {\n event.stopPropagation();\n this.dispatchEvent(new Event('close', { bubbles: true }));\n this.updateSelectedItemIndex();\n }\n }\n\n public handleKeydown(event: Event): void {\n if (event.defaultPrevented || !this.rovingTabindexController) {\n return;\n }\n const { key, root, shiftKey, target } = event as MenuItemKeydownEvent;\n const openSubmenuKey = ['Enter', ' '].includes(key);\n if (shiftKey && target !== this && this.hasAttribute('tabindex')) {\n this.removeAttribute('tabindex');\n const replaceTabindex = (event: FocusEvent | KeyboardEvent): void => {\n if (\n !(event as KeyboardEvent).shiftKey &&\n !this.hasAttribute('tabindex')\n ) {\n document.removeEventListener('keyup', replaceTabindex);\n this.removeEventListener('focusout', replaceTabindex);\n }\n };\n document.addEventListener('keyup', replaceTabindex);\n this.addEventListener('focusout', replaceTabindex);\n }\n if (key === 'Tab') {\n this.closeDescendentOverlays();\n return;\n }\n if (openSubmenuKey && root?.hasSubmenu && !root.open) {\n // Remove focus while opening overlay from keyboard or the visible focus\n // will slip back to the first item in the menu.\n event.preventDefault();\n root.openOverlay(true);\n return;\n }\n if (key === ' ' || key === 'Enter') {\n event.preventDefault();\n root?.focusElement?.click();\n if (root) {\n this.selectOrToggleItem(root);\n }\n return;\n }\n this.navigateBetweenRelatedMenus(event as MenuItemKeydownEvent);\n }\n\n private _hasUpdatedSelectedItemIndex = false;\n\n /**\n * on focus, removes focus from focus styling item, and updates the selected item index\n */\n private prepareToCleanUp(): void {\n document.addEventListener(\n 'focusout',\n () => {\n requestAnimationFrame(() => {\n const focusedItem = this.focusInItem;\n if (focusedItem) {\n focusedItem.focused = false;\n }\n });\n },\n { once: true }\n );\n }\n\n public updateSelectedItemIndex(): void {\n let firstOrFirstSelectedIndex = 0;\n const selectedItemsMap = new Map<MenuItem, boolean>();\n const selected: string[] = [];\n const selectedItems: MenuItem[] = [];\n let itemIndex = this.childItems.length;\n while (itemIndex) {\n itemIndex -= 1;\n const childItem = this.childItems[itemIndex];\n if (childItem.menuData.selectionRoot === this) {\n if (\n childItem.selected ||\n (!this._hasUpdatedSelectedItemIndex &&\n this.selected.includes(childItem.value))\n ) {\n firstOrFirstSelectedIndex = itemIndex;\n selectedItemsMap.set(childItem, true);\n selected.unshift(childItem.value);\n selectedItems.unshift(childItem);\n }\n // Remove \"focused\" from non-\"selected\" items ONLY\n // Preserve \"focused\" on index===0 when no selection\n if (itemIndex !== firstOrFirstSelectedIndex) {\n childItem.focused = false;\n }\n }\n }\n\n this.selectedItemsMap = selectedItemsMap;\n this._selected = selected;\n this.selectedItems = selectedItems;\n this.value = this.selected.join(this.valueSeparator);\n this.focusedItemIndex = firstOrFirstSelectedIndex;\n this.focusInItemIndex = firstOrFirstSelectedIndex;\n }\n\n private _willUpdateItems = false;\n private _updateFocus?: MenuItem;\n\n private handleItemsChanged(): void {\n this.cachedChildItems = undefined;\n if (!this._willUpdateItems) {\n this._willUpdateItems = true;\n this.cacheUpdated = this.updateCache();\n }\n }\n\n private async updateCache(): Promise<void> {\n if (!this.hasUpdated) {\n await Promise.all([\n new Promise((res) => requestAnimationFrame(() => res(true))),\n this.updateComplete,\n ]);\n } else {\n await new Promise((res) => requestAnimationFrame(() => res(true)));\n }\n if (this.cachedChildItems === undefined) {\n this.updateSelectedItemIndex();\n this.updateItemFocus();\n }\n\n this._willUpdateItems = false;\n }\n\n private updateItemFocus(): void {\n this.focusInItem?.setAttribute('tabindex', '0');\n if (this.childItems.length == 0) {\n return;\n }\n }\n\n public closeDescendentOverlays(): void {\n this.descendentOverlays.forEach((overlay) => {\n overlay.open = false;\n });\n this.descendentOverlays = new Map<Overlay, Overlay>();\n }\n\n private handleSlotchange({\n target,\n }: Event & { target: HTMLSlotElement }): void {\n const assignedElements = target.assignedElements({\n flatten: true,\n }) as MenuItem[];\n if (this.childItems.length !== assignedElements.length) {\n assignedElements.forEach((item) => {\n if (typeof item.triggerUpdate !== 'undefined') {\n item.triggerUpdate();\n } else if (\n typeof (item as unknown as Menu).childItems !== 'undefined'\n ) {\n (item as unknown as Menu).childItems.forEach((child) => {\n child.triggerUpdate();\n });\n }\n });\n }\n if (this._updateFocus) {\n this.rovingTabindexController?.focusOnItem(this._updateFocus);\n this._updateFocus = undefined;\n }\n }\n\n protected renderMenuItemSlot(): TemplateResult {\n return html`\n <slot\n @sp-menu-submenu-opened=${this.handleDescendentOverlayOpened}\n @sp-menu-submenu-closed=${this.handleDescendentOverlayClosed}\n @slotchange=${this.handleSlotchange}\n ></slot>\n `;\n }\n\n public override render(): TemplateResult {\n return this.renderMenuItemSlot();\n }\n\n protected override firstUpdated(changed: PropertyValues): void {\n super.firstUpdated(changed);\n const updates: Promise<unknown>[] = [\n new Promise((res) => requestAnimationFrame(() => res(true))),\n ];\n [...this.children].forEach((item) => {\n if ((item as MenuItem).localName === 'sp-menu-item') {\n updates.push((item as MenuItem).updateComplete);\n }\n });\n this.childItemsUpdated = Promise.all(updates);\n }\n\n protected override updated(changes: PropertyValues<this>): void {\n super.updated(changes);\n if (changes.has('selects') && this.hasUpdated) {\n this.selectsChanged();\n }\n if (\n changes.has('label') &&\n (this.label || typeof changes.get('label') !== 'undefined')\n ) {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n /* c8 ignore next 3 */\n } else {\n this.removeAttribute('aria-label');\n }\n }\n }\n\n protected selectsChanged(): void {\n const updates: Promise<unknown>[] = [\n new Promise((res) => requestAnimationFrame(() => res(true))),\n ];\n this.childItemSet.forEach((childItem) => {\n updates.push(childItem.triggerUpdate());\n });\n this.childItemsUpdated = Promise.all(updates);\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute('role') && !this.ignore) {\n this.setAttribute('role', this.ownRole);\n }\n this.updateComplete.then(() => this.updateItemFocus());\n }\n\n private isFocusableElement(el: MenuItem): boolean {\n return el ? !el.disabled : false;\n }\n\n public override disconnectedCallback(): void {\n this.cachedChildItems = undefined;\n this.selectedItems = [];\n this.selectedItemsMap.clear();\n this.childItemSet.clear();\n this.descendentOverlays = new Map<Overlay, Overlay>();\n super.disconnectedCallback();\n }\n\n protected childItemsUpdated!: Promise<unknown[]>;\n protected cacheUpdated = Promise.resolve();\n /* c8 ignore next 3 */\n protected resolveCacheUpdated = (): void => {\n return;\n };\n\n protected override async getUpdateComplete(): Promise<boolean> {\n const complete = (await super.getUpdateComplete()) as boolean;\n await this.childItemsUpdated;\n await this.cacheUpdated;\n return complete;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["html", "SizedMixin", "SpectrumElement", "property", "query", "RovingTabindexController", "menuStyles", "event", "el", "elements", "firstEnabledIndex", "firstSelectedIndex", "index", "value", "selected", "old", "item", "_a", "itemsList", "slottedElements", "slottedElement", "flattenedChildren", "_b", "ignoreMenu", "selects", "preventScroll", "selectedItem", "childItem", "currentY", "deltaY", "deltaTime", "target", "menuItem", "before", "diff", "newIndex", "targetItem", "resolvedSelects", "oldSelectedItemsMap", "oldSelected", "oldSelectedItems", "oldValue", "selectedItems", "oldItem", "key", "root", "dir", "shouldOpenSubmenu", "shouldCloseSelfAsSubmenu", "lastFocusedItem", "shiftKey", "openSubmenuKey", "replaceTabindex", "focusedItem", "firstOrFirstSelectedIndex", "selectedItemsMap", "itemIndex", "res", "overlay", "assignedElements", "child", "changed", "updates", "changes", "complete", "__decorateClass"]
4
+ "sourcesContent": ["/**\n * Copyright 2026 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n CSSResultArray,\n html,\n PropertyValues,\n render,\n SizedMixin,\n SpectrumElement,\n TemplateResult,\n} from '@spectrum-web-components/base';\nimport {\n property,\n query,\n state,\n} from '@spectrum-web-components/base/src/decorators.js';\nimport type { Overlay } from '@spectrum-web-components/overlay';\nimport { RovingTabindexController } from '@spectrum-web-components/reactive-controllers/src/RovingTabindex.js';\n\nimport '@spectrum-web-components/icons-ui/icons/sp-icon-arrow500.js';\nimport '../sp-menu-divider.js';\n\nimport menuStyles from './menu.css.js';\nimport type {\n MenuItemAddedOrUpdatedEvent,\n MenuItemKeydownEvent,\n} from './MenuItem.js';\nimport { MenuItem } from './MenuItem.js';\n\nexport interface MenuChildItem {\n menuItem: MenuItem;\n managed: boolean;\n active: boolean;\n focusable: boolean;\n focusRoot: Menu;\n}\n\ntype SelectsType = 'none' | 'ignore' | 'inherit' | 'multiple' | 'single';\ntype RoleType = 'group' | 'menu' | 'listbox' | 'none';\n\n/**\n * Spectrum Menu Component\n *\n * @element sp-menu\n *\n * @slot - menu items to be listed in the menu\n * @fires change - Announces that the `value` of the element has changed\n * @attr selects - whether the element has a specific selection algorithm that it applies\n * to its item descendants. `single` allows only one descendent to be selected at a time.\n * `multiple` allows many descendants to be selected. `inherit` will be applied dynamically\n * when an ancestor of this element is actively managing the selection of its descendents.\n * When the `selects` attribute is not present a `value` will not be maintained and the Menu\n * Item children of this Menu will not have their `selected` state managed.\n */\nexport class Menu extends SizedMixin(SpectrumElement, { noDefaultSize: true }) {\n public static override get styles(): CSSResultArray {\n return [menuStyles];\n }\n\n static override shadowRootOptions = {\n ...SpectrumElement.shadowRootOptions,\n delegatesFocus: true,\n };\n\n private get isSubmenu(): boolean {\n return this.slot === 'submenu';\n }\n\n private asMenu(element: HTMLElement): Menu {\n return element as HTMLElement as Menu;\n }\n\n private get _mobileViewRoot(): Menu | null {\n return this.closest('sp-menu[mobile-view]') as Menu | null;\n }\n\n protected rovingTabindexController?: RovingTabindexController<MenuItem>;\n\n /**\n * iPad scroll detection properties\n *\n * This feature prevents menu item selection during iPad scrolling to avoid\n * accidental selections when users are trying to scroll through a long menu.\n *\n * How it works:\n * 1. On touchstart: Record initial Y position and timestamp\n * 2. On touchmove: Calculate vertical movement and time elapsed\n * 3. If movement > threshold AND time < threshold: Mark as scrolling\n * 4. On touchend: Reset scrolling state after a delay\n * 5. During selection: Prevent selection if scrolling is detected\n *\n * This prevents the common iPad issue where users accidentally select menu\n * items while trying to scroll through the menu content.\n *\n * Threshold Values:\n * - Movement threshold: 10px (consistent with Card component click vs. drag detection)\n * - Time threshold: 300ms (consistent with longpress duration across the design system)\n * - Reset delay: 100ms (allows final touch events to be processed)\n *\n * These values are carefully chosen to balance preventing accidental triggers\n * while allowing intentional scroll gestures. They represent a common UX pattern\n * in mobile interfaces and are consistent with other components in the design system.\n */\n private touchStartY: number | undefined = undefined;\n private touchStartTime: number | undefined = undefined;\n private isCurrentlyScrolling = false;\n\n /**\n * Minimum vertical movement (in pixels) required to trigger scrolling detection.\n *\n * This threshold is consistent with other components in the design system:\n * - Card component uses 10px for click vs. drag detection\n * - Menu component uses 10px for scroll vs. selection detection\n *\n * The 10px threshold is carefully chosen to:\n * - Allow for natural finger tremor and accidental touches\n * - Distinguish between intentional scroll gestures and taps\n * - Provide consistent behavior across the platform\n *\n * @see {@link packages/card/src/Card.ts} for similar threshold usage\n */\n private scrollThreshold = 10; // pixels\n\n /**\n * Maximum time (in milliseconds) for a movement to be considered scrolling.\n *\n * This threshold is consistent with other timing values in the design system:\n * - Longpress duration: 300ms (ActionButton, LongpressController)\n * - Scroll detection: 300ms (Menu component)\n *\n * Quick movements within this timeframe are likely intentional scrolls,\n * while slower movements are more likely taps or selections.\n *\n * @see {@link packages/action-button/src/ActionButton.ts} for longpress duration\n * @see {@link packages/overlay/src/LongpressController.ts} for longpress duration\n */\n private scrollTimeThreshold = 300; // milliseconds\n\n /**\n * Public getter for scrolling state\n * Returns true if the component is currently in a scrolling state\n */\n public get isScrolling(): boolean {\n return this.isCurrentlyScrolling;\n }\n\n public set isScrolling(value: boolean) {\n // For testing purposes, allow setting the scrolling state\n this.isCurrentlyScrolling = value;\n }\n\n /**\n * Returns the MenuItem whose submenu is currently displayed at the\n * top of the mobile drill-down stack, or `undefined` when no submenu\n * is open.\n */\n public get currentMobileSubmenu(): MenuItem | undefined {\n return this._mobileSubmenuStack[this._mobileSubmenuStack.length - 1];\n }\n\n /**\n * Opens a mobile submenu by projecting its content into this menu's\n * light DOM, pushing it onto the submenu stack, triggering the\n * slide-in animation, and focusing the back button.\n *\n * @param item - The MenuItem whose submenu should be opened.\n */\n public openMobileSubmenu(item: MenuItem): void {\n this._projectMobileSubmenu(item);\n this._mobileSubmenuStack = [...this._mobileSubmenuStack, item];\n this._triggerMobileTransition('forward');\n this._focusProjectedSubmenu(item);\n }\n\n /**\n * Closes the topmost mobile submenu by restoring it to its original\n * parent MenuItem, popping it from the stack, and either re-focusing\n * the previous submenu's back button or returning focus to the\n * triggering MenuItem when no deeper level remains.\n */\n public closeMobileSubmenu(): void {\n const closedItem = this.currentMobileSubmenu;\n if (closedItem) {\n this._restoreMobileSubmenu(closedItem);\n }\n this._mobileSubmenuStack = this._mobileSubmenuStack.slice(0, -1);\n\n const previous = this.currentMobileSubmenu;\n if (previous?.submenuElement) {\n previous.submenuElement.setAttribute('slot', 'mobile-submenu');\n this._focusProjectedSubmenu(previous);\n } else if (closedItem) {\n this.updateComplete.then(() => {\n closedItem.focus();\n });\n }\n this._triggerMobileTransition('back');\n }\n\n /**\n * Focuses the mobile back button inside the given item's projected\n * submenu. Explicitly resets `tabIndex` and `focused` on every other\n * child of the submenu so only the back row is in the tab order\n * after the drill-down opens. We avoid delegating to the projected\n * submenu's `RovingTabindexController` here because the back button\n * is appended dynamically via `render()` and may not yet be in the\n * controller's element cache when this runs, which would cause\n * focus to fall back to the first nested item instead.\n *\n * @param item - The MenuItem whose projected submenu should receive focus.\n */\n private async _focusProjectedSubmenu(item: MenuItem): Promise<void> {\n const submenuEl = item.submenuElement;\n if (!submenuEl) {\n return;\n }\n const backItem = submenuEl.querySelector(\n '.mobile-back-button'\n ) as MenuItem | null;\n if (!backItem) {\n return;\n }\n await backItem.updateComplete;\n const submenu = this.asMenu(submenuEl);\n await submenu.updateComplete;\n\n submenu.childItems.forEach((child) => {\n child.tabIndex = -1;\n child.focused = false;\n });\n backItem.tabIndex = 0;\n backItem.focused = true;\n if (submenu.rovingTabindexController) {\n submenu.rovingTabindexController.currentIndex =\n submenu.childItems.indexOf(backItem);\n }\n backItem.focus();\n }\n\n /**\n * Focuses the mobile back button of the currently visible projected\n * submenu. Used when the user presses ArrowUp from the first nested\n * item so focus moves to the back row instead of wrapping. Manages\n * `tabIndex`/`focused` explicitly to keep the back row as the only\n * tabbable element in the projected submenu.\n */\n private _focusMobileBackRow(): void {\n const submenuEl = this.currentMobileSubmenu?.submenuElement;\n if (!submenuEl) {\n return;\n }\n const backItem = submenuEl.querySelector(\n '.mobile-back-button'\n ) as MenuItem | null;\n if (!backItem) {\n return;\n }\n const submenu = this.asMenu(submenuEl);\n submenu.childItems.forEach((child) => {\n child.tabIndex = -1;\n child.focused = false;\n });\n backItem.tabIndex = 0;\n backItem.focused = true;\n if (submenu.rovingTabindexController) {\n submenu.rovingTabindexController.currentIndex =\n submenu.childItems.indexOf(backItem);\n }\n backItem.focus();\n }\n\n /**\n * Focuses the first focusable item inside the currently visible\n * projected submenu, skipping the back row. Used when the user\n * presses ArrowDown from the back row. Manages `tabIndex`/`focused`\n * explicitly so only the focused item is in the tab order.\n */\n private _focusFirstItemInCurrentNestedSubmenu(): void {\n const submenuEl = this.currentMobileSubmenu?.submenuElement;\n if (!submenuEl) {\n return;\n }\n const submenu = this.asMenu(submenuEl);\n const firstItem = submenu.childItems.find(\n (child) =>\n !child.disabled && !child.classList.contains('mobile-back-button')\n );\n if (!firstItem) {\n return;\n }\n submenu.childItems.forEach((child) => {\n child.tabIndex = -1;\n child.focused = false;\n });\n firstItem.tabIndex = 0;\n firstItem.focused = true;\n if (submenu.rovingTabindexController) {\n submenu.rovingTabindexController.currentIndex =\n submenu.childItems.indexOf(firstItem);\n }\n firstItem.focus();\n }\n\n /**\n * Capture-phase keydown handler that overrides the default\n * `RovingTabindexController` wrap behavior at the mobile drill-down\n * boundary:\n * - ArrowUp on the first nested item moves focus to the back row.\n * - ArrowDown on the back row moves focus to the first nested item.\n *\n * Runs in the capture phase so it preempts the projected submenu's\n * controller, which handles arrow keys in the bubble phase.\n */\n private handleMobileDrilldownKeydownCapture = (\n event: KeyboardEvent\n ): void => {\n if (event.defaultPrevented || !this.rovingTabindexController) {\n return;\n }\n const { key, target } = event;\n if (!(target instanceof MenuItem)) {\n return;\n }\n\n const mobileRoot = this._mobileViewRoot;\n\n if (mobileRoot?.currentMobileSubmenu && key === 'ArrowUp') {\n const items = this.childItems.filter((c) => this.isFocusableElement(c));\n const firstNonBack = items.find(\n (c) => !c.classList.contains('mobile-back-button')\n );\n if (firstNonBack === target) {\n event.preventDefault();\n event.stopImmediatePropagation();\n mobileRoot._focusMobileBackRow();\n }\n return;\n }\n\n if (\n this.mobileView &&\n this._mobileSubmenuStack.length > 0 &&\n key === 'ArrowDown' &&\n target.classList.contains('mobile-back-button')\n ) {\n event.preventDefault();\n event.stopImmediatePropagation();\n this._focusFirstItemInCurrentNestedSubmenu();\n }\n };\n\n /**\n * Triggers a CSS slide animation on the mobile submenu wrapper.\n * Waits for the current Lit update cycle, then sets the\n * `mobile-transition` attribute so the keyframe animation plays.\n *\n * @param direction - `'forward'` slides content in from the right,\n * `'back'` slides content in from the left.\n */\n private _triggerMobileTransition(direction: 'forward' | 'back'): void {\n this.updateComplete.then(() => {\n const wrapper = this.shadowRoot?.querySelector(\n '.mobile-submenu-animation-wrapper'\n );\n if (!wrapper) {\n return;\n }\n wrapper.removeAttribute('mobile-transition');\n requestAnimationFrame(() => {\n wrapper.setAttribute('mobile-transition', direction);\n });\n });\n }\n\n /**\n * Cleans up the `mobile-transition` attribute once the CSS slide\n * animation finishes, preventing the animation from replaying on\n * subsequent layout changes. Bound declaratively via `@animationend`\n * on the wrapper, so it only fires for that element.\n */\n private _handleAnimationEnd = (event: AnimationEvent): void => {\n const target = event.currentTarget as HTMLElement;\n target?.removeAttribute('mobile-transition');\n };\n\n /**\n * Restores every projected submenu back to its original parent and\n * clears the submenu stack. Called during `disconnectedCallback` or\n * when the menu overlay closes to ensure a clean state.\n */\n public resetMobileSubmenus(): void {\n for (let i = this._mobileSubmenuStack.length - 1; i >= 0; i--) {\n this._restoreMobileSubmenu(this._mobileSubmenuStack[i]);\n }\n this._mobileSubmenuStack = [];\n }\n\n /**\n * Moves the submenu element from its MenuItem parent into this Menu's\n * light DOM with a `mobile-submenu` slot, so it projects through the\n * named slot in the shadow DOM. Any previously visible projected submenu\n * is moved to a non-rendered slot to avoid both showing at once.\n */\n private _projectMobileSubmenu(item: MenuItem): void {\n const submenuEl = item.submenuElement;\n if (!submenuEl) {\n return;\n }\n\n const currentlyVisible = this.currentMobileSubmenu?.submenuElement;\n if (currentlyVisible) {\n currentlyVisible.setAttribute('slot', 'mobile-submenu-stacked');\n }\n\n const parentElement = submenuEl.parentElement;\n if (!parentElement) {\n return;\n }\n\n item._mobileSubmenuProjected = true;\n this._mobileSubmenuOriginalParents.set(submenuEl, parentElement);\n\n const submenu = this.asMenu(submenuEl);\n const savedChildItems = new Set(submenu.childItemSet);\n\n submenuEl.setAttribute('slot', 'mobile-submenu');\n this.appendChild(submenuEl);\n\n this._restoreSubmenuChildState(submenu, savedChildItems);\n this._renderMobileBackElements(submenuEl);\n }\n\n /**\n * Restores the submenu element back to its original MenuItem parent\n * and resets the slot attribute.\n */\n private _restoreMobileSubmenu(item: MenuItem): void {\n const submenuEl = item.submenuElement;\n if (!submenuEl) {\n return;\n }\n\n this._removeMobileBackElements(submenuEl);\n\n const submenu = this.asMenu(submenuEl);\n const originalParent = this._mobileSubmenuOriginalParents.get(submenuEl);\n if (originalParent) {\n const savedChildItems = new Set(submenu.childItemSet);\n\n submenuEl.setAttribute('slot', 'submenu');\n originalParent.appendChild(submenuEl);\n this._mobileSubmenuOriginalParents.delete(submenuEl);\n\n this._restoreSubmenuChildState(submenu, savedChildItems);\n }\n item._mobileSubmenuProjected = false;\n }\n\n /**\n * Handles click on the mobile back button. Stops the event from\n * reaching parent menus and closes the current mobile submenu.\n */\n private handleMobileBackClick = (event: Event): void => {\n event.stopPropagation();\n event.preventDefault();\n this.closeMobileSubmenu();\n };\n\n /**\n * Maps each projected submenu element to its Lit render container so\n * that back button elements can be individually cleaned up when a\n * submenu is restored, without affecting other levels in the stack.\n */\n private _mobileBackContainers = new Map<HTMLElement, HTMLElement>();\n\n /**\n * Declaratively renders the mobile back button and divider into the\n * projected submenu element using Lit's `render()`, so the back button\n * lives inside the same `<sp-menu>` and participates in its\n * `RovingTabindexController` for keyboard navigation. Re-renders\n * whenever `dir` or `mobileBackLabel` change so the icon orientation\n * and label stay in sync.\n */\n private _renderMobileBackElements(submenuEl: HTMLElement): void {\n this._removeMobileBackElements(submenuEl);\n const container = document.createElement('div');\n container.className = 'mobile-back-container';\n submenuEl.insertBefore(container, submenuEl.firstChild);\n this._mobileBackContainers.set(submenuEl, container);\n\n render(\n html`\n <sp-menu-item\n class=\"mobile-back-button\"\n data-mobile-back\n @click=${this.handleMobileBackClick}\n >\n <sp-icon-arrow500\n slot=\"icon\"\n class=\"mobile-back-icon\"\n ></sp-icon-arrow500>\n ${this.mobileBackLabel}\n </sp-menu-item>\n <sp-menu-divider data-mobile-back></sp-menu-divider>\n `,\n container\n );\n const submenu = this.asMenu(submenuEl);\n const backItem = submenuEl.querySelector(\n '.mobile-back-button'\n ) as MenuItem | null;\n if (backItem) {\n submenu.childItemSet.add(backItem);\n }\n submenu.cachedChildItems = undefined;\n submenu.rovingTabindexController?.clearElementCache();\n if (submenu.rovingTabindexController) {\n submenu.rovingTabindexController.currentIndex = 0;\n }\n }\n\n /**\n * Re-render any open mobile back containers so changes to\n * `mobileBackLabel` (and other reactive values consumed by the\n * back-button template) propagate without needing to close\n * and re-open the drill-down.\n */\n private _refreshMobileBackElements(): void {\n this._mobileBackContainers.forEach((_container, submenuEl) => {\n this._renderMobileBackElements(submenuEl);\n });\n }\n\n /**\n * Removes the mobile back button render container from the given\n * projected submenu element.\n */\n private _removeMobileBackElements(submenuEl: HTMLElement): void {\n const container = this._mobileBackContainers.get(submenuEl);\n if (container) {\n const backItem = submenuEl.querySelector(\n '.mobile-back-button'\n ) as MenuItem | null;\n if (backItem) {\n this.asMenu(submenuEl).childItemSet.delete(backItem);\n }\n container.remove();\n this._mobileBackContainers.delete(submenuEl);\n const submenu = this.asMenu(submenuEl);\n submenu.cachedChildItems = undefined;\n submenu.rovingTabindexController?.clearElementCache();\n if (submenu.rovingTabindexController) {\n submenu.rovingTabindexController.currentIndex = 0;\n }\n }\n }\n\n /**\n * Re-adds saved child items to the submenu's `childItemSet` and\n * invalidates cached references after DOM re-parenting, so the\n * `RovingTabindexController` picks up the correct set of focusable\n * children.\n *\n * @param submenu - The submenu whose child state needs restoring.\n * @param savedChildItems - The set of MenuItem children captured\n * before the DOM move.\n */\n private _restoreSubmenuChildState(\n submenu: Menu,\n savedChildItems: Set<MenuItem>\n ): void {\n savedChildItems.forEach((child) => submenu.childItemSet.add(child));\n submenu.cachedChildItems = undefined;\n submenu.rovingTabindexController?.clearElementCache();\n }\n\n /**\n * Maps each projected submenu element to its original parent so the\n * element can be moved back when the submenu is closed.\n */\n private _mobileSubmenuOriginalParents = new Map<HTMLElement, HTMLElement>();\n\n /**\n * label of the menu\n */\n @property({ type: String, reflect: true })\n public label = '';\n\n /**\n * whether menu should be ignored by roving tabindex controller\n */\n @property({ type: Boolean, reflect: true })\n public ignore = false;\n\n /**\n * Enables mobile submenu navigation where tapping a submenu item replaces\n * the current menu content with the submenu's children (drill-down) instead\n * of opening a flyout overlay.\n */\n @property({ type: Boolean, attribute: 'mobile-view', reflect: true })\n public mobileView = false;\n\n /**\n * Label for the mobile back button, used for localization.\n */\n @property({ type: String, attribute: 'mobile-back-label' })\n public mobileBackLabel = 'Back';\n\n @state()\n private _mobileSubmenuStack: MenuItem[] = [];\n\n /**\n * how the menu allows selection of its items:\n * - `undefined` (default): no selection is allowed\n * - `\"inherit\"`: the selection behavior is managed from an ancestor\n * - `\"single\"`: only one item can be selected at a time\n * - `\"multiple\"`: multiple items can be selected\n */\n @property({ type: String, reflect: true })\n public selects: undefined | 'inherit' | 'single' | 'multiple';\n\n /**\n * value of the selected item(s)\n */\n @property({ type: String })\n public value = '';\n\n // For the multiple select case, we'll join the value strings together\n // for the value property with this separator\n @property({ type: String, attribute: 'value-separator' })\n public valueSeparator = ',';\n\n /**\n * selected items values as string\n */\n @property({ attribute: false })\n public get selected(): string[] {\n return !this.selects ? [] : this._selected;\n }\n\n public set selected(selected: string[]) {\n if (selected === this.selected) {\n return;\n }\n const old = this.selected;\n this._selected = selected;\n this.selectedItems = [];\n this.selectedItemsMap.clear();\n this.childItems.forEach((item) => {\n if (this !== item.menuData.selectionRoot) {\n return;\n }\n item.selected = this.selected.includes(item.value);\n if (item.selected) {\n this.selectedItems.push(item);\n this.selectedItemsMap.set(item, true);\n }\n });\n this.requestUpdate('selected', old);\n }\n\n protected _selected = [] as string[];\n\n /**\n * array of selected menu items\n */\n @property({ attribute: false })\n public selectedItems = [] as MenuItem[];\n\n @query('slot:not([name])')\n public menuSlot!: HTMLSlotElement;\n\n private childItemSet = new Set<MenuItem>();\n public focusedItemIndex = 0;\n public focusInItemIndex = 0;\n\n /**\n * Whether to support the pointerdown-drag-pointerup selection strategy.\n * Defaults to false to prevent click/touch events from being captured\n * behind the menu tray in mobile environments (since the menu closes\n * immediately on pointerup).\n */\n\n public shouldSupportDragAndSelect = false;\n\n public get focusInItem(): MenuItem | undefined {\n return this.rovingTabindexController?.focusInElement;\n }\n\n protected get controlsRovingTabindex(): boolean {\n return true;\n }\n\n private selectedItemsMap = new Map<MenuItem, boolean>();\n\n /**\n * child items managed by menu\n */\n public get childItems(): MenuItem[] {\n if (!this.cachedChildItems) {\n this.cachedChildItems = this.updateCachedMenuItems();\n }\n return this.cachedChildItems;\n }\n\n private cachedChildItems: MenuItem[] | undefined;\n\n private updateCachedMenuItems(): MenuItem[] {\n if (!this.menuSlot) {\n return [];\n }\n const itemsList = [];\n const slottedElements = this.menuSlot.assignedElements({\n flatten: true,\n }) as HTMLElement[];\n // Recursively flatten <slot> and non-<sp-menu-item> elements assigned to the menu into a single array.\n for (const [i, slottedElement] of slottedElements.entries()) {\n if (this.childItemSet.has(slottedElement as MenuItem)) {\n // Assign <sp-menu-item> members of the array that are in this.childItemSet to this.chachedChildItems.\n itemsList.push(slottedElement as MenuItem);\n continue;\n }\n const isHTMLSlotElement = slottedElement.localName === 'slot';\n const flattenedChildren = isHTMLSlotElement\n ? (slottedElement as HTMLSlotElement).assignedElements({\n flatten: true,\n })\n : [...slottedElement.querySelectorAll(`:scope > *`)];\n slottedElements.splice(\n i,\n 1,\n slottedElement,\n ...(flattenedChildren as HTMLElement[])\n );\n }\n\n this.cachedChildItems = [...itemsList];\n this.rovingTabindexController?.clearElementCache();\n\n return this.cachedChildItems;\n }\n\n /**\n * Hide this getter from web-component-analyzer until\n * https://github.com/runem/web-component-analyzer/issues/131\n * has been addressed.\n *\n * @private\n */\n public get childRole(): string {\n if (this.resolvedRole === 'listbox') {\n return 'option';\n }\n switch (this.resolvedSelects) {\n case 'single':\n return 'menuitemradio';\n case 'multiple':\n return 'menuitemcheckbox';\n default:\n return 'menuitem';\n }\n }\n\n protected get ownRole(): string {\n return 'menu';\n }\n\n /**\n * menuitem role based on selection type\n */\n private resolvedSelects?: SelectsType;\n\n /**\n * menu role based on selection type\n */\n private resolvedRole?: RoleType;\n\n /**\n * When a descendant `<sp-menu-item>` element is added or updated it will dispatch\n * this event to announce its presence in the DOM. During the CAPTURE phase the first\n * Menu based element that the event encounters will manage the focus state of the\n * dispatching `<sp-menu-item>` element.\n *\n * @param event\n */\n private onFocusableItemAddedOrUpdated(\n event: MenuItemAddedOrUpdatedEvent\n ): void {\n event.menuCascade.set(this, {\n hadFocusRoot: !!event.item.menuData.focusRoot,\n ancestorWithSelects: event.currentAncestorWithSelects,\n });\n if (this.selects) {\n event.currentAncestorWithSelects = this;\n }\n event.item.menuData.focusRoot = event.item.menuData.focusRoot || this;\n }\n\n /**\n * When a descendant `<sp-menu-item>` element is added or updated it will dispatch\n * this event to announce its presence in the DOM. During the BUBBLE phase the first\n * Menu based element that the event encounters that does not inherit selection will\n * manage the selection state of the dispatching `<sp-menu-item>` element.\n *\n * @param event\n */\n private onSelectableItemAddedOrUpdated(\n event: MenuItemAddedOrUpdatedEvent\n ): void {\n const cascadeData = event.menuCascade.get(this);\n /* c8 ignore next 1 */\n if (!cascadeData) {\n return;\n }\n\n event.item.menuData.parentMenu = event.item.menuData.parentMenu || this;\n this.addChildItem(event.item);\n\n if (this.selects === 'inherit') {\n this.resolvedSelects = 'inherit';\n const ignoreMenu = event.currentAncestorWithSelects?.ignore;\n this.resolvedRole = ignoreMenu\n ? 'none'\n : ((event.currentAncestorWithSelects?.getAttribute('role') ||\n this.getAttribute('role') ||\n undefined) as RoleType);\n } else if (this.selects) {\n this.resolvedRole = this.ignore\n ? 'none'\n : ((this.getAttribute('role') || undefined) as RoleType);\n this.resolvedSelects = this.selects;\n } else {\n this.resolvedRole = this.ignore\n ? 'none'\n : ((this.getAttribute('role') || undefined) as RoleType);\n this.resolvedSelects = this.resolvedRole === 'none' ? 'ignore' : 'none';\n }\n\n if (this.resolvedRole === 'none') {\n return;\n }\n\n const selects =\n this.resolvedSelects === 'single' || this.resolvedSelects === 'multiple';\n event.item.menuData.cleanupSteps.push((item: MenuItem) =>\n this.removeChildItem(item)\n );\n if (\n (selects || (!this.selects && this.resolvedSelects !== 'ignore')) &&\n !event.item.menuData.selectionRoot\n ) {\n event.item.setRole(this.childRole);\n event.item.menuData.selectionRoot =\n event.item.menuData.selectionRoot || this;\n if (event.item.selected) {\n this.selectedItemsMap.set(event.item, true);\n this.selectedItems = [...this.selectedItems, event.item];\n this._selected = [...this.selected, event.item.value];\n this.value = this.selected.join(this.valueSeparator);\n }\n }\n }\n\n private addChildItem(item: MenuItem): void {\n this.childItemSet.add(item);\n this.handleItemsChanged();\n }\n\n private async removeChildItem(item: MenuItem): Promise<void> {\n if (item.focused || item.hasAttribute('focused') || item.active) {\n this._updateFocus = this.getNeighboringFocusableElement(item);\n }\n this.childItemSet.delete(item);\n this.cachedChildItems = undefined;\n }\n\n public constructor() {\n super();\n\n /**\n * only create an RTI if menu controls keyboard navigation and one does not already exist\n */\n if (!this.rovingTabindexController && this.controlsRovingTabindex) {\n this.rovingTabindexController = new RovingTabindexController<MenuItem>(\n this,\n {\n direction: 'vertical',\n focusInIndex: (elements: MenuItem[] | undefined) => {\n let firstEnabledIndex = -1;\n const firstSelectedIndex = elements?.findIndex((el, index) => {\n if (!elements[firstEnabledIndex] && !el.disabled) {\n firstEnabledIndex = index;\n }\n return el.selected && !el.disabled;\n });\n return elements &&\n firstSelectedIndex &&\n elements[firstSelectedIndex]\n ? firstSelectedIndex\n : firstEnabledIndex;\n },\n elements: () => this.childItems,\n isFocusableElement: this.isFocusableElement.bind(this),\n hostDelegatesFocus: true,\n stopKeyEventPropagation: true,\n }\n );\n }\n\n this.addEventListener(\n 'sp-menu-item-added-or-updated',\n this.onSelectableItemAddedOrUpdated\n );\n this.addEventListener(\n 'sp-menu-item-added-or-updated',\n this.onFocusableItemAddedOrUpdated,\n {\n capture: true,\n }\n );\n this.addEventListener('click', this.handleClick);\n this.addEventListener('touchend', this.handlePointerup);\n this.addEventListener('focusout', this.handleFocusout);\n this.addEventListener('sp-menu-item-keydown', this.handleKeydown);\n // Capture-phase keydown so mobile drill-down navigation between\n // the back row and the first nested item runs before the projected\n // submenu's `RovingTabindexController` (which would otherwise\n // wrap the focus within the nested menu).\n this.addEventListener(\n 'keydown',\n this.handleMobileDrilldownKeydownCapture,\n true\n );\n this.addEventListener('pointerup', this.handlePointerup);\n this.addEventListener('sp-opened', this.handleSubmenuOpened);\n this.addEventListener('sp-closed', this.handleSubmenuClosed);\n\n // Add touch event listeners for iPad scroll detection\n this.addEventListener('touchstart', this.handleTouchStart, {\n passive: true,\n });\n this.addEventListener('touchmove', this.handleTouchMove, {\n passive: true,\n });\n }\n\n /**\n * for picker elements, will set focus on first selected item\n */\n public focusOnFirstSelectedItem({ preventScroll }: FocusOptions = {}): void {\n if (!this.rovingTabindexController) {\n return;\n }\n const selectedItem = this.selectedItems.find((el) =>\n this.isFocusableElement(el)\n );\n if (!selectedItem) {\n this.focus({ preventScroll });\n return;\n }\n\n if (selectedItem && !preventScroll) {\n selectedItem.scrollIntoView({ block: 'nearest' });\n }\n this.rovingTabindexController?.focusOnItem(selectedItem);\n }\n\n public override focus({ preventScroll }: FocusOptions = {}): void {\n if (this.rovingTabindexController) {\n if (\n !this.childItems.length ||\n this.childItems.every((childItem) => childItem.disabled)\n ) {\n return;\n }\n if (\n this.childItems.some(\n (childItem) => childItem.menuData.focusRoot !== this\n )\n ) {\n super.focus({ preventScroll });\n return;\n }\n this.rovingTabindexController.focus({ preventScroll });\n }\n }\n\n /**\n * Handles touchstart events for iPad scroll detection.\n *\n * Records the initial touch position and timestamp to establish a baseline\n * for detecting scroll gestures. Only processes single-touch events to\n * avoid interference with multi-touch gestures.\n *\n * @param event - The TouchEvent from the touchstart event\n */\n private handleTouchStart(event: TouchEvent): void {\n if (event.touches.length === 1) {\n this.touchStartY = event.touches[0].clientY;\n this.touchStartTime = Date.now();\n this.isCurrentlyScrolling = false;\n }\n }\n\n /**\n * Handles touchmove events for iPad scroll detection.\n *\n * Calculates the vertical movement distance and time elapsed since touchstart.\n * If the movement exceeds the threshold (10px) and happens within the time\n * threshold (300ms), it marks the interaction as scrolling. This helps\n * distinguish between intentional scroll gestures and accidental touches.\n *\n * @param event - The TouchEvent from the touchmove event\n */\n private handleTouchMove(event: TouchEvent): void {\n if (\n event.touches.length === 1 &&\n this.touchStartY !== undefined &&\n this.touchStartTime !== undefined\n ) {\n const currentY = event.touches[0].clientY;\n const deltaY = Math.abs(currentY - this.touchStartY);\n const deltaTime = Date.now() - this.touchStartTime;\n\n if (\n deltaY > this.scrollThreshold &&\n deltaTime < this.scrollTimeThreshold\n ) {\n this.isCurrentlyScrolling = true;\n }\n }\n }\n\n /**\n * Handles touchend events for iPad scroll detection.\n *\n * Resets the scrolling state after a short delay (100ms) to allow for\n * any final touch events to be processed. This delay prevents immediate\n * state changes that could interfere with the selection logic.\n *\n * The 100ms delay is consistent with the design system's approach to\n * touch event handling and ensures that any final touch events or\n * gesture recognition can complete before the scrolling state is reset.\n */\n private handleTouchEnd(): void {\n // Reset scrolling state after a short delay\n setTimeout(() => {\n this.isCurrentlyScrolling = false;\n this.touchStartY = undefined;\n this.touchStartTime = undefined;\n }, 100);\n }\n\n // if the click and pointerup events are on the same target, we should not\n // handle the click event.\n private pointerUpTarget = null as EventTarget | null;\n\n private handleFocusout(): void {\n if (!this.matches(':focus-within')) {\n this.rovingTabindexController?.reset();\n }\n }\n\n private handleClick(event: Event): void {\n if (this.pointerUpTarget === event.target) {\n this.pointerUpTarget = null;\n return;\n }\n this.handlePointerBasedSelection(event);\n }\n\n private handlePointerup(event: Event): void {\n // Reset scrolling state for iPad scroll detection\n // This ensures the scrolling state is properly reset for both touch\n // and pointer events, maintaining consistency across different input methods.\n this.handleTouchEnd();\n\n /*\n * early return if drag and select is not supported\n * in this case, selection will be handled by the click event\n */\n if (!this.shouldSupportDragAndSelect) {\n return;\n }\n this.pointerUpTarget = event.target;\n this.handlePointerBasedSelection(event);\n }\n\n private async handlePointerBasedSelection(event: Event): Promise<void> {\n // Only handle left clicks\n if (event instanceof MouseEvent && event.button !== 0) {\n return;\n }\n\n // Prevent selection if we're currently scrolling (iPad fix)\n // This prevents accidental menu item selection when users are trying\n // to scroll through a long menu on iPad devices.\n if (this.isScrolling) {\n return;\n }\n\n const path = event.composedPath();\n const target = path.find((el) => {\n /* c8 ignore next 3 */\n if (!(el instanceof Element)) {\n return false;\n }\n return el.getAttribute('role') === this.childRole;\n }) as MenuItem;\n if (event.defaultPrevented) {\n const index = this.childItems.indexOf(target);\n if (target?.menuData?.focusRoot === this && index > -1) {\n this.focusedItemIndex = index;\n }\n return;\n }\n if (target?.href && target.href.length) {\n // This event will NOT ALLOW CANCELATION as link action\n // cancelation should occur on the `<sp-menu-item>` itself.\n this.dispatchEvent(\n new Event('change', {\n bubbles: true,\n composed: true,\n })\n );\n return;\n } else if (\n target?.menuData?.selectionRoot === this &&\n this.childItems.length\n ) {\n event.preventDefault();\n if (target.hasSubmenu || target.open) {\n return;\n }\n this.selectOrToggleItem(target);\n } else {\n return;\n }\n this.prepareToCleanUp();\n }\n\n private descendentOverlays = new Map<Overlay, Overlay>();\n\n protected handleDescendentOverlayOpened(event: Event): void {\n const target = event.composedPath()[0] as MenuItem;\n /* c8 ignore next 1 */\n if (!target.overlayElement) {\n return;\n }\n this.descendentOverlays.set(target.overlayElement, target.overlayElement);\n }\n\n protected handleDescendentOverlayClosed(event: Event): void {\n const target = event.composedPath()[0] as MenuItem;\n /* c8 ignore next 1 */\n if (!target.overlayElement) {\n return;\n }\n this.descendentOverlays.delete(target.overlayElement);\n }\n\n public handleSubmenuClosed = (event: Event): void => {\n event.stopPropagation();\n if (this.mobileView) {\n this.resetMobileSubmenus();\n return;\n }\n const target = event.composedPath()[0] as Overlay;\n target.dispatchEvent(\n new Event('sp-menu-submenu-closed', {\n bubbles: true,\n composed: true,\n })\n );\n };\n\n /**\n * given a menu item, returns the next focusable menu item before or after it;\n * if no menu item is provided, returns the first focusable menu item\n *\n * @param menuItem {MenuItem}\n * @param before {boolean} return the item before; default is false\n * @returns {MenuItem}\n */\n public getNeighboringFocusableElement(\n menuItem?: MenuItem,\n before = false\n ): MenuItem {\n const diff = before ? -1 : 1;\n const elements = this.rovingTabindexController?.elements || [];\n const index = menuItem ? elements.indexOf(menuItem) : -1;\n let newIndex = Math.min(Math.max(0, index + diff), elements.length - 1);\n while (\n !this.isFocusableElement(elements[newIndex]) &&\n 0 < newIndex &&\n newIndex < elements.length - 1\n ) {\n newIndex += diff;\n }\n return this.isFocusableElement(elements[newIndex])\n ? (elements[newIndex] as MenuItem)\n : menuItem || elements[0];\n }\n\n public handleSubmenuOpened = (event: Event): void => {\n event.stopPropagation();\n const target = event.composedPath()[0] as Overlay;\n target.dispatchEvent(\n new Event('sp-menu-submenu-opened', {\n bubbles: true,\n composed: true,\n })\n );\n\n const openedItem = event\n .composedPath()\n .find((el) => this.childItemSet.has(el as MenuItem));\n /* c8 ignore next 1 */\n if (!openedItem) {\n return;\n }\n };\n\n public async selectOrToggleItem(targetItem: MenuItem): Promise<void> {\n const resolvedSelects = this.resolvedSelects;\n const oldSelectedItemsMap = new Map(this.selectedItemsMap);\n const oldSelected = this.selected.slice();\n const oldSelectedItems = this.selectedItems.slice();\n const oldValue = this.value;\n\n if (targetItem.menuData.selectionRoot !== this) {\n return;\n }\n\n if (resolvedSelects === 'multiple') {\n if (this.selectedItemsMap.has(targetItem)) {\n this.selectedItemsMap.delete(targetItem);\n } else {\n this.selectedItemsMap.set(targetItem, true);\n }\n\n // Match HTML select and set the first selected\n // item as the value. Also set the selected array\n // in the order of the menu items.\n const selected: string[] = [];\n const selectedItems: MenuItem[] = [];\n\n this.childItemSet.forEach((childItem) => {\n if (childItem.menuData.selectionRoot !== this) {\n return;\n }\n\n if (this.selectedItemsMap.has(childItem)) {\n selected.push(childItem.value);\n selectedItems.push(childItem);\n }\n });\n this._selected = selected;\n this.selectedItems = selectedItems;\n this.value = this.selected.join(this.valueSeparator);\n } else {\n this.selectedItemsMap.clear();\n this.selectedItemsMap.set(targetItem, true);\n this.value = targetItem.value;\n this._selected = [targetItem.value];\n this.selectedItems = [targetItem];\n }\n\n const applyDefault = this.dispatchEvent(\n new Event('change', {\n cancelable: true,\n bubbles: true,\n composed: true,\n })\n );\n\n if (!applyDefault) {\n // Cancel the event & don't apply the selection\n this._selected = oldSelected;\n this.selectedItems = oldSelectedItems;\n this.selectedItemsMap = oldSelectedItemsMap;\n this.value = oldValue;\n return;\n }\n // Apply the selection changes to the menu items\n if (resolvedSelects === 'single') {\n for (const oldItem of oldSelectedItemsMap.keys()) {\n if (oldItem !== targetItem) {\n oldItem.selected = false;\n }\n }\n targetItem.selected = true;\n } else if (resolvedSelects === 'multiple') {\n targetItem.selected = !targetItem.selected;\n } else if (\n !targetItem.hasSubmenu &&\n targetItem?.menuData?.focusRoot === this\n ) {\n this.dispatchEvent(new Event('close', { bubbles: true }));\n }\n }\n\n protected navigateBetweenRelatedMenus(event: MenuItemKeydownEvent): void {\n const { key, root } = event;\n const dir = this.dir;\n const shouldOpenSubmenu =\n (dir === 'ltr' && key === 'ArrowRight') ||\n (dir === 'rtl' && key === 'ArrowLeft');\n const shouldCloseSelfAsSubmenu =\n (dir === 'ltr' && key === 'ArrowLeft') ||\n (dir === 'rtl' && key === 'ArrowRight') ||\n key === 'Escape';\n const lastFocusedItem = root as MenuItem;\n\n if (this.mobileView) {\n if (shouldOpenSubmenu && lastFocusedItem?.hasSubmenu) {\n event.stopPropagation();\n this.openMobileSubmenu(lastFocusedItem);\n return;\n }\n if (shouldCloseSelfAsSubmenu && this._mobileSubmenuStack.length > 0) {\n event.stopPropagation();\n this.closeMobileSubmenu();\n return;\n }\n return;\n }\n\n const mobileRoot = this._mobileViewRoot;\n if (mobileRoot) {\n if (shouldOpenSubmenu && lastFocusedItem?.hasSubmenu) {\n event.stopPropagation();\n mobileRoot.openMobileSubmenu(lastFocusedItem);\n return;\n }\n if (shouldCloseSelfAsSubmenu) {\n event.stopPropagation();\n mobileRoot.closeMobileSubmenu();\n return;\n }\n return;\n }\n\n if (shouldOpenSubmenu) {\n if (lastFocusedItem?.hasSubmenu) {\n event.stopPropagation();\n lastFocusedItem.openOverlay(true);\n }\n } else if (shouldCloseSelfAsSubmenu && this.isSubmenu) {\n event.stopPropagation();\n this.dispatchEvent(new Event('close', { bubbles: true }));\n this.updateSelectedItemIndex();\n }\n }\n\n public handleKeydown(event: Event): void {\n if (event.defaultPrevented || !this.rovingTabindexController) {\n return;\n }\n if (this.mobileView && this._mobileSubmenuStack.length > 0) {\n const { key } = event as MenuItemKeydownEvent;\n const dir = this.dir;\n const shouldClose =\n (dir === 'ltr' && key === 'ArrowLeft') ||\n (dir === 'rtl' && key === 'ArrowRight') ||\n key === 'Escape';\n if (shouldClose) {\n event.stopPropagation();\n event.preventDefault();\n this.closeMobileSubmenu();\n }\n return;\n }\n const { key, root, shiftKey, target } = event as MenuItemKeydownEvent;\n const openSubmenuKey = ['Enter', ' '].includes(key);\n if (shiftKey && target !== this && this.hasAttribute('tabindex')) {\n this.removeAttribute('tabindex');\n const replaceTabindex = (event: FocusEvent | KeyboardEvent): void => {\n if (\n !(event as KeyboardEvent).shiftKey &&\n !this.hasAttribute('tabindex')\n ) {\n document.removeEventListener('keyup', replaceTabindex);\n this.removeEventListener('focusout', replaceTabindex);\n }\n };\n document.addEventListener('keyup', replaceTabindex);\n this.addEventListener('focusout', replaceTabindex);\n }\n if (key === 'Tab') {\n this.closeDescendentOverlays();\n return;\n }\n if (openSubmenuKey && root?.hasSubmenu && !root.open) {\n event.preventDefault();\n const mobileRoot = this._mobileViewRoot;\n if (this.mobileView) {\n this.openMobileSubmenu(root);\n } else if (mobileRoot) {\n mobileRoot.openMobileSubmenu(root);\n } else {\n root.openOverlay(true);\n }\n return;\n }\n if (key === ' ' || key === 'Enter') {\n event.preventDefault();\n root?.focusElement?.click();\n if (root) {\n this.selectOrToggleItem(root);\n }\n return;\n }\n this.navigateBetweenRelatedMenus(event as MenuItemKeydownEvent);\n }\n\n private _hasUpdatedSelectedItemIndex = false;\n\n /**\n * on focus, removes focus from focus styling item, and updates the selected item index\n */\n private prepareToCleanUp(): void {\n document.addEventListener(\n 'focusout',\n () => {\n requestAnimationFrame(() => {\n const focusedItem = this.focusInItem;\n if (focusedItem) {\n focusedItem.focused = false;\n }\n });\n },\n { once: true }\n );\n }\n\n public updateSelectedItemIndex(): void {\n let firstOrFirstSelectedIndex = 0;\n const selectedItemsMap = new Map<MenuItem, boolean>();\n const selected: string[] = [];\n const selectedItems: MenuItem[] = [];\n let itemIndex = this.childItems.length;\n while (itemIndex) {\n itemIndex -= 1;\n const childItem = this.childItems[itemIndex];\n if (childItem.menuData.selectionRoot === this) {\n if (\n childItem.selected ||\n (!this._hasUpdatedSelectedItemIndex &&\n this.selected.includes(childItem.value))\n ) {\n firstOrFirstSelectedIndex = itemIndex;\n selectedItemsMap.set(childItem, true);\n selected.unshift(childItem.value);\n selectedItems.unshift(childItem);\n }\n // Remove \"focused\" from non-\"selected\" items ONLY\n // Preserve \"focused\" on index===0 when no selection\n if (itemIndex !== firstOrFirstSelectedIndex) {\n childItem.focused = false;\n }\n }\n }\n\n this.selectedItemsMap = selectedItemsMap;\n this._selected = selected;\n this.selectedItems = selectedItems;\n this.value = this.selected.join(this.valueSeparator);\n this.focusedItemIndex = firstOrFirstSelectedIndex;\n this.focusInItemIndex = firstOrFirstSelectedIndex;\n }\n\n private _willUpdateItems = false;\n private _updateFocus?: MenuItem;\n\n private handleItemsChanged(): void {\n this.cachedChildItems = undefined;\n if (!this._willUpdateItems) {\n this._willUpdateItems = true;\n this.cacheUpdated = this.updateCache();\n }\n }\n\n private async updateCache(): Promise<void> {\n if (!this.hasUpdated) {\n await Promise.all([\n new Promise((res) => requestAnimationFrame(() => res(true))),\n this.updateComplete,\n ]);\n } else {\n await new Promise((res) => requestAnimationFrame(() => res(true)));\n }\n if (this.cachedChildItems === undefined) {\n this.updateSelectedItemIndex();\n this.updateItemFocus();\n }\n\n this._willUpdateItems = false;\n }\n\n private updateItemFocus(): void {\n this.focusInItem?.setAttribute('tabindex', '0');\n if (this.childItems.length == 0) {\n return;\n }\n }\n\n public closeDescendentOverlays(): void {\n this.descendentOverlays.forEach((overlay) => {\n overlay.open = false;\n });\n this.descendentOverlays = new Map<Overlay, Overlay>();\n }\n\n private handleSlotchange({\n target,\n }: Event & { target: HTMLSlotElement }): void {\n const assignedElements = target.assignedElements({\n flatten: true,\n }) as MenuItem[];\n if (this.childItems.length !== assignedElements.length) {\n assignedElements.forEach((item) => {\n if (typeof item.triggerUpdate !== 'undefined') {\n item.triggerUpdate();\n } else if (typeof this.asMenu(item).childItems !== 'undefined') {\n this.asMenu(item).childItems.forEach((child) => {\n child.triggerUpdate();\n });\n }\n });\n }\n if (this._updateFocus) {\n this.rovingTabindexController?.focusOnItem(this._updateFocus);\n this._updateFocus = undefined;\n }\n }\n\n protected renderMenuItemSlot(): TemplateResult {\n return html`\n <slot\n @sp-menu-submenu-opened=${this.handleDescendentOverlayOpened}\n @sp-menu-submenu-closed=${this.handleDescendentOverlayClosed}\n @slotchange=${this.handleSlotchange}\n ></slot>\n `;\n }\n\n public override render(): TemplateResult {\n const hasMobileSubmenu =\n this.mobileView && this._mobileSubmenuStack.length > 0;\n return html`\n <div\n class=${hasMobileSubmenu ? 'mobile-slot-hidden' : 'mobile-slot-wrapper'}\n >\n ${this.renderMenuItemSlot()}\n </div>\n ${hasMobileSubmenu\n ? html`\n <div\n class=\"mobile-submenu-animation-wrapper\"\n @animationend=${this._handleAnimationEnd}\n >\n <slot name=\"mobile-submenu\"></slot>\n </div>\n `\n : ''}\n `;\n }\n\n protected override firstUpdated(changed: PropertyValues): void {\n super.firstUpdated(changed);\n const updates: Promise<unknown>[] = [\n new Promise((res) => requestAnimationFrame(() => res(true))),\n ];\n [...this.children].forEach((item) => {\n if ((item as MenuItem).localName === 'sp-menu-item') {\n updates.push((item as MenuItem).updateComplete);\n }\n });\n this.childItemsUpdated = Promise.all(updates);\n }\n\n protected override updated(changes: PropertyValues<this>): void {\n super.updated(changes);\n if (changes.has('selects') && this.hasUpdated) {\n this.selectsChanged();\n }\n if (\n changes.has('label') &&\n (this.label || typeof changes.get('label') !== 'undefined')\n ) {\n if (this.label) {\n this.setAttribute('aria-label', this.label);\n /* c8 ignore next 3 */\n } else {\n this.removeAttribute('aria-label');\n }\n }\n if (changes.has('mobileBackLabel') && this.hasUpdated) {\n this._refreshMobileBackElements();\n }\n if (changes.has('mobileView') && this.hasUpdated) {\n // When mobile view is turned off at runtime, restore any\n // projected submenus to their original parents so the menu\n // returns to a normal flyout layout cleanly.\n if (!this.mobileView && this._mobileSubmenuStack.length > 0) {\n this.resetMobileSubmenus();\n }\n }\n }\n\n protected selectsChanged(): void {\n const updates: Promise<unknown>[] = [\n new Promise((res) => requestAnimationFrame(() => res(true))),\n ];\n this.childItemSet.forEach((childItem) => {\n updates.push(childItem.triggerUpdate());\n });\n this.childItemsUpdated = Promise.all(updates);\n }\n\n public override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute('role') && !this.ignore) {\n this.setAttribute('role', this.ownRole);\n }\n this.updateComplete.then(() => this.updateItemFocus());\n }\n\n private isFocusableElement(el: MenuItem): boolean {\n return el ? !el.disabled : false;\n }\n\n public override disconnectedCallback(): void {\n this.cachedChildItems = undefined;\n this.selectedItems = [];\n this.selectedItemsMap.clear();\n this.childItemSet.clear();\n this.descendentOverlays = new Map<Overlay, Overlay>();\n this.resetMobileSubmenus();\n this._mobileSubmenuOriginalParents.clear();\n super.disconnectedCallback();\n }\n\n protected childItemsUpdated!: Promise<unknown[]>;\n protected cacheUpdated = Promise.resolve();\n /* c8 ignore next 3 */\n protected resolveCacheUpdated = (): void => {\n return;\n };\n\n protected override async getUpdateComplete(): Promise<boolean> {\n const complete = (await super.getUpdateComplete()) as boolean;\n await this.childItemsUpdated;\n await this.cacheUpdated;\n return complete;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["html", "render", "SizedMixin", "SpectrumElement", "property", "query", "state", "RovingTabindexController", "menuStyles", "MenuItem", "event", "key", "target", "mobileRoot", "c", "el", "elements", "firstEnabledIndex", "firstSelectedIndex", "index", "element", "value", "item", "closedItem", "previous", "submenuEl", "backItem", "submenu", "child", "_a", "firstItem", "direction", "wrapper", "i", "currentlyVisible", "parentElement", "savedChildItems", "originalParent", "container", "_container", "selected", "old", "itemsList", "slottedElements", "slottedElement", "flattenedChildren", "_b", "ignoreMenu", "selects", "preventScroll", "selectedItem", "childItem", "currentY", "deltaY", "deltaTime", "menuItem", "before", "diff", "newIndex", "targetItem", "resolvedSelects", "oldSelectedItemsMap", "oldSelected", "oldSelectedItems", "oldValue", "selectedItems", "oldItem", "root", "dir", "shouldOpenSubmenu", "shouldCloseSelfAsSubmenu", "lastFocusedItem", "shiftKey", "openSubmenuKey", "replaceTabindex", "focusedItem", "firstOrFirstSelectedIndex", "selectedItemsMap", "itemIndex", "res", "overlay", "assignedElements", "hasMobileSubmenu", "changed", "updates", "changes", "complete", "__decorateClass"]
7
7
  }
@@ -10,10 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  import { CSSResultArray, PropertyValues, SpectrumElement } from '@spectrum-web-components/base';
13
- declare const MenuDivider_base: typeof SpectrumElement & {
14
- new (...args: any[]): import("@spectrum-web-components/base").SizedElementInterface;
15
- prototype: import("@spectrum-web-components/base").SizedElementInterface;
16
- } & import("@spectrum-web-components/core/mixins/sized-mixin.js").SizedElementConstructor;
13
+ declare const MenuDivider_base: typeof SpectrumElement & import("@spectrum-web-components/base").Constructor<import("@spectrum-web-components/base").SizedElementInterface> & import("@spectrum-web-components/base").SizedElementConstructor;
17
14
  /**
18
15
  * @element sp-menu-divider
19
16
  */
package/src/MenuItem.d.ts CHANGED
@@ -49,6 +49,12 @@ export declare class MenuItemKeydownEvent extends KeyboardEvent {
49
49
  get metaKey(): boolean;
50
50
  get repeat(): boolean;
51
51
  get shiftKey(): boolean;
52
+ /**
53
+ * Original `KeyboardEvent` that triggered this forwarded event,
54
+ * exposed so listeners on the parent menu can call
55
+ * `preventDefault()`/`stopPropagation()` on the underlying event.
56
+ */
57
+ get nativeEvent(): KeyboardEvent | undefined;
52
58
  }
53
59
  export type MenuItemChildren = {
54
60
  icon: Element[];
@@ -113,7 +119,24 @@ export declare class MenuItem extends MenuItem_base {
113
119
  noWrap: boolean;
114
120
  private anchorElement;
115
121
  overlayElement: Overlay;
116
- private submenuElement?;
122
+ /**
123
+ * Reference to the slotted submenu element, captured by `manageSubmenu`.
124
+ * Public so the parent `<sp-menu>` can project this submenu for mobile
125
+ * drill-down and so tests can assert on its contents.
126
+ *
127
+ * @internal
128
+ */
129
+ submenuElement?: HTMLElement;
130
+ /**
131
+ * Set by the parent Menu when the submenu element is projected to the
132
+ * mobile-submenu slot. Guards against `manageSubmenu` clearing
133
+ * `hasSubmenu` when the slot appears empty during projection.
134
+ * Cross-class implementation detail; do not depend on it from outside
135
+ * the `@spectrum-web-components/menu` package.
136
+ *
137
+ * @internal
138
+ */
139
+ _mobileSubmenuProjected: boolean;
117
140
  /**
118
141
  * the focusable element of the menu item
119
142
  */
@@ -145,10 +168,18 @@ export declare class MenuItem extends MenuItem_base {
145
168
  private proxyFocus;
146
169
  private shouldProxyClick;
147
170
  protected breakItemChildrenCache(): void;
171
+ private get isMobileView();
172
+ /**
173
+ * Returns the root sp-menu with mobile-view, traversing up from
174
+ * either the focusRoot or the DOM tree.
175
+ */
176
+ private get _mobileRootMenu();
148
177
  protected renderSubmenu(): TemplateResult;
149
178
  protected render(): TemplateResult;
150
179
  /**
151
- * determines if item has a submenu and updates the `aria-haspopup` attribute
180
+ * Determines if item has a submenu and updates the `aria-haspopup` attribute.
181
+ * Skips clearing state when the submenu is temporarily projected to the
182
+ * parent Menu's mobile-submenu slot.
152
183
  */
153
184
  protected manageSubmenu(event: Event & {
154
185
  target: HTMLSlotElement;
@@ -100,6 +100,14 @@ export class MenuItemKeydownEvent extends KeyboardEvent {
100
100
  var _a;
101
101
  return ((_a = this._event) == null ? void 0 : _a.shiftKey) || false;
102
102
  }
103
+ /**
104
+ * Original `KeyboardEvent` that triggered this forwarded event,
105
+ * exposed so listeners on the parent menu can call
106
+ * `preventDefault()`/`stopPropagation()` on the underlying event.
107
+ */
108
+ get nativeEvent() {
109
+ return this._event;
110
+ }
103
111
  }
104
112
  export class MenuItem extends LikeAnchor(
105
113
  ObserveSlotText(ObserveSlotPresence(Focusable, '[slot="icon"]'))
@@ -113,6 +121,16 @@ export class MenuItem extends LikeAnchor(
113
121
  this._value = "";
114
122
  this.hasSubmenu = false;
115
123
  this.noWrap = false;
124
+ /**
125
+ * Set by the parent Menu when the submenu element is projected to the
126
+ * mobile-submenu slot. Guards against `manageSubmenu` clearing
127
+ * `hasSubmenu` when the slot appears empty during projection.
128
+ * Cross-class implementation detail; do not depend on it from outside
129
+ * the `@spectrum-web-components/menu` package.
130
+ *
131
+ * @internal
132
+ */
133
+ this._mobileSubmenuProjected = false;
116
134
  this.open = false;
117
135
  /**
118
136
  * whether menu item's submenu is opened via keyboard
@@ -133,13 +151,20 @@ export class MenuItem extends LikeAnchor(
133
151
  this.focus();
134
152
  };
135
153
  this.handleTouchSubmenuToggle = (event) => {
136
- var _a;
154
+ var _a, _b;
137
155
  if (event.pointerId !== this._activePointerId) {
138
156
  return;
139
157
  }
140
158
  (_a = this._touchAbortController) == null ? void 0 : _a.abort();
141
159
  this._touchHandledViaPointerup = true;
142
160
  this._activePointerId = void 0;
161
+ if (this.isMobileView) {
162
+ (_b = this._mobileRootMenu) == null ? void 0 : _b.openMobileSubmenu(this);
163
+ setTimeout(() => {
164
+ this._touchHandledViaPointerup = false;
165
+ }, 0);
166
+ return;
167
+ }
143
168
  if (this.open) {
144
169
  this.open = false;
145
170
  } else {
@@ -164,7 +189,8 @@ export class MenuItem extends LikeAnchor(
164
189
  this.handleKeydown = (event) => {
165
190
  const { target, key } = event;
166
191
  const openSubmenuKey = this.hasSubmenu && !this.open && [" ", "Enter"].includes(key);
167
- if (target === this) {
192
+ const targetIsThisItem = target === this || target instanceof Element && target.closest("sp-menu-item") === this;
193
+ if (targetIsThisItem) {
168
194
  if (["ArrowLeft", "ArrowRight", "Escape"].includes(key) || openSubmenuKey) {
169
195
  event.preventDefault();
170
196
  }
@@ -294,6 +320,20 @@ export class MenuItem extends LikeAnchor(
294
320
  this._itemChildren = void 0;
295
321
  this.triggerUpdate();
296
322
  }
323
+ get isMobileView() {
324
+ return !!this._mobileRootMenu;
325
+ }
326
+ /**
327
+ * Returns the root sp-menu with mobile-view, traversing up from
328
+ * either the focusRoot or the DOM tree.
329
+ */
330
+ get _mobileRootMenu() {
331
+ const focusRoot = this.menuData.focusRoot;
332
+ if (focusRoot == null ? void 0 : focusRoot.mobileView) {
333
+ return focusRoot;
334
+ }
335
+ return this.closest("sp-menu[mobile-view]");
336
+ }
297
337
  renderSubmenu() {
298
338
  const slot = html`
299
339
  <slot
@@ -311,6 +351,14 @@ export class MenuItem extends LikeAnchor(
311
351
  if (!this.hasSubmenu) {
312
352
  return slot;
313
353
  }
354
+ if (this.isMobileView) {
355
+ return html`
356
+ <div class="mobile-submenu-slot-hidden">${slot}</div>
357
+ <sp-icon-chevron100
358
+ class="spectrum-UIIcon-ChevronRight100 chevron icon"
359
+ ></sp-icon-chevron100>
360
+ `;
361
+ }
314
362
  this.dependencyManager.add("sp-overlay");
315
363
  this.dependencyManager.add("sp-popover");
316
364
  import("@spectrum-web-components/overlay/sp-overlay.js");
@@ -370,12 +418,18 @@ export class MenuItem extends LikeAnchor(
370
418
  `;
371
419
  }
372
420
  /**
373
- * determines if item has a submenu and updates the `aria-haspopup` attribute
421
+ * Determines if item has a submenu and updates the `aria-haspopup` attribute.
422
+ * Skips clearing state when the submenu is temporarily projected to the
423
+ * parent Menu's mobile-submenu slot.
374
424
  */
375
425
  manageSubmenu(event) {
376
- this.submenuElement = event.target.assignedElements({
426
+ const assigned = event.target.assignedElements({
377
427
  flatten: true
378
428
  })[0];
429
+ if (!assigned && this._mobileSubmenuProjected) {
430
+ return;
431
+ }
432
+ this.submenuElement = assigned;
379
433
  this.hasSubmenu = !!this.submenuElement;
380
434
  if (this.hasSubmenu) {
381
435
  this.setAttribute("aria-haspopup", "true");
@@ -503,6 +557,13 @@ export class MenuItem extends LikeAnchor(
503
557
  }
504
558
  }
505
559
  handleSubmenuClick(event) {
560
+ var _a;
561
+ if (this.isMobileView) {
562
+ event.stopPropagation();
563
+ event.preventDefault();
564
+ (_a = this._mobileRootMenu) == null ? void 0 : _a.openMobileSubmenu(this);
565
+ return;
566
+ }
506
567
  if (this._touchHandledViaPointerup) {
507
568
  event.stopPropagation();
508
569
  event.preventDefault();
@@ -520,7 +581,7 @@ export class MenuItem extends LikeAnchor(
520
581
  });
521
582
  }
522
583
  handlePointerenter(event) {
523
- if (event.pointerType === "touch") {
584
+ if (event.pointerType === "touch" || this.isMobileView) {
524
585
  return;
525
586
  }
526
587
  if (this.leaveTimeout) {