diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor index 3610cc8..af2fb0d 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor @@ -17,7 +17,13 @@ @foreach (var col in OrderedColumns()) { + // @key keeps Blazor reusing one DOM node per column across + // re-renders (reorder/resize), so audit-grid.js binds drag + // listeners exactly once per and never leaks them onto + // discarded nodes — the __auditGridCellBound guard relies on + // this node stability to be fully sound. diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs index 928a050..d6b08c4 100644 --- a/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs +++ b/src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.cs @@ -37,6 +37,16 @@ namespace ScadaLink.CentralUI.Components.Audit; /// rows) — that's the conventional "we've reached the /// end" signal for keyset paging without a count query. /// +/// +/// +/// Accessibility. Column resize and reorder are mouse/pointer-only — +/// they use a pointer-driven resize handle and native HTML5 drag-and-drop with +/// no keyboard equivalent and no ARIA for the reorder. This is a conscious +/// scope decision for an internal tool, not an oversight: only the column- +/// customisation gesture is mouse-only. The persisted layout itself +/// renders as plain HTML, so keyboard and assistive-technology users still get +/// a fully readable, navigable grid. +/// /// public partial class AuditResultsGrid : IAsyncDisposable { @@ -99,6 +109,9 @@ public partial class AuditResultsGrid : IAsyncDisposable /// data-test + the column-order parameter); the label is the user-facing /// header text. Mirrors Component-AuditLog.md §10. /// + // Label intentionally equals Key for every column today; the separate Label + // field is future-proofing for humanised headers (e.g. "Occurred (UTC)") — + // populating it is a deliberate later change, out of scope here. private static readonly IReadOnlyList<(string Key, string Label)> AllColumns = new[] { ("OccurredAtUtc", "OccurredAtUtc"), @@ -251,6 +264,12 @@ public partial class AuditResultsGrid : IAsyncDisposable // is idempotent — already-bound cells are skipped, and the .NET // reference is refreshed — so a re-render after a reorder still leaves // every header cell wired without leaking handlers. + // + // OnColumnResized/OnColumnReordered both call StateHasChanged(), which + // re-runs this method and calls init again. That repeat call is an + // intentional cheap no-op: the @key-stable nodes plus the + // __auditGridCellBound guard mean init re-scans the header and rebinds + // nothing — so there is deliberately no gating logic here. if (_selfRef is not null) { try diff --git a/src/ScadaLink.CentralUI/wwwroot/js/audit-grid.js b/src/ScadaLink.CentralUI/wwwroot/js/audit-grid.js index a1ce628..3ede5ed 100644 --- a/src/ScadaLink.CentralUI/wwwroot/js/audit-grid.js +++ b/src/ScadaLink.CentralUI/wwwroot/js/audit-grid.js @@ -171,6 +171,12 @@ window.auditGrid = { // Apply a width to a via a CSS custom property. The scoped stylesheet // reads --audit-col-width; absent it, the column falls back to auto. + // + // Known, intentional behaviour: during a live resize drag this updates the + // width immediately, but the body cells only catch up on the next + // .NET re-render (driven by OnColumnResized at pointer-up). The brief + // header/body width mismatch mid-drag is an accepted trade-off for an + // internal tool — not a bug. _applyWidth: function (th, widthPx) { th.style.setProperty("--audit-col-width", widthPx + "px"); },