ng-devui
Version:
DevUI components based on Angular
437 lines (428 loc) • 19.5 kB
JavaScript
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