Skip to content

Commit 26cebda

Browse files
JohnMcLearclaude
andcommitted
fix(7696): float nice-select dropdowns above scrollable popups
Qodo flagged that making .popup-content a scroll container clips absolutely-positioned descendants — so the Settings popup's font and language dropdowns can be truncated when their list extends past the popup's scroll bounds on short viewports. Mirror the existing toolbar workaround: when a nice-select sits inside .popup-content, switch the list to position:fixed (CSS) and place it with viewport-relative coordinates from getBoundingClientRect (JS), respecting the existing reverse class for upward-opening lists. Also relax the regression test per Qodo: drop the brittle scrollHeight > clientHeight assertion in favour of asserting the popup declares overflow-y:auto and proving Delete pad is initially off-screen, then reachable via scroll. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fb10ec7 commit 26cebda

3 files changed

Lines changed: 31 additions & 10 deletions

File tree

src/static/css/pad/form.css

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,11 @@ select, .nice-select {
132132
bottom: calc(100% + 5px);
133133
top: auto;
134134
}
135-
.toolbar .nice-select .list {
135+
.toolbar .nice-select .list,
136+
/* Popups are scroll containers (see popup.css), which would otherwise clip the
137+
absolutely-positioned dropdown list. Float it above the popup with fixed
138+
positioning, matching how the toolbar dropdowns escape their container. */
139+
.popup .nice-select .list {
136140
position: fixed;
137141
top: auto;
138142
left: auto;

src/static/js/vendors/nice-select.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,22 @@
123123
}
124124
$dropdown.find('.list').css('max-height', $maxListHeight + 'px');
125125

126+
// Popups are scroll containers (since #7696) which would clip the
127+
// absolutely-positioned dropdown list. The list is repositioned with
128+
// `position: fixed` (see form.css) so it floats above the popup; we
129+
// need viewport-relative coordinates here. Done after the reverse
130+
// class is decided so we know which side of the dropdown to anchor.
131+
if ($dropdown.closest('.toolbar').length === 0
132+
&& $dropdown.closest('.popup-content').length > 0) {
133+
var rect = $dropdown[0].getBoundingClientRect();
134+
var $list = $dropdown.find('.list');
135+
$list.css('left', rect.left);
136+
$list.css('min-width', $dropdown.outerWidth() + 'px');
137+
$list.css('top', $dropdown.hasClass('reverse')
138+
? rect.top - $maxListHeight - 5
139+
: rect.bottom);
140+
}
141+
126142
} else {
127143
$dropdown.trigger('focus');
128144
}

src/tests/frontend-new/specs/pad_settings.spec.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,16 @@ test.describe('creator-owned pad settings', () => {
179179
await expect(popupContent).toBeVisible();
180180
await expect(page.locator('#pad-settings-section')).toBeVisible();
181181

182-
const dims = await popupContent.evaluate((el) => ({
183-
overflowY: getComputedStyle(el).overflowY,
184-
scrolls: el.scrollHeight > el.clientHeight,
185-
}));
186-
expect(dims.overflowY).toBe('auto');
187-
expect(dims.scrolls).toBe(true);
188-
189-
await page.locator('#delete-pad').scrollIntoViewIfNeeded();
190-
await expect(page.locator('#delete-pad')).toBeInViewport();
182+
// The popup must declare scrollable overflow (otherwise the previous bug
183+
// recurs even if content happens to fit by coincidence).
184+
await expect(popupContent).toHaveCSS('overflow-y', 'auto');
185+
186+
// Delete pad sits at the bottom of Pad-wide Settings; on a short viewport
187+
// it starts off-screen and must become reachable by scrolling the popup.
188+
const deletePad = page.locator('#delete-pad');
189+
await expect(deletePad).not.toBeInViewport();
190+
await deletePad.scrollIntoViewIfNeeded();
191+
await expect(deletePad).toBeInViewport();
191192
});
192193

193194
// #7592: ticking "Disable chat" must visibly disable the dependent

0 commit comments

Comments
 (0)