fix(popover): remove hidden popover from layout to kill phantom scrollbar
Flowbite re-initialises popovers on every htmx swap. A popover hidden via Tailwind `invisible` (visibility:hidden) still occupies layout, so once Popper parks it with a transform offset it expands the table's overflow-x-auto wrapper and a spurious scrollbar appears (horizontal here, vertical in #40). Add `[&.invisible]:hidden` so the popover is removed from layout while hidden; Flowbite drops `invisible` on show, restoring display. Relates to #40. e2e regression covers no-overflow-after-swap plus popover-still-shows-on-hover. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -138,10 +138,16 @@ def _popover_html(
|
|||||||
)
|
)
|
||||||
|
|
||||||
popover_tooltip_class = (
|
popover_tooltip_class = (
|
||||||
"absolute z-10 invisible inline-block text-sm text-white "
|
# `[&.invisible]:hidden`: while Flowbite keeps the popover hidden it
|
||||||
"transition-opacity duration-300 bg-white border border-purple-200 "
|
# carries the `invisible` class (visibility:hidden), which still
|
||||||
"rounded-lg shadow-xs opacity-0 dark:text-white dark:border-purple-600 "
|
# occupies layout — an absolutely-positioned, Popper-transformed
|
||||||
"dark:bg-purple-800"
|
# popover then expands its scroll container, producing a phantom
|
||||||
|
# scrollbar (issue #53 / #40). Removing it from layout while hidden
|
||||||
|
# fixes that; Flowbite drops `invisible` on show, restoring display.
|
||||||
|
"absolute z-10 invisible [&.invisible]:hidden inline-block text-sm "
|
||||||
|
"text-white transition-opacity duration-300 bg-white border "
|
||||||
|
"border-purple-200 rounded-lg shadow-xs opacity-0 dark:text-white "
|
||||||
|
"dark:border-purple-600 dark:bg-purple-800"
|
||||||
)
|
)
|
||||||
|
|
||||||
div = Div(
|
div = Div(
|
||||||
|
|||||||
@@ -46,3 +46,58 @@ def test_finish_session_swaps_row_in_place(authenticated_page: Page, live_server
|
|||||||
|
|
||||||
session.refresh_from_db()
|
session.refresh_from_db()
|
||||||
assert session.timestamp_end is not None
|
assert session.timestamp_end is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_finish_session_swap_does_not_add_scrollbar(
|
||||||
|
authenticated_page: Page, live_server
|
||||||
|
):
|
||||||
|
"""Regression for the phantom horizontal scrollbar (issues #53 / #40).
|
||||||
|
|
||||||
|
Flowbite re-initialises popovers on every htmx swap; a popover hidden via
|
||||||
|
Tailwind ``invisible`` (visibility:hidden) still occupies layout, so once
|
||||||
|
Popper parks it with a transform it expands the table's overflow-x-auto
|
||||||
|
wrapper and a spurious scrollbar appears. The popover must be removed from
|
||||||
|
layout while hidden.
|
||||||
|
"""
|
||||||
|
page = authenticated_page
|
||||||
|
page.set_viewport_size({"width": 1280, "height": 800})
|
||||||
|
platform = Platform.objects.create(name="PC", icon="pc", group="PC")
|
||||||
|
# A long name guarantees a truncated NameWithIcon popover in the row.
|
||||||
|
game = Game.objects.create(name="A Very Long Game Title That Truncates")
|
||||||
|
game.platform = platform
|
||||||
|
game.save()
|
||||||
|
device = Device.objects.create(name="Desktop")
|
||||||
|
session = Session.objects.create(
|
||||||
|
game=game, device=device, timestamp_start=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
page.goto(f"{live_server.url}{reverse('games:list_sessions')}")
|
||||||
|
|
||||||
|
# The fix only removes the popover from layout while it is hidden; it must
|
||||||
|
# still display on hover. Verify on the freshly-loaded page.
|
||||||
|
trigger = page.locator(f"#session-row-{session.pk} [data-popover-target]").first
|
||||||
|
popover_id = trigger.get_attribute("data-popover-target")
|
||||||
|
trigger.hover()
|
||||||
|
page.wait_for_timeout(400)
|
||||||
|
shown_display = page.evaluate(
|
||||||
|
"""(id) => getComputedStyle(document.querySelector(`[id="${id}"]`)).display""",
|
||||||
|
popover_id,
|
||||||
|
)
|
||||||
|
assert shown_display != "none", "popover stayed display:none on hover"
|
||||||
|
page.mouse.move(0, 0)
|
||||||
|
|
||||||
|
page.locator(f"#session-row-{session.pk}").locator(
|
||||||
|
'button[title="Finish session now"]'
|
||||||
|
).click()
|
||||||
|
expect(page.locator(f"#session-row-{session.pk}")).to_contain_text("—")
|
||||||
|
page.wait_for_timeout(500) # allow Flowbite afterSettle re-init + Popper
|
||||||
|
|
||||||
|
# After the swap re-inits popovers, the table wrapper must not become
|
||||||
|
# horizontally scrollable (the phantom-scrollbar regression).
|
||||||
|
overflow = page.evaluate(
|
||||||
|
"""() => {
|
||||||
|
const w = document.querySelector('.overflow-x-auto');
|
||||||
|
return w.scrollWidth - w.clientWidth;
|
||||||
|
}"""
|
||||||
|
)
|
||||||
|
assert overflow <= 0, f"table wrapper overflows by {overflow}px after swap"
|
||||||
|
|||||||
+5
-119
@@ -1487,9 +1487,6 @@
|
|||||||
.h-10 {
|
.h-10 {
|
||||||
height: calc(var(--spacing) * 10);
|
height: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
.h-12 {
|
|
||||||
height: calc(var(--spacing) * 12);
|
|
||||||
}
|
|
||||||
.h-\[calc\(100\%-1rem\)\] {
|
.h-\[calc\(100\%-1rem\)\] {
|
||||||
height: calc(100% - 1rem);
|
height: calc(100% - 1rem);
|
||||||
}
|
}
|
||||||
@@ -2489,9 +2486,6 @@
|
|||||||
.align-middle {
|
.align-middle {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.align-top {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
.font-mono {
|
.font-mono {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
@@ -2754,9 +2748,6 @@
|
|||||||
.text-white {
|
.text-white {
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
}
|
}
|
||||||
.text-yellow-300 {
|
|
||||||
color: var(--color-yellow-300);
|
|
||||||
}
|
|
||||||
.lowercase {
|
.lowercase {
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
@@ -2904,106 +2895,6 @@
|
|||||||
.ring-inset {
|
.ring-inset {
|
||||||
--tw-ring-inset: inset;
|
--tw-ring-inset: inset;
|
||||||
}
|
}
|
||||||
.group-hover\:absolute {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:-top-8 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
top: calc(var(--spacing) * -8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:-left-6 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
left: calc(var(--spacing) * -6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:max-w-none {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:min-w-60 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
min-width: calc(var(--spacing) * 60);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:rounded-xs {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
border-radius: var(--radius-xs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:bg-purple-600 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
background-color: var(--color-purple-600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:px-6 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
padding-inline: calc(var(--spacing) * 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:py-3\.5 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
padding-block: calc(var(--spacing) * 3.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:text-purple-100 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
color: var(--color-purple-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:decoration-purple-900 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
text-decoration-color: var(--color-purple-900);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:outline-4 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
outline-style: var(--tw-outline-style);
|
|
||||||
outline-width: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:outline-purple-400 {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
outline-color: var(--color-purple-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-hover\:outline-dashed {
|
|
||||||
&:is(:where(.group):hover *) {
|
|
||||||
@media (hover: hover) {
|
|
||||||
--tw-outline-style: dashed;
|
|
||||||
outline-style: dashed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group-data-\[search-select-highlighted\]\:border-white {
|
.group-data-\[search-select-highlighted\]\:border-white {
|
||||||
&:is(:where(.group)[data-search-select-highlighted] *) {
|
&:is(:where(.group)[data-search-select-highlighted] *) {
|
||||||
border-color: var(--color-white);
|
border-color: var(--color-white);
|
||||||
@@ -3487,11 +3378,6 @@
|
|||||||
outline-color: var(--color-brand-strong);
|
outline-color: var(--color-brand-strong);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sm\:table-cell {
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sm\:max-w-\(--breakpoint-sm\) {
|
.sm\:max-w-\(--breakpoint-sm\) {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
max-width: var(--breakpoint-sm);
|
max-width: var(--breakpoint-sm);
|
||||||
@@ -3647,11 +3533,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:table-cell {
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lg\:max-w-3xl {
|
.lg\:max-w-3xl {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
max-width: var(--container-3xl);
|
max-width: var(--container-3xl);
|
||||||
@@ -4242,6 +4123,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.\[\&\.invisible\]\:hidden {
|
||||||
|
&.invisible {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.\[\&\:first-of-type_button\]\:rounded-s-lg {
|
.\[\&\:first-of-type_button\]\:rounded-s-lg {
|
||||||
&:first-of-type button {
|
&:first-of-type button {
|
||||||
border-start-start-radius: var(--radius-lg);
|
border-start-start-radius: var(--radius-lg);
|
||||||
|
|||||||
Reference in New Issue
Block a user