diff --git a/src/packages/tour/helperLayer.ts b/src/packages/tour/helperLayer.ts index 8f81bd744..83b398d42 100644 --- a/src/packages/tour/helperLayer.ts +++ b/src/packages/tour/helperLayer.ts @@ -1,5 +1,5 @@ import { style } from "../../util/style"; -import van from "../dom/van"; +import van, { State } from "../dom/van"; import { helperLayerClassName } from "./classNames"; import { setPositionRelativeToStep } from "./position"; import { TourStep } from "./steps"; @@ -10,14 +10,14 @@ const getClassName = ({ step, tourHighlightClass, }: { - step: TourStep; + step: State; tourHighlightClass: string; }) => { let highlightClass = helperLayerClassName; // check for a current step highlight class - if (typeof step.highlightClass === "string") { - highlightClass += ` ${step.highlightClass}`; + if (step.val && typeof step.val.highlightClass === "string") { + highlightClass += ` ${step.val.highlightClass}`; } // check for options highlight class @@ -29,7 +29,8 @@ const getClassName = ({ }; export type HelperLayerProps = { - step: TourStep; + currentStep: State; + steps: TourStep[]; targetElement: HTMLElement; tourHighlightClass: string; overlayOpacity: number; @@ -37,20 +38,19 @@ export type HelperLayerProps = { }; export const HelperLayer = ({ - step, + currentStep, + steps, targetElement, tourHighlightClass, overlayOpacity, helperLayerPadding, }: HelperLayerProps) => { - if (!step) { - return null; - } - - const className = getClassName({ step: step, tourHighlightClass }); + const step = van.derive(() => + currentStep.val !== undefined ? steps[currentStep.val] : null + ); const helperLayer = div({ - className, + className: () => getClassName({ step, tourHighlightClass }), style: style({ // the inner box shadow is the border for the highlighted element // the outer box shadow is the overlay effect @@ -58,12 +58,16 @@ export const HelperLayer = ({ }), }); - setPositionRelativeToStep( - targetElement, - helperLayer, - step, - helperLayerPadding - ); + van.derive(() => { + if (!step.val) return; + + setPositionRelativeToStep( + targetElement, + helperLayer, + step.val, + helperLayerPadding + ); + }); return helperLayer; }; diff --git a/src/packages/tour/tourRoot.ts b/src/packages/tour/tourRoot.ts index 027d3b0a9..5820f6283 100644 --- a/src/packages/tour/tourRoot.ts +++ b/src/packages/tour/tourRoot.ts @@ -16,121 +16,139 @@ export type TourRootProps = { export const TourRoot = ({ tour }: TourRootProps) => { const currentStep = tour.currentStepSignal; const steps = tour.getSteps(); - const step = van.derive(() => - currentStep.val !== undefined ? steps[currentStep.val] : null - ); - return () => { - if (!step.val) { - return null; + const helperLayer = HelperLayer({ + currentStep, + steps, + targetElement: tour.getTargetElement(), + tourHighlightClass: tour.getOption("highlightClass"), + overlayOpacity: tour.getOption("overlayOpacity"), + helperLayerPadding: tour.getOption("helperElementPadding"), + }); + + const root = div( + { className: "introjs-tour" }, + // helperLayer should not be re-rendered when the state changes for the transition to work + helperLayer, + () => { + // do not remove this check, it is necessary for this state-binding to work + // and render the entire section every time the state changes + if (currentStep.val === undefined) { + return null; + } + + const step = van.derive(() => + currentStep.val !== undefined ? steps[currentStep.val] : null + ); + + if (!step.val) { + return null; + } + + const exitOnOverlayClick = tour.getOption("exitOnOverlayClick") === true; + const overlayLayer = OverlayLayer({ + exitOnOverlayClick, + onExitTour: async () => { + return tour.exit(); + }, + }); + + const referenceLayer = ReferenceLayer({ + step: step.val, + targetElement: tour.getTargetElement(), + helperElementPadding: tour.getOption("helperElementPadding"), + + positionPrecedence: tour.getOption("positionPrecedence"), + autoPosition: tour.getOption("autoPosition"), + showStepNumbers: tour.getOption("showStepNumbers"), + + steps: tour.getSteps(), + currentStep: tour.currentStepSignal, + + onBulletClick: (stepNumber: number) => { + tour.goToStep(stepNumber); + }, + + bullets: tour.getOption("showBullets"), + + buttons: tour.getOption("showButtons"), + nextLabel: "Next", + onNextClick: async (e: any) => { + if (!tour.isLastStep()) { + await nextStep(tour); + } else if ( + new RegExp(doneButtonClassName, "gi").test( + (e.target as HTMLElement).className + ) + ) { + await tour + .callback("complete") + ?.call(tour, tour.getCurrentStep(), "done"); + + await tour.exit(); + } + }, + prevLabel: tour.getOption("prevLabel"), + onPrevClick: async () => { + if (tour.getCurrentStep() > 0) { + await previousStep(tour); + } + }, + skipLabel: tour.getOption("skipLabel"), + onSkipClick: async () => { + if (tour.isLastStep()) { + await tour + .callback("complete") + ?.call(tour, tour.getCurrentStep(), "skip"); + } + + await tour.callback("skip")?.call(tour, tour.getCurrentStep()); + + await tour.exit(); + }, + buttonClass: tour.getOption("buttonClass"), + nextToDone: tour.getOption("nextToDone"), + doneLabel: tour.getOption("doneLabel"), + hideNext: tour.getOption("hideNext"), + hidePrev: tour.getOption("hidePrev"), + + progress: tour.getOption("showProgress"), + progressBarAdditionalClass: tour.getOption( + "progressBarAdditionalClass" + ), + + stepNumbers: tour.getOption("showStepNumbers"), + stepNumbersOfLabel: tour.getOption("stepNumbersOfLabel"), + + scrollToElement: tour.getOption("scrollToElement"), + scrollPadding: tour.getOption("scrollPadding"), + + dontShowAgain: tour.getOption("dontShowAgain"), + onDontShowAgainChange: (e: any) => { + tour.setDontShowAgain((e.target).checked); + }, + dontShowAgainLabel: tour.getOption("dontShowAgainLabel"), + }); + + const disableInteraction = step.val.disableInteraction + ? DisableInteraction({ + currentStep: tour.currentStepSignal, + steps: tour.getSteps(), + targetElement: tour.getTargetElement(), + helperElementPadding: tour.getOption("helperElementPadding"), + }) + : null; + + return div(overlayLayer, referenceLayer, disableInteraction); } + ); - const exitOnOverlayClick = tour.getOption("exitOnOverlayClick") === true; - const overlayLayer = OverlayLayer({ - exitOnOverlayClick, - onExitTour: async () => { - return tour.exit(); - }, - }); - - const helperLayer = HelperLayer({ - step: step.val, - targetElement: tour.getTargetElement(), - tourHighlightClass: tour.getOption("highlightClass"), - overlayOpacity: tour.getOption("overlayOpacity"), - helperLayerPadding: tour.getOption("helperElementPadding"), - }); - - const referenceLayer = ReferenceLayer({ - step: step.val, - targetElement: tour.getTargetElement(), - helperElementPadding: tour.getOption("helperElementPadding"), - - positionPrecedence: tour.getOption("positionPrecedence"), - autoPosition: tour.getOption("autoPosition"), - showStepNumbers: tour.getOption("showStepNumbers"), - - steps: tour.getSteps(), - currentStep: tour.currentStepSignal, - - onBulletClick: (stepNumber: number) => { - tour.goToStep(stepNumber); - }, - - bullets: tour.getOption("showBullets"), - - buttons: tour.getOption("showButtons"), - nextLabel: "Next", - onNextClick: async (e: any) => { - if (!tour.isLastStep()) { - await nextStep(tour); - } else if ( - new RegExp(doneButtonClassName, "gi").test( - (e.target as HTMLElement).className - ) - ) { - await tour - .callback("complete") - ?.call(tour, tour.getCurrentStep(), "done"); + van.derive(() => { + // to clean up the root element when the tour is done + if (currentStep.val === undefined || currentStep.val < 0) { + root.remove(); + } + }); - await tour.exit(); - } - }, - prevLabel: tour.getOption("prevLabel"), - onPrevClick: async () => { - if (tour.getCurrentStep() > 0) { - await previousStep(tour); - } - }, - skipLabel: tour.getOption("skipLabel"), - onSkipClick: async () => { - if (tour.isLastStep()) { - await tour - .callback("complete") - ?.call(tour, tour.getCurrentStep(), "skip"); - } - - await tour.callback("skip")?.call(tour, tour.getCurrentStep()); - - await tour.exit(); - }, - buttonClass: tour.getOption("buttonClass"), - nextToDone: tour.getOption("nextToDone"), - doneLabel: tour.getOption("doneLabel"), - hideNext: tour.getOption("hideNext"), - hidePrev: tour.getOption("hidePrev"), - - progress: tour.getOption("showProgress"), - progressBarAdditionalClass: tour.getOption("progressBarAdditionalClass"), - - stepNumbers: tour.getOption("showStepNumbers"), - stepNumbersOfLabel: tour.getOption("stepNumbersOfLabel"), - - scrollToElement: tour.getOption("scrollToElement"), - scrollPadding: tour.getOption("scrollPadding"), - - dontShowAgain: tour.getOption("dontShowAgain"), - onDontShowAgainChange: (e: any) => { - tour.setDontShowAgain((e.target).checked); - }, - dontShowAgainLabel: tour.getOption("dontShowAgainLabel"), - }); - - const disableInteraction = step.val.disableInteraction - ? DisableInteraction({ - currentStep: tour.currentStepSignal, - steps: tour.getSteps(), - targetElement: tour.getTargetElement(), - helperElementPadding: tour.getOption("helperElementPadding"), - }) - : null; - - return div( - { className: "introjs-tour" }, - overlayLayer, - helperLayer, - referenceLayer, - disableInteraction - ); - }; + return root; };