Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions packages/components/dropdown/dropdown-trigger.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,37 +592,33 @@ export class KbqDropdownTrigger implements AfterContentInit, OnDestroy {
}
}

const resolvedOffsetY = this.offsetY ?? (overlayY === 'top' ? offsetY : -offsetY);
const resolvedFallbackOffsetY = this.offsetY ?? (overlayFallbackY === 'top' ? offsetY : -offsetY);

positionStrategy.withPositions([
{
originX,
originY,
overlayX,
overlayY,
offsetY: this.offsetY ?? offsetY,
offsetX: this.offsetX ?? -offsetX
},
{ originX, originY, overlayX, overlayY, offsetY: resolvedOffsetY, offsetX: this.offsetX ?? -offsetX },
{
originX: originFallbackX,
originY,
overlayX: overlayFallbackX,
overlayY,
offsetY: this.offsetY ?? offsetY,
offsetY: resolvedOffsetY,
offsetX: this.offsetX ?? offsetX
},
{
originX,
originY: originFallbackY,
overlayX,
overlayY: overlayFallbackY,
offsetY: this.offsetY ?? -offsetY,
offsetY: resolvedFallbackOffsetY,
offsetX: this.offsetX ?? -offsetX
},
{
originX: originFallbackX,
originY: originFallbackY,
overlayX: overlayFallbackX,
overlayY: overlayFallbackY,
offsetY: this.offsetY ?? -offsetY,
offsetY: resolvedFallbackOffsetY,
offsetX: this.offsetX ?? -offsetX
}
]);
Expand Down
75 changes: 74 additions & 1 deletion packages/components/dropdown/dropdown.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FocusMonitor } from '@angular/cdk/a11y';
import { Direction, Directionality } from '@angular/cdk/bidi';
import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
import { FlexibleConnectedPositionStrategy, Overlay, OverlayContainer } from '@angular/cdk/overlay';
import { ScrollDispatcher } from '@angular/cdk/scrolling';
import {
ChangeDetectionStrategy,
Expand Down Expand Up @@ -31,6 +31,7 @@ import {
TAB,
createKeyboardEvent,
createMouseEvent,
defaultOffsetY,
dispatchEvent,
dispatchFakeEvent,
dispatchKeyboardEvent,
Expand Down Expand Up @@ -961,11 +962,83 @@ describe('KbqDropdown', () => {
expect(Math.floor(overlayRect.top)).toBe(Math.floor(triggerRect.bottom));
});

it('should open above trigger when yPosition is above and there is space above', () => {
const fixture = createComponent(PositionedDropdown);

fixture.detectChanges();
const trigger = fixture.componentInstance.triggerEl().nativeElement;

// Push trigger down so it has space to open above
trigger.style.position = 'fixed';
trigger.style.top = '600px';
trigger.style.left = '100px';

fixture.componentInstance.trigger().open();
fixture.detectChanges();

const overlayPane = getOverlayPane();
const triggerRect = trigger.getBoundingClientRect();
const overlayRect = overlayPane.getBoundingClientRect();

// Panel is above: overlay bottom should not exceed trigger top
expect(Math.floor(overlayRect.bottom)).toBeLessThanOrEqual(Math.floor(triggerRect.top));
});

function getOverlayPane(): HTMLElement {
return overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
}
});

describe('y-position offsetY', () => {
afterEach(() => jest.restoreAllMocks());

it('should pass negative offsetY for primary positions when yPosition is above', () => {
const withPositionsSpy = jest.spyOn(FlexibleConnectedPositionStrategy.prototype, 'withPositions');
const fixture = createComponent(PositionedDropdown); // yPosition='above' by default

fixture.detectChanges();
fixture.componentInstance.trigger().open();
fixture.detectChanges();

const positions = withPositionsSpy.mock.calls[0][0];

// Primary positions: overlayY='bottom' means panel is above trigger.
// offsetY must be negative to push the panel UP and create a gap (not overlap).
expect(positions[0].overlayY).toBe('bottom');
expect(positions[0].offsetY).toBe(-defaultOffsetY);
expect(positions[1].overlayY).toBe('bottom');
expect(positions[1].offsetY).toBe(-defaultOffsetY);

// Fallback positions: overlayY='top' means panel is below trigger.
// offsetY must be positive to push the panel DOWN and create a gap.
expect(positions[2].overlayY).toBe('top');
expect(positions[2].offsetY).toBe(defaultOffsetY);
expect(positions[3].overlayY).toBe('top');
expect(positions[3].offsetY).toBe(defaultOffsetY);
});

it('should pass positive offsetY for primary positions when yPosition is below', () => {
const withPositionsSpy = jest.spyOn(FlexibleConnectedPositionStrategy.prototype, 'withPositions');
const fixture = createComponent(SimpleDropdown); // yPosition='below' by default

fixture.detectChanges();
fixture.componentInstance.trigger().open();
fixture.detectChanges();

const positions = withPositionsSpy.mock.calls[0][0];

// Primary positions: overlayY='top' means panel is below trigger.
// offsetY must be positive to push the panel DOWN and create a gap.
expect(positions[0].overlayY).toBe('top');
expect(positions[0].offsetY).toBe(defaultOffsetY);

// Fallback positions: overlayY='bottom' means panel is above trigger.
// offsetY must be negative to push the panel UP and create a gap.
expect(positions[2].overlayY).toBe('bottom');
expect(positions[2].offsetY).toBe(-defaultOffsetY);
});
});

describe('overlapping trigger', () => {
/**
* This test class is used to create components containing a dropdown.
Expand Down
Loading