diff --git a/.changeset/tall-tooltips-focus.md b/.changeset/tall-tooltips-focus.md new file mode 100644 index 00000000000..0cc8260ce56 --- /dev/null +++ b/.changeset/tall-tooltips-focus.md @@ -0,0 +1,5 @@ +--- +"@evo-web/marko": patch +--- + +Fix `evo-tooltip` not opening on click in Safari and macOS Firefox. The tooltip opens on `focusin`, but those browsers do not give buttons focus on click, so a `noHover` tooltip with a button host could only be opened with the keyboard. The host now focuses itself on click, composing with any consumer-provided `onClick`. diff --git a/packages/evo-marko/src/tags/evo-tooltip/index.marko b/packages/evo-marko/src/tags/evo-tooltip/index.marko index 3c2362a1c15..ddcbdf5962d 100644 --- a/packages/evo-marko/src/tags/evo-tooltip/index.marko +++ b/packages/evo-marko/src/tags/evo-tooltip/index.marko @@ -93,12 +93,21 @@ export interface Input extends Marko.HTML.Span { open = el.contains(e.relatedTarget as Node | null); onFocusOut && onFocusOut(e, el); }> - + <${hostAs} ...htmlHost id=hostId class=["tooltip__host", hostClass] - aria-expanded=expander.ariaExpanded/> + aria-expanded=expander.ariaExpanded + onClick(e: PointerEvent, el: HTMLElement) { + el.focus(); + hostOnClick && hostOnClick(e, el); + }/> + <@host as="button" onClick=input.onHostClick> + Settings + + Tooltip content + diff --git a/packages/evo-marko/src/tags/evo-tooltip/test/test.browser.ts b/packages/evo-marko/src/tags/evo-tooltip/test/test.browser.ts new file mode 100644 index 00000000000..ba0f6fdae08 --- /dev/null +++ b/packages/evo-marko/src/tags/evo-tooltip/test/test.browser.ts @@ -0,0 +1,38 @@ +import { afterEach, describe, it, expect, vi } from "vitest"; +import { render, cleanup } from "@marko/testing-library"; +import ButtonHost from "./fixtures/button-host.marko"; + +afterEach(cleanup); + +const settle = () => new Promise((resolve) => setTimeout(resolve, 50)); + +describe("evo-tooltip", () => { + describe("given a noHover tooltip with a button host", () => { + it("opens on click even when the browser does not focus on click", async () => { + const onHostClick = vi.fn(); + const { container } = await render(ButtonHost, { onHostClick }); + const host = + container.querySelector(".tooltip__host")!; + expect(host.getAttribute("aria-expanded")).toBe("false"); + + host.click(); + await settle(); + expect(document.activeElement).toBe(host); + expect(host.getAttribute("aria-expanded")).toBe("true"); + expect(onHostClick).toHaveBeenCalledTimes(1); + }); + + it("closes when focus leaves the host", async () => { + const { container } = await render(ButtonHost, {}); + const host = + container.querySelector(".tooltip__host")!; + host.click(); + await settle(); + expect(host.getAttribute("aria-expanded")).toBe("true"); + + host.blur(); + await settle(); + expect(host.getAttribute("aria-expanded")).toBe("false"); + }); + }); +});