diff --git a/projects/angular/src/utils/popover/providers/popover-position.service.spec.ts b/projects/angular/src/utils/popover/providers/popover-position.service.spec.ts index 65c9394606..95677d5bcf 100644 --- a/projects/angular/src/utils/popover/providers/popover-position.service.spec.ts +++ b/projects/angular/src/utils/popover/providers/popover-position.service.spec.ts @@ -394,6 +394,28 @@ export default function (): void { expect(handleVerticalAxisTwoViolationsSpy).not.toHaveBeenCalled(); expect(handleHorizontalAxisTwoViolationsSpy.calls.count()).toEqual(1); }); + + it('content is rendered within the the body (y >= 0) when AXIS is HORIZONTAL and there is a BOTTOM ViewportViolation', function (this: TestContext) { + const bottomLeftViolation: ClrPopoverPosition = { + axis: ClrAxis.VERTICAL, + side: ClrSide.AFTER, + anchor: ClrAlignment.END, + content: ClrAlignment.START, + }; + + // Set the popover content to be as big as the window + popoverContent.style.width = '25px'; + popoverContent.style.height = window.innerHeight + 'px'; + // Set the anchor element to render at the bottom of the body/window + this.eventService.anchorButtonRef.nativeElement.style.bottom = '100%'; + this.eventService.anchorButtonRef.nativeElement.style.position = 'absolute'; + this.positionService.position = bottomLeftViolation; + document.body.appendChild(popoverContent); + const result = this.positionService.alignContent(popoverContent); + + // Verify the yOffset is not a negative value (on the view this would translate to a negative top value). + expect(result.yOffset).toBeGreaterThanOrEqual(0); + }); }); describe('handles content realignment', function (this: TestContext) { diff --git a/projects/angular/src/utils/popover/providers/popover-position.service.ts b/projects/angular/src/utils/popover/providers/popover-position.service.ts index 520bf03041..3947171fa0 100644 --- a/projects/angular/src/utils/popover/providers/popover-position.service.ts +++ b/projects/angular/src/utils/popover/providers/popover-position.service.ts @@ -65,12 +65,12 @@ export class ClrPopoverPositionService { * * Note, more than 3 viewport violations and there isn't anything we can do to help. Also when there are two * violations, we can't help if the violations are TOP+BOTTOM || LEFT+RIGHT => There is no transformation we - * can make to the postion that will help. + * can make to the position that will help. * * Some examples: * There is only one error and Primary axis is VERTICAL * - this.handleVerticalAxisOneViolation has a switch that will use the error sum to apply the correct - * transform to the postion based on the reduction of visibilityViolations. + * transform to the position based on the reduction of visibilityViolations. * * There are two errors and Primary axis is HORIZONTAL * - handleHorizontalAxisTwoViolations has a switch that uses the error sum to apply both transforms needed to @@ -103,6 +103,15 @@ export class ClrPopoverPositionService { this.contentOffsets.yOffset += Math.abs(this.currentContentCoords.top); } + /** + * This detects the condition where the popover is flipped because it would violate the bottom of the viewport, but flipping it results in the + * popover rendering above the top of the body (y coordinate outside the body). In that event, it should be rendered within the body + * as much as possible, so this logic sets the top of popover to render touching the top of the body. + */ + if (this.contentOffsets.yOffset + this.currentAnchorCoords.y < 0) { + this.contentOffsets.yOffset = 0 - this.currentContentCoords.top; + } + return this.contentOffsets; }