Skip to content

Commit

Permalink
chore(components): update the post-menu to be usable inside other com… (
Browse files Browse the repository at this point in the history
#4337)

…ponents
  • Loading branch information
alizedebray authored Dec 19, 2024
1 parent 277ef0d commit 85297d3
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 28 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 (
<Host role="menuitem" data-version={version}>
Expand Down
7 changes: 6 additions & 1 deletion packages/components/src/components/post-menu/post-menu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

.popover-container {
display: flex;
flex-direction: column;
}
44 changes: 28 additions & 16 deletions packages/components/src/components/post-menu/post-menu.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLElement> => {
return element.querySelectorAll(`& > ${focusableSelector}:not(${focusDisablingSelector})`);
};
2 changes: 1 addition & 1 deletion packages/components/src/utils/get-root.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down

0 comments on commit 85297d3

Please sign in to comment.