Harden tables selection interactions
This commit is contained in:
@@ -30,7 +30,7 @@ It is intentionally implementation-focused:
|
|||||||
|
|
||||||
- Branch: `frontend/tables-overhaul`
|
- Branch: `frontend/tables-overhaul`
|
||||||
- Last updated: `2026-03-21`
|
- Last updated: `2026-03-21`
|
||||||
- Current focus: `Phase 3`
|
- Current focus: `Phase 4`
|
||||||
- Document mode: living plan and progress log
|
- Document mode: living plan and progress log
|
||||||
|
|
||||||
### Progress Log
|
### Progress Log
|
||||||
@@ -72,6 +72,7 @@ It is intentionally implementation-focused:
|
|||||||
| 2026-03-21 | P3.9 | Completed | Pulled legend/help out of the always-on canvas component and turned it into an explicitly toggled secondary surface controlled from the context bar. |
|
| 2026-03-21 | P3.9 | Completed | Pulled legend/help out of the always-on canvas component and turned it into an explicitly toggled secondary surface controlled from the context bar. |
|
||||||
| 2026-03-21 | P3.10 | Completed | Softened the default `Reference` mode by replacing repeated curation wording in resting cells with subtle status indicators and by simplifying the top-level guidance copy. |
|
| 2026-03-21 | P3.10 | Completed | Softened the default `Reference` mode by replacing repeated curation wording in resting cells with subtle status indicators and by simplifying the top-level guidance copy. |
|
||||||
| 2026-03-21 | P3.11 | Completed | Moved the live editor and curation entry points into the shared inspector content and removed the last remaining grid-owned action buttons. |
|
| 2026-03-21 | P3.11 | Completed | Moved the live editor and curation entry points into the shared inspector content and removed the last remaining grid-owned action buttons. |
|
||||||
|
| 2026-03-21 | P3.12 | Completed | Added keyboard-selectable cells, visible focus treatment, and selection normalization so changing filters or modes cannot leave the inspector pointing at hidden cells. |
|
||||||
|
|
||||||
### Lessons Learned
|
### Lessons Learned
|
||||||
|
|
||||||
@@ -467,7 +468,7 @@ Build the shared interaction infrastructure needed by multiple destinations befo
|
|||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
`In progress`
|
`Completed`
|
||||||
|
|
||||||
### Task Progress
|
### Task Progress
|
||||||
|
|
||||||
@@ -484,7 +485,7 @@ Build the shared interaction infrastructure needed by multiple destinations befo
|
|||||||
| `P3.9` | Completed | Legend/help is now on-demand and controlled from the context bar instead of always rendering below the canvas. |
|
| `P3.9` | Completed | Legend/help is now on-demand and controlled from the context bar instead of always rendering below the canvas. |
|
||||||
| `P3.10` | Completed | Default `Reference` mode now uses quieter status indicators and calmer guidance copy so the page reads less like a maintenance surface. |
|
| `P3.10` | Completed | Default `Reference` mode now uses quieter status indicators and calmer guidance copy so the page reads less like a maintenance surface. |
|
||||||
| `P3.11` | Completed | Full editor and curation entry points now live in the shared inspector content instead of the grid itself. |
|
| `P3.11` | Completed | Full editor and curation entry points now live in the shared inspector content instead of the grid itself. |
|
||||||
| `P3.12` | Pending | Normalize click/tap/keyboard selection and close the phase with a hardening pass. |
|
| `P3.12` | Completed | Cell selection now works by click, tap, and keyboard, and view changes clear stale selection instead of leaving the inspector out of sync with the visible grid. |
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|
||||||
|
|||||||
@@ -677,18 +677,21 @@
|
|||||||
private Task UpdateReferenceModeAsync(string mode)
|
private Task UpdateReferenceModeAsync(string mode)
|
||||||
{
|
{
|
||||||
referenceMode = NormalizeMode(mode);
|
referenceMode = NormalizeMode(mode);
|
||||||
|
NormalizeSelectedCellForCurrentView();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task UpdateSelectedGroupAsync(string groupKey)
|
private Task UpdateSelectedGroupAsync(string groupKey)
|
||||||
{
|
{
|
||||||
selectedGroupKey = NormalizeOptionalFilter(groupKey);
|
selectedGroupKey = NormalizeOptionalFilter(groupKey);
|
||||||
|
NormalizeSelectedCellForCurrentView();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task UpdateSelectedColumnAsync(string columnKey)
|
private Task UpdateSelectedColumnAsync(string columnKey)
|
||||||
{
|
{
|
||||||
selectedColumnKey = NormalizeOptionalFilter(columnKey);
|
selectedColumnKey = NormalizeOptionalFilter(columnKey);
|
||||||
|
NormalizeSelectedCellForCurrentView();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,6 +771,8 @@
|
|||||||
{
|
{
|
||||||
selectedCell = null;
|
selectedCell = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NormalizeSelectedCellForCurrentView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeMode(string? mode) =>
|
private static string NormalizeMode(string? mode) =>
|
||||||
@@ -796,4 +801,40 @@
|
|||||||
var digitsOnly = new string(value.Where(char.IsDigit).ToArray());
|
var digitsOnly = new string(value.Where(char.IsDigit).ToArray());
|
||||||
return digitsOnly.Length == 0 ? string.Empty : digitsOnly;
|
return digitsOnly.Length == 0 ? string.Empty : digitsOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NormalizeSelectedCellForCurrentView()
|
||||||
|
{
|
||||||
|
if (selectedCell is null || tableDetail is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell = tableDetail.Cells.FirstOrDefault(item => item.ResultId == selectedCell.ResultId);
|
||||||
|
if (cell is null || !MatchesCurrentView(cell))
|
||||||
|
{
|
||||||
|
selectedCell = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MatchesCurrentView(CriticalTableCellDetail cell)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(selectedGroupKey) &&
|
||||||
|
!string.Equals(cell.GroupKey, selectedGroupKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(selectedColumnKey) &&
|
||||||
|
!string.Equals(cell.ColumnKey, selectedColumnKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return referenceMode switch
|
||||||
|
{
|
||||||
|
TablesReferenceMode.NeedsCuration => !cell.IsCurated,
|
||||||
|
TablesReferenceMode.Curated => cell.IsCurated,
|
||||||
|
_ => true
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,13 @@
|
|||||||
|
|
||||||
@if (MatchesModeFilter(cell))
|
@if (MatchesModeFilter(cell))
|
||||||
{
|
{
|
||||||
<div class="@GetCellCssClass(cell, displayColumn.GroupKey)" @onclick="() => SelectCell(cell)">
|
<div
|
||||||
|
class="@GetCellCssClass(cell, displayColumn.GroupKey)"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-pressed="@isSelectedCell"
|
||||||
|
@onclick="() => SelectCell(cell)"
|
||||||
|
@onkeydown="args => HandleCellKeyDown(args, cell)">
|
||||||
<div class="critical-table-cell-shell">
|
<div class="critical-table-cell-shell">
|
||||||
<div class="critical-table-cell-actions">
|
<div class="critical-table-cell-actions">
|
||||||
@if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal))
|
@if (string.Equals(CurrentMode, TablesReferenceMode.Reference, StringComparison.Ordinal))
|
||||||
@@ -306,5 +312,17 @@
|
|||||||
private bool IsSelectedCell(CriticalTableCellDetail cell) =>
|
private bool IsSelectedCell(CriticalTableCellDetail cell) =>
|
||||||
SelectedCell is not null && cell.ResultId == SelectedCell.ResultId;
|
SelectedCell is not null && cell.ResultId == SelectedCell.ResultId;
|
||||||
|
|
||||||
|
private Task HandleCellKeyDown(KeyboardEventArgs args, CriticalTableCellDetail cell)
|
||||||
|
{
|
||||||
|
if (string.Equals(args.Key, "Enter", StringComparison.Ordinal) ||
|
||||||
|
string.Equals(args.Key, " ", StringComparison.Ordinal) ||
|
||||||
|
string.Equals(args.Key, "Spacebar", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return SelectCell(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";
|
private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1643,6 +1643,11 @@ pre,
|
|||||||
transition: box-shadow 0.16s ease, background-color 0.16s ease, transform 0.16s ease;
|
transition: box-shadow 0.16s ease, background-color 0.16s ease, transform 0.16s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-table-cell:focus-visible {
|
||||||
|
outline: 2px solid rgba(13, 148, 136, 0.45);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-table-cell.is-curated {
|
.critical-table-cell.is-curated {
|
||||||
background:
|
background:
|
||||||
linear-gradient(135deg, rgba(102, 138, 83, 0.16), transparent 34%),
|
linear-gradient(135deg, rgba(102, 138, 83, 0.16), transparent 34%),
|
||||||
|
|||||||
Reference in New Issue
Block a user