UNPKG

ngx-joyride

Version:

An Angular Tour (Joyride) library built entirely in Angular, without using any heavy external dependencies like Bootstrap or JQuery. From now on you can easily guide your users through your site showing them all the sections and features.

1 lines 135 kB
{"version":3,"file":"ngx-joyride.mjs","sources":["../../../projects/ngx-joyride/src/lib/models/joyride-step.class.ts","../../../projects/ngx-joyride/src/lib/models/joyride-error.class.ts","../../../projects/ngx-joyride/src/lib/services/joyride-options.service.ts","../../../projects/ngx-joyride/src/lib/services/logger.service.ts","../../../projects/ngx-joyride/src/lib/services/joyride-steps-container.service.ts","../../../projects/ngx-joyride/src/lib/services/dom.service.ts","../../../projects/ngx-joyride/src/lib/services/templates.service.ts","../../../projects/ngx-joyride/src/lib/directives/joyride.directive.ts","../../../projects/ngx-joyride/src/lib/models/joyride-step-info.class.ts","../../../projects/ngx-joyride/src/lib/services/document.service.ts","../../../projects/ngx-joyride/src/lib/services/joyride-backdrop.service.ts","../../../projects/ngx-joyride/src/lib/services/event-listener.service.ts","../../../projects/ngx-joyride/src/lib/components/arrow/arrow.component.ts","../../../projects/ngx-joyride/src/lib/components/arrow/arrow.component.html","../../../projects/ngx-joyride/src/lib/components/close-button/close-button.component.ts","../../../projects/ngx-joyride/src/lib/components/button/button.component.ts","../../../projects/ngx-joyride/src/lib/components/button/button.component.html","../../../projects/ngx-joyride/src/lib/components/step/joyride-step.component.ts","../../../projects/ngx-joyride/src/lib/components/step/joyride-step.component.html","../../../projects/ngx-joyride/src/lib/services/step-drawer.service.ts","../../../projects/ngx-joyride/src/lib/services/joyride-step.service.ts","../../../projects/ngx-joyride/src/lib/services/joyride.service.ts","../../../projects/ngx-joyride/src/lib/joyride.module.ts","../../../projects/ngx-joyride/src/public-api.ts","../../../projects/ngx-joyride/src/ngx-joyride.ts"],"sourcesContent":["import { ViewContainerRef, TemplateRef, EventEmitter } from '@angular/core';\nimport { JoyrideStepComponent } from '../components/step/joyride-step.component';\nimport { ReplaySubject } from 'rxjs';\n\nexport class JoyrideStep {\n constructor() {\n this.title = new ReplaySubject<string>();\n this.text = new ReplaySubject<string>();\n }\n name: string;\n route: string;\n position: string;\n title: ReplaySubject<string>;\n text: ReplaySubject<string>;\n stepContent: TemplateRef<any>;\n stepContentParams: Object;\n nextClicked: EventEmitter<any>;\n prevCliked: EventEmitter<any>;\n tourDone: EventEmitter<any>;\n transformCssStyle: string;\n isElementOrAncestorFixed: boolean;\n targetViewContainer: ViewContainerRef;\n stepInstance: JoyrideStepComponent;\n}\n","export class JoyrideError extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, JoyrideError.prototype);\n }\n}\n\nexport class JoyrideStepDoesNotExist extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, JoyrideStepDoesNotExist.prototype);\n }\n}\n\nexport class JoyrideStepOutOfRange extends Error {\n constructor(message: string) {\n super(message);\n Object.setPrototypeOf(this, JoyrideStepOutOfRange.prototype);\n }\n}\n","import { Injectable } from '@angular/core';\nimport {\n JoyrideOptions,\n CustomTexts,\n ICustomTexts\n} from '../models/joyride-options.class';\nimport { of, Observable } from 'rxjs';\n\nexport const DEFAULT_THEME_COLOR = '#3b5560';\nexport const STEP_DEFAULT_POSITION = 'bottom';\nexport const DEFAULT_TIMEOUT_BETWEEN_STEPS = 1;\n\nexport class ObservableCustomTexts implements ICustomTexts {\n prev: Observable<string>;\n next: Observable<string>;\n done: Observable<string>;\n close: Observable<string>;\n}\nexport const DEFAULT_TEXTS: ObservableCustomTexts = {\n prev: of('prev'),\n next: of('next'),\n done: of('done'),\n close: of(null)\n};\n\nexport interface IJoyrideOptionsService {\n setOptions(options: JoyrideOptions): void;\n getBackdropColor(): string;\n getThemeColor(): string;\n getStepDefaultPosition();\n getStepsOrder(): string[];\n getFirstStep(): string;\n getWaitingTime(): number;\n areLogsEnabled(): boolean;\n isCounterVisible(): boolean;\n isPrevButtonVisible(): boolean;\n getCustomTexts(): ObservableCustomTexts;\n}\n\n@Injectable()\nexport class JoyrideOptionsService implements IJoyrideOptionsService {\n private themeColor: string = DEFAULT_THEME_COLOR;\n private stepDefaultPosition: string = STEP_DEFAULT_POSITION;\n private logsEnabled = false;\n private showCounter = true;\n private showPrevButton = true;\n private stepsOrder: string[] = [];\n private firstStep: string;\n private waitingTime: number;\n private customTexts: ObservableCustomTexts;\n\n setOptions(options: JoyrideOptions) {\n this.stepsOrder = options.steps;\n this.stepDefaultPosition = options.stepDefaultPosition\n ? options.stepDefaultPosition\n : this.stepDefaultPosition;\n this.logsEnabled =\n typeof options.logsEnabled !== 'undefined'\n ? options.logsEnabled\n : this.logsEnabled;\n this.showCounter =\n typeof options.showCounter !== 'undefined'\n ? options.showCounter\n : this.showCounter;\n this.showPrevButton =\n typeof options.showPrevButton !== 'undefined'\n ? options.showPrevButton\n : this.showPrevButton;\n this.themeColor = options.themeColor\n ? options.themeColor\n : this.themeColor;\n this.firstStep = options.startWith;\n this.waitingTime =\n typeof options.waitingTime !== 'undefined'\n ? options.waitingTime\n : DEFAULT_TIMEOUT_BETWEEN_STEPS;\n typeof options.customTexts !== 'undefined'\n ? this.setCustomText(options.customTexts)\n : this.setCustomText(DEFAULT_TEXTS);\n }\n\n getBackdropColor() {\n return this.hexToRgb(this.themeColor);\n }\n\n getThemeColor() {\n return this.themeColor;\n }\n\n getStepDefaultPosition() {\n return this.stepDefaultPosition;\n }\n\n getStepsOrder() {\n return this.stepsOrder;\n }\n\n getFirstStep() {\n return this.firstStep;\n }\n\n getWaitingTime() {\n return this.waitingTime;\n }\n\n areLogsEnabled() {\n return this.logsEnabled;\n }\n\n isCounterVisible() {\n return this.showCounter;\n }\n\n isPrevButtonVisible() {\n return this.showPrevButton;\n }\n\n getCustomTexts(): ObservableCustomTexts {\n return this.customTexts;\n }\n\n private setCustomText(texts: CustomTexts) {\n let prev: string | Observable<string>;\n let next: string | Observable<string>;\n let done;\n let close;\n prev = texts.prev ? texts.prev : DEFAULT_TEXTS.prev;\n next = texts.next ? texts.next : DEFAULT_TEXTS.next;\n done = texts.done ? texts.done : DEFAULT_TEXTS.done;\n close = texts.close ? texts.close : DEFAULT_TEXTS.close;\n this.customTexts = {\n prev: this.toObservable(prev),\n next: this.toObservable(next),\n done: this.toObservable(done),\n close: this.toObservable(close)\n } as ObservableCustomTexts;\n }\n\n private toObservable(value: string | Observable<string>) {\n return value instanceof Observable ? value : of(value);\n }\n\n private hexToRgb(hex: any): string {\n const shorthandRegex = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\n hex = hex.replace(shorthandRegex, (m: any, r: any, g: any, b: any) => {\n return r + r + g + g + b + b;\n });\n\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? `${parseInt(result[1], 16)}, ${parseInt(\n result[2],\n 16\n )}, ${parseInt(result[3], 16)}`\n : null;\n }\n}\n","import { Injectable } from '@angular/core';\nimport { JoyrideOptionsService } from './joyride-options.service';\n\nconst JOYRIDE = 'ngx-joyride:::';\n\n@Injectable()\nexport class LoggerService {\n\n constructor(private readonly optionService: JoyrideOptionsService) { }\n\n debug(message?: string, data: any = \"\") {\n if (this.optionService.areLogsEnabled()) {\n console.debug(JOYRIDE + message, data);\n }\n }\n\n info(message?: string, data: any = \"\") {\n if (this.optionService.areLogsEnabled()) {\n console.info(JOYRIDE + message, data);\n }\n }\n\n warn(message?: string, data: any = \"\") {\n if (this.optionService.areLogsEnabled()) {\n console.warn(JOYRIDE + message, data);\n }\n }\n\n error(message?: string, data: any = \"\") {\n if (this.optionService.areLogsEnabled()) {\n console.error(JOYRIDE + message, data);\n }\n }\n\n}","import { Injectable } from '@angular/core';\nimport { JoyrideStep } from '../models/joyride-step.class';\nimport { Subject } from 'rxjs';\nimport { JoyrideOptionsService } from './joyride-options.service';\nimport { LoggerService } from './logger.service';\nimport { JoyrideError, JoyrideStepOutOfRange } from '../models/joyride-error.class';\n\nconst ROUTE_SEPARATOR = '@';\n\nclass Step {\n id: string;\n step: JoyrideStep;\n}\n\nexport enum StepActionType {\n NEXT = 'NEXT',\n PREV = 'PREV'\n}\n\n@Injectable()\nexport class JoyrideStepsContainerService {\n private steps: Step[];\n private tempSteps: JoyrideStep[] = [];\n private currentStepIndex = -2;\n stepHasBeenModified: Subject<JoyrideStep> = new Subject<JoyrideStep>();\n\n constructor(private readonly stepOptions: JoyrideOptionsService, private readonly logger: LoggerService) {}\n\n private getFirstStepIndex(): number {\n const firstStep = this.stepOptions.getFirstStep();\n const stepIds = this.stepOptions.getStepsOrder();\n\n let index = stepIds.indexOf(firstStep);\n if (index < 0) {\n index = 0;\n if (firstStep !== undefined) this.logger.warn(`The step ${firstStep} does not exist. Check in your step list if it's present.`);\n }\n\n return index;\n }\n\n init() {\n this.logger.info('Initializing the steps array.');\n this.steps = [];\n this.currentStepIndex = this.getFirstStepIndex() - 1;\n let stepIds = this.stepOptions.getStepsOrder();\n stepIds.forEach(stepId => this.steps.push({ id: stepId, step: null }));\n }\n\n addStep(stepToAdd: JoyrideStep) {\n let stepExist = this.tempSteps.filter(step => step.name === stepToAdd.name).length > 0;\n if (!stepExist) {\n this.logger.info(`Adding step ${stepToAdd.name} to the steps list.`);\n this.tempSteps.push(stepToAdd);\n } else {\n let stepIndexToReplace = this.tempSteps.findIndex(step => step.name === stepToAdd.name);\n this.tempSteps[stepIndexToReplace] = stepToAdd;\n }\n }\n get(action: StepActionType): JoyrideStep {\n if (action === StepActionType.NEXT) this.currentStepIndex++;\n else this.currentStepIndex--;\n\n if (this.currentStepIndex < 0 || this.currentStepIndex >= this.steps.length)\n throw new JoyrideStepOutOfRange('The first or last step of the tour cannot be found!');\n\n const stepName = this.getStepName(this.steps[this.currentStepIndex].id);\n const index = this.tempSteps.findIndex(step => step.name === stepName);\n let stepFound = this.tempSteps[index];\n this.steps[this.currentStepIndex].step = stepFound;\n\n if (stepFound == null) {\n this.logger.warn(`Step ${this.steps[this.currentStepIndex].id} not found in the DOM. Check if it's hidden by *ngIf directive.`);\n }\n\n return stepFound;\n }\n\n getStepRoute(action: StepActionType) {\n let stepID: string;\n if (action === StepActionType.NEXT) {\n stepID = this.steps[this.currentStepIndex + 1] ? this.steps[this.currentStepIndex + 1].id : null;\n } else {\n stepID = this.steps[this.currentStepIndex - 1] ? this.steps[this.currentStepIndex - 1].id : null;\n }\n let stepRoute = stepID && stepID.includes(ROUTE_SEPARATOR) ? stepID.split(ROUTE_SEPARATOR)[1] : '';\n\n return stepRoute;\n }\n\n updatePosition(stepName: string, position: string) {\n let index = this.getStepIndex(stepName);\n if (this.steps[index].step) {\n this.steps[index].step.position = position;\n this.stepHasBeenModified.next(this.steps[index].step);\n } else {\n this.logger.warn(\n `Trying to modify the position of ${stepName} to ${position}. Step not found!Is this step located in a different route?`\n );\n }\n }\n getStepNumber(stepName: string): number {\n return this.getStepIndex(stepName) + 1;\n }\n\n getStepsCount() {\n let stepsOrder = this.stepOptions.getStepsOrder();\n return stepsOrder.length;\n }\n\n private getStepIndex(stepName: string): number {\n const index = this.steps\n .map(step => (step.id.includes(ROUTE_SEPARATOR) ? step.id.split(ROUTE_SEPARATOR)[0] : step.id))\n .findIndex(name => stepName === name);\n if (index === -1) throw new JoyrideError(`The step with name: ${stepName} does not exist in the step list.`);\n return index;\n }\n\n private getStepName(stepID: string): string {\n let stepName = stepID && stepID.includes(ROUTE_SEPARATOR) ? stepID.split(ROUTE_SEPARATOR)[0] : stepID;\n return stepName;\n }\n}\n","import { Injectable, Inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\n@Injectable()\nexport class DomRefService {\n private fakeDocument: Document = <Document>{ body: {}, documentElement: {} };\n private fakeWindow: Window = <Window>{ document: this.fakeDocument, navigator: {} };\n constructor(@Inject(PLATFORM_ID) private platformId: Object) {}\n getNativeWindow(): Window {\n if (isPlatformBrowser(this.platformId)) return window;\n else return this.fakeWindow;\n }\n\n getNativeDocument() {\n if (isPlatformBrowser(this.platformId)) return document;\n else return this.fakeDocument;\n }\n}","import { Injectable, TemplateRef } from '@angular/core';\n\n@Injectable()\nexport class TemplatesService {\n private _prevButton: TemplateRef<any>;\n private _nextButton: TemplateRef<any>;\n private _doneButton: TemplateRef<any>;\n private _counter: TemplateRef<any>;\n\n setPrevButton(template: TemplateRef<any>) {\n this._prevButton = template;\n }\n\n getPrevButton() {\n return this._prevButton;\n }\n\n setNextButton(template: TemplateRef<any>) {\n this._nextButton = template;\n }\n\n getNextButton() {\n return this._nextButton;\n }\n\n setDoneButton(template: TemplateRef<any>) {\n this._doneButton = template;\n }\n\n getDoneButton() {\n return this._doneButton;\n }\n setCounter(template: TemplateRef<any>) {\n this._counter = template;\n }\n\n getCounter() {\n return this._counter;\n }\n}\n","import {\n Directive,\n ElementRef,\n AfterViewInit,\n Input,\n ViewContainerRef,\n TemplateRef,\n Output,\n EventEmitter,\n Inject,\n PLATFORM_ID,\n OnChanges,\n SimpleChanges,\n OnDestroy\n} from '@angular/core';\nimport { JoyrideStep } from '../models/joyride-step.class';\nimport { JoyrideStepsContainerService } from '../services/joyride-steps-container.service';\nimport { JoyrideError } from '../models/joyride-error.class';\nimport { Router } from '@angular/router';\nimport { DomRefService } from '../services/dom.service';\nimport { isPlatformBrowser } from '@angular/common';\nimport { TemplatesService } from '../services/templates.service';\nimport { Observable, Subscription } from 'rxjs';\n\nexport const NO_POSITION = 'NO_POSITION';\n\n@Directive({\n selector: 'joyrideStep, [joyrideStep]'\n})\nexport class JoyrideDirective implements AfterViewInit, OnChanges, OnDestroy {\n @Input('joyrideStep')\n name: string;\n\n @Input()\n nextStep?: string;\n\n @Input()\n title?: string | Observable<string>;\n\n @Input()\n text?: string | Observable<string>;\n\n @Input()\n stepPosition?: string = NO_POSITION;\n\n @Input()\n stepContent?: TemplateRef<any>;\n\n @Input()\n stepContentParams?: Object;\n\n @Input()\n prevTemplate?: TemplateRef<any>;\n\n @Input()\n nextTemplate?: TemplateRef<any>;\n\n @Input()\n doneTemplate?: TemplateRef<any>;\n\n @Input()\n counterTemplate?: TemplateRef<any>;\n\n @Output()\n prev?: EventEmitter<any> = new EventEmitter<any>();\n\n @Output()\n next?: EventEmitter<any> = new EventEmitter<any>();\n\n @Output()\n done?: EventEmitter<any> = new EventEmitter<any>();\n\n private windowRef: Window;\n private step: JoyrideStep;\n private subscriptions: Subscription[] = [];\n\n constructor(\n private readonly joyrideStepsContainer: JoyrideStepsContainerService,\n private viewContainerRef: ViewContainerRef,\n private readonly domService: DomRefService,\n private readonly router: Router,\n private readonly templateService: TemplatesService,\n @Inject(PLATFORM_ID) private platformId: Object\n ) {\n this.windowRef = this.domService.getNativeWindow();\n this.step = new JoyrideStep();\n }\n\n ngAfterViewInit() {\n if (!isPlatformBrowser(this.platformId)) return;\n if (this.prevTemplate) this.templateService.setPrevButton(this.prevTemplate);\n if (this.nextTemplate) this.templateService.setNextButton(this.nextTemplate);\n if (this.doneTemplate) this.templateService.setDoneButton(this.doneTemplate);\n if (this.counterTemplate) this.templateService.setCounter(this.counterTemplate);\n this.step.position = this.stepPosition;\n this.step.targetViewContainer = this.viewContainerRef;\n this.setAsyncFields(this.step);\n this.step.stepContent = this.stepContent;\n this.step.stepContentParams = this.stepContentParams;\n this.step.nextClicked = this.next;\n this.step.prevCliked = this.prev;\n this.step.tourDone = this.done;\n if (!this.name) throw new JoyrideError(\"All the steps should have the 'joyrideStep' property set with a custom name.\");\n this.step.name = this.name;\n this.step.route = this.router.url.substr(0, 1) === '/' ? this.router.url.substr(1) : this.router.url;\n this.step.transformCssStyle = this.windowRef.getComputedStyle(this.viewContainerRef.element.nativeElement).transform;\n this.step.isElementOrAncestorFixed =\n this.isElementFixed(this.viewContainerRef.element) ||\n this.isAncestorsFixed(this.viewContainerRef.element.nativeElement.parentElement);\n\n this.joyrideStepsContainer.addStep(this.step);\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes['title'] || changes['text']) {\n this.setAsyncFields(this.step);\n }\n }\n\n private isElementFixed(element: ElementRef) {\n return this.windowRef.getComputedStyle(element.nativeElement).position === 'fixed';\n }\n\n private setAsyncFields(step: JoyrideStep) {\n if (this.title instanceof Observable) {\n this.subscriptions.push(\n this.title.subscribe(title => {\n step.title.next(title);\n })\n );\n } else {\n step.title.next(this.title);\n }\n if (this.text instanceof Observable) {\n this.subscriptions.push(\n this.text.subscribe(text => {\n step.text.next(text);\n })\n );\n } else {\n step.text.next(this.text);\n }\n }\n\n private isAncestorsFixed(nativeElement: any): boolean {\n if (!nativeElement || !nativeElement.parentElement) return false;\n let isElementFixed = this.windowRef.getComputedStyle(nativeElement.parentElement).position === 'fixed';\n if (nativeElement.nodeName === 'BODY') {\n return isElementFixed;\n }\n if (isElementFixed) return true;\n else return this.isAncestorsFixed(nativeElement.parentElement);\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach(sub => {\n sub.unsubscribe();\n });\n }\n}\n","import { StepActionType } from \"../services/joyride-steps-container.service\";\n\nexport class JoyrideStepInfo {\n number: number;\n name: string;\n route: string;\n actionType: StepActionType;\n}","import { Injectable, ElementRef, Inject, PLATFORM_ID } from '@angular/core';\nimport { DomRefService } from './dom.service';\nimport { isPlatformBrowser } from \"@angular/common\";\n\nexport interface IDocumentService {\n getElementFixedTop(elementRef: ElementRef): number;\n\n getElementFixedLeft(elementRef: ElementRef);\n\n getElementAbsoluteTop(elementRef: ElementRef);\n\n getElementAbsoluteLeft(elementRef: ElementRef);\n\n setDocumentHeight();\n\n getDocumentHeight(): number;\n isParentScrollable(elementRef: ElementRef): boolean;\n isElementBeyondOthers(\n elementRef: ElementRef,\n isElementFixed: boolean,\n keywordToDiscard: string\n ): number;\n scrollToTheTop(elementRef: ElementRef): void;\n scrollToTheBottom(elementRef: ElementRef): void;\n}\n\n@Injectable()\nexport class DocumentService implements IDocumentService {\n private documentHeight: number;\n\n constructor(private readonly DOMService: DomRefService, @Inject(PLATFORM_ID) platformId: Object) {\n if (!isPlatformBrowser(platformId)) {\n return;\n }\n this.setDocumentHeight();\n var doc = DOMService.getNativeDocument();\n if (doc && !doc.elementsFromPoint) {\n // IE 11 - Edge browsers\n doc.elementsFromPoint = this.elementsFromPoint.bind(this);\n }\n }\n\n getElementFixedTop(elementRef: ElementRef) {\n return elementRef.nativeElement.getBoundingClientRect().top;\n }\n\n getElementFixedLeft(elementRef: ElementRef) {\n return elementRef.nativeElement.getBoundingClientRect().left;\n }\n\n getElementAbsoluteTop(elementRef: ElementRef) {\n const scrollOffsets = this.getScrollOffsets();\n return (\n elementRef.nativeElement.getBoundingClientRect().top +\n scrollOffsets.y\n );\n }\n\n getElementAbsoluteLeft(elementRef: ElementRef) {\n const scrollOffsets = this.getScrollOffsets();\n return (\n elementRef.nativeElement.getBoundingClientRect().left +\n scrollOffsets.x\n );\n }\n\n setDocumentHeight() {\n this.documentHeight = this.calculateDocumentHeight();\n }\n\n getDocumentHeight() {\n return this.documentHeight;\n }\n\n isParentScrollable(elementRef: ElementRef): boolean {\n return (\n this.getFirstScrollableParent(elementRef.nativeElement) !==\n this.DOMService.getNativeDocument().body\n );\n }\n\n isElementBeyondOthers(\n elementRef: ElementRef,\n isElementFixed: boolean,\n keywordToDiscard: string\n ) {\n const x1 = isElementFixed\n ? this.getElementFixedLeft(elementRef)\n : this.getElementAbsoluteLeft(elementRef);\n const y1 = isElementFixed\n ? this.getElementFixedTop(elementRef)\n : this.getElementAbsoluteTop(elementRef);\n const x2 =\n x1 + elementRef.nativeElement.getBoundingClientRect().width - 1;\n const y2 =\n y1 + elementRef.nativeElement.getBoundingClientRect().height - 1;\n\n const elements1 = this.DOMService.getNativeDocument().elementsFromPoint(\n x1,\n y1\n );\n const elements2 = this.DOMService.getNativeDocument().elementsFromPoint(\n x2,\n y2\n );\n\n if (elements1.length === 0 && elements2.length === 0) return 1;\n if (\n this.getFirstElementWithoutKeyword(elements1, keywordToDiscard) !==\n elementRef.nativeElement ||\n this.getFirstElementWithoutKeyword(elements2, keywordToDiscard) !==\n elementRef.nativeElement\n ) {\n return 2;\n }\n return 3;\n }\n\n scrollIntoView(elementRef: ElementRef, isElementFixed: boolean): void {\n const firstScrollableParent = this.getFirstScrollableParent(\n elementRef.nativeElement\n );\n const top = isElementFixed\n ? this.getElementFixedTop(elementRef)\n : this.getElementAbsoluteTop(elementRef);\n if (\n firstScrollableParent !== this.DOMService.getNativeDocument().body\n ) {\n if (firstScrollableParent.scrollTo) {\n firstScrollableParent.scrollTo(0, top - 150);\n } else {\n // IE 11 - Edge browsers\n firstScrollableParent.scrollTop = top - 150;\n }\n } else {\n this.DOMService.getNativeWindow().scrollTo(0, top - 150);\n }\n }\n\n scrollToTheTop(elementRef: ElementRef): void {\n const firstScrollableParent = this.getFirstScrollableParent(\n elementRef.nativeElement\n );\n if (\n firstScrollableParent !== this.DOMService.getNativeDocument().body\n ) {\n if (firstScrollableParent.scrollTo) {\n firstScrollableParent.scrollTo(0, 0);\n } else {\n // IE 11 - Edge browsers\n firstScrollableParent.scrollTop = 0;\n }\n } else {\n this.DOMService.getNativeWindow().scrollTo(0, 0);\n }\n }\n\n scrollToTheBottom(elementRef: ElementRef): void {\n const firstScrollableParent = this.getFirstScrollableParent(\n elementRef.nativeElement\n );\n if (\n firstScrollableParent !== this.DOMService.getNativeDocument().body\n ) {\n if (firstScrollableParent.scrollTo) {\n firstScrollableParent.scrollTo(\n 0,\n this.DOMService.getNativeDocument().body.scrollHeight\n );\n } else {\n // IE 11 - Edge browsers\n firstScrollableParent.scrollTop =\n firstScrollableParent.scrollHeight -\n firstScrollableParent.clientHeight;\n }\n } else {\n this.DOMService.getNativeWindow().scrollTo(\n 0,\n this.DOMService.getNativeDocument().body.scrollHeight\n );\n }\n }\n\n private getFirstScrollableParent(node: any) {\n const regex = /(auto|scroll|overlay)/;\n\n const style = (node: any, prop: any) =>\n this.DOMService.getNativeWindow()\n .getComputedStyle(node, null)\n .getPropertyValue(prop);\n\n const scroll = (node: any) =>\n regex.test(\n style(node, 'overflow') +\n style(node, 'overflow-y') +\n style(node, 'overflow-x')\n );\n\n const scrollparent = (node: any): any => {\n return !node || node === this.DOMService.getNativeDocument().body\n ? this.DOMService.getNativeDocument().body\n : scroll(node)\n ? node\n : scrollparent(node.parentNode);\n };\n\n return scrollparent(node);\n }\n\n private calculateDocumentHeight() {\n const documentRef = this.DOMService.getNativeDocument();\n return Math.max(\n documentRef.body.scrollHeight,\n documentRef.documentElement.scrollHeight,\n documentRef.body.offsetHeight,\n documentRef.documentElement.offsetHeight,\n documentRef.body.clientHeight,\n documentRef.documentElement.clientHeight\n );\n }\n\n private getScrollOffsets() {\n const winReference = this.DOMService.getNativeWindow();\n const docReference = this.DOMService.getNativeDocument();\n\n // This works for all browsers except IE versions 8 and before\n if (winReference.pageXOffset != null)\n return { x: winReference.pageXOffset, y: winReference.pageYOffset };\n\n // For IE (or any browser) in Standards mode\n if (docReference.compatMode == 'CSS1Compat')\n return {\n x: docReference.documentElement.scrollLeft,\n y: docReference.documentElement.scrollTop\n };\n\n // For browsers in Quirks mode\n return {\n x: docReference.body.scrollLeft,\n y: docReference.body.scrollTop\n };\n }\n\n private elementsFromPoint(x, y) {\n var parents = [];\n var parent = void 0;\n do {\n const elem = this.DOMService.getNativeDocument().elementFromPoint(\n x,\n y\n );\n if (elem && parent !== elem) {\n parent = elem;\n parents.push(parent);\n parent.style.pointerEvents = 'none';\n } else {\n parent = false;\n }\n } while (parent);\n parents.forEach(function(parent) {\n return (parent.style.pointerEvents = 'all');\n });\n return parents;\n }\n\n private getFirstElementWithoutKeyword(\n elements: Element[],\n keyword: string\n ): Element {\n while (\n elements[0] &&\n elements[0].classList.toString().includes(keyword)\n ) {\n elements.shift();\n }\n return elements[0];\n }\n}\n","import { Injectable, Renderer2, RendererFactory2, ViewContainerRef } from '@angular/core';\nimport { DocumentService } from './document.service';\nimport { Scroll } from './event-listener.service';\nimport { JoyrideOptionsService } from './joyride-options.service';\nimport { JoyrideStep } from '../models/joyride-step.class';\n\n@Injectable()\nexport class JoyrideBackdropService {\n private renderer: Renderer2;\n private currentBackdropContainer: any;\n private lastBackdropContainer: any;\n private backdropContent: any;\n private backdropTop: any;\n private backdropBottom: any;\n private backdropMiddleContainer: any;\n private backdropMiddleContent: any;\n private leftBackdrop: any;\n private targetBackdrop: any;\n private rightBackdrop: any;\n private elementRef: ViewContainerRef;\n private targetAbsoluteTop: number;\n private targetAbsoluteLeft: number;\n private lastXScroll: number = 0;\n private lastYScroll: number = 0;\n\n constructor(\n private readonly documentService: DocumentService,\n private readonly optionsService: JoyrideOptionsService,\n private readonly rendererFactory: RendererFactory2\n ) {\n this.setRenderer();\n }\n\n private setRenderer() {\n this.renderer = this.rendererFactory.createRenderer(null, null);\n }\n\n draw(step: JoyrideStep) {\n this.elementRef = step.targetViewContainer;\n this.targetAbsoluteTop = this.getTargetTotalTop(step);\n this.targetAbsoluteLeft = this.getTargetTotalLeft(step);\n\n this.currentBackdropContainer = this.renderer.createElement('div');\n this.renderer.addClass(this.currentBackdropContainer, 'backdrop-container');\n this.renderer.setStyle(this.currentBackdropContainer, 'position', 'fixed');\n this.renderer.setStyle(this.currentBackdropContainer, 'top', '0px');\n this.renderer.setStyle(this.currentBackdropContainer, 'left', '0px');\n this.renderer.setStyle(this.currentBackdropContainer, 'width', '100%');\n this.renderer.setStyle(this.currentBackdropContainer, 'height', '100%');\n this.renderer.setStyle(this.currentBackdropContainer, 'z-index', '1000');\n this.renderer.setAttribute(this.currentBackdropContainer, 'id', 'backdrop-' + step.name);\n\n this.backdropContent = this.renderer.createElement('div');\n this.renderer.addClass(this.backdropContent, 'backdrop-content');\n this.renderer.setStyle(this.backdropContent, 'position', 'relative');\n this.renderer.setStyle(this.backdropContent, 'height', '100%');\n this.renderer.setStyle(this.backdropContent, 'display', 'flex');\n this.renderer.setStyle(this.backdropContent, 'flex-direction', 'column');\n this.renderer.appendChild(this.currentBackdropContainer, this.backdropContent);\n\n this.backdropTop = this.renderer.createElement('div');\n this.renderer.addClass(this.backdropTop, 'joyride-backdrop');\n this.renderer.addClass(this.backdropTop, 'backdrop-top');\n this.renderer.setStyle(this.backdropTop, 'width', '100%');\n this.renderer.setStyle(this.backdropTop, 'height', this.targetAbsoluteTop - this.lastYScroll + 'px');\n this.renderer.setStyle(this.backdropTop, 'flex-shrink', '0');\n this.renderer.setStyle(this.backdropTop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);\n this.renderer.appendChild(this.backdropContent, this.backdropTop);\n\n this.backdropMiddleContainer = this.renderer.createElement('div');\n this.renderer.addClass(this.backdropMiddleContainer, 'backdrop-middle-container');\n this.renderer.setStyle(this.backdropMiddleContainer, 'height', this.elementRef.element.nativeElement.offsetHeight + 'px');\n this.renderer.setStyle(this.backdropMiddleContainer, 'width', '100%');\n this.renderer.setStyle(this.backdropMiddleContainer, 'flex-shrink', '0');\n this.renderer.appendChild(this.backdropContent, this.backdropMiddleContainer);\n\n this.backdropMiddleContent = this.renderer.createElement('div');\n this.renderer.addClass(this.backdropMiddleContent, 'backdrop-middle-content');\n this.renderer.setStyle(this.backdropMiddleContent, 'display', 'flex');\n this.renderer.setStyle(this.backdropMiddleContent, 'width', '100%');\n this.renderer.setStyle(this.backdropMiddleContent, 'height', '100%');\n this.renderer.appendChild(this.backdropMiddleContainer, this.backdropMiddleContent);\n\n this.leftBackdrop = this.renderer.createElement('div');\n this.renderer.addClass(this.leftBackdrop, 'joyride-backdrop');\n this.renderer.addClass(this.leftBackdrop, 'backdrop-left');\n this.renderer.setStyle(this.leftBackdrop, 'flex-shrink', '0');\n this.renderer.setStyle(this.leftBackdrop, 'width', this.targetAbsoluteLeft - this.lastXScroll + 'px');\n this.renderer.setStyle(this.leftBackdrop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);\n this.renderer.appendChild(this.backdropMiddleContent, this.leftBackdrop);\n\n this.targetBackdrop = this.renderer.createElement('div');\n this.renderer.addClass(this.targetBackdrop, 'backdrop-target');\n this.renderer.setStyle(this.targetBackdrop, 'flex-shrink', '0');\n this.renderer.setStyle(this.targetBackdrop, 'width', this.elementRef.element.nativeElement.offsetWidth + 'px');\n this.renderer.appendChild(this.backdropMiddleContent, this.targetBackdrop);\n\n this.rightBackdrop = this.renderer.createElement('div');\n this.renderer.addClass(this.rightBackdrop, 'joyride-backdrop');\n this.renderer.addClass(this.rightBackdrop, 'backdrop-right');\n this.renderer.setStyle(this.rightBackdrop, 'width', '100%');\n this.renderer.setStyle(this.rightBackdrop, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);\n this.renderer.appendChild(this.backdropMiddleContent, this.rightBackdrop);\n\n this.backdropBottom = this.renderer.createElement('div');\n this.renderer.addClass(this.backdropBottom, 'joyride-backdrop');\n this.renderer.addClass(this.backdropBottom, 'backdrop-bottom');\n this.renderer.setStyle(this.backdropBottom, 'width', '100%');\n this.renderer.setStyle(this.backdropBottom, 'height', '100%');\n this.renderer.setStyle(this.backdropBottom, 'background-color', `rgba(${this.optionsService.getBackdropColor()}, 0.7)`);\n this.renderer.appendChild(this.backdropContent, this.backdropBottom);\n\n this.removeLastBackdrop();\n this.drawCurrentBackdrop();\n this.lastBackdropContainer = this.currentBackdropContainer;\n }\n\n remove() {\n this.removeLastBackdrop();\n }\n\n redrawTarget(step: JoyrideStep) {\n this.targetAbsoluteLeft = this.getTargetTotalLeft(step);\n this.targetAbsoluteTop = this.getTargetTotalTop(step);\n this.handleVerticalScroll(step);\n this.handleHorizontalScroll(step);\n }\n\n private getTargetTotalTop(step: JoyrideStep) {\n let targetVC = step.targetViewContainer;\n return step.isElementOrAncestorFixed\n ? this.documentService.getElementFixedTop(targetVC.element)\n : this.documentService.getElementAbsoluteTop(targetVC.element);\n }\n\n private getTargetTotalLeft(step: JoyrideStep) {\n let targetVC = step.targetViewContainer;\n\n return step.isElementOrAncestorFixed\n ? this.documentService.getElementFixedLeft(targetVC.element)\n : this.documentService.getElementAbsoluteLeft(targetVC.element);\n }\n redraw(step: JoyrideStep, scroll: Scroll) {\n if (this.lastYScroll !== scroll.scrollY) {\n this.lastYScroll = scroll.scrollY;\n if (this.elementRef) {\n this.handleVerticalScroll(step);\n }\n }\n if (this.lastXScroll !== scroll.scrollX) {\n this.lastXScroll = scroll.scrollX;\n if (this.elementRef) {\n this.handleHorizontalScroll(step);\n }\n }\n }\n\n private handleHorizontalScroll(step: JoyrideStep) {\n let newBackdropLeftWidth = step.isElementOrAncestorFixed ? this.targetAbsoluteLeft : this.targetAbsoluteLeft - this.lastXScroll;\n\n if (newBackdropLeftWidth >= 0) {\n this.renderer.setStyle(this.leftBackdrop, 'width', newBackdropLeftWidth + 'px');\n this.renderer.setStyle(this.targetBackdrop, 'width', this.elementRef.element.nativeElement.offsetWidth + 'px');\n } else {\n this.handleTargetPartialWidth(newBackdropLeftWidth);\n }\n }\n\n private handleTargetPartialWidth(newBackdropLeftWidth: number) {\n this.renderer.setStyle(this.leftBackdrop, 'width', 0 + 'px');\n let visibleTargetWidth = this.elementRef.element.nativeElement.offsetWidth + newBackdropLeftWidth;\n if (visibleTargetWidth >= 0) {\n this.renderer.setStyle(this.targetBackdrop, 'width', visibleTargetWidth + 'px');\n } else {\n this.renderer.setStyle(this.targetBackdrop, 'width', 0 + 'px');\n }\n }\n\n private handleVerticalScroll(step: JoyrideStep) {\n let newBackdropTopHeight = step.isElementOrAncestorFixed ? this.targetAbsoluteTop : this.targetAbsoluteTop - this.lastYScroll;\n\n if (newBackdropTopHeight >= 0) {\n this.renderer.setStyle(this.backdropTop, 'height', newBackdropTopHeight + 'px');\n this.renderer.setStyle(this.backdropMiddleContainer, 'height', this.elementRef.element.nativeElement.offsetHeight + 'px');\n } else {\n this.handleTargetPartialHeight(newBackdropTopHeight);\n }\n }\n\n private handleTargetPartialHeight(newBackdropTopHeight: number) {\n this.renderer.setStyle(this.backdropTop, 'height', 0 + 'px');\n let visibleTargetHeight = this.elementRef.element.nativeElement.offsetHeight + newBackdropTopHeight;\n if (visibleTargetHeight >= 0) {\n this.renderer.setStyle(this.backdropMiddleContainer, 'height', visibleTargetHeight + 'px');\n } else {\n this.renderer.setStyle(this.backdropMiddleContainer, 'height', 0 + 'px');\n }\n }\n\n private removeLastBackdrop() {\n if (this.lastBackdropContainer) {\n this.renderer.removeChild(document.body, this.lastBackdropContainer);\n this.lastBackdropContainer = undefined;\n }\n }\n\n private drawCurrentBackdrop() {\n this.renderer.appendChild(document.body, this.currentBackdropContainer);\n }\n}\n","import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { DomRefService } from './dom.service';\n\nexport class Scroll {\n scrollX: number;\n scrollY: number;\n}\n\n@Injectable()\nexport class EventListenerService {\n private renderer: Renderer2;\n private scrollUnlisten: any;\n private resizeUnlisten: any;\n\n scrollEvent: Subject<Scroll> = new Subject<Scroll>();\n resizeEvent: Subject<number> = new Subject<number>();\n\n constructor(private readonly rendererFactory: RendererFactory2, private readonly DOMService: DomRefService) {\n this.renderer = rendererFactory.createRenderer(null, null);\n }\n\n startListeningScrollEvents() {\n this.scrollUnlisten = this.renderer.listen('document', 'scroll', evt => {\n this.scrollEvent.next({\n scrollX: this.DOMService.getNativeWindow().pageXOffset,\n scrollY: this.DOMService.getNativeWindow().pageYOffset\n });\n });\n }\n\n startListeningResizeEvents() {\n this.resizeUnlisten = this.renderer.listen('window', 'resize', evt => {\n this.resizeEvent.next(evt);\n });\n }\n\n stopListeningScrollEvents() {\n this.scrollUnlisten();\n }\n\n stopListeningResizeEvents() {\n this.resizeUnlisten();\n }\n}\n","\nimport { Component, Input, ViewEncapsulation } from \"@angular/core\";\n\n@Component({\n selector: 'joyride-arrow',\n templateUrl: './arrow.component.html',\n styleUrls: ['./arrow.component.scss'],\n encapsulation: ViewEncapsulation.None\n})\nexport class JoyrideArrowComponent {\n @Input()\n position: string = 'top';\n}","<div [class.joyride-arrow__top]=\"position == 'top'\"\n [class.joyride-arrow__bottom]=\"position == 'bottom'\"\n [class.joyride-arrow__left]=\"position == 'left'\"\n [class.joyride-arrow__right]=\"position == 'right'\">\n</div>","import { Component } from '@angular/core';\n\n@Component({\n selector: 'joy-close-button',\n template: `<svg viewBox=\"0 0 25 25\" xmlns=\"http://www.w3.org/2000/svg\">\n <line x1=\"1\" y1=\"24\" \n x2=\"24\" y2=\"1\" \n stroke=\"black\" \n stroke-width=\"3\"/>\n <line x1=\"1\" y1=\"1\" \n x2=\"24\" y2=\"24\" \n stroke=\"black\" \n stroke-width=\"3\"/>\n </svg>`\n})\n\nexport class JoyrideCloseButtonComponent { }","import { Component, Input, EventEmitter, Output } from \"@angular/core\";\n\n@Component({\n selector: 'joyride-button',\n templateUrl: './button.component.html',\n styleUrls: ['./button.component.scss']\n})\nexport class JoyrideButtonComponent {\n hover: boolean;\n \n @Input() \n color: string;\n \n @Output()\n clicked: EventEmitter<any> = new EventEmitter();\n\n onClick() {\n this.clicked.emit();\n }\n}","<button (mouseleave)=\"hover=false\" (mouseover)=\"hover=true\"\n [ngStyle]=\"{'background-color': hover ? '#fff' : color, \n 'color': hover ? color : '#fff',\n 'border-color' : hover ? color : 'transparent'}\"\n class=\"joyride-button\" (click)=\"onClick()\">\n <ng-content></ng-content>\n</button>","import {\n Component,\n Input,\n AfterViewInit,\n ViewEncapsulation,\n OnInit,\n OnDestroy,\n ElementRef,\n ViewChild,\n Renderer2,\n Injector,\n TemplateRef,\n HostListener\n} from '@angular/core';\nimport { JoyrideStep } from '../../models/joyride-step.class';\nimport {\n JoyrideStepService,\n ARROW_SIZE,\n DISTANCE_FROM_TARGET,\n IJoyrideStepService\n} from '../../services';\nimport { JoyrideStepsContainerService } from '../../services/joyride-steps-container.service';\nimport { EventListenerService } from '../../services/event-listener.service';\nimport { Subscription, Observable } from 'rxjs';\nimport { DocumentService } from '../../services/document.service';\nimport { JoyrideOptionsService } from '../../services/joyride-options.service';\nimport { LoggerService } from '../../services/logger.service';\nimport { TemplatesService } from '../../services/templates.service';\n\nconst STEP_MIN_WIDTH = 200;\nconst STEP_MAX_WIDTH = 400;\nconst CUSTOM_STEP_MAX_WIDTH_VW = 90;\nconst STEP_HEIGHT = 200;\nconst ASPECT_RATIO = 1.212;\nexport const DEFAULT_DISTANCE_FROM_MARGIN_TOP = 2;\nexport const DEFAULT_DISTANCE_FROM_MARGIN_LEFT = 2;\nconst DEFAULT_DISTANCE_FROM_MARGIN_BOTTOM = 5;\nconst DEFAULT_DISTANCE_FROM_MARGIN_RIGHT = 5;\nexport enum KEY_CODE {\n RIGHT_ARROW = 39,\n LEFT_ARROW = 37,\n ESCAPE_KEY= 27\n}\n\n@Component({\n selector: 'joyride-step',\n templateUrl: './joyride-step.component.html',\n styleUrls: ['./joyride-step.component.scss'],\n encapsulation: ViewEncapsulation.None\n})\nexport class JoyrideStepComponent implements OnInit, OnDestroy, AfterViewInit {\n stepWidth: number = STEP_MIN_WIDTH;\n stepHeight: number = STEP_HEIGHT;\n leftPosition: number;\n topPosition: number;\n showArrow = true;\n arrowPosition: string;\n arrowLeftPosition: number;\n arrowTopPosition: number;\n title: Observable<string>;\n text: Observable<string>;\n counter: string;\n isCounterVisible: boolean;\n isPrevButtonVisible: boolean;\n themeColor: string;\n customContent: TemplateRef<any>;\n customPrevButton: TemplateRef<any>;\n customNextButton: TemplateRef<any>;\n customDoneButton: TemplateRef<any>;\n customCounter: TemplateRef<any>;\n counterData: any;\n ctx: Object;\n\n private arrowSize: number = ARROW_SIZE;\n private stepAbsoluteLeft: number;\n private stepAbsoluteTop: number;\n private targetWidth: number;\n targetHeight: number;\n private targetAbsoluteLeft: number;\n private targetAbsoluteTop: number;\n\n private subscriptions: Subscription[] = [];\n joyrideStepService: IJoyrideStepService;\n\n private positionAlreadyFixed: boolean;\n private documentHeight: number;\n\n prevText: Observable<string>;\n nextText: Observable<string>;\n doneText: Observable<string>;\n\n @Input() step?: JoyrideStep;\n @ViewChild('stepHolder', { static: true }) stepHolder: ElementRef;\n @ViewChild('stepContainer', { static: true }) stepContainer: ElementRef;\n\n constructor(\n private injector: Injector,\n private readonly stepsContainerService: JoyrideStepsContainerService,\n private readonly eventListenerService: EventListenerService,\n private readonly documentService: DocumentService,\n private readonly renderer: Renderer2,\n private readonly logger: LoggerService,\n private readonly optionsService: JoyrideOptionsService,\n private readonly templateService: TemplatesService\n ) {}\n\n ngOnInit(): void {\n // Need to Inject here otherwise you will obtain a circular dependency\n this.joyrideStepService = this.injector.get(JoyrideStepService);\n\n this.documentHeight = this.documentService.getDocumentHeight();\n this.subscriptions.push(this.subscribeToResizeEvents());\n this.title = this.step.title.asObservable();\n this.text = this.step.text.asObservable();\n\n this.setCustomTemplates();\n this.setCustomTexts();\n\n this.counter = this.getCounter();\n this.isCounterVisible = this.optionsService.isCounterVisible();\n this.isPrevButtonVisible = this.optionsService.isPrevButtonVisible();\n this.themeColor = this.optionsService.getThemeColor();\n\n if (this.text) this.text.subscribe(val => this.checkRedraw(val));\n if (this.title) this.title.subscribe(val => this.checkRedraw(val));\n }\n\n ngAfterViewInit() {\n if (this.isCustomized()) {\n this.renderer.setStyle(\n this.stepContainer.nativeElement,\n 'max-width',\n CUSTOM_STEP_MAX_WIDTH_VW + 'vw'\n );\n this.updateStepDimensions();\n } else {\n this.renderer.setStyle(\n this.stepContainer.nativeElement,\n 'max-width',\n STEP_MAX_WIDTH + 'px'\n );\n let dimensions = this.getDimensionsByAspectRatio(\n this.stepContainer.nativeElement.clientWidth,\n this.stepContainer.nativeElement.clientHeight,\n ASPECT_RATIO\n );\n dimensions = this.adjustDimensions(\n dimensions.width,\n dimensions.height\n );\n this.stepWidth = dimensions.width;\n this.stepHeight = dimensions.height;\n