diff --git a/packages/components/src/components/post-menu-item/post-menu-item.scss b/packages/components/src/components/post-menu-item/post-menu-item.scss deleted file mode 100644 index f09d32524a..0000000000 --- a/packages/components/src/components/post-menu-item/post-menu-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: block; -} \ No newline at end of file diff --git a/packages/components/src/components/post-menu-item/post-menu-item.tsx b/packages/components/src/components/post-menu-item/post-menu-item.tsx index b4c7de0469..5f6685d5d9 100644 --- a/packages/components/src/components/post-menu-item/post-menu-item.tsx +++ b/packages/components/src/components/post-menu-item/post-menu-item.tsx @@ -1,14 +1,10 @@ -import { Component, h, Element, Host } from '@stencil/core'; +import { Component, h, Host } from '@stencil/core'; import { version } from '@root/package.json'; @Component({ tag: 'post-menu-item', - shadow: true, - styleUrl: 'post-menu-item.scss', }) export class PostMenuItem { - @Element() host: HTMLPostMenuItemElement; - render() { return ( diff --git a/packages/components/src/components/post-menu/post-menu.scss b/packages/components/src/components/post-menu/post-menu.scss index 95b15284b7..d207ad3f3d 100644 --- a/packages/components/src/components/post-menu/post-menu.scss +++ b/packages/components/src/components/post-menu/post-menu.scss @@ -8,4 +8,9 @@ post-popovercontainer { padding: var(--post-menu-padding); background-color: var(--post-menu-bg, #ffffff); border-color: var(--post-menu-bg, #ffffff); -} \ No newline at end of file +} + +.popover-container { + display: flex; + flex-direction: column; +} diff --git a/packages/components/src/components/post-menu/post-menu.tsx b/packages/components/src/components/post-menu/post-menu.tsx index 865a24a729..775c831172 100644 --- a/packages/components/src/components/post-menu/post-menu.tsx +++ b/packages/components/src/components/post-menu/post-menu.tsx @@ -1,7 +1,17 @@ -import { Component, Element, Event, EventEmitter, h, Host, Method, Prop, State } from '@stencil/core'; +import { + Component, + Element, + Event, + EventEmitter, + h, + Host, + Method, + Prop, + State, +} from '@stencil/core'; import { Placement } from '@floating-ui/dom'; import { version } from '@root/package.json'; -import { isFocusable } from '@/utils/is-focusable'; +import { getFocusableChildren } from '@/utils/get-focusable-children'; import { getRoot } from '@/utils'; @Component({ @@ -76,7 +86,7 @@ export class PostMenu { /** * Displays the popover menu, focusing the first menu item. - * + * * @param target - The HTML element relative to which the popover menu should be displayed. */ @Method() @@ -131,12 +141,15 @@ export class PostMenu { private controlKeyDownHandler(e: KeyboardEvent) { const menuItems = this.getSlottedItems(); + if (!menuItems.length) { return; } - const currentFocusedElement = this.root.activeElement as HTMLElement; // Use root's activeElement - let currentIndex = menuItems.findIndex(el => el === currentFocusedElement); + let currentIndex = menuItems.findIndex(el => { + // Check if the item is currently focused within its rendered scope (document or shadow root) + return el === getRoot(el).activeElement; + }); switch (e.key) { case this.KEYCODES.UP: @@ -169,20 +182,19 @@ export class PostMenu { } } - private getSlottedItems() { + private getSlottedItems(): Element[] { const slot = this.host.shadowRoot.querySelector('slot'); const slottedElements = slot ? slot.assignedElements() : []; - const menuItems = slottedElements - .filter(el => el.tagName === 'POST-MENU-ITEM') - .map(el => { - const slot = el.shadowRoot.querySelector('slot'); - const assignedElements = slot ? slot.assignedElements() : []; - return assignedElements.filter(isFocusable); - }) - .flat(); - - return menuItems; + return ( + slottedElements + // If the element is a slot, get the assigned elements + .flatMap(el => (el instanceof HTMLSlotElement ? el.assignedElements() : el)) + // Filter out elements that have a 'menuitem' role + .filter(el => el.getAttribute('role') === 'menuitem') + // For each menu item, get any focusable children (e.g., buttons, links) + .flatMap(el => Array.from(getFocusableChildren(el))) + ); } render() { diff --git a/packages/components/src/utils/is-focusable.ts b/packages/components/src/utils/get-focusable-children.ts similarity index 74% rename from packages/components/src/utils/is-focusable.ts rename to packages/components/src/utils/get-focusable-children.ts index 66aa7d74dc..12a33efc02 100644 --- a/packages/components/src/utils/is-focusable.ts +++ b/packages/components/src/utils/get-focusable-children.ts @@ -21,8 +21,9 @@ const focusDisablingSelector = `:where(${[ '[popover]:not(:popover-open) *', 'details:not([open]) > *:not(details > summary:first-of-type)', 'details:not([open]) > *:not(details > summary:first-of-type) *', + '[tabindex^="-"]', ].join(',')})`; -export const isFocusable = (element: Element) => { - return element?.matches(focusableSelector) && !element?.matches(focusDisablingSelector); +export const getFocusableChildren = (element: Element): NodeListOf => { + return element.querySelectorAll(`& > ${focusableSelector}:not(${focusDisablingSelector})`); }; diff --git a/packages/components/src/utils/get-root.ts b/packages/components/src/utils/get-root.ts index 0c84953d8c..ad33d956ab 100644 --- a/packages/components/src/utils/get-root.ts +++ b/packages/components/src/utils/get-root.ts @@ -1,4 +1,4 @@ -export function getRoot(element: HTMLElement): Document | ShadowRoot { +export function getRoot(element: Element): Document | ShadowRoot { const root = element.getRootNode(); if (root instanceof Document || root instanceof ShadowRoot) {