From da7fc24e4fad0f589ff495a4a91550cf4c434eab Mon Sep 17 00:00:00 2001 From: binrysearch Date: Sun, 1 Sep 2024 22:20:01 +0100 Subject: [PATCH] complete tourTooltip --- src/packages/hint/tooltip.ts | 5 +- src/packages/tooltip/tooltip.ts | 14 +- src/packages/tour/showElement.ts | 392 +++++-------------------------- src/packages/tour/tour.ts | 4 + src/packages/tour/tourTooltip.ts | 281 +++++++++++++--------- 5 files changed, 242 insertions(+), 454 deletions(-) diff --git a/src/packages/hint/tooltip.ts b/src/packages/hint/tooltip.ts index 215a19ae6..2093428be 100644 --- a/src/packages/hint/tooltip.ts +++ b/src/packages/hint/tooltip.ts @@ -13,6 +13,7 @@ import { setPositionRelativeTo } from "../../util/setPositionRelativeTo"; import DOMEvent from "../../util/DOMEvent"; import getOffset from "../../util/getOffset"; import { HintTooltip } from "./hintTooltip"; +import van from "../dom/van"; // The hint close function used when the user clicks outside the hint let _hintCloseFunction: () => void | undefined; @@ -90,9 +91,9 @@ export async function showHintDialog(hint: Hint, stepId: number) { if (!hintItem) return; const tooltipLayer = HintTooltip({ - position: hintItem.position, + position: van.state(hintItem.position), text: item.hint || "", - targetOffset: getOffset(hintItem.element as HTMLElement), + targetOffset: van.state(getOffset(hintItem.element as HTMLElement)), // hints don't have step numbers showStepNumbers: false, diff --git a/src/packages/tooltip/tooltip.ts b/src/packages/tooltip/tooltip.ts index 073c62205..28be187ce 100644 --- a/src/packages/tooltip/tooltip.ts +++ b/src/packages/tooltip/tooltip.ts @@ -4,7 +4,6 @@ import van, { ChildDom, State } from "../dom/van"; import { arrowClassName, tooltipClassName, - tooltipTextClassName, } from "../tour/classNames"; import { determineAutoPosition, TooltipPosition } from "./tooltipPosition"; @@ -301,8 +300,8 @@ const alignTooltip = ( }; export type TooltipProps = { - position: TooltipPosition; - targetOffset: Offset; + position: State; + targetOffset: State; hintMode: boolean; showStepNumbers: boolean; @@ -313,7 +312,7 @@ export type TooltipProps = { export const Tooltip = ( { - position: initialPosition, + position, targetOffset, hintMode = false, showStepNumbers = false, @@ -324,7 +323,6 @@ export const Tooltip = ( }: TooltipProps, children?: ChildDom[] ) => { - const position = van.state(initialPosition); const top = van.state("auto"); const right = van.state("auto"); const bottom = van.state("auto"); @@ -337,7 +335,7 @@ export const Tooltip = ( const tooltipWidth = van.state(300); const windowSize = getWindowSize(); const tooltipBottomOverflow = van.derive( - () => targetOffset.top + tooltipHeight.val! > windowSize.height + () => targetOffset.val!.top + tooltipHeight.val! > windowSize.height ); van.derive(() => { @@ -350,7 +348,7 @@ export const Tooltip = ( ) { position.val = determineAutoPosition( positionPrecedence, - targetOffset, + targetOffset.val!, tooltipWidth.val, tooltipHeight.val, position.val @@ -367,7 +365,7 @@ export const Tooltip = ( ) { alignTooltip( position.val, - targetOffset, + targetOffset.val!, tooltipWidth.val, tooltipHeight.val, top, diff --git a/src/packages/tour/showElement.ts b/src/packages/tour/showElement.ts index b3ab4b32c..b0283f576 100644 --- a/src/packages/tour/showElement.ts +++ b/src/packages/tour/showElement.ts @@ -41,6 +41,9 @@ import { } from "../../util/queryElement"; import { setPositionRelativeToStep } from "./position"; import getPropValue from "../../util/getPropValue"; +import { TourTooltip } from "./tourTooltip"; +import getOffset from "../..//util/getOffset"; +import van from "../dom/van"; /** * Gets the current progress percentage @@ -263,9 +266,6 @@ export default async function _showElement(tour: Tour, step: TourStep) { ); let highlightClass = helperLayerClassName; - let nextTooltipButton: HTMLElement; - let prevTooltipButton: HTMLElement; - let skipTooltipButton: HTMLElement; //check for a current step highlight class if (typeof step.highlightClass === "string") { @@ -282,34 +282,11 @@ export default async function _showElement(tour: Tour, step: TourStep) { tooltipTextClassName, oldReferenceLayer ); - const oldTooltipTitleLayer = getElementByClassName( - tooltipTitleClassName, - oldReferenceLayer - ); - const oldArrowLayer = getElementByClassName( - arrowClassName, - oldReferenceLayer - ); const oldTooltipContainer = getElementByClassName( tooltipClassName, oldReferenceLayer ); - skipTooltipButton = getElementByClassName( - skipButtonClassName, - oldReferenceLayer - ); - - prevTooltipButton = getElementByClassName( - previousButtonClassName, - oldReferenceLayer - ); - - nextTooltipButton = getElementByClassName( - nextButtonClassName, - oldReferenceLayer - ); - //update or reset the helper highlight class setClass(oldHelperLayer, highlightClass); @@ -346,62 +323,10 @@ export default async function _showElement(tour: Tour, step: TourStep) { window.clearTimeout(_lastShowElementTimer); } - const oldHelperNumberLayer = queryElementByClassName( - helperNumberLayerClassName, - oldReferenceLayer - ); - _lastShowElementTimer = window.setTimeout(() => { - // set current step to the label - if (oldHelperNumberLayer !== null) { - oldHelperNumberLayer.innerHTML = `${step.step} ${tour.getOption( - "stepNumbersOfLabel" - )} ${tour.getSteps().length}`; - } - - // set current tooltip text - oldTooltipLayer.innerHTML = step.intro || ""; - - // set current tooltip title - oldTooltipTitleLayer.innerHTML = step.title || ""; - - //set the tooltip position - oldTooltipContainer.style.display = "block"; - placeTooltip( - oldTooltipContainer, - oldArrowLayer, - step.element as HTMLElement, - step.position, - tour.getOption("positionPrecedence"), - tour.getOption("showStepNumbers"), - tour.getOption("autoPosition"), - step.tooltipClass ?? tour.getOption("tooltipClass") - ); - - //change active bullet - _updateBullets(tour.getOption("showBullets"), oldReferenceLayer, step); - - _updateProgressBar( - oldReferenceLayer, - tour.getCurrentStep(), - tour.getSteps().length - ); - //show the tooltip oldTooltipContainer.style.opacity = "1"; - //reset button focus - if ( - nextTooltipButton && - new RegExp(doneButtonClassName, "gi").test(nextTooltipButton.className) - ) { - // skip button is now "done" button - nextTooltipButton.focus(); - } else if (nextTooltipButton) { - //still in the tour, focus on next - nextTooltipButton.focus(); - } - // change the scroll of the window, if needed scrollTo( tour.getOption("scrollToElement"), @@ -420,23 +345,6 @@ export default async function _showElement(tour: Tour, step: TourStep) { const referenceLayer = createElement("div", { className: tooltipReferenceLayerClassName, }); - const arrowLayer = createElement("div", { - className: arrowClassName, - }); - const tooltipLayer = createElement("div", { - className: tooltipClassName, - }); - const tooltipTextLayer = createElement("div", { - className: tooltipTextClassName, - }); - const tooltipHeaderLayer = createElement("div", { - className: tooltipHeaderClassName, - }); - const tooltipTitleLayer = createElement("h1", { - className: tooltipTitleClassName, - }); - - const buttonsLayer = createElement("div"); setStyle(helperLayer, { // the inner box shadow is the border for the highlighted element @@ -471,136 +379,75 @@ export default async function _showElement(tour: Tour, step: TourStep) { appendChild(tour.getTargetElement(), helperLayer, true); appendChild(tour.getTargetElement(), referenceLayer); - tooltipTextLayer.innerHTML = step.intro; - tooltipTitleLayer.innerHTML = step.title; - - setClass(buttonsLayer, tooltipButtonsClassName); - - if (tour.getOption("showButtons") === false) { - buttonsLayer.style.display = "none"; - } - - tooltipHeaderLayer.appendChild(tooltipTitleLayer); - tooltipLayer.appendChild(tooltipHeaderLayer); - tooltipLayer.appendChild(tooltipTextLayer); - - // "Do not show again" checkbox - if (tour.getOption("dontShowAgain")) { - const dontShowAgainWrapper = createElement("div", { - className: dontShowAgainClassName, - }); - const dontShowAgainCheckbox = createElement("input", { - type: "checkbox", - id: dontShowAgainClassName, - name: dontShowAgainClassName, - }); - dontShowAgainCheckbox.onchange = (e) => { - tour.setDontShowAgain((e.target).checked); - }; - const dontShowAgainCheckboxLabel = createElement("label", { - htmlFor: dontShowAgainClassName, - }); - dontShowAgainCheckboxLabel.innerText = - tour.getOption("dontShowAgainLabel"); - dontShowAgainWrapper.appendChild(dontShowAgainCheckbox); - dontShowAgainWrapper.appendChild(dontShowAgainCheckboxLabel); - - tooltipLayer.appendChild(dontShowAgainWrapper); - } - - tooltipLayer.appendChild(_createBullets(tour, step)); - tooltipLayer.appendChild(_createProgressBar(tour)); - - // add helper layer number - const helperNumberLayer = createElement("div"); - - if (tour.getOption("showStepNumbers") === true) { - setClass(helperNumberLayer, helperNumberLayerClassName); - - helperNumberLayer.innerHTML = `${step.step} ${tour.getOption( - "stepNumbersOfLabel" - )} ${tour.getSteps().length}`; - tooltipLayer.appendChild(helperNumberLayer); - } - - tooltipLayer.appendChild(arrowLayer); - referenceLayer.appendChild(tooltipLayer); - - //next button - nextTooltipButton = createElement("a"); + const tooltip = TourTooltip({ + 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"); + } - nextTooltipButton.onclick = async () => { - if (!tour.isLastStep()) { - await nextStep(tour); - } else if ( - new RegExp(doneButtonClassName, "gi").test(nextTooltipButton.className) - ) { - await tour - .callback("complete") - ?.call(tour, tour.getCurrentStep(), "done"); + 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"), - setAnchorAsButton(nextTooltipButton); - nextTooltipButton.innerHTML = tour.getOption("nextLabel"); + progress: tour.getOption("showProgress"), + progressBarAdditionalClass: tour.getOption("progressBarAdditionalClass"), - //previous button - prevTooltipButton = createElement("a"); + stepNumbers: tour.getOption("showStepNumbers"), + stepNumbersOfLabel: tour.getOption("stepNumbersOfLabel"), - prevTooltipButton.onclick = async () => { - if (tour.getCurrentStep() > 0) { - await previousStep(tour); - } - }; - - setAnchorAsButton(prevTooltipButton); - prevTooltipButton.innerHTML = tour.getOption("prevLabel"); - - //skip button - skipTooltipButton = createElement("a", { - className: skipButtonClassName, + dontShowAgain: tour.getOption("dontShowAgain"), + onDontShowAgainChange: (e: any) => { + tour.setDontShowAgain((e.target).checked); + }, + dontShowAgainLabel: tour.getOption("dontShowAgainLabel"), }); - setAnchorAsButton(skipTooltipButton); - skipTooltipButton.innerHTML = tour.getOption("skipLabel"); - - skipTooltipButton.onclick = async () => { - if (tour.isLastStep()) { - await tour - .callback("complete") - ?.call(tour, tour.getCurrentStep(), "skip"); - } - - await tour.callback("skip")?.call(tour, tour.getCurrentStep()); - - await tour.exit(); - }; - - tooltipHeaderLayer.appendChild(skipTooltipButton); - - // in order to prevent displaying previous button always - if (tour.getSteps().length > 1) { - buttonsLayer.appendChild(prevTooltipButton); - } - - // we always need the next button because this - // button changes to "Done" in the last step of the tour - buttonsLayer.appendChild(nextTooltipButton); - tooltipLayer.appendChild(buttonsLayer); - - // set proper position - placeTooltip( - tooltipLayer, - arrowLayer, - step.element as HTMLElement, - step.position, - tour.getOption("positionPrecedence"), - tour.getOption("showStepNumbers"), - tour.getOption("autoPosition"), - step.tooltipClass ?? tour.getOption("tooltipClass") - ); + referenceLayer.appendChild(tooltip); // change the scroll of the window, if needed scrollTo( @@ -608,7 +455,7 @@ export default async function _showElement(tour: Tour, step: TourStep) { step.scrollTo, tour.getOption("scrollPadding"), step.element as HTMLElement, - tooltipLayer + tooltip ); //end of new element if-else condition @@ -628,115 +475,6 @@ export default async function _showElement(tour: Tour, step: TourStep) { _disableInteraction(tour, step); } - // when it's the first step of tour - if (tour.getCurrentStep() === 0 && tour.getSteps().length > 1) { - if (nextTooltipButton) { - setClass( - nextTooltipButton, - tour.getOption("buttonClass"), - nextButtonClassName - ); - nextTooltipButton.innerHTML = tour.getOption("nextLabel"); - } - - if (tour.getOption("hidePrev") === true) { - if (prevTooltipButton) { - setClass( - prevTooltipButton, - tour.getOption("buttonClass"), - previousButtonClassName, - hiddenButtonClassName - ); - } - if (nextTooltipButton) { - addClass(nextTooltipButton, fullButtonClassName); - } - } else { - if (prevTooltipButton) { - setClass( - prevTooltipButton, - tour.getOption("buttonClass"), - previousButtonClassName, - disabledButtonClassName - ); - } - } - } else if (tour.isLastStep() || tour.getSteps().length === 1) { - // last step of tour - if (prevTooltipButton) { - setClass( - prevTooltipButton, - tour.getOption("buttonClass"), - previousButtonClassName - ); - } - - if (tour.getOption("hideNext") === true) { - if (nextTooltipButton) { - setClass( - nextTooltipButton, - tour.getOption("buttonClass"), - nextButtonClassName, - hiddenButtonClassName - ); - } - if (prevTooltipButton) { - addClass(prevTooltipButton, fullButtonClassName); - } - } else { - if (nextTooltipButton) { - if (tour.getOption("nextToDone") === true) { - nextTooltipButton.innerHTML = tour.getOption("doneLabel"); - addClass( - nextTooltipButton, - tour.getOption("buttonClass"), - nextButtonClassName, - doneButtonClassName - ); - } else { - setClass( - nextTooltipButton, - tour.getOption("buttonClass"), - nextButtonClassName, - disabledButtonClassName - ); - } - } - } - } else { - // steps between start and end - if (prevTooltipButton) { - setClass( - prevTooltipButton, - tour.getOption("buttonClass"), - previousButtonClassName - ); - } - if (nextTooltipButton) { - setClass( - nextTooltipButton, - tour.getOption("buttonClass"), - nextButtonClassName - ); - nextTooltipButton.innerHTML = tour.getOption("nextLabel"); - } - } - - if (prevTooltipButton) { - prevTooltipButton.setAttribute("role", "button"); - } - if (nextTooltipButton) { - nextTooltipButton.setAttribute("role", "button"); - } - if (skipTooltipButton) { - skipTooltipButton.setAttribute("role", "button"); - } - - //Set focus on "next" button, so that hitting Enter always moves you onto the next step - if (nextTooltipButton) { - nextTooltipButton.focus(); - } - setShowElement(step.element as HTMLElement); await tour.callback("afterChange")?.call(tour, step.element); diff --git a/src/packages/tour/tour.ts b/src/packages/tour/tour.ts index 9ccfc1cfe..b61e9d4da 100644 --- a/src/packages/tour/tour.ts +++ b/src/packages/tour/tour.ts @@ -21,6 +21,7 @@ import { getContainerElement } from "../../util/containerElement"; import DOMEvent from "../../util/DOMEvent"; import onKeyDown from "./onKeyDown"; import onResize from "./onResize"; +import van from "../dom/van"; /** * Intro.js Tour class @@ -28,6 +29,7 @@ import onResize from "./onResize"; export class Tour implements Package { private _steps: TourStep[] = []; private _currentStep: number = -1; + public currentStepSignal = van.state(-1); private _direction: "forward" | "backward"; private readonly _targetElement: HTMLElement; private _options: TourOptions; @@ -186,6 +188,7 @@ export class Tour implements Package { this._direction = "backward"; } + this.currentStepSignal.val = step; this._currentStep = step; return this; } @@ -225,6 +228,7 @@ export class Tour implements Package { * Go to the next step of the tour */ async nextStep() { + this.currentStepSignal.val! += 1; await nextStep(this); return this; } diff --git a/src/packages/tour/tourTooltip.ts b/src/packages/tour/tourTooltip.ts index 07dab220e..7eb42efb7 100644 --- a/src/packages/tour/tourTooltip.ts +++ b/src/packages/tour/tourTooltip.ts @@ -1,5 +1,5 @@ import { Tooltip, type TooltipProps } from "../tooltip/tooltip"; -import van from "../dom/van"; +import van, { PropValueOrDerived, State } from "../dom/van"; import { activeClassName, bulletsClassName, @@ -20,6 +20,7 @@ import { } from "./classNames"; import { TourStep } from "./steps"; import { dataStepNumberAttribute } from "./dataAttributes"; +import getOffset from "../../util/getOffset"; const { h1, div, input, label, ul, li, a, p } = van.tags; @@ -45,26 +46,28 @@ const DontShowAgain = ({ const Bullets = ({ steps, - step, + currentStep, onBulletClick, }: { steps: TourStep[]; - step: TourStep; + currentStep: State; onBulletClick: (stepNumber: number) => void; }): HTMLElement => { + + const step = van.derive(() => steps[currentStep.val!]); + return div({ className: bulletsClassName }, [ ul({ role: "tablist" }, [ ...steps.map(({ step: stepNumber }) => { const innerLi = li( { - role: "presentation", - className: `${ - step.step === stepNumber - } ? ${activeClassName} : ""`, + role: "presentation" }, [ a({ role: "tab", + className: () => + `${step.val!.step === stepNumber ? activeClassName : ""}`, onclick: (e: any) => { const stepNumberAttribute = ( e.target as HTMLElement @@ -91,10 +94,10 @@ const ProgressBar = ({ progressBarAdditionalClass }: { steps: TourStep[]; - currentStep: number; + currentStep: State; progressBarAdditionalClass: string; }) => { - const progress = ((currentStep) / steps.length) * 100; + const progress = van.derive(() => ((currentStep.val!) / steps.length) * 100); return div({ className: progressClassName }, [ div({ @@ -103,7 +106,7 @@ const ProgressBar = ({ "aria-valuemin": "0", "aria-valuemax": "100", "aria-valuenow": () => progress.toString(), - style: () => `width:${progress}%;`, + style: `width:${progress}%;`, }), ]); } @@ -125,16 +128,19 @@ const StepNumber = ({ const Button = ({ label, onClick, + disabled, className }: { label: string; onClick: (e: any) => void; - className?: string + disabled?: PropValueOrDerived + className?: PropValueOrDerived }) => { return a( { role: "button", tabIndex: 0, + ariaDisabled: disabled ?? false, onclick: onClick, className: className ?? "", }, @@ -143,65 +149,129 @@ const Button = ({ }; const NextButton = ({ - label, + steps, + currentStep, + + nextLabel, + doneLabel, + + hideNext, + hidePrev, + nextToDone, onClick, - isDisabled, - isFullButton, - isDoneButton, buttonClass }: { - label: string; + steps: TourStep[]; + currentStep: State; + + nextLabel: string; + doneLabel: string; + + hideNext: boolean; + hidePrev: boolean; + nextToDone: boolean; onClick: (e: any) => void; - isFullButton: boolean; - isDisabled: boolean; - // next button can be a done button as well - isDoneButton: boolean; buttonClass: string; }) => { - const classNames = [buttonClass]; + const isFullButton = van.derive( + () => currentStep.val === 0 && steps.length > 1 && hidePrev + ); - if (isDoneButton) { - classNames.push(doneButtonClassName); - } else { - classNames.push(nextButtonClassName); - } + const isLastStep = van.derive( + () => currentStep.val === steps.length - 1 || steps.length === 1 + ); - if (isDisabled) { - classNames.push(disabledButtonClassName); - } + const isDisabled = van.derive(() => { + // when the current step is the last one or there is only one step to show + return ( + isLastStep.val && + !hideNext && + !nextToDone + ); + }); + + const isDoneButton = van.derive(() => { + return ( + isLastStep.val && + !hideNext && + nextToDone + ); + }); + + const nextButton = Button({ + label: isDoneButton.val ? doneLabel : nextLabel, + onClick, + className: () => { + const classNames = [buttonClass, nextButtonClassName]; + + if (isDoneButton.val) { + classNames.push(doneButtonClassName); + } + + if (isDisabled.val) { + classNames.push(disabledButtonClassName); + } + + if (isFullButton.val) { + classNames.push(fullButtonClassName); + } + + return classNames.filter(Boolean).join(" "); + }, + }); - if (isFullButton) { - classNames.push(fullButtonClassName); - } + nextButton.focus() - return Button({ label, onClick, className: classNames.filter(Boolean).join(" ") }); + return nextButton; } const PrevButton = ({ label, + steps, + currentStep, + hidePrev, + hideNext, onClick, - isFullButton, - isDisabled, - buttonClass + buttonClass, }: { label: string; + steps: TourStep[]; + currentStep: State; + hidePrev: boolean; + hideNext: boolean; onClick: (e: any) => void; - isFullButton: boolean; - isDisabled: boolean; buttonClass: string; }) => { - const classNames = [buttonClass, previousButtonClassName]; + const isDisabled = van.derive(() => { + // when the current step is the first one and there are more steps to show + return currentStep.val === 0 && steps.length > 1 && !hidePrev; + }); - if (isFullButton) { + const isFullButton = van.derive(() => { + // when the current step is the last one or there is only one step to show + return ( + (currentStep.val === steps.length - 1 || steps.length === 1) && hideNext + ); + }); + + return Button({ + label, + onClick, + disabled: () => isDisabled.val, + className: () => { + const classNames = [buttonClass, previousButtonClassName]; + if (isFullButton) { classNames.push(fullButtonClassName); - } + } - if (isDisabled) { + if (isDisabled.val) { classNames.push(disabledButtonClassName); - } + } - return Button({ label, onClick, className: classNames.filter(Boolean).join(" ") }); -} + return classNames.filter(Boolean).join(" "); + }, + }); +}; const Buttons = ({ steps, @@ -221,7 +291,7 @@ const Buttons = ({ onPrevClick, }: { steps: TourStep[]; - currentStep: number; + currentStep: State; buttonClass: string; @@ -236,66 +306,43 @@ const Buttons = ({ prevLabel: string; onPrevClick: (e: any) => void; }) => { - const children: ChildNode[] = []; - - let shouldShowPrev = steps.length > 1; - let isPrevButtonDisabled = false; - let isPrevButtonFull = false; - - let shouldShowNext = true; - let shouldRenderNextAsDone = false; - let isNextButtonFull = false; - let isNextButtonDisabled = false; - - // when the current step is the first one and there are more steps to show - if (currentStep === 0 && steps.length > 1) { - if (hidePrev) { - shouldShowPrev = false; - isNextButtonFull = true; - } else { - isPrevButtonDisabled = true - } - } else if (currentStep === steps.length - 1 || steps.length === 1) { - // when the current step is the last one or there is only one step to show - if (hideNext) { - shouldShowNext = false; - isPrevButtonFull = true; - } else { - if (nextToDone) { - shouldRenderNextAsDone = true; - } else { - isNextButtonDisabled = true; - } - } - } - - // in order to prevent always displaying the previous button - if (shouldShowPrev) { - children.push( - PrevButton({ - label: prevLabel, - onClick: onPrevClick, - isDisabled: isPrevButtonDisabled, - isFullButton: isPrevButtonFull, - buttonClass, - }) - ); - } + const isLastStep = van.derive( + () => currentStep.val === steps.length - 1 || steps.length === 1 + ); - if (shouldShowNext) { - children.push( - NextButton({ - label: shouldRenderNextAsDone ? doneLabel : nextLabel, - isDoneButton: shouldRenderNextAsDone, - onClick: onNextClick, - isDisabled: isNextButtonDisabled, - isFullButton: isNextButtonFull, - buttonClass, - }) - ); - } + const isFirstStep = van.derive( + () => currentStep.val === 0 && steps.length > 1 + ); - return div({ className: tooltipButtonsClassName }, children); + return div( + { className: tooltipButtonsClassName }, + () => + isFirstStep.val && hidePrev + ? null + : PrevButton({ + label: prevLabel, + steps, + currentStep, + hidePrev, + hideNext, + onClick: onPrevClick, + buttonClass, + }), + () => + isLastStep.val && hideNext + ? null + : NextButton({ + currentStep, + steps, + doneLabel, + nextLabel, + onClick: onNextClick, + hideNext, + hidePrev, + nextToDone, + buttonClass, + }) + ); }; const Header = ({ @@ -315,13 +362,9 @@ const Header = ({ ]); }; -export type TourTooltipProps = Omit & { - title: string; - text: string; - +export type TourTooltipProps = Omit & { steps: TourStep[]; - step: TourStep; - currentStep: number; + currentStep: State; bullets: boolean; onBulletClick: (stepNumber: number) => void; @@ -352,14 +395,10 @@ export type TourTooltipProps = Omit & { export const TourTooltip = ({ steps, - step, currentStep, onBulletClick, - title, - text, - bullets, buttons, @@ -388,7 +427,13 @@ export const TourTooltip = ({ }: TourTooltipProps) => { const children = []; - children.push(Header({ title, skipLabel, onSkipClick })); + const step = van.derive(() => steps[currentStep.val!]); + const title = van.derive(() => step.val!.title); + const text = van.derive(() => step.val!.intro); + const position = van.derive(() => step.val!.position); + const targetOffset = van.derive(() => getOffset(step.val!.element as HTMLElement)); + + children.push(Header({ title: title.val!, skipLabel, onSkipClick })); children.push( div({ className: tooltipTextClassName }, p(text)), @@ -399,7 +444,7 @@ export const TourTooltip = ({ } if (bullets) { - children.push(Bullets({ steps, step, onBulletClick })); + children.push(Bullets({ steps, currentStep, onBulletClick })); } if (progress) { @@ -409,7 +454,7 @@ export const TourTooltip = ({ } if (stepNumbers) { - children.push(StepNumber({ step, steps, stepNumbersOfLabel })); + children.push(StepNumber({ step: step.val!, steps, stepNumbersOfLabel })); } if (buttons) { @@ -437,6 +482,8 @@ export const TourTooltip = ({ { ...props, hintMode: false, + position, + targetOffset }, children );