From 5b7ded8469369a4d1926b8c51ed416b30a651572 Mon Sep 17 00:00:00 2001 From: Zacky Ma Date: Tue, 5 May 2026 15:27:22 -0700 Subject: [PATCH 1/2] feat(web-components): adopt focusgroup and its polyfill (#35884) --- ...-b9ad2d8f-37a9-4a6d-b6fb-9d498fa34fb5.json | 7 + package.json | 2 +- .../web-components/docs/web-components.api.md | 59 ++-- packages/web-components/package.json | 4 +- .../src/_docs/developer/polyfilling.mdx | 14 + .../src/menu-item/menu-item.template.ts | 2 +- .../web-components/src/menu-item/menu-item.ts | 30 +- .../src/menu-list/menu-list.base.ts | 160 ++-------- .../src/menu-list/menu-list.spec.ts | 2 +- .../src/menu-list/menu-list.stories.ts | 15 + .../src/menu-list/menu-list.template.ts | 6 +- .../web-components/src/menu-list/menu-list.ts | 33 +- packages/web-components/src/menu/menu.ts | 7 +- .../src/radio-group/radio-group.base.ts | 146 +++------ .../src/radio-group/radio-group.spec.ts | 62 +++- .../src/radio-group/radio-group.template.ts | 2 +- .../src/radio-group/radio-group.ts | 34 +- packages/web-components/src/tab/tab.styles.ts | 18 ++ .../web-components/src/tab/tab.template.ts | 2 +- packages/web-components/src/tab/tab.ts | 18 +- .../src/tablist/tablist.base.ts | 299 +++++------------- .../src/tablist/tablist.spec.ts | 58 ++-- .../src/tablist/tablist.stories.ts | 10 +- .../src/tablist/tablist.styles.ts | 146 +++++---- .../src/tablist/tablist.template.ts | 6 +- .../web-components/src/tablist/tablist.ts | 165 ++-------- .../src/tree-item/tree-item.base.ts | 45 ++- .../src/tree-item/tree-item.template.ts | 2 +- packages/web-components/src/tree/tree.base.ts | 149 ++------- packages/web-components/src/tree/tree.spec.ts | 6 +- .../web-components/src/tree/tree.template.ts | 5 +- packages/web-components/src/tree/tree.ts | 33 ++ .../web-components/src/utils/focusgroup.ts | 47 +++ yarn.lock | 8 +- 34 files changed, 696 insertions(+), 906 deletions(-) create mode 100644 change/@fluentui-web-components-b9ad2d8f-37a9-4a6d-b6fb-9d498fa34fb5.json create mode 100644 packages/web-components/src/utils/focusgroup.ts diff --git a/change/@fluentui-web-components-b9ad2d8f-37a9-4a6d-b6fb-9d498fa34fb5.json b/change/@fluentui-web-components-b9ad2d8f-37a9-4a6d-b6fb-9d498fa34fb5.json new file mode 100644 index 00000000000000..e7ddd4bfa239e6 --- /dev/null +++ b/change/@fluentui-web-components-b9ad2d8f-37a9-4a6d-b6fb-9d498fa34fb5.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "adopt focusgroup and polyfill", + "packageName": "@fluentui/web-components", + "email": "machi@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index 586e301ed95135..6da693a8bdd197 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@microsoft/api-extractor": "7.51.0", "@microsoft/api-extractor-model": "7.31.2", "@microsoft/eslint-plugin-sdl": "1.0.1", - "@microsoft/focusgroup-polyfill": "^1.2.1", + "@microsoft/focusgroup-polyfill": "^1.3.0", "@microsoft/load-themed-styles": "1.10.26", "@microsoft/loader-load-themed-styles": "2.0.17", "@microsoft/tsdoc": "0.15.1", diff --git a/packages/web-components/docs/web-components.api.md b/packages/web-components/docs/web-components.api.md index 3d4298702c8398..ee7caf3dacb89b 100644 --- a/packages/web-components/docs/web-components.api.md +++ b/packages/web-components/docs/web-components.api.md @@ -739,11 +739,7 @@ export class BaseMenuList extends FASTElement { elementInternals: ElementInternals; focus(): void; handleChange(source: any, propertyName: string): void; - // @internal - handleFocusOut: (e: FocusEvent) => void; - // @internal (undocumented) - handleMenuKeyDown(e: KeyboardEvent): void | boolean; - protected isMenuItemElement: (el: Element) => el is HTMLElement; + protected isMenuItemElement(el: Element): el is MenuItem; // @internal (undocumented) readonly isNestedMenu: () => boolean; // @internal (undocumented) @@ -751,7 +747,9 @@ export class BaseMenuList extends FASTElement { // (undocumented) protected itemsChanged(oldValue: HTMLElement[], newValue: HTMLElement[]): void; // (undocumented) - protected menuItems: Element[] | undefined; + protected menuChildren: HTMLElement[] | undefined; + // (undocumented) + protected menuItems: MenuItem[] | undefined; // (undocumented) protected setItems(): void; } @@ -808,8 +806,6 @@ export class BaseRadioGroup extends FASTElement { focus(): void; // @internal focusinHandler(e: FocusEvent): boolean | void; - // @internal - focusoutHandler(e: FocusEvent): boolean | void; static formAssociated: boolean; // (undocumented) formResetCallback(): void; @@ -893,22 +889,26 @@ export class BaseTablist extends FASTElement { // @internal (undocumented) protected activeidChanged(oldValue: string, newValue: string): void; activetab: Tab; - adjust(adjustment: number): void; // @internal (undocumented) connectedCallback(): void; disabled: boolean; - // @internal + // @internal (undocumented) protected disabledChanged(prev: boolean, next: boolean): void; // @internal elementInternals: ElementInternals; - orientation: TablistOrientation; // @internal (undocumented) + handleFocusIn(event: FocusEvent): void; + orientation: TablistOrientation; + // (undocumented) protected orientationChanged(prev: TablistOrientation, next: TablistOrientation): void; - protected setTabs(): void; + protected setTabs({ connectToPanel, forceDisabled }?: { + connectToPanel?: boolean | undefined; + forceDisabled?: boolean | undefined; + }): void; // @internal slottedTabs: Node[]; - // @internal - slottedTabsChanged(prev: Node[] | undefined, next: Node[] | undefined): void; + // @internal (undocumented) + protected slottedTabsChanged(prev: Node[] | undefined, next: Node[] | undefined): void; // @internal (undocumented) tabs: Tab[]; // @internal (undocumented) @@ -1076,8 +1076,6 @@ export class BaseTextInput extends FASTElement { export class BaseTree extends FASTElement { constructor(); // @internal - blurHandler(e: FocusEvent): void; - // @internal changeHandler(e: Event): boolean | void; // Warning: (ae-forgotten-export) The symbol "BaseTreeItem" needs to be exported by the entry point index.d.ts // @@ -1087,17 +1085,14 @@ export class BaseTree extends FASTElement { childTreeItemsChanged(): void; // @internal clickHandler(e: Event): boolean | void; - // (undocumented) - connectedCallback(): void; currentSelected: HTMLElement | null; // @internal (undocumented) defaultSlot: HTMLSlotElement; // @internal defaultSlotChanged(): void; + protected get descendantTreeItems(): BaseTreeItem[]; // @internal elementInternals: ElementInternals; - // @internal - focusHandler(e: FocusEvent): void; // @internal (undocumented) handleDefaultSlotChange(): void; // @internal @@ -3287,6 +3282,10 @@ export class MenuItem extends FASTElement { handleMouseOut: (e: MouseEvent) => boolean; // @internal (undocumented) handleMouseOver: (e: MouseEvent) => boolean; + // @internal (undocumented) + handleSubmenuFocusOut: (e: FocusEvent) => void; + // @internal + handleToggle: (e: Event) => void; hidden: boolean; role: MenuItemRole; roleChanged(prev: MenuItemRole | undefined, next: MenuItemRole | undefined): void; @@ -3298,8 +3297,6 @@ export class MenuItem extends FASTElement { protected slottedSubmenuChanged(prev: HTMLElement[] | undefined, next: HTMLElement[]): void; // @internal (undocumented) submenu: HTMLElement | undefined; - // @internal - toggleHandler: (e: Event) => void; } // @internal @@ -3340,6 +3337,10 @@ export const MenuItemTemplate: ElementViewTemplate; // @public export class MenuList extends BaseMenuList { + // (undocumented) + disconnectedCallback(): void; + // (undocumented) + setItems(): void; } // @public (undocumented) @@ -3486,6 +3487,10 @@ export const RadioDefinition: FASTElementDefinition; // @public export class RadioGroup extends BaseRadioGroup { + // (undocumented) + disconnectedCallback(): void; + // (undocumented) + radiosChanged(prev: Radio[] | undefined, next: Radio[] | undefined): void; } // @public @@ -3955,6 +3960,8 @@ export class Tab extends FASTElement { // (undocumented) connectedCallback(): void; disabled: boolean; + // (undocumented) + protected disabledChanged(prev: boolean, next: boolean): void; // @internal elementInternals: ElementInternals; } @@ -3970,9 +3977,11 @@ export const TabDefinition: FASTElementDefinition; // @public export class Tablist extends BaseTablist { - activeidChanged(oldValue: string, newValue: string): void; appearance?: TablistAppearance; + // (undocumented) + disconnectedCallback(): void; size?: TablistSize; + // (undocumented) tabsChanged(prev: Tab[] | undefined, next: Tab[] | undefined): void; } @@ -4369,6 +4378,10 @@ export class Tree extends BaseTree { protected appearanceChanged(): void; // @internal childTreeItemsChanged(): void; + // (undocumented) + disconnectedCallback(): void; + // @internal (undocumented) + itemToggleHandler(): void; size: TreeItemSize; // (undocumented) protected sizeChanged(): void; diff --git a/packages/web-components/package.json b/packages/web-components/package.json index d6c3726f90a2f8..cc1b9b7c7df83f 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -77,6 +77,7 @@ "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.10", "@microsoft/fast-element": "2.0.0", + "@microsoft/focusgroup-polyfill": "^1.3.0", "@tensile-perf/web-components": "~0.2.2", "@storybook/html": "9.1.17", "@storybook/html-vite": "9.1.17", @@ -93,7 +94,8 @@ "tslib": "^2.1.0" }, "peerDependencies": { - "@microsoft/fast-element": "^2.0.0" + "@microsoft/fast-element": "^2.0.0", + "@microsoft/focusgroup-polyfill": "^1.3.0" }, "beachball": { "disallowedChangeTypes": [ diff --git a/packages/web-components/src/_docs/developer/polyfilling.mdx b/packages/web-components/src/_docs/developer/polyfilling.mdx index 70e19d36cad9bf..2e24491fcb8d53 100644 --- a/packages/web-components/src/_docs/developer/polyfilling.mdx +++ b/packages/web-components/src/_docs/developer/polyfilling.mdx @@ -94,3 +94,17 @@ if (!CSS.supports('anchor-name: --foo')) { import { default as applyPolyfill } from '@oddbird/css-anchor-positioning/fn'; window.CSS_ANCHOR_POLYFILL = applyPolyfill; ``` + +## HTML Focusgroup + +For components that require directional navigations (moving focus between focusable elements within a component with arrow keys instead of tab), Fluent Web Components have adopted [HTML Focusgroup](https://open-ui.org/components/scoped-focusgroup.explainer/). The components that currently use focusgroup include MenuList/MenuItem, RadioGroup/Radio, Tablist/Tab, and Tree/TreeItem. + +For browsers that don’t yet support focusgroup, we use [`@microsoft/focusgroup-polyfill`](https://github.com/microsoft/polyfills/tree/main/packages/focusgroup) and automatically polyfill the components when they are connected to the DOM. + +If you want to opt out of the polyfill, you can use the base classes of these components, e.g. + +```js +import { BaseTablist } from '@fluentui/web-components/tablist/base.js'; + +export class MyTablist extends BaseTablist {} +``` diff --git a/packages/web-components/src/menu-item/menu-item.template.ts b/packages/web-components/src/menu-item/menu-item.template.ts index 9fc26092f8bb38..244e0e36ecb607 100644 --- a/packages/web-components/src/menu-item/menu-item.template.ts +++ b/packages/web-components/src/menu-item/menu-item.template.ts @@ -18,7 +18,7 @@ export function menuItemTemplate(options: MenuItemOptions = @click="${(x, c) => x.handleMenuItemClick(c.event as MouseEvent)}" @mouseover="${(x, c) => x.handleMouseOver(c.event as MouseEvent)}" @mouseout="${(x, c) => x.handleMouseOut(c.event as MouseEvent)}" - @toggle="${(x, c) => x.toggleHandler(c.event as ToggleEvent)}" + @toggle="${(x, c) => x.handleToggle(c.event as ToggleEvent)}" > ${staticallyCompose(options.indicator)} ${startSlotTemplate(options)} diff --git a/packages/web-components/src/menu-item/menu-item.ts b/packages/web-components/src/menu-item/menu-item.ts index b5e6e4b73c8efc..b6ea94d1f3bdd0 100644 --- a/packages/web-components/src/menu-item/menu-item.ts +++ b/packages/web-components/src/menu-item/menu-item.ts @@ -133,12 +133,15 @@ export class MenuItem extends FASTElement { * @internal */ protected slottedSubmenuChanged(prev: HTMLElement[] | undefined, next: HTMLElement[]) { - this.submenu?.removeEventListener('toggle', this.toggleHandler); + this.submenu?.removeEventListener('toggle', this.handleToggle); + this.submenu?.removeEventListener('focusout', this.handleSubmenuFocusOut); if (next.length) { this.submenu = next[0]; this.submenu.toggleAttribute('popover', true); - this.submenu.addEventListener('toggle', this.toggleHandler); + this.submenu.setAttribute('focusgroup', 'none'); + this.submenu.addEventListener('toggle', this.handleToggle); + this.submenu.addEventListener('focusout', this.handleSubmenuFocusOut); this.elementInternals.ariaHasPopup = 'menu'; toggleState(this.elementInternals, 'submenu', true); } else { @@ -236,16 +239,29 @@ export class MenuItem extends FASTElement { * Setup required ARIA on open/close * @internal */ - public toggleHandler = (e: Event): void => { - if (e instanceof ToggleEvent && e.newState === 'open') { - this.setAttribute('tabindex', '-1'); + public handleToggle = (e: Event): void => { + if (!(e instanceof ToggleEvent)) { + return; + } + + if (e.newState === 'open') { this.elementInternals.ariaExpanded = 'true'; this.setSubmenuPosition(); } - if (e instanceof ToggleEvent && e.newState === 'closed') { + if (e.newState === 'closed') { this.elementInternals.ariaExpanded = 'false'; - this.setAttribute('tabindex', '0'); } + + this.submenu?.setAttribute('focusgroup', e.newState === 'open' ? 'menu' : 'none'); + }; + + /** @internal */ + public handleSubmenuFocusOut = (e: FocusEvent) => { + if (e.relatedTarget && this.submenu?.contains(e.relatedTarget as Node)) { + return; + } + + this.submenu?.togglePopover(false); }; /** diff --git a/packages/web-components/src/menu-list/menu-list.base.ts b/packages/web-components/src/menu-list/menu-list.base.ts index 2071db75c55a7d..404189e9668103 100644 --- a/packages/web-components/src/menu-list/menu-list.base.ts +++ b/packages/web-components/src/menu-list/menu-list.base.ts @@ -1,11 +1,11 @@ -import { FASTElement, Observable, observable, Updates } from '@microsoft/fast-element'; -import { isHTMLElement, keyArrowDown, keyArrowUp, keyEnd, keyHome } from '@microsoft/fast-web-utilities'; +import { FASTElement, observable, Updates } from '@microsoft/fast-element'; +import { isHTMLElement } from '@microsoft/fast-web-utilities'; import type { MenuItemColumnCount } from '../menu-item/menu-item.js'; import type { MenuItem } from '../menu-item/menu-item.js'; import { isMenuItem, MenuItemRole } from '../menu-item/menu-item.options.js'; /** - * A Base Menu List Custom HTML Element. + * A Base MenuList Custom HTML Element. * Implements the {@link https://www.w3.org/TR/wai-aria-1.1/#menu | ARIA menu }. * * @public @@ -27,18 +27,14 @@ export class BaseMenuList extends FASTElement { // only update children after the component is connected and // the setItems has run on connectedCallback // (menuItems is undefined until then) - if (this.$fastController.isConnected && this.menuItems !== undefined) { + if (this.$fastController.isConnected && this.menuChildren !== undefined) { this.setItems(); } } - protected menuItems: Element[] | undefined; + protected menuChildren: HTMLElement[] | undefined; - /** - * The index of the focusable element in the items array - * defaults to -1 - */ - private focusIndex: number = -1; + protected menuItems: MenuItem[] | undefined; private static focusableElementRoles = MenuItemRole; @@ -67,8 +63,7 @@ export class BaseMenuList extends FASTElement { */ public disconnectedCallback(): void { super.disconnectedCallback(); - this.removeItemListeners(); - this.menuItems = undefined; + this.menuChildren = undefined; this.removeEventListener('change', this.changedMenuItemHandler); } @@ -89,71 +84,7 @@ export class BaseMenuList extends FASTElement { * @public */ public focus(): void { - this.setFocus(0, 1); - } - - /** - * @internal - */ - public handleMenuKeyDown(e: KeyboardEvent): void | boolean { - if (e.defaultPrevented || this.menuItems === undefined) { - return; - } - switch (e.key) { - case keyArrowDown: - // go forward one index - this.setFocus(this.focusIndex + 1, 1); - return; - case keyArrowUp: - // go back one index - this.setFocus(this.focusIndex - 1, -1); - return; - case keyEnd: - // set focus on last item - this.setFocus(this.menuItems.length - 1, -1); - return; - case keyHome: - // set focus on first item - this.setFocus(0, 1); - return; - default: - // if we are not handling the event, do not prevent default - return true; - } - } - - /** - * if focus is moving out of the menu, reset to a stable initial state - * @internal - */ - public handleFocusOut = (e: FocusEvent) => { - if (!this.contains(e.relatedTarget as Element) && this.menuItems !== undefined) { - // find our first focusable element - const focusIndex: number = this.menuItems.findIndex(this.isFocusableElement); - // set the current focus index's tabindex to -1 - this.menuItems[this.focusIndex].setAttribute('tabindex', '-1'); - // set the first focusable element tabindex to 0 - this.menuItems[focusIndex].setAttribute('tabindex', '0'); - // set the focus index - this.focusIndex = focusIndex; - } - }; - - private handleItemFocus = (e: FocusEvent) => { - const targetItem: HTMLElement = e.target as HTMLElement; - - if (this.menuItems !== undefined && targetItem !== this.menuItems[this.focusIndex]) { - this.menuItems[this.focusIndex].setAttribute('tabindex', '-1'); - this.focusIndex = this.menuItems.indexOf(targetItem); - targetItem.setAttribute('tabindex', '0'); - } - }; - - private removeItemListeners(items: HTMLElement[] = this.items): void { - items.forEach(item => { - item.removeEventListener('focus', this.handleItemFocus); - Observable.getNotifier(item).unsubscribe(this, 'hidden'); - }); + this.menuItems?.find(item => !item.disabled)?.focus(); } private static elementIndent(el: HTMLElement): MenuItemColumnCount { @@ -170,39 +101,21 @@ export class BaseMenuList extends FASTElement { protected setItems(): void { const children: HTMLElement[] = Array.from(this.children) as HTMLElement[]; - this.removeItemListeners(children); - - children.forEach((child: Element) => Observable.getNotifier(child).subscribe(this, 'hidden')); - - const newItems: Element[] = children.filter(child => !child.hasAttribute('hidden')); - - this.menuItems = newItems; - - const menuItems = this.menuItems.filter(this.isMenuItemElement); - - // if our focus index is not -1 we have items - if (menuItems.length) { - this.focusIndex = 0; - } - - menuItems.forEach((item: HTMLElement, index: number) => { - item.setAttribute('tabindex', index === 0 ? '0' : '-1'); - item.addEventListener('focus', this.handleItemFocus); - }); + this.menuChildren = children.filter(child => !child.hasAttribute('hidden')); /** * Set the indent attribute on MenuItem elements based on their * position in the MenuList. Each MenuItem element has a data-indent attribute that is * used to set the indent of the element's start slot content. */ - const filteredMenuListItems = this.menuItems?.filter(this.isMenuItemElement); - const indent: MenuItemColumnCount = filteredMenuListItems?.reduce((accum, current) => { + this.menuItems = this.menuChildren?.filter(this.isMenuItemElement); + const indent: MenuItemColumnCount = this.menuItems?.reduce((accum, current) => { const elementValue = BaseMenuList.elementIndent(current as HTMLElement); return Math.max(accum, elementValue as number) as MenuItemColumnCount; }, 0); - filteredMenuListItems?.forEach((item: HTMLElement) => { + this.menuItems?.forEach((item: HTMLElement) => { item.dataset.indent = `${indent}`; }); } @@ -220,11 +133,11 @@ export class BaseMenuList extends FASTElement { * Handle change from child MenuItem element and set radio group behavior */ private changedMenuItemHandler = (e: Event): void => { - if (this.menuItems === undefined) { + if (this.menuChildren === undefined) { return; } const changedMenuItem: MenuItem = e.target as MenuItem; - const changeItemIndex: number = this.menuItems.indexOf(changedMenuItem); + const changeItemIndex: number = this.menuChildren.indexOf(changedMenuItem); if (changeItemIndex === -1) { return; @@ -232,7 +145,7 @@ export class BaseMenuList extends FASTElement { if (changedMenuItem.role === 'menuitemradio' && changedMenuItem.checked === true) { for (let i = changeItemIndex - 1; i >= 0; --i) { - const item: Element = this.menuItems[i]; + const item: Element = this.menuChildren[i]; const role: string | null = (item as HTMLElement).role; if (role === MenuItemRole.menuitemradio) { (item as MenuItem).checked = false; @@ -241,9 +154,9 @@ export class BaseMenuList extends FASTElement { break; } } - const maxIndex: number = this.menuItems.length - 1; + const maxIndex: number = this.menuChildren.length - 1; for (let i = changeItemIndex + 1; i <= maxIndex; ++i) { - const item: Element = this.menuItems[i]; + const item: Element = this.menuChildren[i]; const role: string | null = (item as HTMLElement).role; if (role === MenuItemRole.menuitemradio) { (item as MenuItem).checked = false; @@ -258,44 +171,7 @@ export class BaseMenuList extends FASTElement { /** * check if the item is a menu item */ - protected isMenuItemElement = (el: Element): el is HTMLElement => { + protected isMenuItemElement(el: Element): el is MenuItem { return isMenuItem(el) || (isHTMLElement(el) && !!el.role && el.role in BaseMenuList.focusableElementRoles); - }; - - /** - * check if the item is focusable - */ - private isFocusableElement = (el: Element): el is HTMLElement => { - return this.isMenuItemElement(el); - }; - - private setFocus(focusIndex: number, adjustment: number): void { - if (this.menuItems === undefined) { - return; - } - - while (focusIndex >= 0 && focusIndex < this.menuItems.length) { - const child: Element = this.menuItems[focusIndex]; - - if (this.isFocusableElement(child)) { - // change the previous index to -1 - if (this.focusIndex > -1 && this.menuItems.length >= this.focusIndex - 1) { - this.menuItems[this.focusIndex].setAttribute('tabindex', '-1'); - } - - // update the focus index - this.focusIndex = focusIndex; - - // update the tabindex of next focusable element - child.setAttribute('tabindex', '0'); - - // focus the element - child.focus(); - - break; - } - - focusIndex += adjustment; - } } } diff --git a/packages/web-components/src/menu-list/menu-list.spec.ts b/packages/web-components/src/menu-list/menu-list.spec.ts index 2210e8c3b0881d..be9d7281371c3d 100644 --- a/packages/web-components/src/menu-list/menu-list.spec.ts +++ b/packages/web-components/src/menu-list/menu-list.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '../../test/playwright/index.js'; import type { MenuItem } from '../menu-item/menu-item.js'; import { MenuItemRole } from '../menu-item/menu-item.options.js'; -test.describe('Menu', () => { +test.describe('MenuList', () => { test.use({ tagName: 'fluent-menu-list', waitFor: ['fluent-menu-item'], diff --git a/packages/web-components/src/menu-list/menu-list.stories.ts b/packages/web-components/src/menu-list/menu-list.stories.ts index 2a43ed5aa3260e..d72aa56672230f 100644 --- a/packages/web-components/src/menu-list/menu-list.stories.ts +++ b/packages/web-components/src/menu-list/menu-list.stories.ts @@ -128,6 +128,21 @@ export const RadioItems: Story = { }, }; +export const RadioItemsWithSeparators: Story = { + args: { + slottedContent: () => html` + Item 1 + Item 2 + + Item 3 + Item 4 + + Item 5 + Item 6 + `, + }, +}; + export const DisabledItems: Story = { args: { slottedContent: () => html` diff --git a/packages/web-components/src/menu-list/menu-list.template.ts b/packages/web-components/src/menu-list/menu-list.template.ts index 6c5b222d636100..f0f347186ed5cd 100644 --- a/packages/web-components/src/menu-list/menu-list.template.ts +++ b/packages/web-components/src/menu-list/menu-list.template.ts @@ -3,11 +3,7 @@ import type { MenuList } from './menu-list.js'; export function menuTemplate(): ElementViewTemplate { return html` -