mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* fix(test): retire shared read pools between files Retire the shared state-read worker singleton with its state database lifecycle after drain. Otherwise a new file can reuse a pool whose cleanup callback belongs to a retired lifecycle and leak a mocked fleet reply into real workspace snapshots. Extend the native ordered lifecycle regression with three reader generations. It reproduces the exact workspace snapshot failure before the mapping and passes afterward; 20 standalone qualification runs pass. Production behavior and persistent state are unchanged. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * refactor(update): simplify update execution and finalization Remove unused Git, in-place package, and deferred service-load paths. Keep the existing staged publication, activation, and post-core completion owners while preserving recovery and live-authority checks. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * refactor(update): simplify update execution and finalization Worked on by: - @steipete - @fuller-stack-dev Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> OpenClaw-Publication: 743c012b-39e8-4ed9-bac5-d815971d0dd3 * test(update): follow staged install and completion owners Repair the CI fixtures for parent-owned plugin completion and mandatory native package staging. Preserve authority refusal, legacy child publication, original Bun ownership, launcher relocation, and caller environment assertions. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(ui): publish chat rail position and keyboard entry together Route visibility observation through the existing layout commit so current position, retained virtual markers, and the roving Tab stop are published together. Preserve keyboard exploration inside the rail and existing native Tab assertions. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(ui): preserve dropdown focus while opening Initialize focus when the popup becomes usable, before animation completion can overwrite newer keyboard or consumer intent. Fence reentrant focus handlers with the existing transition owner and preserve both published distributions. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * refactor(update): simplify update execution and finalization Worked on by: - @steipete - @fuller-stack-dev Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> OpenClaw-Publication: ba5d7a68-6088-4a5a-904b-756271e7f6a3 * refactor(update): simplify update execution and finalization Worked on by: - @steipete - @fuller-stack-dev Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> OpenClaw-Publication: 768c6d92-2bf8-49dc-b4ea-3b297caae4c0 * fix(ui): wait for popup readiness before initial focus Observe the existing popup owner on fresh mounts before assigning initial focus. Preserve cancellation, listener cleanup, warm reopening, and the existing positioning initializer. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * refactor(update): simplify update execution and finalization Worked on by: - @steipete - @fuller-stack-dev Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> OpenClaw-Publication: 71ac1ff4-5100-45dc-a1c2-669513c2925f * chore(ci): refresh updater qualification after main repairs Refresh the unchanged reviewed updater tree after canonical main fixes7952e08424andf76d38a4aclanded. The historical Node fixture and unique worker inventory contracts are verified. This is the required fresh push after the known red-main repair, not a rerun to conceal unexplained failures. Prior staging and release-tooling timeout evidence remains in the PR. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(ui): keep agent-menu initial focus with its dropdown owner Use standard autofocus for the enabled selection and initialize focus once in the existing dropdown owner. Preserve later keyboard and outside focus, canceled transitions, and native autofocus roving state. Keep both published distributions aligned. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * refactor(update): simplify update execution and finalization Worked on by: - @steipete - @fuller-stack-dev Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> OpenClaw-Publication: fd1dd065-beee-49b7-ad32-3c67d84c0013 * fix(tooling): retain upstream Node compiler policy in staging fixtures Apply canonical main7205715961(#154509) byte-for-byte. The shared script shim preserves explicit compiler flags and the real staging fixture uses the existing Node shutdown policy. The prior hosted staging timeouts remain documented; this fixes the independently demonstrated policy gap without claiming that a passing replay establishes their cause. No timeout, retries, production defaults, or assertions are relaxed. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * fix(test): begin Discord starvation timing after audio consumption The continuous-start notification follows AudioPlayer.play(), while the SDK can still be buffering the resource. Signal fixture readiness from the first real non-silence source packet instead of starting the measurement before playback. The added readiness assertion fails with zero consumed packets on the original helper. Preserve the real worker/direct-port/encoder path, intentional main-thread starvation, packet and gap assertions, and the existing test timeout. Production voice behavior is unchanged. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> * chore: refresh CI after upstream database fixture drainage repair Canonical main now contains103115f2f0, which awaits database resource drainage before deleting the manual skill-library fixture. The failed merge-ref job used its parentfd578cb8a6and retained a reclamation lease after root deletion. Refresh the required synthetic merge-context checks after that confirmed main repair. No authored source or reviewed tree changes; retain tree 8e29f281f882e327641bd9c4fe343ec0e4ee9153 and the existing scoped proof. The separate sidebar reconnect failure remains under bounded investigation, not claimed fixed by a replay. Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> --------- Co-authored-by: steipete <58493+steipete@users.noreply.github.com> Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
3044 lines
123 KiB
Diff
3044 lines
123 KiB
Diff
diff --git a/dist/chunks/chunk.BCXHEFKE.js b/dist/chunks/chunk.BCXHEFKE.js
|
|
index 01d9b3366bda44776cc7db51481c9b81be72d228..bee944e7dcd41e0e658ce0e73f2bf4156ff4d920 100644
|
|
--- a/dist/chunks/chunk.BCXHEFKE.js
|
|
+++ b/dist/chunks/chunk.BCXHEFKE.js
|
|
@@ -25,9 +25,6 @@ import {
|
|
import {
|
|
uniqueId
|
|
} from "./chunk.KNJT7KBU.js";
|
|
-import {
|
|
- waitForEvent
|
|
-} from "./chunk.F25QOBDY.js";
|
|
import {
|
|
animateWithClass
|
|
} from "./chunk.L6CIKOFQ.js";
|
|
@@ -63,83 +60,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?.contains(target) || this.contains(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?.contains(relatedTarget));
|
|
- const movedIntoTooltip = Boolean(relatedTarget && this.contains(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();
|
|
@@ -148,43 +107,32 @@ 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();
|
|
+ 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);
|
|
@@ -216,41 +164,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?.();
|
|
@@ -264,23 +218,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;
|
|
}
|
|
@@ -290,26 +240,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`
|
|
@@ -332,7 +275,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>
|
|
@@ -384,7 +327,7 @@ __decorateClass([
|
|
state()
|
|
], WaTooltip.prototype, "anchor", 2);
|
|
__decorateClass([
|
|
- watch("open", { waitUntilFirstUpdate: true })
|
|
+ watch(["open", "disabled"], { waitUntilFirstUpdate: true })
|
|
], WaTooltip.prototype, "handleOpenChange", 1);
|
|
__decorateClass([
|
|
watch("for")
|
|
@@ -392,9 +335,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.CWBSN34U.js b/dist/chunks/chunk.CWBSN34U.js
|
|
index 5b1df53a771e31410852af7ea483db52edbc0140..dbb7dc5db6d83acaf9261846451ce86b9eb4cab6 100644
|
|
--- a/dist/chunks/chunk.CWBSN34U.js
|
|
+++ b/dist/chunks/chunk.CWBSN34U.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);
|
|
}
|
|
@@ -588,6 +573,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() {
|
|
@@ -598,56 +585,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.JZGJWX35.js b/dist/chunks/chunk.JZGJWX35.js
|
|
index 593bac0b7708fc0e908f9f8de650e45f8b3610ec..66e09365025b0e593e38d8410c8019d660081e16 100644
|
|
--- a/dist/chunks/chunk.JZGJWX35.js
|
|
+++ b/dist/chunks/chunk.JZGJWX35.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.KZJTFPDQ.js b/dist/chunks/chunk.KZJTFPDQ.js
|
|
index 11de1ac9d650dc1e2db6922f8fb6cd217bc13a4d..287223ce3a16e03424e85a330b985e899e3f31c3 100644
|
|
--- a/dist/chunks/chunk.KZJTFPDQ.js
|
|
+++ b/dist/chunks/chunk.KZJTFPDQ.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,11 @@ 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();
|
|
+ const activeItem = items.find((item) => item === focusedItem) ?? items.find((item) => item.active);
|
|
+ const activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
|
|
let itemToSelect;
|
|
if (event.key === "ArrowUp") {
|
|
event.preventDefault();
|
|
@@ -113,39 +101,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 +126,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 +152,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 +177,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 +196,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 +206,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 +236,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 +265,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 +362,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 +388,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 +458,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/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/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 4128d582ee76895db1e7a8b55349680d84bfa40f..70d0ee2585a901e6e75c8120b88d3dda369b1d92 100644
|
|
--- a/dist/custom-elements.json
|
|
+++ b/dist/custom-elements.json
|
|
@@ -17736,22 +17736,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"
|
|
},
|
|
{
|
|
@@ -17762,22 +17752,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"
|
|
},
|
|
{
|
|
@@ -17854,13 +17829,6 @@
|
|
"text": "handleOptionsChange() => void"
|
|
}
|
|
},
|
|
- {
|
|
- "kind": "method",
|
|
- "name": "handleDisabledChange",
|
|
- "type": {
|
|
- "text": "handleDisabledChange() => void"
|
|
- }
|
|
- },
|
|
{
|
|
"kind": "method",
|
|
"name": "show",
|
|
@@ -17946,6 +17914,11 @@
|
|
"name": "WebAwesomeElement",
|
|
"module": "internal/webawesome-element.js"
|
|
}
|
|
+ },
|
|
+ {
|
|
+ "kind": "method",
|
|
+ "name": "requestOpen",
|
|
+ "privacy": "private"
|
|
}
|
|
],
|
|
"events": [
|
|
@@ -27842,13 +27815,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.63RYV7SI.js b/dist-cdn/chunks/chunk.63RYV7SI.js
|
|
index 886b0e05a9d51bbe6636d1e394d49ef4edfb84fc..45902a28071c6a90dfeb29b8667f0ac3267100aa 100644
|
|
--- a/dist-cdn/chunks/chunk.63RYV7SI.js
|
|
+++ b/dist-cdn/chunks/chunk.63RYV7SI.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,11 @@ 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();
|
|
+ const activeItem = items.find((item) => item === focusedItem) ?? items.find((item) => item.active);
|
|
+ const activeItemIndex = activeItem ? items.indexOf(activeItem) : -1;
|
|
let itemToSelect;
|
|
if (event.key === "ArrowUp") {
|
|
event.preventDefault();
|
|
@@ -123,39 +111,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 +136,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 +162,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 +187,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 +206,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 +216,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 +246,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 +275,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 +372,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 +398,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 +468,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.A73F6EP3.js b/dist-cdn/chunks/chunk.A73F6EP3.js
|
|
index 598039ac639bfa2343778fbe65212a4ce2a54845..4c755be245c69d80d46e59b009aeb05738657020 100644
|
|
--- a/dist-cdn/chunks/chunk.A73F6EP3.js
|
|
+++ b/dist-cdn/chunks/chunk.A73F6EP3.js
|
|
@@ -25,9 +25,6 @@ import {
|
|
import {
|
|
uniqueId
|
|
} from "./chunk.O6IZ4I7T.js";
|
|
-import {
|
|
- waitForEvent
|
|
-} from "./chunk.F25QOBDY.js";
|
|
import {
|
|
animateWithClass
|
|
} from "./chunk.L6CIKOFQ.js";
|
|
@@ -70,83 +67,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?.contains(target) || this.contains(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?.contains(relatedTarget));
|
|
- const movedIntoTooltip = Boolean(relatedTarget && this.contains(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();
|
|
@@ -155,43 +114,32 @@ 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();
|
|
+ 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);
|
|
@@ -223,41 +171,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?.();
|
|
@@ -271,23 +225,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;
|
|
}
|
|
@@ -297,26 +247,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`
|
|
@@ -339,7 +282,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>
|
|
@@ -391,7 +334,7 @@ __decorateClass([
|
|
r()
|
|
], WaTooltip.prototype, "anchor", 2);
|
|
__decorateClass([
|
|
- watch("open", { waitUntilFirstUpdate: true })
|
|
+ watch(["open", "disabled"], { waitUntilFirstUpdate: true })
|
|
], WaTooltip.prototype, "handleOpenChange", 1);
|
|
__decorateClass([
|
|
watch("for")
|
|
@@ -399,9 +342,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/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.VVUBGO75.js b/dist-cdn/chunks/chunk.VVUBGO75.js
|
|
index 79243983dd4273713e7d1cc5d7e9a7b103cb787c..b7007d1129565982cac88ea9878d78e47072e340 100644
|
|
--- a/dist-cdn/chunks/chunk.VVUBGO75.js
|
|
+++ b/dist-cdn/chunks/chunk.VVUBGO75.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);
|
|
}
|
|
@@ -602,6 +587,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() {
|
|
@@ -612,56 +599,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.W2C46OQX.js b/dist-cdn/chunks/chunk.W2C46OQX.js
|
|
index da164574ce537b386894e751d6e9727cc9249d08..4625393889d6d1b92bddc212b8ba407843f70136 100644
|
|
--- a/dist-cdn/chunks/chunk.W2C46OQX.js
|
|
+++ b/dist-cdn/chunks/chunk.W2C46OQX.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/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 4128d582ee76895db1e7a8b55349680d84bfa40f..70d0ee2585a901e6e75c8120b88d3dda369b1d92 100644
|
|
--- a/dist-cdn/custom-elements.json
|
|
+++ b/dist-cdn/custom-elements.json
|
|
@@ -17736,22 +17736,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"
|
|
},
|
|
{
|
|
@@ -17762,22 +17752,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"
|
|
},
|
|
{
|
|
@@ -17854,13 +17829,6 @@
|
|
"text": "handleOptionsChange() => void"
|
|
}
|
|
},
|
|
- {
|
|
- "kind": "method",
|
|
- "name": "handleDisabledChange",
|
|
- "type": {
|
|
- "text": "handleDisabledChange() => void"
|
|
- }
|
|
- },
|
|
{
|
|
"kind": "method",
|
|
"name": "show",
|
|
@@ -17946,6 +17914,11 @@
|
|
"name": "WebAwesomeElement",
|
|
"module": "internal/webawesome-element.js"
|
|
}
|
|
+ },
|
|
+ {
|
|
+ "kind": "method",
|
|
+ "name": "requestOpen",
|
|
+ "privacy": "private"
|
|
}
|
|
],
|
|
"events": [
|
|
@@ -27842,13 +27815,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;
|