fix(centralui): stabilize audit grid th nodes with @key; doc grid limitations
This commit is contained in:
@@ -17,7 +17,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
@foreach (var col in OrderedColumns())
|
@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 <th> and never leaks them onto
|
||||||
|
// discarded nodes — the __auditGridCellBound guard relies on
|
||||||
|
// this node stability to be fully sound.
|
||||||
<th class="audit-grid-th"
|
<th class="audit-grid-th"
|
||||||
|
@key="col.Key"
|
||||||
data-test="col-header-@col.Key"
|
data-test="col-header-@col.Key"
|
||||||
data-col-key="@col.Key"
|
data-col-key="@col.Key"
|
||||||
style="@ColumnWidthStyle(col.Key)">
|
style="@ColumnWidthStyle(col.Key)">
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ namespace ScadaLink.CentralUI.Components.Audit;
|
|||||||
/// <see cref="PageSize"/> rows) — that's the conventional "we've reached the
|
/// <see cref="PageSize"/> rows) — that's the conventional "we've reached the
|
||||||
/// end" signal for keyset paging without a count query.
|
/// end" signal for keyset paging without a count query.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// <b>Accessibility.</b> 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-
|
||||||
|
/// <i>customisation</i> 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.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class AuditResultsGrid : IAsyncDisposable
|
public partial class AuditResultsGrid : IAsyncDisposable
|
||||||
{
|
{
|
||||||
@@ -99,6 +109,9 @@ public partial class AuditResultsGrid : IAsyncDisposable
|
|||||||
/// <c>data-test</c> + the column-order parameter); the label is the user-facing
|
/// <c>data-test</c> + the column-order parameter); the label is the user-facing
|
||||||
/// header text. Mirrors Component-AuditLog.md §10.
|
/// header text. Mirrors Component-AuditLog.md §10.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// 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[]
|
private static readonly IReadOnlyList<(string Key, string Label)> AllColumns = new[]
|
||||||
{
|
{
|
||||||
("OccurredAtUtc", "OccurredAtUtc"),
|
("OccurredAtUtc", "OccurredAtUtc"),
|
||||||
@@ -251,6 +264,12 @@ public partial class AuditResultsGrid : IAsyncDisposable
|
|||||||
// is idempotent — already-bound cells are skipped, and the .NET
|
// is idempotent — already-bound cells are skipped, and the .NET
|
||||||
// reference is refreshed — so a re-render after a reorder still leaves
|
// reference is refreshed — so a re-render after a reorder still leaves
|
||||||
// every header cell wired without leaking handlers.
|
// 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 <th> 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)
|
if (_selfRef is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -171,6 +171,12 @@ window.auditGrid = {
|
|||||||
|
|
||||||
// Apply a width to a <th> via a CSS custom property. The scoped stylesheet
|
// Apply a width to a <th> via a CSS custom property. The scoped stylesheet
|
||||||
// reads --audit-col-width; absent it, the column falls back to auto.
|
// reads --audit-col-width; absent it, the column falls back to auto.
|
||||||
|
//
|
||||||
|
// Known, intentional behaviour: during a live resize drag this updates the
|
||||||
|
// <th> width immediately, but the <td> 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) {
|
_applyWidth: function (th, widthPx) {
|
||||||
th.style.setProperty("--audit-col-width", widthPx + "px");
|
th.style.setProperty("--audit-col-width", widthPx + "px");
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user