UNPKG

ng-devui

Version:

DevUI components based on Angular

437 lines (428 loc) 19.5 kB
import * as i0 from '@angular/core'; import { Injectable, Directive, Input, HostListener, ContentChildren, forwardRef, Inject, HostBinding, NgModule } from '@angular/core'; import * as i2 from '@angular/router'; import { NavigationEnd, RouterModule } from '@angular/router'; import { ReplaySubject, Subscription, Subject } from 'rxjs'; import { filter, debounceTime } from 'rxjs/operators'; import { scrollAnimate } from 'ng-devui/utils'; import { CommonModule } from '@angular/common'; class AnchorService { constructor() { this.currentActiveAnchorSubject = new ReplaySubject(1); this.currentActiveAnchorObs = this.currentActiveAnchorSubject.asObservable(); this.anchorList = []; } setCurrentActive(anchor) { if (this.anchorList.indexOf(anchor) >= 0) { this.currentActiveAnchor = anchor; this.currentActiveAnchorSubject.next(anchor); } else if (!anchor) { this.currentActiveAnchor = ''; } } getCurrentActive() { return this.currentActiveAnchorObs; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorService, decorators: [{ type: Injectable }] }); class AnchorDirective { set isActive(active) { this._isActive = active; this.activeChangeSubject.next(active); if (active) { this.anchorService.setCurrentActive(this.anchor); } else if (this.anchorService.currentActiveAnchor === this.anchor) { this.anchorService.setCurrentActive(''); } } get isActive() { return this._isActive; } set boxElement(box) { this._boxElement = box; this.updateScrollListenTarget(); } get boxElement() { return this._boxElement; } constructor(el, anchorService) { this.el = el; this.anchorService = anchorService; this.anchorActive = 'active'; this.activeChangeSubject = new ReplaySubject(1); this.REACH_TOP_VISION_OFFSET = 50; this.THROTTLE_DELAY = 100; this.THROTTLE_TRIGGER = 600; this.throttle = () => { const fn = this.checkActiveStatus; const time = Date.now(); if (this.scrollTimer) { clearTimeout(this.scrollTimer); } if (!this.scrollPreStart) { this.scrollPreStart = time; } if (time - this.scrollPreStart > this.THROTTLE_TRIGGER) { fn(); this.scrollPreStart = null; this.scrollTimer = null; } else { this.scrollTimer = setTimeout(() => { fn(); this.scrollPreStart = null; this.scrollTimer = null; }, this.THROTTLE_DELAY); } }; this.checkActiveStatus = (activeChangeBy) => { if (this.boxElement.isScrollingToTarget) { return; } const dom = this.boxElement.scrollTarget; const fix = dom && dom instanceof Element ? dom.getBoundingClientRect().top : 0; const top = this.element.getBoundingClientRect().top - fix - ((this.boxElement.view && this.boxElement.view.top) || 0); const bottom = this.element.getBoundingClientRect().bottom - fix - ((this.boxElement.view && this.boxElement.view.top) || 0); const currentActiveAnchor = this.anchorService.currentActiveAnchor; if (this.anchor === this.boxElement.defaultAnchor && (!currentActiveAnchor || currentActiveAnchor === this.boxElement.defaultAnchor)) { this.activeChangeBy = activeChangeBy || 'scroll'; this.isActive = bottom > this.REACH_TOP_VISION_OFFSET; return; } this.activeChangeBy = activeChangeBy || 'scroll'; this.isActive = bottom > this.REACH_TOP_VISION_OFFSET && top < this.REACH_TOP_VISION_OFFSET; }; this.element = this.el.nativeElement; } ngAfterViewInit() { this.activeChangeSubscription = this.activeChangeSubject.asObservable().subscribe((active) => { if (this.lastActiveBy) { this.element.classList.remove(this.lastActiveBy); } if (active) { this.element.classList.add(this.anchorActive); this.lastActiveBy = 'anchor-active-by-' + this.activeChangeBy; setTimeout(() => { this.element.classList.add(this.lastActiveBy); }, 0); } else { this.element.classList.remove(this.anchorActive); } }); } ngOnDestroy() { this.scrollListenTarget.removeEventListener('scroll', this.throttle); if (this.activeChangeSubscription) { this.activeChangeSubscription.unsubscribe(); } } beFocused() { this.boxElement.forceActiveAnchor(this.anchor, 'click-inside'); this.boxElement.isScrollingToTarget = false; } updateScrollListenTarget() { if (this.scrollListenTarget) { return; } if (this.boxElement && typeof window !== 'undefined') { this.scrollListenTarget = this.boxElement.scrollTarget || window; // window有scroll事件,document.documentElement没有scroll事件 } this.scrollListenTarget.addEventListener('scroll', this.throttle, { passive: true }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorDirective, deps: [{ token: i0.ElementRef }, { token: AnchorService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AnchorDirective, selector: "[dAnchor]", inputs: { anchor: ["dAnchor", "anchor"], anchorActive: "anchorActive" }, host: { listeners: { "click": "beFocused()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorDirective, decorators: [{ type: Directive, args: [{ selector: '[dAnchor]', }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: AnchorService }], propDecorators: { anchor: [{ type: Input, args: ['dAnchor'] }], anchorActive: [{ type: Input }], beFocused: [{ type: HostListener, args: ['click'] }] } }); class AnchorBoxDirective { set anchorList(list) { if (this.sub) { this.sub.unsubscribe(); } this.sub = new Subscription(); this.anchorMap = {}; this._anchorList = list; this.anchorService.anchorList = this._anchorList.map((item) => item.anchor); this._anchorList.toArray().forEach((targetAnchor) => { this.anchorMap[targetAnchor.anchor] = targetAnchor; targetAnchor.boxElement = this; this.sub.add(targetAnchor.activeChangeSubject.pipe(filter((bool) => !!bool)).subscribe(() => { this.activeChangeSubject.next(targetAnchor); })); }); this.refreshAnchorMap.next(); } get anchorList() { return this._anchorList; } constructor(anchorService) { this.anchorService = anchorService; this.isScrollingToTarget = false; this.activeChangeSubject = new Subject(); this.activeChange = this.activeChangeSubject.asObservable(); this.isInit = true; this.refreshAnchorMap = new Subject(); } ngOnChanges(changes) { if (changes.defaultAnchor?.currentValue) { this.forceActiveAnchor(this.defaultAnchor, 'initial'); } } ngOnDestroy() { this.activeChangeSubject.complete(); if (this.sub) { this.sub.unsubscribe(); } } forceActiveAnchor(anchorName, forceActiveSource = 'scroll', deactivateOtherAnchor = true) { if (this.anchorMap && this.anchorService.anchorList.indexOf(anchorName) >= 0) { this.anchorMap[anchorName].activeChangeBy = forceActiveSource; this.anchorMap[anchorName].isActive = true; if (deactivateOtherAnchor) { Object.keys(this.anchorMap) .filter((name) => name !== anchorName) .map((name) => this.anchorMap[name]) .forEach((anchor) => { anchor.activeChangeBy = forceActiveSource; anchor.isActive = false; }); } } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorBoxDirective, deps: [{ token: AnchorService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AnchorBoxDirective, selector: "[dAnchorBox]", inputs: { view: "view", defaultAnchor: "defaultAnchor", scrollTarget: "scrollTarget" }, queries: [{ propertyName: "anchorList", predicate: AnchorDirective, descendants: true }], usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorBoxDirective, decorators: [{ type: Directive, args: [{ selector: '[dAnchorBox]', }] }], ctorParameters: () => [{ type: AnchorService }], propDecorators: { view: [{ type: Input }], defaultAnchor: [{ type: Input }], scrollTarget: [{ type: Input }], anchorList: [{ type: ContentChildren, args: [AnchorDirective, { descendants: true }] }] } }); class AnchorLinkDirective { get anchorActiveClass() { return this.anchorBlock && this.anchorBlock.isActive ? this.anchorActive || '' : ''; } set anchorName(anchor) { this._anchorName = anchor; this.bindAnchorAfterBoxReady(); } get anchorName() { return this._anchorName; } constructor(box) { this.bindAnchorAfterBoxReady = () => { if (this.boxElement.anchorMap) { setTimeout(() => { this.anchorBlock = this.boxElement.anchorMap[this.anchorName]; }, 0); } else { this.bindingAnchorTimer = setTimeout(this.bindAnchorAfterBoxReady, 500); } }; this.boxElement = box; } ngOnInit() { this.subscribeAnchorMapChange(); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } subscribeAnchorMapChange() { if (this.boxElement) { this.subscription = this.boxElement.refreshAnchorMap.subscribe(() => { if (this.bindingAnchorTimer) { clearTimeout(this.bindingAnchorTimer); this.bindingAnchorTimer = undefined; } this.bindAnchorAfterBoxReady(); }); } } scrollToAnchor(activeChangeBy) { if (typeof document === 'undefined') { return; } if (!this.anchorBlock) { return; } const callback = () => { setTimeout(() => { this.boxElement.forceActiveAnchor(this.anchorName, activeChangeBy || 'anchor-link'); this.boxElement.isScrollingToTarget = false; }, 120); }; ((container, anchor) => { let containerScrollTop = container.scrollTop; let containerOffsetTop = container.getBoundingClientRect().top; if (container === document.documentElement) { containerScrollTop += document.body.scrollTop; // scrollTop兼容性问题 containerOffsetTop = 0; // offsettop抵消 } scrollAnimate(container, containerScrollTop, containerScrollTop + anchor.getBoundingClientRect().top - containerOffsetTop - ((this.boxElement.view && this.boxElement.view.top) || 0), undefined, undefined, callback); })(this.boxElement.scrollTarget || document.documentElement, this.anchorBlock.element); this.boxElement.isScrollingToTarget = true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorLinkDirective, deps: [{ token: forwardRef(() => AnchorBoxDirective) }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AnchorLinkDirective, selector: "[dAnchorLink]", inputs: { anchorName: ["dAnchorLink", "anchorName"], anchorActive: "anchorActive" }, host: { listeners: { "click": "scrollToAnchor()" }, properties: { "class": "this.anchorActiveClass" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorLinkDirective, decorators: [{ type: Directive, args: [{ selector: '[dAnchorLink]', }] }], ctorParameters: () => [{ type: AnchorBoxDirective, decorators: [{ type: Inject, args: [forwardRef(() => AnchorBoxDirective)] }] }], propDecorators: { anchorActiveClass: [{ type: HostBinding, args: ['class'] }], anchorName: [{ type: Input, args: ['dAnchorLink'] }], anchorActive: [{ type: Input }], scrollToAnchor: [{ type: HostListener, args: ['click'] }] } }); class AnchorBoxHashSupportDirective { constructor(box, router, route) { this.box = box; this.router = router; this.route = route; this.updateUrlWhenAnchorActive = true; this.scrollToAnchorByHashOnlyInit = false; this.sub = new Subscription(); this.manual = false; this.navigateToHash = (targetAnchor) => { if (targetAnchor.activeChangeBy === 'initial') { return; } this.router.navigate([], { fragment: targetAnchor.anchor, replaceUrl: true }); this.manual = true; }; this.navigateToAnchor = (event) => { if (this.manual) { this.manual = false; return; } if (this.scrollToAnchorByHashOnlyInit) { return; } const frag = this.router.parseUrl(event.url).fragment; this.scrollToFragment(frag); }; this.scrollToFragment = (frag) => { if (!frag) { return; } if (this.box.anchorMap[frag]) { const tempAnchor = new AnchorLinkDirective(this.box); tempAnchor.anchorName = frag; tempAnchor.anchorBlock = this.box.anchorMap[frag]; tempAnchor.scrollToAnchor('fragment'); } }; } ngOnInit() { this.sub.add(this.box.activeChange.pipe(debounceTime(300), filter(anchor => this.updateUrlWhenAnchorActive)).subscribe(this.navigateToHash)); this.sub.add(this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(this.navigateToAnchor)); } ngAfterViewInit() { const frag = this.route.snapshot.fragment; setTimeout(() => { this.scrollToFragment(frag); }, 120); } ngOnDestroy() { if (this.sub) { this.sub.unsubscribe(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorBoxHashSupportDirective, deps: [{ token: AnchorBoxDirective }, { token: i2.Router }, { token: i2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AnchorBoxHashSupportDirective, selector: "[dAnchorBox][dAnchorHashSupport]", inputs: { updateUrlWhenAnchorActive: "updateUrlWhenAnchorActive", scrollToAnchorByHashOnlyInit: "scrollToAnchorByHashOnlyInit" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorBoxHashSupportDirective, decorators: [{ type: Directive, args: [{ selector: '[dAnchorBox][dAnchorHashSupport]' }] }], ctorParameters: () => [{ type: AnchorBoxDirective }, { type: i2.Router }, { type: i2.ActivatedRoute }], propDecorators: { updateUrlWhenAnchorActive: [{ type: Input }], scrollToAnchorByHashOnlyInit: [{ type: Input }] } }); class AnchorModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: AnchorModule, declarations: [AnchorLinkDirective, AnchorBoxDirective, AnchorDirective, AnchorBoxHashSupportDirective], imports: [CommonModule, RouterModule], exports: [AnchorLinkDirective, AnchorBoxDirective, AnchorDirective, AnchorBoxHashSupportDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorModule, providers: [AnchorService], imports: [CommonModule, RouterModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AnchorModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, RouterModule ], declarations: [ AnchorLinkDirective, AnchorBoxDirective, AnchorDirective, AnchorBoxHashSupportDirective ], exports: [ AnchorLinkDirective, AnchorBoxDirective, AnchorDirective, AnchorBoxHashSupportDirective ], providers: [AnchorService], }] }] }); /** * Generated bundle index. Do not edit. */ export { AnchorBoxDirective, AnchorBoxHashSupportDirective, AnchorDirective, AnchorLinkDirective, AnchorModule, AnchorService }; //# sourceMappingURL=ng-devui-anchor.mjs.map