Compare commits

...

5 Commits

Author SHA1 Message Date
Cory LaViska
c2d3bbdfa2 Merge branch 'next' into popover-fixes 2026-01-13 11:18:29 -08:00
Cory LaViska
f3be2c47d2 reword guard 2026-01-13 14:16:30 -05:00
Cory LaViska
0bf38d7d91 Merge branch 'next' into popover-fixes 2026-01-13 14:03:34 -05:00
Cory LaViska
f5c961eeba fixes #1911 2025-12-30 11:49:16 -05:00
Cory LaViska
22dc34f467 fix popover bug; closes #1911 2025-12-30 11:31:44 -05:00
4 changed files with 36 additions and 25 deletions

View File

@@ -13,10 +13,15 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
## Next
- Added `justify-content` CSS utilities [pr:1930]
- Added missing `.wa-gap-4xl` utility class [pr:1931]
- Added `pointercancel` and `touchcancel` event handling to draggable elements to prevent drags from getting stuck
- Added `justify-content` CSS utilities [pr:1930]
- Added missing `.wa-gap-4xl` utility class [pr:1931]
- Fixed a bug in `<wa-combobox>` that prevented the listbox from opening when options were preselected [issue:1883]
- Fixed a bug in `<wa-popup>` and `<wa-dropdown-item>` that caused an error when removing a popup while it was opening [issue:1910]
- Fixed a bug in `<wa-popup>` and `<wa-dropdown>` that caused errors when shadow DOM queries returned null [issue:1911]
- Fixed a bug in `<wa-combobox>` that prevented the listbox from opening when options were preselected [issue:1883]
- Fixed a bug in draggable elements that caused a TypeError on `touchend` events when `event.touches` was empty
## 3.1.0

View File

@@ -155,20 +155,21 @@ export default class WaDropdownItem extends WebAwesomeElement {
/** Opens the submenu. */
async openSubmenu() {
if (!this.hasSubmenu || !this.submenuElement) return;
const submenu = this.submenuElement;
if (!this.hasSubmenu || !submenu || !this.isConnected) return;
// Notify parent dropdown to handle positioning
this.notifyParentOfOpening();
// Use Popover API to show the submenu
this.submenuElement.showPopover();
this.submenuElement.hidden = false;
this.submenuElement.setAttribute('data-visible', '');
submenu.showPopover?.();
submenu.hidden = false;
submenu.setAttribute('data-visible', '');
this.submenuOpen = true;
this.setAttribute('aria-expanded', 'true');
// Animate the submenu
await animateWithClass(this.submenuElement, 'show');
await animateWithClass(submenu, 'show');
// Set focus to the first submenu item
setTimeout(() => {
@@ -210,16 +211,19 @@ export default class WaDropdownItem extends WebAwesomeElement {
/** Closes the submenu. */
async closeSubmenu() {
if (!this.hasSubmenu || !this.submenuElement) return;
const submenu = this.submenuElement;
if (!this.hasSubmenu || !submenu) return;
this.submenuOpen = false;
this.setAttribute('aria-expanded', 'false');
if (!this.submenuElement.hidden) {
await animateWithClass(this.submenuElement, 'hide');
this.submenuElement.hidden = true;
this.submenuElement.removeAttribute('data-visible');
this.submenuElement.hidePopover();
if (!submenu.hidden) {
await animateWithClass(submenu, 'hide');
if (submenu?.isConnected) {
submenu.hidden = true;
submenu.removeAttribute('data-visible');
submenu.hidePopover?.();
}
}
}

View File

@@ -138,9 +138,9 @@ export default class WaDropdown extends WebAwesomeElement {
/** Gets all dropdown items slotted in the menu. */
private getItems(includeDisabled = false): WaDropdownItem[] {
const items = this.defaultSlot
.assignedElements({ flatten: true })
.filter(el => el.localName === 'wa-dropdown-item') as WaDropdownItem[];
const items = (this.defaultSlot?.assignedElements({ flatten: true }) ?? []).filter(
el => el.localName === 'wa-dropdown-item',
) as WaDropdownItem[];
return includeDisabled ? items : items.filter(item => !item.disabled);
}
@@ -165,9 +165,9 @@ export default class WaDropdown extends WebAwesomeElement {
/** Syncs item sizes with the dropdown's size property. */
private syncItemSizes() {
const items = this.defaultSlot
.assignedElements({ flatten: true })
.filter(el => el.localName === 'wa-dropdown-item') as WaDropdownItem[];
const items = (this.defaultSlot?.assignedElements({ flatten: true }) ?? []).filter(
el => el.localName === 'wa-dropdown-item',
) as WaDropdownItem[];
items.forEach(item => (item.size = this.size));
}
@@ -230,7 +230,7 @@ export default class WaDropdown extends WebAwesomeElement {
/** Shows the dropdown menu. This should only be called from within updated(). */
private async showMenu() {
const anchor = this.getTrigger();
if (!anchor) return;
if (!anchor || !this.popup || !this.menu) return;
const showEvent = new WaShowEvent();
this.dispatchEvent(showEvent);
@@ -270,6 +270,8 @@ export default class WaDropdown extends WebAwesomeElement {
/** Hides the dropdown menu. This should only be called from within updated(). */
private async hideMenu() {
if (!this.popup || !this.menu) return;
const hideEvent = new WaHideEvent({ source: this });
this.dispatchEvent(hideEvent);
if (hideEvent.defaultPrevented) {
@@ -720,12 +722,12 @@ export default class WaDropdown extends WebAwesomeElement {
nativeButton.setAttribute('aria-haspopup', 'menu');
nativeButton.setAttribute('aria-expanded', this.open ? 'true' : 'false');
this.menu.setAttribute('aria-expanded', 'false');
this.menu?.setAttribute('aria-expanded', 'false');
}
render() {
// On initial render, we want to use this.open, for everything else, we sync off of this.popup.active to get animations working.
let active = this.hasUpdated ? this.popup.active : this.open;
let active = this.hasUpdated ? this.popup?.active : this.open;
return html`
<wa-popup

View File

@@ -286,11 +286,11 @@ export default class WaPopup extends WebAwesomeElement {
private start() {
// We can't start the positioner without an anchor
if (!this.anchorEl || !this.active) {
if (!this.anchorEl || !this.active || !this.isConnected) {
return;
}
this.popup.showPopover?.();
this.popup?.showPopover?.();
this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
this.reposition();
@@ -299,7 +299,7 @@ export default class WaPopup extends WebAwesomeElement {
private async stop(): Promise<void> {
return new Promise(resolve => {
this.popup.hidePopover?.();
this.popup?.hidePopover?.();
if (this.cleanup) {
this.cleanup();
@@ -317,7 +317,7 @@ export default class WaPopup extends WebAwesomeElement {
/** Forces the popup to recalculate and reposition itself. */
reposition() {
// Nothing to do if the popup is inactive or the anchor doesn't exist
if (!this.active || !this.anchorEl) {
if (!this.active || !this.anchorEl || !this.popup) {
return;
}
@@ -498,7 +498,7 @@ export default class WaPopup extends WebAwesomeElement {
}
private updateHoverBridge = () => {
if (this.hoverBridge && this.anchorEl) {
if (this.hoverBridge && this.anchorEl && this.popup) {
const anchorRect = this.anchorEl.getBoundingClientRect();
const popupRect = this.popup.getBoundingClientRect();
const isVertical = this.placement.includes('top') || this.placement.includes('bottom');