diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index 4489495f8..c0e53ce4f 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -10,6 +10,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Added `sl-label-change` event to `` - Added `blur()`, `click()`, and `focus()` methods as well as `sl-blur` and `sl-focus` events to `` [#730](https://github.com/shoelace-style/shoelace/issues/730) +- Added Tabler Icons example to icons page - 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) - Fixed a bug where the FormData event polyfill was causing issues with older browsers [#747](https://github.com/shoelace-style/shoelace/issues/747) - Fixed a bug that caused a console error when setting `value` to `null` or `undefined` in ``, ``, and `` [#751](https://github.com/shoelace-style/shoelace/pull/751) @@ -19,6 +20,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Improved behavior of clearable and password toggle buttons in `` and `` [#745](https://github.com/shoelace-style/shoelace/issues/745) - Improved performance of `` by caching menu items instead of traversing for them each time - Improved drag utility so initial click/touch events can be accepted [#758](https://github.com/shoelace-style/shoelace/issues/758) +- Refactored `` to be more performant by caching menu items on slot change - Revert form submit logic [#718](https://github.com/shoelace-style/shoelace/issues/718) - Updated the `disabled` attribute so it reflects in `` [#741](https://github.com/shoelace-style/shoelace/discussions/741) - Updated the `name` and `icon` attribute so they reflect in `` [#742](https://github.com/shoelace-style/shoelace/pull/742) diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 7e40c4052..152763613 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -28,21 +28,19 @@ export default class SlMenu extends LitElement { private typeToSelectString = ''; private typeToSelectTimeout: number; + private allItems: SlMenuItem[] = []; + private nonDisabledItems: SlMenuItem[] = []; firstUpdated() { this.setAttribute('role', 'menu'); } - getAllItems(options: { includeDisabled: boolean } = { includeDisabled: true }) { + getAllItems() { return [...this.defaultSlot.assignedElements({ flatten: true })].filter((el: HTMLElement) => { if (el.getAttribute('role') !== 'menuitem') { return false; } - if (!options.includeDisabled && (el as SlMenuItem).disabled) { - return false; - } - return true; }) as SlMenuItem[]; } @@ -52,7 +50,7 @@ export default class SlMenu extends LitElement { * The menu item may or may not have focus, but for keyboard interaction purposes it's considered the "active" item. */ getCurrentItem() { - return this.getAllItems({ includeDisabled: false }).find(i => i.getAttribute('tabindex') === '0'); + return this.nonDisabledItems.find(i => i.getAttribute('tabindex') === '0'); } /** @@ -60,11 +58,10 @@ export default class SlMenu extends LitElement { * `tabindex="-1"` to all other items. This method must be called prior to setting focus on a menu item. */ setCurrentItem(item: SlMenuItem) { - const items = this.getAllItems({ includeDisabled: false }); - const activeItem = item.disabled ? items[0] : item; + const activeItem = item.disabled ? this.nonDisabledItems[0] : item; // Update tab indexes - items.forEach(i => { + this.nonDisabledItems.forEach(i => { i.setAttribute('tabindex', i === activeItem ? '0' : '-1'); }); } @@ -76,7 +73,6 @@ export default class SlMenu extends LitElement { * type-to-select behavior when the menu doesn't have focus. */ typeToSelect(event: KeyboardEvent) { - const items = this.getAllItems({ includeDisabled: false }); clearTimeout(this.typeToSelectTimeout); this.typeToSelectTimeout = window.setTimeout(() => (this.typeToSelectString = ''), 1000); @@ -92,10 +88,10 @@ export default class SlMenu extends LitElement { // Restore focus in browsers that don't support :focus-visible when using the keyboard if (!hasFocusVisible) { - items.forEach(item => item.classList.remove('sl-focus-invisible')); + this.nonDisabledItems.forEach(item => item.classList.remove('sl-focus-invisible')); } - for (const item of items) { + for (const item of this.nonDisabledItems) { const slot = item.shadowRoot?.querySelector('slot:not([name])'); const label = getTextContent(slot).toLowerCase().trim(); if (label.startsWith(this.typeToSelectString)) { @@ -120,8 +116,7 @@ export default class SlMenu extends LitElement { handleKeyUp() { // Restore focus in browsers that don't support :focus-visible when using the keyboard if (!hasFocusVisible) { - const items = this.getAllItems(); - items.forEach(item => { + this.allItems.forEach(item => { item.classList.remove('sl-focus-invisible'); }); } @@ -144,11 +139,10 @@ export default class SlMenu extends LitElement { // Move the selection when pressing down or up if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) { - const items = this.getAllItems({ includeDisabled: false }); const activeItem = this.getCurrentItem(); - let index = activeItem ? items.indexOf(activeItem) : 0; + let index = activeItem ? this.nonDisabledItems.indexOf(activeItem) : 0; - if (items.length > 0) { + if (this.nonDisabledItems.length > 0) { event.preventDefault(); if (event.key === 'ArrowDown') { @@ -158,18 +152,18 @@ export default class SlMenu extends LitElement { } else if (event.key === 'Home') { index = 0; } else if (event.key === 'End') { - index = items.length - 1; + index = this.nonDisabledItems.length - 1; } if (index < 0) { - index = items.length - 1; + index = this.nonDisabledItems.length - 1; } - if (index > items.length - 1) { + if (index > this.nonDisabledItems.length - 1) { index = 0; } - this.setCurrentItem(items[index]); - items[index].focus(); + this.setCurrentItem(this.nonDisabledItems[index]); + this.nonDisabledItems[index].focus(); return; } @@ -192,11 +186,12 @@ export default class SlMenu extends LitElement { } handleSlotChange() { - const items = this.getAllItems({ includeDisabled: false }); + this.allItems = this.getAllItems(); + this.nonDisabledItems = this.allItems.filter(i => !i.disabled); // Reset the roving tab index when the slotted items change - if (items.length > 0) { - this.setCurrentItem(items[0]); + if (this.nonDisabledItems.length > 0) { + this.setCurrentItem(this.nonDisabledItems[0]); } }