add angular directive

This commit is contained in:
Rene Haas
2022-11-08 01:07:11 +01:00
parent 03d2d8c2c6
commit d7aa1051eb
5 changed files with 123 additions and 67 deletions
@@ -7,12 +7,10 @@ import {
ViewChild, ViewChild,
ElementRef, ElementRef,
OnDestroy, OnDestroy,
OnChanges,
AfterViewInit, AfterViewInit,
SimpleChanges,
NgZone,
} from '@angular/core'; } from '@angular/core';
import { OverlayScrollbars } from 'overlayscrollbars'; import { OverlayScrollbars } from 'overlayscrollbars';
import { OverlayScrollbarsDirective } from '~/overlayscrollbars.directive';
import type { PartialOptions, EventListeners, EventListenerMap } from 'overlayscrollbars'; import type { PartialOptions, EventListeners, EventListenerMap } from 'overlayscrollbars';
const mergeEventListeners = (emits: EventListeners, events: EventListeners) => const mergeEventListeners = (emits: EventListeners, events: EventListeners) =>
@@ -31,17 +29,19 @@ const mergeEventListeners = (emits: EventListeners, events: EventListeners) =>
); );
@Component({ @Component({
selector: '[overlay-scrollbars]', // https://angular.io/guide/styleguide#component-selectors selector: '[overlay-scrollbars-component]', // https://angular.io/guide/styleguide#component-selectors
exportAs: 'overlayScrollbars', exportAs: 'overlayScrollbars',
host: { 'data-overlayscrollbars': '' }, host: { 'data-overlayscrollbars': '' },
template: `<div #content><ng-content></ng-content></div>`, template: `<div
#content
[overlayScrollbarsDirective]
[options]="options"
[events]="mergeEvents(events)"
>
<ng-content></ng-content>
</div>`,
}) })
export class OverlayScrollbarsComponent implements OnDestroy, OnChanges, AfterViewInit { export class OverlayScrollbarsComponent implements OnDestroy, AfterViewInit {
private instanceRef: OverlayScrollbars | null = null;
@ViewChild('content')
private contentRef?: ElementRef<HTMLDivElement>;
@Input('options') @Input('options')
options?: PartialOptions | false | null; options?: PartialOptions | false | null;
@Input('events') @Input('events')
@@ -56,9 +56,42 @@ export class OverlayScrollbarsComponent implements OnDestroy, OnChanges, AfterVi
@Output('osScroll') @Output('osScroll')
onScroll = new EventEmitter<EventListenerMap['scroll']>(); onScroll = new EventEmitter<EventListenerMap['scroll']>();
constructor(private targetRef: ElementRef<HTMLElement>, private ngZone: NgZone) {} @ViewChild('content')
private contentRef?: ElementRef<HTMLDivElement>;
@ViewChild('content', { read: OverlayScrollbarsDirective })
private osDirective?: OverlayScrollbarsDirective;
private mergedEvents(originalEvents: OverlayScrollbarsComponent['events']) { constructor(private targetRef: ElementRef<HTMLElement>) {}
instance(): OverlayScrollbars | null {
return this.osDirective!.instance();
}
element(): HTMLElement {
return this.targetRef.nativeElement;
}
ngAfterViewInit() {
const targetElm = this.element();
const contentElm = this.contentRef!.nativeElement;
/* istanbul ignore else */
if (targetElm && contentElm) {
this.osDirective!.initialize({
target: targetElm,
elements: {
viewport: contentElm,
content: contentElm,
},
});
}
}
ngOnDestroy() {
this.osDirective?.instance()!.destroy();
}
private mergeEvents(originalEvents: OverlayScrollbarsComponent['events']) {
return mergeEventListeners( return mergeEventListeners(
{ {
initialized: (...args) => this.onInitialized.emit(args), initialized: (...args) => this.onInitialized.emit(args),
@@ -69,52 +102,4 @@ export class OverlayScrollbarsComponent implements OnDestroy, OnChanges, AfterVi
originalEvents || {} originalEvents || {}
); );
} }
instance(): OverlayScrollbars | null {
return this.instanceRef;
}
element(): HTMLElement {
return this.targetRef.nativeElement;
}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
const targetElm = this.element();
const contentElm = this.contentRef!.nativeElement;
/* istanbul ignore else */
if (targetElm && contentElm) {
this.instanceRef = OverlayScrollbars(
{
target: targetElm,
elements: {
viewport: contentElm,
content: contentElm,
},
},
this.options || {},
this.mergedEvents(this.events)
);
}
});
}
ngOnDestroy() {
this.instanceRef?.destroy();
}
ngOnChanges(changes: SimpleChanges) {
const optionsChange = changes.options;
const eventsChange = changes.events;
if (OverlayScrollbars.valid(this.instanceRef)) {
if (optionsChange) {
this.instanceRef.options(optionsChange.currentValue || {}, true);
}
if (eventsChange) {
this.instanceRef.on(this.mergedEvents(eventsChange.currentValue), true);
}
}
}
} }
@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
import { Directive, Input, OnChanges, SimpleChanges, NgZone } from '@angular/core';
import { OverlayScrollbars } from 'overlayscrollbars';
import type { InitializationTarget } from 'overlayscrollbars';
import type { OverlayScrollbarsComponent } from '~/overlayscrollbars.component';
@Directive({
selector: '[overlayScrollbarsDirective]', // https://angular.io/guide/styleguide#component-selectors
exportAs: 'overlayScrollbars',
})
export class OverlayScrollbarsDirective implements OnChanges {
private instanceRef: OverlayScrollbars | null = null;
@Input('options')
options?: OverlayScrollbarsComponent['options'];
@Input('events')
events?: OverlayScrollbarsComponent['events'];
constructor(private ngZone: NgZone) {}
initialize(target: InitializationTarget): OverlayScrollbars {
this.ngZone.runOutsideAngular(() => {
this.instanceRef = OverlayScrollbars(
target,
this.options || {},
/* istanbul ignore next */
this.events || {}
);
});
return this.instanceRef!;
}
instance(): OverlayScrollbars | null {
return this.instanceRef;
}
ngOnChanges(changes: SimpleChanges) {
const optionsChange = changes.options;
const eventsChange = changes.events;
if (optionsChange) {
const curr = optionsChange.currentValue;
this.options = curr;
if (OverlayScrollbars.valid(this.instanceRef)) {
this.instanceRef.options(curr || {}, true);
}
}
/* istanbul ignore else */
if (eventsChange) {
const curr = eventsChange.currentValue;
this.events = curr;
if (OverlayScrollbars.valid(this.instanceRef)) {
this.instanceRef.on(
/* istanbul ignore next */
curr || {},
true
);
}
}
}
}
@@ -1,8 +1,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { OverlayScrollbarsDirective } from './overlayscrollbars.directive';
import { OverlayScrollbarsComponent } from './overlayscrollbars.component'; import { OverlayScrollbarsComponent } from './overlayscrollbars.component';
@NgModule({ @NgModule({
declarations: [OverlayScrollbarsComponent], declarations: [OverlayScrollbarsComponent, OverlayScrollbarsDirective],
exports: [OverlayScrollbarsComponent], exports: [OverlayScrollbarsComponent, OverlayScrollbarsDirective],
}) })
export class OverlayscrollbarsModule {} export class OverlayscrollbarsModule {}
@@ -1,2 +1,3 @@
export * from './overlayscrollbars.component'; export * from './overlayscrollbars.component';
export * from './overlayscrollbars.directive';
export * from './overlayscrollbars.module'; export * from './overlayscrollbars.module';
@@ -1,14 +1,18 @@
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { OverlayScrollbars } from 'overlayscrollbars'; import { OverlayScrollbars } from 'overlayscrollbars';
import { OverlayScrollbarsComponent, OverlayscrollbarsModule } from '~/public-api'; import {
OverlayScrollbarsComponent,
OverlayScrollbarsDirective,
OverlayscrollbarsModule,
} from '~/public-api';
import type { ComponentFixture } from '@angular/core/testing'; import type { ComponentFixture } from '@angular/core/testing';
import type { EventListenerMap } from 'overlayscrollbars'; import type { EventListenerMap } from 'overlayscrollbars';
@Component({ @Component({
template: ` template: `
<div <div
[overlay-scrollbars] [overlay-scrollbars-component]
[options]="options" [options]="options"
[events]="events" [events]="events"
(osInitialized)="onInitialized($event)" (osInitialized)="onInitialized($event)"
@@ -17,7 +21,7 @@ import type { EventListenerMap } from 'overlayscrollbars';
(osScroll)="onScroll($event)" (osScroll)="onScroll($event)"
[ngClass]="clazz" [ngClass]="clazz"
[ngStyle]="style" [ngStyle]="style"
#ref="overlayScrollbars" #ref
> >
hello <span>angular</span> hello <span>angular</span>
<div *ngIf="children === 0" id="empty">empty</div> <div *ngIf="children === 0" id="empty">empty</div>
@@ -73,7 +77,7 @@ describe('OverlayscrollbarsNgxComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
...new OverlayscrollbarsModule(), ...new OverlayscrollbarsModule(),
declarations: [OverlayScrollbarsComponent, Test], declarations: [OverlayScrollbarsComponent, OverlayScrollbarsDirective, Test],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(OverlayScrollbarsComponent); fixture = TestBed.createComponent(OverlayScrollbarsComponent);