-
-
Notifications
You must be signed in to change notification settings - Fork 655
Description
Shepherd Events Issue
Description
When the showOn function returns false, the configured step is supposed to be skipped. This works. But if the configured step is the first step, this additionally leads to unexpected behavior. The example below we wanted to make the page non-interactive by setting pointer-events to none. This would normally work fine, but of course this property needs to be removed again, once the tour completes or cancels.
Observed behavior (see example below)
When starting the tour, the tour automatically skips to the second step, as the first step should not be shown. This is expected. But then when using the left arrow to go to the previous step something unexpected happens. Shepherd correctly again does not show the first step, as it should not be shown. But then Shepherd visually closes the tour, but none of the documented events are fired.
Although the tour is no longer shown after this, internally the tour is still active and on the step that was shown previously. This can be confirmed by the following code returning "step-2". If the tour was closed correctly, Shepherd.activeTour would return null.
Shepherd.activeTour.getCurrentStep().options.idExpected Behavior
If the tour is visually closed the way described above, either a complete or cancel event should be fired, or a newly specified event specifically for this behavior should be fired.
Interestingly for the opposite case, where the last step is not shown, the complete event is still fired correctly when using the right arrow on the second last step.
Another valid option would be to visually keep the tour open, this would match the expectation one would obtain after running the above js-snippet.
Minimal Example
Keep in mind that in normal circumstances we use more complicated showOn rules, and maybe the hidden steps stack (multiple steps behind each other right from the start are not shown). If it would be only ever one step, then we could theoretically just create two different tours where one tour contains the first step and the other tour does not contain the first step at all instead of just not showing it, but this is less elegant and because of the “stack” example from before, this would also be combinatorics nightmare, depending on how many steps are hidden.
Another solution would be to add a first step that is always shown as the first step, no matter what, but for some tours this would just not be appropriate.
In the example below, we add the no-pointer-events class to the content when the tour starts, and when it is closed either by the cancel or by the complete event the class is removed again. But when the tour closes without those events, the removal of this class (or any other cleanup that would be needed) is not performed.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Shepherd Issue Reproduction</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@10.0.1/dist/css/shepherd.css" />
<style>
.no-pointer-events { pointer-events: none; }
</style>
</head>
<body>
<div id="content">
<button id="startTourButton">start tour</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/shepherd.js@10.0.1/dist/js/shepherd.min.js"></script>
<script>
const tour = new Shepherd.Tour({ useModalOverlay: true, defaultStepOptions: { cancelIcon: { enabled: true } } });
tour.addStep({ id: "step-1", text: "Step 1", showOn: () => false });
tour.addStep({ id: "step-2", text: "Step 2" });
tour.on("cancel", () => console.log("Tour was canceled."));
tour.on("complete", () => console.log("Tour was completed."));
tour.on("hide", () => console.log("Tour was hidden."));
tour.on("show", () => console.log("Tour was shown."));
tour.on("start", () => console.log("Tour was started."));
tour.on("active", () => console.log("Tour was activated."));
tour.on("inactive", () => console.log("Tour was inactivated."));
document.getElementById("startTourButton").addEventListener("click", () => tour.start());
let content = document.getElementById("content");
tour.on("start", () => content.classList.add("no-pointer-events"));
["cancel", "complete"].forEach((event) => tour.on(event, () => content.classList.remove("no-pointer-events")));
</script>
</body>
</html>