diff --git a/examples/reference/widgets/Tabulator.ipynb b/examples/reference/widgets/Tabulator.ipynb index 206f252fbe..5d29793032 100644 --- a/examples/reference/widgets/Tabulator.ipynb +++ b/examples/reference/widgets/Tabulator.ipynb @@ -47,6 +47,7 @@ "* **`groupby`** (`list`): Groups rows in the table by one or more columns.\n", "* **`header_align`** (`dict` or `str`): A mapping from column name to header alignment or a fixed header alignment, which should be one of `'left'`, `'center'`, `'right'`.\n", "* **`header_filters`** (`boolean`/`dict`): A boolean enabling filters in the column headers or a dictionary providing filter definitions for specific columns.\n", + "* **`header_tooltips`** (`dict`): Dictionary mapping from column name to a tooltip to show when hovering over the column header. \n", "* **`hidden_columns`** (`list`): List of columns to hide.\n", "* **`hierarchical`** (boolean, default=False): Whether to render multi-indexes as hierarchical index (note hierarchical must be enabled during instantiation and cannot be modified later)\n", "* **`initial_page_size`** (`int`, `default=20`): If pagination is enabled and `page_size` this determines the initial size of each page before rendering.\n", diff --git a/panel/tests/ui/widgets/test_tabulator.py b/panel/tests/ui/widgets/test_tabulator.py index 886975969a..4ba7cc8bde 100644 --- a/panel/tests/ui/widgets/test_tabulator.py +++ b/panel/tests/ui/widgets/test_tabulator.py @@ -3954,3 +3954,18 @@ def test_filtering_all(self, page): self.set_filtering(page, n) self.check_selected(page, list(range(10)), 0) expect(page.locator('.tabulator')).to_have_count(1) + + +def test_tabulator_header_tooltips(page): + df = pd.DataFrame({"header": [True, False, True]}) + widget = Tabulator(df, header_tooltips={"header": "Test"}) + + serve_component(page, widget) + + header = page.locator('.tabulator-col-title', has_text="header") + expect(header).to_have_count(1) + header.hover() + + page.wait_for_timeout(200) + + expect(page.locator('.tabulator-tooltip')).to_have_text("Test") diff --git a/panel/tests/widgets/test_tables.py b/panel/tests/widgets/test_tables.py index 050acf1824..88af3e4905 100644 --- a/panel/tests/widgets/test_tables.py +++ b/panel/tests/widgets/test_tables.py @@ -2597,3 +2597,13 @@ def test_selection_cleared_remote_pagination_new_values(document, comm): table.value = df.copy() assert table.selection == [] + + +def test_save_user_columns_configuration(document, comm): + df = pd.DataFrame({"header": [True, False, True]}) + configuration={"columns": [{"field": "header", "headerTooltip": True}]} + tabulator = Tabulator(df, configuration=configuration, show_index=False) + + expected = [{'field': 'header', 'sorter': 'boolean', 'headerTooltip': True}] + model = tabulator.get_root(document, comm) + assert model.configuration["columns"] == expected diff --git a/panel/widgets/tables.py b/panel/widgets/tables.py index 7232f3736d..4641d8f97d 100644 --- a/panel/widgets/tables.py +++ b/panel/widgets/tables.py @@ -1129,6 +1129,10 @@ class Tabulator(BaseTable): Whether to enable filters in the header or dictionary configuring filters for each column.""") + header_tooltips = param.Dict(default={}, doc=""" + Dictionary mapping from column name to a tooltip to show when + hovering over the column header.""") + hidden_columns = param.List(default=[], nested_refs=True, doc=""" List of columns to hide.""") @@ -1220,7 +1224,7 @@ class Tabulator(BaseTable): _rename: ClassVar[Mapping[str, str | None]] = { 'selection': None, 'row_content': None, 'row_height': None, 'text_align': None, 'embed_content': None, 'header_align': None, - 'header_filters': None, 'styles': 'cell_styles', + 'header_filters': None, 'header_tooltips': None, 'styles': 'cell_styles', 'title_formatters': None, 'sortable': None, 'initial_page_size': None } @@ -1985,6 +1989,9 @@ def _config_columns(self, column_objs: list[TableColumn]) -> list[dict[str, Any] col_dict['width'] = self.widths[field] col_dict.update(self._get_filter_spec(column)) + if field in self.header_tooltips: + col_dict["headerTooltip"] = self.header_tooltips[field] + if isinstance(index, tuple): if columns: children = columns @@ -2029,7 +2036,11 @@ def _get_configuration(self, columns: list[dict[str, Any]]) -> dict[str, Any]: if self.groups and 'columns' in configuration: raise ValueError("Groups must be defined either explicitly " "or via the configuration, not both.") - configuration['columns'] = self._config_columns(columns) + user_columns = {v["field"]: v for v in configuration.get('columns', {})} + configuration["columns"] = self._config_columns(columns) + for idx, col in enumerate(columns): + if (name := col.field) in user_columns: + configuration["columns"][idx] |= user_columns[name] configuration['dataTree'] = self.hierarchical if self.sizing_mode in ('stretch_height', 'stretch_both'): configuration['maxHeight'] = '100%'