ngx-ui-tour-core
Version:
UI tour library for Angular 12+
1 lines • 74.8 kB
Source Map (JSON)
{"version":3,"file":"ngx-ui-tour-core.mjs","sources":["../../../libs/ngx-ui-tour-core/src/lib/utils/deep-merge.ts","../../../libs/ngx-ui-tour-core/src/lib/utils/is-covered.ts","../../../libs/ngx-ui-tour-core/src/lib/utils/is-in-viewport.ts","../../../libs/ngx-ui-tour-core/src/lib/utils/overflow-utils.ts","../../../libs/ngx-ui-tour-core/src/lib/utils/get-scrollable-parent.ts","../../../libs/ngx-ui-tour-core/src/lib/utils/scroll-utils.ts","../../../libs/ngx-ui-tour-core/src/lib/scrolling.service.ts","../../../libs/ngx-ui-tour-core/src/lib/tour-resize-observer.service.ts","../../../libs/ngx-ui-tour-core/src/lib/tour-backdrop.service.ts","../../../libs/ngx-ui-tour-core/src/lib/anchor-click.service.ts","../../../libs/ngx-ui-tour-core/src/lib/scroll-blocking.service.ts","../../../libs/ngx-ui-tour-core/src/lib/tour.service.ts","../../../libs/ngx-ui-tour-core/src/lib/tour-hotkey-listener.component.ts","../../../libs/ngx-ui-tour-core/src/lib/base-tour-proxy-anchor.ts","../../../libs/ngx-ui-tour-core/src/ngx-ui-tour-core.ts"],"sourcesContent":["type PlainObject = Record<string | number | symbol, unknown>;\r\n\r\nexport function deepMerge<T>(...objects: T[]): T {\r\n return objects.reduce((acc: T, cur: T) => {\r\n\r\n cur ??= {} as T;\r\n const keys = Object.keys(cur) as (keyof T)[];\r\n\r\n for (const key of keys) {\r\n const accValue = acc[key] as PlainObject | unknown,\r\n curValue = cur[key] as PlainObject | unknown;\r\n\r\n if (isPlainObject(accValue) && isPlainObject(curValue)) {\r\n acc[key] = deepMerge(accValue, curValue) as T[keyof T];\r\n } else {\r\n acc[key] = curValue as T[keyof T];\r\n }\r\n }\r\n\r\n return acc;\r\n }, {} as T);\r\n}\r\n\r\nfunction isPlainObject(value: unknown | PlainObject): value is PlainObject {\r\n return value instanceof Object && value.constructor === Object;\r\n}\r\n","import {ElementSides} from './is-in-viewport';\r\n\r\nexport function isCovered(htmlElement: HTMLElement, sidesToCheck: ElementSides = ElementSides.All): boolean {\r\n const rect = htmlElement.getBoundingClientRect(),\r\n topEl = document.elementFromPoint(rect.left, rect.top),\r\n bottomEl = document.elementFromPoint(rect.right, rect.bottom),\r\n isTopCovered = !!topEl && topEl !== htmlElement && !areElementsRelated(topEl, htmlElement),\r\n isBottomCovered = !!bottomEl && bottomEl !== htmlElement && !areElementsRelated(bottomEl, htmlElement);\r\n\r\n if (sidesToCheck === ElementSides.Top) {\r\n return isTopCovered;\r\n }\r\n\r\n if (sidesToCheck === ElementSides.Bottom) {\r\n return isBottomCovered;\r\n }\r\n\r\n return isTopCovered || isBottomCovered;\r\n}\r\n\r\nfunction areElementsRelated(el1: Node, el2: Node): boolean {\r\n return el1.contains(el2) || el2.contains(el1);\r\n}","export const enum ElementSides {\r\n Top,\r\n Bottom,\r\n All\r\n}\r\n\r\nexport function isInViewport(htmlElement: HTMLElement, sidesToCheck: ElementSides = ElementSides.All): boolean {\r\n const viewportWidth = window.innerWidth,\r\n viewportHeight = window.innerHeight,\r\n boundingRectangle = htmlElement.getBoundingClientRect(),\r\n areCornersInViewport = boundingRectangle.left >= 0 && boundingRectangle.right <= viewportWidth,\r\n isTopInViewport = boundingRectangle.top >= 0,\r\n isBottomInViewport = boundingRectangle.bottom <= viewportHeight;\r\n\r\n if (sidesToCheck === ElementSides.Top) {\r\n return isTopInViewport && areCornersInViewport;\r\n }\r\n if (sidesToCheck === ElementSides.Bottom) {\r\n return isBottomInViewport && areCornersInViewport;\r\n }\r\n\r\n return isTopInViewport && isBottomInViewport && areCornersInViewport;\r\n}\r\n","export class OverflowUtils {\r\n\r\n static getVisibleSection(childRect: DOMRect, containerRect: DOMRect): DOMRect {\r\n return OverflowUtils._isHeightOverflowing(childRect, containerRect) ?\r\n OverflowUtils._getOverlap(childRect, containerRect) :\r\n childRect;\r\n }\r\n\r\n static isHeightOverflowing(child: HTMLElement | DOMRect, container: HTMLElement | DOMRect): boolean {\r\n return OverflowUtils._isHeightOverflowing(\r\n child instanceof HTMLElement ? child.getBoundingClientRect() : child,\r\n container instanceof HTMLElement ? container.getBoundingClientRect() : container,\r\n );\r\n }\r\n\r\n private static _isHeightOverflowing(childRect: DOMRect, containerRect: DOMRect): boolean {\r\n return containerRect.height < childRect.height;\r\n }\r\n\r\n private static _getOverlap(a: DOMRect, b: DOMRect): DOMRect {\r\n const top = Math.max(a.top, b.top),\r\n left = Math.max(a.left, b.left),\r\n right = Math.min(a.right, b.right),\r\n bottom = Math.min(a.bottom, b.bottom);\r\n return new DOMRect(left, top, right - left, bottom - top);\r\n }\r\n}\r\n\r\n","export function getScrollableParent(node: Node): HTMLElement | null {\r\n if (!(node instanceof HTMLElement || node instanceof ShadowRoot)) {\r\n return null;\r\n }\r\n\r\n const element = node instanceof ShadowRoot ? node.host as HTMLElement : node;\r\n\r\n const style = getComputedStyle(element),\r\n isScrollable = element.scrollHeight > element.clientHeight,\r\n overflow = style.overflowY,\r\n scrollableOverflow = ['scroll', 'auto'];\r\n\r\n if (isScrollable && scrollableOverflow.includes(overflow)) {\r\n return element;\r\n }\r\n\r\n return getScrollableParent(element.parentNode);\r\n}\r\n","import {getScrollableParent} from './get-scrollable-parent';\r\n\r\nexport class ScrollUtils {\r\n \r\n static getScrollContainer(\r\n anchorEl: HTMLElement,\r\n userScrollContainer: string | HTMLElement | undefined\r\n ): HTMLElement | null {\r\n if (typeof userScrollContainer === 'string') {\r\n return document.documentElement.querySelector(userScrollContainer);\r\n }\r\n if (userScrollContainer instanceof HTMLElement) {\r\n return userScrollContainer;\r\n }\r\n\r\n return getScrollableParent(anchorEl);\r\n }\r\n \r\n}\r\n","import {ElementSides, isCovered, isInViewport, OverflowUtils, ScrollUtils} from './utils';\r\nimport {debounceTime, firstValueFrom, fromEvent, map, of, timeout} from 'rxjs';\r\nimport {inject, Injectable, PLATFORM_ID} from '@angular/core';\r\nimport {DOCUMENT, isPlatformBrowser} from '@angular/common';\r\n\r\nexport interface ScrollOptions {\r\n center: boolean;\r\n smoothScroll: boolean;\r\n scrollContainer?: string | HTMLElement;\r\n}\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ScrollingService {\r\n\r\n private readonly platformId = inject(PLATFORM_ID);\r\n private readonly isBrowser = isPlatformBrowser(this.platformId);\r\n private readonly document = inject(DOCUMENT);\r\n private readonly window = this.document.defaultView;\r\n private scrollOptions: ScrollOptions;\r\n private anchorEl: HTMLElement;\r\n\r\n ensureVisible(anchorElement: HTMLElement, options: ScrollOptions): Promise<void> {\r\n this.scrollOptions = options;\r\n this.anchorEl = anchorElement;\r\n\r\n const behavior: ScrollBehavior = options.smoothScroll && this.isBrowser ? 'smooth' : 'auto';\r\n\r\n const userScrollContainer = this.scrollOptions.scrollContainer,\r\n scrollContainer = ScrollUtils.getScrollContainer(anchorElement, userScrollContainer) ?? document.documentElement;\r\n\r\n if (OverflowUtils.isHeightOverflowing(anchorElement, scrollContainer)) {\r\n anchorElement.scrollIntoView({\r\n block: 'start',\r\n inline: 'start',\r\n behavior\r\n });\r\n } else if (options.center && !('safari' in this.window)) {\r\n anchorElement.scrollIntoView({\r\n block: 'center',\r\n inline: 'center',\r\n behavior\r\n });\r\n } else if (!isInViewport(anchorElement, ElementSides.Bottom) || isCovered(anchorElement, ElementSides.Bottom)) {\r\n anchorElement.scrollIntoView({\r\n block: 'end',\r\n inline: 'nearest',\r\n behavior\r\n });\r\n } else if (!isInViewport(anchorElement, ElementSides.Top) || isCovered(anchorElement, ElementSides.Top)) {\r\n anchorElement.scrollIntoView({\r\n block: 'start',\r\n inline: 'nearest',\r\n behavior\r\n });\r\n } else {\r\n return Promise.resolve();\r\n }\r\n\r\n return behavior === 'smooth' ? firstValueFrom(this.waitForScrollFinish$) : Promise.resolve();\r\n }\r\n\r\n private get waitForScrollFinish$() {\r\n const userScrollContainer = this.scrollOptions.scrollContainer,\r\n // Default here is \"document\" instead of \"document.documentElement\" on purpose\r\n scrollContainer = ScrollUtils.getScrollContainer(this.anchorEl, userScrollContainer) ?? document;\r\n\r\n return fromEvent(scrollContainer, 'scroll')\r\n .pipe(\r\n timeout({\r\n each: 75,\r\n with: () => of(undefined)\r\n }),\r\n debounceTime(50),\r\n map(() => undefined)\r\n );\r\n }\r\n\r\n}\r\n","import {DOCUMENT, isPlatformBrowser} from '@angular/common';\r\nimport {inject, Injectable, PLATFORM_ID} from '@angular/core';\r\nimport {debounceTime, fromEvent, merge, Subject} from 'rxjs';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class TourResizeObserverService {\r\n\r\n private readonly resizeElSubject = new Subject<void>();\r\n private readonly platformId = inject(PLATFORM_ID);\r\n private readonly isResizeObserverSupported = isPlatformBrowser(this.platformId) && !!ResizeObserver;\r\n private resizeObserver?: ResizeObserver;\r\n private readonly document = inject(DOCUMENT);\r\n private readonly window = this.document.defaultView;\r\n\r\n public readonly resize$ = merge(\r\n this.resizeElSubject,\r\n fromEvent(this.window, 'resize')\r\n ).pipe(\r\n debounceTime(10)\r\n );\r\n\r\n observeElement(target: Element) {\r\n if (this.isResizeObserverSupported && !this.resizeObserver) {\r\n this.resizeObserver = new ResizeObserver(\r\n () => this.resizeElSubject.next()\r\n );\r\n }\r\n\r\n this.resizeObserver?.observe(target);\r\n }\r\n\r\n unobserveElement(target: Element) {\r\n this.resizeObserver?.unobserve(target);\r\n }\r\n\r\n disconnect() {\r\n this.resizeObserver?.disconnect();\r\n this.resizeObserver = undefined;\r\n }\r\n\r\n}\r\n","import type {ElementRef} from '@angular/core';\r\nimport {inject, Injectable, RendererFactory2} from '@angular/core';\r\nimport type {Subscription} from 'rxjs';\r\nimport {ScrollingService} from './scrolling.service';\r\nimport {TourResizeObserverService} from './tour-resize-observer.service';\r\nimport type {IStepOption} from './tour.service';\r\nimport {DOCUMENT} from '@angular/common';\r\nimport {OverflowUtils, ScrollUtils} from './utils';\r\n\r\ninterface Rectangle {\r\n width: number;\r\n height: number;\r\n top: number;\r\n left: number;\r\n}\r\n\r\nexport interface BackdropConfig {\r\n zIndex?: string;\r\n backgroundColor?: string;\r\n /**\r\n * Parent container CSS selector or html element reference. Set to fix backdrop stacking issues. Defaults to body.\r\n */\r\n parentContainer?: string | HTMLElement;\r\n /**\r\n * Offset in pixels to add space between the backdrop and the anchor element.\r\n */\r\n offset?: number;\r\n}\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class TourBackdropService {\r\n\r\n private backdropElements: HTMLElement[];\r\n private targetHtmlElement: HTMLElement;\r\n private step: IStepOption;\r\n private resizeSubscription: Subscription;\r\n private isSpotlightClosed = false;\r\n\r\n private readonly rendererFactory = inject(RendererFactory2);\r\n private readonly renderer = this.rendererFactory.createRenderer(null, null);\r\n private readonly resizeObserverService = inject(TourResizeObserverService);\r\n private readonly scrollingService = inject(ScrollingService);\r\n private readonly document = inject(DOCUMENT);\r\n\r\n public show(targetElement: ElementRef, step: IStepOption) {\r\n if (this.targetHtmlElement) {\r\n this.resizeObserverService.unobserveElement(this.targetHtmlElement);\r\n }\r\n\r\n this.targetHtmlElement = targetElement.nativeElement;\r\n this.step = step;\r\n\r\n this.resizeObserverService.observeElement(this.targetHtmlElement);\r\n\r\n if (!this.backdropElements) {\r\n this.backdropElements = this.createBackdropElements();\r\n this.subscribeToResizeEvents();\r\n }\r\n\r\n this.isSpotlightClosed = false;\r\n this.setBackdropPosition();\r\n }\r\n\r\n public closeSpotlight() {\r\n if (!this.backdropElements) {\r\n return;\r\n }\r\n\r\n const targetRect = this.targetHtmlElement.getBoundingClientRect(),\r\n centerX = targetRect.left + (targetRect.width / 2),\r\n centerY = targetRect.top + (targetRect.height / 2),\r\n centerRect = {\r\n top: centerY,\r\n right: centerX,\r\n bottom: centerY,\r\n left: centerX,\r\n width: 0,\r\n height: 0\r\n } as DOMRect;\r\n\r\n this.isSpotlightClosed = true;\r\n this.setBackdropPosition(centerRect);\r\n }\r\n\r\n private setBackdropPosition(rectangle: DOMRect = null) {\r\n const docEl = this.document.documentElement,\r\n scrollContainer = ScrollUtils.getScrollContainer(this.targetHtmlElement, this.step.scrollContainer) ?? docEl,\r\n elementBoundingRect = rectangle ?? this.targetHtmlElement.getBoundingClientRect(),\r\n scrollContainerRect = scrollContainer.getBoundingClientRect(),\r\n visibleSection = OverflowUtils.getVisibleSection(elementBoundingRect, scrollContainerRect),\r\n scrollHeight = docEl.scrollHeight,\r\n scrollWidth = docEl.scrollWidth,\r\n window = this.document.defaultView,\r\n scrollX = window.scrollX,\r\n scrollY = window.scrollY,\r\n offset = this.isSpotlightClosed ? 0 : this.step.backdropConfig?.offset ?? 0,\r\n leftRect: Rectangle = {\r\n width: visibleSection.left + scrollX - offset,\r\n height: scrollHeight,\r\n top: 0,\r\n left: 0\r\n },\r\n topRect: Rectangle = {\r\n width: visibleSection.width + offset * 2,\r\n height: visibleSection.top + scrollY - offset,\r\n top: 0,\r\n left: visibleSection.left + scrollX - offset\r\n },\r\n bottomRect: Rectangle = {\r\n width: visibleSection.width + offset * 2,\r\n height: scrollHeight - (visibleSection.bottom + scrollY) - offset,\r\n top: visibleSection.bottom + scrollY + offset,\r\n left: visibleSection.left + scrollX - offset\r\n },\r\n rightRect: Rectangle = {\r\n width: scrollWidth - (visibleSection.right + scrollX) - offset,\r\n height: scrollHeight,\r\n top: 0,\r\n left: visibleSection.right + scrollX + offset\r\n },\r\n rectangles: Rectangle[] = [leftRect, topRect, bottomRect, rightRect];\r\n\r\n for (let i = 0; i < rectangles.length; i++) {\r\n const styles = this.createBackdropStyles(rectangles[i]);\r\n this.applyStyles(styles, this.backdropElements[i]);\r\n }\r\n }\r\n\r\n private subscribeToResizeEvents() {\r\n this.resizeSubscription = this.resizeObserverService.resize$\r\n .subscribe(\r\n () => {\r\n this.setBackdropPosition();\r\n if (!this.step.disableScrollToAnchor) {\r\n this.scrollingService.ensureVisible(this.targetHtmlElement, {\r\n center: this.step.centerAnchorOnScroll,\r\n smoothScroll: false\r\n });\r\n }\r\n }\r\n );\r\n }\r\n\r\n public close() {\r\n if (this.backdropElements) {\r\n this.resizeObserverService.unobserveElement(this.targetHtmlElement);\r\n this.removeBackdropElement();\r\n this.resizeSubscription.unsubscribe();\r\n }\r\n }\r\n\r\n public disconnectResizeObserver() {\r\n this.resizeObserverService.disconnect();\r\n }\r\n\r\n private removeBackdropElement() {\r\n this.backdropElements.forEach(\r\n backdropElement => this.renderer.removeChild(this.parentContainer, backdropElement)\r\n );\r\n this.backdropElements = undefined;\r\n }\r\n\r\n private applyStyles(styles: Partial<CSSStyleDeclaration>, element: HTMLElement) {\r\n for (const name of Object.keys(styles)) {\r\n this.renderer.setStyle(element, name, styles[name as keyof CSSStyleDeclaration]);\r\n }\r\n }\r\n\r\n private createBackdropStyles(rectangle: Rectangle) {\r\n const config = this.step.backdropConfig,\r\n normalizedRect = {\r\n ...rectangle,\r\n width: Math.max(rectangle.width, 0),\r\n height: Math.max(rectangle.height, 0)\r\n };\r\n\r\n return {\r\n position: 'absolute',\r\n width: `${normalizedRect.width}px`,\r\n height: `${normalizedRect.height}px`,\r\n top: `${normalizedRect.top}px`,\r\n left: `${normalizedRect.left}px`,\r\n backgroundColor: config?.backgroundColor ?? 'rgba(0, 0, 0, 0.7)',\r\n zIndex: config?.zIndex ?? '101'\r\n } as Partial<CSSStyleDeclaration>;\r\n }\r\n\r\n private createBackdropElement() {\r\n const backdropElement = this.renderer.createElement('div');\r\n this.renderer.addClass(backdropElement, 'ngx-ui-tour_backdrop');\r\n this.renderer.appendChild(this.parentContainer, backdropElement);\r\n return backdropElement;\r\n }\r\n\r\n private createBackdropElements() {\r\n return Array\r\n .from({length: 4})\r\n .map(() => this.createBackdropElement());\r\n }\r\n\r\n private get parentContainer(): HTMLElement {\r\n const parent = this.step.backdropConfig?.parentContainer;\r\n\r\n if (parent instanceof HTMLElement) {\r\n return parent;\r\n }\r\n if (typeof parent === 'string') {\r\n const queryResult = this.document.documentElement.querySelector(parent) as HTMLElement;\r\n\r\n return queryResult ?? this.document.body;\r\n }\r\n\r\n return this.document.body;\r\n }\r\n\r\n}\r\n","import {inject, Injectable, RendererFactory2} from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class AnchorClickService {\r\n\r\n private readonly rendererFactory = inject(RendererFactory2);\r\n private readonly renderer = this.rendererFactory.createRenderer(null, null);\r\n\r\n private unListenToAnchorClickFn: () => void;\r\n\r\n public removeListener() {\r\n if (this.unListenToAnchorClickFn) {\r\n this.unListenToAnchorClickFn();\r\n this.unListenToAnchorClickFn = undefined;\r\n }\r\n }\r\n\r\n public addListener(anchorEl: HTMLElement, callback: () => void) {\r\n this.unListenToAnchorClickFn = this.renderer.listen(anchorEl, 'click', callback);\r\n }\r\n}","import {inject, Injectable, PLATFORM_ID, RendererFactory2} from '@angular/core';\r\nimport {isPlatformBrowser} from '@angular/common';\r\nimport {ScrollUtils} from './utils';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ScrollBlockingService {\r\n\r\n private isEnabled = false;\r\n private userScrollContainer: string | HTMLElement;\r\n\r\n private readonly platformId = inject(PLATFORM_ID);\r\n private readonly isBrowser = isPlatformBrowser(this.platformId);\r\n private readonly rendererFactory = inject(RendererFactory2);\r\n private readonly renderer = this.rendererFactory.createRenderer(null, null);\r\n\r\n enable(scrollContainer: string | HTMLElement) {\r\n if (!this.isBrowser || this.isEnabled) {\r\n return;\r\n }\r\n\r\n this.userScrollContainer = scrollContainer;\r\n this.toggleOverflow();\r\n this.isEnabled = true;\r\n }\r\n\r\n disable() {\r\n if (!this.isEnabled) {\r\n return;\r\n }\r\n\r\n this.toggleOverflow();\r\n this.isEnabled = false;\r\n }\r\n\r\n private toggleOverflow() {\r\n // Don't try to automatically detect scroll container here since that breaks smooth scrolling\r\n const scrollContainer = ScrollUtils.getScrollContainer(null, this.userScrollContainer) ?? document.documentElement;\r\n\r\n if (this.isEnabled) {\r\n this.renderer.removeStyle(scrollContainer, 'overflow');\r\n } else {\r\n this.renderer.setStyle(scrollContainer, 'overflow', 'hidden');\r\n }\r\n }\r\n\r\n}\r\n","import {inject, Injectable} from '@angular/core';\r\nimport type {IsActiveMatchOptions, UrlSegment} from '@angular/router';\r\nimport {NavigationStart, Router} from '@angular/router';\r\n\r\nimport type {TourAnchorDirective} from './tour-anchor.directive';\r\nimport type {Observable} from 'rxjs';\r\nimport {delay, filter, first, map, merge as mergeStatic, of, Subject, takeUntil, timeout} from 'rxjs';\r\nimport {ScrollingService} from './scrolling.service';\r\nimport type {BackdropConfig} from './tour-backdrop.service';\r\nimport {TourBackdropService} from './tour-backdrop.service';\r\nimport {AnchorClickService} from './anchor-click.service';\r\nimport {ScrollBlockingService} from './scroll-blocking.service';\r\nimport {deepMerge} from './utils';\r\n\r\nexport interface StepDimensions {\r\n width?: string;\r\n minWidth?: string;\r\n maxWidth?: string;\r\n}\r\n\r\nexport interface IStepOption {\r\n stepId?: string;\r\n anchorId?: string;\r\n title?: string;\r\n content?: string;\r\n route?: string | UrlSegment[];\r\n nextStep?: number | string;\r\n prevStep?: number | string;\r\n disableScrollToAnchor?: boolean;\r\n centerAnchorOnScroll?: boolean;\r\n smoothScroll?: boolean;\r\n /**\r\n * CSS selector or html element reference. Only set this config if you have enabled \"smoothScroll\" and tour step\r\n * description pops-up before scrolling has finished or doesn't show up at all. This should only be the case when\r\n * scroll container is part of shadow DOM.\r\n */\r\n scrollContainer?: string | HTMLElement;\r\n prevBtnTitle?: string;\r\n nextBtnTitle?: string;\r\n endBtnTitle?: string;\r\n enableBackdrop?: boolean;\r\n backdropConfig?: BackdropConfig;\r\n isAsync?: boolean;\r\n asyncStepTimeout?: number;\r\n isOptional?: boolean;\r\n delayAfterNavigation?: number;\r\n delayBeforeStepShow?: number;\r\n nextOnAnchorClick?: boolean;\r\n duplicateAnchorHandling?: 'error' | 'registerFirst' | 'registerLast';\r\n disablePageScrolling?: boolean;\r\n allowUserInitiatedNavigation?: boolean;\r\n stepDimensions?: StepDimensions;\r\n popoverClass?: string;\r\n showProgress?: boolean;\r\n}\r\n\r\nexport enum TourState {\r\n OFF,\r\n ON,\r\n PAUSED\r\n}\r\n\r\nexport enum Direction {\r\n Forwards,\r\n Backwards\r\n}\r\n\r\nexport interface StepChangeParams<T extends IStepOption = IStepOption> {\r\n step: T;\r\n direction: Direction;\r\n}\r\n\r\nconst DEFAULT_STEP_OPTIONS: IStepOption = {\r\n disableScrollToAnchor: false,\r\n prevBtnTitle: 'Prev',\r\n nextBtnTitle: 'Next',\r\n endBtnTitle: 'End',\r\n enableBackdrop: false,\r\n isAsync: false,\r\n isOptional: false,\r\n delayAfterNavigation: 100,\r\n delayBeforeStepShow: 0,\r\n nextOnAnchorClick: false,\r\n duplicateAnchorHandling: 'error',\r\n centerAnchorOnScroll: true,\r\n disablePageScrolling: true,\r\n smoothScroll: true,\r\n allowUserInitiatedNavigation: false,\r\n stepDimensions: {\r\n minWidth: '250px',\r\n maxWidth: '280px',\r\n width: 'auto'\r\n },\r\n showProgress: true\r\n};\r\n\r\n// noinspection JSUnusedGlobalSymbols\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class TourService<T extends IStepOption = IStepOption> {\r\n\r\n public stepShow$ = new Subject<StepChangeParams<T>>();\r\n public stepHide$ = new Subject<StepChangeParams<T>>();\r\n public initialize$ = new Subject<T[]>();\r\n public start$ = new Subject<void>();\r\n public end$ = new Subject<void>();\r\n public pause$ = new Subject<void>();\r\n public resume$ = new Subject<void>();\r\n public anchorRegister$ = new Subject<string>();\r\n public anchorUnregister$ = new Subject<string>();\r\n public events$: Observable<{ name: string; value: unknown }> = mergeStatic(\r\n this.stepShow$.pipe(map(value => ({name: 'stepShow', value}))),\r\n this.stepHide$.pipe(map(value => ({name: 'stepHide', value}))),\r\n this.initialize$.pipe(map(value => ({name: 'initialize', value}))),\r\n this.start$.pipe(map(value => ({name: 'start', value}))),\r\n this.end$.pipe(map(value => ({name: 'end', value}))),\r\n this.pause$.pipe(map(value => ({name: 'pause', value}))),\r\n this.resume$.pipe(map(value => ({name: 'resume', value}))),\r\n this.anchorRegister$.pipe(\r\n map(value => ({\r\n name: 'anchorRegister',\r\n value\r\n }))\r\n ),\r\n this.anchorUnregister$.pipe(\r\n map(value => ({\r\n name: 'anchorUnregister',\r\n value\r\n }))\r\n )\r\n );\r\n\r\n public steps: T[] = [];\r\n public currentStep: T;\r\n\r\n public anchors: Record<string, TourAnchorDirective> = {};\r\n private status: TourState = TourState.OFF;\r\n private isHotKeysEnabled = true;\r\n private direction = Direction.Forwards;\r\n private waitingForScroll = false;\r\n private navigationStarted = false;\r\n private userDefaults: T;\r\n\r\n private readonly router = inject(Router);\r\n private readonly backdrop = inject(TourBackdropService);\r\n private readonly anchorClickService = inject(AnchorClickService);\r\n private readonly scrollBlockingService = inject(ScrollBlockingService);\r\n private readonly scrollingService = inject(ScrollingService);\r\n\r\n public initialize(steps: T[], stepDefaults?: T): void {\r\n if (this.status === TourState.ON) {\r\n console.warn('Can not re-initialize the UI tour while it\\'s still active');\r\n return;\r\n }\r\n\r\n if (steps && steps.length > 0) {\r\n this.status = TourState.OFF;\r\n this.steps = steps.map(\r\n step => deepMerge(\r\n DEFAULT_STEP_OPTIONS as T,\r\n this.userDefaults,\r\n stepDefaults,\r\n step\r\n )\r\n );\r\n this.validateSteps();\r\n this.initialize$.next(this.steps);\r\n this.subscribeToNavigationStartEvent();\r\n }\r\n }\r\n\r\n public setDefaults(defaultOptions: T): void {\r\n this.userDefaults = defaultOptions;\r\n }\r\n\r\n public getDefaults(): T {\r\n return this.userDefaults;\r\n }\r\n\r\n private validateSteps() {\r\n for (const step of this.steps) {\r\n if (step.isAsync && step.isOptional && !step.asyncStepTimeout) {\r\n throw new Error(`Tour step with anchor id \"${step.anchorId}\" can only be both \"async\" and ` +\r\n `\"optional\" when \"asyncStepTimeout\" is specified!`);\r\n }\r\n }\r\n }\r\n\r\n private subscribeToNavigationStartEvent()\r\n {\r\n this.router.events\r\n .pipe(\r\n filter((event): event is NavigationStart => event instanceof NavigationStart),\r\n takeUntil(this.end$)\r\n )\r\n .subscribe(\r\n (event) => {\r\n if (!this.currentStep) {\r\n return;\r\n }\r\n\r\n const browserBackBtnPressed = event.navigationTrigger === 'popstate',\r\n userNavigationAllowed = this.currentStep.allowUserInitiatedNavigation;\r\n\r\n if (!this.navigationStarted && (browserBackBtnPressed || !userNavigationAllowed)) {\r\n this.end();\r\n }\r\n }\r\n );\r\n }\r\n\r\n public disableHotkeys(): void {\r\n this.isHotKeysEnabled = false;\r\n }\r\n\r\n public enableHotkeys(): void {\r\n this.isHotKeysEnabled = true;\r\n }\r\n\r\n public start(): void {\r\n if (this.status === TourState.ON) {\r\n console.warn('tourService.start() called while the tour is already running.');\r\n return;\r\n }\r\n this.startAt(0);\r\n }\r\n\r\n public startAt(stepId: number | string): void {\r\n this.status = TourState.ON;\r\n this.goToStep(this.loadStep(stepId));\r\n this.start$.next();\r\n }\r\n\r\n public end(): void {\r\n if (this.waitingForScroll) {\r\n return;\r\n }\r\n\r\n if (this.status === TourState.OFF) {\r\n return;\r\n }\r\n this.status = TourState.OFF;\r\n this.disableTour();\r\n this.currentStep = undefined;\r\n this.direction = Direction.Forwards;\r\n this.end$.next();\r\n }\r\n\r\n public pause(): void {\r\n this.status = TourState.PAUSED;\r\n this.disableTour();\r\n this.pause$.next();\r\n }\r\n\r\n private disableTour() {\r\n this.hideStep(this.currentStep);\r\n this.anchorClickService.removeListener();\r\n this.backdrop.close();\r\n this.backdrop.disconnectResizeObserver();\r\n this.scrollBlockingService.disable();\r\n }\r\n\r\n public resume(): void {\r\n this.status = TourState.ON;\r\n this.showStep(this.currentStep);\r\n this.resume$.next();\r\n }\r\n\r\n public toggle(pause?: boolean): void {\r\n if (pause) {\r\n if (this.currentStep) {\r\n this.pause();\r\n } else {\r\n this.resume();\r\n }\r\n } else {\r\n if (this.currentStep) {\r\n this.end();\r\n } else {\r\n this.start();\r\n }\r\n }\r\n }\r\n\r\n public next(): void {\r\n if (this.waitingForScroll) {\r\n return;\r\n }\r\n\r\n this.direction = Direction.Forwards;\r\n if (this.hasNext(this.currentStep)) {\r\n this.goToStep(\r\n this.loadStep(\r\n this.currentStep.nextStep ?? this.getStepIndex(this.currentStep) + 1\r\n )\r\n );\r\n }\r\n }\r\n\r\n private getStepIndex(step: T): number {\r\n const index = this.steps.indexOf(step);\r\n\r\n return index < 0 ? 0 : index;\r\n }\r\n\r\n public hasNext(step: T): boolean {\r\n if (!step) {\r\n console.warn('Can\\'t get next step. No currentStep.');\r\n return false;\r\n }\r\n return (\r\n step.nextStep !== undefined ||\r\n (this.getStepIndex(step) < this.steps.length - 1 && !this.isNextOptionalAnchorMissing(step))\r\n );\r\n }\r\n\r\n private isNextOptionalAnchorMissing(step: T): boolean {\r\n const stepIndex = this.getStepIndex(step);\r\n\r\n for (let i = stepIndex + 1; i < this.steps.length; i++) {\r\n const nextStep = this.steps[i];\r\n\r\n if (!nextStep.isOptional || this.anchors[nextStep.anchorId])\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n public prev(): void {\r\n if (this.waitingForScroll) {\r\n return;\r\n }\r\n\r\n this.direction = Direction.Backwards;\r\n if (this.hasPrev(this.currentStep)) {\r\n this.goToStep(\r\n this.loadStep(\r\n this.currentStep.prevStep ?? this.getStepIndex(this.currentStep) - 1\r\n )\r\n );\r\n }\r\n }\r\n\r\n public hasPrev(step: T): boolean {\r\n if (!step) {\r\n console.warn('Can\\'t get previous step. No currentStep.');\r\n return false;\r\n }\r\n return step.prevStep !== undefined ||\r\n (this.getStepIndex(step) > 0 && !this.isPrevOptionalAnchorMising(step));\r\n }\r\n\r\n private isPrevOptionalAnchorMising(step: T): boolean {\r\n const stepIndex = this.getStepIndex(step);\r\n\r\n for (let i = stepIndex - 1; i > -1; i--) {\r\n const prevStep = this.steps[i];\r\n\r\n if (!prevStep.isOptional || this.anchors[prevStep.anchorId])\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n public goto(stepId: number | string): void {\r\n this.goToStep(this.loadStep(stepId));\r\n }\r\n\r\n public register(anchorId: string, anchor: TourAnchorDirective): void {\r\n if (!anchorId) {\r\n return;\r\n }\r\n\r\n if (this.anchors[anchorId]) {\r\n const step = this.findStepByAnchorId(anchorId),\r\n duplicateAnchorHandling = step?.duplicateAnchorHandling ??\r\n this.userDefaults?.duplicateAnchorHandling ?? 'error';\r\n\r\n switch (duplicateAnchorHandling) {\r\n case 'error':\r\n throw new Error(`Tour anchor with id \"${anchorId}\" already registered!`);\r\n case 'registerFirst':\r\n return;\r\n }\r\n }\r\n\r\n this.anchors[anchorId] = anchor;\r\n this.anchorRegister$.next(anchorId);\r\n }\r\n\r\n private findStepByAnchorId(anchorId: string): T {\r\n return this.steps.find(step => step.anchorId === anchorId);\r\n }\r\n\r\n public unregister(anchorId: string): void {\r\n if (!anchorId) {\r\n return;\r\n }\r\n delete this.anchors[anchorId];\r\n this.anchorUnregister$.next(anchorId);\r\n }\r\n\r\n public getStatus(): TourState {\r\n return this.status;\r\n }\r\n\r\n public isHotkeysEnabled(): boolean {\r\n return this.isHotKeysEnabled;\r\n }\r\n\r\n private goToStep(step: T): void {\r\n if (!step) {\r\n console.warn('Can\\'t go to non-existent step');\r\n this.end();\r\n return;\r\n }\r\n if (this.currentStep) {\r\n this.backdrop.closeSpotlight();\r\n this.hideStep(this.currentStep);\r\n }\r\n\r\n this.anchorClickService.removeListener();\r\n\r\n if (step.route !== undefined && step.route !== null) {\r\n this.navigateToRouteAndSetStep(step);\r\n } else {\r\n this.setCurrentStepAsync(step);\r\n }\r\n }\r\n\r\n private listenToOnAnchorClick(step: T) {\r\n if (step.nextOnAnchorClick) {\r\n const anchorEl = this.anchors[step.anchorId].element.nativeElement;\r\n this.anchorClickService.addListener(anchorEl, () => this.next());\r\n }\r\n }\r\n\r\n private async navigateToRouteAndSetStep(step: T) {\r\n const url = typeof step.route === 'string' ? step.route : this.router.createUrlTree(step.route),\r\n matchOptions: IsActiveMatchOptions = {\r\n paths: 'exact',\r\n matrixParams: 'exact',\r\n queryParams: 'subset',\r\n fragment: 'exact'\r\n };\r\n\r\n const isActive = this.router.isActive(url, matchOptions);\r\n\r\n if (isActive) {\r\n this.setCurrentStepAsync(step);\r\n return;\r\n }\r\n\r\n this.navigationStarted = true;\r\n const navigated = await this.router.navigateByUrl(url);\r\n this.navigationStarted = false;\r\n\r\n if (!navigated) {\r\n console.warn('Navigation to route failed: ', step.route);\r\n this.end();\r\n } else {\r\n this.setCurrentStepAsync(step, step.delayAfterNavigation);\r\n }\r\n }\r\n\r\n private loadStep(stepId: number | string): T {\r\n if (typeof stepId === 'number') {\r\n return this.steps[stepId];\r\n } else {\r\n return this.steps.find(step => step.stepId === stepId);\r\n }\r\n }\r\n\r\n private setCurrentStep(step: T): void {\r\n this.currentStep = step;\r\n this.showStep(this.currentStep);\r\n }\r\n\r\n private setCurrentStepAsync(step: T, delay = 0): void {\r\n delay = delay || step.delayBeforeStepShow;\r\n\r\n setTimeout(() => this.setCurrentStep(step), delay);\r\n }\r\n\r\n protected async showStep(step: T, skipAsync = false): Promise<void> {\r\n const anchor = this.anchors[step && step.anchorId];\r\n\r\n if (!anchor) {\r\n if (step.isAsync && !skipAsync) {\r\n let anchorRegistered$ = this.anchorRegister$\r\n .pipe(\r\n filter(anchorId => anchorId === step.anchorId),\r\n first(),\r\n delay(0)\r\n );\r\n\r\n if (step.asyncStepTimeout) {\r\n anchorRegistered$ = anchorRegistered$\r\n .pipe(\r\n timeout({\r\n each: step.asyncStepTimeout,\r\n with: () => of(null)\r\n })\r\n );\r\n }\r\n\r\n anchorRegistered$\r\n .subscribe(\r\n () => this.showStep(step, true)\r\n );\r\n return;\r\n }\r\n if (step.isOptional) {\r\n this[this.direction === Direction.Forwards ? 'next' : 'prev']();\r\n return;\r\n }\r\n\r\n console.warn(`Can't attach to unregistered anchor with id \"${step.anchorId}\"`);\r\n this.end();\r\n return;\r\n }\r\n this.listenToOnAnchorClick(step);\r\n this.waitingForScroll = true;\r\n await this.scrollToAnchor(step);\r\n this.waitingForScroll = false;\r\n anchor.showTourStep(step);\r\n this.toggleBackdrop(step);\r\n this.togglePageScrolling(step);\r\n this.stepShow$.next({\r\n step,\r\n direction: this.direction\r\n });\r\n }\r\n\r\n private hideStep(step: T): void {\r\n const anchor = this.anchors[step && step.anchorId];\r\n if (!anchor) {\r\n return;\r\n }\r\n anchor.hideTourStep();\r\n this.stepHide$.next({\r\n step,\r\n direction: this.direction\r\n });\r\n }\r\n\r\n private scrollToAnchor(step: T): Promise<void> {\r\n if (step.disableScrollToAnchor) {\r\n return Promise.resolve();\r\n }\r\n\r\n const anchor = this.anchors[step?.anchorId],\r\n htmlElement = anchor.element.nativeElement;\r\n\r\n return this.scrollingService.ensureVisible(htmlElement, {\r\n center: step.centerAnchorOnScroll,\r\n smoothScroll: step.smoothScroll,\r\n scrollContainer: step.scrollContainer\r\n });\r\n }\r\n\r\n private toggleBackdrop(step: T) {\r\n const anchor = this.anchors[step?.anchorId];\r\n\r\n if (step.enableBackdrop) {\r\n this.backdrop.show(anchor.element, step);\r\n } else {\r\n this.backdrop.close();\r\n }\r\n }\r\n\r\n private togglePageScrolling(step: T) {\r\n if (step.disablePageScrolling) {\r\n this.scrollBlockingService.enable(step.scrollContainer);\r\n } else {\r\n this.scrollBlockingService.disable();\r\n }\r\n }\r\n\r\n}\r\n","import {ChangeDetectionStrategy, Component, HostListener, inject} from '@angular/core';\r\nimport {TourService, TourState} from './tour.service';\r\n\r\n\r\n@Component({\r\n selector: 'tour-hotkey-listener',\r\n template: `<ng-content></ng-content>`,\r\n changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class TourHotkeyListenerComponent {\r\n\r\n protected readonly tourService = inject(TourService);\r\n\r\n /**\r\n * Configures hot keys for controlling the tour with the keyboard\r\n */\r\n @HostListener('window:keydown.Escape')\r\n public onEscapeKey(): void {\r\n if (\r\n this.tourService.getStatus() === TourState.ON &&\r\n this.tourService.isHotkeysEnabled()\r\n ) {\r\n this.tourService.end();\r\n }\r\n }\r\n\r\n @HostListener('window:keydown.ArrowRight')\r\n public onArrowRightKey(): void {\r\n const step = this.tourService.currentStep;\r\n\r\n if (\r\n this.tourService.getStatus() === TourState.ON &&\r\n this.tourService.hasNext(this.tourService.currentStep) &&\r\n this.tourService.isHotkeysEnabled() &&\r\n !step?.nextOnAnchorClick\r\n ) {\r\n this.tourService.next();\r\n }\r\n }\r\n\r\n @HostListener('window:keydown.ArrowLeft')\r\n public onArrowLeftKey(): void {\r\n if (\r\n this.tourService.getStatus() === TourState.ON &&\r\n this.tourService.hasPrev(this.tourService.currentStep) &&\r\n this.tourService.isHotkeysEnabled()\r\n ) {\r\n this.tourService.prev();\r\n }\r\n }\r\n}\r\n","import {afterNextRender, AfterRenderPhase, Directive, ElementRef, inject} from '@angular/core';\r\nimport type {TourAnchorDirective} from './tour-anchor.directive';\r\nimport {DOCUMENT} from '@angular/common';\r\n\r\n@Directive()\r\nexport abstract class BaseTourProxyAnchor {\r\n\r\n protected abstract readonly anchorDirective: TourAnchorDirective;\r\n private readonly document = inject(DOCUMENT);\r\n\r\n public abstract anchorEl: string | HTMLElement;\r\n\r\n constructor() {\r\n afterNextRender(\r\n () => this.setAnchorElement(), {\r\n phase: AfterRenderPhase.Read\r\n }\r\n );\r\n }\r\n\r\n private setAnchorElement(): void {\r\n if (this.anchorEl instanceof HTMLElement) {\r\n this.anchorDirective.element = new ElementRef<HTMLElement>(this.anchorEl);\r\n return;\r\n }\r\n const htmlElement = this.document.querySelector<HTMLElement>(this.anchorEl);\r\n\r\n if (!htmlElement) {\r\n throw new Error(`Element with \"${this.anchorEl}\" CSS selector could not be found!`);\r\n }\r\n\r\n this.anchorDirective.element = new ElementRef<HTMLElement>(htmlElement);\r\n }\r\n\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":["mergeStatic"],"mappings":"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