diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md
index 93992c39..2e94405c 100644
--- a/docs/resources/changelog.md
+++ b/docs/resources/changelog.md
@@ -6,6 +6,12 @@ Components with the Experimental bad
_During the beta period, these restrictions may be relaxed in the event of a mission-critical bug._ 🐛
+## Next
+
+- Added `sl-label-change` event to ``
+- Fixed a bug where updating a menu item's label wouldn't update the display label in `` [#729](https://github.com/shoelace-style/shoelace/issues/729)
+- Improved performance of `` by caching menu items instead of traversing for them each time
+
## 2.0.0-beta.73
- Added `button` part to ``
diff --git a/src/components/menu-item/menu-item.ts b/src/components/menu-item/menu-item.ts
index 8ae68dbd..2f100459 100644
--- a/src/components/menu-item/menu-item.ts
+++ b/src/components/menu-item/menu-item.ts
@@ -2,6 +2,8 @@ import { html, LitElement } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import '../../components/icon/icon';
+import { emit } from '../../internal/event';
+import { getTextContent } from '../../internal/slot';
import { watch } from '../../internal/watch';
import styles from './menu-item.styles';
@@ -11,6 +13,9 @@ import styles from './menu-item.styles';
*
* @dependency sl-icon
*
+ * @event sl-label-change - Emitted when the menu item's text label changes. For performance reasons, this event is only
+ * emitted if the default slot's `slotchange` event is triggered. It will not fire when the label is first set.
+ *
* @slot - The menu item's label.
* @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.
@@ -24,6 +29,9 @@ import styles from './menu-item.styles';
export default class SlMenuItem extends LitElement {
static styles = styles;
+ private cachedTextLabel: string;
+
+ @query('slot:not([name])') defaultSlot: HTMLSlotElement;
@query('.menu-item') menuItem: HTMLElement;
/** Draws the item in a checked state. */
@@ -39,6 +47,11 @@ export default class SlMenuItem extends LitElement {
this.setAttribute('role', 'menuitem');
}
+ /** Returns a text label based on the contents of the menu item's default slot. */
+ getTextLabel() {
+ return getTextContent(this.defaultSlot);
+ }
+
@watch('checked')
handleCheckedChange() {
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
@@ -49,6 +62,21 @@ export default class SlMenuItem extends LitElement {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
+ handleDefaultSlotChange() {
+ const textLabel = this.getTextLabel();
+
+ // Ignore the first time the label is set
+ if (typeof this.cachedTextLabel === 'undefined') {
+ this.cachedTextLabel = textLabel;
+ return;
+ }
+
+ if (textLabel !== this.cachedTextLabel) {
+ this.cachedTextLabel = textLabel;
+ emit(this, 'sl-label-change');
+ }
+ }
+
render() {
return html`