From 67b464e535ab53b17fd6ebf94a3fbbfa785307c9 Mon Sep 17 00:00:00 2001 From: Ankit Garg <146389256+AnKiT-GaRG2@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:40:48 +0530 Subject: [PATCH] fix(ui-components): resize overlays from their top left corner Overlays such as Collections Info are positioned with a bottom/right anchor, so dragging the bottom right resize handle grew them towards the top left and could push them off screen. Re-anchor the overlay by its top left corner when a resize starts, keeping it where it is on screen. Fixes #1048 --- .../ui-menu/overlay/overlay.component.test.ts | 28 ++++++++++++++++++ .../ui-menu/overlay/overlay.component.ts | 29 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.test.ts b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.test.ts index 9dbc06bd2..935ee2a43 100644 --- a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.test.ts +++ b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.test.ts @@ -103,6 +103,34 @@ describe('OverlayComponent', () => { expect(card.style.height).toBe('360px'); }); + it('should anchor a bottom/right positioned overlay by its top left corner', () => { + const wrapper = document.createElement('div'); + wrapper.className = 'cdk-global-overlay-wrapper'; + wrapper.style.justifyContent = 'flex-end'; + wrapper.style.alignItems = 'flex-end'; + const pane = document.createElement('div'); + pane.className = 'cdk-overlay-pane'; + pane.style.marginRight = '15px'; + pane.style.marginBottom = '80px'; + wrapper.appendChild(pane); + pane.appendChild(fixture.nativeElement); + + // Bottom/right anchored at first, then at the top left of the wrapper. + pane.getBoundingClientRect = jest + .fn() + .mockReturnValueOnce({ left: 500, top: 200 } as DOMRect) + .mockReturnValueOnce({ left: 0, top: 0 } as DOMRect); + + component.onResizeStart(pointerEvent('pointerdown', 400, 300)); + + expect(wrapper.style.justifyContent).toBe('flex-start'); + expect(wrapper.style.alignItems).toBe('flex-start'); + expect(pane.style.marginLeft).toBe('500px'); + expect(pane.style.marginTop).toBe('200px'); + expect(pane.style.marginRight).toBe('0px'); + expect(pane.style.marginBottom).toBe('0px'); + }); + it('should not resize below the minimum size', () => { component.onResizeStart(pointerEvent('pointerdown', 400, 300)); handle.dispatchEvent(pointerEvent('pointermove', 0, 0)); diff --git a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.ts b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.ts index d5542b9e7..629100396 100644 --- a/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.ts +++ b/packages/phoenix-ng/projects/phoenix-ui-components/lib/components/ui-menu/overlay/overlay.component.ts @@ -138,6 +138,7 @@ export class OverlayComponent implements OnInit, AfterViewInit, OnDestroy { event.preventDefault(); this.bringToFront(); + this.anchorToTopLeft(); // The 3D overlay sizes its canvas, everything else sizes the whole card, // so measure whichever one the drag is going to act on. @@ -170,6 +171,34 @@ export class OverlayComponent implements OnInit, AfterViewInit, OnDestroy { }); } + /** + * Anchor the overlay by its top left corner. + * + * Overlays opened with a `bottom`/`right` position keep that corner fixed, + * so growing them moves the top left corner instead of the one being dragged. + * Switch to top/left anchoring without moving the overlay on screen. + */ + private anchorToTopLeft() { + const host = this.elementRef.nativeElement as HTMLElement; + const wrapper = host.closest('.cdk-global-overlay-wrapper'); + const pane = host.closest('.cdk-overlay-pane'); + if (!wrapper || !pane) { + return; + } + + const before = pane.getBoundingClientRect(); + wrapper.style.justifyContent = 'flex-start'; + wrapper.style.alignItems = 'flex-start'; + pane.style.marginLeft = '0px'; + pane.style.marginTop = '0px'; + pane.style.marginRight = '0px'; + pane.style.marginBottom = '0px'; + + const after = pane.getBoundingClientRect(); + pane.style.marginLeft = `${before.left - after.left}px`; + pane.style.marginTop = `${before.top - after.top}px`; + } + /** Resize the overlay to follow the pointer. */ private onResizeMove = (event: PointerEvent) => { const session = this.resizeSession;