From 17d5c46e4de1c0a0f3ed8e37b41e896cd7fec7de Mon Sep 17 00:00:00 2001 From: LuLaValva Date: Wed, 12 Aug 2026 17:38:49 -0700 Subject: [PATCH] fix(evo-tooltip): open on click in browsers that do not focus buttons on click Safari and macOS Firefox do not give buttons focus on click, and the tooltip only opens on focusin, so a noHover tooltip with a button host could not be opened with a pointer. The host now focuses itself on click, composing with any consumer-provided onClick. Co-Authored-By: Claude Fable 5 --- .changeset/tall-tooltips-focus.md | 5 +++ .../src/tags/evo-tooltip/index.marko | 13 ++++++- .../test/fixtures/button-host.marko | 10 +++++ .../src/tags/evo-tooltip/test/test.browser.ts | 38 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 .changeset/tall-tooltips-focus.md create mode 100644 packages/evo-marko/src/tags/evo-tooltip/test/fixtures/button-host.marko create mode 100644 packages/evo-marko/src/tags/evo-tooltip/test/test.browser.ts 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"); + }); + }); +});