Enhance tables canvas reading states
This commit is contained in:
@@ -65,6 +65,7 @@ It is intentionally implementation-focused:
|
|||||||
| 2026-03-21 | P3.2 | Completed | Replaced the floating table picker with a permanent left-rail layout, converted the old selector component into a real page header, and kept the current selection flow intact inside the new reference frame. |
|
| 2026-03-21 | P3.2 | Completed | Replaced the floating table picker with a permanent left-rail layout, converted the old selector component into a real page header, and kept the current selection flow intact inside the new reference frame. |
|
||||||
| 2026-03-21 | P3.3 | Completed | Added rail search, family filters, pinned and recent sections, curated status chips, and keyboard up/down plus Enter handling on top of the new permanent table index rail. |
|
| 2026-03-21 | P3.3 | Completed | Added rail search, family filters, pinned and recent sections, curated status chips, and keyboard up/down plus Enter handling on top of the new permanent table index rail. |
|
||||||
| 2026-03-21 | P3.4 | Completed | Added a sticky context bar with reference-mode tabs, variant and severity selectors, roll-jump state, and active filter chips, then wired those controls into page state and canvas filtering. |
|
| 2026-03-21 | P3.4 | Completed | Added a sticky context bar with reference-mode tabs, variant and severity selectors, roll-jump state, and active filter chips, then wired those controls into page state and canvas filtering. |
|
||||||
|
| 2026-03-21 | P3.5 | Completed | Reworked the canvas with sticky headers, a sticky roll-band column, row and column emphasis driven by selection and roll-jump state, selected-cell treatment, and a comfortable/dense density toggle. |
|
||||||
|
|
||||||
### Lessons Learned
|
### Lessons Learned
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ It is intentionally implementation-focused:
|
|||||||
- The `Tables` navigation model needs its own persistent geometry before advanced behaviors land. Converting the selector to a real rail first keeps later search and keyboard work from being tangled up with another structural rewrite.
|
- The `Tables` navigation model needs its own persistent geometry before advanced behaviors land. Converting the selector to a real rail first keeps later search and keyboard work from being tangled up with another structural rewrite.
|
||||||
- Rail keyboard behavior is easiest to maintain when it works from one deduplicated option order, even if the UI shows multiple sections. Keeping one internal option list avoids separate arrow-key state per section.
|
- Rail keyboard behavior is easiest to maintain when it works from one deduplicated option order, even if the UI shows multiple sections. Keeping one internal option list avoids separate arrow-key state per section.
|
||||||
- The context bar controls should own one shared view-state model before the canvas gets more visual treatment. Wiring the filters into the host page now avoids a second refactor when row, column, and cell emphasis land.
|
- The context bar controls should own one shared view-state model before the canvas gets more visual treatment. Wiring the filters into the host page now avoids a second refactor when row, column, and cell emphasis land.
|
||||||
|
- Canvas emphasis becomes maintainable once selection, roll-jump, and density are all fed through one explicit state model. That lets the grid respond to context without hiding selection logic inside CSS-only heuristics.
|
||||||
|
|
||||||
## Target Outcomes
|
## Target Outcomes
|
||||||
|
|
||||||
@@ -463,7 +465,7 @@ Build the shared interaction infrastructure needed by multiple destinations befo
|
|||||||
| `P3.2` | Completed | The old dropdown picker is gone; `/tables` now uses a permanent left rail and a real page header while keeping the current selection flow intact. |
|
| `P3.2` | Completed | The old dropdown picker is gone; `/tables` now uses a permanent left rail and a real page header while keeping the current selection flow intact. |
|
||||||
| `P3.3` | Completed | The rail now supports search-as-you-type, family filters, pinned and recent sections, curated status chips, and a deduplicated arrow/Enter keyboard path. |
|
| `P3.3` | Completed | The rail now supports search-as-you-type, family filters, pinned and recent sections, curated status chips, and a deduplicated arrow/Enter keyboard path. |
|
||||||
| `P3.4` | Completed | The table surface now has a sticky context bar with mode tabs, variant/severity focus, roll-jump state, and active filter chips wired into host-page view state. |
|
| `P3.4` | Completed | The table surface now has a sticky context bar with mode tabs, variant/severity focus, roll-jump state, and active filter chips wired into host-page view state. |
|
||||||
| `P3.5` | Pending | Rework the canvas for sticky headers, sticky roll bands, stronger reading emphasis, and density control. |
|
| `P3.5` | Completed | The canvas now supports sticky headers and roll bands, row and column emphasis from selection and roll-jump state, selected-cell treatment, and a comfortable/dense density toggle. |
|
||||||
| `P3.6` | Pending | Remove visible resting-state action stacks from non-selected cells. |
|
| `P3.6` | Pending | Remove visible resting-state action stacks from non-selected cells. |
|
||||||
| `P3.7` | Pending | Add the desktop selection-driven inspector. |
|
| `P3.7` | Pending | Add the desktop selection-driven inspector. |
|
||||||
| `P3.8` | Pending | Add the mobile bottom-sheet inspector variant. |
|
| `P3.8` | Pending | Add the mobile bottom-sheet inspector variant. |
|
||||||
|
|||||||
@@ -61,17 +61,23 @@
|
|||||||
SelectedGroupKey="selectedGroupKey"
|
SelectedGroupKey="selectedGroupKey"
|
||||||
SelectedColumnKey="selectedColumnKey"
|
SelectedColumnKey="selectedColumnKey"
|
||||||
RollJumpValue="rollJumpValue"
|
RollJumpValue="rollJumpValue"
|
||||||
|
DensityMode="densityMode"
|
||||||
OnTogglePin="TogglePinnedTableAsync"
|
OnTogglePin="TogglePinnedTableAsync"
|
||||||
OnModeChanged="UpdateReferenceModeAsync"
|
OnModeChanged="UpdateReferenceModeAsync"
|
||||||
OnGroupChanged="UpdateSelectedGroupAsync"
|
OnGroupChanged="UpdateSelectedGroupAsync"
|
||||||
OnColumnChanged="UpdateSelectedColumnAsync"
|
OnColumnChanged="UpdateSelectedColumnAsync"
|
||||||
OnRollJumpChanged="UpdateRollJumpAsync" />
|
OnRollJumpChanged="UpdateRollJumpAsync"
|
||||||
|
OnDensityChanged="UpdateDensityModeAsync" />
|
||||||
|
|
||||||
<TablesCanvas
|
<TablesCanvas
|
||||||
Detail="detail"
|
Detail="detail"
|
||||||
CurrentMode="referenceMode"
|
CurrentMode="referenceMode"
|
||||||
SelectedGroupKey="selectedGroupKey"
|
SelectedGroupKey="selectedGroupKey"
|
||||||
SelectedColumnKey="selectedColumnKey"
|
SelectedColumnKey="selectedColumnKey"
|
||||||
|
RollJumpValue="rollJumpValue"
|
||||||
|
DensityMode="densityMode"
|
||||||
|
SelectedCell="selectedCell"
|
||||||
|
OnSelectCell="SelectCell"
|
||||||
OnOpenCuration="OpenCellCurationAsync"
|
OnOpenCuration="OpenCellCurationAsync"
|
||||||
OnOpenEditor="OpenCellEditorAsync" />
|
OnOpenEditor="OpenCellEditorAsync" />
|
||||||
</div>
|
</div>
|
||||||
@@ -150,6 +156,8 @@
|
|||||||
private string selectedGroupKey = string.Empty;
|
private string selectedGroupKey = string.Empty;
|
||||||
private string selectedColumnKey = string.Empty;
|
private string selectedColumnKey = string.Empty;
|
||||||
private string rollJumpValue = string.Empty;
|
private string rollJumpValue = string.Empty;
|
||||||
|
private string densityMode = TablesDensityMode.Comfortable;
|
||||||
|
private TablesCellSelection? selectedCell;
|
||||||
private bool hasResolvedStoredTableSelection;
|
private bool hasResolvedStoredTableSelection;
|
||||||
private CriticalTableReference? SelectedTableReference =>
|
private CriticalTableReference? SelectedTableReference =>
|
||||||
referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase));
|
referenceData?.CriticalTables.FirstOrDefault(item => string.Equals(item.Key, selectedTableSlug, StringComparison.OrdinalIgnoreCase));
|
||||||
@@ -664,6 +672,23 @@
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task UpdateDensityModeAsync(string mode)
|
||||||
|
{
|
||||||
|
densityMode = NormalizeDensityMode(mode);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectCell(TablesCellSelection selection)
|
||||||
|
{
|
||||||
|
if (tableDetail?.Cells.Any(cell => cell.ResultId == selection.ResultId) != true)
|
||||||
|
{
|
||||||
|
selectedCell = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedCell = selection;
|
||||||
|
}
|
||||||
|
|
||||||
private void NormalizeViewStateForCurrentDetail()
|
private void NormalizeViewStateForCurrentDetail()
|
||||||
{
|
{
|
||||||
referenceMode = NormalizeMode(referenceMode);
|
referenceMode = NormalizeMode(referenceMode);
|
||||||
@@ -673,6 +698,8 @@
|
|||||||
selectedGroupKey = string.Empty;
|
selectedGroupKey = string.Empty;
|
||||||
selectedColumnKey = string.Empty;
|
selectedColumnKey = string.Empty;
|
||||||
rollJumpValue = string.Empty;
|
rollJumpValue = string.Empty;
|
||||||
|
densityMode = NormalizeDensityMode(densityMode);
|
||||||
|
selectedCell = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,6 +714,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
rollJumpValue = NormalizeRollInput(rollJumpValue);
|
rollJumpValue = NormalizeRollInput(rollJumpValue);
|
||||||
|
densityMode = NormalizeDensityMode(densityMode);
|
||||||
|
|
||||||
|
if (selectedCell is not null && tableDetail.Cells.All(cell => cell.ResultId != selectedCell.ResultId))
|
||||||
|
{
|
||||||
|
selectedCell = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeMode(string? mode) =>
|
private static string NormalizeMode(string? mode) =>
|
||||||
@@ -700,6 +733,11 @@
|
|||||||
private static string NormalizeOptionalFilter(string? value) =>
|
private static string NormalizeOptionalFilter(string? value) =>
|
||||||
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
|
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
|
||||||
|
|
||||||
|
private static string NormalizeDensityMode(string? mode) =>
|
||||||
|
string.Equals(mode, TablesDensityMode.Dense, StringComparison.Ordinal)
|
||||||
|
? TablesDensityMode.Dense
|
||||||
|
: TablesDensityMode.Comfortable;
|
||||||
|
|
||||||
private static string NormalizeRollInput(string? value)
|
private static string NormalizeRollInput(string? value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<div class="table-scroll">
|
<div class="table-scroll">
|
||||||
<div class="critical-table-grid" role="group" aria-label="@Detail.DisplayName" style="@gridTemplateStyle">
|
<div class="critical-table-grid @BuildGridCssClass()" role="group" aria-label="@Detail.DisplayName" style="@gridTemplateStyle">
|
||||||
@if (Detail.Groups.Count > 0)
|
@if (Detail.Groups.Count > 0)
|
||||||
{
|
{
|
||||||
<div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
|
<div class="critical-table-grid-header-cell critical-table-grid-corner" aria-hidden="true"></div>
|
||||||
@foreach (var group in visibleGroups)
|
@foreach (var group in visibleGroups)
|
||||||
{
|
{
|
||||||
<div
|
<div
|
||||||
class="critical-table-grid-header-cell critical-table-grid-group-header"
|
class="@BuildGroupHeaderCssClass(group.Key)"
|
||||||
style="@BuildColumnSpanStyle(visibleColumns.Count)">
|
style="@BuildColumnSpanStyle(visibleColumns.Count)">
|
||||||
<span>@group.Label</span>
|
<span>@group.Label</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,14 +16,14 @@
|
|||||||
<div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
|
<div class="critical-table-grid-header-cell critical-table-grid-roll-band-header" aria-hidden="true"></div>
|
||||||
@foreach (var displayColumn in displayColumns)
|
@foreach (var displayColumn in displayColumns)
|
||||||
{
|
{
|
||||||
<div class="critical-table-grid-header-cell critical-table-grid-column-header">
|
<div class="@BuildColumnHeaderCssClass(displayColumn.GroupKey, displayColumn.ColumnKey)">
|
||||||
<span>@displayColumn.ColumnLabel</span>
|
<span>@displayColumn.ColumnLabel</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@foreach (var rollBand in Detail.RollBands)
|
@foreach (var rollBand in Detail.RollBands)
|
||||||
{
|
{
|
||||||
<div class="critical-table-grid-header-cell critical-table-grid-roll-band">@rollBand.Label</div>
|
<div class="@BuildRollBandCssClass(rollBand.Label)">@rollBand.Label</div>
|
||||||
@foreach (var displayColumn in displayColumns)
|
@foreach (var displayColumn in displayColumns)
|
||||||
{
|
{
|
||||||
if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var resolvedCell) && resolvedCell is not null)
|
if (TryGetCell(rollBand.Label, displayColumn.GroupKey, displayColumn.ColumnKey, out var resolvedCell) && resolvedCell is not null)
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
@if (MatchesModeFilter(cell))
|
@if (MatchesModeFilter(cell))
|
||||||
{
|
{
|
||||||
<div class="@GetCellCssClass(cell)">
|
<div class="@GetCellCssClass(cell, displayColumn.GroupKey)" @onclick="() => SelectCell(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 (cell.IsCurated)
|
@if (cell.IsCurated)
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="critical-cell-action-button is-curation"
|
class="critical-cell-action-button is-curation"
|
||||||
title="Open the curation preview for this cell."
|
title="Open the curation preview for this cell."
|
||||||
|
@onclick:stopPropagation="true"
|
||||||
@onclick="() => OnOpenCuration.InvokeAsync(cell.ResultId)">
|
@onclick="() => OnOpenCuration.InvokeAsync(cell.ResultId)">
|
||||||
Needs Curation
|
Needs Curation
|
||||||
</button>
|
</button>
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class="critical-cell-action-button is-edit"
|
class="critical-cell-action-button is-edit"
|
||||||
title="Open the full editor for this cell."
|
title="Open the full editor for this cell."
|
||||||
|
@onclick:stopPropagation="true"
|
||||||
@onclick="() => OnOpenEditor.InvokeAsync(cell.ResultId)">
|
@onclick="() => OnOpenEditor.InvokeAsync(cell.ResultId)">
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -105,6 +107,18 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string SelectedColumnKey { get; set; } = string.Empty;
|
public string SelectedColumnKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string RollJumpValue { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DensityMode { get; set; } = TablesDensityMode.Comfortable;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public TablesCellSelection? SelectedCell { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<TablesCellSelection> OnSelectCell { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnOpenCuration { get; set; }
|
public EventCallback<int> OnOpenCuration { get; set; }
|
||||||
|
|
||||||
@@ -175,10 +189,133 @@
|
|||||||
_ => true
|
_ => true
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";
|
private string? ActiveRollBand =>
|
||||||
|
!string.IsNullOrWhiteSpace(SelectedCell?.RollBand)
|
||||||
|
? SelectedCell.RollBand
|
||||||
|
: ResolveRollJumpBandLabel();
|
||||||
|
|
||||||
private static string GetCellCssClass(CriticalTableCellDetail cell) =>
|
private string? ActiveColumnKey =>
|
||||||
cell.IsCurated
|
!string.IsNullOrWhiteSpace(SelectedCell?.ColumnKey)
|
||||||
? "critical-table-cell is-curated"
|
? SelectedCell.ColumnKey
|
||||||
: "critical-table-cell needs-curation";
|
: (!string.IsNullOrWhiteSpace(SelectedColumnKey) ? SelectedColumnKey : null);
|
||||||
|
|
||||||
|
private string? ActiveGroupKey =>
|
||||||
|
!string.IsNullOrWhiteSpace(SelectedCell?.GroupKey)
|
||||||
|
? SelectedCell.GroupKey
|
||||||
|
: (!string.IsNullOrWhiteSpace(SelectedGroupKey) ? SelectedGroupKey : null);
|
||||||
|
|
||||||
|
private string BuildGridCssClass() =>
|
||||||
|
string.Equals(DensityMode, TablesDensityMode.Dense, StringComparison.Ordinal)
|
||||||
|
? "is-dense"
|
||||||
|
: "is-comfortable";
|
||||||
|
|
||||||
|
private string BuildGroupHeaderCssClass(string groupKey)
|
||||||
|
{
|
||||||
|
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-group-header" };
|
||||||
|
if (string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-group");
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(' ', classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildColumnHeaderCssClass(string? groupKey, string columnKey)
|
||||||
|
{
|
||||||
|
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-column-header" };
|
||||||
|
|
||||||
|
if (string.Equals(columnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-column");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(groupKey) &&
|
||||||
|
string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-group");
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(' ', classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildRollBandCssClass(string rollBandLabel)
|
||||||
|
{
|
||||||
|
var classes = new List<string> { "critical-table-grid-header-cell", "critical-table-grid-roll-band" };
|
||||||
|
if (string.Equals(rollBandLabel, ActiveRollBand, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-row");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(rollBandLabel, ResolveRollJumpBandLabel(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-roll-target");
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(' ', classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCellCssClass(CriticalTableCellDetail cell, string? groupKey)
|
||||||
|
{
|
||||||
|
var classes = new List<string>
|
||||||
|
{
|
||||||
|
"critical-table-cell",
|
||||||
|
cell.IsCurated ? "is-curated" : "needs-curation"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.Equals(cell.RollBand, ActiveRollBand, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-row");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(cell.ColumnKey, ActiveColumnKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-column");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(groupKey) &&
|
||||||
|
string.Equals(groupKey, ActiveGroupKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-active-group");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedCell is not null && cell.ResultId == SelectedCell.ResultId)
|
||||||
|
{
|
||||||
|
classes.Add("is-selected-cell");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(cell.RollBand, ResolveRollJumpBandLabel(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
classes.Add("is-roll-target");
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(' ', classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ResolveRollJumpBandLabel()
|
||||||
|
{
|
||||||
|
if (!int.TryParse(RollJumpValue, out var targetRoll))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var rollBand in Detail.RollBands)
|
||||||
|
{
|
||||||
|
if (targetRoll < rollBand.MinRoll)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rollBand.MaxRoll is null || targetRoll <= rollBand.MaxRoll.Value)
|
||||||
|
{
|
||||||
|
return rollBand.Label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task SelectCell(CriticalTableCellDetail cell) =>
|
||||||
|
OnSelectCell.InvokeAsync(new TablesCellSelection(cell.ResultId, cell.RollBand, cell.ColumnKey, cell.GroupKey));
|
||||||
|
|
||||||
|
private static string BuildColumnSpanStyle(int span) => $"grid-column: span {span};";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace RolemasterDb.App.Components.Tables;
|
||||||
|
|
||||||
|
public sealed record TablesCellSelection(
|
||||||
|
int ResultId,
|
||||||
|
string RollBand,
|
||||||
|
string ColumnKey,
|
||||||
|
string? GroupKey);
|
||||||
@@ -13,12 +13,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tables-context-controls">
|
<div class="tables-context-controls">
|
||||||
<SegmentedTabs
|
<div class="tables-context-tab-row">
|
||||||
Items="modeTabs"
|
<SegmentedTabs
|
||||||
SelectedValue="CurrentMode"
|
Items="modeTabs"
|
||||||
SelectedValueChanged="OnModeChanged"
|
SelectedValue="CurrentMode"
|
||||||
AriaLabel="Reference mode"
|
SelectedValueChanged="OnModeChanged"
|
||||||
CssClass="tables-context-mode-tabs" />
|
AriaLabel="Reference mode"
|
||||||
|
CssClass="tables-context-mode-tabs" />
|
||||||
|
|
||||||
|
<SegmentedTabs
|
||||||
|
Items="densityTabs"
|
||||||
|
SelectedValue="DensityMode"
|
||||||
|
SelectedValueChanged="OnDensityChanged"
|
||||||
|
AriaLabel="Table density"
|
||||||
|
CssClass="tables-context-density-tabs" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tables-context-fields">
|
<div class="tables-context-fields">
|
||||||
@if (Detail.Groups.Count > 1)
|
@if (Detail.Groups.Count > 1)
|
||||||
@@ -104,6 +113,12 @@
|
|||||||
new(TablesReferenceMode.Curated, "Curated")
|
new(TablesReferenceMode.Curated, "Curated")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private readonly IReadOnlyList<SegmentedTabItem> densityTabs =
|
||||||
|
[
|
||||||
|
new(TablesDensityMode.Comfortable, "Comfortable"),
|
||||||
|
new(TablesDensityMode.Dense, "Dense")
|
||||||
|
];
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public CriticalTableDetail Detail { get; set; } = default!;
|
public CriticalTableDetail Detail { get; set; } = default!;
|
||||||
|
|
||||||
@@ -122,6 +137,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string RollJumpValue { get; set; } = string.Empty;
|
public string RollJumpValue { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DensityMode { get; set; } = TablesDensityMode.Comfortable;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback OnTogglePin { get; set; }
|
public EventCallback OnTogglePin { get; set; }
|
||||||
|
|
||||||
@@ -137,6 +155,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<string> OnRollJumpChanged { get; set; }
|
public EventCallback<string> OnRollJumpChanged { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<string> OnDensityChanged { get; set; }
|
||||||
|
|
||||||
private string GetReadingHint() =>
|
private string GetReadingHint() =>
|
||||||
Detail.Groups.Count > 0
|
Detail.Groups.Count > 0
|
||||||
? "Find the roll band on the left, then read across to the group and severity you need."
|
? "Find the roll band on the left, then read across to the group and severity you need."
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace RolemasterDb.App.Components.Tables;
|
||||||
|
|
||||||
|
public static class TablesDensityMode
|
||||||
|
{
|
||||||
|
public const string Comfortable = "comfortable";
|
||||||
|
public const string Dense = "dense";
|
||||||
|
}
|
||||||
@@ -1382,10 +1382,21 @@ pre,
|
|||||||
margin-top: 0.85rem;
|
margin-top: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tables-context-tab-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.tables-context-mode-tabs {
|
.tables-context-mode-tabs {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tables-context-density-tabs {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
.tables-context-fields {
|
.tables-context-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1452,6 +1463,9 @@ pre,
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
border-top: 1px solid rgba(127, 96, 55, 0.2);
|
border-top: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
border-left: 1px solid rgba(127, 96, 55, 0.2);
|
border-left: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
|
isolation: isolate;
|
||||||
|
--tables-group-header-top: 0;
|
||||||
|
--tables-column-header-top: 3.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table-grid-header-cell {
|
.critical-table-grid-header-cell {
|
||||||
@@ -1474,14 +1488,56 @@ pre,
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-group-header {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--tables-group-header-top);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-column-header {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--tables-column-header-top);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid.is-dense {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
--tables-column-header-top: 2.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid.is-dense .critical-table-grid-header-cell {
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid.is-dense .critical-table-cell {
|
||||||
|
padding: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-table-grid-corner,
|
.critical-table-grid-corner,
|
||||||
.critical-table-grid-roll-band-header {
|
.critical-table-grid-roll-band-header {
|
||||||
background: rgba(255, 247, 230, 0.52);
|
background: rgba(255, 247, 230, 0.52);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-corner {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--tables-group-header-top);
|
||||||
|
left: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-roll-band-header {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--tables-column-header-top);
|
||||||
|
left: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-table-grid-roll-band {
|
.critical-table-grid-roll-band {
|
||||||
background: rgba(255, 247, 230, 0.52);
|
background: rgba(255, 247, 230, 0.52);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table-cell {
|
.critical-table-cell {
|
||||||
@@ -1492,6 +1548,8 @@ pre,
|
|||||||
border-right: 1px solid rgba(127, 96, 55, 0.2);
|
border-right: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
border-bottom: 1px solid rgba(127, 96, 55, 0.2);
|
border-bottom: 1px solid rgba(127, 96, 55, 0.2);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.16s ease, background-color 0.16s ease, transform 0.16s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.critical-table-cell.is-curated {
|
.critical-table-cell.is-curated {
|
||||||
@@ -1506,6 +1564,40 @@ pre,
|
|||||||
rgba(255, 255, 255, 0.85);
|
rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.critical-table-cell.is-active-row,
|
||||||
|
.critical-table-grid-roll-band.is-active-row {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(13, 148, 136, 0.12), rgba(13, 148, 136, 0.04)),
|
||||||
|
rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-column-header.is-active-column,
|
||||||
|
.critical-table-cell.is-active-column {
|
||||||
|
box-shadow: inset 0 0 0 999px rgba(13, 148, 136, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-grid-group-header.is-active-group,
|
||||||
|
.critical-table-grid-column-header.is-active-group,
|
||||||
|
.critical-table-cell.is-active-group {
|
||||||
|
box-shadow: inset 0 0 0 999px rgba(188, 117, 43, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-cell.is-selected-cell {
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(13, 148, 136, 0.62);
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(13, 148, 136, 0.16), transparent 38%),
|
||||||
|
rgba(255, 255, 255, 0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critical-table-cell.is-roll-target::after,
|
||||||
|
.critical-table-grid-roll-band.is-roll-target::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border: 2px dashed rgba(13, 148, 136, 0.35);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-table-cell-shell {
|
.critical-table-cell-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2256,6 +2348,11 @@ pre,
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tables-context-tab-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
.critical-editor-header,
|
.critical-editor-header,
|
||||||
.critical-editor-footer {
|
.critical-editor-footer {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user