mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
Fix spreadsheet keyboard focus: active cell on entry, F6 cycling, grid tab stop
- Default the active sheet to A1 when it has no selection, centralized in a single SetActiveSheet method (covers load, sheet switch, add and delete). Without an active cell the focused grid showed no indicator and the toolbar tools stayed disabled, so F6 into the toolbar found nothing focusable and got stuck. - focusRegion now skips hidden focusables (inline-rendered but closed color-picker and numeric-stepper popups), so F6 can no longer be stranded on the ribbon. - Give the virtual-grid scroller tabindex=-1 so Chrome's focusable-scroll-container behavior stops turning it into a phantom tab stop that scrolls but never navigates.
This commit is contained in:
@@ -429,7 +429,17 @@ public partial class RadzenSpreadsheet : RadzenComponent, IAsyncDisposable, ISpr
|
||||
{
|
||||
workbook = value;
|
||||
workbookView = null;
|
||||
sheetIndex = 0;
|
||||
SetActiveSheet(0);
|
||||
}
|
||||
|
||||
private void SetActiveSheet(int index)
|
||||
{
|
||||
sheetIndex = index;
|
||||
|
||||
if (Worksheet?.Selection.Cell == CellRef.Invalid)
|
||||
{
|
||||
sheet.Selection.Select(new CellRef(0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CloseMenusAsync()
|
||||
@@ -451,7 +461,7 @@ public partial class RadzenSpreadsheet : RadzenComponent, IAsyncDisposable, ISpr
|
||||
|
||||
await CloseMenusAsync();
|
||||
|
||||
sheetIndex = index;
|
||||
SetActiveSheet(index);
|
||||
}
|
||||
|
||||
private async Task OnAddSheetAsync()
|
||||
@@ -465,7 +475,7 @@ public partial class RadzenSpreadsheet : RadzenComponent, IAsyncDisposable, ISpr
|
||||
|
||||
var name = GenerateSheetName();
|
||||
workbook.AddSheet(name, 100, 26);
|
||||
sheetIndex = workbook.Sheets.Count - 1;
|
||||
SetActiveSheet(workbook.Sheets.Count - 1);
|
||||
}
|
||||
|
||||
private async Task OnSheetAction(RadzenSplitButtonItem? item, Worksheet sheet)
|
||||
@@ -505,14 +515,12 @@ public partial class RadzenSpreadsheet : RadzenComponent, IAsyncDisposable, ISpr
|
||||
workbook.RemoveSheet(sheet);
|
||||
workbookView?.Remove(sheet);
|
||||
|
||||
if (sheetIndex >= workbook.Sheets.Count)
|
||||
{
|
||||
sheetIndex = workbook.Sheets.Count - 1;
|
||||
}
|
||||
else if (removedIndex < sheetIndex)
|
||||
{
|
||||
sheetIndex--;
|
||||
}
|
||||
// Re-activate even when the index stays the same: deleting the active, non-last sheet shifts a
|
||||
// different sheet into that index, which still needs its active cell ensured.
|
||||
SetActiveSheet(
|
||||
sheetIndex >= workbook.Sheets.Count ? workbook.Sheets.Count - 1 :
|
||||
removedIndex < sheetIndex ? sheetIndex - 1 :
|
||||
sheetIndex);
|
||||
}
|
||||
|
||||
private async Task OnRenameSheetAsync(Worksheet sheet)
|
||||
@@ -695,8 +703,6 @@ public partial class RadzenSpreadsheet : RadzenComponent, IAsyncDisposable, ISpr
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a sort command scoped to the data the clicked cell belongs to, never including a
|
||||
// table/auto-filter header (or table totals) row in the sorted range.
|
||||
private SortCommand BuildSortCommand(int row, int column, SortOrder order)
|
||||
{
|
||||
foreach (var table in Worksheet!.Tables)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="rz-virtual-grid-scroller" @ref=@scrollable>
|
||||
<div class="rz-virtual-grid-scroller" tabindex="-1" @ref=@scrollable>
|
||||
<div class="rz-virtual-grid-spacer" style=@SpacerStyle></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6065,8 +6065,9 @@ class Spreadsheet {
|
||||
this.dotNetRef.invokeMethodAsync('OnKeyDownAsync', this.toEventArgs(e), isGridContext);
|
||||
}
|
||||
|
||||
// F6 / Shift+F6 cycle focus between the spreadsheet regions: ribbon tabs -> active toolbar ->
|
||||
// formula bar -> grid -> sheet tabs. This is the advertised escape from the grid (WCAG 2.1.2).
|
||||
// F6 / Shift+F6 cycle focus between the spreadsheet regions in their visible top-to-bottom order:
|
||||
// ribbon tabs -> active toolbar -> formula bar -> grid -> sheet tabs. This is the advertised escape
|
||||
// from the grid (WCAG 2.1.2).
|
||||
focusAdjacent = (forward) => {
|
||||
// this.element IS this spreadsheet's root (.rz-spreadsheet, role=application). querySelector
|
||||
// only matches descendants of root, so regions from another spreadsheet on the page are never
|
||||
@@ -6114,8 +6115,12 @@ class Spreadsheet {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusable = region.querySelector('button:not([disabled]), a[href], input:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]');
|
||||
(focusable || region).focus();
|
||||
// Skip hidden focusables: inline-rendered but closed popups (color pickers, numeric steppers)
|
||||
// match the selector yet calling focus() on a hidden element silently fails, which would strand
|
||||
// the user on the previous region. Pick the first one that is actually rendered.
|
||||
const focusables = region.querySelectorAll('button:not([disabled]), a[href], input:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]');
|
||||
const target = [...focusables].find(el => el.offsetParent !== null || el.getClientRects().length > 0);
|
||||
(target || region).focus();
|
||||
}
|
||||
|
||||
// Opens the cell context menu at the active cell (Shift+F10 / ContextMenu key). The cell element is
|
||||
|
||||
Reference in New Issue
Block a user