diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index 1acc01bb..f175b51a 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -50,6 +50,7 @@ This change applies to all design tokens that implement a color. Refer to the [c - Fixed a bug in `sl-radio-group` where clicking a radio button would cause the wrong control to be focused - Fixed a bug in `sl-button` and `sl-icon-button` where an unintended `ref` attribute was present - Fixed a bug in the focus-visible utility that failed to respond to mouseup events +- Fixed a bug where clicking on a menu item would persist its hover/focus state - Improved contrast throughout all components [#128](https://github.com/shoelace-style/shoelace/issues/128) - Refactored thumb position logic in `sl-switch` [#490](https://github.com/shoelace-style/shoelace/pull/490) - Reworked the dark theme to use an inverted token approach instead of light DOM selectors diff --git a/src/components/menu-item/menu-item.styles.ts b/src/components/menu-item/menu-item.styles.ts index dcd520c8..856f5d71 100644 --- a/src/components/menu-item/menu-item.styles.ts +++ b/src/components/menu-item/menu-item.styles.ts @@ -60,7 +60,7 @@ export default css` } :host(:hover:not([aria-disabled='true'])) .menu-item, - :host(:focus:not([aria-disabled='true'])) .menu-item { + :host(.sl-focus-visible:focus:not([aria-disabled='true'])) .menu-item { outline: none; background-color: rgb(var(--sl-color-primary-600)); color: rgb(var(--sl-color-neutral-1000)); diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 54c7d899..13887fe7 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -2,6 +2,7 @@ import { LitElement, html } from 'lit'; import { customElement, query } from 'lit/decorators.js'; import { emit } from '../../internal/event'; import { getTextContent } from '../../internal/slot'; +import { focusVisible } from '../../internal/focus-visible'; import type SlMenuItem from '../menu-item/menu-item'; import styles from './menu.styles'; @@ -25,6 +26,19 @@ export default class SlMenu extends LitElement { private typeToSelectString = ''; private typeToSelectTimeout: any; + connectedCallback() { + super.connectedCallback(); + focusVisible.observe(this, { + visible: () => this.getAllItems().map(item => item.classList.add('sl-focus-visible')), + notVisible: () => this.getAllItems().map(item => item.classList.remove('sl-focus-visible')) + }); + } + + disconnectedCallback() { + super.disconnectedCallback(); + focusVisible.unobserve(this); + } + getAllItems(options: { includeDisabled: boolean } = { includeDisabled: true }) { return [...this.defaultSlot.assignedElements({ flatten: true })].filter((el: HTMLElement) => { if (el.getAttribute('role') !== 'menuitem') { diff --git a/src/internal/focus-visible.ts b/src/internal/focus-visible.ts index 460da94e..c815b079 100644 --- a/src/internal/focus-visible.ts +++ b/src/internal/focus-visible.ts @@ -6,14 +6,29 @@ // const listeners = new WeakMap(); -export function observe(el: HTMLElement) { +interface ObserveOptions { + visible: () => void; + notVisible: () => void; +} + +export function observe(el: HTMLElement, options?: ObserveOptions) { const keys = ['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageDown', 'PageUp']; const is = (event: KeyboardEvent) => { if (keys.includes(event.key)) { el.classList.add('focus-visible'); + + if (options?.visible) { + options.visible(); + } + } + }; + const isNot = () => { + el.classList.remove('focus-visible'); + + if (options?.notVisible) { + options.notVisible(); } }; - const isNot = () => el.classList.remove('focus-visible'); listeners.set(el, { is, isNot }); el.addEventListener('keydown', is);