openclaw/patches/@awesome.me__webawesome@3.13.0.patch
Peter Steinberger 02cf3f0d1d
fix(ui): keep Enter on a hover-opened submenu owner from selecting a row (#161393)
With the pointer resting on "Assign to…", Web Awesome opens its submenu on hover. Pressing Enter on the focused owner item then fell back to the submenu's active row and assigned the session to the first person (the current user). The dropdown key handler now only falls back to the level's active item when no dropdown item holds focus. The people-search browser test hovers before Enter and fails without the fix. It flaked in CI whenever the shared browser left the mouse over that item.
2026-09-30 23:49:52 +00:00

3096 lines
126 KiB
Diff

diff --git a/dist/chunks/chunk.4HJPQ4O4.js b/dist/chunks/chunk.4HJPQ4O4.js
index 0cd72f56fc98357609a4b2b7576f3579e36bc997..49f741894a607d779a79ca3644e2cfcfcff09197 100644
--- a/dist/chunks/chunk.4HJPQ4O4.js
+++ b/dist/chunks/chunk.4HJPQ4O4.js
@@ -25,9 +25,6 @@ import {
import {
uniqueId
} from "./chunk.KNJT7KBU.js";
-import {
- waitForEvent
-} from "./chunk.F25QOBDY.js";
import {
animateWithClass
} from "./chunk.L6CIKOFQ.js";
@@ -80,83 +77,45 @@ var WaTooltip = class extends WebAwesomeElement {
this.for = null;
this.anchor = null;
this.eventController = new AbortController();
- this.handleBlur = () => {
- this.dismissedByPress = false;
- if (this.hasTrigger("focus")) {
- this.hide();
- }
- };
- this.handleClick = () => {
- if (this.hasTrigger("click")) {
- if (this.open) {
- this.hide();
- } else {
- this.show();
+ this.openTransition = { open: false };
+ this.handleTrigger = (event) => {
+ const type = event.type;
+ const hover = type === "mouseover" || type === "mouseout";
+ let open = type === "focus" || type === "mouseover";
+ if (type === "click" || type === "mousedown") {
+ if (!this.hasTrigger("click")) {
+ if (!this.hasTrigger("manual")) this.lightDismiss();
+ return;
}
- return;
- }
- if (this.hasTrigger("manual")) {
- return;
- }
- this.lightDismiss();
- };
- this.handleFocus = () => {
- if (this.dismissedByPress) {
- return;
- }
- if (this.hasTrigger("focus")) {
- this.show();
+ if (type !== "click") return;
+ open = !this.open;
+ } else {
+ if (!open) {
+ const target = event.relatedTarget;
+ if (hover && target && ((this.anchor && containsComposedNode(this.anchor, target)) || containsComposedNode(this, target))) return;
+ this.dismissedByPress = false;
+ }
+ if (this.dismissedByPress || !this.hasTrigger(hover ? "hover" : "focus")) return;
}
- };
- this.handleMouseDown = () => {
- if (this.hasTrigger("click") || this.hasTrigger("manual")) {
- return;
+ const request = () => open ? this.show() : this.hide();
+ if (hover) {
+ clearTimeout(this.hoverTimeout);
+ this.hoverTimeout = window.setTimeout(request, open ? this.showDelay : this.hideDelay);
+ } else {
+ request();
}
- this.lightDismiss();
};
- this.handleDocumentKeyDown = (event) => {
- if (this.hasTrigger("manual")) {
- return;
- }
- if (event.key === "Escape" && this.open && isTopDismissible(this)) {
+ this.handleDocumentDismiss = (event) => {
+ if (this.hasTrigger("manual")) return;
+ if (event.type === "keydown") {
+ if (event.key !== "Escape" || !this.open || !isTopDismissible(this)) return;
event.preventDefault();
event.stopPropagation();
- this.hide();
- }
- };
- this.handleDocumentClick = (event) => {
- if (this.hasTrigger("manual")) {
- return;
- }
- if (this.anchor && event.composedPath().includes(this.anchor)) {
+ } else if (this.anchor && event.composedPath().includes(this.anchor)) {
return;
}
this.hide();
};
- this.handleMouseOver = () => {
- if (this.dismissedByPress) {
- return;
- }
- if (this.hasTrigger("hover")) {
- clearTimeout(this.hoverTimeout);
- this.hoverTimeout = window.setTimeout(() => this.show(), this.showDelay);
- }
- };
- this.handleMouseOut = (event) => {
- const relatedTarget = event.relatedTarget;
- const movedIntoAnchor = Boolean(relatedTarget && this.anchor && containsComposedNode(this.anchor, relatedTarget));
- const movedIntoTooltip = Boolean(relatedTarget && containsComposedNode(this, relatedTarget));
- if (movedIntoAnchor || movedIntoTooltip) {
- return;
- }
- this.dismissedByPress = false;
- if (this.hasTrigger("hover")) {
- clearTimeout(this.hoverTimeout);
- this.hoverTimeout = window.setTimeout(() => {
- this.hide();
- }, this.hideDelay);
- }
- };
}
connectedCallback() {
super.connectedCallback();
@@ -165,43 +124,33 @@ var WaTooltip = class extends WebAwesomeElement {
if (this.eventController.signal.aborted) {
this.eventController = new AbortController();
}
- this.addEventListener("mouseout", this.handleMouseOut);
+ this.addEventListener("mouseout", this.handleTrigger);
this.dismissedByPress = false;
- if (this.open) {
- this.open = false;
- this.updateComplete.then(() => {
- this.open = true;
- });
- }
+ // The owner reads current intent after rendering, including on reconnect.
+ void this.requestOpen(this.open);
if (!this.id) {
this.id = uniqueId("wa-tooltip-");
}
- if (this.for && this.anchor) {
+ if (this.for) {
this.anchor = null;
this.handleForChange();
- } else if (this.for) {
- this.handleForChange();
}
}
}
disconnectedCallback() {
super.disconnectedCallback();
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("click", this.handleDocumentClick);
unregisterDismissible(this);
- this.eventController.abort();
+ // Avoid retaining detached anchors in the default abort reason's stack.
+ this.eventController.abort(null);
+ clearTimeout(this.hoverTimeout);
+ this.openTransition = { open: false, promise: this.openTransition.promise };
+ this.popup?.popup?.classList.remove("show-with-scale", "hide-with-scale");
+ if (this.body) this.body.hidden = true;
+ if (this.popup) this.popup.active = false;
if (this.anchor) {
this.removeFromAriaLabelledBy(this.anchor, this.id);
}
}
- firstUpdated(changedProperties) {
- this.body.hidden = !this.open;
- if (this.open) {
- this.popup.active = true;
- this.popup.reposition();
- }
- super.firstUpdated(changedProperties);
- }
/** Hides the tooltip, or cancels a pending show, and keeps it hidden until re-armed. */
lightDismiss() {
clearTimeout(this.hoverTimeout);
@@ -233,41 +182,47 @@ var WaTooltip = class extends WebAwesomeElement {
}
}
async handleOpenChange() {
- if (this.open) {
- if (this.disabled) {
- return;
- }
- const waShowEvent = new WaShowEvent();
- this.dispatchEvent(waShowEvent);
- if (waShowEvent.defaultPrevented) {
- this.open = false;
- return;
- }
- if (!this.hasTrigger("manual")) {
- document.addEventListener("keydown", this.handleDocumentKeyDown, { signal: this.eventController.signal });
- document.addEventListener("click", this.handleDocumentClick, { signal: this.eventController.signal });
- registerDismissible(this);
- }
- this.body.hidden = false;
- this.popup.active = true;
- await animateWithClass(this.popup.popup, "show-with-scale");
- this.popup.reposition();
- this.dispatchEvent(new WaAfterShowEvent());
- } else {
- const waHideEvent = new WaHideEvent();
- this.dispatchEvent(waHideEvent);
- if (waHideEvent.defaultPrevented) {
- this.open = true;
- return;
+ if (this.disabled) this.open = false;
+ const previous = this.openTransition;
+ const open = this.open;
+ if (!this.isConnected || previous.open === open) return previous.promise;
+ const allowed = this.dispatchEvent(open ? new WaShowEvent() : new WaHideEvent());
+ if (this.openTransition === previous) {
+ if (!allowed && !this.disabled) {
+ this.open = previous.open;
+ } else if ((this.open && !this.disabled) === open) {
+ const transition = this.openTransition = { open };
+ // Disconnect invalidates the transition, so identity also fences connections.
+ const isCurrent = () => this.openTransition === transition && (this.open && !this.disabled) === open;
+ const dismissible = open && !this.hasTrigger("manual");
+ for (const type of ["keydown", "click"]) {
+ document[dismissible ? "addEventListener" : "removeEventListener"](type, this.handleDocumentDismiss, { signal: this.eventController.signal });
+ }
+ if (dismissible) registerDismissible(this);
+ else unregisterDismissible(this);
+ if (open) {
+ this.body.hidden = false;
+ this.popup.active = true;
+ }
+ // Join previous animation cleanup before reusing its classes.
+ this.popup.popup.classList.remove("show-with-scale", "hide-with-scale");
+ transition.promise = (async () => {
+ await previous.promise;
+ if (!isCurrent()) return;
+ await animateWithClass(this.popup.popup, open ? "show-with-scale" : "hide-with-scale");
+ if (!isCurrent()) return;
+ if (open) {
+ this.popup.reposition();
+ } else {
+ this.popup.active = false;
+ this.body.hidden = true;
+ }
+ // Reposition emits a synchronous public event that can revoke ownership.
+ if (isCurrent()) this.dispatchEvent(open ? new WaAfterShowEvent() : new WaAfterHideEvent());
+ })();
}
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("click", this.handleDocumentClick);
- unregisterDismissible(this);
- await animateWithClass(this.popup.popup, "hide-with-scale");
- this.popup.active = false;
- this.body.hidden = true;
- this.dispatchEvent(new WaAfterHideEvent());
}
+ return this.openTransition.promise;
}
handleForChange() {
const rootNode = this.getRootNode?.();
@@ -281,23 +236,19 @@ var WaTooltip = class extends WebAwesomeElement {
}
this.dismissedByPress = false;
const { signal } = this.eventController;
- if (newAnchor) {
- this.addToAriaLabelledBy(newAnchor, this.id);
- newAnchor.addEventListener("blur", this.handleBlur, { capture: true, signal });
- newAnchor.addEventListener("focus", this.handleFocus, { capture: true, signal });
- newAnchor.addEventListener("click", this.handleClick, { signal });
- newAnchor.addEventListener("mousedown", this.handleMouseDown, { signal });
- newAnchor.addEventListener("mouseover", this.handleMouseOver, { signal });
- newAnchor.addEventListener("mouseout", this.handleMouseOut, { signal });
- }
- if (oldAnchor) {
- this.removeFromAriaLabelledBy(oldAnchor, this.id);
- oldAnchor.removeEventListener("blur", this.handleBlur, { capture: true });
- oldAnchor.removeEventListener("focus", this.handleFocus, { capture: true });
- oldAnchor.removeEventListener("click", this.handleClick);
- oldAnchor.removeEventListener("mousedown", this.handleMouseDown);
- oldAnchor.removeEventListener("mouseover", this.handleMouseOver);
- oldAnchor.removeEventListener("mouseout", this.handleMouseOut);
+ const events = [
+ ["blur", this.handleTrigger, true],
+ ["focus", this.handleTrigger, true],
+ ["click", this.handleTrigger],
+ ["mousedown", this.handleTrigger],
+ ["mouseover", this.handleTrigger],
+ ["mouseout", this.handleTrigger]
+ ];
+ if (newAnchor) this.addToAriaLabelledBy(newAnchor, this.id);
+ if (oldAnchor) this.removeFromAriaLabelledBy(oldAnchor, this.id);
+ for (const [type, listener, capture] of events) {
+ newAnchor?.addEventListener(type, listener, { capture, signal });
+ oldAnchor?.removeEventListener(type, listener, capture);
}
this.anchor = newAnchor;
}
@@ -307,26 +258,19 @@ var WaTooltip = class extends WebAwesomeElement {
this.popup.reposition();
}
}
- handleDisabledChange() {
- if (this.disabled && this.open) {
- this.hide();
- }
+ async requestOpen(open) {
+ if (!this.isConnected) return;
+ this.open = open && !this.disabled;
+ await this.updateComplete;
+ return this.handleOpenChange();
}
/** Shows the tooltip. */
- async show() {
- if (this.open) {
- return void 0;
- }
- this.open = true;
- return waitForEvent(this, "wa-after-show");
+ show() {
+ return this.requestOpen(true);
}
/** Hides the tooltip */
- async hide() {
- if (!this.open) {
- return void 0;
- }
- this.open = false;
- return waitForEvent(this, "wa-after-hide");
+ hide() {
+ return this.requestOpen(false);
}
render() {
return html`
@@ -349,7 +293,7 @@ var WaTooltip = class extends WebAwesomeElement {
hover-bridge
.anchor=${this.anchor}
>
- <div part="body" class="body">
+ <div part="body" class="body" hidden>
<slot></slot>
</div>
</wa-popup>
@@ -401,7 +345,7 @@ __decorateClass([
state()
], WaTooltip.prototype, "anchor", 2);
__decorateClass([
- watch("open", { waitUntilFirstUpdate: true })
+ watch(["open", "disabled"], { waitUntilFirstUpdate: true })
], WaTooltip.prototype, "handleOpenChange", 1);
__decorateClass([
watch("for")
@@ -409,9 +353,6 @@ __decorateClass([
__decorateClass([
watch(["distance", "placement", "skidding"])
], WaTooltip.prototype, "handleOptionsChange", 1);
-__decorateClass([
- watch("disabled")
-], WaTooltip.prototype, "handleDisabledChange", 1);
WaTooltip = __decorateClass([
customElement("wa-tooltip")
], WaTooltip);
diff --git a/dist/chunks/chunk.BV5W4T7H.js b/dist/chunks/chunk.BV5W4T7H.js
index ae8bd9b5f14b5b0fbdab4faca38319fbc7e48b2e..9a52ad235e67c90728ca8829d627f9298d8b637d 100644
--- a/dist/chunks/chunk.BV5W4T7H.js
+++ b/dist/chunks/chunk.BV5W4T7H.js
@@ -29,6 +29,7 @@ var WaDropdownItem = class extends WebAwesomeElement {
constructor() {
super(...arguments);
this.hasSlotController = new HasSlotController(this, "[default]", "start", "end");
+ this.submenuGeneration = 0;
this.active = false;
this.variant = "default";
this.size = "m";
@@ -40,7 +41,10 @@ var WaDropdownItem = class extends WebAwesomeElement {
this.submenuOpen = false;
this.hasSubmenu = false;
this.handleSlotChange = () => {
- this.hasSubmenu = this.hasSlotController.test("submenu");
+ const hasSubmenu = this.hasSlotController.test("submenu");
+ // Retire the branch before losing the old submenu state and popup.
+ if (!hasSubmenu) void this.closeSubmenu();
+ this.hasSubmenu = hasSubmenu;
this.updateHasSubmenuState();
if (this.hasSubmenu) {
this.setAttribute("aria-haspopup", "menu");
@@ -67,7 +71,6 @@ var WaDropdownItem = class extends WebAwesomeElement {
/** Handles pointer enter to open the submenu on hover */
this.handlePointerEnter = (event) => {
if (event.pointerType === "mouse" && this.hasSubmenu && !this.disabled) {
- this.notifyParentOfOpening();
this.submenuOpen = true;
}
};
@@ -84,7 +87,15 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
disconnectedCallback() {
super.disconnectedCallback();
- this.closeSubmenu();
+ this.submenuGeneration++;
+ void this.closeSubmenu();
+ const submenu = this.submenuElement;
+ if (submenu) {
+ submenu.classList.remove("show", "hide");
+ submenu.hidden = true;
+ submenu.removeAttribute("data-visible");
+ submenu.hidePopover?.();
+ }
this.removeEventListener?.("click", this.handleHostClick);
this.removeEventListener?.("pointerenter", this.handlePointerEnter);
this.shadowRoot?.removeEventListener?.("click", this.handleClick, { capture: true });
@@ -127,11 +138,8 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
if (changedProperties.has("submenuOpen")) {
this.customStates.set("submenu-open", this.submenuOpen);
- if (this.submenuOpen) {
- this.openSubmenu();
- } else {
- this.closeSubmenu();
- }
+ const previousAnimation = this.submenuAnimation;
+ this.submenuAnimation = this.updateSubmenuVisibility(previousAnimation);
}
}
/** Update the has-submenu custom state */
@@ -140,22 +148,51 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
/** Opens the submenu. */
async openSubmenu() {
- const submenu = this.submenuElement;
- if (!this.hasSubmenu || !submenu || !this.isConnected) return;
- this.notifyParentOfOpening();
- submenu.showPopover?.();
- submenu.hidden = false;
- submenu.setAttribute("data-visible", "");
+ if (!this.hasSubmenu || !this.submenuElement || !this.isConnected) return;
this.submenuOpen = true;
- this.setAttribute("aria-expanded", "true");
- await animateWithClass(submenu, "show");
- setTimeout(() => {
+ await this.updateComplete;
+ return this.submenuAnimation;
+ }
+ /** Reconciles property and public-method requests through one submenu lifecycle. */
+ async updateSubmenuVisibility(previousAnimation) {
+ const submenu = this.submenuElement;
+ if (!this.submenuOpen) this.dispatchEvent(new Event("submenu-closing"));
+ if (!this.hasSubmenu || !submenu || !this.isConnected) {
+ await previousAnimation;
+ return;
+ }
+ const open = this.submenuOpen;
+ const generation = ++this.submenuGeneration;
+ const isCurrent = () => generation === this.submenuGeneration && this.isConnected && this.submenuOpen === open;
+ // Retire the previous class before joining its cleanup. A stale close must never
+ // hide a reopened native popover, and repeated public requests share this promise.
+ submenu.classList.remove("show", "hide");
+ if (open) {
+ this.notifyParentOfOpening();
+ if (!isCurrent()) return;
+ submenu.showPopover?.();
+ submenu.hidden = false;
+ submenu.setAttribute("data-visible", "");
+ }
+ // Opening observers initialize embedded controls before the expanded state changes.
+ this.setAttribute("aria-expanded", String(open));
+ await previousAnimation;
+ await this.updateComplete;
+ if (!isCurrent()) return;
+ // Focus once when opening becomes usable, not again when the animation finishes:
+ // the operator may already have moved focus or returned to the parent by then.
+ if (open) {
const items = this.getSubmenuItems();
- if (items.length > 0) {
- items.forEach((item, index) => item.active = index === 0);
- items[0].focus({ preventScroll: true });
- }
- }, 0);
+ items.forEach((item, index) => item.active = index === 0);
+ items[0]?.focus({ preventScroll: true });
+ }
+ await animateWithClass(submenu, open ? "show" : "hide");
+ if (!isCurrent()) return;
+ if (!open) {
+ submenu.hidden = true;
+ submenu.removeAttribute("data-visible");
+ submenu.hidePopover?.();
+ }
}
/** Notifies the parent dropdown that this item is opening its submenu */
notifyParentOfOpening() {
@@ -165,30 +202,15 @@ var WaDropdownItem = class extends WebAwesomeElement {
detail: { item: this }
});
this.dispatchEvent(event);
- const parent = this.parentElement;
- if (parent) {
- const siblings = [...parent.children].filter(
- (el) => el !== this && el.localName === "wa-dropdown-item" && el.getAttribute("slot") === this.getAttribute("slot") && el.submenuOpen
- );
- siblings.forEach((sibling) => {
- sibling.submenuOpen = false;
- });
- }
}
/** Closes the submenu. */
async closeSubmenu() {
- const submenu = this.submenuElement;
- if (!this.hasSubmenu || !submenu) return;
+ // A never-connected item's first update cannot settle; closing it is a public no-op.
+ if (!this.hasSubmenu || !this.submenuElement) return;
this.submenuOpen = false;
- this.setAttribute("aria-expanded", "false");
- if (!submenu.hidden) {
- await animateWithClass(submenu, "hide");
- if (submenu?.isConnected) {
- submenu.hidden = true;
- submenu.removeAttribute("data-visible");
- submenu.hidePopover?.();
- }
- }
+ this.dispatchEvent(new Event("submenu-closing"));
+ await this.updateComplete;
+ return this.submenuAnimation;
}
/** Determines whether the item navigates when selected. Items with submenus never navigate. */
isLink() {
@@ -218,8 +240,9 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
/** Gets all dropdown items in the submenu. */
getSubmenuItems() {
- return [...this.children].filter(
- (el) => el.localName === "wa-dropdown-item" && el.getAttribute("slot") === "submenu" && !el.hasAttribute("disabled")
+ const slot = this.submenuElement?.querySelector('slot[name="submenu"]');
+ return (slot?.assignedElements({ flatten: true }) ?? []).filter(
+ (el) => el.localName === "wa-dropdown-item" && !el.disabled
);
}
render() {
@@ -278,7 +301,7 @@ var WaDropdownItem = class extends WebAwesomeElement {
>
<slot name="submenu"></slot>
</div>
- ` : ""}
+ ` : html`<slot name="submenu" hidden></slot>`}
`;
}
};
diff --git a/dist/chunks/chunk.KB3IOPVF.js b/dist/chunks/chunk.KB3IOPVF.js
index 3b1abee761087a9d9784b7a91f6555e1be323145..a870bafe03138f84a1dcdc8a687cda3e5940ad1e 100644
--- a/dist/chunks/chunk.KB3IOPVF.js
+++ b/dist/chunks/chunk.KB3IOPVF.js
@@ -169,10 +169,17 @@ var WaPopup = class extends WebAwesomeElement {
if (!this.anchorEl || !this.active || !this.isConnected) {
return;
}
+ this.cleanup?.();
+ this.cleanup = void 0;
this.popup?.showPopover?.();
- this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
+ const cleanup = autoUpdate(this.anchorEl, this.popup, () => {
this.reposition();
});
+ if (!this.active || !this.isConnected) {
+ cleanup();
+ return;
+ }
+ this.cleanup = cleanup;
}
async stop() {
return new Promise((resolve) => {
diff --git a/dist/chunks/chunk.L6CIKOFQ.js b/dist/chunks/chunk.L6CIKOFQ.js
index 20d309f89017ef7d1857f3b1c88216a88354fefd..ea0b8ee34255009d9d9e86605dbdc05969ea3840 100644
--- a/dist/chunks/chunk.L6CIKOFQ.js
+++ b/dist/chunks/chunk.L6CIKOFQ.js
@@ -5,32 +5,19 @@ async function animate(el, keyframes, options) {
return el.animate(keyframes, options).finished.catch(() => {
});
}
-function animateWithClass(el, className) {
- return new Promise((resolve) => {
- const controller = new AbortController();
- const { signal } = controller;
- if (el.classList.contains(className)) {
- return;
- }
- el.classList.add(className);
- let resolved = false;
- let onEnd = () => {
- if (resolved) {
- return;
- }
- resolved = true;
- el.classList.remove(className);
- resolve();
- controller.abort();
- };
- el.addEventListener("animationend", onEnd, { once: true, signal });
- el.addEventListener("animationcancel", onEnd, { once: true, signal });
- requestAnimationFrame(() => {
- if (!resolved && el.getAnimations().length === 0) {
- onEnd();
- }
- });
- });
+async function animateWithClass(el, className) {
+ // Observe pending class removal before reusing the same animation name;
+ // otherwise an immediate show/hide can reuse the completed CSSAnimation.
+ el.getAnimations();
+ el.classList.add(className);
+ // Popup activation is reactive: let its display styles render before sampling.
+ // Native completion also settles animations canceled before animationstart, unlike DOM events.
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
+ const animations = el.getAnimations().filter(
+ (animation) => animation instanceof CSSAnimation && animation.effect?.getComputedTiming().endTime !== Infinity
+ );
+ await Promise.allSettled(animations.map((animation) => animation.finished));
+ el.classList.remove(className);
}
function parseDuration(duration) {
duration = duration.toString().toLowerCase();
diff --git a/dist/chunks/chunk.LKUZUNJV.js b/dist/chunks/chunk.LKUZUNJV.js
index e85a015903374362f83cdb454cb7f449346a9448..79901048ffc7619e842bfa31e09cd65ce602c07e 100644
--- a/dist/chunks/chunk.LKUZUNJV.js
+++ b/dist/chunks/chunk.LKUZUNJV.js
@@ -43,9 +43,6 @@ import {
import {
size_styles_default
} from "./chunk.G5ZZIGWB.js";
-import {
- waitForEvent
-} from "./chunk.F25QOBDY.js";
import {
animateWithClass
} from "./chunk.L6CIKOFQ.js";
@@ -74,6 +71,7 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this.selectionOrder = /* @__PURE__ */ new Map();
this.typeToSelectString = "";
this.slotChangePending = false;
+ this.openTransition = { open: false };
this.displayLabel = "";
this.selectedOptions = [];
this.name = "";
@@ -294,7 +292,12 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
}
disconnectedCallback() {
super.disconnectedCallback();
- this.removeOpenListeners();
+ this.openTransition.controller?.abort();
+ this.openTransition = { open: false };
+ this.popup?.popup?.classList.remove("show", "hide");
+ if (this.listbox) this.listbox.hidden = true;
+ if (this.popup) this.popup.active = false;
+ unregisterDismissible(this);
this.cachedOptions = null;
}
updateDefaultValue() {
@@ -308,24 +311,6 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this._defaultValue = this.getAttribute("value") || null;
}
}
- addOpenListeners() {
- document.addEventListener("focusin", this.handleDocumentFocusIn);
- document.addEventListener("keydown", this.handleDocumentKeyDown);
- document.addEventListener("mousedown", this.handleDocumentMouseDown);
- registerDismissible(this);
- if (this.getRootNode() !== document) {
- this.getRootNode().addEventListener("focusin", this.handleDocumentFocusIn);
- }
- }
- removeOpenListeners() {
- document.removeEventListener("focusin", this.handleDocumentFocusIn);
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("mousedown", this.handleDocumentMouseDown);
- unregisterDismissible(this);
- if (this.getRootNode() !== document) {
- this.getRootNode().removeEventListener("focusin", this.handleDocumentFocusIn);
- }
- }
handleFocus() {
this.displayInput.setSelectionRange(0, 0);
}
@@ -590,6 +575,8 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
handleDisabledChange() {
if (this.disabled && this.open) {
this.open = false;
+ // The open watcher has already run for this update.
+ this.handleOpenChange();
}
}
handleValueChange() {
@@ -600,56 +587,72 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this.updateValidity();
}
async handleOpenChange() {
- if (this.open && !this.disabled) {
- this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption());
- const waShowEvent = new WaShowEvent();
- this.dispatchEvent(waShowEvent);
- if (waShowEvent.defaultPrevented) {
- this.open = false;
- return;
- }
- this.addOpenListeners();
+ const open = this.open && !this.disabled;
+ if (!this.isConnected || this.openTransition.open === open) return this.openAnimation;
+ if (open) this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption());
+ const event = open ? new WaShowEvent() : new WaHideEvent();
+ this.dispatchEvent(event);
+ if (event.defaultPrevented && !this.disabled) {
+ this.open = this.openTransition.open;
+ return this.openAnimation;
+ }
+ if (!this.isConnected || (this.open && !this.disabled) !== open) return this.openAnimation;
+ this.openTransition.controller?.abort();
+ this.openTransition = { open, controller: new AbortController() };
+ const { signal } = this.openTransition.controller;
+ const isCurrent = () => !signal.aborted && this.isConnected && (this.open && !this.disabled) === open;
+ if (open) {
+ document.addEventListener("focusin", this.handleDocumentFocusIn, { signal });
+ document.addEventListener("keydown", this.handleDocumentKeyDown, { signal });
+ document.addEventListener("mousedown", this.handleDocumentMouseDown, { signal });
+ registerDismissible(this);
+ const root = this.getRootNode();
+ if (root !== document) root.addEventListener("focusin", this.handleDocumentFocusIn, { signal });
this.listbox.hidden = false;
this.popup.active = true;
requestAnimationFrame(() => {
- this.setCurrentOption(this.currentOption);
+ if (isCurrent()) this.setCurrentOption(this.currentOption);
});
- await animateWithClass(this.popup.popup, "show");
- if (this.currentOption) {
+ } else {
+ unregisterDismissible(this);
+ }
+ // Retire the old animation before joining its cleanup; only the accepted
+ // transition may publish visibility, focus, or completion after an await.
+ this.popup.popup.classList.remove("show", "hide");
+ const previousAnimation = this.openAnimation;
+ this.openAnimation = (async () => {
+ await previousAnimation;
+ if (!isCurrent()) return;
+ await animateWithClass(this.popup.popup, open ? "show" : "hide");
+ if (!isCurrent()) return;
+ if (open && this.currentOption) {
scrollIntoView(this.currentOption, this.listbox, "vertical", "auto");
}
- this.dispatchEvent(new WaAfterShowEvent());
- } else {
- const waHideEvent = new WaHideEvent();
- this.dispatchEvent(waHideEvent);
- if (waHideEvent.defaultPrevented) {
- this.open = false;
- return;
+ if (!open) {
+ this.listbox.hidden = true;
+ this.popup.active = false;
}
- this.removeOpenListeners();
- await animateWithClass(this.popup.popup, "hide");
- this.listbox.hidden = true;
- this.popup.active = false;
- this.dispatchEvent(new WaAfterHideEvent());
- }
+ this.dispatchEvent(open ? new WaAfterShowEvent() : new WaAfterHideEvent());
+ })();
+ return this.openAnimation;
}
/** Shows the listbox. */
async show() {
- if (this.open || this.disabled) {
+ if (this.disabled) {
this.open = false;
- return void 0;
+ return;
}
+ if (!this.isConnected) return;
this.open = true;
- return waitForEvent(this, "wa-after-show");
+ await this.updateComplete;
+ return this.handleOpenChange();
}
/** Hides the listbox. */
async hide() {
- if (!this.open || this.disabled) {
- this.open = false;
- return void 0;
- }
this.open = false;
- return waitForEvent(this, "wa-after-hide");
+ if (!this.isConnected) return;
+ await this.updateComplete;
+ return this.openAnimation;
}
/** Sets focus on the control. */
focus(options) {
diff --git a/dist/chunks/chunk.XSKA3LE6.js b/dist/chunks/chunk.XSKA3LE6.js
index 000b05d33179ddd930fd72221becf7a7e3042a2f..971c99516ef06aabb30afca195c4aa45f941bfed 100644
--- a/dist/chunks/chunk.XSKA3LE6.js
+++ b/dist/chunks/chunk.XSKA3LE6.js
@@ -58,17 +58,16 @@ var openDropdowns = /* @__PURE__ */ new Set();
var WaDropdown = class extends WebAwesomeElement {
constructor() {
super(...arguments);
- this.submenuCleanups = /* @__PURE__ */ new Map();
this.localize = new LocalizeController(this);
this.userTypedQuery = "";
- this.openSubmenuStack = [];
+ this.activeSubmenus = [];
this.open = false;
this.size = "m";
this.placement = "bottom-start";
this.distance = 0;
this.skidding = 0;
/** Handles key down events when the menu is open */
- this.handleDocumentKeyDown = async (event) => {
+ this.handleDocumentKeyDown = (event) => {
const isRtl = this.localize.dir() === "rtl";
if (event.key === "Escape" && this.open && isTopDismissible(this)) {
const trigger = this.getTrigger();
@@ -78,22 +77,12 @@ var WaDropdown = class extends WebAwesomeElement {
trigger?.focus({ preventScroll: true });
return;
}
- const activeElement = [...activeElements()].find((el) => el.localName === "wa-dropdown-item");
- const isFocusedOnItem = activeElement?.localName === "wa-dropdown-item";
+ const focusedItem = [...activeElements()].find((el) => el.localName === "wa-dropdown-item");
const currentSubmenuItem = this.getCurrentSubmenuItem();
- const isInSubmenu = !!currentSubmenuItem;
- let items;
- let activeItem;
- let activeItemIndex;
- if (isInSubmenu) {
- items = this.getSubmenuItems(currentSubmenuItem);
- activeItem = items.find((item) => item.active || item === activeElement);
- activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
- } else {
- items = this.getItems();
- activeItem = items.find((item) => item.active || item === activeElement);
- activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
- }
+ const items = currentSubmenuItem ? this.getSubmenuItems(currentSubmenuItem) : this.getItems();
+ // A focused item outside this level (e.g. the owner of a hover-opened submenu) keeps its keys.
+ const activeItem = items.find((item) => item === focusedItem) ?? (focusedItem ? void 0 : items.find((item) => item.active));
+ const activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
let itemToSelect;
if (event.key === "ArrowUp") {
event.preventDefault();
@@ -113,39 +102,23 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect = items[0];
}
}
- if (event.key === (isRtl ? "ArrowLeft" : "ArrowRight") && isFocusedOnItem && activeItem) {
+ if (event.key === (isRtl ? "ArrowLeft" : "ArrowRight") && focusedItem && activeItem) {
if (activeItem.hasSubmenu) {
event.preventDefault();
event.stopPropagation();
activeItem.submenuOpen = true;
- this.addToSubmenuStack(activeItem);
- setTimeout(() => {
- const submenuItems = this.getSubmenuItems(activeItem);
- if (submenuItems.length > 0) {
- submenuItems.forEach((item, index) => item.active = index === 0);
- submenuItems[0].focus({ preventScroll: true });
- }
- }, 0);
return;
}
}
- if (event.key === (isRtl ? "ArrowRight" : "ArrowLeft") && isInSubmenu) {
+ if (event.key === (isRtl ? "ArrowRight" : "ArrowLeft") && currentSubmenuItem) {
event.preventDefault();
event.stopPropagation();
- const removedItem = this.removeFromSubmenuStack();
- if (removedItem) {
- removedItem.submenuOpen = false;
- setTimeout(() => {
- removedItem.focus({ preventScroll: true });
- removedItem.active = true;
- const parentItems = removedItem.slot === "submenu" ? this.getSubmenuItems(removedItem.parentElement) : this.getItems();
- parentItems.forEach((item) => {
- if (item !== removedItem) {
- item.active = false;
- }
- });
- }, 0);
- }
+ // Closing retires ownership synchronously; only this keypress restores focus.
+ void currentSubmenuItem.closeSubmenu();
+ currentSubmenuItem.focus({ preventScroll: true });
+ const parent = this.getCurrentSubmenuItem();
+ const parentItems = parent ? this.getSubmenuItems(parent, true) : this.getItems(true);
+ parentItems.forEach((item) => item.active = item === currentSubmenuItem);
return;
}
if (event.key === "Home" || event.key === "End") {
@@ -154,7 +127,7 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect = event.key === "Home" ? items[0] : items[items.length - 1];
}
if (event.key === "Tab") {
- await this.hideMenu();
+ this.open = false;
}
if (event.key.length === 1 && !(event.metaKey || event.ctrlKey || event.altKey) && !(event.key === " " && this.userTypedQuery === "")) {
clearTimeout(this.userTypedTimeout);
@@ -180,19 +153,11 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect.scrollIntoView({ block: "nearest" });
return;
}
- if ((event.key === "Enter" || event.key === " " && this.userTypedQuery === "") && isFocusedOnItem && activeItem) {
+ if ((event.key === "Enter" || event.key === " " && this.userTypedQuery === "") && focusedItem && activeItem) {
event.preventDefault();
event.stopPropagation();
if (activeItem.hasSubmenu) {
activeItem.submenuOpen = true;
- this.addToSubmenuStack(activeItem);
- setTimeout(() => {
- const submenuItems = this.getSubmenuItems(activeItem);
- if (submenuItems.length > 0) {
- submenuItems.forEach((item, index) => item.active = index === 0);
- submenuItems[0].focus({ preventScroll: true });
- }
- }, 0);
} else {
this.makeSelection(activeItem, event);
}
@@ -213,7 +178,8 @@ var WaDropdown = class extends WebAwesomeElement {
};
/** Handle global mouse movement for safe triangle logic */
this.handleGlobalMouseMove = (event) => {
- const currentSubmenuItem = this.getCurrentSubmenuItem();
+ const branch = this.activeSubmenus.at(-1);
+ const currentSubmenuItem = branch?.item;
if (!currentSubmenuItem?.submenuOpen || !currentSubmenuItem.submenuElement) return;
const submenuRect = currentSubmenuItem.submenuElement.getBoundingClientRect();
const isRtl = this.localize.dir() === "rtl";
@@ -231,7 +197,7 @@ var WaDropdown = class extends WebAwesomeElement {
);
if (!isOverItem && !isOverSubmenu) {
setTimeout(() => {
- if (!submenuItemHovered && !submenuElementHovered) {
+ if (this.activeSubmenus.includes(branch) && !currentSubmenuItem.matches(":hover") && !currentSubmenuItem.submenuElement.matches(":hover")) {
currentSubmenuItem.submenuOpen = false;
}
}, 100);
@@ -241,15 +207,20 @@ var WaDropdown = class extends WebAwesomeElement {
handleSizeChange() {
warnDeprecatedSize(this.localName, this.size);
}
+ connectedCallback() {
+ super.connectedCallback();
+ if (this.hasUpdated && this.open) {
+ void this.updateMenuVisibility(true);
+ }
+ }
disconnectedCallback() {
super.disconnectedCallback();
+ this.menuTransition?.abort();
+ this.menu?.classList.remove("show", "hide");
+ if (this.popup) this.popup.active = false;
+ openDropdowns.delete(this);
clearInterval(this.userTypedTimeout);
this.closeAllSubmenus();
- this.submenuCleanups.forEach((cleanup) => cleanup());
- this.submenuCleanups.clear();
- document.removeEventListener("mousemove", this.handleGlobalMouseMove);
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("pointerdown", this.handleDocumentPointerDown);
unregisterDismissible(this);
}
firstUpdated(changedProperties) {
@@ -266,12 +237,7 @@ var WaDropdown = class extends WebAwesomeElement {
return;
}
this.customStates.set("open", this.open);
- if (this.open) {
- await this.showMenu();
- } else {
- this.closeAllSubmenus();
- await this.hideMenu();
- }
+ await this.updateMenuVisibility(this.open);
}
if (changedProperties.has("size")) {
this.syncItemSizes();
@@ -300,104 +266,96 @@ var WaDropdown = class extends WebAwesomeElement {
);
items.forEach((item) => item.size = this.size);
}
- /** Handles the submenu navigation stack */
- addToSubmenuStack(item) {
- const index = this.openSubmenuStack.indexOf(item);
- if (index !== -1) {
- this.openSubmenuStack = this.openSubmenuStack.slice(0, index + 1);
- } else {
- this.openSubmenuStack.push(item);
- }
- }
- /** Removes the last item from the submenu stack */
- removeFromSubmenuStack() {
- return this.openSubmenuStack.pop();
- }
- /** Gets the current active submenu item */
+ /** Gets the current active submenu item. */
getCurrentSubmenuItem() {
- return this.openSubmenuStack.length > 0 ? this.openSubmenuStack[this.openSubmenuStack.length - 1] : void 0;
+ return this.activeSubmenus.at(-1)?.item;
}
- /** Closes all submenus in the dropdown. */
- closeAllSubmenus() {
- const items = this.getItems(true);
- items.forEach((item) => {
- item.submenuOpen = false;
- });
- this.openSubmenuStack = [];
- }
- /** Closes sibling submenus at the same level as the specified item. */
- closeSiblingSubmenus(item) {
- const parentDropdownItem = item.closest('wa-dropdown-item:not([slot="submenu"])');
- let siblingItems;
- if (parentDropdownItem) {
- siblingItems = this.getSubmenuItems(parentDropdownItem, true);
- } else {
- siblingItems = this.getItems(true);
- }
- siblingItems.forEach((siblingItem) => {
- if (siblingItem !== item && siblingItem.submenuOpen) {
- siblingItem.submenuOpen = false;
- }
- });
- if (!this.openSubmenuStack.includes(item)) {
- this.openSubmenuStack.push(item);
+ /** Retires an ancestor branch before its hide animations can complete. */
+ closeAllSubmenus(from = 0) {
+ // Remove ownership first: item close notifications may reenter this owner.
+ for (const { item, cleanup } of this.activeSubmenus.splice(from).reverse()) {
+ cleanup();
+ void item.closeSubmenu();
}
}
/** Get the slotted trigger button, a <wa-button> or <button> element */
getTrigger() {
return this.querySelector('[slot="trigger"]');
}
- /** Shows the dropdown menu. This should only be called from within updated(). */
- async showMenu() {
- const anchor = this.getTrigger();
- if (!anchor || !this.popup || !this.menu) return;
- const showEvent = new WaShowEvent();
- this.dispatchEvent(showEvent);
- if (showEvent.defaultPrevented) {
- this.open = false;
+ /** Reconciles accepted visibility and its animation from updates or reconnection. */
+ async updateMenuVisibility(open) {
+ if (!this.isConnected || !this.popup || !this.menu || open && !this.getTrigger()) return;
+ const event = open ? new WaShowEvent() : new WaHideEvent({ source: this });
+ this.dispatchEvent(event);
+ if (event.defaultPrevented) {
+ this.open = !open;
return;
}
- if (this.popup.active) {
- return;
+ // Membership records accepted open ownership. The popup stays active while hiding,
+ // so its active flag cannot distinguish a canceled hide from a reopen.
+ if (!this.isConnected || this.open !== open || open && openDropdowns.has(this)) return;
+ // One cancellation owner releases document listeners and fences completion on
+ // replacement or disconnection; a veto above leaves that ownership untouched.
+ this.menuTransition?.abort();
+ this.menuTransition = new AbortController();
+ const { signal } = this.menuTransition;
+ const isCurrent = () => !signal.aborted && this.isConnected && this.open === open;
+ if (open) {
+ openDropdowns.forEach((dropdown) => dropdown.open = false);
+ this.popup.active = true;
+ openDropdowns.add(this);
+ registerDismissible(this);
+ document.addEventListener("keydown", this.handleDocumentKeyDown, { signal });
+ document.addEventListener("pointerdown", this.handleDocumentPointerDown, { signal });
+ document.addEventListener("mousemove", this.handleGlobalMouseMove, { signal });
+ } else {
+ this.closeAllSubmenus();
+ openDropdowns.delete(this);
+ unregisterDismissible(this);
}
- openDropdowns.forEach((dropdown) => dropdown.open = false);
- this.popup.active = true;
- this.open = true;
- openDropdowns.add(this);
- registerDismissible(this);
this.syncAriaAttributes();
- document.addEventListener("keydown", this.handleDocumentKeyDown);
- document.addEventListener("pointerdown", this.handleDocumentPointerDown);
- document.addEventListener("mousemove", this.handleGlobalMouseMove);
- this.menu.classList.remove("hide");
- await animateWithClass(this.menu, "show");
- const items = this.getItems();
- if (items.length > 0) {
- items.forEach((item, index) => item.active = index === 0);
- items[0].focus({ preventScroll: true });
+ // Join canceled animation cleanup before reusing its class. Show and hide share
+ // one CSS animation name, so overlapping helpers cannot own separate animations.
+ this.menu.classList.remove("show", "hide");
+ const focused = [...activeElements()].pop();
+ await this.menuAnimation;
+ if (!isCurrent()) return;
+ this.menuAnimation = animateWithClass(this.menu, open ? "show" : "hide");
+ if (open) {
+ await this.popup.updateComplete;
+ // Initial anchor slot changes can restart the native popover on the next frame.
+ // Focus after that activation settles, before waiting for the menu animation.
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
+ if (!isCurrent()) return;
+ // Input accepted during cleanup or popup rendering already owns the selection.
+ const submenu = this.getCurrentSubmenuItem();
+ const items = submenu ? this.getSubmenuItems(submenu, true) : this.getItems(true);
+ const content = this.defaultSlot.assignedElements({ flatten: true });
+ const ownsFocus = (element) => element instanceof HTMLElement &&
+ element.closest("wa-dropdown") === this && !element.matches(":disabled, [disabled]") &&
+ content.some((root) => root.contains(element));
+ const focusedItem = [...activeElements()].find(ownsFocus);
+ const autofocusItem = focusedItem ? void 0 : [...this.querySelectorAll("[autofocus]")].find(
+ (element) => ownsFocus(element) && element.getClientRects().length > 0
+ );
+ const initialItem = focusedItem ?? autofocusItem ?? items.find((item) => !item.disabled);
+ // Owned input is authoritative; newer outside focus also supersedes initialization.
+ if (initialItem && (focusedItem || [...activeElements()].pop() === focused)) {
+ items.forEach((item) => item.active = item === initialItem);
+ if (initialItem.localName === "wa-dropdown-item") initialItem.active = true;
+ if (!focusedItem) initialItem.focus({ preventScroll: true });
+ if (initialItem.autofocus) initialItem.scrollIntoView({ block: "nearest" });
+ }
+ if (!isCurrent()) return;
}
- this.dispatchEvent(new WaAfterShowEvent());
- }
- /** Hides the dropdown menu. This should only be called from within updated(). */
- async hideMenu() {
- if (!this.popup || !this.menu) return;
- const hideEvent = new WaHideEvent({ source: this });
- this.dispatchEvent(hideEvent);
- if (hideEvent.defaultPrevented) {
- this.open = true;
- return;
+ await this.menuAnimation;
+ if (!isCurrent()) return;
+ if (open) {
+ this.dispatchEvent(new WaAfterShowEvent());
+ } else {
+ this.popup.active = false;
+ this.dispatchEvent(new WaAfterHideEvent());
}
- this.open = false;
- openDropdowns.delete(this);
- unregisterDismissible(this);
- this.syncAriaAttributes();
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("pointerdown", this.handleDocumentPointerDown);
- document.removeEventListener("mousemove", this.handleGlobalMouseMove);
- this.menu.classList.remove("show");
- await animateWithClass(this.menu, "hide");
- this.popup.active = this.open;
- this.dispatchEvent(new WaAfterHideEvent());
}
/** Handles clicks on the menu. */
handleMenuClick(event) {
@@ -405,8 +363,6 @@ var WaDropdown = class extends WebAwesomeElement {
if (!item || item.disabled) return;
if (item.hasSubmenu) {
if (!item.submenuOpen) {
- this.closeSiblingSubmenus(item);
- this.addToSubmenuStack(item);
item.submenuOpen = true;
}
event.stopPropagation();
@@ -433,60 +389,57 @@ var WaDropdown = class extends WebAwesomeElement {
handleTriggerClick() {
this.open = !this.open;
}
- /** Handles submenu opening events */
+ /** Keeps one composed ancestor branch, including forwarded submenu slots. */
handleSubmenuOpening(event) {
- const openingItem = event.detail.item;
- this.closeSiblingSubmenus(openingItem);
- this.addToSubmenuStack(openingItem);
- this.setupSubmenuPosition(openingItem);
- this.processSubmenuItems(openingItem);
+ event.stopPropagation();
+ const path = event.composedPath();
+ const branch = path.slice(0, path.indexOf(this)).filter((el) => el instanceof HTMLElement && el.localName === "wa-dropdown-item").reverse();
+ // A child requested during ancestor teardown cannot own an orphan popover.
+ if (branch.some((item) => !item.submenuOpen)) {
+ void event.detail.item.closeSubmenu();
+ return;
+ }
+ let shared = 0;
+ while (shared < branch.length && this.activeSubmenus[shared]?.item === branch[shared]) shared++;
+ // Repeated ancestor notifications must not discard its still-open descendants.
+ if (shared === branch.length) return;
+ this.closeAllSubmenus(shared);
+ for (const item of branch.slice(shared)) {
+ this.activeSubmenus.push({ item, cleanup: this.setupSubmenuPosition(item) });
+ }
}
- /** Sets up submenu positioning with autoUpdate */
+ /** Binds positioning and close notifications to this exact open lifetime. */
setupSubmenuPosition(item) {
- if (!item.submenuElement) return;
- this.cleanupSubmenuPosition(item);
+ const controller = new AbortController();
+ const { signal } = controller;
const cleanup = autoUpdate(item, item.submenuElement, () => {
- this.positionSubmenu(item);
+ this.positionSubmenu(item, () => !signal.aborted);
this.updateSafeTriangleCoordinates(item);
});
- this.submenuCleanups.set(item, cleanup);
+ // Listen on the item itself: a disconnected item's event cannot bubble here.
+ item.addEventListener("submenu-closing", () => {
+ const index = this.activeSubmenus.findIndex((entry) => entry.item === item);
+ if (index !== -1) this.closeAllSubmenus(index);
+ }, { signal });
const submenuSlot = item.submenuElement.querySelector('slot[name="submenu"]');
- if (submenuSlot) {
- submenuSlot.removeEventListener("slotchange", WaDropdown.handleSubmenuSlotChange);
- submenuSlot.addEventListener("slotchange", WaDropdown.handleSubmenuSlotChange);
- WaDropdown.handleSubmenuSlotChange({ target: submenuSlot });
- }
- }
- static handleSubmenuSlotChange(event) {
- const slot = event.target;
- if (!slot) return;
- const items = slot.assignedElements().filter((el) => el.localName === "wa-dropdown-item");
- if (items.length === 0) return;
- const hasSubmenuItems = items.some((item) => item.hasSubmenu);
- const hasCheckboxItems = items.some((item) => item.type === "checkbox");
- items.forEach((item) => {
- item.submenuAdjacent = hasSubmenuItems;
- item.checkboxAdjacent = hasCheckboxItems;
- });
+ submenuSlot?.addEventListener("slotchange", () => this.processSubmenuItems(item), { signal });
+ this.processSubmenuItems(item);
+ return () => {
+ controller.abort();
+ cleanup();
+ };
}
processSubmenuItems(item) {
- if (!item.submenuElement) return;
- const submenuItems = this.getSubmenuItems(item, true);
- const hasSubmenuItems = submenuItems.some((subItem) => subItem.hasSubmenu);
- submenuItems.forEach((subItem) => {
- subItem.submenuAdjacent = hasSubmenuItems;
+ const items = this.getSubmenuItems(item, true);
+ const hasSubmenu = items.some((child) => child.hasSubmenu);
+ const hasCheckbox = items.some((child) => child.type === "checkbox");
+ items.forEach((child) => {
+ child.submenuAdjacent = hasSubmenu;
+ child.checkboxAdjacent = hasCheckbox;
});
}
- /** Cleans up submenu positioning */
- cleanupSubmenuPosition(item) {
- const cleanup = this.submenuCleanups.get(item);
- if (cleanup) {
- cleanup();
- this.submenuCleanups.delete(item);
- }
- }
/** Positions a submenu relative to its parent item */
- positionSubmenu(item) {
+ positionSubmenu(item, isCurrent) {
if (!item.submenuElement) return;
const isRtl = this.localize.dir() === "rtl";
const placement = isRtl ? "left-start" : "right-start";
@@ -506,6 +459,7 @@ var WaDropdown = class extends WebAwesomeElement {
})
]
}).then(({ x, y, placement: placement2 }) => {
+ if (!isCurrent()) return;
item.submenuElement.setAttribute("data-placement", placement2);
Object.assign(item.submenuElement.style, {
left: `${x}px`,
diff --git a/dist/components/dropdown/dropdown.d.ts b/dist/components/dropdown/dropdown.d.ts
index 561ccfa258274c2c41cb728c0bfef26381c9f30c..7da1ccc5e1a55eeef5df886f612449832dc01157 100644
--- a/dist/components/dropdown/dropdown.d.ts
+++ b/dist/components/dropdown/dropdown.d.ts
@@ -29,11 +29,12 @@ import '../popup/popup.js';
*/
export default class WaDropdown extends WebAwesomeElement {
static css: import("lit").CSSResult[];
- private submenuCleanups;
private readonly localize;
private userTypedQuery;
private userTypedTimeout;
- private openSubmenuStack;
+ private activeSubmenus;
+ private menuTransition?;
+ private menuAnimation?;
defaultSlot: HTMLSlotElement;
private menu;
private popup;
@@ -51,6 +52,7 @@ export default class WaDropdown extends WebAwesomeElement {
distance: number;
/** The offset of the dropdown menu along its trigger. */
skidding: number;
+ connectedCallback(): void;
disconnectedCallback(): void;
firstUpdated(changedProperties: PropertyValues<typeof this>): void;
updated(changedProperties: PropertyValues<typeof this>): Promise<void>;
@@ -60,22 +62,14 @@ export default class WaDropdown extends WebAwesomeElement {
private getSubmenuItems;
/** Syncs item sizes with the dropdown's size property. */
private syncItemSizes;
- /** Handles the submenu navigation stack */
- private addToSubmenuStack;
- /** Removes the last item from the submenu stack */
- private removeFromSubmenuStack;
- /** Gets the current active submenu item */
+ /** Gets the current active submenu item. */
private getCurrentSubmenuItem;
- /** Closes all submenus in the dropdown. */
+ /** Retires an ancestor branch before its hide animations can complete. */
private closeAllSubmenus;
- /** Closes sibling submenus at the same level as the specified item. */
- private closeSiblingSubmenus;
/** Get the slotted trigger button, a <wa-button> or <button> element */
private getTrigger;
- /** Shows the dropdown menu. This should only be called from within updated(). */
- private showMenu;
- /** Hides the dropdown menu. This should only be called from within updated(). */
- private hideMenu;
+ /** Reconciles accepted visibility and its animation from updates or reconnection. */
+ private updateMenuVisibility;
/** Handles key down events when the menu is open */
private handleDocumentKeyDown;
/** Handles pointer down events when the dropdown is open. */
@@ -90,10 +84,7 @@ export default class WaDropdown extends WebAwesomeElement {
private handleSubmenuOpening;
/** Sets up submenu positioning with autoUpdate */
private setupSubmenuPosition;
- private static handleSubmenuSlotChange;
private processSubmenuItems;
- /** Cleans up submenu positioning */
- private cleanupSubmenuPosition;
/** Positions a submenu relative to its parent item */
private positionSubmenu;
/** Updates the safe triangle coordinates for a submenu */
diff --git a/dist/components/dropdown-item/dropdown-item.d.ts b/dist/components/dropdown-item/dropdown-item.d.ts
index 1e458bc4c742d94cd58d688acde8a45f9c681763..477eac752d3d2c0a81ee3a59a80c09b81d972888 100644
--- a/dist/components/dropdown-item/dropdown-item.d.ts
+++ b/dist/components/dropdown-item/dropdown-item.d.ts
@@ -35,6 +35,8 @@ import '../icon/icon.js';
export default class WaDropdownItem extends WebAwesomeElement {
static css: import("lit").CSSResult;
private readonly hasSlotController;
+ private submenuGeneration;
+ private submenuAnimation?;
submenuElement: HTMLDivElement;
private linkElement;
/** @internal The controller will set this property to true when the item is active. */
@@ -91,6 +93,8 @@ export default class WaDropdownItem extends WebAwesomeElement {
private updateHasSubmenuState;
/** Opens the submenu. */
openSubmenu(): Promise<void>;
+ /** Reconciles property and public-method requests through one submenu lifecycle. */
+ private updateSubmenuVisibility;
/** Notifies the parent dropdown that this item is opening its submenu */
private notifyParentOfOpening;
/** Closes the submenu. */
diff --git a/dist/components/select/select.d.ts b/dist/components/select/select.d.ts
index 9b4c748a83e5e990fd6eeb6367b37713b56773b2..f801fce20728a10630b6e47099a1054b464fb37c 100644
--- a/dist/components/select/select.d.ts
+++ b/dist/components/select/select.d.ts
@@ -66,6 +66,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
static get validators(): import("../../internal/webawesome-form-associated-element.js").Validator<WebAwesomeFormAssociatedElement>[];
assumeInteractionOn: string[];
private cachedOptions;
+ private openTransition;
+ private openAnimation;
private readonly hasSlotController;
private readonly localize;
private selectionOrder;
@@ -154,8 +156,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
connectedCallback(): void;
disconnectedCallback(): void;
private updateDefaultValue;
- private addOpenListeners;
- private removeOpenListeners;
private handleFocus;
private handleDocumentFocusIn;
private handleDocumentKeyDown;
diff --git a/dist/components/tooltip/tooltip.d.ts b/dist/components/tooltip/tooltip.d.ts
index dfbeaf70a47cf538c5c47e3efc338a74fde965d8..d2b2e05e8f364ed0834394bc0c8c77f132a03f81 100644
--- a/dist/components/tooltip/tooltip.d.ts
+++ b/dist/components/tooltip/tooltip.d.ts
@@ -1,4 +1,3 @@
-import { type PropertyValues } from 'lit';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import WaPopup from '../popup/popup.js';
/**
@@ -62,19 +61,14 @@ export default class WaTooltip extends WebAwesomeElement {
for: string | null;
anchor: null | Element;
private eventController;
+ private openTransition;
+ private requestOpen;
connectedCallback(): void;
disconnectedCallback(): void;
- firstUpdated(changedProperties: PropertyValues<typeof this>): void;
- private handleBlur;
- private handleClick;
- private handleFocus;
- private handleMouseDown;
+ private handleTrigger;
/** Hides the tooltip, or cancels a pending show, and keeps it hidden until re-armed. */
private lightDismiss;
- private handleDocumentKeyDown;
- private handleDocumentClick;
- private handleMouseOver;
- private handleMouseOut;
+ private handleDocumentDismiss;
private hasTrigger;
/** Adds the tooltip ID to the aria-labelledby attribute */
private addToAriaLabelledBy;
@@ -83,7 +77,6 @@ export default class WaTooltip extends WebAwesomeElement {
handleOpenChange(): Promise<void>;
handleForChange(): void;
handleOptionsChange(): Promise<void>;
- handleDisabledChange(): void;
/** Shows the tooltip. */
show(): Promise<void>;
/** Hides the tooltip */
diff --git a/dist/custom-elements.json b/dist/custom-elements.json
index 48c83fbc1a4ab54cd027784a9750b73d52bd81c9..26d881a6d608917a2c2d763238e93f2edc59b9db 100644
--- a/dist/custom-elements.json
+++ b/dist/custom-elements.json
@@ -18189,22 +18189,12 @@
},
{
"kind": "field",
- "name": "handleBlur",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleClick",
+ "name": "openTransition",
"privacy": "private"
},
{
"kind": "field",
- "name": "handleFocus",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseDown",
+ "name": "handleTrigger",
"privacy": "private"
},
{
@@ -18215,22 +18205,7 @@
},
{
"kind": "field",
- "name": "handleDocumentKeyDown",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleDocumentClick",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseOver",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseOut",
+ "name": "handleDocumentDismiss",
"privacy": "private"
},
{
@@ -18307,13 +18282,6 @@
"text": "handleOptionsChange() => void"
}
},
- {
- "kind": "method",
- "name": "handleDisabledChange",
- "type": {
- "text": "handleDisabledChange() => void"
- }
- },
{
"kind": "method",
"name": "show",
@@ -18399,6 +18367,11 @@
"name": "WebAwesomeElement",
"module": "internal/webawesome-element.js"
}
+ },
+ {
+ "kind": "method",
+ "name": "requestOpen",
+ "privacy": "private"
}
],
"events": [
@@ -27985,13 +27958,13 @@
"privacy": "private"
},
{
- "kind": "method",
- "name": "addOpenListeners",
+ "kind": "field",
+ "name": "openTransition",
"privacy": "private"
},
{
- "kind": "method",
- "name": "removeOpenListeners",
+ "kind": "field",
+ "name": "openAnimation",
"privacy": "private"
},
{
diff --git a/dist/internal/animate.d.ts b/dist/internal/animate.d.ts
index bedb1923179f2a73be2eaf24e8c1a3c35718683d..04dc1c15097ad58de31a8670e2c870d3a475b96f 100644
--- a/dist/internal/animate.d.ts
+++ b/dist/internal/animate.d.ts
@@ -1,9 +1,6 @@
/** Same as `el.animate()`, except returns a promise that doesn't throw an error when the animation is canceled. */
export declare function animate(el: Element, keyframes: Keyframe[], options?: KeyframeAnimationOptions): Promise<void | Animation>;
-/**
- * Applies a class to the specified element to animate it. The class is removed after the animation finishes and then
- * the promise resolves. If a timeout is provided, the class will be removed and the animation will
- */
+/** Applies an animation class and removes it after its finite CSS animations finish or are canceled. */
export declare function animateWithClass(el: Element, className: string): Promise<void>;
/** Parses a CSS duration and returns the number of milliseconds. */
export declare function parseDuration(duration: number | string): number;
diff --git a/dist-cdn/chunks/chunk.EPFYRZYN.js b/dist-cdn/chunks/chunk.EPFYRZYN.js
index dcad1dbeba28c41e7798b9524c87cd19248e1493..b53cb1bcbd40e8d6aae77687f326a259964de436 100644
--- a/dist-cdn/chunks/chunk.EPFYRZYN.js
+++ b/dist-cdn/chunks/chunk.EPFYRZYN.js
@@ -46,9 +46,6 @@ import {
import {
size_styles_default
} from "./chunk.JB5Y2AN3.js";
-import {
- waitForEvent
-} from "./chunk.F25QOBDY.js";
import {
animateWithClass
} from "./chunk.L6CIKOFQ.js";
@@ -88,6 +85,7 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this.selectionOrder = /* @__PURE__ */ new Map();
this.typeToSelectString = "";
this.slotChangePending = false;
+ this.openTransition = { open: false };
this.displayLabel = "";
this.selectedOptions = [];
this.name = "";
@@ -308,7 +306,12 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
}
disconnectedCallback() {
super.disconnectedCallback();
- this.removeOpenListeners();
+ this.openTransition.controller?.abort();
+ this.openTransition = { open: false };
+ this.popup?.popup?.classList.remove("show", "hide");
+ if (this.listbox) this.listbox.hidden = true;
+ if (this.popup) this.popup.active = false;
+ unregisterDismissible(this);
this.cachedOptions = null;
}
updateDefaultValue() {
@@ -322,24 +325,6 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this._defaultValue = this.getAttribute("value") || null;
}
}
- addOpenListeners() {
- document.addEventListener("focusin", this.handleDocumentFocusIn);
- document.addEventListener("keydown", this.handleDocumentKeyDown);
- document.addEventListener("mousedown", this.handleDocumentMouseDown);
- registerDismissible(this);
- if (this.getRootNode() !== document) {
- this.getRootNode().addEventListener("focusin", this.handleDocumentFocusIn);
- }
- }
- removeOpenListeners() {
- document.removeEventListener("focusin", this.handleDocumentFocusIn);
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("mousedown", this.handleDocumentMouseDown);
- unregisterDismissible(this);
- if (this.getRootNode() !== document) {
- this.getRootNode().removeEventListener("focusin", this.handleDocumentFocusIn);
- }
- }
handleFocus() {
this.displayInput.setSelectionRange(0, 0);
}
@@ -604,6 +589,8 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
handleDisabledChange() {
if (this.disabled && this.open) {
this.open = false;
+ // The open watcher has already run for this update.
+ this.handleOpenChange();
}
}
handleValueChange() {
@@ -614,56 +601,72 @@ var WaSelect = class extends WebAwesomeFormAssociatedElement {
this.updateValidity();
}
async handleOpenChange() {
- if (this.open && !this.disabled) {
- this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption());
- const waShowEvent = new WaShowEvent();
- this.dispatchEvent(waShowEvent);
- if (waShowEvent.defaultPrevented) {
- this.open = false;
- return;
- }
- this.addOpenListeners();
+ const open = this.open && !this.disabled;
+ if (!this.isConnected || this.openTransition.open === open) return this.openAnimation;
+ if (open) this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption());
+ const event = open ? new WaShowEvent() : new WaHideEvent();
+ this.dispatchEvent(event);
+ if (event.defaultPrevented && !this.disabled) {
+ this.open = this.openTransition.open;
+ return this.openAnimation;
+ }
+ if (!this.isConnected || (this.open && !this.disabled) !== open) return this.openAnimation;
+ this.openTransition.controller?.abort();
+ this.openTransition = { open, controller: new AbortController() };
+ const { signal } = this.openTransition.controller;
+ const isCurrent = () => !signal.aborted && this.isConnected && (this.open && !this.disabled) === open;
+ if (open) {
+ document.addEventListener("focusin", this.handleDocumentFocusIn, { signal });
+ document.addEventListener("keydown", this.handleDocumentKeyDown, { signal });
+ document.addEventListener("mousedown", this.handleDocumentMouseDown, { signal });
+ registerDismissible(this);
+ const root = this.getRootNode();
+ if (root !== document) root.addEventListener("focusin", this.handleDocumentFocusIn, { signal });
this.listbox.hidden = false;
this.popup.active = true;
requestAnimationFrame(() => {
- this.setCurrentOption(this.currentOption);
+ if (isCurrent()) this.setCurrentOption(this.currentOption);
});
- await animateWithClass(this.popup.popup, "show");
- if (this.currentOption) {
+ } else {
+ unregisterDismissible(this);
+ }
+ // Retire the old animation before joining its cleanup; only the accepted
+ // transition may publish visibility, focus, or completion after an await.
+ this.popup.popup.classList.remove("show", "hide");
+ const previousAnimation = this.openAnimation;
+ this.openAnimation = (async () => {
+ await previousAnimation;
+ if (!isCurrent()) return;
+ await animateWithClass(this.popup.popup, open ? "show" : "hide");
+ if (!isCurrent()) return;
+ if (open && this.currentOption) {
scrollIntoView(this.currentOption, this.listbox, "vertical", "auto");
}
- this.dispatchEvent(new WaAfterShowEvent());
- } else {
- const waHideEvent = new WaHideEvent();
- this.dispatchEvent(waHideEvent);
- if (waHideEvent.defaultPrevented) {
- this.open = false;
- return;
+ if (!open) {
+ this.listbox.hidden = true;
+ this.popup.active = false;
}
- this.removeOpenListeners();
- await animateWithClass(this.popup.popup, "hide");
- this.listbox.hidden = true;
- this.popup.active = false;
- this.dispatchEvent(new WaAfterHideEvent());
- }
+ this.dispatchEvent(open ? new WaAfterShowEvent() : new WaAfterHideEvent());
+ })();
+ return this.openAnimation;
}
/** Shows the listbox. */
async show() {
- if (this.open || this.disabled) {
+ if (this.disabled) {
this.open = false;
- return void 0;
+ return;
}
+ if (!this.isConnected) return;
this.open = true;
- return waitForEvent(this, "wa-after-show");
+ await this.updateComplete;
+ return this.handleOpenChange();
}
/** Hides the listbox. */
async hide() {
- if (!this.open || this.disabled) {
- this.open = false;
- return void 0;
- }
this.open = false;
- return waitForEvent(this, "wa-after-hide");
+ if (!this.isConnected) return;
+ await this.updateComplete;
+ return this.openAnimation;
}
/** Sets focus on the control. */
focus(options) {
diff --git a/dist-cdn/chunks/chunk.L6CIKOFQ.js b/dist-cdn/chunks/chunk.L6CIKOFQ.js
index 20d309f89017ef7d1857f3b1c88216a88354fefd..ea0b8ee34255009d9d9e86605dbdc05969ea3840 100644
--- a/dist-cdn/chunks/chunk.L6CIKOFQ.js
+++ b/dist-cdn/chunks/chunk.L6CIKOFQ.js
@@ -5,32 +5,19 @@ async function animate(el, keyframes, options) {
return el.animate(keyframes, options).finished.catch(() => {
});
}
-function animateWithClass(el, className) {
- return new Promise((resolve) => {
- const controller = new AbortController();
- const { signal } = controller;
- if (el.classList.contains(className)) {
- return;
- }
- el.classList.add(className);
- let resolved = false;
- let onEnd = () => {
- if (resolved) {
- return;
- }
- resolved = true;
- el.classList.remove(className);
- resolve();
- controller.abort();
- };
- el.addEventListener("animationend", onEnd, { once: true, signal });
- el.addEventListener("animationcancel", onEnd, { once: true, signal });
- requestAnimationFrame(() => {
- if (!resolved && el.getAnimations().length === 0) {
- onEnd();
- }
- });
- });
+async function animateWithClass(el, className) {
+ // Observe pending class removal before reusing the same animation name;
+ // otherwise an immediate show/hide can reuse the completed CSSAnimation.
+ el.getAnimations();
+ el.classList.add(className);
+ // Popup activation is reactive: let its display styles render before sampling.
+ // Native completion also settles animations canceled before animationstart, unlike DOM events.
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
+ const animations = el.getAnimations().filter(
+ (animation) => animation instanceof CSSAnimation && animation.effect?.getComputedTiming().endTime !== Infinity
+ );
+ await Promise.allSettled(animations.map((animation) => animation.finished));
+ el.classList.remove(className);
}
function parseDuration(duration) {
duration = duration.toString().toLowerCase();
diff --git a/dist-cdn/chunks/chunk.OHGWARAS.js b/dist-cdn/chunks/chunk.OHGWARAS.js
index f0acaba806a2879c569a25877f8ef0dc94567791..c16274b21a03ff1a16525db107b491c76b74ba73 100644
--- a/dist-cdn/chunks/chunk.OHGWARAS.js
+++ b/dist-cdn/chunks/chunk.OHGWARAS.js
@@ -36,6 +36,7 @@ var WaDropdownItem = class extends WebAwesomeElement {
constructor() {
super(...arguments);
this.hasSlotController = new HasSlotController(this, "[default]", "start", "end");
+ this.submenuGeneration = 0;
this.active = false;
this.variant = "default";
this.size = "m";
@@ -47,7 +48,10 @@ var WaDropdownItem = class extends WebAwesomeElement {
this.submenuOpen = false;
this.hasSubmenu = false;
this.handleSlotChange = () => {
- this.hasSubmenu = this.hasSlotController.test("submenu");
+ const hasSubmenu = this.hasSlotController.test("submenu");
+ // Retire the branch before losing the old submenu state and popup.
+ if (!hasSubmenu) void this.closeSubmenu();
+ this.hasSubmenu = hasSubmenu;
this.updateHasSubmenuState();
if (this.hasSubmenu) {
this.setAttribute("aria-haspopup", "menu");
@@ -74,7 +78,6 @@ var WaDropdownItem = class extends WebAwesomeElement {
/** Handles pointer enter to open the submenu on hover */
this.handlePointerEnter = (event) => {
if (event.pointerType === "mouse" && this.hasSubmenu && !this.disabled) {
- this.notifyParentOfOpening();
this.submenuOpen = true;
}
};
@@ -91,7 +94,15 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
disconnectedCallback() {
super.disconnectedCallback();
- this.closeSubmenu();
+ this.submenuGeneration++;
+ void this.closeSubmenu();
+ const submenu = this.submenuElement;
+ if (submenu) {
+ submenu.classList.remove("show", "hide");
+ submenu.hidden = true;
+ submenu.removeAttribute("data-visible");
+ submenu.hidePopover?.();
+ }
this.removeEventListener?.("click", this.handleHostClick);
this.removeEventListener?.("pointerenter", this.handlePointerEnter);
this.shadowRoot?.removeEventListener?.("click", this.handleClick, { capture: true });
@@ -134,11 +145,8 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
if (changedProperties.has("submenuOpen")) {
this.customStates.set("submenu-open", this.submenuOpen);
- if (this.submenuOpen) {
- this.openSubmenu();
- } else {
- this.closeSubmenu();
- }
+ const previousAnimation = this.submenuAnimation;
+ this.submenuAnimation = this.updateSubmenuVisibility(previousAnimation);
}
}
/** Update the has-submenu custom state */
@@ -147,22 +155,51 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
/** Opens the submenu. */
async openSubmenu() {
- const submenu = this.submenuElement;
- if (!this.hasSubmenu || !submenu || !this.isConnected) return;
- this.notifyParentOfOpening();
- submenu.showPopover?.();
- submenu.hidden = false;
- submenu.setAttribute("data-visible", "");
+ if (!this.hasSubmenu || !this.submenuElement || !this.isConnected) return;
this.submenuOpen = true;
- this.setAttribute("aria-expanded", "true");
- await animateWithClass(submenu, "show");
- setTimeout(() => {
+ await this.updateComplete;
+ return this.submenuAnimation;
+ }
+ /** Reconciles property and public-method requests through one submenu lifecycle. */
+ async updateSubmenuVisibility(previousAnimation) {
+ const submenu = this.submenuElement;
+ if (!this.submenuOpen) this.dispatchEvent(new Event("submenu-closing"));
+ if (!this.hasSubmenu || !submenu || !this.isConnected) {
+ await previousAnimation;
+ return;
+ }
+ const open = this.submenuOpen;
+ const generation = ++this.submenuGeneration;
+ const isCurrent = () => generation === this.submenuGeneration && this.isConnected && this.submenuOpen === open;
+ // Retire the previous class before joining its cleanup. A stale close must never
+ // hide a reopened native popover, and repeated public requests share this promise.
+ submenu.classList.remove("show", "hide");
+ if (open) {
+ this.notifyParentOfOpening();
+ if (!isCurrent()) return;
+ submenu.showPopover?.();
+ submenu.hidden = false;
+ submenu.setAttribute("data-visible", "");
+ }
+ // Opening observers initialize embedded controls before the expanded state changes.
+ this.setAttribute("aria-expanded", String(open));
+ await previousAnimation;
+ await this.updateComplete;
+ if (!isCurrent()) return;
+ // Focus once when opening becomes usable, not again when the animation finishes:
+ // the operator may already have moved focus or returned to the parent by then.
+ if (open) {
const items = this.getSubmenuItems();
- if (items.length > 0) {
- items.forEach((item, index) => item.active = index === 0);
- items[0].focus({ preventScroll: true });
- }
- }, 0);
+ items.forEach((item, index) => item.active = index === 0);
+ items[0]?.focus({ preventScroll: true });
+ }
+ await animateWithClass(submenu, open ? "show" : "hide");
+ if (!isCurrent()) return;
+ if (!open) {
+ submenu.hidden = true;
+ submenu.removeAttribute("data-visible");
+ submenu.hidePopover?.();
+ }
}
/** Notifies the parent dropdown that this item is opening its submenu */
notifyParentOfOpening() {
@@ -172,30 +209,15 @@ var WaDropdownItem = class extends WebAwesomeElement {
detail: { item: this }
});
this.dispatchEvent(event);
- const parent = this.parentElement;
- if (parent) {
- const siblings = [...parent.children].filter(
- (el) => el !== this && el.localName === "wa-dropdown-item" && el.getAttribute("slot") === this.getAttribute("slot") && el.submenuOpen
- );
- siblings.forEach((sibling) => {
- sibling.submenuOpen = false;
- });
- }
}
/** Closes the submenu. */
async closeSubmenu() {
- const submenu = this.submenuElement;
- if (!this.hasSubmenu || !submenu) return;
+ // A never-connected item's first update cannot settle; closing it is a public no-op.
+ if (!this.hasSubmenu || !this.submenuElement) return;
this.submenuOpen = false;
- this.setAttribute("aria-expanded", "false");
- if (!submenu.hidden) {
- await animateWithClass(submenu, "hide");
- if (submenu?.isConnected) {
- submenu.hidden = true;
- submenu.removeAttribute("data-visible");
- submenu.hidePopover?.();
- }
- }
+ this.dispatchEvent(new Event("submenu-closing"));
+ await this.updateComplete;
+ return this.submenuAnimation;
}
/** Determines whether the item navigates when selected. Items with submenus never navigate. */
isLink() {
@@ -225,8 +247,9 @@ var WaDropdownItem = class extends WebAwesomeElement {
}
/** Gets all dropdown items in the submenu. */
getSubmenuItems() {
- return [...this.children].filter(
- (el) => el.localName === "wa-dropdown-item" && el.getAttribute("slot") === "submenu" && !el.hasAttribute("disabled")
+ const slot = this.submenuElement?.querySelector('slot[name="submenu"]');
+ return (slot?.assignedElements({ flatten: true }) ?? []).filter(
+ (el) => el.localName === "wa-dropdown-item" && !el.disabled
);
}
render() {
@@ -285,7 +308,7 @@ var WaDropdownItem = class extends WebAwesomeElement {
>
<slot name="submenu"></slot>
</div>
- ` : ""}
+ ` : html`<slot name="submenu" hidden></slot>`}
`;
}
};
diff --git a/dist-cdn/chunks/chunk.QYBSWZDP.js b/dist-cdn/chunks/chunk.QYBSWZDP.js
index 7d00635638361679a37070b02f3c2df9f6921cea..52b573eeac08eb9ada098fb43b9648c66eda936f 100644
--- a/dist-cdn/chunks/chunk.QYBSWZDP.js
+++ b/dist-cdn/chunks/chunk.QYBSWZDP.js
@@ -1643,10 +1643,17 @@ var WaPopup = class extends WebAwesomeElement {
if (!this.anchorEl || !this.active || !this.isConnected) {
return;
}
+ this.cleanup?.();
+ this.cleanup = void 0;
this.popup?.showPopover?.();
- this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
+ const cleanup = autoUpdate(this.anchorEl, this.popup, () => {
this.reposition();
});
+ if (!this.active || !this.isConnected) {
+ cleanup();
+ return;
+ }
+ this.cleanup = cleanup;
}
async stop() {
return new Promise((resolve) => {
diff --git a/dist-cdn/chunks/chunk.R3TB74QH.js b/dist-cdn/chunks/chunk.R3TB74QH.js
index f2c229192dc968570e8350f74778ffb0993c3b20..95a89e20fe546ac3ebcd39c4bd91bd088819820f 100644
--- a/dist-cdn/chunks/chunk.R3TB74QH.js
+++ b/dist-cdn/chunks/chunk.R3TB74QH.js
@@ -68,17 +68,16 @@ var openDropdowns = /* @__PURE__ */ new Set();
var WaDropdown = class extends WebAwesomeElement {
constructor() {
super(...arguments);
- this.submenuCleanups = /* @__PURE__ */ new Map();
this.localize = new LocalizeController(this);
this.userTypedQuery = "";
- this.openSubmenuStack = [];
+ this.activeSubmenus = [];
this.open = false;
this.size = "m";
this.placement = "bottom-start";
this.distance = 0;
this.skidding = 0;
/** Handles key down events when the menu is open */
- this.handleDocumentKeyDown = async (event) => {
+ this.handleDocumentKeyDown = (event) => {
const isRtl = this.localize.dir() === "rtl";
if (event.key === "Escape" && this.open && isTopDismissible(this)) {
const trigger = this.getTrigger();
@@ -88,22 +87,12 @@ var WaDropdown = class extends WebAwesomeElement {
trigger?.focus({ preventScroll: true });
return;
}
- const activeElement = [...activeElements()].find((el) => el.localName === "wa-dropdown-item");
- const isFocusedOnItem = activeElement?.localName === "wa-dropdown-item";
+ const focusedItem = [...activeElements()].find((el) => el.localName === "wa-dropdown-item");
const currentSubmenuItem = this.getCurrentSubmenuItem();
- const isInSubmenu = !!currentSubmenuItem;
- let items;
- let activeItem;
- let activeItemIndex;
- if (isInSubmenu) {
- items = this.getSubmenuItems(currentSubmenuItem);
- activeItem = items.find((item) => item.active || item === activeElement);
- activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
- } else {
- items = this.getItems();
- activeItem = items.find((item) => item.active || item === activeElement);
- activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
- }
+ const items = currentSubmenuItem ? this.getSubmenuItems(currentSubmenuItem) : this.getItems();
+ // A focused item outside this level (e.g. the owner of a hover-opened submenu) keeps its keys.
+ const activeItem = items.find((item) => item === focusedItem) ?? (focusedItem ? void 0 : items.find((item) => item.active));
+ const activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
let itemToSelect;
if (event.key === "ArrowUp") {
event.preventDefault();
@@ -123,39 +112,23 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect = items[0];
}
}
- if (event.key === (isRtl ? "ArrowLeft" : "ArrowRight") && isFocusedOnItem && activeItem) {
+ if (event.key === (isRtl ? "ArrowLeft" : "ArrowRight") && focusedItem && activeItem) {
if (activeItem.hasSubmenu) {
event.preventDefault();
event.stopPropagation();
activeItem.submenuOpen = true;
- this.addToSubmenuStack(activeItem);
- setTimeout(() => {
- const submenuItems = this.getSubmenuItems(activeItem);
- if (submenuItems.length > 0) {
- submenuItems.forEach((item, index) => item.active = index === 0);
- submenuItems[0].focus({ preventScroll: true });
- }
- }, 0);
return;
}
}
- if (event.key === (isRtl ? "ArrowRight" : "ArrowLeft") && isInSubmenu) {
+ if (event.key === (isRtl ? "ArrowRight" : "ArrowLeft") && currentSubmenuItem) {
event.preventDefault();
event.stopPropagation();
- const removedItem = this.removeFromSubmenuStack();
- if (removedItem) {
- removedItem.submenuOpen = false;
- setTimeout(() => {
- removedItem.focus({ preventScroll: true });
- removedItem.active = true;
- const parentItems = removedItem.slot === "submenu" ? this.getSubmenuItems(removedItem.parentElement) : this.getItems();
- parentItems.forEach((item) => {
- if (item !== removedItem) {
- item.active = false;
- }
- });
- }, 0);
- }
+ // Closing retires ownership synchronously; only this keypress restores focus.
+ void currentSubmenuItem.closeSubmenu();
+ currentSubmenuItem.focus({ preventScroll: true });
+ const parent = this.getCurrentSubmenuItem();
+ const parentItems = parent ? this.getSubmenuItems(parent, true) : this.getItems(true);
+ parentItems.forEach((item) => item.active = item === currentSubmenuItem);
return;
}
if (event.key === "Home" || event.key === "End") {
@@ -164,7 +137,7 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect = event.key === "Home" ? items[0] : items[items.length - 1];
}
if (event.key === "Tab") {
- await this.hideMenu();
+ this.open = false;
}
if (event.key.length === 1 && !(event.metaKey || event.ctrlKey || event.altKey) && !(event.key === " " && this.userTypedQuery === "")) {
clearTimeout(this.userTypedTimeout);
@@ -190,19 +163,11 @@ var WaDropdown = class extends WebAwesomeElement {
itemToSelect.scrollIntoView({ block: "nearest" });
return;
}
- if ((event.key === "Enter" || event.key === " " && this.userTypedQuery === "") && isFocusedOnItem && activeItem) {
+ if ((event.key === "Enter" || event.key === " " && this.userTypedQuery === "") && focusedItem && activeItem) {
event.preventDefault();
event.stopPropagation();
if (activeItem.hasSubmenu) {
activeItem.submenuOpen = true;
- this.addToSubmenuStack(activeItem);
- setTimeout(() => {
- const submenuItems = this.getSubmenuItems(activeItem);
- if (submenuItems.length > 0) {
- submenuItems.forEach((item, index) => item.active = index === 0);
- submenuItems[0].focus({ preventScroll: true });
- }
- }, 0);
} else {
this.makeSelection(activeItem, event);
}
@@ -223,7 +188,8 @@ var WaDropdown = class extends WebAwesomeElement {
};
/** Handle global mouse movement for safe triangle logic */
this.handleGlobalMouseMove = (event) => {
- const currentSubmenuItem = this.getCurrentSubmenuItem();
+ const branch = this.activeSubmenus.at(-1);
+ const currentSubmenuItem = branch?.item;
if (!currentSubmenuItem?.submenuOpen || !currentSubmenuItem.submenuElement) return;
const submenuRect = currentSubmenuItem.submenuElement.getBoundingClientRect();
const isRtl = this.localize.dir() === "rtl";
@@ -241,7 +207,7 @@ var WaDropdown = class extends WebAwesomeElement {
);
if (!isOverItem && !isOverSubmenu) {
setTimeout(() => {
- if (!submenuItemHovered && !submenuElementHovered) {
+ if (this.activeSubmenus.includes(branch) && !currentSubmenuItem.matches(":hover") && !currentSubmenuItem.submenuElement.matches(":hover")) {
currentSubmenuItem.submenuOpen = false;
}
}, 100);
@@ -251,15 +217,20 @@ var WaDropdown = class extends WebAwesomeElement {
handleSizeChange() {
warnDeprecatedSize(this.localName, this.size);
}
+ connectedCallback() {
+ super.connectedCallback();
+ if (this.hasUpdated && this.open) {
+ void this.updateMenuVisibility(true);
+ }
+ }
disconnectedCallback() {
super.disconnectedCallback();
+ this.menuTransition?.abort();
+ this.menu?.classList.remove("show", "hide");
+ if (this.popup) this.popup.active = false;
+ openDropdowns.delete(this);
clearInterval(this.userTypedTimeout);
this.closeAllSubmenus();
- this.submenuCleanups.forEach((cleanup) => cleanup());
- this.submenuCleanups.clear();
- document.removeEventListener("mousemove", this.handleGlobalMouseMove);
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("pointerdown", this.handleDocumentPointerDown);
unregisterDismissible(this);
}
firstUpdated(changedProperties) {
@@ -276,12 +247,7 @@ var WaDropdown = class extends WebAwesomeElement {
return;
}
this.customStates.set("open", this.open);
- if (this.open) {
- await this.showMenu();
- } else {
- this.closeAllSubmenus();
- await this.hideMenu();
- }
+ await this.updateMenuVisibility(this.open);
}
if (changedProperties.has("size")) {
this.syncItemSizes();
@@ -310,104 +276,96 @@ var WaDropdown = class extends WebAwesomeElement {
);
items.forEach((item) => item.size = this.size);
}
- /** Handles the submenu navigation stack */
- addToSubmenuStack(item) {
- const index = this.openSubmenuStack.indexOf(item);
- if (index !== -1) {
- this.openSubmenuStack = this.openSubmenuStack.slice(0, index + 1);
- } else {
- this.openSubmenuStack.push(item);
- }
- }
- /** Removes the last item from the submenu stack */
- removeFromSubmenuStack() {
- return this.openSubmenuStack.pop();
- }
- /** Gets the current active submenu item */
+ /** Gets the current active submenu item. */
getCurrentSubmenuItem() {
- return this.openSubmenuStack.length > 0 ? this.openSubmenuStack[this.openSubmenuStack.length - 1] : void 0;
+ return this.activeSubmenus.at(-1)?.item;
}
- /** Closes all submenus in the dropdown. */
- closeAllSubmenus() {
- const items = this.getItems(true);
- items.forEach((item) => {
- item.submenuOpen = false;
- });
- this.openSubmenuStack = [];
- }
- /** Closes sibling submenus at the same level as the specified item. */
- closeSiblingSubmenus(item) {
- const parentDropdownItem = item.closest('wa-dropdown-item:not([slot="submenu"])');
- let siblingItems;
- if (parentDropdownItem) {
- siblingItems = this.getSubmenuItems(parentDropdownItem, true);
- } else {
- siblingItems = this.getItems(true);
- }
- siblingItems.forEach((siblingItem) => {
- if (siblingItem !== item && siblingItem.submenuOpen) {
- siblingItem.submenuOpen = false;
- }
- });
- if (!this.openSubmenuStack.includes(item)) {
- this.openSubmenuStack.push(item);
+ /** Retires an ancestor branch before its hide animations can complete. */
+ closeAllSubmenus(from = 0) {
+ // Remove ownership first: item close notifications may reenter this owner.
+ for (const { item, cleanup } of this.activeSubmenus.splice(from).reverse()) {
+ cleanup();
+ void item.closeSubmenu();
}
}
/** Get the slotted trigger button, a <wa-button> or <button> element */
getTrigger() {
return this.querySelector('[slot="trigger"]');
}
- /** Shows the dropdown menu. This should only be called from within updated(). */
- async showMenu() {
- const anchor = this.getTrigger();
- if (!anchor || !this.popup || !this.menu) return;
- const showEvent = new WaShowEvent();
- this.dispatchEvent(showEvent);
- if (showEvent.defaultPrevented) {
- this.open = false;
+ /** Reconciles accepted visibility and its animation from updates or reconnection. */
+ async updateMenuVisibility(open) {
+ if (!this.isConnected || !this.popup || !this.menu || open && !this.getTrigger()) return;
+ const event = open ? new WaShowEvent() : new WaHideEvent({ source: this });
+ this.dispatchEvent(event);
+ if (event.defaultPrevented) {
+ this.open = !open;
return;
}
- if (this.popup.active) {
- return;
+ // Membership records accepted open ownership. The popup stays active while hiding,
+ // so its active flag cannot distinguish a canceled hide from a reopen.
+ if (!this.isConnected || this.open !== open || open && openDropdowns.has(this)) return;
+ // One cancellation owner releases document listeners and fences completion on
+ // replacement or disconnection; a veto above leaves that ownership untouched.
+ this.menuTransition?.abort();
+ this.menuTransition = new AbortController();
+ const { signal } = this.menuTransition;
+ const isCurrent = () => !signal.aborted && this.isConnected && this.open === open;
+ if (open) {
+ openDropdowns.forEach((dropdown) => dropdown.open = false);
+ this.popup.active = true;
+ openDropdowns.add(this);
+ registerDismissible(this);
+ document.addEventListener("keydown", this.handleDocumentKeyDown, { signal });
+ document.addEventListener("pointerdown", this.handleDocumentPointerDown, { signal });
+ document.addEventListener("mousemove", this.handleGlobalMouseMove, { signal });
+ } else {
+ this.closeAllSubmenus();
+ openDropdowns.delete(this);
+ unregisterDismissible(this);
}
- openDropdowns.forEach((dropdown) => dropdown.open = false);
- this.popup.active = true;
- this.open = true;
- openDropdowns.add(this);
- registerDismissible(this);
this.syncAriaAttributes();
- document.addEventListener("keydown", this.handleDocumentKeyDown);
- document.addEventListener("pointerdown", this.handleDocumentPointerDown);
- document.addEventListener("mousemove", this.handleGlobalMouseMove);
- this.menu.classList.remove("hide");
- await animateWithClass(this.menu, "show");
- const items = this.getItems();
- if (items.length > 0) {
- items.forEach((item, index) => item.active = index === 0);
- items[0].focus({ preventScroll: true });
+ // Join canceled animation cleanup before reusing its class. Show and hide share
+ // one CSS animation name, so overlapping helpers cannot own separate animations.
+ this.menu.classList.remove("show", "hide");
+ const focused = [...activeElements()].pop();
+ await this.menuAnimation;
+ if (!isCurrent()) return;
+ this.menuAnimation = animateWithClass(this.menu, open ? "show" : "hide");
+ if (open) {
+ await this.popup.updateComplete;
+ // Initial anchor slot changes can restart the native popover on the next frame.
+ // Focus after that activation settles, before waiting for the menu animation.
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
+ if (!isCurrent()) return;
+ // Input accepted during cleanup or popup rendering already owns the selection.
+ const submenu = this.getCurrentSubmenuItem();
+ const items = submenu ? this.getSubmenuItems(submenu, true) : this.getItems(true);
+ const content = this.defaultSlot.assignedElements({ flatten: true });
+ const ownsFocus = (element) => element instanceof HTMLElement &&
+ element.closest("wa-dropdown") === this && !element.matches(":disabled, [disabled]") &&
+ content.some((root) => root.contains(element));
+ const focusedItem = [...activeElements()].find(ownsFocus);
+ const autofocusItem = focusedItem ? void 0 : [...this.querySelectorAll("[autofocus]")].find(
+ (element) => ownsFocus(element) && element.getClientRects().length > 0
+ );
+ const initialItem = focusedItem ?? autofocusItem ?? items.find((item) => !item.disabled);
+ // Owned input is authoritative; newer outside focus also supersedes initialization.
+ if (initialItem && (focusedItem || [...activeElements()].pop() === focused)) {
+ items.forEach((item) => item.active = item === initialItem);
+ if (initialItem.localName === "wa-dropdown-item") initialItem.active = true;
+ if (!focusedItem) initialItem.focus({ preventScroll: true });
+ if (initialItem.autofocus) initialItem.scrollIntoView({ block: "nearest" });
+ }
+ if (!isCurrent()) return;
}
- this.dispatchEvent(new WaAfterShowEvent());
- }
- /** Hides the dropdown menu. This should only be called from within updated(). */
- async hideMenu() {
- if (!this.popup || !this.menu) return;
- const hideEvent = new WaHideEvent({ source: this });
- this.dispatchEvent(hideEvent);
- if (hideEvent.defaultPrevented) {
- this.open = true;
- return;
+ await this.menuAnimation;
+ if (!isCurrent()) return;
+ if (open) {
+ this.dispatchEvent(new WaAfterShowEvent());
+ } else {
+ this.popup.active = false;
+ this.dispatchEvent(new WaAfterHideEvent());
}
- this.open = false;
- openDropdowns.delete(this);
- unregisterDismissible(this);
- this.syncAriaAttributes();
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("pointerdown", this.handleDocumentPointerDown);
- document.removeEventListener("mousemove", this.handleGlobalMouseMove);
- this.menu.classList.remove("show");
- await animateWithClass(this.menu, "hide");
- this.popup.active = this.open;
- this.dispatchEvent(new WaAfterHideEvent());
}
/** Handles clicks on the menu. */
handleMenuClick(event) {
@@ -415,8 +373,6 @@ var WaDropdown = class extends WebAwesomeElement {
if (!item || item.disabled) return;
if (item.hasSubmenu) {
if (!item.submenuOpen) {
- this.closeSiblingSubmenus(item);
- this.addToSubmenuStack(item);
item.submenuOpen = true;
}
event.stopPropagation();
@@ -443,60 +399,57 @@ var WaDropdown = class extends WebAwesomeElement {
handleTriggerClick() {
this.open = !this.open;
}
- /** Handles submenu opening events */
+ /** Keeps one composed ancestor branch, including forwarded submenu slots. */
handleSubmenuOpening(event) {
- const openingItem = event.detail.item;
- this.closeSiblingSubmenus(openingItem);
- this.addToSubmenuStack(openingItem);
- this.setupSubmenuPosition(openingItem);
- this.processSubmenuItems(openingItem);
+ event.stopPropagation();
+ const path = event.composedPath();
+ const branch = path.slice(0, path.indexOf(this)).filter((el) => el instanceof HTMLElement && el.localName === "wa-dropdown-item").reverse();
+ // A child requested during ancestor teardown cannot own an orphan popover.
+ if (branch.some((item) => !item.submenuOpen)) {
+ void event.detail.item.closeSubmenu();
+ return;
+ }
+ let shared = 0;
+ while (shared < branch.length && this.activeSubmenus[shared]?.item === branch[shared]) shared++;
+ // Repeated ancestor notifications must not discard its still-open descendants.
+ if (shared === branch.length) return;
+ this.closeAllSubmenus(shared);
+ for (const item of branch.slice(shared)) {
+ this.activeSubmenus.push({ item, cleanup: this.setupSubmenuPosition(item) });
+ }
}
- /** Sets up submenu positioning with autoUpdate */
+ /** Binds positioning and close notifications to this exact open lifetime. */
setupSubmenuPosition(item) {
- if (!item.submenuElement) return;
- this.cleanupSubmenuPosition(item);
+ const controller = new AbortController();
+ const { signal } = controller;
const cleanup = autoUpdate(item, item.submenuElement, () => {
- this.positionSubmenu(item);
+ this.positionSubmenu(item, () => !signal.aborted);
this.updateSafeTriangleCoordinates(item);
});
- this.submenuCleanups.set(item, cleanup);
+ // Listen on the item itself: a disconnected item's event cannot bubble here.
+ item.addEventListener("submenu-closing", () => {
+ const index = this.activeSubmenus.findIndex((entry) => entry.item === item);
+ if (index !== -1) this.closeAllSubmenus(index);
+ }, { signal });
const submenuSlot = item.submenuElement.querySelector('slot[name="submenu"]');
- if (submenuSlot) {
- submenuSlot.removeEventListener("slotchange", WaDropdown.handleSubmenuSlotChange);
- submenuSlot.addEventListener("slotchange", WaDropdown.handleSubmenuSlotChange);
- WaDropdown.handleSubmenuSlotChange({ target: submenuSlot });
- }
- }
- static handleSubmenuSlotChange(event) {
- const slot = event.target;
- if (!slot) return;
- const items = slot.assignedElements().filter((el) => el.localName === "wa-dropdown-item");
- if (items.length === 0) return;
- const hasSubmenuItems = items.some((item) => item.hasSubmenu);
- const hasCheckboxItems = items.some((item) => item.type === "checkbox");
- items.forEach((item) => {
- item.submenuAdjacent = hasSubmenuItems;
- item.checkboxAdjacent = hasCheckboxItems;
- });
+ submenuSlot?.addEventListener("slotchange", () => this.processSubmenuItems(item), { signal });
+ this.processSubmenuItems(item);
+ return () => {
+ controller.abort();
+ cleanup();
+ };
}
processSubmenuItems(item) {
- if (!item.submenuElement) return;
- const submenuItems = this.getSubmenuItems(item, true);
- const hasSubmenuItems = submenuItems.some((subItem) => subItem.hasSubmenu);
- submenuItems.forEach((subItem) => {
- subItem.submenuAdjacent = hasSubmenuItems;
+ const items = this.getSubmenuItems(item, true);
+ const hasSubmenu = items.some((child) => child.hasSubmenu);
+ const hasCheckbox = items.some((child) => child.type === "checkbox");
+ items.forEach((child) => {
+ child.submenuAdjacent = hasSubmenu;
+ child.checkboxAdjacent = hasCheckbox;
});
}
- /** Cleans up submenu positioning */
- cleanupSubmenuPosition(item) {
- const cleanup = this.submenuCleanups.get(item);
- if (cleanup) {
- cleanup();
- this.submenuCleanups.delete(item);
- }
- }
/** Positions a submenu relative to its parent item */
- positionSubmenu(item) {
+ positionSubmenu(item, isCurrent) {
if (!item.submenuElement) return;
const isRtl = this.localize.dir() === "rtl";
const placement = isRtl ? "left-start" : "right-start";
@@ -516,6 +469,7 @@ var WaDropdown = class extends WebAwesomeElement {
})
]
}).then(({ x: x2, y, placement: placement2 }) => {
+ if (!isCurrent()) return;
item.submenuElement.setAttribute("data-placement", placement2);
Object.assign(item.submenuElement.style, {
left: `${x2}px`,
diff --git a/dist-cdn/chunks/chunk.RJFFANZD.js b/dist-cdn/chunks/chunk.RJFFANZD.js
index 09aca15ec05df1d0b6df0a9510fdc40fc2614879..2e066839b8831f772665f509eb3885e512f6d084 100644
--- a/dist-cdn/chunks/chunk.RJFFANZD.js
+++ b/dist-cdn/chunks/chunk.RJFFANZD.js
@@ -25,9 +25,6 @@ import {
import {
uniqueId
} from "./chunk.O6IZ4I7T.js";
-import {
- waitForEvent
-} from "./chunk.F25QOBDY.js";
import {
animateWithClass
} from "./chunk.L6CIKOFQ.js";
@@ -87,83 +84,45 @@ var WaTooltip = class extends WebAwesomeElement {
this.for = null;
this.anchor = null;
this.eventController = new AbortController();
- this.handleBlur = () => {
- this.dismissedByPress = false;
- if (this.hasTrigger("focus")) {
- this.hide();
- }
- };
- this.handleClick = () => {
- if (this.hasTrigger("click")) {
- if (this.open) {
- this.hide();
- } else {
- this.show();
+ this.openTransition = { open: false };
+ this.handleTrigger = (event) => {
+ const type = event.type;
+ const hover = type === "mouseover" || type === "mouseout";
+ let open = type === "focus" || type === "mouseover";
+ if (type === "click" || type === "mousedown") {
+ if (!this.hasTrigger("click")) {
+ if (!this.hasTrigger("manual")) this.lightDismiss();
+ return;
}
- return;
- }
- if (this.hasTrigger("manual")) {
- return;
- }
- this.lightDismiss();
- };
- this.handleFocus = () => {
- if (this.dismissedByPress) {
- return;
- }
- if (this.hasTrigger("focus")) {
- this.show();
+ if (type !== "click") return;
+ open = !this.open;
+ } else {
+ if (!open) {
+ const target = event.relatedTarget;
+ if (hover && target && ((this.anchor && containsComposedNode(this.anchor, target)) || containsComposedNode(this, target))) return;
+ this.dismissedByPress = false;
+ }
+ if (this.dismissedByPress || !this.hasTrigger(hover ? "hover" : "focus")) return;
}
- };
- this.handleMouseDown = () => {
- if (this.hasTrigger("click") || this.hasTrigger("manual")) {
- return;
+ const request = () => open ? this.show() : this.hide();
+ if (hover) {
+ clearTimeout(this.hoverTimeout);
+ this.hoverTimeout = window.setTimeout(request, open ? this.showDelay : this.hideDelay);
+ } else {
+ request();
}
- this.lightDismiss();
};
- this.handleDocumentKeyDown = (event) => {
- if (this.hasTrigger("manual")) {
- return;
- }
- if (event.key === "Escape" && this.open && isTopDismissible(this)) {
+ this.handleDocumentDismiss = (event) => {
+ if (this.hasTrigger("manual")) return;
+ if (event.type === "keydown") {
+ if (event.key !== "Escape" || !this.open || !isTopDismissible(this)) return;
event.preventDefault();
event.stopPropagation();
- this.hide();
- }
- };
- this.handleDocumentClick = (event) => {
- if (this.hasTrigger("manual")) {
- return;
- }
- if (this.anchor && event.composedPath().includes(this.anchor)) {
+ } else if (this.anchor && event.composedPath().includes(this.anchor)) {
return;
}
this.hide();
};
- this.handleMouseOver = () => {
- if (this.dismissedByPress) {
- return;
- }
- if (this.hasTrigger("hover")) {
- clearTimeout(this.hoverTimeout);
- this.hoverTimeout = window.setTimeout(() => this.show(), this.showDelay);
- }
- };
- this.handleMouseOut = (event) => {
- const relatedTarget = event.relatedTarget;
- const movedIntoAnchor = Boolean(relatedTarget && this.anchor && containsComposedNode(this.anchor, relatedTarget));
- const movedIntoTooltip = Boolean(relatedTarget && containsComposedNode(this, relatedTarget));
- if (movedIntoAnchor || movedIntoTooltip) {
- return;
- }
- this.dismissedByPress = false;
- if (this.hasTrigger("hover")) {
- clearTimeout(this.hoverTimeout);
- this.hoverTimeout = window.setTimeout(() => {
- this.hide();
- }, this.hideDelay);
- }
- };
}
connectedCallback() {
super.connectedCallback();
@@ -172,43 +131,33 @@ var WaTooltip = class extends WebAwesomeElement {
if (this.eventController.signal.aborted) {
this.eventController = new AbortController();
}
- this.addEventListener("mouseout", this.handleMouseOut);
+ this.addEventListener("mouseout", this.handleTrigger);
this.dismissedByPress = false;
- if (this.open) {
- this.open = false;
- this.updateComplete.then(() => {
- this.open = true;
- });
- }
+ // The owner reads current intent after rendering, including on reconnect.
+ void this.requestOpen(this.open);
if (!this.id) {
this.id = uniqueId("wa-tooltip-");
}
- if (this.for && this.anchor) {
+ if (this.for) {
this.anchor = null;
this.handleForChange();
- } else if (this.for) {
- this.handleForChange();
}
}
}
disconnectedCallback() {
super.disconnectedCallback();
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("click", this.handleDocumentClick);
unregisterDismissible(this);
- this.eventController.abort();
+ // Avoid retaining detached anchors in the default abort reason's stack.
+ this.eventController.abort(null);
+ clearTimeout(this.hoverTimeout);
+ this.openTransition = { open: false, promise: this.openTransition.promise };
+ this.popup?.popup?.classList.remove("show-with-scale", "hide-with-scale");
+ if (this.body) this.body.hidden = true;
+ if (this.popup) this.popup.active = false;
if (this.anchor) {
this.removeFromAriaLabelledBy(this.anchor, this.id);
}
}
- firstUpdated(changedProperties) {
- this.body.hidden = !this.open;
- if (this.open) {
- this.popup.active = true;
- this.popup.reposition();
- }
- super.firstUpdated(changedProperties);
- }
/** Hides the tooltip, or cancels a pending show, and keeps it hidden until re-armed. */
lightDismiss() {
clearTimeout(this.hoverTimeout);
@@ -240,41 +189,47 @@ var WaTooltip = class extends WebAwesomeElement {
}
}
async handleOpenChange() {
- if (this.open) {
- if (this.disabled) {
- return;
- }
- const waShowEvent = new WaShowEvent();
- this.dispatchEvent(waShowEvent);
- if (waShowEvent.defaultPrevented) {
- this.open = false;
- return;
- }
- if (!this.hasTrigger("manual")) {
- document.addEventListener("keydown", this.handleDocumentKeyDown, { signal: this.eventController.signal });
- document.addEventListener("click", this.handleDocumentClick, { signal: this.eventController.signal });
- registerDismissible(this);
- }
- this.body.hidden = false;
- this.popup.active = true;
- await animateWithClass(this.popup.popup, "show-with-scale");
- this.popup.reposition();
- this.dispatchEvent(new WaAfterShowEvent());
- } else {
- const waHideEvent = new WaHideEvent();
- this.dispatchEvent(waHideEvent);
- if (waHideEvent.defaultPrevented) {
- this.open = true;
- return;
+ if (this.disabled) this.open = false;
+ const previous = this.openTransition;
+ const open = this.open;
+ if (!this.isConnected || previous.open === open) return previous.promise;
+ const allowed = this.dispatchEvent(open ? new WaShowEvent() : new WaHideEvent());
+ if (this.openTransition === previous) {
+ if (!allowed && !this.disabled) {
+ this.open = previous.open;
+ } else if ((this.open && !this.disabled) === open) {
+ const transition = this.openTransition = { open };
+ // Disconnect invalidates the transition, so identity also fences connections.
+ const isCurrent = () => this.openTransition === transition && (this.open && !this.disabled) === open;
+ const dismissible = open && !this.hasTrigger("manual");
+ for (const type of ["keydown", "click"]) {
+ document[dismissible ? "addEventListener" : "removeEventListener"](type, this.handleDocumentDismiss, { signal: this.eventController.signal });
+ }
+ if (dismissible) registerDismissible(this);
+ else unregisterDismissible(this);
+ if (open) {
+ this.body.hidden = false;
+ this.popup.active = true;
+ }
+ // Join previous animation cleanup before reusing its classes.
+ this.popup.popup.classList.remove("show-with-scale", "hide-with-scale");
+ transition.promise = (async () => {
+ await previous.promise;
+ if (!isCurrent()) return;
+ await animateWithClass(this.popup.popup, open ? "show-with-scale" : "hide-with-scale");
+ if (!isCurrent()) return;
+ if (open) {
+ this.popup.reposition();
+ } else {
+ this.popup.active = false;
+ this.body.hidden = true;
+ }
+ // Reposition emits a synchronous public event that can revoke ownership.
+ if (isCurrent()) this.dispatchEvent(open ? new WaAfterShowEvent() : new WaAfterHideEvent());
+ })();
}
- document.removeEventListener("keydown", this.handleDocumentKeyDown);
- document.removeEventListener("click", this.handleDocumentClick);
- unregisterDismissible(this);
- await animateWithClass(this.popup.popup, "hide-with-scale");
- this.popup.active = false;
- this.body.hidden = true;
- this.dispatchEvent(new WaAfterHideEvent());
}
+ return this.openTransition.promise;
}
handleForChange() {
const rootNode = this.getRootNode?.();
@@ -288,23 +243,19 @@ var WaTooltip = class extends WebAwesomeElement {
}
this.dismissedByPress = false;
const { signal } = this.eventController;
- if (newAnchor) {
- this.addToAriaLabelledBy(newAnchor, this.id);
- newAnchor.addEventListener("blur", this.handleBlur, { capture: true, signal });
- newAnchor.addEventListener("focus", this.handleFocus, { capture: true, signal });
- newAnchor.addEventListener("click", this.handleClick, { signal });
- newAnchor.addEventListener("mousedown", this.handleMouseDown, { signal });
- newAnchor.addEventListener("mouseover", this.handleMouseOver, { signal });
- newAnchor.addEventListener("mouseout", this.handleMouseOut, { signal });
- }
- if (oldAnchor) {
- this.removeFromAriaLabelledBy(oldAnchor, this.id);
- oldAnchor.removeEventListener("blur", this.handleBlur, { capture: true });
- oldAnchor.removeEventListener("focus", this.handleFocus, { capture: true });
- oldAnchor.removeEventListener("click", this.handleClick);
- oldAnchor.removeEventListener("mousedown", this.handleMouseDown);
- oldAnchor.removeEventListener("mouseover", this.handleMouseOver);
- oldAnchor.removeEventListener("mouseout", this.handleMouseOut);
+ const events = [
+ ["blur", this.handleTrigger, true],
+ ["focus", this.handleTrigger, true],
+ ["click", this.handleTrigger],
+ ["mousedown", this.handleTrigger],
+ ["mouseover", this.handleTrigger],
+ ["mouseout", this.handleTrigger]
+ ];
+ if (newAnchor) this.addToAriaLabelledBy(newAnchor, this.id);
+ if (oldAnchor) this.removeFromAriaLabelledBy(oldAnchor, this.id);
+ for (const [type, listener, capture] of events) {
+ newAnchor?.addEventListener(type, listener, { capture, signal });
+ oldAnchor?.removeEventListener(type, listener, capture);
}
this.anchor = newAnchor;
}
@@ -314,26 +265,19 @@ var WaTooltip = class extends WebAwesomeElement {
this.popup.reposition();
}
}
- handleDisabledChange() {
- if (this.disabled && this.open) {
- this.hide();
- }
+ async requestOpen(open) {
+ if (!this.isConnected) return;
+ this.open = open && !this.disabled;
+ await this.updateComplete;
+ return this.handleOpenChange();
}
/** Shows the tooltip. */
- async show() {
- if (this.open) {
- return void 0;
- }
- this.open = true;
- return waitForEvent(this, "wa-after-show");
+ show() {
+ return this.requestOpen(true);
}
/** Hides the tooltip */
- async hide() {
- if (!this.open) {
- return void 0;
- }
- this.open = false;
- return waitForEvent(this, "wa-after-hide");
+ hide() {
+ return this.requestOpen(false);
}
render() {
return x`
@@ -356,7 +300,7 @@ var WaTooltip = class extends WebAwesomeElement {
hover-bridge
.anchor=${this.anchor}
>
- <div part="body" class="body">
+ <div part="body" class="body" hidden>
<slot></slot>
</div>
</wa-popup>
@@ -408,7 +352,7 @@ __decorateClass([
r()
], WaTooltip.prototype, "anchor", 2);
__decorateClass([
- watch("open", { waitUntilFirstUpdate: true })
+ watch(["open", "disabled"], { waitUntilFirstUpdate: true })
], WaTooltip.prototype, "handleOpenChange", 1);
__decorateClass([
watch("for")
@@ -416,9 +360,6 @@ __decorateClass([
__decorateClass([
watch(["distance", "placement", "skidding"])
], WaTooltip.prototype, "handleOptionsChange", 1);
-__decorateClass([
- watch("disabled")
-], WaTooltip.prototype, "handleDisabledChange", 1);
WaTooltip = __decorateClass([
t("wa-tooltip")
], WaTooltip);
diff --git a/dist-cdn/components/dropdown/dropdown.d.ts b/dist-cdn/components/dropdown/dropdown.d.ts
index 561ccfa258274c2c41cb728c0bfef26381c9f30c..7da1ccc5e1a55eeef5df886f612449832dc01157 100644
--- a/dist-cdn/components/dropdown/dropdown.d.ts
+++ b/dist-cdn/components/dropdown/dropdown.d.ts
@@ -29,11 +29,12 @@ import '../popup/popup.js';
*/
export default class WaDropdown extends WebAwesomeElement {
static css: import("lit").CSSResult[];
- private submenuCleanups;
private readonly localize;
private userTypedQuery;
private userTypedTimeout;
- private openSubmenuStack;
+ private activeSubmenus;
+ private menuTransition?;
+ private menuAnimation?;
defaultSlot: HTMLSlotElement;
private menu;
private popup;
@@ -51,6 +52,7 @@ export default class WaDropdown extends WebAwesomeElement {
distance: number;
/** The offset of the dropdown menu along its trigger. */
skidding: number;
+ connectedCallback(): void;
disconnectedCallback(): void;
firstUpdated(changedProperties: PropertyValues<typeof this>): void;
updated(changedProperties: PropertyValues<typeof this>): Promise<void>;
@@ -60,22 +62,14 @@ export default class WaDropdown extends WebAwesomeElement {
private getSubmenuItems;
/** Syncs item sizes with the dropdown's size property. */
private syncItemSizes;
- /** Handles the submenu navigation stack */
- private addToSubmenuStack;
- /** Removes the last item from the submenu stack */
- private removeFromSubmenuStack;
- /** Gets the current active submenu item */
+ /** Gets the current active submenu item. */
private getCurrentSubmenuItem;
- /** Closes all submenus in the dropdown. */
+ /** Retires an ancestor branch before its hide animations can complete. */
private closeAllSubmenus;
- /** Closes sibling submenus at the same level as the specified item. */
- private closeSiblingSubmenus;
/** Get the slotted trigger button, a <wa-button> or <button> element */
private getTrigger;
- /** Shows the dropdown menu. This should only be called from within updated(). */
- private showMenu;
- /** Hides the dropdown menu. This should only be called from within updated(). */
- private hideMenu;
+ /** Reconciles accepted visibility and its animation from updates or reconnection. */
+ private updateMenuVisibility;
/** Handles key down events when the menu is open */
private handleDocumentKeyDown;
/** Handles pointer down events when the dropdown is open. */
@@ -90,10 +84,7 @@ export default class WaDropdown extends WebAwesomeElement {
private handleSubmenuOpening;
/** Sets up submenu positioning with autoUpdate */
private setupSubmenuPosition;
- private static handleSubmenuSlotChange;
private processSubmenuItems;
- /** Cleans up submenu positioning */
- private cleanupSubmenuPosition;
/** Positions a submenu relative to its parent item */
private positionSubmenu;
/** Updates the safe triangle coordinates for a submenu */
diff --git a/dist-cdn/components/dropdown-item/dropdown-item.d.ts b/dist-cdn/components/dropdown-item/dropdown-item.d.ts
index 1e458bc4c742d94cd58d688acde8a45f9c681763..477eac752d3d2c0a81ee3a59a80c09b81d972888 100644
--- a/dist-cdn/components/dropdown-item/dropdown-item.d.ts
+++ b/dist-cdn/components/dropdown-item/dropdown-item.d.ts
@@ -35,6 +35,8 @@ import '../icon/icon.js';
export default class WaDropdownItem extends WebAwesomeElement {
static css: import("lit").CSSResult;
private readonly hasSlotController;
+ private submenuGeneration;
+ private submenuAnimation?;
submenuElement: HTMLDivElement;
private linkElement;
/** @internal The controller will set this property to true when the item is active. */
@@ -91,6 +93,8 @@ export default class WaDropdownItem extends WebAwesomeElement {
private updateHasSubmenuState;
/** Opens the submenu. */
openSubmenu(): Promise<void>;
+ /** Reconciles property and public-method requests through one submenu lifecycle. */
+ private updateSubmenuVisibility;
/** Notifies the parent dropdown that this item is opening its submenu */
private notifyParentOfOpening;
/** Closes the submenu. */
diff --git a/dist-cdn/components/select/select.d.ts b/dist-cdn/components/select/select.d.ts
index 9b4c748a83e5e990fd6eeb6367b37713b56773b2..f801fce20728a10630b6e47099a1054b464fb37c 100644
--- a/dist-cdn/components/select/select.d.ts
+++ b/dist-cdn/components/select/select.d.ts
@@ -66,6 +66,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
static get validators(): import("../../internal/webawesome-form-associated-element.js").Validator<WebAwesomeFormAssociatedElement>[];
assumeInteractionOn: string[];
private cachedOptions;
+ private openTransition;
+ private openAnimation;
private readonly hasSlotController;
private readonly localize;
private selectionOrder;
@@ -154,8 +156,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
connectedCallback(): void;
disconnectedCallback(): void;
private updateDefaultValue;
- private addOpenListeners;
- private removeOpenListeners;
private handleFocus;
private handleDocumentFocusIn;
private handleDocumentKeyDown;
diff --git a/dist-cdn/components/tooltip/tooltip.d.ts b/dist-cdn/components/tooltip/tooltip.d.ts
index dfbeaf70a47cf538c5c47e3efc338a74fde965d8..d2b2e05e8f364ed0834394bc0c8c77f132a03f81 100644
--- a/dist-cdn/components/tooltip/tooltip.d.ts
+++ b/dist-cdn/components/tooltip/tooltip.d.ts
@@ -1,4 +1,3 @@
-import { type PropertyValues } from 'lit';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import WaPopup from '../popup/popup.js';
/**
@@ -62,19 +61,14 @@ export default class WaTooltip extends WebAwesomeElement {
for: string | null;
anchor: null | Element;
private eventController;
+ private openTransition;
+ private requestOpen;
connectedCallback(): void;
disconnectedCallback(): void;
- firstUpdated(changedProperties: PropertyValues<typeof this>): void;
- private handleBlur;
- private handleClick;
- private handleFocus;
- private handleMouseDown;
+ private handleTrigger;
/** Hides the tooltip, or cancels a pending show, and keeps it hidden until re-armed. */
private lightDismiss;
- private handleDocumentKeyDown;
- private handleDocumentClick;
- private handleMouseOver;
- private handleMouseOut;
+ private handleDocumentDismiss;
private hasTrigger;
/** Adds the tooltip ID to the aria-labelledby attribute */
private addToAriaLabelledBy;
@@ -83,7 +77,6 @@ export default class WaTooltip extends WebAwesomeElement {
handleOpenChange(): Promise<void>;
handleForChange(): void;
handleOptionsChange(): Promise<void>;
- handleDisabledChange(): void;
/** Shows the tooltip. */
show(): Promise<void>;
/** Hides the tooltip */
diff --git a/dist-cdn/custom-elements.json b/dist-cdn/custom-elements.json
index 48c83fbc1a4ab54cd027784a9750b73d52bd81c9..26d881a6d608917a2c2d763238e93f2edc59b9db 100644
--- a/dist-cdn/custom-elements.json
+++ b/dist-cdn/custom-elements.json
@@ -18189,22 +18189,12 @@
},
{
"kind": "field",
- "name": "handleBlur",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleClick",
+ "name": "openTransition",
"privacy": "private"
},
{
"kind": "field",
- "name": "handleFocus",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseDown",
+ "name": "handleTrigger",
"privacy": "private"
},
{
@@ -18215,22 +18205,7 @@
},
{
"kind": "field",
- "name": "handleDocumentKeyDown",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleDocumentClick",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseOver",
- "privacy": "private"
- },
- {
- "kind": "field",
- "name": "handleMouseOut",
+ "name": "handleDocumentDismiss",
"privacy": "private"
},
{
@@ -18307,13 +18282,6 @@
"text": "handleOptionsChange() => void"
}
},
- {
- "kind": "method",
- "name": "handleDisabledChange",
- "type": {
- "text": "handleDisabledChange() => void"
- }
- },
{
"kind": "method",
"name": "show",
@@ -18399,6 +18367,11 @@
"name": "WebAwesomeElement",
"module": "internal/webawesome-element.js"
}
+ },
+ {
+ "kind": "method",
+ "name": "requestOpen",
+ "privacy": "private"
}
],
"events": [
@@ -27985,13 +27958,13 @@
"privacy": "private"
},
{
- "kind": "method",
- "name": "addOpenListeners",
+ "kind": "field",
+ "name": "openTransition",
"privacy": "private"
},
{
- "kind": "method",
- "name": "removeOpenListeners",
+ "kind": "field",
+ "name": "openAnimation",
"privacy": "private"
},
{
diff --git a/dist-cdn/internal/animate.d.ts b/dist-cdn/internal/animate.d.ts
index bedb1923179f2a73be2eaf24e8c1a3c35718683d..04dc1c15097ad58de31a8670e2c870d3a475b96f 100644
--- a/dist-cdn/internal/animate.d.ts
+++ b/dist-cdn/internal/animate.d.ts
@@ -1,9 +1,6 @@
/** Same as `el.animate()`, except returns a promise that doesn't throw an error when the animation is canceled. */
export declare function animate(el: Element, keyframes: Keyframe[], options?: KeyframeAnimationOptions): Promise<void | Animation>;
-/**
- * Applies a class to the specified element to animate it. The class is removed after the animation finishes and then
- * the promise resolves. If a timeout is provided, the class will be removed and the animation will
- */
+/** Applies an animation class and removes it after its finite CSS animations finish or are canceled. */
export declare function animateWithClass(el: Element, className: string): Promise<void>;
/** Parses a CSS duration and returns the number of milliseconds. */
export declare function parseDuration(duration: number | string): number;