Optimizing keyboard navigation and selection handling
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import bisect
|
||||
import html
|
||||
import logging
|
||||
import re
|
||||
@@ -80,6 +81,7 @@ class DatagridState(DbObject):
|
||||
self.selection: DatagridSelectionState = DatagridSelectionState()
|
||||
self.cell_formats: dict = {}
|
||||
self.table_format: list = []
|
||||
self.ns_visible_indices: list[int] | None = None
|
||||
|
||||
|
||||
class DatagridSettings(DbObject):
|
||||
@@ -403,8 +405,12 @@ class DataGrid(MultipleInstance):
|
||||
if col.visible and col.type != ColumnType.RowSelection_]
|
||||
|
||||
def _get_visible_row_indices(self) -> list[int]:
|
||||
df = self._get_filtered_df()
|
||||
return list(df.index) if df is not None else []
|
||||
if self._state.ns_visible_indices is None:
|
||||
if self._df is None:
|
||||
self._state.ns_visible_indices = []
|
||||
else:
|
||||
self._state.ns_visible_indices = list(self._apply_filter(self._df).index)
|
||||
return self._state.ns_visible_indices
|
||||
|
||||
def _navigate(self, pos: tuple, direction: str) -> tuple:
|
||||
col_pos, row_index = pos
|
||||
@@ -421,11 +427,11 @@ class DataGrid(MultipleInstance):
|
||||
prev_cols = [c for c in navigable_cols if c < col_pos]
|
||||
return (prev_cols[-1], row_index) if prev_cols else pos
|
||||
elif direction == "down":
|
||||
next_rows = [r for r in visible_rows if r > row_index]
|
||||
return (col_pos, next_rows[0]) if next_rows else pos
|
||||
next_pos = bisect.bisect_right(visible_rows, row_index)
|
||||
return (col_pos, visible_rows[next_pos]) if next_pos < len(visible_rows) else pos
|
||||
elif direction == "up":
|
||||
prev_rows = [r for r in visible_rows if r < row_index]
|
||||
return (col_pos, prev_rows[-1]) if prev_rows else pos
|
||||
prev_pos = bisect.bisect_left(visible_rows, row_index) - 1
|
||||
return (col_pos, visible_rows[prev_pos]) if prev_pos >= 0 else pos
|
||||
|
||||
return pos
|
||||
|
||||
@@ -439,9 +445,13 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
return None
|
||||
|
||||
def _update_current_position(self, pos):
|
||||
def _update_current_position(self, pos, reset_selection: bool = False):
|
||||
self._state.selection.last_selected = self._state.selection.selected
|
||||
self._state.selection.selected = pos
|
||||
|
||||
if reset_selection:
|
||||
self._state.selection.extra_selected.clear()
|
||||
self._state.edition.under_edition = None
|
||||
self._state.save()
|
||||
|
||||
def _get_format_rules(self, col_pos, row_index, col_def):
|
||||
@@ -508,14 +518,23 @@ class DataGrid(MultipleInstance):
|
||||
self._columns.insert(0, DataGridRowSelectionColumnState())
|
||||
|
||||
def _enter_edition(self, pos):
|
||||
logger.debug(f"enter_edition: {pos=}")
|
||||
col_pos, row_index = pos
|
||||
col_def = self._columns[col_pos]
|
||||
if col_def.type in (ColumnType.RowSelection_, ColumnType.RowIndex, ColumnType.Formula):
|
||||
return self.render_partial()
|
||||
if col_def.type == ColumnType.Bool:
|
||||
return self._toggle_bool_cell(col_pos, row_index, col_def)
|
||||
self._state.edition.under_edition = pos
|
||||
self._state.save()
|
||||
return self.render_partial("cell", pos=pos)
|
||||
|
||||
def _toggle_bool_cell(self, col_pos, row_index, col_def):
|
||||
col_array = self._fast_access.get(col_def.col_id)
|
||||
current_value = col_array[row_index] if col_array is not None and row_index < len(col_array) else False
|
||||
self._data_service.set_data(col_def.col_id, row_index, not bool(current_value))
|
||||
return self.render_partial("cell", pos=(col_pos, row_index))
|
||||
|
||||
def _convert_edition_value(self, value_str, col_type):
|
||||
if col_type == ColumnType.Number:
|
||||
try:
|
||||
@@ -645,11 +664,13 @@ class DataGrid(MultipleInstance):
|
||||
def filter(self):
|
||||
logger.debug("filter")
|
||||
self._state.filtered[FILTER_INPUT_CID] = self._datagrid_filter.get_query()
|
||||
self._state.ns_visible_indices = None
|
||||
return self.render_partial("body")
|
||||
|
||||
def handle_on_click(self, combination, is_inside, cell_id):
|
||||
logger.debug(f"on_click table={self.get_table_name()} {combination=} {is_inside=} {cell_id=}")
|
||||
if is_inside and cell_id:
|
||||
logger.debug(f" is_inside=True")
|
||||
self._state.selection.extra_selected.clear()
|
||||
|
||||
pos = self._get_pos_from_element_id(cell_id)
|
||||
@@ -659,9 +680,15 @@ class DataGrid(MultipleInstance):
|
||||
pos == self._state.selection.selected and
|
||||
self._state.edition.under_edition is None):
|
||||
return self._enter_edition(pos)
|
||||
else:
|
||||
logger.debug(
|
||||
f" {pos=}, selected={self._state.selection.selected}, under_edition={self._state.edition.under_edition}")
|
||||
|
||||
self._update_current_position(pos)
|
||||
|
||||
else:
|
||||
logger.debug(f" is_inside=False")
|
||||
|
||||
return self.render_partial()
|
||||
|
||||
def on_mouse_selection(self, combination, is_inside, cell_id_mousedown, cell_id_mouseup):
|
||||
@@ -685,13 +712,15 @@ class DataGrid(MultipleInstance):
|
||||
def on_key_pressed(self, combination, has_focus, is_inside):
|
||||
logger.debug(f"on_key_pressed table={self.get_table_name()} {combination=} {has_focus=} {is_inside=}")
|
||||
if combination == "esc":
|
||||
self._update_current_position(None)
|
||||
self._state.selection.extra_selected.clear()
|
||||
self._update_current_position(None, reset_selection=True)
|
||||
return self.render_partial("cell", pos=self._state.selection.last_selected)
|
||||
|
||||
elif (combination == "enter" and
|
||||
self._settings.enable_edition and
|
||||
self._state.selection.selected and
|
||||
self._state.edition.under_edition is None):
|
||||
return self._enter_edition(self._state.selection.selected)
|
||||
|
||||
elif combination in self._ARROW_KEY_DIRECTIONS:
|
||||
current_pos = (self._state.selection.selected
|
||||
or self._state.selection.last_selected
|
||||
@@ -1139,25 +1168,19 @@ class DataGrid(MultipleInstance):
|
||||
)
|
||||
|
||||
def mk_selection_manager(self):
|
||||
|
||||
extra_attr = {
|
||||
"hx-on::after-settle": f"updateDatagridSelection('{self._id}');",
|
||||
}
|
||||
|
||||
selected = []
|
||||
|
||||
|
||||
if self._state.selection.selected:
|
||||
# selected.append(("cell", self._get_element_id_from_pos("cell", self._state.selection.selected)))
|
||||
selected.append(("focus", self._get_element_id_from_pos("cell", self._state.selection.selected)))
|
||||
|
||||
|
||||
for extra_sel in self._state.selection.extra_selected:
|
||||
selected.append(extra_sel)
|
||||
|
||||
|
||||
return Div(
|
||||
*[Div(selection_type=s_type, element_id=f"{elt_id}") for s_type, elt_id in selected],
|
||||
id=f"tsm_{self._id}",
|
||||
selection_mode=f"{self._state.selection.selection_mode}",
|
||||
**extra_attr,
|
||||
)
|
||||
|
||||
def mk_aggregation_cell(self, col_def, row_index: int, footer_conf, oob=False):
|
||||
@@ -1244,7 +1267,8 @@ class DataGrid(MultipleInstance):
|
||||
id=self._id,
|
||||
cls="grid",
|
||||
style="height: 100%; grid-template-rows: auto 1fr;",
|
||||
tabindex="-1"
|
||||
tabindex="-1",
|
||||
**{"hx-on:htmx:after-swap": f"if(event.detail.target.id==='tsm_{self._id}') updateDatagridSelection('{self._id}');"}
|
||||
)
|
||||
|
||||
def render_partial(self, fragment="cell", **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user