* [PATCH yew-widget-toolkit 0/5] data table fixes
@ 2026-10-16 9:01 Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 1/5] widget: data table: keep hidden columns when headers are recreated Dominik Csapak
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
Some miscellaneous fixes for the data table. Not all of them are
reachable in our products, but still worth fixing IMO.
Dominik Csapak (5):
widget: data table: keep hidden columns when headers are recreated
widget: data table: update listeners when store or selection change
widget: data table: update cell configuration on property change
widget: data table: header: fix keyboard navigation from group headers
widget: data table: header: skip hidden cells in keyboard navigation
src/widget/data_table/data_table.rs | 47 +++++++++++++++------
src/widget/data_table/header_widget.rs | 58 +++++++++++++-------------
2 files changed, 65 insertions(+), 40 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH yew-widget-toolkit 1/5] widget: data table: keep hidden columns when headers are recreated
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
@ 2026-10-16 9:01 ` Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 2/5] widget: data table: update listeners when store or selection change Dominik Csapak
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
The data table resets the hidden state of all columns every time it
gets a new headers Rc, but the header widget keeps its state as long
as the column names don't change. Some callers create the headers in
their view function (e.g. the running tasks list in proxmox-yew-comp),
so the table gets a new Rc every time the parent renders.
This means that a column that was hidden via the header menu stays
hidden in the header, but is shown again in the rows, so the cells
don't match the header columns anymore.
To reproduce:
* use a data table where the parent creates the headers in its view
* hide a column via the header menu
* trigger a re-render of the parent, e.g. by changing its state
Fix this by using the same check as the header widget, and only reset
the hidden state when the column names change.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/widget/data_table/data_table.rs | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/widget/data_table/data_table.rs b/src/widget/data_table/data_table.rs
index 9407844..b7a3941 100644
--- a/src/widget/data_table/data_table.rs
+++ b/src/widget/data_table/data_table.rs
@@ -1067,13 +1067,20 @@ impl<S: DataStore> PwtDataTable<S> {
for header in props.headers.iter() {
header.extract_column_list(&mut columns);
}
- let mut column_hidden = Vec::new();
- for column in columns.iter() {
- column_hidden.push(column.hidden);
+ // Like the header widget, keep the hidden state while the column names stay the same.
+ // Callers sometimes recreate the headers on every render, and resetting it here would show
+ // columns in the rows that the user hid in the header.
+ let same_columns = self.columns.len() == columns.len()
+ && self
+ .columns
+ .iter()
+ .zip(columns.iter())
+ .all(|(old, new)| old.name == new.name);
+ if !same_columns {
+ self.column_hidden = Rc::new(columns.iter().map(|column| column.hidden).collect());
}
self.headers = Rc::new(headers);
self.columns = Rc::new(columns);
- self.column_hidden = Rc::new(column_hidden);
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH yew-widget-toolkit 2/5] widget: data table: update listeners when store or selection change
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 1/5] widget: data table: keep hidden columns when headers are recreated Dominik Csapak
@ 2026-10-16 9:01 ` Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 3/5] widget: data table: update cell configuration on property change Dominik Csapak
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
The listeners for the store and the selection are only registered when
the table is created. If the parent passes a different store or
selection later, the table still listens to the old ones, so changes
to the new store or selection don't update the table until something
else triggers a render.
To reproduce:
* render a data table with a store
* re-render the parent with a new store
* change the data of the new store, e.g. with 'set_data'
The table still shows the data the new store had when it was passed
in.
Fix this by registering the listeners again when the store or the
selection changes, like we already do in the SelectionView, and update
the selection status for a new selection.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/widget/data_table/data_table.rs | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/widget/data_table/data_table.rs b/src/widget/data_table/data_table.rs
index b7a3941..6a30dc2 100644
--- a/src/widget/data_table/data_table.rs
+++ b/src/widget/data_table/data_table.rs
@@ -1813,10 +1813,19 @@ impl<S: DataStore + 'static> Component for PwtDataTable<S> {
let props = ctx.props();
if props.store != old_props.store {
- // store changed
+ self._store_observer = props
+ .store
+ .add_listener(ctx.link().callback(|_| Msg::DataChange));
self.update_scroll_info(props);
}
+ if props.selection != old_props.selection {
+ self._selection_observer = props.selection.as_ref().map(|selection| {
+ selection.add_listener(ctx.link().callback(|_| Msg::SelectionChange))
+ });
+ self.update_selection_status(props);
+ }
+
if props.headers != old_props.headers {
self.init_headers(props);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH yew-widget-toolkit 3/5] widget: data table: update cell configuration on property change
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 1/5] widget: data table: keep hidden columns when headers are recreated Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 2/5] widget: data table: update listeners when store or selection change Dominik Csapak
@ 2026-10-16 9:01 ` Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 4/5] widget: data table: header: fix keyboard navigation from group headers Dominik Csapak
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
The cell configuration was only used when the table was created, so
changing it later (e.g. adding a class) had no effect on the cells.
Fix this by setting it again when the property changes.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/widget/data_table/data_table.rs | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/widget/data_table/data_table.rs b/src/widget/data_table/data_table.rs
index 6a30dc2..1fb09d6 100644
--- a/src/widget/data_table/data_table.rs
+++ b/src/widget/data_table/data_table.rs
@@ -583,6 +583,15 @@ fn render_empty_row_with_sizes(widths: &[f64], column_hidden: &[bool], bordered:
.into()
}
+// Cells get the default class, unless the configuration sets its own.
+fn cell_config(config: &CellConfiguration) -> Rc<CellConfiguration> {
+ Rc::new(if config.class.is_empty() {
+ CellConfiguration::new().class("pwt-datatable-cell")
+ } else {
+ config.clone()
+ })
+}
+
impl<S: DataStore> PwtDataTable<S> {
// avoid slow search by lookup up keys nearby cursor first
fn filtered_record_pos(&self, props: &DataTable<S>, key: &Key) -> Option<usize> {
@@ -1091,12 +1100,6 @@ impl<S: DataStore + 'static> Component for PwtDataTable<S> {
fn create(ctx: &Context<Self>) -> Self {
let props = ctx.props();
- let cell_config = if props.cell_configuration.class.is_empty() {
- CellConfiguration::new().class("pwt-datatable-cell")
- } else {
- props.cell_configuration.clone()
- };
-
let _store_observer = props
.store
.add_listener(ctx.link().callback(|_| Msg::DataChange));
@@ -1123,7 +1126,7 @@ impl<S: DataStore + 'static> Component for PwtDataTable<S> {
column_widths: Vec::new(),
column_hidden: Rc::new(Vec::new()),
scroll_info: VirtualScrollInfo::default(),
- cell_config: Rc::new(cell_config),
+ cell_config: cell_config(&props.cell_configuration),
scroll_top: 0,
set_scroll_top: None,
viewport_height: 0.0,
@@ -1830,6 +1833,10 @@ impl<S: DataStore + 'static> Component for PwtDataTable<S> {
self.init_headers(props);
}
+ if props.cell_configuration != old_props.cell_configuration {
+ self.cell_config = cell_config(&props.cell_configuration);
+ }
+
true
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH yew-widget-toolkit 4/5] widget: data table: header: fix keyboard navigation from group headers
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
` (2 preceding siblings ...)
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 3/5] widget: data table: update cell configuration on property change Dominik Csapak
@ 2026-10-16 9:01 ` Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 5/5] widget: data table: header: skip hidden cells in keyboard navigation Dominik Csapak
2026-10-16 9:44 ` applied: [PATCH yew-widget-toolkit 0/5] data table fixes Dietmar Maurer
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
Only column headers update the header cursor when they get the focus,
but group headers are focusable too. When a group header gets the
focus, the cursor is either not set yet, so the arrow keys do nothing,
or it still points to the previously focused cell, so the arrow keys
move from there instead.
Since the first header cell is the initial tab stop, this affects
every table where the first header is a group header.
To reproduce:
* use a data table where the first header is a group header
* focus the header with the tab key
* press the right arrow key
The focus stays on the group header instead of moving to the next
cell.
Fix this by also setting the cursor when a group header gets the
focus.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
| 1 +
1 file changed, 1 insertion(+)
--git a/src/widget/data_table/header_widget.rs b/src/widget/data_table/header_widget.rs
index b6f06f6..2f55e88 100644
--- a/src/widget/data_table/header_widget.rs
+++ b/src/widget/data_table/header_widget.rs
@@ -425,6 +425,7 @@ impl<T: 'static> PwtHeaderWidget<T> {
format!("grid-column: {} / span {}", start_col + 1, span,),
)
.with_child(group.name.clone())
+ .onfocusin(link.callback(move |_| Msg::FocusCell(cell_idx)))
.into(),
);
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH yew-widget-toolkit 5/5] widget: data table: header: skip hidden cells in keyboard navigation
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
` (3 preceding siblings ...)
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 4/5] widget: data table: header: fix keyboard navigation from group headers Dominik Csapak
@ 2026-10-16 9:01 ` Dominik Csapak
2026-10-16 9:44 ` applied: [PATCH yew-widget-toolkit 0/5] data table fixes Dietmar Maurer
5 siblings, 0 replies; 7+ messages in thread
From: Dominik Csapak @ 2026-10-16 9:01 UTC (permalink / raw)
To: yew-devel
When moving the header cursor with the arrow keys, we go over all
header cells, including the hidden ones. Since those are not rendered,
the focus does not move, so the user has to press the arrow key once
more for every hidden cell in between.
Also, only the cell of the cursor (or the first cell if there is no
cursor) gets 'tabindex=0'. When that cell is hidden, no rendered cell
is a tab stop and the header can't be reached with the tab key at all.
This happens e.g. when hiding the focused column via its own header
menu, or when the first header cell is hidden from the start, e.g. a
first column with 'hidden(true)'.
To reproduce:
* focus a column header and hide that column via its header menu
* try to tab into the header, it is skipped
* focus the column left of the hidden one and press the right arrow
key, the focus does not move
Fix this by skipping hidden cells when moving the cursor, and by using
the first visible cell as tab stop when the cursor cell is hidden.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
| 57 +++++++++++++-------------
1 file changed, 29 insertions(+), 28 deletions(-)
--git a/src/widget/data_table/header_widget.rs b/src/widget/data_table/header_widget.rs
index 2f55e88..0c0ff45 100644
--- a/src/widget/data_table/header_widget.rs
+++ b/src/widget/data_table/header_widget.rs
@@ -193,6 +193,21 @@ impl<T: 'static> PwtHeaderWidget<T> {
span
}
+ fn cell_visible(&self, cell_idx: usize) -> bool {
+ cell_idx < self.state.cell_count() && !self.state.get_cell_hidden(cell_idx)
+ }
+
+ // The cell that gets focus when tabbing into the header. Hidden cells are not rendered, so
+ // fall back to the first visible one.
+ fn tab_stop(&self) -> Option<usize> {
+ match self.cursor {
+ Some(cursor) if self.cell_visible(cursor) => Some(cursor),
+ Some(_) | None => {
+ (0..self.state.cell_count()).find(|cell_idx| self.cell_visible(*cell_idx))
+ }
+ }
+ }
+
fn unique_cell_id(&self, cell_idx: usize) -> String {
format!("{}-cell-{}", self.unique_id, cell_idx)
}
@@ -208,11 +223,7 @@ impl<T: 'static> PwtHeaderWidget<T> {
) {
let column_idx = cell.start_col;
let cell_idx = cell.cell_idx;
- let active = self
- .cursor
- .map(|cursor| cursor == cell_idx)
- .unwrap_or(false);
- let tabindex = if active || (self.cursor.is_none() && (cell_idx == 0)) {
+ let tabindex = if self.tab_stop() == Some(cell_idx) {
AttrValue::Static("0")
} else {
AttrValue::Static("-1")
@@ -387,11 +398,7 @@ impl<T: 'static> PwtHeaderWidget<T> {
header_row: &mut Vec<Html>,
) -> usize {
let cell_idx = group.cell_idx;
- let active = self
- .cursor
- .map(|cursor| cursor == cell_idx)
- .unwrap_or(false);
- let tabindex = if active || (self.cursor.is_none() && (cell_idx == 0)) {
+ let tabindex = if self.tab_stop() == Some(cell_idx) {
"0"
} else {
"-1"
@@ -593,28 +600,22 @@ impl<T: 'static> Component for PwtHeaderWidget<T> {
true
}
Msg::MoveCursor(direction) => {
- let last = self.state.cell_count().saturating_sub(1);
+ let count = self.state.cell_count();
let cursor = match self.cursor {
Some(cursor) => cursor,
None => return false,
};
- self.cursor = Some(match direction {
- false => {
- if cursor > 0 {
- cursor - 1
- } else {
- last
- }
- }
- true => {
- if (cursor + 1) <= last {
- cursor + 1
- } else {
- 0
- }
- }
- });
- self.focus_active_cell();
+ // Wrap around at either end and skip hidden cells, as they are not rendered.
+ let next = (1..count)
+ .map(|step| match direction {
+ true => (cursor + step) % count,
+ false => (cursor + count - step) % count,
+ })
+ .find(|cell_idx| self.cell_visible(*cell_idx));
+ if let Some(next) = next {
+ self.cursor = Some(next);
+ self.focus_active_cell();
+ }
true
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 7+ messages in thread
* applied: [PATCH yew-widget-toolkit 0/5] data table fixes
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
` (4 preceding siblings ...)
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 5/5] widget: data table: header: skip hidden cells in keyboard navigation Dominik Csapak
@ 2026-10-16 9:44 ` Dietmar Maurer
5 siblings, 0 replies; 7+ messages in thread
From: Dietmar Maurer @ 2026-10-16 9:44 UTC (permalink / raw)
To: Dominik Csapak, yew-devel
applied
On 10/16/26 11:03 AM, Dominik Csapak wrote:
> Some miscellaneous fixes for the data table. Not all of them are
> reachable in our products, but still worth fixing IMO.
>
> Dominik Csapak (5):
> widget: data table: keep hidden columns when headers are recreated
> widget: data table: update listeners when store or selection change
> widget: data table: update cell configuration on property change
> widget: data table: header: fix keyboard navigation from group headers
> widget: data table: header: skip hidden cells in keyboard navigation
>
> src/widget/data_table/data_table.rs | 47 +++++++++++++++------
> src/widget/data_table/header_widget.rs | 58 +++++++++++++-------------
> 2 files changed, 65 insertions(+), 40 deletions(-)
>
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-16 9:44 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 9:01 [PATCH yew-widget-toolkit 0/5] data table fixes Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 1/5] widget: data table: keep hidden columns when headers are recreated Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 2/5] widget: data table: update listeners when store or selection change Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 3/5] widget: data table: update cell configuration on property change Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 4/5] widget: data table: header: fix keyboard navigation from group headers Dominik Csapak
2026-10-16 9:01 ` [PATCH yew-widget-toolkit 5/5] widget: data table: header: skip hidden cells in keyboard navigation Dominik Csapak
2026-10-16 9:44 ` applied: [PATCH yew-widget-toolkit 0/5] data table fixes Dietmar Maurer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox