diff --git a/src/core/dontShowAgain.ts b/src/core/dontShowAgain.ts deleted file mode 100644 index 388a2ebb8..000000000 --- a/src/core/dontShowAgain.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IntroJs } from "../intro"; -import { deleteCookie, getCookie, setCookie } from "../util/cookie"; - -const dontShowAgainCookieValue = "true"; - -/** - * Set the "Don't show again" state - * - * @api private - */ -export function setDontShowAgain(intro: IntroJs, dontShowAgain: boolean) { - if (dontShowAgain) { - setCookie( - intro._options.dontShowAgainCookie, - dontShowAgainCookieValue, - intro._options.dontShowAgainCookieDays - ); - } else { - deleteCookie(intro._options.dontShowAgainCookie); - } -} - -/** - * Get the "Don't show again" state from cookies - * - * @api private - */ -export function getDontShowAgain(intro: IntroJs): boolean { - const dontShowCookie = getCookie(intro._options.dontShowAgainCookie); - return dontShowCookie !== "" && dontShowCookie === dontShowAgainCookieValue; -} diff --git a/src/core/hint.ts b/src/core/hint.ts index c67d8c966..d3f59789f 100644 --- a/src/core/hint.ts +++ b/src/core/hint.ts @@ -3,7 +3,7 @@ import removeClass from "../util/removeClass"; import isFixed from "../util/isFixed"; import getOffset from "../util/getOffset"; import cloneObject from "../util/cloneObject"; -import DOMEvent from "./DOMEvent"; +import DOMEvent from "../util/DOMEvent"; import setAnchorAsButton from "../util/setAnchorAsButton"; import setHelperLayerPosition from "./setHelperLayerPosition"; import placeTooltip from "./placeTooltip"; diff --git a/src/core/placeTooltip.ts b/src/core/placeTooltip.ts index fe0ac8463..9c08f66ed 100644 --- a/src/core/placeTooltip.ts +++ b/src/core/placeTooltip.ts @@ -4,8 +4,19 @@ import addClass from "../util/addClass"; import checkRight from "../util/checkRight"; import checkLeft from "../util/checkLeft"; import removeEntry from "../util/removeEntry"; -import { HintStep, IntroStep, TooltipPosition } from "../packages/tour/steps"; -import { IntroJs } from "../intro"; + +export type TooltipPosition = + | "floating" + | "top" + | "bottom" + | "left" + | "right" + | "top-right-aligned" + | "top-left-aligned" + | "top-middle-aligned" + | "bottom-right-aligned" + | "bottom-left-aligned" + | "bottom-middle-aligned"; /** * auto-determine alignment @@ -159,15 +170,16 @@ function _determineAutoPosition( * @api private */ export default function placeTooltip( - intro: IntroJs, - currentStep: IntroStep | HintStep, tooltipLayer: HTMLElement, arrowLayer: HTMLElement, + targetElement: HTMLElement, + position: TooltipPosition, + positionPrecedence: TooltipPosition[], + showStepNumbers = false, + autoPosition = true, + tooltipClassName = "", hintMode: boolean = false ) { - if (!currentStep) return; - - let tooltipCssClass = ""; let tooltipOffset: { top: number; left: number; @@ -181,7 +193,6 @@ export default function placeTooltip( height: number; }; let windowSize: { width: number; height: number }; - let currentTooltipPosition: TooltipPosition; //reset the old style tooltipLayer.style.top = ""; @@ -193,42 +204,31 @@ export default function placeTooltip( arrowLayer.style.display = "inherit"; - //if we have a custom css class for each step - if (typeof currentStep.tooltipClass === "string") { - tooltipCssClass = currentStep.tooltipClass; - } else { - tooltipCssClass = intro._options.tooltipClass; - } - - tooltipLayer.className = ["introjs-tooltip", tooltipCssClass] - .filter(Boolean) - .join(" "); + addClass(tooltipLayer, `introjs-tooltip ${tooltipClassName}`); tooltipLayer.setAttribute("role", "dialog"); - currentTooltipPosition = currentStep.position; - // Floating is always valid, no point in calculating - if (currentTooltipPosition !== "floating" && intro._options.autoPosition) { - currentTooltipPosition = _determineAutoPosition( - intro._options.positionPrecedence, - currentStep.element as HTMLElement, + if (position !== "floating" && autoPosition) { + position = _determineAutoPosition( + positionPrecedence, + targetElement, tooltipLayer, - currentTooltipPosition + position ); } let tooltipLayerStyleLeft: number; - targetOffset = getOffset(currentStep.element as HTMLElement); + targetOffset = getOffset(targetElement as HTMLElement); tooltipOffset = getOffset(tooltipLayer); windowSize = getWindowSize(); - addClass(tooltipLayer, `introjs-${currentTooltipPosition}`); + addClass(tooltipLayer, `introjs-${position}`); let tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2; - switch (currentTooltipPosition) { + switch (position) { case "top-right-aligned": arrowLayer.className = "introjs-arrow bottom-right"; @@ -300,7 +300,7 @@ export default function placeTooltip( } break; case "left": - if (!hintMode && intro._options.showStepNumbers === true) { + if (!hintMode && showStepNumbers === true) { tooltipLayer.style.top = "15px"; } diff --git a/src/packages/tour/classNames.ts b/src/packages/tour/classNames.ts index d4f94c3ed..82743c5d8 100644 --- a/src/packages/tour/classNames.ts +++ b/src/packages/tour/classNames.ts @@ -1,3 +1,4 @@ +export const overlayClassName = "introjs-overlay"; export const disableInteractionClassName = "introjs-disableInteraction"; export const bulletsClassName = "introjs-bullets"; export const progressClassName = "introjs-progress"; @@ -20,3 +21,5 @@ export const hiddenButtonClassName = "introjs-hidden"; export const disabledButtonClassName = "introjs-disabled"; export const fullButtonClassName = "introjs-fullbutton"; export const activeClassName = "active"; +export const fixedTooltipClassName = "introjs-fixedTooltip"; +export const floatingElementClassName = "introjsFloatingElement"; diff --git a/src/packages/tour/dontShowAgain.ts b/src/packages/tour/dontShowAgain.ts new file mode 100644 index 000000000..1cedc7cb5 --- /dev/null +++ b/src/packages/tour/dontShowAgain.ts @@ -0,0 +1,30 @@ +import { deleteCookie, getCookie, setCookie } from "../../util/cookie"; + +const dontShowAgainCookieValue = "true"; + +/** + * Set the "Don't show again" state + * + * @api private + */ +export function setDontShowAgain( + dontShowAgain: boolean, + cookieName: string, + cookieDays: number +) { + if (dontShowAgain) { + setCookie(cookieName, dontShowAgainCookieValue, cookieDays); + } else { + deleteCookie(cookieName); + } +} + +/** + * Get the "Don't show again" state from cookies + * + * @api private + */ +export function getDontShowAgain(cookieName: string): boolean { + const dontShowCookie = getCookie(cookieName); + return dontShowCookie !== "" && dontShowCookie === dontShowAgainCookieValue; +} diff --git a/src/packages/tour/exitIntro.ts b/src/packages/tour/exitIntro.ts index 8dd2ed9b9..07e096ef2 100644 --- a/src/packages/tour/exitIntro.ts +++ b/src/packages/tour/exitIntro.ts @@ -1,9 +1,20 @@ -import DOMEvent from "../../core/DOMEvent"; +import DOMEvent from "../../util/DOMEvent"; import onKeyDown from "../../core/onKeyDown"; import onResize from "./onResize"; import removeShowElement from "../../core/removeShowElement"; import removeChild from "../../util/removeChild"; import { Tour } from "./tour"; +import { + disableInteractionClassName, + floatingElementClassName, + helperLayerClassName, + overlayClassName, + tooltipReferenceLayerClassName, +} from "./classNames"; +import { + queryElementByClassName, + queryElementsByClassName, +} from "src/util/queryElement"; /** * Exit from intro @@ -26,7 +37,7 @@ export default async function exitIntro(tour: Tour, force: boolean = false) { // remove overlay layers from the page const overlayLayers = Array.from( - targetElement.querySelectorAll(".introjs-overlay") + queryElementsByClassName(overlayClassName, targetElement) ); if (overlayLayers && overlayLayers.length) { @@ -36,25 +47,29 @@ export default async function exitIntro(tour: Tour, force: boolean = false) { } //remove all helper layers - const helperLayer = targetElement.querySelector( - ".introjs-helperLayer" + const helperLayer = queryElementByClassName( + helperLayerClassName, + targetElement ); removeChild(helperLayer, true); - const referenceLayer = targetElement.querySelector( - ".introjs-tooltipReferenceLayer" + const referenceLayer = queryElementByClassName( + tooltipReferenceLayerClassName, + targetElement ); removeChild(referenceLayer); //remove disableInteractionLayer - const disableInteractionLayer = targetElement.querySelector( - ".introjs-disableInteraction" + const disableInteractionLayer = queryElementByClassName( + disableInteractionClassName, + targetElement ); removeChild(disableInteractionLayer); //remove intro floating element - const floatingElement = document.querySelector( - ".introjsFloatingElement" + const floatingElement = queryElementByClassName( + floatingElementClassName, + targetElement ); removeChild(floatingElement); diff --git a/src/packages/tour/introForElement.ts b/src/packages/tour/introForElement.ts index a6419334d..ebcd10704 100644 --- a/src/packages/tour/introForElement.ts +++ b/src/packages/tour/introForElement.ts @@ -1,5 +1,5 @@ import addOverlayLayer from "../../core/addOverlayLayer"; -import DOMEvent from "../../core/DOMEvent"; +import DOMEvent from "../../util/DOMEvent"; import { nextStep } from "./steps"; import onKeyDown from "../../core/onKeyDown"; import onResize from "./onResize"; diff --git a/src/packages/tour/option.ts b/src/packages/tour/option.ts index 9d703d0e7..ed6d3dbdb 100644 --- a/src/packages/tour/option.ts +++ b/src/packages/tour/option.ts @@ -1,7 +1,8 @@ -import { IntroStep, ScrollTo, TooltipPosition } from "./steps"; +import { TooltipPosition } from "../../core/placeTooltip"; +import { TourStep, ScrollTo } from "./steps"; export interface TourOptions { - steps: Partial[]; + steps: Partial[]; /* Is this tour instance active? Don't show the tour again if this flag is set to false */ isActive: boolean; /* Next button label in tooltip box */ @@ -111,6 +112,6 @@ export function getDefaultTourOptions(): TourOptions { helperElementPadding: 10, buttonClass: "introjs-button", - progressBarAdditionalClass: false, + progressBarAdditionalClass: "", }; } diff --git a/src/packages/tour/setHelperLayerPosition.ts b/src/packages/tour/setHelperLayerPosition.ts new file mode 100644 index 000000000..bdc829dfd --- /dev/null +++ b/src/packages/tour/setHelperLayerPosition.ts @@ -0,0 +1,48 @@ +import getOffset from "../../util/getOffset"; +import isFixed from "../../util/isFixed"; +import addClass from "../../util/addClass"; +import removeClass from "../../util/removeClass"; +import setStyle from "../../util/setStyle"; +import { TourStep } from "./steps"; +import { Tour } from "./tour"; +import { fixedTooltipClassName } from "./classNames"; + +/** + * Update the position of the helper layer on the screen + * + * @api private + */ +export default function setHelperLayerPosition( + tour: Tour, + step: TourStep, + helperLayer: HTMLElement +) { + if (!helperLayer || !step) return; + + const elementPosition = getOffset( + step.element as HTMLElement, + tour.getTargetElement() + ); + let widthHeightPadding = tour.getOption("helperElementPadding"); + + // If the target element is fixed, the tooltip should be fixed as well. + // Otherwise, remove a fixed class that may be left over from the previous + // step. + if (step.element instanceof Element && isFixed(step.element)) { + addClass(helperLayer, fixedTooltipClassName); + } else { + removeClass(helperLayer, fixedTooltipClassName); + } + + if (step.position === "floating") { + widthHeightPadding = 0; + } + + //set new position to helper layer + setStyle(helperLayer, { + width: `${elementPosition.width + widthHeightPadding}px`, + height: `${elementPosition.height + widthHeightPadding}px`, + top: `${elementPosition.top - widthHeightPadding / 2}px`, + left: `${elementPosition.left - widthHeightPadding / 2}px`, + }); +} diff --git a/src/packages/tour/showElement.ts b/src/packages/tour/showElement.ts index 33a3bca77..832c3c56d 100644 --- a/src/packages/tour/showElement.ts +++ b/src/packages/tour/showElement.ts @@ -5,7 +5,7 @@ import scrollTo from "../../util/scrollTo"; import exitIntro from "./exitIntro"; import setAnchorAsButton from "../../util/setAnchorAsButton"; import { TourStep, nextStep, previousStep } from "./steps"; -import setHelperLayerPosition from "../../core/setHelperLayerPosition"; +import setHelperLayerPosition from "./setHelperLayerPosition"; import placeTooltip from "../../core/placeTooltip"; import removeShowElement from "../../core/removeShowElement"; import createElement from "../../util/createElement"; @@ -266,7 +266,7 @@ export default async function _showElement(tour: Tour, step: TourStep) { tooltipTitleClassName, oldReferenceLayer ); - const oldArrowLayer = queryElementByClassName( + const oldArrowLayer = getElementByClassName( arrowClassName, oldReferenceLayer ); @@ -331,7 +331,16 @@ export default async function _showElement(tour: Tour, step: TourStep) { //set the tooltip position oldTooltipContainer.style.display = "block"; - placeTooltip(tour, step, oldTooltipContainer, oldArrowLayer); + 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); @@ -533,7 +542,16 @@ export default async function _showElement(tour: Tour, step: TourStep) { tooltipLayer.appendChild(buttonsLayer); // set proper position - placeTooltip(tour, step, tooltipLayer, arrowLayer); + placeTooltip( + tooltipLayer, + arrowLayer, + step.element as HTMLElement, + step.position, + tour.getOption("positionPrecedence"), + tour.getOption("showStepNumbers"), + tour.getOption("autoPosition"), + step.tooltipClass ?? tour.getOption("tooltipClass") + ); // change the scroll of the window, if needed scrollTo( diff --git a/src/packages/tour/steps.ts b/src/packages/tour/steps.ts index b48816e4a..e13597d24 100644 --- a/src/packages/tour/steps.ts +++ b/src/packages/tour/steps.ts @@ -1,22 +1,10 @@ +import { TooltipPosition } from "../../core/placeTooltip"; import exitIntro from "./exitIntro"; import showElement from "./showElement"; import { Tour } from "./tour"; export type ScrollTo = "off" | "element" | "tooltip"; -export type TooltipPosition = - | "floating" - | "top" - | "bottom" - | "left" - | "right" - | "top-right-aligned" - | "top-left-aligned" - | "top-middle-aligned" - | "bottom-right-aligned" - | "bottom-left-aligned" - | "bottom-middle-aligned"; - export type TourStep = { step: number; title: string; diff --git a/src/packages/tour/tour.ts b/src/packages/tour/tour.ts index d8c82314a..70faf8ed1 100644 --- a/src/packages/tour/tour.ts +++ b/src/packages/tour/tour.ts @@ -1,4 +1,4 @@ -import { goToStep, nextStep, TourStep } from "./steps"; +import { nextStep, TourStep } from "./steps"; import { Package } from "../package"; import { introAfterChangeCallback, @@ -12,9 +12,10 @@ import { } from "./callback"; import { getDefaultTourOptions, TourOptions } from "./option"; import { setOptions, setOption } from "../../option"; -import introForElement from "src/packages/tour/introForElement"; +import introForElement from "./introForElement"; import exitIntro from "./exitIntro"; -import isFunction from "src/util/isFunction"; +import isFunction from "../../util/isFunction"; +import { getDontShowAgain, setDontShowAgain } from "./dontShowAgain"; export class Tour implements Package { private _steps: TourStep[] = []; @@ -161,7 +162,23 @@ export class Tour implements Package { } isActive(): boolean { - throw new Error("Method not implemented."); + if ( + this.getOption("dontShowAgain") && + getDontShowAgain(this.getOption("dontShowAgainCookie")) + ) { + return false; + } + + return this.getOption("isActive"); + } + + setDontShowAgain(dontShowAgain: boolean) { + setDontShowAgain( + dontShowAgain, + this.getOption("dontShowAgainCookie"), + this.getOption("dontShowAgainCookieDays") + ); + return this; } async render(): Promise { diff --git a/src/core/DOMEvent.ts b/src/util/DOMEvent.ts similarity index 98% rename from src/core/DOMEvent.ts rename to src/util/DOMEvent.ts index eabd4b159..d9638bd27 100644 --- a/src/core/DOMEvent.ts +++ b/src/util/DOMEvent.ts @@ -1,4 +1,4 @@ -import stamp from "../util/stamp"; +import stamp from "./stamp"; /** * DOMEvent Handles all DOM events diff --git a/src/util/queryElement.ts b/src/util/queryElement.ts index 38be93fbc..847ff857a 100644 --- a/src/util/queryElement.ts +++ b/src/util/queryElement.ts @@ -6,6 +6,13 @@ export const queryElement = ( return (container ?? document).querySelector(selector); }; +export const queryElements = ( + selector: string, + container?: HTMLElement +): NodeListOf => { + return (container ?? document).querySelectorAll(selector); +}; + export const queryElementByClassName = ( className: string, container?: HTMLElement @@ -13,6 +20,13 @@ export const queryElementByClassName = ( return queryElement(`.${className}`, container); }; +export const queryElementsByClassName = ( + className: string, + container?: HTMLElement +): NodeListOf => { + return queryElements(`.${className}`, container); +}; + export const getElementByClassName = ( className: string, container?: HTMLElement