UNPKG

ohayolibs

Version:

Ohayo is a set of essential modules for ohayojp.

155 lines (135 loc) 3.96 kB
import { Platform } from '@angular/cdk/platform'; import { DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Inject, OnDestroy, Optional, ViewEncapsulation, } from '@angular/core'; import { OnboardingConfig, OnboardingItem, OnboardingOpType } from './onboarding.types'; interface OnboardingLightData { el: HTMLElement; top: number; left: number; width: number; height: number; clientHeight: number; clientWidth: number; } @Component({ selector: 'onboarding', templateUrl: './onboarding.component.html', host: { '[class.onboarding]': `true`, '[attr.data-onboarding-active]': `active`, }, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class OnboardingComponent implements OnDestroy { private time: any; private prevSelectorEl: HTMLElement; config: OnboardingConfig; item: OnboardingItem; active = 0; max = 0; readonly op = new EventEmitter<OnboardingOpType>(); running = false; get first(): boolean { return this.active === 0; } get last(): boolean { return this.active === this.max - 1; } private _getDoc(): Document { return this.doc; } private _getWin(): Window { return this._getDoc().defaultView || window; } constructor( private el: ElementRef<HTMLElement>, @Optional() @Inject(DOCUMENT) private doc: any, private platform: Platform, private cdr: ChangeDetectorRef, ) {} private getLightData(): OnboardingLightData | null { const doc = this._getDoc(); const win = this._getWin(); const el = doc.querySelector(this.item.selectors) as HTMLElement; if (!el) { return null; } const scrollTop = win.pageYOffset || doc.documentElement.scrollTop || doc.body.scrollTop; const scrollLeft = win.pageXOffset || doc.documentElement.scrollLeft || doc.body.scrollLeft; const rect = el.getBoundingClientRect(); const top = rect.top + scrollTop; const left = rect.left + scrollLeft; const padding = 8; const needPadding = top > padding && left > padding; const offsetPos = needPadding ? padding : 0; const offsetWH = needPadding ? padding * 2 : 0; return { top: top - offsetPos, left: left - offsetPos, width: rect.width + offsetWH, height: rect.height + offsetWH, el, clientWidth: doc.body.clientWidth, clientHeight: doc.body.clientHeight, }; } private scroll(pos: OnboardingLightData): void { this.prevSelectorEl = pos.el; const scrollY = pos.top - (pos.clientHeight - pos.height) / 2; this._getWin().scrollTo({ top: scrollY }); this.updatePrevElStatus(true); } updateRunning(status: boolean): void { this.running = status; this.cdr.detectChanges(); if (!status) { this.updatePosition(); } } private updatePosition(): void { if (!this.platform.isBrowser) { return; } const pos = this.getLightData(); if (pos == null) { console.warn(`Did not matches selectors [${this.item.selectors}]`); return; } const lightStyle = (this.el.nativeElement.querySelector('.onboarding__light') as HTMLElement).style; lightStyle.top = `${pos.top}px`; lightStyle.left = `${pos.left}px`; lightStyle.width = `${pos.width}px`; lightStyle.height = `${pos.height}px`; this.updatePrevElStatus(false); this.scroll(pos); } private updatePrevElStatus(status: boolean): void { if (this.prevSelectorEl) { this.prevSelectorEl.classList[status ? 'add' : 'remove']('onboarding__light-el'); } } to(type: OnboardingOpType): void { this.op.emit(type); } handleMask(): void { if (this.config.maskClosable === true) { this.to('done'); } } ngOnDestroy(): void { clearTimeout(this.time); this.updatePrevElStatus(false); } }