diff --git a/labs/aria/menu/demo/stories.ts b/labs/aria/menu/demo/stories.ts index 451ee294bd..9b7e4a4144 100644 --- a/labs/aria/menu/demo/stories.ts +++ b/labs/aria/menu/demo/stories.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import '@material/web/labs/aria/menu/md-aria-fieldset.js'; import '@material/web/labs/aria/menu/md-aria-menuitem.js'; import '@material/web/labs/aria/menu/md-aria-menulist.js'; @@ -91,4 +92,138 @@ const menuWithPopover: MaterialStoryInit = { }, }; -export const stories = [menu, menuWithDialog, menuWithPopover]; +const menuWithFieldset: MaterialStoryInit = { + name: 'Menu with fieldset, not checkable', + styles: css``, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + + + `; + }, +}; + +const menuWithFieldsetSingleCheckable: MaterialStoryInit = { + name: 'Menu with fieldset, single-checkable', + styles: css``, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + + + `; + }, +}; + +const menuWithFieldsetMultipleCheckable: MaterialStoryInit = { + name: 'Menu with fieldset, multiple-checkable', + styles: css``, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + + + `; + }, +}; + +const menuWithFieldsetDisabled: MaterialStoryInit = { + name: 'Menu with disabled fieldset', + styles: css``, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + + + `; + }, +}; + +const menuWithMultipleFieldsetsCheckable: MaterialStoryInit = { + name: 'Menu with multiple fieldsets, single- and multiple-checkable', + styles: css``, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + +
+ + Item 4 + Item 5 + Item 6 + +
+ `; + }, +}; + +const menuWithStyles: MaterialStoryInit = { + name: 'Menu with styles', + styles: css` + md-aria-menuitem:state(enabled) { + color: blue; + } + + md-aria-menuitem:state(disabled) { + font-style: italic; + } + + md-aria-menuitem:state(checked) { + color: green; + } + + md-aria-menuitem::part(checkmark)::before { + content: '\\25B8'; /* ▸ */ + } + `, + render(knobs) { + return html` + + + + Item 1 + Item 2 + Item 3 + + + `; + }, +}; + +export const stories = [ + menu, + menuWithDialog, + menuWithPopover, + menuWithFieldset, + menuWithFieldsetSingleCheckable, + menuWithFieldsetMultipleCheckable, + menuWithFieldsetDisabled, + menuWithMultipleFieldsetsCheckable, + menuWithStyles, +]; diff --git a/labs/aria/menu/fieldset.ts b/labs/aria/menu/fieldset.ts new file mode 100644 index 0000000000..82feef1a25 --- /dev/null +++ b/labs/aria/menu/fieldset.ts @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {createContext, provide} from '@lit/context'; +import {CSSResultOrNative, LitElement, css, html} from 'lit'; +import {property} from 'lit/decorators.js'; +import { + internals, + mixinElementInternals, +} from '../../behaviors/element-internals.js'; +import {sharedSlottedContentStyles} from './shared-slotted-content.js'; + +/** + * The `` that should own descendant ``s. + */ +export const ancestorFieldsetContext = createContext< + AriaFieldsetElement | undefined +>(Symbol('ancestorFieldsetContext')); + +/** + * `checkable` state of this ``, for use by owned + * ``s. + */ +export const checkableContext = createContext<'single' | 'multiple' | null>( + Symbol('checkableContext'), +); + +/** + * `disabled` state of this ``, for use by owned + * ``s. + */ +export const disabledContext = createContext( + Symbol('disabledContext'), +); + +/** Private property key for the `ancestorFieldset` context. */ +const ancestorFieldset = Symbol('ancestorFieldset'); + +const baseClass = mixinElementInternals(LitElement); + +/** + * An element implementing the proposed menu behavior of `
`. + */ +export class AriaFieldsetElement extends baseClass { + static override styles: CSSResultOrNative[] = [ + sharedSlottedContentStyles, + css` + :host { + display: block; + margin-inline: 2px; + border: groove 2px ThreeDFace; + padding-block: 0.35em 0.625em; + padding-inline: 0.75em; + min-inline-size: min-content; + } + `, + ]; + + /** Used by decendant menuitems to retrieve other menuitems. */ + @provide({context: ancestorFieldsetContext}) + [ancestorFieldset] = this; + + /** + * Whether or not menuitems in this fieldset are checkable, and if they are + * exclusively checkable. + * + * This property is provided as a separate context so that descendant + * menuitems will respond to changes to its value even when the containing + * fieldset does not change. + */ + @provide({context: checkableContext}) + @property({type: String, reflect: true}) + checkable: 'single' | 'multiple' | null = null; + + /** + * Whether or not menuitems in this fieldset are disabled. + * + * This property is provided as a separate context so that descendant + * menuitems will respond to changes to its value even when the containing + * fieldset does not change. + */ + @provide({context: disabledContext}) + @property({type: Boolean, reflect: true}) + disabled = false; + + constructor() { + super(); + this[internals].role = 'group'; + } + + override render() { + return html``; + } +} diff --git a/labs/aria/menu/fieldset_test.ts b/labs/aria/menu/fieldset_test.ts new file mode 100644 index 0000000000..a833e6b919 --- /dev/null +++ b/labs/aria/menu/fieldset_test.ts @@ -0,0 +1,172 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// import 'jasmine'; (google3-only) + +import './md-aria-fieldset.js'; +import './md-aria-menuitem.js'; +import './md-aria-menulist.js'; + +import {html} from 'lit'; +import {Environment} from '../../../testing/environment.js'; +import {internals} from '../../behaviors/element-internals.js'; + +describe('md-aria-fieldset', () => { + const env = new Environment(); + + async function setUpTest( + template = html` + + + Item 1 + Item 2 + Item 3 + + + `, + ) { + const root = env.render(template); + await env.waitForStability(); + const menulist = root.querySelector('md-aria-menulist')!; + const fieldset = root.querySelector('md-aria-fieldset')!; + const items = Array.from(root.querySelectorAll('md-aria-menuitem')!); + + await new Promise((resolve) => { + menulist.addEventListener('toggle', (event: ToggleEvent) => { + if (event.newState === 'open') { + resolve(); + } + }); + menulist.showPopover(); + }); + await env.waitForStability(); + expect(menulist.matches(':popover-open')).toBeTrue(); + + return {root, menulist, fieldset, items}; + } + + describe('default state', () => { + it('has role "group"', async () => { + const {fieldset} = await setUpTest(); + + expect(fieldset[internals].role).toBe('group'); + }); + + it('items are not checkable by default', async () => { + const {items} = await setUpTest(); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + }); + }); + + describe('single-checkable fieldsets', () => { + async function setUpSingleCheckableTest() { + const result = await setUpTest(); + result.fieldset.checkable = 'single'; + await env.waitForStability(); + return result; + } + + it('only one item can be checked at a time', async () => { + const {items} = await setUpSingleCheckableTest(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([true, false, false]); + + items[1].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, true, false]); + }); + + it('when disabled, no items are checkable', async () => { + const {fieldset, items} = await setUpSingleCheckableTest(); + + fieldset.disabled = true; + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + }); + + it('checking an item closes the menu', async () => { + const {menulist, items} = await setUpSingleCheckableTest(); + + expect(menulist.matches(':popover-open')).toBeTrue(); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(menulist.matches(':popover-open')).toBeFalse(); + }); + }); + + describe('multiple-checkable fieldsets', () => { + async function setUpMultipleCheckableTest() { + const result = await setUpTest(); + result.fieldset.checkable = 'multiple'; + await env.waitForStability(); + return result; + } + + it('multiple items can be checked at once', async () => { + const {items} = await setUpMultipleCheckableTest(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([true, false, false]); + + items[1].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([true, true, false]); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, true, false]); + }); + + it('when disabled, no items are checkable', async () => { + const {fieldset, items} = await setUpMultipleCheckableTest(); + + fieldset.disabled = true; + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(items.map((x) => x.checked)).toEqual([false, false, false]); + }); + + it('checking an item does not close the menu', async () => { + const {menulist, items} = await setUpMultipleCheckableTest(); + + expect(menulist.matches(':popover-open')).toBeTrue(); + + items[0].dispatchEvent(new MouseEvent('click', {bubbles: true})); + await env.waitForStability(); + + expect(menulist.matches(':popover-open')).toBeTrue(); + }); + }); +}); diff --git a/labs/aria/menu/md-aria-fieldset.ts b/labs/aria/menu/md-aria-fieldset.ts new file mode 100644 index 0000000000..5e65edfc1d --- /dev/null +++ b/labs/aria/menu/md-aria-fieldset.ts @@ -0,0 +1,15 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {AriaFieldsetElement} from './fieldset.js'; + +declare global { + interface HTMLElementTagNameMap { + 'md-aria-fieldset': AriaFieldsetElement; + } +} + +customElements.define('md-aria-fieldset', AriaFieldsetElement); diff --git a/labs/aria/menu/menuitem.ts b/labs/aria/menu/menuitem.ts index 9b23b6e589..495fe19566 100644 --- a/labs/aria/menu/menuitem.ts +++ b/labs/aria/menu/menuitem.ts @@ -5,8 +5,8 @@ */ import {consume} from '@lit/context'; -import {CSSResultOrNative, LitElement, css, html} from 'lit'; -import {property} from 'lit/decorators.js'; +import {CSSResultOrNative, LitElement, PropertyValues, css, html} from 'lit'; +import {property, state} from 'lit/decorators.js'; import { afterDispatch, setupDispatchHooks, @@ -21,11 +21,26 @@ import { } from '../../behaviors/element-internals.js'; import {mixinFocusable} from '../../behaviors/focusable.js'; import {sharedCommandInvokerActivationSteps} from '../command.js'; +import { + AriaFieldsetElement, + ancestorFieldsetContext, + checkableContext, + disabledContext, +} from './fieldset.js'; import {AriaMenulistElement, ancestorMenulistContext} from './menulist.js'; /** Private property key for the `ancestorMenulist` context. */ const ancestorMenulist = Symbol('ancestorMenulist'); +/** Private property key for the `ancestorFieldset` context. */ +const ancestorFieldset = Symbol('ancestorFieldset'); + +/** Private property key for the `checkable` context. */ +const fieldsetCheckable = Symbol('fieldsetCheckable'); + +/** Private property key for the `disabled` context. */ +const fieldsetDisabled = Symbol('fieldsetDisabled'); + const baseClass = mixinCustomStateSet( mixinFocusable(mixinElementInternals(LitElement)), ); @@ -33,8 +48,16 @@ const baseClass = mixinCustomStateSet( /** * An element implementing the proposed `` built-in element. * + * Use `::part(checkmark)::before` to style the pseudo-element representing the + * `::checkmark`. (As of writing, pseudo-elements can't be directly exported as + * first-class parts.) + * + * Unlike the proposed `` behavior, this element does not support + * being nested within multiple `
`s. + * * @cssstate enabled - True when the item is enabled. * @cssstate disabled - True when the item is disabled. + * @cssstate checked - True when the item is checked. */ export class AriaMenuitemElement extends baseClass { static override styles: CSSResultOrNative[] = [ @@ -42,29 +65,142 @@ export class AriaMenuitemElement extends baseClass { :host { display: inline-flex; } + + [part='checkmark'] { + &::before { + content: '\\2713' / ''; + } + + :host(:not(:state(_checkable))) & { + display: none; + } + + :host(:not(:state(checked))) & { + visibility: hidden; + } + } `, ]; @consume({context: ancestorMenulistContext, subscribe: true}) [ancestorMenulist]?: AriaMenulistElement; - @property({type: Boolean, reflect: true, noAccessor: true}) - get disabled() { - return this[internals].ariaDisabled === 'true'; + @consume({context: ancestorFieldsetContext, subscribe: true}) + [ancestorFieldset]?: AriaFieldsetElement; + + @consume({context: checkableContext, subscribe: true}) + @state() + [fieldsetCheckable]: 'single' | 'multiple' | null = null; + + @consume({context: disabledContext, subscribe: true}) + @state() + [fieldsetDisabled]: boolean | null = null; + + @property({type: Boolean, reflect: true}) + disabled = false; + + @property({type: Boolean, reflect: true}) + get checked(): boolean { + return Boolean(this.internalChecked); } - set disabled(value: boolean) { - const oldValue = this.disabled; + set checked(value: boolean) { value = Boolean(value); + + if (this.internalCheckable === null) { + return; + } + + if (value && this[fieldsetCheckable] === 'single') { + const items: Iterable = + this[ancestorFieldset]?.querySelectorAll('md-aria-menuitem') ?? []; + for (const item of items) { + if (item !== this) { + item.internalChecked = false; + } + } + } + + this.internalChecked = value; + } + + /** + * The internal disabled state of this element, canonically stored in the + * item's ARIA disabled state. This value is set in `updated` based on both + * the `disabledContext` from the ancestor `` (if any) and + * the `disabled` attribute. + */ + private get internalDisabled(): boolean { + return this[internals].ariaDisabled === 'true'; + } + private set internalDisabled(value: boolean) { this[internals].ariaDisabled = String(value); this[toggleState]('disabled', value); this[toggleState]('enabled', !value); - this.requestUpdate('disabled', oldValue); + } + + /** + * The internal checkable state of this element, canonically stored in the + * item's ARIA role. This value is set in `updated` based on the + * `checkableContext` from the ancestor ``. Setting this + * property does not automatically update `internalChecked`. + */ + private get internalCheckable(): 'single' | 'multiple' | null { + switch (this[internals].role) { + case 'menuitemradio': + return 'single'; + case 'menuitemcheckbox': + return 'multiple'; + case 'menuitem': + return null; + default: + // Unreachable + return null; + } + } + private set internalCheckable(value: 'single' | 'multiple' | null) { + switch (value) { + case 'single': + this[internals].role = 'menuitemradio'; + this[toggleState]('_checkable', true); + break; + case 'multiple': + this[internals].role = 'menuitemcheckbox'; + this[toggleState]('_checkable', true); + break; + case null: + this[internals].role = 'menuitem'; + this[toggleState]('_checkable', false); + break; + default: + // Unreachable + } + } + + /** + * The internal checked state of this element, canonically stored in the + * item's ARIA checked state. Setting this property does not automatically + * update `internalCheckable`. + */ + private get internalChecked(): boolean | null { + const value = this[internals].ariaChecked as string | null; + return value === null ? value : value === 'true'; + } + private set internalChecked(value: boolean | null) { + if (value === null) { + this[internals].ariaChecked = null; + this[toggleState]('checked', false); + return; + } + + this[internals].ariaChecked = String(value); + this[toggleState]('checked', value); } constructor() { super(); - this[internals].role = 'menuitem'; - this.disabled = false; + this.internalDisabled = false; + this.internalCheckable = null; + this.internalChecked = false; setupDispatchHooks(this, 'keydown', 'click'); this.addEventListener('keydown', (event: KeyboardEvent) => { @@ -80,22 +216,56 @@ export class AriaMenuitemElement extends baseClass { }); this.addEventListener('click', (event: Event) => { afterDispatch(event, () => { - if (event.defaultPrevented || this.disabled) { + if (event.defaultPrevented || this.internalDisabled) { return; } - this[ancestorMenulist]?.hidePopover(); + if (this.internalCheckable !== null) { + // Use the user-facing setter to update other items in this fieldset, + // if any. + this.checked = !this.checked; + } + + if (this.internalCheckable !== 'multiple') { + this[ancestorMenulist]?.hidePopover(); + } sharedCommandInvokerActivationSteps(this, event); }); }); } + override disconnectedCallback() { + super.disconnectedCallback(); + + // Discard context when disconnected. + this[ancestorMenulist] = undefined; + this[ancestorFieldset] = undefined; + this[fieldsetDisabled] = null; + this[fieldsetCheckable] = null; + } + + override updated(changedProperties: PropertyValues) { + if ( + changedProperties.has(fieldsetDisabled) || + changedProperties.has('disabled') + ) { + this.internalDisabled = + Boolean(this[fieldsetDisabled]) || Boolean(this.disabled); + } + + if (changedProperties.has(fieldsetCheckable)) { + const checkable = this[fieldsetCheckable]; + this.internalCheckable = checkable; + this.internalChecked = checkable ? Boolean(this.internalChecked) : null; + } + } + override render() { - return html``; + return html``; } override click() { - if (!this.disabled) { + if (!this.internalDisabled) { super.click(); } } diff --git a/labs/aria/menu/menuitem_test.ts b/labs/aria/menu/menuitem_test.ts index 28db02b0d5..7f83ec4eef 100644 --- a/labs/aria/menu/menuitem_test.ts +++ b/labs/aria/menu/menuitem_test.ts @@ -6,6 +6,7 @@ // import 'jasmine'; (google3-only) +import './md-aria-fieldset.js'; import './md-aria-menuitem.js'; import './md-aria-menulist.js'; @@ -31,6 +32,18 @@ function expectDisabled(menuitem: AriaMenuitemElement) { expect(menuitem.matches(':state(disabled)')).toBeTrue(); } +function expectChecked(menuitem: AriaMenuitemElement) { + expect(menuitem.checked).toBeTrue(); + expect(menuitem[internals].ariaChecked).toBe('true'); + expect(menuitem.matches(':state(checked)')).toBeTrue(); +} + +function expectNotChecked(menuitem: AriaMenuitemElement) { + expect(menuitem.checked).toBeFalse(); + expect(menuitem[internals].ariaChecked).toBe('false'); + expect(menuitem.matches(':state(checked)')).toBeFalse(); +} + describe('md-aria-menuitem', () => { const env = new Environment(); @@ -217,4 +230,63 @@ describe('md-aria-menuitem', () => { expect(menulist.matches(':popover-open')).toBeTrue(); }); }); + + describe('Checkedness behavior', () => { + async function setUpCheckednessTest() { + const {root} = await setUpTest(html` + + + Disabled Item + + + `); + const menulist = root.querySelector('md-aria-menulist')!; + const fieldset = root.querySelector('md-aria-fieldset')!; + const menuitem = root.querySelector('md-aria-menuitem')!; + + menulist.showPopover(); + await env.waitForStability(); + + return {menulist, fieldset, menuitem}; + } + + it('has role "menuitemcheckbox" in multiple-checkable fieldsets', async () => { + const {menuitem} = await setUpCheckednessTest(); + + expect(menuitem[internals].role).toEqual('menuitemcheckbox'); + }); + + it('has role "menuitemradio" in single-checkable fieldsets', async () => { + const {fieldset, menuitem} = await setUpCheckednessTest(); + + fieldset.checkable = 'single'; + await env.waitForStability(); + + expect(menuitem[internals].role).toEqual('menuitemradio'); + }); + + it('becomes checked when clicked', async () => { + const {menuitem} = await setUpCheckednessTest(); + + expectNotChecked(menuitem); + + menuitem.click(); + await env.waitForStability(); + + expectChecked(menuitem); + }); + + it('does not become checked when clicked if disabled', async () => { + const {menuitem} = await setUpCheckednessTest(); + + expectNotChecked(menuitem); + + menuitem.disabled = true; + await env.waitForStability(); + menuitem.click(); + await env.waitForStability(); + + expectNotChecked(menuitem); + }); + }); }); diff --git a/labs/aria/menu/menulist.ts b/labs/aria/menu/menulist.ts index a24e406bbe..6a019a3a34 100644 --- a/labs/aria/menu/menulist.ts +++ b/labs/aria/menu/menulist.ts @@ -13,6 +13,8 @@ import { internals, mixinElementInternals, } from '../../behaviors/element-internals.js'; +import {ancestorFieldsetContext, checkableContext} from './fieldset.js'; +import {sharedSlottedContentStyles} from './shared-slotted-content.js'; /** The `` that should own descendant ``s. */ export const ancestorMenulistContext = createContext( @@ -31,6 +33,7 @@ const baseClass = mixinElementInternals(LitElement); */ export class AriaMenulistElement extends baseClass { static override styles: CSSResultOrNative[] = [ + sharedSlottedContentStyles, css` /* Unset UA |[popover]| styles. */ @layer { @@ -76,38 +79,6 @@ export class AriaMenulistElement extends baseClass { :host(:not(:popover-open)) { display: none; } - - ::slotted(md-aria-menuitem) { - display: flex; - align-items: center; - user-select: none; - min-inline-size: 24px; - min-block-size: max(24px, 1lh); - font-weight: inherit; - gap: 0.5em; - padding-inline: 0.5em; - } - - ::slotted(md-aria-menuitem:state(enabled):hover) { - background-color: color-mix(in lab, currentColor 10%, transparent); - } - - ::slotted(md-aria-menuitem:state(disabled)) { - color: color-mix(in lab, currentColor 50%, transparent); - } - - ::slotted(hr) { - color: inherit; - margin-inline: 0px; - border: none; - border-block-start: 1px solid currentColor; - border-image: none; - } - - ::slotted(a:any-link), - ::slotted(img[usemap]) { - display: none; - } `, ]; @@ -120,10 +91,18 @@ export class AriaMenulistElement extends baseClass { constructor() { super(); - const provider = new ContextProvider(this, { + const menulistProvider = new ContextProvider(this, { context: ancestorMenulistContext, }); - provider.setValue(this); + menulistProvider.setValue(this); + const fieldsetProvider = new ContextProvider(this, { + context: ancestorFieldsetContext, + }); + fieldsetProvider.setValue(undefined); + const checkableProvider = new ContextProvider(this, { + context: checkableContext, + }); + checkableProvider.setValue(null); this[internals].role = 'menu'; this[internals].ariaOrientation = 'vertical'; diff --git a/labs/aria/menu/shared-slotted-content.ts b/labs/aria/menu/shared-slotted-content.ts new file mode 100644 index 0000000000..77c6393eed --- /dev/null +++ b/labs/aria/menu/shared-slotted-content.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {css} from 'lit'; + +/** + * Shared styles used by `` and ``, to + * simulate the descendant combinator in UA styles for ``. + */ +export const sharedSlottedContentStyles = css` + /*! + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + + ::slotted(md-aria-menuitem) { + display: flex; + align-items: center; + user-select: none; + min-inline-size: 24px; + min-block-size: max(24px, 1lh); + font-weight: inherit; + gap: 0.5em; + padding-inline: 0.5em; + } + + ::slotted(md-aria-menuitem:state(enabled):hover) { + background-color: color-mix(in lab, currentColor 10%, transparent); + } + + ::slotted(md-aria-menuitem:state(disabled)) { + color: color-mix(in lab, currentColor 50%, transparent); + } + + ::slotted(md-aria-fieldset) { + margin-inline: 0; + border: none; + padding-block: 0; + padding-inline: 0; + } + + ::slotted(hr) { + color: inherit; + margin-inline: 0; + border: none; + border-block-start: 1px solid currentColor; + border-image: none; + } + + ::slotted(a:any-link), + ::slotted(img[usemap]) { + display: none; + } +`;