Skip to content

Commit fd344d5

Browse files
vursenclaude
andcommitted
fix: invalidate overlay content size cache on positionTarget change
The PositionMixin caches the largest content size ever measured via `Math.max(__oldContentWidth, offsetWidth)` to keep alignment stable under window-resize squeeze. The cache was never invalidated when `positionTarget` changed, so an overlay that switched to a target with smaller content (e.g. a tooltip moving from a long-text item to a short-text one) kept calculating fit against the previous larger size and stayed flipped to the wrong side. Reset `__oldContentWidth` and `__oldContentHeight` whenever `positionTarget` changes so the next `_updatePosition` measures the overlay against its current content. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 81e3599 commit fd344d5

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

packages/overlay/src/vaadin-overlay-position-mixin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ export const PositionMixin = (superClass) =>
145145
if (props.has('positionTarget')) {
146146
const oldTarget = props.get('positionTarget');
147147

148+
// Invalidate the cached content size so the next `_updatePosition` call
149+
// measures the overlay against the new target instead of carrying over
150+
// a larger size from the previous one.
151+
this.__oldContentWidth = undefined;
152+
this.__oldContentHeight = undefined;
153+
148154
// 1. When position target is removed, always reset position settings
149155
// 2. When position target is set, reset if overlay was opened before
150156
if ((!this.positionTarget && oldTarget) || (this.positionTarget && !oldTarget && !!this.__margins)) {

packages/overlay/test/position-mixin.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,30 @@ describe('position mixin', () => {
511511
expect(parent.hasAttribute('end-aligned')).to.be.true;
512512
});
513513

514+
it('should re-evaluate alignment when positionTarget changes after content shrinks', async () => {
515+
// Make the content wide enough to force a flip at a target position
516+
// where the narrow content would still fit.
517+
overlayContent.style.width = '200px';
518+
const wideFlipThreshold = document.documentElement.clientWidth - 200 - margin;
519+
target.style.left = `${wideFlipThreshold + 3}px`;
520+
updatePosition();
521+
expect(overlay.hasAttribute('end-aligned')).to.be.true;
522+
523+
// Shrink the content so the same target position fits without a flip.
524+
overlayContent.style.width = '50px';
525+
526+
// Switch to a fresh target at the same position; assert right after
527+
// the synchronous Lit update so we observe the cached-size behavior
528+
// before the deferred `requestAnimationFrame` re-position kicks in.
529+
const newTarget = target.cloneNode(true);
530+
target.parentElement.appendChild(newTarget);
531+
overlay.positionTarget = newTarget;
532+
await overlay.updateComplete;
533+
534+
expect(overlay.hasAttribute('start-aligned')).to.be.true;
535+
expect(overlay.hasAttribute('end-aligned')).to.be.false;
536+
});
537+
514538
describe('no overlap', () => {
515539
beforeEach(() => {
516540
overlay.noHorizontalOverlap = true;

0 commit comments

Comments
 (0)