UNPKG

@ynmstudio/utils

Version:
337 lines (328 loc) 13.2 kB
import * as i0 from '@angular/core'; import { EventEmitter, Input, Output, HostBinding, Directive, Injectable, ContentChildren, InjectionToken, inject, HostListener, NgModule } from '@angular/core'; import * as i2 from '@ynmstudio/utils/inviewport'; import { INTERSECTION_OBSERVER_INIT, InViewportDirective } from '@ynmstudio/utils/inviewport'; /** * A directive used to add an `active` class to a nav item * when the section is in the viewport * * @example * ```html * <a snScrollSpyItem for="foo" href="#section1">Section 1</a> * ``` * */ class ScrollSpyItemDirective { /** * True if the nav item is the active item in the `items` list * for `ScrollSpyDirective` instance * * @memberof ScrollSpyItemDirective */ get getActiveClass() { return this.active ? this.activeClass : ''; } set active(state) { if (this._active === state) return; this.activeChange.emit(state); this._active = state; } get active() { return this._active; } set snScrollSpyItemActive(data) { this.activeClass = data; } /** * Id of section that links navigates to * * @readonly * @memberof ScrollSpyItemDirective */ get section() { return this.href.replace('#', ''); } /** * Creates an instance of ScrollSpyItemDirective. * @memberof ScrollSpyItemDirective */ constructor(cdRef) { this.cdRef = cdRef; this.activeClass = 'active'; /** * Emits when the `active` property changes */ this.activeChange = new EventEmitter(); this._active = false; /** * If true means the section is in the viewport * * @memberof ScrollSpyItemDirective */ this.inViewport = false; } /** * Manually trigger change detection * * @memberof ScrollSpyItemDirective */ detectChanges() { this.cdRef.detectChanges(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyItemDirective, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: ScrollSpyItemDirective, isStandalone: true, selector: "[snScrollSpyItem]", inputs: { snScrollSpyItemActive: "snScrollSpyItemActive", for: "for", href: "href" }, outputs: { activeChange: "activeChange" }, host: { properties: { "class": "this.getActiveClass" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyItemDirective, decorators: [{ type: Directive, args: [{ selector: '[snScrollSpyItem]', standalone: true }] }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { getActiveClass: [{ type: HostBinding, args: ['class'] }], activeChange: [{ type: Output }], snScrollSpyItemActive: [{ type: Input }], for: [{ type: Input }], href: [{ type: Input }] } }); /** * Service that stores a list of `Spy`'s and the state * of their nav items `inViewport` and `active` state * */ class ScrollSpyService { constructor() { /** * List of `Spy`'s * * @memberof ScrollSpyService */ this.spys = []; /** * Stores requests to add items to spy when spy hasn't been created * yet. Once spy has been added then request will be made again. * * @memberof ScrollSpyService */ this.buffer = []; } /** * Add spy to list of `spys` * * @memberof ScrollSpyService */ addSpy(id, items) { this.spys.push({ id, items }); const buffer = this.buffer.filter(i => i.spyId === id); this.buffer = this.buffer.filter(i => i.spyId !== id); buffer.forEach(i => this.setSpySectionStatus(i.sectionId, i.spyId, i.inViewport)); } /** * Remove spy from list of `spys` * * @memberof ScrollSpyService */ removeSpy(id) { const i = this.spys.findIndex(s => s.id === id); this.spys.splice(i, 1); } /** * Set the `inViewport` status for a spy item then sets the active * to true for the first item in the list that has `inViewport` * set to true * * @memberof ScrollSpyService */ setSpySectionStatus(sectionId, spyId, inViewport) { const spy = this.spys.find(s => s.id === spyId); if (!spy) { this.buffer.push({ sectionId, spyId, inViewport }); return; } const item = spy.items.find(i => i.section === sectionId); if (!item) { return; } item.inViewport = inViewport; const firstInViewport = spy.items.find(i => i.inViewport); spy.items.forEach(i => i.active = firstInViewport === i); if (firstInViewport) { firstInViewport.detectChanges(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /** * Adds `active` class to navigation links when section is in the viewport. * Used in conjuction with `snScrollItem` directive which should be added * to anchor links in the nav * * @example * ``` * <ul role="navigation" snScrollSpy id="foo"> * <li><a snScrollSpyItem for="foo" href="#section1">Section 1</a></li> * <li><a snScrollSpyItem for="foo" href="#section2">Section 2</a></li> * <li><a snScrollSpyItem for="foo" href="#section3">Section 3</a></li> * <li><a snScrollSpyItem for="foo" href="#section4">Section 4</a></li> * </ul> * ``` * */ class ScrollSpyDirective { /** * Creates an instance of ScrollSpyDirective. * @memberof ScrollSpyDirective */ constructor(scrollSpySvc) { this.scrollSpySvc = scrollSpySvc; } /** * Adds spy to list of spys in `ScrollSpyService` * * @memberof ScrollSpyDirective */ ngAfterViewInit() { this.scrollSpySvc.addSpy(this.id, this.items); } /** * Remove spy from list of spys when directive is destroyed * * @memberof ScrollSpyDirective */ ngOnDestroy() { this.scrollSpySvc.removeSpy(this.id); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyDirective, deps: [{ token: ScrollSpyService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: ScrollSpyDirective, isStandalone: true, selector: "[snScrollSpy]", inputs: { id: "id" }, queries: [{ propertyName: "items", predicate: ScrollSpyItemDirective, descendants: true }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyDirective, decorators: [{ type: Directive, args: [{ selector: '[snScrollSpy]', standalone: true }] }], ctorParameters: () => [{ type: ScrollSpyService }], propDecorators: { items: [{ type: ContentChildren, args: [ScrollSpyItemDirective, { descendants: true }] }], id: [{ type: Input }] } }); const SCROLL_SPY_INTERSECTION_OBSERVER_INIT = new InjectionToken('Options for IntersectionObserver within ScrollSpy', { factory: () => ({ rootMargin: '-100px 0px 50px 0px' }) }); /** * A component to wrap section content within that will update the * `ScrollSpyService` when it's in view * * @example * ```html * <section sn-scroll-spy-section id="section1" for="foo"> * ... * </section> * ``` */ class ScrollSpySectionDirective { /** * Creates an instance of ScrollSpySectionComponent. * @memberof ScrollSpySectionComponent */ constructor(scrollSpySvc) { this.scrollSpySvc = scrollSpySvc; this.inViewportOptions = inject(SCROLL_SPY_INTERSECTION_OBSERVER_INIT, { optional: true }); } /** * Updates `ScrollSpy` section when element enters/leaves viewport * * @memberof ScrollSpySectionComponent */ onInViewportChange(inViewport) { this.scrollSpySvc.setSpySectionStatus(this.id, this.for, inViewport); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpySectionDirective, deps: [{ token: ScrollSpyService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.3", type: ScrollSpySectionDirective, isStandalone: true, selector: "[sn-scroll-spy-section]", inputs: { id: "id", for: "for", inViewportOptions: "inViewportOptions" }, host: { listeners: { "inViewportChange": "onInViewportChange($event)" } }, providers: [ { provide: INTERSECTION_OBSERVER_INIT, useExisting: SCROLL_SPY_INTERSECTION_OBSERVER_INIT, } ], hostDirectives: [{ directive: i2.InViewportDirective, outputs: ["inViewportChange", "inViewportChange"] }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpySectionDirective, decorators: [{ type: Directive, args: [{ selector: '[sn-scroll-spy-section]', standalone: true, hostDirectives: [ { directive: InViewportDirective, outputs: ['inViewportChange'], } ], providers: [ { provide: INTERSECTION_OBSERVER_INIT, useExisting: SCROLL_SPY_INTERSECTION_OBSERVER_INIT, } ] }] }], ctorParameters: () => [{ type: ScrollSpyService }], propDecorators: { id: [{ type: Input }], for: [{ type: Input }], onInViewportChange: [{ type: HostListener, args: ['inViewportChange', ['$event']] }], inViewportOptions: [{ type: Input }] } }); /** * A simple lightweight library for Angular which automatically * updates links to indicate the currently active section in the viewport * */ class ScrollSpyModule { /** * Specify a static method for root module to ensure providers are * only provided once but allows the module to still be imported * into other modules without reproviding services. * * @memberof ScrollSpyModule */ static forRoot() { return { ngModule: ScrollSpyModule, providers: [ScrollSpyService], }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyModule, imports: [InViewportDirective, ScrollSpySectionDirective, ScrollSpyDirective, ScrollSpyItemDirective], exports: [ScrollSpyDirective, ScrollSpyItemDirective, ScrollSpySectionDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ScrollSpyModule, decorators: [{ type: NgModule, args: [{ imports: [InViewportDirective, ScrollSpySectionDirective, ScrollSpyDirective, ScrollSpyItemDirective], declarations: [], exports: [ScrollSpyDirective, ScrollSpyItemDirective, ScrollSpySectionDirective], }] }] }); /* * Public API Surface of ngx-scrollspy */ /** * Generated bundle index. Do not edit. */ export { SCROLL_SPY_INTERSECTION_OBSERVER_INIT, ScrollSpyDirective, ScrollSpyItemDirective, ScrollSpyModule, ScrollSpySectionDirective, ScrollSpyService }; //# sourceMappingURL=ynmstudio-utils-scrollspy.mjs.map