mirror of
https://github.com/kamranahmedse/driver.js.git
synced 2026-08-31 01:12:43 +08:00
@@ -18,6 +18,8 @@ heading.
|
||||
- `allowScroll` config to lock body scroll while a tour is active.
|
||||
- `onDoneClick` hook, fired when the done button on the final step is clicked.
|
||||
([#500](https://github.com/nilbuild/driver.js/issues/500))
|
||||
- `data` property on a step for passing arbitrary data, accessible from hooks
|
||||
for custom per-step logic. ([#539](https://github.com/nilbuild/driver.js/issues/539))
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -34,14 +36,18 @@ heading.
|
||||
- The popover exposes `driver-popover-side-*` and `driver-popover-align-*`
|
||||
classes so you can target its position from CSS, and the arrow positioning was
|
||||
rewritten for more accurate placement.
|
||||
- Dropped the CSS `:has()` selector for broader browser compatibility.
|
||||
([#586](https://github.com/nilbuild/driver.js/issues/586))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Document event listener leak in `onDriverClick` that left handlers attached
|
||||
after the tour was destroyed.
|
||||
([#452](https://github.com/nilbuild/driver.js/issues/452))
|
||||
- Dropped the CSS `:has()` selector for broader browser compatibility.
|
||||
([#586](https://github.com/nilbuild/driver.js/issues/586))
|
||||
- Positioned highlighted elements and their children (e.g. dropdown menus) no
|
||||
longer get clipped.
|
||||
([#454](https://github.com/nilbuild/driver.js/issues/454),
|
||||
[#563](https://github.com/nilbuild/driver.js/issues/563))
|
||||
|
||||
## 1.5.0
|
||||
|
||||
|
||||
+2
-2
@@ -139,8 +139,8 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Disable the scrolling of parent element if it has an active element*/
|
||||
.driver-active-element-parent {
|
||||
/* Disable the scrolling of a scrollable parent that holds an active element*/
|
||||
.driver-active-element-parent-no-scroll {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -3,7 +3,7 @@ import { refreshOverlay, trackActiveElement, transitionStage } from "./overlay";
|
||||
import { getConfig, getCurrentDriver } from "./config";
|
||||
import { hidePopover, renderPopover } from "./popover";
|
||||
import { repositionPopover } from "./position";
|
||||
import { bringInView } from "./utils";
|
||||
import { bringInView, isScrollable } from "./utils";
|
||||
import { getState, setState } from "./state";
|
||||
|
||||
function mountDummyElement(): Element {
|
||||
@@ -157,7 +157,7 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {
|
||||
}
|
||||
|
||||
document.querySelectorAll(".driver-active-element-parent").forEach(element => {
|
||||
element.classList.remove("driver-active-element-parent");
|
||||
element.classList.remove("driver-active-element-parent", "driver-active-element-parent-no-scroll");
|
||||
});
|
||||
|
||||
fromElement.classList.remove("driver-active-element", "driver-no-interaction");
|
||||
@@ -173,6 +173,10 @@ function transferHighlight(toElement: Element, toStep: DriveStep) {
|
||||
const toParent = toElement.parentElement;
|
||||
if (toParent && toParent !== document.body) {
|
||||
toParent.classList.add("driver-active-element-parent");
|
||||
|
||||
if (isScrollable(toParent)) {
|
||||
toParent.classList.add("driver-active-element-parent-no-scroll");
|
||||
}
|
||||
}
|
||||
|
||||
toElement.classList.add("driver-active-element");
|
||||
@@ -186,7 +190,7 @@ export function destroyHighlight() {
|
||||
document.querySelectorAll(".driver-active-element").forEach(element => {
|
||||
const parent = element.parentElement;
|
||||
if (parent && parent !== document.body) {
|
||||
parent.classList.remove("driver-active-element-parent");
|
||||
parent.classList.remove("driver-active-element-parent", "driver-active-element-parent-no-scroll");
|
||||
}
|
||||
|
||||
element.classList.remove("driver-active-element", "driver-no-interaction");
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { getConfig } from "./config";
|
||||
|
||||
export function isScrollable(element: Element) {
|
||||
const style = window.getComputedStyle(element);
|
||||
return [style.overflow, style.overflowX, style.overflowY].some(value => {
|
||||
return value === "auto" || value === "scroll";
|
||||
});
|
||||
}
|
||||
|
||||
export function easeInOutQuad(elapsed: number, initialValue: number, amountOfChange: number, duration: number): number {
|
||||
if ((elapsed /= duration / 2) < 1) {
|
||||
return (amountOfChange / 2) * elapsed * elapsed + initialValue;
|
||||
|
||||
+48
-5
@@ -7,14 +7,16 @@ const NO_INTERACTION_CLASS = "driver-no-interaction";
|
||||
|
||||
const PARENT_CLASS = "driver-active-element-parent";
|
||||
|
||||
const NO_SCROLL_CLASS = "driver-active-element-parent-no-scroll";
|
||||
|
||||
const NESTED_HTML = `
|
||||
<div id="container-a"><button id="child-a" type="button">A</button></div>
|
||||
<div id="container-b"><button id="child-b" type="button">B</button></div>
|
||||
<button id="top-level" type="button">Top</button>
|
||||
`;
|
||||
|
||||
describe("active element parent", () => {
|
||||
it("locks scrolling on the highlighted element's parent", () => {
|
||||
describe("active element parent marker", () => {
|
||||
it("marks the highlighted element's parent regardless of scrollability", () => {
|
||||
document.body.innerHTML = NESTED_HTML;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#child-a" }] });
|
||||
d.drive();
|
||||
@@ -22,7 +24,7 @@ describe("active element parent", () => {
|
||||
expect(document.getElementById("container-a")?.classList.contains(PARENT_CLASS)).toBe(true);
|
||||
});
|
||||
|
||||
it("moves the lock to the new parent when highlighting a different branch", () => {
|
||||
it("moves the marker to the new parent when highlighting a different branch", () => {
|
||||
document.body.innerHTML = NESTED_HTML;
|
||||
const d = createDriver({
|
||||
animate: false,
|
||||
@@ -35,7 +37,7 @@ describe("active element parent", () => {
|
||||
expect(document.getElementById("container-b")?.classList.contains(PARENT_CLASS)).toBe(true);
|
||||
});
|
||||
|
||||
it("never locks the body element", () => {
|
||||
it("never marks the body element", () => {
|
||||
document.body.innerHTML = NESTED_HTML;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#top-level" }] });
|
||||
d.drive();
|
||||
@@ -43,7 +45,7 @@ describe("active element parent", () => {
|
||||
expect(document.body.classList.contains(PARENT_CLASS)).toBe(false);
|
||||
});
|
||||
|
||||
it("releases the lock when the tour is destroyed", () => {
|
||||
it("releases the marker when the tour is destroyed", () => {
|
||||
document.body.innerHTML = NESTED_HTML;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#child-a" }] });
|
||||
d.drive();
|
||||
@@ -53,6 +55,47 @@ describe("active element parent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("active element parent scroll lock", () => {
|
||||
it("does not lock a non-scrollable parent so positioned children are not clipped", () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="dropdown" style="position: relative">
|
||||
<button id="dropdown-toggle" type="button">Toggle</button>
|
||||
</div>
|
||||
`;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#dropdown-toggle" }] });
|
||||
d.drive();
|
||||
|
||||
const dropdown = document.getElementById("dropdown");
|
||||
expect(dropdown?.classList.contains(PARENT_CLASS)).toBe(true);
|
||||
expect(dropdown?.classList.contains(NO_SCROLL_CLASS)).toBe(false);
|
||||
});
|
||||
|
||||
it("locks a genuinely scrollable parent", () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="scroll-area" style="overflow: auto">
|
||||
<button id="scroll-child" type="button">Child</button>
|
||||
</div>
|
||||
`;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#scroll-child" }] });
|
||||
d.drive();
|
||||
|
||||
expect(document.getElementById("scroll-area")?.classList.contains(NO_SCROLL_CLASS)).toBe(true);
|
||||
});
|
||||
|
||||
it("releases the scroll lock when the tour is destroyed", () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="scroll-area" style="overflow: auto">
|
||||
<button id="scroll-child" type="button">Child</button>
|
||||
</div>
|
||||
`;
|
||||
const d = createDriver({ animate: false, steps: [{ element: "#scroll-child" }] });
|
||||
d.drive();
|
||||
d.destroy();
|
||||
|
||||
expect(document.getElementById("scroll-area")?.classList.contains(NO_SCROLL_CLASS)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("disableActiveInteraction", () => {
|
||||
it("leaves the active element interactive by default", () => {
|
||||
const d = createDriver({ animate: false, steps: SAMPLE_STEPS });
|
||||
|
||||
Reference in New Issue
Block a user