Skip to content

Commit 3db8487

Browse files
JohnMcLearclaude
andcommitted
fix(7696): nice-select reverse list disappeared in scrolled popup
When a nice-select inside a popup-content scroll container sits in the lower half of the viewport, the JS adds the .reverse class so the list opens upward. The default .reverse rule sets bottom: calc(100% + 5px), which is fine when the list is position:absolute relative to its parent — but with the position:fixed treatment the popup branch uses, that percentage resolves against the viewport and pushes the list ~100vh above the screen, so it appears not to open at all until you scroll to the bottom of the popup (where .reverse no longer triggers). Override the rule for both .toolbar and .popup so .reverse drops back to bottom: auto and JS-set `top` controls placement, with a JS belt-and- braces also setting `bottom: auto` inline. Adds a Playwright regression test that scrolls the settings popup to the bottom, opens the Pad-wide font dropdown, and asserts the list is both visible and inside the viewport. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 26cebda commit 3db8487

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

src/static/css/pad/form.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ select, .nice-select {
141141
top: auto;
142142
left: auto;
143143
}
144+
/* The default .reverse rule above sets bottom: calc(100% + 5px) so an
145+
absolutely-positioned list opens upward inside its parent. Once the list is
146+
position:fixed, that percentage resolves against the viewport instead and
147+
would push the list off-screen, so we let JS place it via `top` only. */
148+
.toolbar .nice-select.reverse .list,
149+
.popup .nice-select.reverse .list {
150+
bottom: auto;
151+
}
144152
.nice-select .list:hover .option:not(:hover) {
145153
background-color: transparent !important;
146154
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@
134134
var $list = $dropdown.find('.list');
135135
$list.css('left', rect.left);
136136
$list.css('min-width', $dropdown.outerWidth() + 'px');
137+
// Clear .reverse's `bottom: calc(100% + 5px)` — with position:fixed
138+
// it would resolve against the viewport and push the list offscreen.
139+
$list.css('bottom', 'auto');
137140
$list.css('top', $dropdown.hasClass('reverse')
138141
? rect.top - $maxListHeight - 5
139142
: rect.bottom);

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,35 @@ test.describe('creator-owned pad settings', () => {
191191
await expect(deletePad).toBeInViewport();
192192
});
193193

194+
// #7696 follow-up: the Pad-wide font/language nice-select dropdowns sit
195+
// near the bottom of the popup, so opening one triggers the .reverse path
196+
// (open upward). Floating the list with position:fixed must not pick up
197+
// the default `.reverse { bottom: calc(100% + 5px) }` rule, which would
198+
// resolve against the viewport and place the list off-screen.
199+
test('Pad-wide font dropdown opens visibly when popup is scrolled to bottom', async ({page}) => {
200+
await page.setViewportSize({width: 900, height: 500});
201+
await goToNewPad(page);
202+
await showSettings(page);
203+
204+
// Force the font dropdown into the lower portion of the viewport so
205+
// .reverse triggers and the list opens upward.
206+
await page.locator('#settings > .popup-content').evaluate((el) => {
207+
el.scrollTop = el.scrollHeight;
208+
});
209+
210+
const fontDropdown = page.locator('#padsettings-viewfontmenu + .nice-select');
211+
await expect(fontDropdown).toBeInViewport();
212+
213+
await fontDropdown.click();
214+
const list = fontDropdown.locator('.list');
215+
await expect(list).toBeVisible();
216+
await expect(list).toBeInViewport();
217+
218+
// The first option must be reachable so users can actually pick a font.
219+
await fontDropdown.locator('.option').first().click();
220+
await expect(fontDropdown).not.toHaveClass(/open/);
221+
});
222+
194223
// #7592: ticking "Disable chat" must visibly disable the dependent
195224
// "Chat always on screen" / "Show Chat and Users" toggles, not just
196225
// make the underlying inputs non-interactive.

0 commit comments

Comments
 (0)