From 83ad5f526eba52c0cf6a5b49d064439d6b215b5e Mon Sep 17 00:00:00 2001 From: Nino Righi Date: Wed, 10 Dec 2025 09:50:15 +0000 Subject: [PATCH] Merged PR 2075: fix(ui-layout, ui-input-controls, shared-filter): Set overlayPositions inside... fix(ui-layout, ui-input-controls, shared-filter): Set overlayPositions inside filter-menu-button and outsourced the logic Ref: #5526, #5477 --- .../filter-menu-button.component.html | 6 +- .../filter-menu-button.component.ts | 5 +- .../src/lib/dropdown/dropdown.component.html | 4 +- .../src/lib/dropdown/dropdown.component.ts | 85 ++------------- libs/ui/layout/src/index.ts | 1 + libs/ui/layout/src/lib/overlay-positions.ts | 100 ++++++++++++++++++ 6 files changed, 118 insertions(+), 83 deletions(-) create mode 100644 libs/ui/layout/src/lib/overlay-positions.ts diff --git a/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.html b/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.html index 6b5f0a617..da1534034 100644 --- a/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.html +++ b/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.html @@ -20,11 +20,13 @@ cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="open()" + [cdkConnectedOverlayPositions]="overlayPositions" [cdkConnectedOverlayHasBackdrop]="true" cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" [cdkConnectedOverlayScrollStrategy]="scrollStrategy" - [cdkConnectedOverlayOffsetX]="-10" - [cdkConnectedOverlayOffsetY]="18" + [cdkConnectedOverlayFlexibleDimensions]="true" + [cdkConnectedOverlayGrowAfterOpen]="true" + [cdkConnectedOverlayPush]="true" cdkConnectedOverlayWidth="18.375rem" (backdropClick)="toggle()" > diff --git a/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.ts b/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.ts index f232ec554..5c5ea1b05 100644 --- a/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.ts +++ b/libs/shared/filter/src/lib/menus/filter-menu/filter-menu-button.component.ts @@ -2,13 +2,13 @@ import { Overlay, OverlayModule } from '@angular/cdk/overlay'; import { ChangeDetectionStrategy, Component, - computed, inject, input, model, output, } from '@angular/core'; import { IconButtonComponent } from '@isa/ui/buttons'; +import { DROPDOWN_OVERLAY_POSITIONS } from '@isa/ui/layout'; import { FilterMenuComponent } from './filter-menu.component'; import { FilterService } from '../../core'; @@ -30,6 +30,9 @@ export class FilterMenuButtonComponent { selectedFilters = this.#filter.selectedFilterCount; + /** Standard overlay positions for the filter menu panel */ + readonly overlayPositions = DROPDOWN_OVERLAY_POSITIONS; + /** * Tracks the open state of the filter menu. */ diff --git a/libs/ui/input-controls/src/lib/dropdown/dropdown.component.html b/libs/ui/input-controls/src/lib/dropdown/dropdown.component.html index 11f218df7..5c91a6bb2 100644 --- a/libs/ui/input-controls/src/lib/dropdown/dropdown.component.html +++ b/libs/ui/input-controls/src/lib/dropdown/dropdown.component.html @@ -10,7 +10,9 @@ [cdkConnectedOverlayDisableClose]="false" cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" [cdkConnectedOverlayMinWidth]="overlayMinWidth" - [cdkConnectedOverlayLockPosition]="true" + [cdkConnectedOverlayFlexibleDimensions]="true" + [cdkConnectedOverlayGrowAfterOpen]="true" + [cdkConnectedOverlayPush]="true" [cdkConnectedOverlayScrollStrategy]="blockScrollStrategy" (backdropClick)="close()" (detach)="isOpen.set(false)" diff --git a/libs/ui/input-controls/src/lib/dropdown/dropdown.component.ts b/libs/ui/input-controls/src/lib/dropdown/dropdown.component.ts index 49f2b6433..9ebdbf5b1 100644 --- a/libs/ui/input-controls/src/lib/dropdown/dropdown.component.ts +++ b/libs/ui/input-controls/src/lib/dropdown/dropdown.component.ts @@ -13,9 +13,7 @@ import { signal, viewChild, } from '@angular/core'; - import { isEqual } from 'lodash'; - import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { isaActionChevronUp, isaActionChevronDown } from '@isa/icons'; @@ -23,14 +21,15 @@ import { ActiveDescendantKeyManager } from '@angular/cdk/a11y'; import { CdkConnectedOverlay, CdkOverlayOrigin, - ConnectedPosition, ScrollStrategyOptions, } from '@angular/cdk/overlay'; import { DropdownAppearance } from './dropdown.types'; import { DropdownService } from './dropdown.service'; -import { CloseOnScrollDirective } from '@isa/ui/layout'; +import { + CloseOnScrollDirective, + DROPDOWN_OVERLAY_POSITIONS, +} from '@isa/ui/layout'; import { logger } from '@isa/core/logging'; - import { DropdownOptionComponent } from './dropdown-option.component'; import { DropdownFilterComponent } from './dropdown-filter.component'; import { DROPDOWN_HOST, DropdownHost } from './dropdown-host'; @@ -99,80 +98,8 @@ export class DropdownButtonComponent return this.#scrollStrategy.block(); } - /** Offset in pixels between the trigger and the overlay panel */ - readonly #overlayOffset = 12; - - /** - * Position priority for the overlay panel. - * Order: bottom-left, bottom-right, top-left, top-right, - * right-top, right-bottom, left-top, left-bottom - */ - readonly overlayPositions: ConnectedPosition[] = [ - // Bottom left - { - originX: 'start', - originY: 'bottom', - overlayX: 'start', - overlayY: 'top', - offsetY: this.#overlayOffset, - }, - // Bottom right - { - originX: 'end', - originY: 'bottom', - overlayX: 'end', - overlayY: 'top', - offsetY: this.#overlayOffset, - }, - // Top left - { - originX: 'start', - originY: 'top', - overlayX: 'start', - overlayY: 'bottom', - offsetY: -this.#overlayOffset, - }, - // Top right - { - originX: 'end', - originY: 'top', - overlayX: 'end', - overlayY: 'bottom', - offsetY: -this.#overlayOffset, - }, - // Right top - { - originX: 'end', - originY: 'top', - overlayX: 'start', - overlayY: 'top', - offsetX: this.#overlayOffset, - }, - // Right bottom - { - originX: 'end', - originY: 'bottom', - overlayX: 'start', - overlayY: 'bottom', - offsetX: this.#overlayOffset, - }, - // Left top - { - originX: 'start', - originY: 'top', - overlayX: 'end', - overlayY: 'top', - offsetX: -this.#overlayOffset, - }, - // Left bottom - { - originX: 'start', - originY: 'bottom', - overlayX: 'end', - overlayY: 'bottom', - offsetX: -this.#overlayOffset, - }, - ]; + /** Standard overlay positions for the dropdown panel */ + readonly overlayPositions = DROPDOWN_OVERLAY_POSITIONS; appearance = input(DropdownAppearance.AccentOutline); diff --git a/libs/ui/layout/src/index.ts b/libs/ui/layout/src/index.ts index 8f8c61629..8197e5601 100644 --- a/libs/ui/layout/src/index.ts +++ b/libs/ui/layout/src/index.ts @@ -2,3 +2,4 @@ export * from './lib/breakpoint.directive'; export * from './lib/breakpoint'; export * from './lib/close-on-scroll.directive'; export * from './lib/in-viewport.directive'; +export * from './lib/overlay-positions'; diff --git a/libs/ui/layout/src/lib/overlay-positions.ts b/libs/ui/layout/src/lib/overlay-positions.ts new file mode 100644 index 000000000..71ceca1bf --- /dev/null +++ b/libs/ui/layout/src/lib/overlay-positions.ts @@ -0,0 +1,100 @@ +import { ConnectedPosition } from '@angular/cdk/overlay'; + +/** Default offset in pixels between the trigger and the overlay panel */ +export const OVERLAY_OFFSET = 12; + +/** + * Creates standard dropdown overlay positions with configurable offset. + * + * Position priority for overlay panels: + * CDK tries positions in order and picks the first one that fits in the viewport. + * + * With `flexibleDimensions=true` and `push=true`, the overlay will: + * 1. Try positions in order + * 2. Constrain size to fit in viewport (flexibleDimensions) + * 3. Push into viewport if needed (push) + * + * Priority order (most to least preferred): + * 1. Below the trigger (default, most natural for dropdowns) + * 2. Above the trigger (when no space below) + * 3. To the right (when no vertical space) + * 4. To the left (last resort) + * + * @param offset - Offset in pixels between trigger and overlay (default: 12) + * @returns Array of ConnectedPosition configurations + */ +export const createOverlayPositions = ( + offset: number = OVERLAY_OFFSET, +): ConnectedPosition[] => [ + // Priority 1: Below trigger, left-aligned (default/preferred) + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + offsetY: offset, + }, + // Priority 2: Below trigger, right-aligned + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + offsetY: offset, + }, + // Priority 3: Above trigger, left-aligned + { + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + offsetY: -offset, + }, + // Priority 4: Above trigger, right-aligned + { + originX: 'end', + originY: 'top', + overlayX: 'end', + overlayY: 'bottom', + offsetY: -offset, + }, + // Priority 5: Right of trigger, top-aligned + { + originX: 'end', + originY: 'top', + overlayX: 'start', + overlayY: 'top', + offsetX: offset, + }, + // Priority 6: Right of trigger, bottom-aligned + { + originX: 'end', + originY: 'bottom', + overlayX: 'start', + overlayY: 'bottom', + offsetX: offset, + }, + // Priority 7: Left of trigger, top-aligned + { + originX: 'start', + originY: 'top', + overlayX: 'end', + overlayY: 'top', + offsetX: -offset, + }, + // Priority 8: Left of trigger, bottom-aligned + { + originX: 'start', + originY: 'bottom', + overlayX: 'end', + overlayY: 'bottom', + offsetX: -offset, + }, +]; + +/** + * Standard dropdown overlay positions with default offset (12px). + * Use this for most dropdown/menu components. + */ +export const DROPDOWN_OVERLAY_POSITIONS: ConnectedPosition[] = + createOverlayPositions(OVERLAY_OFFSET);