diff --git a/apps/docs/src/content/guides/changelog.mdx b/apps/docs/src/content/guides/changelog.mdx index 2dcc7ca..2e7c954 100644 --- a/apps/docs/src/content/guides/changelog.mdx +++ b/apps/docs/src/content/guides/changelog.mdx @@ -16,6 +16,8 @@ ships, these entries move under a versioned heading. stays interactive. New `driver.js/hints` entry, ~5KB gzip. [Guide](/docs/hints). ([#363](https://github.com/nilbuild/driver.js/issues/363)) - `--driver-hint-size` and `--driver-hint-color` CSS variables to style the beacons. +- Optional hint overlay (`overlay`, `overlayColor`, `overlayOpacity`) — dims the + page around the open hint's element, tour-style cutout included. - `sideEffects` field in package.json for better tree-shaking. ### Changed @@ -73,12 +75,10 @@ ships, these entries move under a versioned heading. ### Changed -- Breaking Popover footer buttons are now styled - through the `driver-popover-footer-btn` class instead of the - `.driver-popover-footer button` selector. Buttons you add through `onPopoverRender` - no longer inherit the default styling automatically. Add the - `driver-popover-footer-btn` class to opt in, or leave it off to apply your own - styles. The built-in next/previous buttons carry the class out of the box. +- Breaking Popover footer buttons are now styled through the + `driver-popover-footer-btn` class instead of the `.driver-popover-footer button` selector. Buttons you add through + `onPopoverRender` no longer inherit the default styling automatically. Add the `driver-popover-footer-btn` class to + opt in, or leave it off to apply your own styles. The built-in next/previous buttons carry the class out of the box. ([#555](https://github.com/nilbuild/driver.js/issues/555)) - Custom buttons added through `onPopoverRender` now receive their own click events. Only the built-in next, previous, and close buttons have their default diff --git a/apps/docs/src/content/guides/hints.mdx b/apps/docs/src/content/guides/hints.mdx index 0ec5210..11e8a35 100644 --- a/apps/docs/src/content/guides/hints.mdx +++ b/apps/docs/src/content/guides/hints.mdx @@ -126,9 +126,11 @@ Size and colour come from CSS variables: ## Dimming the page -Pass `overlay: true` to dim the page while a hint is open. The beacons and the -popover stay above the overlay, so other hints remain clickable, and clicking -the dimmed page closes the hint like any outside click: +Pass `overlay: true` to dim the page while a hint is open — the hint's element +is cut out of the dim, spotlighting it exactly like a tour step, and it stays +interactive inside the cutout. Everything else, including the other beacons, +sits under the overlay; clicking the dimmed page closes the hint like any +outside click: ```js const productHints = hints({ diff --git a/apps/docs/src/content/guides/styling-hints.mdx b/apps/docs/src/content/guides/styling-hints.mdx index 0d39767..3c45961 100644 --- a/apps/docs/src/content/guides/styling-hints.mdx +++ b/apps/docs/src/content/guides/styling-hints.mdx @@ -147,8 +147,9 @@ const productHints = hints({ ## Dimming the page With `overlay: true` the page dims while a hint is open — a focus mode for -hints. The beacons stay above the overlay, so you can hop from one hint to the -next; clicking the dimmed page closes the hint. +hints. The hint's element is cut out of the dim and stays interactive, exactly +like a tour step's highlight; the other beacons wait under the overlay. +Clicking the dimmed page closes the hint. { }); describe("overlay", () => { - const overlayEl = () => document.querySelector(".driver-hint-overlay"); + const overlayEl = () => document.querySelector(".driver-hint-overlay"); + const overlayPath = () => overlayEl()?.querySelector("path"); it("shows no overlay by default", () => { const productHints = createHints({ hints: SAMPLE_HINTS }); @@ -320,8 +321,42 @@ describe("overlay", () => { productHints.show(); productHints.open("intro"); - expect(overlayEl()?.style.backgroundColor).toBe("rgb(18, 52, 86)"); - expect(overlayEl()?.style.opacity).toBe("0.4"); + expect(["#123456", "rgb(18, 52, 86)"]).toContain(overlayPath()?.style.fill); + expect(overlayPath()?.style.opacity).toBe("0.4"); + }); + + it("cuts the hint's element out of the dim", () => { + const rect = (over: Partial): DOMRect => + ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON() {}, ...over }) as DOMRect; + const el = document.querySelector("#intro")!; + el.getBoundingClientRect = () => rect({ x: 400, y: 300, width: 200, height: 40 }); + + const productHints = createHints({ hints: SAMPLE_HINTS, overlay: true }); + productHints.show(); + productHints.open("intro"); + + // Full-screen subpath plus a cutout subpath. The cutout starts at the + // element's x minus the 10px padding, plus the 5px corner radius: 395. + const d = overlayPath()?.getAttribute("d") ?? ""; + expect(d).toContain(`M${window.innerWidth},0L0,0L0,${window.innerHeight}`); + expect(d).toContain("M395,290"); + }); + + it("tracks the element when the page scrolls", async () => { + const rect = (over: Partial): DOMRect => + ({ top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0, toJSON() {}, ...over }) as DOMRect; + const el = document.querySelector("#intro")!; + el.getBoundingClientRect = () => rect({ x: 400, y: 300, width: 200, height: 40 }); + + const productHints = createHints({ hints: SAMPLE_HINTS, overlay: true }); + productHints.show(); + productHints.open("intro"); + + el.getBoundingClientRect = () => rect({ x: 400, y: 100, width: 200, height: 40 }); + window.dispatchEvent(new Event("scroll")); + await nextFrame(); + + expect(overlayPath()?.getAttribute("d")).toContain("M395,90"); }); it("closes the hint when the dimmed page is clicked", () => { @@ -329,7 +364,8 @@ describe("overlay", () => { productHints.show(); productHints.open("intro"); - overlayEl()!.click(); + // Clicks land on the dimmed path (the svg lets pointer events through). + overlayPath()!.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(popoverEl()).toBeNull(); expect(overlayEl()).toBeNull();