From 3e3173727e831af9bcfd2440583ac28888253a4c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 13:21:12 +0000 Subject: [PATCH] fix(select): use traditional CDK overlay for proper repositioning Replace inline popover overlay with traditional cdkConnectedOverlay using ConnectedPosition array so the dropdown automatically flips above the trigger when there is not enough space below. https://claude.ai/code/session_01MEbQJiNKarA74je1KFZUQF --- .../src/lib/components/select/select-popup.ts | 2 +- libs/ui/src/lib/components/select/select.ts | 33 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/libs/ui/src/lib/components/select/select-popup.ts b/libs/ui/src/lib/components/select/select-popup.ts index 141dc915c..c11779354 100644 --- a/libs/ui/src/lib/components/select/select-popup.ts +++ b/libs/ui/src/lib/components/select/select-popup.ts @@ -29,7 +29,7 @@ export class ScSelectPopup { protected readonly class = computed(() => cn( - 'bg-popover text-popover-foreground ring-foreground/10 relative z-50 mt-1 flex w-full max-h-44 min-w-36 flex-col rounded-lg p-1 shadow-md ring-1', + 'bg-popover text-popover-foreground ring-foreground/10 relative z-50 flex w-full max-h-44 min-w-36 flex-col rounded-lg p-1 shadow-md ring-1', this.combobox.expanded() ? 'opacity-100 visible transition-[max-height,opacity,visibility] duration-150 ease-out' : 'max-h-0 opacity-0 invisible transition-[max-height,opacity,visibility] duration-150 ease-in [transition-delay:0s,0s,150ms]', diff --git a/libs/ui/src/lib/components/select/select.ts b/libs/ui/src/lib/components/select/select.ts index a3f019f86..31711127b 100644 --- a/libs/ui/src/lib/components/select/select.ts +++ b/libs/ui/src/lib/components/select/select.ts @@ -1,5 +1,5 @@ import { Combobox, ComboboxPopupContainer } from '@angular/aria/combobox'; -import { OverlayModule } from '@angular/cdk/overlay'; +import { ConnectedPosition, OverlayModule } from '@angular/cdk/overlay'; import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, @@ -19,6 +19,25 @@ import { ScSelectList } from './select-list'; import { ScSelectPortal } from './select-portal'; import { ScSelectTrigger } from './select-trigger'; +const positions: ConnectedPosition[] = [ + // Below, aligned to start (preferred) + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + offsetY: 4, + }, + // Above, aligned to start (fallback when not enough space below) + { + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + offsetY: -4, + }, +]; + @Component({ selector: 'div[scSelect]', exportAs: 'scSelect', @@ -34,12 +53,10 @@ import { ScSelectTrigger } from './select-trigger'; @if (origin(); as origin) { @@ -65,12 +82,16 @@ export class ScSelect implements FormValueControl { protected readonly selectPortal = contentChild.required(ScSelectPortal); readonly origin = computed(() => this.trigger()?.elementRef); + readonly triggerWidth = computed( + () => this.trigger()?.elementRef.nativeElement.offsetWidth ?? 0, + ); readonly values = computed(() => this.content()?.values() ?? []); readonly displayValue = computed(() => this.value() || this.placeholder()); protected readonly class = computed(() => cn('relative min-w-36 w-fit', this.classInput()), ); + protected readonly positions = positions; protected readonly combobox = inject(Combobox); constructor() {