ngx-joyride
Version:
[](https://badge.fury.io/js/ngx-joyride) [](https://travis-ci.org/tnicola/ngx-joyride) [ • 72.4 kB
JavaScript
import { Injectable, Inject, PLATFORM_ID, EventEmitter, Directive, ViewContainerRef, Input, Output, RendererFactory2, Component, ViewEncapsulation, Injector, Renderer2, ViewChild, HostListener, ComponentFactoryResolver, ApplicationRef, NgModule } from '@angular/core';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import { ReplaySubject, of, Observable, Subject } from 'rxjs';
import { Router, RouterModule } from '@angular/router';
import { finalize } from 'rxjs/operators';
class JoyrideStep {
constructor() {
this.title = new ReplaySubject();
this.text = new ReplaySubject();
}
}
const DEFAULT_THEME_COLOR = '#3b5560';
const STEP_DEFAULT_POSITION = 'bottom';
const DEFAULT_TIMEOUT_BETWEEN_STEPS = 1;
class ObservableCustomTexts {
}
const DEFAULT_TEXTS = {
prev: of('prev'),
next: of('next'),
done: of('done'),
close: of(null)
};
class JoyrideOptionsService {
constructor() {
this.themeColor = DEFAULT_THEME_COLOR;
this.stepDefaultPosition = STEP_DEFAULT_POSITION;
this.logsEnabled = false;
this.showCounter = true;
this.showPrevButton = true;
this.stepsOrder = [];
}
setOptions(options) {
this.stepsOrder = options.steps;
this.stepDefaultPosition = options.stepDefaultPosition
? options.stepDefaultPosition
: this.stepDefaultPosition;
this.logsEnabled =
typeof options.logsEnabled !== 'undefined'
? options.logsEnabled
: this.logsEnabled;
this.showCounter =
typeof options.showCounter !== 'undefined'
? options.showCounter
: this.showCounter;
this.showPrevButton =
typeof options.showPrevButton !== 'undefined'
? options.showPrevButton
: this.showPrevButton;
this.themeColor = options.themeColor
? options.themeColor
: this.themeColor;
this.firstStep = options.startWith;
this.waitingTime =
typeof options.waitingTime !== 'undefined'
? options.waitingTime
: DEFAULT_TIMEOUT_BETWEEN_STEPS;
typeof options.customTexts !== 'undefined'
? this.setCustomText(options.customTexts)
: this.setCustomText(DEFAULT_TEXTS);
}
getBackdropColor() {
return this.hexToRgb(this.themeColor);
}
getThemeColor() {
return this.themeColor;
}
getStepDefaultPosition() {
return this.stepDefaultPosition;
}
getStepsOrder() {
return this.stepsOrder;
}
getFirstStep() {
return this.firstStep;
}
getWaitingTime() {
return this.waitingTime;
}
areLogsEnabled() {
return this.logsEnabled;
}
isCounterVisible() {
return this.showCounter;
}
isPrevButtonVisible() {
return this.showPrevButton;
}
getCustomTexts() {
return this.customTexts;
}
setCustomText(texts) {
let prev;
let next;
let done;
let close;
prev = texts.prev ? texts.prev : DEFAULT_TEXTS.prev;
next = texts.next ? texts.next : DEFAULT_TEXTS.next;
done = texts.done ? texts.done : DEFAULT_TEXTS.done;
close = texts.close ? texts.close : DEFAULT_TEXTS.close;
this.customTexts = {
prev: this.toObservable(prev),
next: this.toObservable(next),
done: this.toObservable(done),
close: this.toObservable(close)
};
}
toObservable(value) {
return value instanceof Observable ? value : of(value);
}
hexToRgb(hex) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, (m, r, g, b) => {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
: null;
}
}
JoyrideOptionsService.decorators = [
{ type: Injectable }
];
const JOYRIDE = 'ngx-joyride:::';
class LoggerService {
constructor(optionService) {
this.optionService = optionService;
}
debug(message, data = "") {
if (this.optionService.areLogsEnabled()) {
console.debug(JOYRIDE + message, data);
}
}
info(message, data = "") {
if (this.optionService.areLogsEnabled()) {
console.info(JOYRIDE + message, data);
}
}
warn(message, data = "") {
if (this.optionService.areLogsEnabled()) {
console.warn(JOYRIDE + message, data);
}
}
error(message, data = "") {
if (this.optionService.areLogsEnabled()) {
console.error(JOYRIDE + message, data);
}
}
}
LoggerService.decorators = [
{ type: Injectable }
];
LoggerService.ctorParameters = () => [
{ type: JoyrideOptionsService }
];
class JoyrideError extends Error {
constructor(message) {
super(message);
Object.setPrototypeOf(this, JoyrideError.prototype);
}
}
class JoyrideStepDoesNotExist extends Error {
constructor(message) {
super(message);
Object.setPrototypeOf(this, JoyrideStepDoesNotExist.prototype);
}
}
class JoyrideStepOutOfRange extends Error {
constructor(message) {
super(message);
Object.setPrototypeOf(this, JoyrideStepOutOfRange.prototype);
}
}
const ROUTE_SEPARATOR = '@';
class Step {
}
var StepActionType;
(function (StepActionType) {
StepActionType["NEXT"] = "NEXT";
StepActionType["PREV"] = "PREV";
})(StepActionType || (StepActionType = {}));
class JoyrideStepsContainerService {
constructor(stepOptions, logger) {
this.stepOptions = stepOptions;
this.logger = logger;
this.tempSteps = [];
this.currentStepIndex = -2;
this.stepHasBeenModified = new Subject();
}
getFirstStepIndex() {
const firstStep = this.stepOptions.getFirstStep();
const stepIds = this.stepOptions.getStepsOrder();
let index = stepIds.indexOf(firstStep);
if (index < 0) {
index = 0;
if (firstStep !== undefined)
this.logger.warn(`The step ${firstStep} does not exist. Check in your step list if it's present.`);
}
return index;
}
init() {
this.logger.info('Initializing the steps array.');
this.steps = [];
this.currentStepIndex = this.getFirstStepIndex() - 1;
let stepIds = this.stepOptions.getStepsOrder();
stepIds.forEach(stepId => this.steps.push({ id: stepId, step: null }));
}
addStep(stepToAdd) {
let stepExist = this.tempSteps.filter(step => step.name === stepToAdd.name).length > 0;
if (!stepExist) {
this.logger.info(`Adding step ${stepToAdd.name} to the steps list.`);
this.tempSteps.push(stepToAdd);
}
else {
let stepIndexToReplace = this.tempSteps.findIndex(step => step.name === stepToAdd.name);
this.tempSteps[stepIndexToReplace] = stepToAdd;
}
}
get(action) {
if (action === StepActionType.NEXT)
this.currentStepIndex++;
else
this.currentStepIndex--;
if (this.currentStepIndex < 0 || this.currentStepIndex >= this.steps.length)
throw new JoyrideStepOutOfRange('The first or last step of the tour cannot be found!');
const stepName = this.getStepName(this.steps[this.currentStepIndex].id);
const index = this.tempSteps.findIndex(step => step.name === stepName);
let stepFound = this.tempSteps[index];
this.steps[this.currentStepIndex].step = stepFound;
if (stepFound == null) {
this.logger.warn(`Step ${this.steps[this.currentStepIndex].id} not found in the DOM. Check if it's hidden by *ngIf directive.`);
}
return stepFound;
}
getStepRoute(action) {
let stepID;
if (action === StepActionType.NEXT) {
stepID = this.steps[this.currentStepIndex + 1] ? this.steps[this.currentStepIndex + 1].id : null;
}
else {
stepID = this.steps[this.currentStepIndex - 1] ? this.steps[this.currentStepIndex - 1].id : null;
}
let stepRoute = stepID && stepID.includes(ROUTE_SEPARATOR) ? stepID.split(ROUTE_SEPARATOR)[1] : '';
return stepRoute;
}
updatePosition(stepName, position) {
let index = this.getStepIndex(stepName);
if (this.steps[index].step) {
this.steps[index].step.position = position;
this.stepHasBeenModified.next(this.steps[index].step);
}
else {
this.logger.warn(`Trying to modify the position of ${stepName} to ${position}. Step not found!Is this step located in a different route?`);
}
}
getStepNumber(stepName) {
return this.getStepIndex(stepName) + 1;
}
getStepsCount() {
let stepsOrder = this.stepOptions.getStepsOrder();
return stepsOrder.length;
}
getStepIndex(stepName) {
const index = this.steps
.map(step => (step.id.includes(ROUTE_SEPARATOR) ? step.id.split(ROUTE_SEPARATOR)[0] : step.id))
.findIndex(name => stepName === name);
if (index === -1)
throw new JoyrideError(`The step with name: ${stepName} does not exist in the step list.`);
return index;
}
getStepName(stepID) {
let stepName = stepID && stepID.includes(ROUTE_SEPARATOR) ? stepID.split(ROUTE_SEPARATOR)[0] : stepID;
return stepName;
}
}
JoyrideStepsContainerService.decorators = [
{ type: Injectable }
];
JoyrideStepsContainerService.ctorParameters = () => [
{ type: JoyrideOptionsService },
{ type: LoggerService }
];
class DomRefService {
constructor(platformId) {
this.platformId = platformId;
this.fakeDocument = { body: {}, documentElement: {} };
this.fakeWindow = { document: this.fakeDocument, navigator: {} };
}
getNativeWindow() {
if (isPlatformBrowser(this.platformId))
return window;
else
return this.fakeWindow;
}
getNativeDocument() {
if (isPlatformBrowser(this.platformId))
return document;
else
return this.fakeDocument;
}
}
DomRefService.decorators = [
{ type: Injectable }
];
DomRefService.ctorParameters = () => [
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
];
class TemplatesService {
setPrevButton(template) {
this._prevButton = template;
}
getPrevButton() {
return this._prevButton;
}
setNextButton(template) {
this._nextButton = template;
}
getNextButton() {
return this._nextButton;
}
setDoneButton(template) {
this._doneButton = template;
}
getDoneButton() {
return this._doneButton;
}
setCounter(template) {
this._counter = template;
}
getCounter() {
return this._counter;
}
}
TemplatesService.decorators = [
{ type: Injectable }
];
const NO_POSITION = 'NO_POSITION';
class JoyrideDirective {
constructor(joyrideStepsContainer, viewContainerRef, domService, router, templateService, platformId) {
this.joyrideStepsContainer = joyrideStepsContainer;
this.viewContainerRef = viewContainerRef;
this.domService = domService;
this.router = router;
this.templateService = templateService;
this.platformId = platformId;
this.stepPosition = NO_POSITION;
this.prev = new EventEmitter();
this.next = new EventEmitter();
this.done = new EventEmitter();
this.subscriptions = [];
this.windowRef = this.domService.getNativeWindow();
this.step = new JoyrideStep();
}
ngAfterViewInit() {
if (!isPlatformBrowser(this.platformId))
return;
if (this.prevTemplate)
this.templateService.setPrevButton(this.prevTemplate);
if (this.nextTemplate)
this.templateService.setNextButton(this.nextTemplate);
if (this.doneTemplate)
this.templateService.setDoneButton(this.doneTemplate);
if (this.counterTemplate)
this.templateService.setCounter(this.counterTemplate);
this.step.position = this.stepPosition;
this.step.targetViewContainer = this.viewContainerRef;
this.setAsyncFields(this.step);
this.step.stepContent = this.stepContent;
this.step.stepContentParams = this.stepContentParams;
this.step.nextClicked = this.next;
this.step.prevCliked = this.prev;
this.step.tourDone = this.done;
if (!this.name)
throw new JoyrideError("All the steps should have the 'joyrideStep' property set with a custom name.");
this.step.name = this.name;
this.step.route = this.router.url.substr(0, 1) === '/' ? this.router.url.substr(1) : this.router.url;
this.step.transformCssStyle = this.windowRef.getComputedStyle(this.viewContainerRef.element.nativeElement).transform;
this.step.isElementOrAncestorFixed =
this.isElementFixed(this.viewContainerRef.element) ||
this.isAncestorsFixed(this.viewContainerRef.element.nativeElement.parentElement);
this.joyrideStepsContainer.addStep(this.step);
}
ngOnChanges(changes) {
if (changes['title'] || changes['text']) {
this.setAsyncFields(this.step);
}
}
isElementFixed(element) {
return this.windowRef.getComputedStyle(element.nativeElement).position === 'fixed';
}
setAsyncFields(step) {
if (this.title instanceof Observable) {
this.subscriptions.push(this.title.subscribe(title => {
step.title.next(title);
}));
}
else {
step.title.next(this.title);
}
if (this.text instanceof Observable) {
this.subscriptions.push(this.text.subscribe(text => {
step.text.next(text);
}));
}
else {
step.text.next(this.text);
}
}
isAncestorsFixed(nativeElement) {
if (!nativeElement || !nativeElement.parentElement)
return false;
let isElementFixed = this.windowRef.getComputedStyle(nativeElement.parentElement).position === 'fixed';
if (nativeElement.nodeName === 'BODY') {
return isElementFixed;
}
if (isElementFixed)
return true;
else
return this.isAncestorsFixed(nativeElement.parentElement);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
}
JoyrideDirective.decorators = [
{ type: Directive, args: [{
selector: 'joyrideStep, [joyrideStep]'
},] }
];
JoyrideDirective.ctorParameters = () => [
{ type: JoyrideStepsContainerService },
{ type: ViewContainerRef },
{ type: DomRefService },
{ type: Router },
{ type: TemplatesService },
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
];
JoyrideDirective.propDecorators = {
name: [{ type: Input, args: ['joyrideStep',] }],
nextStep: [{ type: Input }],
title: [{ type: Input }],
text: [{ type: Input }],
stepPosition: [{ type: Input }],
stepContent: [{ type: Input }],
stepContentParams: [{ type: Input }],
prevTemplate: [{ type: Input }],
nextTemplate: [{ type: Input }],
doneTemplate: [{ type: Input }],
counterTemplate: [{ type: Input }],
prev: [{ type: Output }],
next: [{ type: Output }],
done: [{ type: Output }]
};
class DocumentService {
constructor(DOMService, platformId) {
this.DOMService = DOMService;
if (!isPlatformBrowser(platformId)) {
return;
}
this.setDocumentHeight();
var doc = DOMService.getNativeDocument();
if (doc && !doc.elementsFromPoint) {
// IE 11 - Edge browsers
doc.elementsFromPoint = this.elementsFromPoint.bind(this);
}
}
getElementFixedTop(elementRef) {
return elementRef.nativeElement.getBoundingClientRect().top;
}
getElementFixedLeft(elementRef) {
return elementRef.nativeElement.getBoundingClientRect().left;
}
getElementAbsoluteTop(elementRef) {
const scrollOffsets = this.getScrollOffsets();
return (elementRef.nativeElement.getBoundingClientRect().top +
scrollOffsets.y);
}
getElementAbsoluteLeft(elementRef) {
const scrollOffsets = this.getScrollOffsets();
return (elementRef.nativeElement.getBoundingClientRect().left +
scrollOffsets.x);
}
setDocumentHeight() {
this.documentHeight = this.calculateDocumentHeight();
}
getDocumentHeight() {
return this.documentHeight;
}
isParentScrollable(elementRef) {
return (this.getFirstScrollableParent(elementRef.nativeElement) !==
this.DOMService.getNativeDocument().body);
}
isElementBeyondOthers(elementRef, isElementFixed, keywordToDiscard) {
const x1 = isElementFixed
? this.getElementFixedLeft(elementRef)
: this.getElementAbsoluteLeft(elementRef);
const y1 = isElementFixed
? this.getElementFixedTop(elementRef)
: this.getElementAbsoluteTop(elementRef);
const x2 = x1 + elementRef.nativeElement.getBoundingClientRect().width - 1;
const y2 = y1 + elementRef.nativeElement.getBoundingClientRect().height - 1;
const elements1 = this.DOMService.getNativeDocument().elementsFromPoint(x1, y1);
const elements2 = this.DOMService.getNativeDocument().elementsFromPoint(x2, y2);
if (elements1.length === 0 && elements2.length === 0)
return 1;
if (this.getFirstElementWithoutKeyword(elements1, keywordToDiscard) !==
elementRef.nativeElement ||
this.getFirstElementWithoutKeyword(elements2, keywordToDiscard) !==
elementRef.nativeElement) {
return 2;
}
return 3;
}
scrollIntoView(elementRef, isElementFixed) {
const firstScrollableParent = this.getFirstScrollableParent(elementRef.nativeElement);
const top = isElementFixed
? this.getElementFixedTop(elementRef)
: this.getElementAbsoluteTop(elementRef);
if (firstScrollableParent !== this.DOMService.getNativeDocument().body) {
if (firstScrollableParent.scrollTo) {
firstScrollableParent.scrollTo(0, top - 150);
}
else {
// IE 11 - Edge browsers
firstScrollableParent.scrollTop = top - 150;
}
}
else {
this.DOMService.getNativeWindow().scrollTo(0, top - 150);
}
}
scrollToTheTop(elementRef) {
const firstScrollableParent = this.getFirstScrollableParent(elementRef.nativeElement);
if (firstScrollableParent !== this.DOMService.getNativeDocument().body) {
if (firstScrollableParent.scrollTo) {
firstScrollableParent.scrollTo(0, 0);
}
else {
// IE 11 - Edge browsers
firstScrollableParent.scrollTop = 0;
}
}
else {
this.DOMService.getNativeWindow().scrollTo(0, 0);
}
}
scrollToTheBottom(elementRef) {
const firstScrollableParent = this.getFirstScrollableParent(elementRef.nativeElement);
if (firstScrollableParent !== this.DOMService.getNativeDocument().body) {
if (firstScrollableParent.scrollTo) {
firstScrollableParent.scrollTo(0, this.DOMService.getNativeDocument().body.scrollHeight);
}
else {
// IE 11 - Edge browsers
firstScrollableParent.scrollTop =
firstScrollableParent.scrollHeight -
firstScrollableParent.clientHeight;
}
}
else {
this.DOMService.getNativeWindow().scrollTo(0, this.DOMService.getNativeDocument().body.scrollHeight);
}
}
getFirstScrollableParent(node) {
const regex = /(auto|scroll|overlay)/;
const style = (node, prop) => this.DOMService.getNativeWindow()
.getComputedStyle(node, null)
.getPropertyValue(prop);
const scroll = (node) => regex.test(style(node, 'overflow') +
style(node, 'overflow-y') +
style(node, 'overflow-x'));
const scrollparent = (node) => {
return !node || node === this.DOMService.getNativeDocument().body
? this.DOMService.getNativeDocument().body
: scroll(node)
? node
: scrollparent(node.parentNode);
};
return scrollparent(node);
}
calculateDocumentHeight() {
const documentRef = this.DOMService.getNativeDocument();
return Math.max(documentRef.body.scrollHeight, documentRef.documentElement.scrollHeight, documentRef.body.offsetHeight, documentRef.documentElement.offsetHeight, documentRef.body.clientHeight, documentRef.documentElement.clientHeight);
}
getScrollOffsets() {
const winReference = this.DOMService.getNativeWindow();
const docReference = this.DOMService.getNativeDocument();
// This works for all browsers except IE versions 8 and before
if (winReference.pageXOffset != null)
return { x: winReference.pageXOffset, y: winReference.pageYOffset };
// For IE (or any browser) in Standards mode
if (docReference.compatMode == 'CSS1Compat')
return {
x: docReference.documentElement.scrollLeft,
y: docReference.documentElement.scrollTop
};
// For browsers in Quirks mode
return {
x: docReference.body.scrollLeft,
y: docReference.body.scrollTop
};
}
elementsFromPoint(x, y) {
var parents = [];
var parent = void 0;
do {
const elem = this.DOMService.getNativeDocument().elementFromPoint(x, y);
if (elem && parent !== elem) {
parent = elem;
parents.push(parent);
parent.style.pointerEvents = 'none';
}
else {
parent = false;
}
} while (parent);
parents.forEach(function (parent) {
return (parent.style.pointerEvents = 'all');
});
return parents;
}
getFirstElementWithoutKeyword(elements, keyword) {
while (elements[0] &&
elements[0].classList.toString().includes(keyword)) {
elements.shift();
}
return elements[0];
}
}
DocumentService.decorators = [
{ type: Injectable }
];
DocumentService.ctorParameters = () => [
{ type: DomRefService },
{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
];
class JoyrideBackdropService {
constructor(documentService, optionsService, rendererFactory) {
this.documentService = documentService;
this.optionsService = optionsService;
this.rendererFactory = rendererFactory;
this.lastXScroll = 0;
this.lastYScroll = 0;
this.setRenderer();
}
setRenderer() {
this.renderer = this.rendererFactory.createRenderer(null, null);
}
draw(step) {
this.elementRef = step.targetViewContainer;
this.targetAbsoluteTop = this.getTargetTotalTop(step);
this.targetAbsoluteLeft = this.getTargetTotalLeft(step);
this.currentBackdropContainer = this.renderer.createElement('div');
this.renderer.addClass(this.currentBackdropContainer, 'backdrop-container');
this.renderer.setStyle(this.currentBackdropContainer, 'position', 'fixed');
this.renderer.setStyle(this.currentBackdropContainer, 'top', '0px');
this.renderer.setStyle(this.currentBackdropContainer, 'left', '0px');
this.renderer.setStyle(this.currentBackdropContainer, 'width', '100%');
this.renderer.setStyle(this.currentBackdropContainer, 'height', '100%');
this.renderer.setStyle(this.currentBackdropContainer, 'z-index', '1000');
this.renderer.setAttribute(this.currentBackdropContainer, 'id', 'backdrop-' + step.name);
this.backdropContent = this.renderer.createElement('div');
this.renderer.addClass(this.backdropContent, 'backdrop-content');
this.renderer.setStyle(this.backdropContent, 'position', 'relative');
this.renderer.setStyle(this.backdropContent, 'height', '100%');
this.renderer.setStyle(this.backdropContent, 'display', 'flex');
this.renderer.setStyle(this.backdropContent, 'flex-direction', 'column');
this.renderer.appendChild(this.currentBackdropContainer, this.backdropContent);
this.backdropTop = this.renderer.createElement('div');
this.renderer.addClass(this.backdropTop, 'joyride-backdrop');
this.renderer.addClass(this.backdropTop, 'backdrop-top');
this.renderer.setStyle(this.backdropTop, 'width', '100%');
this.renderer.setStyle(this.backdropTop, 'height', this.targetAbsoluteTop - this.lastYScroll + 'px');
this.renderer.setStyle(this.backdropTop, 'flex-shrink', '0');
this.renderer.setStyle(this.backdropTop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);
this.renderer.appendChild(this.backdropContent, this.backdropTop);
this.backdropMiddleContainer = this.renderer.createElement('div');
this.renderer.addClass(this.backdropMiddleContainer, 'backdrop-middle-container');
this.renderer.setStyle(this.backdropMiddleContainer, 'height', this.elementRef.element.nativeElement.offsetHeight + 'px');
this.renderer.setStyle(this.backdropMiddleContainer, 'width', '100%');
this.renderer.setStyle(this.backdropMiddleContainer, 'flex-shrink', '0');
this.renderer.appendChild(this.backdropContent, this.backdropMiddleContainer);
this.backdropMiddleContent = this.renderer.createElement('div');
this.renderer.addClass(this.backdropMiddleContent, 'backdrop-middle-content');
this.renderer.setStyle(this.backdropMiddleContent, 'display', 'flex');
this.renderer.setStyle(this.backdropMiddleContent, 'width', '100%');
this.renderer.setStyle(this.backdropMiddleContent, 'height', '100%');
this.renderer.appendChild(this.backdropMiddleContainer, this.backdropMiddleContent);
this.leftBackdrop = this.renderer.createElement('div');
this.renderer.addClass(this.leftBackdrop, 'joyride-backdrop');
this.renderer.addClass(this.leftBackdrop, 'backdrop-left');
this.renderer.setStyle(this.leftBackdrop, 'flex-shrink', '0');
this.renderer.setStyle(this.leftBackdrop, 'width', this.targetAbsoluteLeft - this.lastXScroll + 'px');
this.renderer.setStyle(this.leftBackdrop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);
this.renderer.appendChild(this.backdropMiddleContent, this.leftBackdrop);
this.targetBackdrop = this.renderer.createElement('div');
this.renderer.addClass(this.targetBackdrop, 'backdrop-target');
this.renderer.setStyle(this.targetBackdrop, 'flex-shrink', '0');
this.renderer.setStyle(this.targetBackdrop, 'width', this.elementRef.element.nativeElement.offsetWidth + 'px');
this.renderer.appendChild(this.backdropMiddleContent, this.targetBackdrop);
this.rightBackdrop = this.renderer.createElement('div');
this.renderer.addClass(this.rightBackdrop, 'joyride-backdrop');
this.renderer.addClass(this.rightBackdrop, 'backdrop-right');
this.renderer.setStyle(this.rightBackdrop, 'width', '100%');
this.renderer.setStyle(this.rightBackdrop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);
this.renderer.appendChild(this.backdropMiddleContent, this.rightBackdrop);
this.backdropBottom = this.renderer.createElement('div');
this.renderer.addClass(this.backdropBottom, 'joyride-backdrop');
this.renderer.addClass(this.backdropBottom, 'backdrop-bottom');
this.renderer.setStyle(this.backdropBottom, 'width', '100%');
this.renderer.setStyle(this.backdropBottom, 'height', '100%');
this.renderer.setStyle(this.backdropBottom, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);
this.renderer.appendChild(this.backdropContent, this.backdropBottom);
this.removeLastBackdrop();
this.drawCurrentBackdrop();
this.lastBackdropContainer = this.currentBackdropContainer;
}
remove() {
this.removeLastBackdrop();
}
redrawTarget(step) {
this.targetAbsoluteLeft = this.getTargetTotalLeft(step);
this.targetAbsoluteTop = this.getTargetTotalTop(step);
this.handleVerticalScroll(step);
this.handleHorizontalScroll(step);
}
getTargetTotalTop(step) {
let targetVC = step.targetViewContainer;
return step.isElementOrAncestorFixed
? this.documentService.getElementFixedTop(targetVC.element)
: this.documentService.getElementAbsoluteTop(targetVC.element);
}
getTargetTotalLeft(step) {
let targetVC = step.targetViewContainer;
return step.isElementOrAncestorFixed
? this.documentService.getElementFixedLeft(targetVC.element)
: this.documentService.getElementAbsoluteLeft(targetVC.element);
}
redraw(step, scroll) {
if (this.lastYScroll !== scroll.scrollY) {
this.lastYScroll = scroll.scrollY;
if (this.elementRef) {
this.handleVerticalScroll(step);
}
}
if (this.lastXScroll !== scroll.scrollX) {
this.lastXScroll = scroll.scrollX;
if (this.elementRef) {
this.handleHorizontalScroll(step);
}
}
}
handleHorizontalScroll(step) {
let newBackdropLeftWidth = step.isElementOrAncestorFixed ? this.targetAbsoluteLeft : this.targetAbsoluteLeft - this.lastXScroll;
if (newBackdropLeftWidth >= 0) {
this.renderer.setStyle(this.leftBackdrop, 'width', newBackdropLeftWidth + 'px');
this.renderer.setStyle(this.targetBackdrop, 'width', this.elementRef.element.nativeElement.offsetWidth + 'px');
}
else {
this.handleTargetPartialWidth(newBackdropLeftWidth);
}
}
handleTargetPartialWidth(newBackdropLeftWidth) {
this.renderer.setStyle(this.leftBackdrop, 'width', 0 + 'px');
let visibleTargetWidth = this.elementRef.element.nativeElement.offsetWidth + newBackdropLeftWidth;
if (visibleTargetWidth >= 0) {
this.renderer.setStyle(this.targetBackdrop, 'width', visibleTargetWidth + 'px');
}
else {
this.renderer.setStyle(this.targetBackdrop, 'width', 0 + 'px');
}
}
handleVerticalScroll(step) {
let newBackdropTopHeight = step.isElementOrAncestorFixed ? this.targetAbsoluteTop : this.targetAbsoluteTop - this.lastYScroll;
if (newBackdropTopHeight >= 0) {
this.renderer.setStyle(this.backdropTop, 'height', newBackdropTopHeight + 'px');
this.renderer.setStyle(this.backdropMiddleContainer, 'height', this.elementRef.element.nativeElement.offsetHeight + 'px');
}
else {
this.handleTargetPartialHeight(newBackdropTopHeight);
}
}
handleTargetPartialHeight(newBackdropTopHeight) {
this.renderer.setStyle(this.backdropTop, 'height', 0 + 'px');
let visibleTargetHeight = this.elementRef.element.nativeElement.offsetHeight + newBackdropTopHeight;
if (visibleTargetHeight >= 0) {
this.renderer.setStyle(this.backdropMiddleContainer, 'height', visibleTargetHeight + 'px');
}
else {
this.renderer.setStyle(this.backdropMiddleContainer, 'height', 0 + 'px');
}
}
removeLastBackdrop() {
if (this.lastBackdropContainer) {
this.renderer.removeChild(document.body, this.lastBackdropContainer);
this.lastBackdropContainer = undefined;
}
}
drawCurrentBackdrop() {
this.renderer.appendChild(document.body, this.currentBackdropContainer);
}
}
JoyrideBackdropService.decorators = [
{ type: Injectable }
];
JoyrideBackdropService.ctorParameters = () => [
{ type: DocumentService },
{ type: JoyrideOptionsService },
{ type: RendererFactory2 }
];
class Scroll {
}
class EventListenerService {
constructor(rendererFactory, DOMService) {
this.rendererFactory = rendererFactory;
this.DOMService = DOMService;
this.scrollEvent = new Subject();
this.resizeEvent = new Subject();
this.renderer = rendererFactory.createRenderer(null, null);
}
startListeningScrollEvents() {
this.scrollUnlisten = this.renderer.listen('document', 'scroll', evt => {
this.scrollEvent.next({
scrollX: this.DOMService.getNativeWindow().pageXOffset,
scrollY: this.DOMService.getNativeWindow().pageYOffset
});
});
}
startListeningResizeEvents() {
this.resizeUnlisten = this.renderer.listen('window', 'resize', evt => {
this.resizeEvent.next(evt);
});
}
stopListeningScrollEvents() {
this.scrollUnlisten();
}
stopListeningResizeEvents() {
this.resizeUnlisten();
}
}
EventListenerService.decorators = [
{ type: Injectable }
];
EventListenerService.ctorParameters = () => [
{ type: RendererFactory2 },
{ type: DomRefService }
];
const STEP_MIN_WIDTH = 200;
const STEP_MAX_WIDTH = 400;
const CUSTOM_STEP_MAX_WIDTH_VW = 90;
const STEP_HEIGHT = 200;
const ASPECT_RATIO = 1.212;
const DEFAULT_DISTANCE_FROM_MARGIN_TOP = 2;
const DEFAULT_DISTANCE_FROM_MARGIN_LEFT = 2;
const DEFAULT_DISTANCE_FROM_MARGIN_BOTTOM = 5;
const DEFAULT_DISTANCE_FROM_MARGIN_RIGHT = 5;
var KEY_CODE;
(function (KEY_CODE) {
KEY_CODE[KEY_CODE["RIGHT_ARROW"] = 39] = "RIGHT_ARROW";
KEY_CODE[KEY_CODE["LEFT_ARROW"] = 37] = "LEFT_ARROW";
KEY_CODE[KEY_CODE["ESCAPE_KEY"] = 27] = "ESCAPE_KEY";
})(KEY_CODE || (KEY_CODE = {}));
class JoyrideStepComponent {
constructor(injector, stepsContainerService, eventListenerService, documentService, renderer, logger, optionsService, templateService) {
this.injector = injector;
this.stepsContainerService = stepsContainerService;
this.eventListenerService = eventListenerService;
this.documentService = documentService;
this.renderer = renderer;
this.logger = logger;
this.optionsService = optionsService;
this.templateService = templateService;
this.stepWidth = STEP_MIN_WIDTH;
this.stepHeight = STEP_HEIGHT;
this.showArrow = true;
this.arrowSize = ARROW_SIZE;
this.subscriptions = [];
}
ngOnInit() {
// Need to Inject here otherwise you will obtain a circular dependency
this.joyrideStepService = this.injector.get(JoyrideStepService);
this.documentHeight = this.documentService.getDocumentHeight();
this.subscriptions.push(this.subscribeToResizeEvents());
this.title = this.step.title.asObservable();
this.text = this.step.text.asObservable();
this.setCustomTemplates();
this.setCustomTexts();
this.counter = this.getCounter();
this.isCounterVisible = this.optionsService.isCounterVisible();
this.isPrevButtonVisible = this.optionsService.isPrevButtonVisible();
this.themeColor = this.optionsService.getThemeColor();
if (this.text)
this.text.subscribe(val => this.checkRedraw(val));
if (this.title)
this.title.subscribe(val => this.checkRedraw(val));
}
ngAfterViewInit() {
if (this.isCustomized()) {
this.renderer.setStyle(this.stepContainer.nativeElement, 'max-width', CUSTOM_STEP_MAX_WIDTH_VW + 'vw');
this.updateStepDimensions();
}
else {
this.renderer.setStyle(this.stepContainer.nativeElement, 'max-width', STEP_MAX_WIDTH + 'px');
let dimensions = this.getDimensionsByAspectRatio(this.stepContainer.nativeElement.clientWidth, this.stepContainer.nativeElement.clientHeight, ASPECT_RATIO);
dimensions = this.adjustDimensions(dimensions.width, dimensions.height);
this.stepWidth = dimensions.width;
this.stepHeight = dimensions.height;
this.renderer.setStyle(this.stepContainer.nativeElement, 'width', this.stepWidth + 'px');
this.renderer.setStyle(this.stepContainer.nativeElement, 'height', this.stepHeight + 'px');
}
this.drawStep();
}
checkRedraw(val) {
if (val != null) {
// Need to wait that the change is rendered before redrawing
setTimeout(() => {
this.redrawStep();
}, 2);
}
}
isCustomized() {
return (this.step.stepContent ||
this.templateService.getCounter() ||
this.templateService.getPrevButton() ||
this.templateService.getNextButton() ||
this.templateService.getDoneButton());
}
setCustomTexts() {
const customeTexts = this.optionsService.getCustomTexts();
this.prevText = customeTexts.prev;
this.nextText = customeTexts.next;
this.doneText = customeTexts.done;
}
drawStep() {
let position = this.step.isElementOrAncestorFixed
? 'fixed'
: 'absolute';
this.renderer.setStyle(this.stepHolder.nativeElement, 'position', position);
this.renderer.setStyle(this.stepHolder.nativeElement, 'transform', this.step.transformCssStyle);
this.targetWidth = this.step.targetViewContainer.element.nativeElement.getBoundingClientRect().width;
this.targetHeight = this.step.targetViewContainer.element.nativeElement.getBoundingClientRect().height;
this.targetAbsoluteLeft =
position === 'fixed'
? this.documentService.getElementFixedLeft(this.step.targetViewContainer.element)
: this.documentService.getElementAbsoluteLeft(this.step.targetViewContainer.element);
this.targetAbsoluteTop =
position === 'fixed'
? this.documentService.getElementFixedTop(this.step.targetViewContainer.element)
: this.documentService.getElementAbsoluteTop(this.step.targetViewContainer.element);
this.setStepStyle();
}
getCounter() {
let stepPosition = this.stepsContainerService.getStepNumber(this.step.name);
let numberOfSteps = this.stepsContainerService.getStepsCount();
this.counterData = { step: stepPosition, total: numberOfSteps };
return stepPosition + '/' + numberOfSteps;
}
setCustomTemplates() {
this.customContent = this.step.stepContent;
this.ctx = this.step.stepContentParams;
this.customPrevButton = this.templateService.getPrevButton();
this.customNextButton = this.templateService.getNextButton();
this.customDoneButton = this.templateService.getDoneButton();
this.customCounter = this.templateService.getCounter();
}
keyEvent(event) {
console.log(event);
if (event.keyCode === KEY_CODE.RIGHT_ARROW) {
if (this.isLastStep()) {
this.close();
}
else {
this.next();
}
}
else if (event.keyCode === KEY_CODE.LEFT_ARROW) {
this.prev();
}
else if (event.keyCode === KEY_CODE.ESCAPE_KEY) {
this.close();
}
}
prev() {
this.joyrideStepService.prev();
}
next() {
this.joyrideStepService.next();
}
close() {
this.joyrideStepService.close();
}
isFirstStep() {
return this.stepsContainerService.getStepNumber(this.step.name) === 1;
}
isLastStep() {
return (this.stepsContainerService.getStepNumber(this.step.name) ===
this.stepsContainerService.getStepsCount());
}
setStepStyle() {
switch (this.step.position) {
case 'top': {
this.setStyleTop();
break;
}
case 'bottom': {
this.setStyleBottom();
break;
}
case 'right': {
this.setStyleRight();
break;
}
case 'left': {
this.setStyleLeft();
break;
}
case 'center': {
this.setStyleCenter();
break;
}
default: {
this.setStyleBottom();
}
}
}
setStyleTop() {
this.stepsContainerService.updatePosition(this.step.name, 'top');
this.topPosition =
this.targetAbsoluteTop - DISTANCE_FROM_TARGET - this.stepHeight;
this.stepAbsoluteTop =
this.targetAbsoluteTop - DISTANCE_FROM_TARGET - this.stepHeight;
this.arrowTopPosition = this.stepHeight;
this.leftPosition =
this.targetWidth / 2 - this.stepWidth / 2 + this.targetAbsoluteLeft;
this.stepAbsoluteLeft =
this.targetWidth / 2 - this.stepWidth / 2 + this.targetAbsoluteLeft;
this.arrowLeftPosition = this.stepWidth / 2 - this.arrowSize;
this.adjustLeftPosition();
this.adjustRightPosition();
this.arrowPosition = 'bottom';
this.autofixTopPosition();
}
setStyleRight() {
this.stepsContainerService.updatePosition(this.step.name, 'right');
this.topPosition =
this.targetAbsoluteTop +
this.targetHeight / 2 -
this.stepHeight / 2;
this.stepAbsoluteTop =
this.targetAbsoluteTop +
this.targetHeight / 2 -
this.stepHeight / 2;
this.arrowTopPosition = this.stepHeight / 2 - this.arrowSize;
this.leftPosition =
this.targetAbsoluteLeft + this.targetWidth + DISTANCE_FROM_TARGET;
this.stepAbsoluteLeft =
this.targetAbsoluteLeft + this.targetWidth + DISTANCE_FROM_TARGET;
this.arrowLeftPosition = -this.arrowSize;
this.adjustTopPosition();
this.adjustBottomPosition();
this.arrowPosition = 'left';
this.autofixRightPosition();
}
setStyleBottom() {
this.stepsContainerService.updatePosition(this.step.name, 'bottom');
this.topPosition =
this.targetAbsoluteTop + this.targetHeight + DISTANCE_FROM_TARGET;
this.stepAbsoluteTop =
this.targetAbsoluteTop + this.targetHeight + DISTANCE_FROM_TARGET;
this.arrowTopPosition = -this.arrowSize;
this.arrowLeftPosition = this.stepWidth / 2 - this.arrowSize;
this.leftPosition =
this.targetWidth / 2 - this.stepWidth / 2 + this.targetAbsoluteLeft;
this.stepAbsoluteLeft =
this.targetWidth / 2 - this.stepWidth / 2 + this.targetAbsoluteLeft;
this.adjustLeftPosition();
this.adjustRightPosition();
this.arrowPosition = 'top';
this.autofixBottomPosition();
}
setStyleLeft() {
this.stepsContainerService.updatePosition(this.step.name, 'left');
this.topPosition =
this.targetAbsoluteTop +
this.targetHeight / 2 -
this.stepHeight / 2;
this.stepAbsoluteTop =
this.targetAbsoluteTop +
this.targetHeight / 2 -
this.stepHeight / 2;
this.arrowTopPosition = this.stepHeight / 2 - this.arrowSize;
this.leftPosition =
this.targetAbsoluteLeft - this.stepWidth - DISTANCE_FROM_TARGET;
this.stepAbsoluteLeft =
this.targetAbsoluteLeft - this.stepWidth - DISTANCE_FROM_TARGET;
this.arrowLeftPosition = this.stepWidth;
this.adjustTopPosition();
this.adjustBottomPosition();
this.arrowPosition = 'right';
this.autofixLeftPosition();
}
setStyleCenter() {
this.renderer.setStyle(this.stepHolder.nativeElement, 'position', 'fixed');
this.renderer.setStyle(this.stepHolder.nativeElement, 'top', '50%');
this.renderer.setStyle(this.stepHolder.nativeElement, 'left', '50%');
this.updateStepDimensions();
this.renderer.setStyle(this.stepHolder.nativeElement, 'transform', `translate(-${this.stepWidth / 2}px, -${this.stepHeight / 2}px)`);
this.showArrow = false;
}
adjustLeftPosition() {
if (this.leftPosition < 0) {
this.arrowLeftPosition =
this.arrowLeftPosition +
this.leftPosition -
DEFAULT_DISTANCE_FROM_MARGIN_LEFT;
this.leftPosition = DEFAULT_DISTANCE_FROM_MARGIN_LEFT;
}
}
adjustRightPosition() {
let currentWindowWidth = document.body.clientWidth;
if (this.stepAbsoluteLeft + this.stepWidth > currentWindowWidth) {
let newLeftPos = this.leftPosition -
(this.stepAbsoluteLeft +
this.stepWidth +
DEFAULT_DISTANCE_FROM_MARGIN_RIGHT -
currentWindowWidth);
let deltaLeftPosition = newLeftPos - this.leftPosition;
this.leftPosition = newLeftPos;
this.arrowLeftPosition = this.arrowLeftPosition - deltaLeftPosition;
}
}
adjustTopPosition() {
if (this.stepAbsoluteTop < 0) {
this.arrowTopPosition =
this.arrowTopPosition +
this.topPosition -
DEFAULT_DISTANCE_FROM_MARGIN_TOP;
this.topPosition = DEFAULT_DISTANCE_FROM_MARGIN_TOP;
}
}
adjustBottomPosition() {
if (this.stepAbsoluteTop + this.stepHeight > this.documentHeight) {
let newTopPos = this.topPosition -
(this.stepAbsoluteTop +
this.stepHeight +
DEFAULT_DISTANCE_FROM_MARGIN_BOTTOM -
this.documentHeight);
let deltaTopPosition = newTopPos - this.topPosition;
this.topPosition = newTopPos;
this.arrowTopPosition = this.arrowTopPosition - deltaTopPosition;
}
}
autofixTopPosition() {
if (this.positionAlreadyFixed) {
this.logger.warn('No step positions found for this step. The step will be centered.');
}
else if (this.targetAbsoluteTop - this.stepHeight - this.arrowSize <
0) {
this.positionAlreadyFixed = true;
this.setStyleRight();
}
}
autofixRightPosition() {
if (this.targetAbsoluteLeft +
this.targetWidth +
this.stepWidth +
this.arrowSize >
document.body.clientWidth) {
this.setStyleBottom();
}
}
autofixBottomPosition() {
if (this.targetAbsoluteTop +
this.stepHeight +
this.arrowSize +
this.targetHeight >
this.documentHeight) {
this.setStyleLeft();
}
}
autofixLeftPosition() {
if (this.targetAbsoluteLeft - this.stepWidth - this.arrowSize < 0) {
this.setStyleTop();
}
}
subscribeToResizeEvents() {
return this.eventListenerService.resizeEvent.subscribe(() => {
this.redrawStep();
});
}
redrawStep() {
this.updateStepDimensions();
this.drawStep();
}
getDimensionsByAspectRatio(width, height, aspectRatio) {
let calcHeight = (width + height) / (1 + aspectRatio);
let calcWidth = calcHeight * aspectRatio;
return {
width: calcWidth,
height: calcHeight
};
}
adjustDimensions(width, height) {
let area = width * height;
let newWidth = width;
let newHeight = height;
if (width > STEP_MAX_WIDTH) {
newWidth = STEP_MAX_WIDTH;
newHeight = area / newWidth;