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,
ElementRef,
OnDestroy,
OnChanges,
AfterViewInit,
SimpleChanges,
NgZone,
} from '@angular/core';
import { OverlayScrollbars } from 'overlayscrollbars';
import { OverlayScrollbarsDirective } from '~/overlayscrollbars.directive';
import type { PartialOptions, EventListeners, EventListenerMap } from 'overlayscrollbars';
const mergeEventListeners = (emits: EventListeners, events: EventListeners) =>
@@ -31,17 +29,19 @@ const mergeEventListeners = (emits: EventListeners, events: EventListeners) =>
);
@Component({
selector: '[overlay-scrollbars]', // https://angular.io/guide/styleguide#component-selectors
selector: '[overlay-scrollbars-component]', // https://angular.io/guide/styleguide#component-selectors
exportAs: '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 {
private instanceRef: OverlayScrollbars | null = null;
@ViewChild('content')
private contentRef?: ElementRef<HTMLDivElement>;
export class OverlayScrollbarsComponent implements OnDestroy, AfterViewInit {
@Input('options')
options?: PartialOptions | false | null;
@Input('events')
@@ -56,9 +56,42 @@ export class OverlayScrollbarsComponent implements OnDestroy, OnChanges, AfterVi
@Output('osScroll')
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(
{
initialized: (...args) => this.onInitialized.emit(args),
@@ -69,52 +102,4 @@ export class OverlayScrollbarsComponent implements OnDestroy, OnChanges, AfterVi
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 { OverlayScrollbarsDirective } from './overlayscrollbars.directive';
import { OverlayScrollbarsComponent } from './overlayscrollbars.component';
@NgModule({
declarations: [OverlayScrollbarsComponent],
exports: [OverlayScrollbarsComponent],
declarations: [OverlayScrollbarsComponent, OverlayScrollbarsDirective],
exports: [OverlayScrollbarsComponent, OverlayScrollbarsDirective],
})
export class OverlayscrollbarsModule {}
@@ -1,2 +1,3 @@
export * from './overlayscrollbars.component';
export * from './overlayscrollbars.directive';
export * from './overlayscrollbars.module';
@@ -1,14 +1,18 @@
import { Component, ViewChild } from '@angular/core';
import { TestBed } from '@angular/core/testing';
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 { EventListenerMap } from 'overlayscrollbars';
@Component({
template: `
<div
[overlay-scrollbars]
[overlay-scrollbars-component]
[options]="options"
[events]="events"
(osInitialized)="onInitialized($event)"
@@ -17,7 +21,7 @@ import type { EventListenerMap } from 'overlayscrollbars';
(osScroll)="onScroll($event)"
[ngClass]="clazz"
[ngStyle]="style"
#ref="overlayScrollbars"
#ref
>
hello <span>angular</span>
<div *ngIf="children === 0" id="empty">empty</div>
@@ -73,7 +77,7 @@ describe('OverlayscrollbarsNgxComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
...new OverlayscrollbarsModule(),
declarations: [OverlayScrollbarsComponent, Test],
declarations: [OverlayScrollbarsComponent, OverlayScrollbarsDirective, Test],
}).compileComponents();
fixture = TestBed.createComponent(OverlayScrollbarsComponent);