From ed34b1c9492e8a48f5dc8ea58452d5a5ddf0b969 Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Mon, 5 Feb 2024 11:26:57 -0800 Subject: [PATCH] Add scroll options to permanently toggle on scrollbar (#6266) --- panel/dist/css/listpanel.css | 12 +++++ panel/layout/base.py | 71 +++++++++++++++++++--------- panel/tests/ui/layout/test_column.py | 21 +++++++- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/panel/dist/css/listpanel.css b/panel/dist/css/listpanel.css index c076aa375d..af76ff90ec 100644 --- a/panel/dist/css/listpanel.css +++ b/panel/dist/css/listpanel.css @@ -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; diff --git a/panel/layout/base.py b/panel/layout/base.py index 6211d33d8d..4d6be39caf 100644 --- a/panel/layout/base.py +++ b/panel/layout/base.py @@ -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 @@ -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} @@ -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: @@ -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} @@ -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: diff --git a/panel/tests/ui/layout/test_column.py b/panel/tests/ui/layout/test_column.py index d955fe46b5..b746874754 100644 --- a/panel/tests/ui/layout/test_column.py +++ b/panel/tests/ui/layout/test_column.py @@ -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 @@ -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),