{
let anim = findAnimation(targetEl, type, transitionPropOrAnimationName);
@@ -663,17 +636,13 @@ export async function whenTransitionOrAnimationDone(
}
if (!anim) {
- return triggerFallbackStartEnd(onStart, onEnd);
+ return;
}
- onStart?.();
-
try {
await anim.finished;
} catch {
// swallow error if canceled
- } finally {
- onEnd?.();
}
}
diff --git a/packages/calcite-components/src/utils/openCloseComponent.browser.spec.tsx b/packages/calcite-components/src/utils/openCloseComponent.browser.spec.tsx
new file mode 100644
index 00000000000..6fc36c39dcb
--- /dev/null
+++ b/packages/calcite-components/src/utils/openCloseComponent.browser.spec.tsx
@@ -0,0 +1,94 @@
+import { describe, expect, it, vi } from "vitest";
+import { JsxNode, LitElement } from "@arcgis/lumina";
+import { mount } from "@arcgis/lumina-compiler/testing";
+import { waitForAnimationFrame } from "../tests/utils/timing";
+import { createControlledPromise } from "../tests/utils/promises";
+import { onToggleOpenCloseComponent } from "./openCloseComponent";
+
+describe("openCloseComponent", () => {
+ describe("toggleOpenCloseComponent", () => {
+ it("emits beforeOpen/beforeClose events when the transition starts and open/close events when the transition is done", async () => {
+ const emittedEvents: string[] = [];
+
+ class Test extends LitElement {
+ open = false;
+
+ transitionEl!: HTMLDivElement;
+ openProp = "open";
+ transitionProp = "opacity" as const;
+
+ onBeforeOpen(): void {
+ emittedEvents.push("beforeOpen");
+ }
+
+ onOpen(): void {
+ emittedEvents.push("open");
+ }
+
+ onBeforeClose(): void {
+ emittedEvents.push("beforeClose");
+ }
+
+ onClose(): void {
+ emittedEvents.push("close");
+ }
+
+ override render(): JsxNode {
+ return (
+ {
+ if (!el) {
+ return;
+ }
+ this.transitionEl = el;
+ }}
+ />
+ );
+ }
+ }
+
+ const { component } = await mount(Test);
+
+ expect(emittedEvents).toEqual([]);
+
+ const openingControlledPromise = createControlledPromise();
+
+ const getAnimationsSpy = vi.spyOn(component.transitionEl, "getAnimations");
+
+ getAnimationsSpy.mockImplementation(() => [
+ {
+ transitionProperty: "opacity",
+ finished: openingControlledPromise.promise,
+ } as unknown as CSSTransition,
+ ]);
+
+ component.open = true;
+ onToggleOpenCloseComponent(component);
+ await waitForAnimationFrame();
+ expect(emittedEvents).toEqual(["beforeOpen"]);
+
+ openingControlledPromise.resolve();
+ await waitForAnimationFrame();
+ expect(emittedEvents).toEqual(["beforeOpen", "open"]);
+
+ const closingControlledPromise = createControlledPromise();
+ getAnimationsSpy.mockImplementation(() => [
+ {
+ transitionProperty: "opacity",
+ finished: closingControlledPromise.promise,
+ } as unknown as CSSTransition,
+ ]);
+
+ component.open = false;
+ onToggleOpenCloseComponent(component);
+ await waitForAnimationFrame();
+
+ expect(emittedEvents).toEqual(["beforeOpen", "open", "beforeClose"]);
+
+ closingControlledPromise.resolve();
+ await waitForAnimationFrame();
+
+ expect(emittedEvents).toEqual(["beforeOpen", "open", "beforeClose", "close"]);
+ });
+ });
+});
diff --git a/packages/calcite-components/src/utils/openCloseComponent.spec.ts b/packages/calcite-components/src/utils/openCloseComponent.spec.ts
deleted file mode 100644
index 29b8286d537..00000000000
--- a/packages/calcite-components/src/utils/openCloseComponent.spec.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { waitForAnimationFrame } from "../tests/utils/timing";
-import { createControlledPromise } from "../tests/utils/promises";
-import * as openCloseComponent from "./openCloseComponent";
-
-const { onToggleOpenCloseComponent } = openCloseComponent;
-
-describe("openCloseComponent", () => {
- describe("toggleOpenCloseComponent", () => {
- beforeEach(() => {
- vi.spyOn(global, "requestAnimationFrame").mockImplementation((cb) => {
- cb(0);
- return 0;
- });
- });
-
- afterEach(() => {
- vi.resetAllMocks();
- });
-
- it("emits beforeOpen/beforeClose events when the transition starts and open/close events when the transition is done", async () => {
- const transitionEl = window.document.createElement("div");
- const emittedEvents: string[] = [];
- const fakeOpenCloseComponent = {
- el: document.createElement("div"),
- open: true,
- transitionProp: "opacity" as const,
- openTransitionProp: "open",
- transitionEl,
- onBeforeOpen: vi.fn(() => emittedEvents.push("beforeOpen")),
- onOpen: vi.fn(() => emittedEvents.push("open")),
- onBeforeClose: vi.fn(() => emittedEvents.push("beforeClose")),
- onClose: vi.fn(() => emittedEvents.push("close")),
- };
-
- const openingControlledPromise = createControlledPromise();
-
- fakeOpenCloseComponent.transitionEl.getAnimations = () => [
- {
- transitionProperty: "opacity",
- finished: openingControlledPromise.promise,
- } as unknown as CSSTransition,
- ];
-
- onToggleOpenCloseComponent(fakeOpenCloseComponent);
- expect(emittedEvents).toEqual(["beforeOpen"]);
-
- openingControlledPromise.resolve();
- await waitForAnimationFrame();
- expect(emittedEvents).toEqual(["beforeOpen", "open"]);
-
- const closingControlledPromise = createControlledPromise();
- fakeOpenCloseComponent.transitionEl.getAnimations = () => [
- {
- transitionProperty: "opacity",
- finished: closingControlledPromise.promise,
- } as unknown as CSSTransition,
- ];
-
- fakeOpenCloseComponent.open = false;
- onToggleOpenCloseComponent(fakeOpenCloseComponent);
-
- expect(emittedEvents).toEqual(["beforeOpen", "open", "beforeClose"]);
-
- closingControlledPromise.resolve();
- await waitForAnimationFrame();
-
- expect(emittedEvents).toEqual(["beforeOpen", "open", "beforeClose", "close"]);
- });
- });
-});
diff --git a/packages/calcite-components/src/utils/openCloseComponent.ts b/packages/calcite-components/src/utils/openCloseComponent.ts
index f92daa0f1ea..5c6f48927a9 100644
--- a/packages/calcite-components/src/utils/openCloseComponent.ts
+++ b/packages/calcite-components/src/utils/openCloseComponent.ts
@@ -1,15 +1,13 @@
// @ts-strict-ignore
import { KebabCase } from "type-fest";
+import { LitElement } from "@arcgis/lumina";
import { whenTransitionDone } from "./dom";
/**
* Defines interface for components with open/close public emitter.
* All implementations of this interface must handle the following events: `beforeOpen`, `open`, `beforeClose`, `close`.
*/
-export interface OpenCloseComponent {
- /** The host element. */
- readonly el: HTMLElement;
-
+export interface OpenCloseComponent extends LitElement {
/**
* Specifies property on which active transition is watched for.
*
@@ -56,29 +54,21 @@ function isOpen(component: OpenCloseComponent): boolean {
* }
* @param component - OpenCloseComponent uses `open` prop to emit (before)open/close.
*/
-export function onToggleOpenCloseComponent(component: OpenCloseComponent): void {
- requestAnimationFrame((): void => {
- if (!component.transitionEl) {
- return;
- }
+export async function onToggleOpenCloseComponent(component: OpenCloseComponent): Promise {
+ if (isOpen(component)) {
+ component.onBeforeOpen();
+ } else {
+ component.onBeforeClose();
+ }
+
+ await component.updateComplete;
+ if (component.transitionEl) {
+ await whenTransitionDone(component.transitionEl, component.transitionProp);
+ }
- whenTransitionDone(
- component.transitionEl,
- component.transitionProp,
- () => {
- if (isOpen(component)) {
- component.onBeforeOpen();
- } else {
- component.onBeforeClose();
- }
- },
- () => {
- if (isOpen(component)) {
- component.onOpen();
- } else {
- component.onClose();
- }
- },
- );
- });
+ if (isOpen(component)) {
+ component.onOpen();
+ } else {
+ component.onClose();
+ }
}