Apply the current/selected split to table rows
The 2026-08-08 decision renamed Row::selected to Row::current because one
word meant two things: the app's pointer and the user's tick. Cells kept
the old word for the pointer meaning, so a table row emitted class
table-row-selected and aria-current from the same field, saying one thing
in CSS and the other to a screen reader. A table row and a list row are
the same fact in two arrangements.
No tick added to go with it. Row grew one because goingson's contact cards
have a bulk checkbox; no table asks for one, and a member added because
its sibling has it is a member with no consumer to say what it means.
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+42 insertions,
-5 deletions
| 755 |
755 |
|
/// The route that opens this row.
|
| 756 |
756 |
|
pub activate: Option<Action>,
|
| 757 |
757 |
|
/// Whether this is the row currently being shown elsewhere.
|
| 758 |
|
- |
pub selected: bool,
|
|
758 |
+ |
///
|
|
759 |
+ |
/// The same fact [`Row::current`] carries, under the same name. It was
|
|
760 |
+ |
/// `selected` until 2026-08-08, which is the word that decision retired:
|
|
761 |
+ |
/// the app's pointer and the user's tick are two things, and one word for
|
|
762 |
+ |
/// both is how every renderer ends up guessing which was meant. `Row` was
|
|
763 |
+ |
/// renamed and this was missed, so it kept the ambiguous word while
|
|
764 |
+ |
/// emitting `aria-current` from it.
|
|
765 |
+ |
///
|
|
766 |
+ |
/// There is deliberately no tick here to go with it. `Row` grew one because
|
|
767 |
+ |
/// goingson's contact cards have a bulk checkbox; no table asks for one, and
|
|
768 |
+ |
/// a member added because its sibling has it is a member with no consumer to
|
|
769 |
+ |
/// tell us what it should mean.
|
|
770 |
+ |
pub current: bool,
|
| 759 |
771 |
|
}
|
| 760 |
772 |
|
|
| 761 |
773 |
|
impl Cells {
|
| 764 |
776 |
|
Self {
|
| 765 |
777 |
|
values: values.into_iter().map(Into::into).collect(),
|
| 766 |
778 |
|
activate: None,
|
| 767 |
|
- |
selected: false,
|
|
779 |
+ |
current: false,
|
| 768 |
780 |
|
}
|
| 769 |
781 |
|
}
|
| 770 |
782 |
|
|
| 312 |
312 |
|
out: &mut String,
|
| 313 |
313 |
|
) {
|
| 314 |
314 |
|
let mut classes = vec!["table-row"];
|
| 315 |
|
- |
if cells.selected {
|
| 316 |
|
- |
classes.push("table-row-selected");
|
|
315 |
+ |
if cells.current {
|
|
316 |
+ |
// `table-row-current`, matching `row-current` on a list row. It was
|
|
317 |
+ |
// `table-row-selected` while the field was, so the class said one thing
|
|
318 |
+ |
// and the `aria-current` two lines down said the other.
|
|
319 |
+ |
classes.push("table-row-current");
|
| 317 |
320 |
|
}
|
| 318 |
321 |
|
|
| 319 |
322 |
|
out.push_str("<div role=\"row\"");
|
| 320 |
323 |
|
class_attr(&classes, opts, out);
|
| 321 |
|
- |
if cells.selected {
|
|
324 |
+ |
if cells.current {
|
| 322 |
325 |
|
out.push_str(" aria-current=\"true\"");
|
| 323 |
326 |
|
}
|
| 324 |
327 |
|
if let Some(action) = &cells.activate {
|
| 368 |
368 |
|
assert!(!html.contains("style="));
|
| 369 |
369 |
|
}
|
| 370 |
370 |
|
|
|
371 |
+ |
#[test]
|
|
372 |
+ |
fn a_table_row_says_current_the_same_way_a_list_row_does() {
|
|
373 |
+ |
// The 2026-08-08 rename applied where it was missed. `Cells` kept
|
|
374 |
+ |
// `selected` while meaning the app's pointer, so the class said one thing
|
|
375 |
+ |
// and the `aria-current` beside it said the other. A table row and a list
|
|
376 |
+ |
// row are the same fact in two arrangements and a stylesheet should not
|
|
377 |
+ |
// have to know which it is reading.
|
|
378 |
+ |
let html = fragment(&Node::Table {
|
|
379 |
+ |
columns: vec![Column::new("Name").width(layout::Width::Fill)],
|
|
380 |
+ |
rows: vec![Cells {
|
|
381 |
+ |
current: true,
|
|
382 |
+ |
..Cells::new(["kick.wav"])
|
|
383 |
+ |
}],
|
|
384 |
+ |
});
|
|
385 |
+ |
assert!(html.contains("aria-current=\"true\""));
|
|
386 |
+ |
assert!(html.contains("table-row-current"));
|
|
387 |
+ |
assert!(!html.contains("table-row-selected"));
|
|
388 |
+ |
|
|
389 |
+ |
// And no tick, because a table has none to describe.
|
|
390 |
+ |
assert!(!html.contains("type=\"checkbox\""));
|
|
391 |
+ |
}
|
|
392 |
+ |
|
| 371 |
393 |
|
#[test]
|
| 372 |
394 |
|
fn a_cell_value_is_text_and_cannot_become_markup() {
|
| 373 |
395 |
|
let html = fragment(&Node::Table {
|