Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scroll options to permanently toggle on scrollbar #6266

Merged
merged 10 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions panel/dist/css/listpanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@
overflow-x: auto;
}

:host(.scroll) {
overflow: scroll;
}

:host(.scroll-vertical) {
overflow-y: scroll;
}

:host(.scroll-horizontal) {
overflow-x: scroll;
}

.scroll-button {
/* For location */
position: sticky;
Expand Down
71 changes: 49 additions & 22 deletions panel/layout/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@

from ..viewable import Viewable

_SCROLL_MAPPING = {
'both-auto': 'scrollable',
'x-auto': 'scrollable-horizontal',
'y-auto': 'scrollable-vertical',
'both': 'scroll',
'x': 'scroll-horizontal',
'y': 'scroll-vertical',
}

_row = namedtuple("row", ["children"]) # type: ignore
_col = namedtuple("col", ["children"]) # type: ignore

Expand Down Expand Up @@ -787,9 +796,18 @@ class ListPanel(ListLike, Panel):
An abstract baseclass for Panel objects with list-like children.
"""

scroll = param.Boolean(default=False, doc="""
Whether to add scrollbars if the content overflows the size
of the container.""")
scroll = param.Selector(
default=False,
objects=[False, True, "both-auto", "y-auto", "x-auto", "both", "x", "y"],
doc="""Whether to add scrollbars if the content overflows the size
of the container. If "both-auto", will only add scrollbars if
the content overflows in either directions. If "x-auto" or "y-auto",
will only add scrollbars if the content overflows in the
respective direction. If "both", will always add scrollbars.
If "x" or "y", will always add scrollbars in the respective
direction. If False, overflowing content will be clipped.
If True, will only add scrollbars in the direction of the container,
(e.g. Column: vertical, Row: horizontal).""")

_rename: ClassVar[Mapping[str, str | None]] = {'scroll': None}

Expand Down Expand Up @@ -819,15 +837,15 @@ def _linked_properties(self):
)

def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]:
if 'scroll' in params:
scroll = params['scroll']
if (scroll := params.get('scroll')):
css_classes = params.get('css_classes', self.css_classes)
if scroll:
if self._direction is not None:
css_classes += [f'scrollable-{self._direction}']
else:
css_classes += ['scrollable']
params['css_classes'] = css_classes
if scroll in _SCROLL_MAPPING:
scroll_class = _SCROLL_MAPPING[scroll]
elif self._direction:
scroll_class = f'scrollable-{self._direction}'
else:
scroll_class = 'scrollable'
params['css_classes'] = css_classes + [scroll_class]
return super()._process_param_change(params)

def _cleanup(self, root: Model | None = None) -> None:
Expand All @@ -843,9 +861,18 @@ class NamedListPanel(NamedListLike, Panel):
active = param.Integer(default=0, bounds=(0, None), doc="""
Index of the currently displayed objects.""")

scroll = param.Boolean(default=False, doc="""
Whether to add scrollbars if the content overflows the size
of the container.""")
scroll = param.ObjectSelector(
default=False,
objects=[False, True, "both-auto", "y-auto", "x-auto", "both", "x", "y"],
doc="""Whether to add scrollbars if the content overflows the size
of the container. If "both-auto", will only add scrollbars if
the content overflows in either directions. If "x-auto" or "y-auto",
will only add scrollbars if the content overflows in the
respective direction. If "both", will always add scrollbars.
If "x" or "y", will always add scrollbars in the respective
direction. If False, overflowing content will be clipped.
If True, will only add scrollbars in the direction of the container,
(e.g. Column: vertical, Row: horizontal).""")

_rename: ClassVar[Mapping[str, str | None]] = {'scroll': None}

Expand All @@ -854,15 +881,15 @@ class NamedListPanel(NamedListLike, Panel):
__abstract = True

def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]:
if 'scroll' in params:
scroll = params['scroll']
if (scroll := params.get('scroll')):
css_classes = params.get('css_classes', self.css_classes)
if scroll:
if self._direction is not None:
css_classes += [f'scrollable-{self._direction}']
else:
css_classes += ['scrollable']
params['css_classes'] = css_classes
if scroll in _SCROLL_MAPPING:
scroll_class = _SCROLL_MAPPING[scroll]
elif self._direction:
scroll_class = f'scrollable-{self._direction}'
else:
scroll_class = 'scrollable'
params['css_classes'] = css_classes + [scroll_class]
return super()._process_param_change(params)

def _cleanup(self, root: Model | None = None) -> None:
Expand Down
21 changes: 20 additions & 1 deletion panel/tests/ui/layout/test_column.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from playwright.sync_api import expect

from panel import Column, Spacer
from panel.layout.base import _SCROLL_MAPPING, Column
from panel.layout.spacer import Spacer
from panel.tests.util import serve_component, wait_until

pytestmark = pytest.mark.ui
Expand All @@ -27,6 +28,24 @@ def test_column_scroll(page):
expect(col_el).to_have_class('bk-panel-models-layout-Column scrollable-vertical')


@pytest.mark.parametrize('scroll', _SCROLL_MAPPING.keys())
def test_column_scroll_string(page, scroll):
col = Column(
Spacer(styles=dict(background='red'), width=200, height=200),
Spacer(styles=dict(background='green'), width=200, height=200),
Spacer(styles=dict(background='blue'), width=200, height=200),
scroll=scroll, height=420
)
serve_component(page, col)

col_el = page.locator(".bk-panel-models-layout-Column")
bbox = col_el.bounding_box()

assert bbox['width'] in (200, 215) # Ignore if browser hides empty scrollbar
assert bbox['height'] == 420
expect(col_el).to_have_class(f'bk-panel-models-layout-Column {_SCROLL_MAPPING[scroll]}')


def test_column_auto_scroll_limit(page):
col = Column(
Spacer(styles=dict(background='red'), width=200, height=200),
Expand Down
Loading