From 5c7f3e9746298827c68eb2891c8cc03a3efe2b8a Mon Sep 17 00:00:00 2001 From: mjakupi Date: Tue, 20 Jun 2023 14:16:16 +0200 Subject: [PATCH] Fix for Issue#86 #87 --- .../src/lib/models/joyride-options.class.ts | 1 + .../src/lib/services/document.service.ts | 77 +++++++++++++------ .../lib/services/joyride-options.service.ts | 70 ++++++----------- .../src/lib/services/joyride-step.service.ts | 23 ++++-- 4 files changed, 95 insertions(+), 76 deletions(-) diff --git a/projects/ngx-joyride/src/lib/models/joyride-options.class.ts b/projects/ngx-joyride/src/lib/models/joyride-options.class.ts index 734256a1..7b8329b6 100644 --- a/projects/ngx-joyride/src/lib/models/joyride-options.class.ts +++ b/projects/ngx-joyride/src/lib/models/joyride-options.class.ts @@ -10,6 +10,7 @@ export class JoyrideOptions { showPrevButton?: boolean; customTexts?: CustomTexts; logsEnabled?: boolean; + fixedHeader?: string; } export class ICustomTexts { diff --git a/projects/ngx-joyride/src/lib/services/document.service.ts b/projects/ngx-joyride/src/lib/services/document.service.ts index 7d299ca5..dcad79b6 100644 --- a/projects/ngx-joyride/src/lib/services/document.service.ts +++ b/projects/ngx-joyride/src/lib/services/document.service.ts @@ -1,6 +1,5 @@ -import { Injectable, ElementRef, Inject, PLATFORM_ID } from '@angular/core'; +import { Injectable, ElementRef } from '@angular/core'; import { DomRefService } from './dom.service'; -import { isPlatformBrowser } from "@angular/common"; export interface IDocumentService { getElementFixedTop(elementRef: ElementRef): number; @@ -28,15 +27,11 @@ export interface IDocumentService { export class DocumentService implements IDocumentService { private documentHeight: number; - constructor(private readonly DOMService: DomRefService, @Inject(PLATFORM_ID) platformId: Object) { - if (!isPlatformBrowser(platformId)) { - return; - } + constructor(private readonly DOMService: DomRefService) { this.setDocumentHeight(); - var doc = DOMService.getNativeDocument(); - if (doc && !doc.elementsFromPoint) { + if (!document.elementsFromPoint) { // IE 11 - Edge browsers - doc.elementsFromPoint = this.elementsFromPoint.bind(this); + document.elementsFromPoint = this.elementsFromPoint.bind(this); } } @@ -107,33 +102,55 @@ export class DocumentService implements IDocumentService { if (elements1.length === 0 && elements2.length === 0) return 1; if ( this.getFirstElementWithoutKeyword(elements1, keywordToDiscard) !== - elementRef.nativeElement || + elementRef.nativeElement || this.getFirstElementWithoutKeyword(elements2, keywordToDiscard) !== - elementRef.nativeElement + elementRef.nativeElement ) { return 2; } return 3; } - scrollIntoView(elementRef: ElementRef, isElementFixed: boolean): void { + // scrollIntoView(elementRef: ElementRef, isElementFixed: boolean, fixedHeaderHeight?: number): void { + // 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 - fixedHeaderHeight); + // } else { + // // IE 11 - Edge browsers + // firstScrollableParent.scrollTop = top - fixedHeaderHeight; + // } + // } else { + // this.DOMService.getNativeWindow().scrollTo(0, top - fixedHeaderHeight); + // } + // } + + scrollIntoView(elementRef: ElementRef, isElementFixed: boolean, fixedHeaderHeight?: number): void { const firstScrollableParent = this.getFirstScrollableParent( elementRef.nativeElement ); const top = isElementFixed ? this.getElementFixedTop(elementRef) - : this.getElementAbsoluteTop(elementRef); + : this.firstScrollableParentScrollOffset(elementRef).y + elementRef.nativeElement.getBoundingClientRect().top; + // : this.getElementAbsoluteTop(elementRef); // Returns incorrect values if body elem doesnt have scroll and some custom child elem of body has scrollbar if ( firstScrollableParent !== this.DOMService.getNativeDocument().body ) { if (firstScrollableParent.scrollTo) { - firstScrollableParent.scrollTo(0, top - 150); + firstScrollableParent.scrollTo(0, top - fixedHeaderHeight); } else { // IE 11 - Edge browsers - firstScrollableParent.scrollTop = top - 150; + firstScrollableParent.scrollTop = top - fixedHeaderHeight; } } else { - this.DOMService.getNativeWindow().scrollTo(0, top - 150); + this.DOMService.getNativeWindow().scrollTo(0, top - fixedHeaderHeight); } } @@ -192,16 +209,16 @@ export class DocumentService implements IDocumentService { const scroll = (node: any) => regex.test( style(node, 'overflow') + - style(node, 'overflow-y') + - style(node, 'overflow-x') + style(node, 'overflow-y') + + style(node, 'overflow-x') ); const scrollparent = (node: any): any => { return !node || node === this.DOMService.getNativeDocument().body ? this.DOMService.getNativeDocument().body : scroll(node) - ? node - : scrollparent(node.parentNode); + ? node + : scrollparent(node.parentNode); }; return scrollparent(node); @@ -219,6 +236,20 @@ export class DocumentService implements IDocumentService { ); } + private firstScrollableParentScrollOffset(elementRef?: ElementRef) { + if(elementRef) { + const firstScrollableParent = this.getFirstScrollableParent( + elementRef.nativeElement + ); + if(firstScrollableParent) { + return { + x: firstScrollableParent.scrollLeft, + y: firstScrollableParent.scrollTop + } + } + } + } + private getScrollOffsets() { const winReference = this.DOMService.getNativeWindow(); const docReference = this.DOMService.getNativeDocument(); @@ -257,8 +288,8 @@ export class DocumentService implements IDocumentService { parent = false; } } while (parent); - parents.forEach(function(parent) { - return (parent.style.pointerEvents = 'all'); + parents.forEach(function(p) { + return (p.style.pointerEvents = 'all'); }); return parents; } @@ -270,7 +301,7 @@ export class DocumentService implements IDocumentService { while ( elements[0] && elements[0].classList.toString().includes(keyword) - ) { + ) { elements.shift(); } return elements[0]; diff --git a/projects/ngx-joyride/src/lib/services/joyride-options.service.ts b/projects/ngx-joyride/src/lib/services/joyride-options.service.ts index fae94346..4a820b06 100644 --- a/projects/ngx-joyride/src/lib/services/joyride-options.service.ts +++ b/projects/ngx-joyride/src/lib/services/joyride-options.service.ts @@ -1,9 +1,5 @@ import { Injectable } from '@angular/core'; -import { - JoyrideOptions, - CustomTexts, - ICustomTexts -} from '../models/joyride-options.class'; +import { JoyrideOptions, CustomTexts, ICustomTexts } from '../models/joyride-options.class'; import { of, Observable } from 'rxjs'; export const DEFAULT_THEME_COLOR = '#3b5560'; @@ -41,42 +37,26 @@ export interface IJoyrideOptionsService { export class JoyrideOptionsService implements IJoyrideOptionsService { private themeColor: string = DEFAULT_THEME_COLOR; private stepDefaultPosition: string = STEP_DEFAULT_POSITION; - private logsEnabled = false; - private showCounter = true; - private showPrevButton = true; + private logsEnabled: boolean = true; + private showCounter: boolean = true; + private showPrevButton: boolean = true; private stepsOrder: string[] = []; private firstStep: string; + private fixedHeader: string; private waitingTime: number; private customTexts: ObservableCustomTexts; setOptions(options: JoyrideOptions) { 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.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); + this.fixedHeader = options.fixedHeader; + 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() { @@ -99,6 +79,10 @@ export class JoyrideOptionsService implements IJoyrideOptionsService { return this.firstStep; } + getFixedHeader() { + return this.fixedHeader; + } + getWaitingTime() { return this.waitingTime; } @@ -120,20 +104,17 @@ export class JoyrideOptionsService implements IJoyrideOptionsService { } private setCustomText(texts: CustomTexts) { - let prev: string | Observable; - let next: string | Observable; - let done; - let close; + let prev, next, done, close: string | Observable; 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 = { + this.customTexts = { prev: this.toObservable(prev), next: this.toObservable(next), done: this.toObservable(done), close: this.toObservable(close) - } as ObservableCustomTexts; + }; } private toObservable(value: string | Observable) { @@ -141,17 +122,12 @@ export class JoyrideOptionsService implements IJoyrideOptionsService { } private hexToRgb(hex: any): string { - const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, (m: any, r: any, g: any, b: any) => { 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; + var 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; } } diff --git a/projects/ngx-joyride/src/lib/services/joyride-step.service.ts b/projects/ngx-joyride/src/lib/services/joyride-step.service.ts index e0e8eb5f..e7572bad 100644 --- a/projects/ngx-joyride/src/lib/services/joyride-step.service.ts +++ b/projects/ngx-joyride/src/lib/services/joyride-step.service.ts @@ -32,7 +32,7 @@ export class JoyrideStepService implements IJoyrideStepService { private winTopPosition: number = 0; private winBottomPosition: number = 0; private stepsObserver: ReplaySubject = new ReplaySubject(); - + private fixedHeaderHeight: number = 0; constructor( private readonly backDropService: JoyrideBackdropService, private readonly eventListener: EventListenerService, @@ -105,10 +105,10 @@ export class JoyrideStepService implements IJoyrideStepService { this.tryShowStep(StepActionType.NEXT); } - private async navigateToStepPage(action: StepActionType) { + private navigateToStepPage(action: StepActionType) { let stepRoute = this.stepsContainerService.getStepRoute(action); if (stepRoute) { - return await this.router.navigate([stepRoute]); + this.router.navigate([stepRoute]); } } @@ -120,9 +120,16 @@ export class JoyrideStepService implements IJoyrideStepService { }); } - private async tryShowStep(actionType: StepActionType) { - await this.navigateToStepPage(actionType); + private tryShowStep(actionType: StepActionType) { + this.navigateToStepPage(actionType); const timeout = this.optionsService.getWaitingTime(); + const fixedHeader = this.optionsService.getFixedHeader(); + if(fixedHeader) { + const fixedHeaderEl = this.DOMService.getNativeDocument().body.querySelector(fixedHeader); + if(fixedHeaderEl) { + this.fixedHeaderHeight = fixedHeaderEl.getBoundingClientRect().height; + } + } if (timeout > 100) this.backDropService.remove(); setTimeout(() => { try { @@ -144,12 +151,12 @@ export class JoyrideStepService implements IJoyrideStepService { this.currentStep = this.stepsContainerService.get(actionType); if (this.currentStep == null) throw new JoyrideStepDoesNotExist(''); - this.notifyStepClicked(actionType); // Scroll the element to get it visible if it's in a scrollable element this.scrollIfElementBeyondOtherElements(); this.backDropService.draw(this.currentStep); this.drawStep(this.currentStep); this.scrollIfStepAndTargetAreNotVisible(); + this.notifyStepClicked(actionType); } private notifyStepClicked(actionType: StepActionType) { @@ -230,6 +237,10 @@ export class JoyrideStepService implements IJoyrideStepService { if (this.isElementBeyondOthers() === 2) { this.documentService.scrollToTheBottom(this.currentStep.targetViewContainer.element); } + + if (this.isElementBeyondOthers() === 2 && this.documentService.isParentScrollable(this.currentStep.targetViewContainer.element)) { // Added to handle middle section & with parentscrollable + this.documentService.scrollIntoView(this.currentStep.targetViewContainer.element, this.currentStep.isElementOrAncestorFixed, this.fixedHeaderHeight); + } if (this.isElementBeyondOthers() === 1 && this.documentService.isParentScrollable(this.currentStep.targetViewContainer.element)) { this.documentService.scrollIntoView(this.currentStep.targetViewContainer.element, this.currentStep.isElementOrAncestorFixed); }