From 872d110f07658407d703cbd42953d550813b34c6 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Wed, 21 Jan 2026 22:33:17 +0100 Subject: [PATCH] Working on DatagridFilter --- src/myfasthtml/assets/myfasthtml.css | 35 ++++++----- src/myfasthtml/controls/DataGrid.py | 17 ++--- src/myfasthtml/controls/DataGridFilter.py | 76 +++++++++++++++++++++++ 3 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 src/myfasthtml/controls/DataGridFilter.py diff --git a/src/myfasthtml/assets/myfasthtml.css b/src/myfasthtml/assets/myfasthtml.css index cd09e25..44dba99 100644 --- a/src/myfasthtml/assets/myfasthtml.css +++ b/src/myfasthtml/assets/myfasthtml.css @@ -838,14 +838,13 @@ .dt2-footer { background-color: var(--color-base-200); border-radius: 10px 10px 0 0; - min-width: max-content; /* Content width propagates to scrollable parent */ + min-width: max-content; /* Content width propagates to scrollable parent */ } /* Body */ .dt2-body { overflow: hidden; - font-size: 14px; - min-width: max-content; /* Content width propagates to scrollable parent */ + min-width: max-content; /* Content width propagates to scrollable parent */ } /* Row */ @@ -951,13 +950,17 @@ /* Table with Grid layout - horizontal scroll enabled, scrollbars hidden */ .dt2-table { + --color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000); + --color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000); height: 100%; display: grid; - grid-template-rows: auto 1fr auto; /* header, body, footer */ - overflow-x: auto; /* Enable horizontal scroll */ - overflow-y: hidden; /* No vertical scroll on table */ - scrollbar-width: none; /* Firefox: hide scrollbar */ - -ms-overflow-style: none; /* IE/Edge: hide scrollbar */ + grid-template-rows: auto 1fr auto; /* header, body, footer */ + overflow-x: auto; /* Enable horizontal scroll */ + overflow-y: hidden; /* No vertical scroll on table */ + scrollbar-width: none; /* Firefox: hide scrollbar */ + -ms-overflow-style: none; /* IE/Edge: hide scrollbar */ + border: 1px solid var(--color-border); + border-radius: 10px; } /* Chrome/Safari: hide scrollbar */ @@ -968,20 +971,20 @@ /* Header - no scroll, takes natural height */ .dt2-header-container { overflow: hidden; - min-width: max-content; /* Force table to be as wide as content */ + min-width: max-content; /* Force table to be as wide as content */ } /* Body - scrollable vertically via JS, scrollbars hidden */ .dt2-body-container { - overflow: hidden; /* Scrollbars hidden, scroll via JS */ - min-height: 0; /* Important for Grid to allow shrinking */ - min-width: max-content; /* Force table to be as wide as content */ + overflow: hidden; /* Scrollbars hidden, scroll via JS */ + min-height: 0; /* Important for Grid to allow shrinking */ + min-width: max-content; /* Force table to be as wide as content */ } /* Footer - no scroll, takes natural height */ .dt2-footer-container { overflow: hidden; - min-width: max-content; /* Force table to be as wide as content */ + min-width: max-content; /* Force table to be as wide as content */ } /* Custom scrollbars container - overlaid on table */ @@ -991,7 +994,7 @@ bottom: 0; left: 0; right: 0; - pointer-events: none; /* Let clicks pass through */ + pointer-events: none; /* Let clicks pass through */ z-index: 10; } @@ -1002,7 +1005,7 @@ background-color: var(--color-base-200); opacity: 1; transition: opacity 0.2s ease-in-out; - pointer-events: auto; /* Enable interaction */ + pointer-events: auto; /* Enable interaction */ } /* Vertical scrollbar wrapper - right side, full table height */ @@ -1016,7 +1019,7 @@ /* Horizontal scrollbar wrapper - bottom, full width minus vertical scrollbar */ .dt2-scrollbars-horizontal-wrapper { left: 0; - right: 8px; /* Leave space for vertical scrollbar */ + right: 8px; /* Leave space for vertical scrollbar */ bottom: 0; height: 8px; } diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py index 621ecf9..2458269 100644 --- a/src/myfasthtml/controls/DataGrid.py +++ b/src/myfasthtml/controls/DataGrid.py @@ -72,6 +72,7 @@ class DatagridSettings(DbObject): self.views_visible: bool = True self.open_file_visible: bool = True self.open_settings_visible: bool = True + self.text_size: str = "sm" class Commands(BaseCommands): @@ -255,21 +256,21 @@ class DataGrid(MultipleInstance): if not filter_keyword_lower: # OPTIMIZATION: Return plain HTML string instead of Label object # Include "truncate text-sm" to match mk.label() behavior (ellipsis + font size) - return NotStr(f'{value_str}') + return NotStr(f'{value_str}') index = value_str.lower().find(filter_keyword_lower) if index < 0: - return NotStr(f'{value_str}') + return NotStr(f'{value_str}') # Has highlighting - need to use Span objects # Add "truncate text-sm" to match mk.label() behavior len_keyword = len(filter_keyword_lower) res = [] if index > 0: - res.append(Span(value_str[:index], cls=f"{css_class} text-sm")) - res.append(Span(value_str[index:index + len_keyword], cls=f"{css_class} text-sm dt2-highlight-1")) + res.append(Span(value_str[:index], cls=f"{css_class}")) + res.append(Span(value_str[index:index + len_keyword], cls=f"{css_class} dt2-highlight-1")) if index + len_keyword < len(value_str): - res.append(Span(value_str[index + len_keyword:], cls=f"{css_class} text-sm")) + res.append(Span(value_str[index + len_keyword:], cls=f"{css_class}")) return Span(*res, cls=f"{css_class} truncate") if len(res) > 1 else res[0] column_type = col_def.type @@ -281,7 +282,7 @@ class DataGrid(MultipleInstance): # RowIndex - simplest case, just return the number as plain HTML if column_type == ColumnType.RowIndex: - return NotStr(f'{row_index}') + return NotStr(f'{row_index}') # Convert value to string value_str = str(value) @@ -346,7 +347,7 @@ class DataGrid(MultipleInstance): def mk_body(self): return Div( *self.mk_body_content_page(0), - cls="dt2-body" + cls=f"dt2-body text-{self._settings.text_size}", ) def mk_footers(self): @@ -469,7 +470,9 @@ class DataGrid(MultipleInstance): if self._state.ne_df is None: return Div("No data to display !") + from myfasthtml.controls.DataGridFilter import DataGridFilter return Div( + Div(DataGridFilter(self), cls="mb-2"), self.mk_table(), Script(f"initDataGrid('{self._id}');"), id=self._id, diff --git a/src/myfasthtml/controls/DataGridFilter.py b/src/myfasthtml/controls/DataGridFilter.py new file mode 100644 index 0000000..85312bb --- /dev/null +++ b/src/myfasthtml/controls/DataGridFilter.py @@ -0,0 +1,76 @@ +import logging + +from fasthtml.components import * + +from myfasthtml.controls.BaseCommands import BaseCommands +from myfasthtml.controls.helpers import mk +from myfasthtml.core.commands import Command +from myfasthtml.core.dbmanager import DbObject +from myfasthtml.core.instances import MultipleInstance +from myfasthtml.icons.fluent import brain_circuit20_regular +from myfasthtml.icons.fluent_p1 import filter20_regular, search20_regular +from myfasthtml.icons.fluent_p2 import dismiss_circle20_regular + +logger = logging.getLogger("DataGridFilter") + +filter_type = { + "filter": filter20_regular, + "search": search20_regular, + "ai": brain_circuit20_regular +} + + +class DataGridFilterState(DbObject): + def __init__(self, owner): + with self.initializing(): + super().__init__(owner) + self.filter_type: str = "filter" + + +class Commands(BaseCommands): + def change_filter_type(self): + return Command("ChangeFilterType", + "Change filter type", + self._owner, + self._owner.change_filter_type).htmx(target=f"#{self._id}") + + def on_filter_changed(self): + return Command("FilterChanged", + "Filter changed", + self._owner, + self._owner.filter_changed).htmx(target=None) + + +class DataGridFilter(MultipleInstance): + def __init__(self, parent, _id=None): + super().__init__(parent, _id=_id or "-filter") + self.commands = Commands(self) + self._state = DataGridFilterState(self) + + def change_filter_type(self): + keys = list(filter_type.keys()) # ["filter", "search", "ai"] + current_idx = keys.index(self._state.filter_type) + self._state.filter_type = keys[(current_idx + 1) % len(keys)] + return self + + def filter_changed(self, f): + logger.debug(f"filter_changed {f=}") + return self + + def render(self): + return Div( + mk.label( + Input(name="f", + placeholder="Search...", + **self.commands.on_filter_changed().get_htmx_params(escaped=True)), + icon=mk.icon(filter_type[self._state.filter_type], command=self.commands.change_filter_type()), + cls="input input-sm flex gap-2" + ), + mk.icon(dismiss_circle20_regular, size=24), + # Keyboard(self, _id="-keyboard").add("enter", self.commands.on_filter_changed()), + cls="flex", + id=self._id + ) + + def __ft__(self): + return self.render()