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
This commit is contained in:
Nino Righi
2025-12-10 09:50:15 +00:00
committed by Lorenz Hilpert
parent ccc5285602
commit 83ad5f526e
6 changed files with 118 additions and 83 deletions

View File

@@ -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()"
>

View File

@@ -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.
*/

View File

@@ -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)"

View File

@@ -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<T>
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>(DropdownAppearance.AccentOutline);

View File

@@ -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';

View File

@@ -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);