From 7de6a676b89fb5aa874d4db25e8ee06bca191333 Mon Sep 17 00:00:00 2001 From: Lea Verou Date: Mon, 12 May 2025 11:26:47 -0400 Subject: [PATCH] Drop `base` part from `menu-item` and `menu-label` (rel #207) (#884) * [Menu-item] Drop `base` wrapper (rel #207) Also add two states: `has-submenu` and `submenu-expanded` * Add `checked-icon` and `submenu-icon` slots * [Menu-label] Drop `base` part * update changelog --------- Co-authored-by: Cory LaViska --- docs/docs/resources/changelog.md | 2 + src/components/menu-item/menu-item.css | 49 +++++++-------- src/components/menu-item/menu-item.ts | 60 +++++++++---------- .../menu-item/submenu-controller.ts | 2 +- src/components/menu-label/menu-label.css | 7 +-- src/components/menu-label/menu-label.ts | 4 +- 6 files changed, 56 insertions(+), 68 deletions(-) diff --git a/docs/docs/resources/changelog.md b/docs/docs/resources/changelog.md index c7d0403fc..4b3e0f832 100644 --- a/docs/docs/resources/changelog.md +++ b/docs/docs/resources/changelog.md @@ -33,6 +33,8 @@ During the alpha period, things might break! We take breaking changes very serio - Added the [`.wa-cloak` utility](/docs/utilities/fouce) to prevent FOUCE - Added the [`allDefined()` utility](/docs/usage/#all-defined) for awaiting component registration - Simplified `` by removing the `base` CSS part +- Simplified `` and `` by removing the `base` CSS part +- Added slots to `checked-icon` and `submenu-icon` in `` so custom icons can be used ### Bug fixes diff --git a/src/components/menu-item/menu-item.css b/src/components/menu-item/menu-item.css index 6012cc1d0..dd8344a6b 100644 --- a/src/components/menu-item/menu-item.css +++ b/src/components/menu-item/menu-item.css @@ -5,13 +5,6 @@ display: block; color: var(--wa-color-text-normal); -} - -:host([inert]) { - display: none; -} - -.menu-item { position: relative; display: flex; align-items: stretch; @@ -25,22 +18,26 @@ cursor: pointer; } -:host([disabled]) .menu-item { +:host([inert]) { + display: none; +} + +:host([disabled]) { outline: none; opacity: 0.5; cursor: not-allowed; } -.menu-item.menu-item--loading { +:host([loading]) { outline: none; cursor: wait; } -.menu-item.menu-item--loading *:not(wa-spinner) { +:host([loading]) *:not(wa-spinner) { opacity: 0.5; } -.menu-item--loading wa-spinner { +:host([loading]) wa-spinner { --indicator-color: currentColor; --track-width: 0.0625rem; position: absolute; @@ -50,35 +47,35 @@ opacity: 1; } -.menu-item .label { +.label { flex: 1 1 auto; display: inline-block; text-overflow: ellipsis; overflow: hidden; } -.menu-item .prefix { +.prefix { flex: 0 0 auto; display: flex; align-items: center; } -.menu-item .prefix::slotted(*) { +.prefix::slotted(*) { margin-inline-end: var(--wa-space-xs); } -.menu-item .suffix { +.suffix { flex: 0 0 auto; display: flex; align-items: center; } -.menu-item .suffix::slotted(*) { +.suffix::slotted(*) { margin-inline-start: var(--wa-space-xs); } /* Safe triangle */ -.menu-item--submenu-expanded::after { +:host(:state(submenu-expanded))::after { content: ''; position: fixed; z-index: 899; @@ -97,13 +94,13 @@ outline: none; } -:host(:hover:not([aria-disabled='true'], :focus-visible)) .menu-item, -.menu-item--submenu-expanded { +:host(:hover:not([aria-disabled='true'], :focus-visible)), +:host(:state(submenu-expanded)) { background-color: var(--background-color-hover); color: var(--text-color-hover); } -:host(:focus-visible) .menu-item { +:host(:focus-visible) { outline: var(--wa-focus-ring); outline-offset: calc(-1 * var(--wa-focus-ring-width)); background: var(--background-color-hover); @@ -111,8 +108,8 @@ opacity: 1; } -.menu-item .check, -.menu-item .chevron { +.check, +.chevron { flex: 0 0 auto; display: flex; align-items: center; @@ -122,8 +119,8 @@ visibility: hidden; } -.menu-item--checked .check, -.menu-item--has-submenu .chevron { +:host([checked]) .check, +:host(:state(has-submenu)) .chevron { visibility: visible; } @@ -139,8 +136,8 @@ wa-popup:dir(rtl)::part(popup) { } @media (forced-colors: active) { - :host(:hover:not([aria-disabled='true'])) .menu-item, - :host(:focus-visible) .menu-item { + :host(:hover:not([aria-disabled='true'])), + :host(:focus-visible) { outline: dashed 1px SelectedItem; outline-offset: -1px; } diff --git a/src/components/menu-item/menu-item.ts b/src/components/menu-item/menu-item.ts index 3356c1f63..7a3e1fe9a 100644 --- a/src/components/menu-item/menu-item.ts +++ b/src/components/menu-item/menu-item.ts @@ -1,7 +1,6 @@ import type { PropertyValues } from 'lit'; import { html } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; -import { classMap } from 'lit/directives/class-map.js'; import getText from '../../internal/get-text.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; @@ -24,8 +23,9 @@ import { SubmenuController } from './submenu-controller.js'; * @slot prefix - Used to prepend an icon or similar element to the menu item. * @slot suffix - Used to append an icon or similar element to the menu item. * @slot submenu - Used to denote a nested menu. + * @slot checked-icon - The icon used to indicate that this menu item is checked. Usually a ``. + * @slot submenu-icon - The icon used to indicate that this menu item has a submenu. Usually a ``. * - * @csspart base - The component's base wrapper. * @csspart checked-icon - The checked icon, which is only visible when the menu item is checked. * @csspart prefix - The prefix container. * @csspart label - The menu item label. @@ -37,6 +37,9 @@ import { SubmenuController } from './submenu-controller.js'; * @cssproperty --background-color-hover - The menu item's background color on hover. * @cssproperty --text-color-hover - The label color on hover. * @cssproperty [--submenu-offset=-2px] - The distance submenus shift to overlap the parent menu. + * + * @cssstate has-submenu - Applied when the menu item has a submenu. + * @cssstate submenu-expanded - Applied when the menu item has a submenu and it is expanded. */ @customElement('wa-menu-item') export default class WaMenuItem extends WebAwesomeElement { @@ -129,6 +132,8 @@ export default class WaMenuItem extends WebAwesomeElement { /** @internal - prevent the CEM from recording this event */ this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false })); } + + this.toggleCustomState('has-submenu', this.isSubmenu()); } private handleHostClick = (event: MouseEvent) => { @@ -188,49 +193,40 @@ export default class WaMenuItem extends WebAwesomeElement { return changed; } - isSubmenu() { + /** Does this element have a submenu? */ + private isSubmenu() { return this.hasUpdated ? this.querySelector(`:scope > [slot="submenu"]`) !== null : this.withSubmenu; } render() { const isRtl = this.hasUpdated ? this.localize.dir() === 'rtl' : this.dir === 'rtl'; const isSubmenuExpanded = this.submenuController.isExpanded(); + this.toggleCustomState('submenu-expanded', isSubmenuExpanded); + + this.internals.ariaHasPopup = this.isSubmenu() + ''; + this.internals.ariaExpanded = isSubmenuExpanded + ''; return html` -
- - - + + + - + - + - + - - - + + + - ${this.submenuController.renderSubmenu()} ${this.loading ? html`` : ''} -
+ ${this.submenuController.renderSubmenu()} ${this.loading ? html`` : ''} `; } } diff --git a/src/components/menu-item/submenu-controller.ts b/src/components/menu-item/submenu-controller.ts index 9f019940a..b1090bc8d 100644 --- a/src/components/menu-item/submenu-controller.ts +++ b/src/components/menu-item/submenu-controller.ts @@ -271,7 +271,7 @@ export class SubmenuController implements ReactiveController { `; + return html``; } }