Skip to content

Commit

Permalink
feat(overlay): first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Badisi committed Jun 21, 2024
1 parent db5e577 commit a163353
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 66 deletions.
14 changes: 10 additions & 4 deletions projects/overlay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"repository": {
"type": "git",
"url": "https://github.com/dsi-hug/ngx-components.git"
"url": "git+https://github.com/dsi-hug/ngx-components.git"
},
"keywords": [
"angular",
Expand All @@ -24,11 +24,17 @@
"lint": "eslint . --fix",
"test": "ng test overlay",
"test:ci": "ng test overlay --watch=false --browsers=ChromeHeadless",
"build": "ng build overlay -c production"
"build:ng": "ng build overlay -c=production",
"build": "nx build:ng @hug/ngx-overlay --verbose",
"release": "nx release -p=@hug/ngx-overlay --yes --verbose",
"release:dry-run": "nx release -p=@hug/ngx-overlay --verbose --dry-run"
},
"peerDependencies": {
"@angular/common": "^14.3.0",
"@angular/core": "^14.3.0"
"@angular/common": ">= 14",
"@angular/core": ">= 14",
"@angular/cdk": ">= 14",
"rxjs": ">= 7.0.0",
"@hug/ngx-core": "1.1.2"
},
"dependencies": {
"tslib": "^2.6.3"
Expand Down
30 changes: 30 additions & 0 deletions projects/overlay/src/connection-position-pair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ConnectionPositionPair, OriginConnectionPosition, OverlayConnectionPosition } from '@angular/cdk/overlay';

export class OverlayConnectionPositionPair extends ConnectionPositionPair {
public static parse(value: string): ConnectionPositionPair[] {
const values = value.trim().split(',');
const positions = new Array<ConnectionPositionPair>();
values.forEach(pos => {
const poss = pos.trim().split(' ');
if (poss.length !== 4) {
throw new Error('Invalid positions property for DejaMenuComponent. String entry must be of type \'positions="start top end bottom"\'');
}

const originPosition = {
originX: poss[0],
originY: poss[1]
} as OriginConnectionPosition;

const overlayPosition = {
overlayX: poss[2],
overlayY: poss[3]
} as OverlayConnectionPosition;

positions.push(new OverlayConnectionPositionPair(originPosition, overlayPosition));
});

return positions;
}
}

export const defaultConnectionPositionPair: ConnectionPositionPair[] = OverlayConnectionPositionPair.parse('start bottom start top,start top start bottom,end bottom end top,end top end bottom');
22 changes: 0 additions & 22 deletions projects/overlay/src/example/example.component.spec.ts

This file was deleted.

11 changes: 0 additions & 11 deletions projects/overlay/src/example/example.component.ts

This file was deleted.

16 changes: 0 additions & 16 deletions projects/overlay/src/example/example.service.spec.ts

This file was deleted.

6 changes: 0 additions & 6 deletions projects/overlay/src/example/example.service.ts

This file was deleted.

8 changes: 2 additions & 6 deletions projects/overlay/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
/*
* Public API Surface of lib
*/

export * from './example/example.service';
export * from './example/example.component';
export * from './overlay.component';
export * from './connection-position-pair';
5 changes: 5 additions & 0 deletions projects/overlay/src/overlay.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ng-container *ngIf="overlayInfos$ | async as overlayInfos">
<ng-template #overlayCmp cdk-connected-overlay [cdkConnectedOverlayHasBackdrop]="hasBackdrop" [cdkConnectedOverlayBackdropClass]="overlayBackdropClass" [cdkConnectedOverlayOpen]="true" [cdkConnectedOverlayOffsetY]="overlayInfos.offsetY" [cdkConnectedOverlayOffsetX]="overlayInfos.offsetX" [cdkConnectedOverlayOrigin]="overlayInfos.origin" (backdropClick)="close()" (detach)="close()" [cdkConnectedOverlayPositions]="positionPairs" [cdkConnectedOverlayWidth]="overlayInfos.width">
<ng-content></ng-content>
</ng-template>
</ng-container>
5 changes: 5 additions & 0 deletions projects/overlay/src/overlay.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@media print {
.ngx-overlay-container {
display: none;
}
}
195 changes: 195 additions & 0 deletions projects/overlay/src/overlay.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { CdkConnectedOverlay, CdkOverlayOrigin, OverlayContainer, OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { MediaService } from '@hug/ngx-core';
import { BehaviorSubject, combineLatestWith, delay, distinctUntilChanged, EMPTY, map, mergeWith, Observable, of, ReplaySubject, shareReplay, startWith, Subject, switchMap } from 'rxjs';

import { defaultConnectionPositionPair, OverlayConnectionPositionPair } from './connection-position-pair';

export interface ShowParams {
event?: MouseEvent;
offsetX?: number;
offsetY?: number;
}

interface OverlayInfos {
offsetX: number;
offsetY: number;
origin: CdkOverlayOrigin;
width: string;
}

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
selector: 'overlay',
styleUrls: ['./overlay.component.scss'],
templateUrl: './overlay.component.html',
standalone: true,
imports: [
CommonModule,
OverlayModule
]
})
export class OverlayComponent implements OnChanges {
@Input() public ownerElement!: HTMLElement;

@Input() public width!: string;

@Input() public widthForMobile = '100%';

@Input() public overlayBackdropClass = 'cdk-overlay-transparent-backdrop';

@Input() public overlayContainerClass?: string;

@Input() public isMobile?: BooleanInput;

/** Overlay pane containing the options. */
@ViewChild(CdkConnectedOverlay, { static: true }) private overlay?: CdkConnectedOverlay;

public readonly isVisible$: Observable<boolean>;

protected overlayInfos$: Observable<OverlayInfos | undefined>;

private show$ = new ReplaySubject<ShowParams>(1);
private hide$ = new Subject<void>();

/** Renvoie une valeur qui indique si le dialog est affiché. */
private isMobileExt$ = new BehaviorSubject<boolean | undefined>(undefined);

private _hasBackdrop = true;
private ownerElement$ = new BehaviorSubject<HTMLElement | undefined>(undefined);

@Input() public set hasBackdrop(value: BooleanInput) {
this._hasBackdrop = coerceBooleanProperty(value);
}

public get hasBackdrop(): BooleanInput {
return this._hasBackdrop;
}

private _positions = defaultConnectionPositionPair;
private _positionsForMobile?: OverlayConnectionPositionPair[];

public constructor(private elementRef: ElementRef, private overlayContainer: OverlayContainer, mediaService: MediaService) {
const containerElement = this.overlayContainer.getContainerElement();
containerElement.addEventListener('contextmenu', (event: Event) => {
event.preventDefault();
return false;
});

const isMobile$ = this.isMobileExt$.pipe(
switchMap(isMobileExt => {
if (isMobileExt !== undefined) {
return of(isMobileExt);
}

return mediaService.isMobile$;
}),
startWith(false),
shareReplay({ bufferSize: 1, refCount: false })
);

this.overlayInfos$ = this.show$.pipe(
mergeWith(this.hide$),
switchMap(showParams => {
if (!showParams) {
return of(undefined);
}

containerElement.className = Array.from(containerElement.classList)
.filter(token => token.startsWith('cdk'))
.join(' ');

containerElement.classList.add('ngx-overlay-container');
if (this.overlayContainerClass) {
this.overlayContainerClass.split(' ').forEach(className => {
containerElement.classList.add(className);
});
}

const info$ = this.ownerElement$.pipe(
combineLatestWith(isMobile$),
map(([ownerElement, isMobile]) => ({
offsetX: showParams.offsetX && +showParams.offsetX || 0,
offsetY: showParams.offsetY && +showParams.offsetY || 0,
origin: new CdkOverlayOrigin(new ElementRef((isMobile && document.body) ?? showParams.event?.target ?? ownerElement ?? this.elementRef.nativeElement)),
width: isMobile ? this.widthForMobile : this.width
} as OverlayInfos))
);

const updatePosition$ = info$.pipe(
delay(1),
switchMap(() => {
this.overlay?.overlayRef?.updatePosition();
return EMPTY;
})
);

return info$.pipe(
mergeWith(updatePosition$)
);
}),
shareReplay({ bufferSize: 1, refCount: false })
);


this.isVisible$ = this.overlayInfos$.pipe(
map(Boolean),
startWith(false),
distinctUntilChanged()
);
}

public ngOnChanges(changes: SimpleChanges): void {
if (changes['isMobile']) {
this.isMobileExt$.next(this.isMobile !== undefined ? coerceBooleanProperty(this.isMobile) : undefined);
}

if (changes['ownerElement']) {
this.ownerElement$.next(this.ownerElement);
}
}

public get positionPairs(): OverlayConnectionPositionPair[] {
return this.positions;
}

public get positions(): OverlayConnectionPositionPair[] {
if (!this.isMobile) {
return this._positions;
} else if (this._positionsForMobile) {
return this._positionsForMobile;
} else {
return OverlayConnectionPositionPair.parse('start top start top');
}
}

@Input()
public set positions(value: OverlayConnectionPositionPair[] | string) {
this._positions = typeof value === 'string' ? OverlayConnectionPositionPair.parse(value) : value;
}

/** Si pas null, sera utilisé quand isMobile est vrai. Si null et si isMobile est vrai,
* alors c'est la valeur 'start top start top' qui est utilisée.
* */
@Input()
public set positionsForMobile(value: OverlayConnectionPositionPair[] | string) {
this._positionsForMobile = typeof value === 'string' ? OverlayConnectionPositionPair.parse(value) : value;
}

/** Affiche le dialog. */
public show(eventOrOffsetX?: MouseEvent | number, offsetY?: number): void {
if (typeof eventOrOffsetX === 'number') {
this.show$.next({ offsetX: eventOrOffsetX, offsetY });
} else {
this.show$.next({ event: eventOrOffsetX, offsetY });
}
}

/** Ferme le dialog. */
public close(): void {
this.hide$.next();
}
}
2 changes: 1 addition & 1 deletion projects/overlay/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"declaration": true,
Expand Down

0 comments on commit a163353

Please sign in to comment.