From f4677f472a9b52c53b49f2bff61233fdcd83a0cb Mon Sep 17 00:00:00 2001 From: Dmitry Smurygin Date: Thu, 1 Jun 2023 22:01:26 +0300 Subject: [PATCH] feature: add dropdown vertical position setting --- .../dropdown-demo.component.html | 16 ++++++ .../dropdown-demo/dropdown-demo.component.ts | 12 +++++ .../dropdown-demo-example-1.component.html | 9 +++- .../dropdown-demo-example-1.component.ts | 3 +- .../dropdown-demo-example-2.component.ts | 1 + .../dropdown-demo-example-3.component.ts | 1 + .../directives/dropdown-template.directive.ts | 4 +- .../dropdown/directives/dropdown.directive.ts | 1 + .../tooltip-content.component.ts | 28 +++++----- .../interfaces/dropdown-config.interface.ts | 3 +- .../dropdown-directive-params.interface.ts | 3 +- .../kit/src/services/dropdowns.service.ts | 53 ++++++++----------- 12 files changed, 87 insertions(+), 47 deletions(-) diff --git a/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.html b/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.html index ae1b9dc06..2b0ccf0d6 100644 --- a/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.html +++ b/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.html @@ -26,6 +26,7 @@

Opening a dropdown as context menu

[disabled]="disabled$ | async" [widthType]="widthType$ | async" [horizontalPosition]="horizontalPosition$ | async" + [verticalPosition]="verticalPosition$ | async" > pupaDropdown Properties @@ -77,6 +78,21 @@

Opening a dropdown as context menu

[options]="horizontalPositionOptions" > + + + +
Sets vertical position of dropdown.
+
+ +
diff --git a/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.ts b/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.ts index 078b22a3c..57618d3f8 100644 --- a/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.ts +++ b/projects/demo/src/app/pages/dropdown-demo/dropdown-demo.component.ts @@ -55,4 +55,16 @@ export class DropdownDemoComponent { value: 'end', }, ]; + + public readonly verticalPositionOptions: PropsOption[] = [ + { + caption: 'bottom', + value: 'bottom', + isDefault: true, + }, + { + caption: 'top', + value: 'top', + }, + ]; } diff --git a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.html b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.html index ee6ceead3..343578b8b 100644 --- a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.html +++ b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.html @@ -3,7 +3,14 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit - +
sample content
🎳
diff --git a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.ts b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.ts index 8def507c3..760167b5c 100644 --- a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.ts +++ b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-1/dropdown-demo-example-1.component.ts @@ -1,4 +1,4 @@ -import { HorizontalConnectionPos } from '@angular/cdk/overlay'; +import { HorizontalConnectionPos, VerticalConnectionPos } from '@angular/cdk/overlay'; import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; import { DropdownWidthType } from '@bimeister/pupakit.kit'; @@ -13,4 +13,5 @@ export class DropdownDemoExample1Component { @Input() public disabled: boolean; @Input() public widthType: DropdownWidthType; @Input() public horizontalPosition: HorizontalConnectionPos; + @Input() public verticalPosition: VerticalConnectionPos; } diff --git a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-2/dropdown-demo-example-2.component.ts b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-2/dropdown-demo-example-2.component.ts index 1984124f8..97242bd7c 100644 --- a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-2/dropdown-demo-example-2.component.ts +++ b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-2/dropdown-demo-example-2.component.ts @@ -24,6 +24,7 @@ export class DropdownDemoExample2Component { target: coordinates, widthType: 'auto', horizontalPosition: 'center', + verticalPosition: 'bottom', component: DropdownDemoContainerComponent, injector: this.injector, }); diff --git a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-3/dropdown-demo-example-3.component.ts b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-3/dropdown-demo-example-3.component.ts index 40fcda826..30aeb5ed4 100644 --- a/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-3/dropdown-demo-example-3.component.ts +++ b/projects/demo/src/app/pages/dropdown-demo/examples/dropdown-demo-example-3/dropdown-demo-example-3.component.ts @@ -20,6 +20,7 @@ export class DropdownDemoExample3Component { target: [event.clientX, event.clientY], widthType: 'auto', horizontalPosition: 'center', + verticalPosition: 'bottom', component: DropdownDemoContainerComponent, injector: this.injector, }); diff --git a/projects/kit/src/components/dropdown/directives/dropdown-template.directive.ts b/projects/kit/src/components/dropdown/directives/dropdown-template.directive.ts index b45bae234..c0a895d7c 100644 --- a/projects/kit/src/components/dropdown/directives/dropdown-template.directive.ts +++ b/projects/kit/src/components/dropdown/directives/dropdown-template.directive.ts @@ -1,4 +1,4 @@ -import { HorizontalConnectionPos } from '@angular/cdk/overlay'; +import { HorizontalConnectionPos, VerticalConnectionPos } from '@angular/cdk/overlay'; import { Directive, Input, OnChanges, TemplateRef } from '@angular/core'; import { DropdownDirectiveParams } from '../../../declarations/interfaces/dropdown-directive-params.interface'; import { DropdownTemplateContext } from '../../../declarations/interfaces/dropdown-template-context.interface'; @@ -12,12 +12,14 @@ export class DropdownTemplateDirective implements OnChanges { @Input() public pupaDropdownTemplate?: DropdownHost; @Input() public pupaDropdownTemplateWidthType: DropdownWidthType = 'auto'; @Input() public pupaDropdownTemplateHorizontalPosition: HorizontalConnectionPos = 'start'; + @Input() public pupaDropdownTemplateVerticalPosition: VerticalConnectionPos = 'bottom'; private get dropdownDirectiveParams(): DropdownDirectiveParams { return { templateRef: this.templateRef, widthType: this.pupaDropdownTemplateWidthType, horizontalPosition: this.pupaDropdownTemplateHorizontalPosition, + verticalPosition: this.pupaDropdownTemplateVerticalPosition, }; } diff --git a/projects/kit/src/components/dropdown/directives/dropdown.directive.ts b/projects/kit/src/components/dropdown/directives/dropdown.directive.ts index 2800f772c..5e342a9b7 100644 --- a/projects/kit/src/components/dropdown/directives/dropdown.directive.ts +++ b/projects/kit/src/components/dropdown/directives/dropdown.directive.ts @@ -84,6 +84,7 @@ export class DropdownDirective implements AfterViewInit, OnDestroy, DropdownHost target: this.pupaDropdownRealTriggerElement ?? this.triggerRef.nativeElement, widthType: this.params.widthType, horizontalPosition: this.params.horizontalPosition, + verticalPosition: this.params.verticalPosition, theme, data: { templateRef: this.params.templateRef, diff --git a/projects/kit/src/components/tooltip/components/tooltip-content/tooltip-content.component.ts b/projects/kit/src/components/tooltip/components/tooltip-content/tooltip-content.component.ts index 195005380..d7c600934 100644 --- a/projects/kit/src/components/tooltip/components/tooltip-content/tooltip-content.component.ts +++ b/projects/kit/src/components/tooltip/components/tooltip-content/tooltip-content.component.ts @@ -88,7 +88,19 @@ export class TooltipContentComponent implements OnDestroy { } private calculateTooltipStyleTransform(): Subscription { - const offsetXPx$: Observable = this.tooltipPosition$.pipe( + return zip(this.getTooltipOffsetXPx(), this.getTooltipOffsetYPx()) + .pipe( + map(([offsetXPx, offsetYPx]: [number, number]) => `translate(${offsetXPx}px, ${offsetYPx}px)`), + distinctUntilChanged() + ) + .subscribe((transformStyle: string) => { + this.styleTransform$.next(transformStyle); + this.detectChanges(); + }); + } + + private getTooltipOffsetXPx(): Observable { + return this.tooltipPosition$.pipe( filterNotNil(), map((tooltipPosition: ConnectedOverlayPositionChange) => tooltipPosition.connectionPair), map((connectionPair: ConnectionPositionPair) => { @@ -102,8 +114,10 @@ export class TooltipContentComponent implements OnDestroy { } }) ); + } - const offsetYPx$: Observable = this.tooltipPosition$.pipe( + private getTooltipOffsetYPx(): Observable { + return this.tooltipPosition$.pipe( filterNotNil(), map((tooltipPosition: ConnectedOverlayPositionChange) => tooltipPosition.connectionPair), map((connectionPair: ConnectionPositionPair) => { @@ -117,16 +131,6 @@ export class TooltipContentComponent implements OnDestroy { } }) ); - - return zip(offsetXPx$, offsetYPx$) - .pipe( - map(([offsetXPx, offsetYPx]: [number, number]) => `translate(${offsetXPx}px, ${offsetYPx}px)`), - distinctUntilChanged() - ) - .subscribe((transformStyle: string) => { - this.styleTransform$.next(transformStyle); - this.detectChanges(); - }); } private detectChanges(): void { diff --git a/projects/kit/src/declarations/interfaces/dropdown-config.interface.ts b/projects/kit/src/declarations/interfaces/dropdown-config.interface.ts index 52a15d7f4..719166636 100644 --- a/projects/kit/src/declarations/interfaces/dropdown-config.interface.ts +++ b/projects/kit/src/declarations/interfaces/dropdown-config.interface.ts @@ -1,4 +1,4 @@ -import { HorizontalConnectionPos } from '@angular/cdk/overlay'; +import { HorizontalConnectionPos, VerticalConnectionPos } from '@angular/cdk/overlay'; import { ComponentType } from '@angular/cdk/portal'; import { Injector } from '@angular/core'; import { Position, Theme } from '@bimeister/pupakit.common'; @@ -8,6 +8,7 @@ export interface DropdownConfig { target: HTMLElement | Position; widthType: DropdownWidthType; horizontalPosition: HorizontalConnectionPos; + verticalPosition: VerticalConnectionPos; data?: TData; component?: ComponentType; injector?: Injector; diff --git a/projects/kit/src/declarations/interfaces/dropdown-directive-params.interface.ts b/projects/kit/src/declarations/interfaces/dropdown-directive-params.interface.ts index 3c750a1d4..f7a5fb4a6 100644 --- a/projects/kit/src/declarations/interfaces/dropdown-directive-params.interface.ts +++ b/projects/kit/src/declarations/interfaces/dropdown-directive-params.interface.ts @@ -1,4 +1,4 @@ -import { HorizontalConnectionPos } from '@angular/cdk/overlay'; +import { HorizontalConnectionPos, VerticalConnectionPos } from '@angular/cdk/overlay'; import { TemplateRef } from '@angular/core'; import { DropdownWidthType } from '../types/dropdown-width.type'; import { DropdownTemplateContext } from './dropdown-template-context.interface'; @@ -7,4 +7,5 @@ export interface DropdownDirectiveParams { templateRef: TemplateRef; widthType: DropdownWidthType; horizontalPosition: HorizontalConnectionPos; + verticalPosition: VerticalConnectionPos; } diff --git a/projects/kit/src/services/dropdowns.service.ts b/projects/kit/src/services/dropdowns.service.ts index c94d40790..416b9cf20 100644 --- a/projects/kit/src/services/dropdowns.service.ts +++ b/projects/kit/src/services/dropdowns.service.ts @@ -1,11 +1,4 @@ -import { - ConnectionPositionPair, - FlexibleConnectedPositionStrategy, - HorizontalConnectionPos, - Overlay, - OverlayRef, - VerticalConnectionPos, -} from '@angular/cdk/overlay'; +import { ConnectionPositionPair, FlexibleConnectedPositionStrategy, Overlay, OverlayRef } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import { Injectable, Injector } from '@angular/core'; import { getUuid, isNil, Uuid } from '@bimeister/utilities'; @@ -21,8 +14,17 @@ import { DropdownContainerData } from '../declarations/interfaces/dropdown-conta import { DropdownDataType } from '../declarations/types/utility-types/dropdown-data.utility-type'; import { OVERLAY_VIEWPORT_MARGIN_PX, Position, Theme } from '@bimeister/pupakit.common'; -const HORIZONTAL_POSITIONS: HorizontalConnectionPos[] = ['center', 'end', 'start']; -const VERTICAL_POSITIONS: VerticalConnectionPos[] = ['top', 'bottom']; +const OVERLAY_POSITIONS: ConnectionPositionPair[] = [ + new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }), + new ConnectionPositionPair({ originX: 'center', originY: 'bottom' }, { overlayX: 'center', overlayY: 'top' }), + new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'top' }), + new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'center', originY: 'bottom' }, { overlayX: 'center', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'start', originY: 'top' }, { overlayX: 'start', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'center', originY: 'top' }, { overlayX: 'center', overlayY: 'bottom' }), + new ConnectionPositionPair({ originX: 'end', originY: 'top' }, { overlayX: 'end', overlayY: 'bottom' }), +]; @Injectable({ providedIn: 'root' }) export class DropdownsService { @@ -115,7 +117,7 @@ export class DropdownsService { .position() .flexibleConnectedTo(target instanceof HTMLElement ? target : { x: target[0], y: target[1] }) .withFlexibleDimensions(false) - .withPositions(this.getOverlayPositionsByHorizontalPosition(config.horizontalPosition)) + .withPositions(this.getOverlayPositions(config)) .withViewportMargin(OVERLAY_VIEWPORT_MARGIN_PX); } @@ -132,27 +134,18 @@ export class DropdownsService { return 'auto'; } - private getOverlayPositionsByHorizontalPosition( - currentHorizontalPos: HorizontalConnectionPos + private getOverlayPositions>( + config: DropdownConfig> ): ConnectionPositionPair[] { - const sortedHorizontalPositions: HorizontalConnectionPos[] = HORIZONTAL_POSITIONS.sort( - (horizontalPos: HorizontalConnectionPos) => (horizontalPos === currentHorizontalPos ? -1 : 1) - ); - - const overlayPositions: ConnectionPositionPair[] = VERTICAL_POSITIONS.flatMap( - (verticalPos: VerticalConnectionPos) => - sortedHorizontalPositions.map((horizontalPos: HorizontalConnectionPos) => - this.getConnectionPositionPair(horizontalPos, verticalPos) - ) + const sortedHorizontalPositions: ConnectionPositionPair[] = OVERLAY_POSITIONS.sort( + (position: ConnectionPositionPair) => + position.overlayX === config.horizontalPosition && + position.originY === config.verticalPosition && + position.overlayY !== config.verticalPosition + ? -1 + : 1 ); - return overlayPositions; - } - - private getConnectionPositionPair( - overlayX: HorizontalConnectionPos, - overlayY: VerticalConnectionPos - ): ConnectionPositionPair { - return new ConnectionPositionPair({ originX: overlayX, originY: 'bottom' }, { overlayX, overlayY }); + return sortedHorizontalPositions; } }