Add quick-filter panel slot to MgGridBase

Introduced a flexible quick-filter panel to the grid system via a new FilterPanel slot in MgGridBase. Added interface and implementation for filter panel visibility and toggle logic. Updated toolbar to show a Show/Hide Filters button when applicable. MgTagBox now applies per-field quick-filters using SetFieldQuickFilter. Removed redundant MgGridFilterPanelBase. Updated documentation and ADR 0004 to cover the new feature. Added CSS for consistent filter panel styling and demonstrated usage in GridPartner.razor. Legacy MgGridBase updated for compatibility.
This commit is contained in:
Loretta 2026-07-16 06:28:55 +02:00
parent 4eb0d1566c
commit 2d62fad300
10 changed files with 119 additions and 58 deletions

View File

@ -101,6 +101,15 @@ public interface IMgGridBase : IGrid
/// <paramref name="criteria"/> = null clears this field's filter. See ADR 0004. /// <paramref name="criteria"/> = null clears this field's filter. See ADR 0004.
/// </summary> /// </summary>
void SetFieldQuickFilter(string fieldName, CriteriaOperator? criteria); void SetFieldQuickFilter(string fieldName, CriteriaOperator? criteria);
/// <summary>Whether this grid has a quick-filter panel (i.e. <c>FilterPanel</c> is set).</summary>
bool HasFilterPanel { get; }
/// <summary>Whether the quick-filter panel is currently shown (toggled from the toolbar).</summary>
bool IsFilterPanelVisible { get; }
/// <summary>Shows/hides the quick-filter panel. No-op when the grid has no filter panel.</summary>
void ToggleFilterPanel();
} }
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
@ -251,7 +260,13 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
// Body // Body
contentBuilder.OpenElement(12, "div"); contentBuilder.OpenElement(12, "div");
contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body"); contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body");
if (FilterPanel != null) contentBuilder.AddContent(14, FilterPanel); if (FilterPanel != null && IsFilterPanelVisible)
{
contentBuilder.OpenElement(14, "div");
contentBuilder.AddAttribute(15, "class", "mg-grid-filter-panel");
contentBuilder.AddContent(16, FilterPanel);
contentBuilder.CloseElement();
}
base.BuildRenderTree(contentBuilder); base.BuildRenderTree(contentBuilder);
contentBuilder.CloseElement(); // body div contentBuilder.CloseElement(); // body div
@ -263,7 +278,13 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
contentBuilder.OpenElement(0, "div"); contentBuilder.OpenElement(0, "div");
contentBuilder.SetKey(_gridRenderKey); contentBuilder.SetKey(_gridRenderKey);
contentBuilder.AddAttribute(1, "style", "display: contents;"); contentBuilder.AddAttribute(1, "style", "display: contents;");
if (FilterPanel != null) contentBuilder.AddContent(2, FilterPanel); if (FilterPanel != null && IsFilterPanelVisible)
{
contentBuilder.OpenElement(2, "div");
contentBuilder.AddAttribute(3, "class", "mg-grid-filter-panel");
contentBuilder.AddContent(4, FilterPanel);
contentBuilder.CloseElement();
}
base.BuildRenderTree(contentBuilder); base.BuildRenderTree(contentBuilder);
contentBuilder.CloseElement(); contentBuilder.CloseElement();
} }
@ -294,12 +315,28 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name; [Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
/// <summary> /// <summary>
/// Optional quick-filter panel rendered above the grid (typically a <c>GridFilterPanel</c> hosting /// Optional quick-filter controls (typically <see cref="MgTagBox{TData,TValue}"/>) rendered above the grid
/// <see cref="MgTagBox{TData,TValue}"/> controls). Rendered within this grid's <see cref="IMgGridBase"/> /// inside a styled <c>.mg-grid-filter-panel</c> container (the slot wraps the content). Within this grid's <see cref="IMgGridBase"/>
/// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004. /// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004.
/// </summary> /// </summary>
[Parameter] public RenderFragment? FilterPanel { get; set; } [Parameter] public RenderFragment? FilterPanel { get; set; }
/// <summary>Initial visibility of the <see cref="FilterPanel"/>; the toolbar toggle flips it at runtime. Default: shown.</summary>
[Parameter] public bool ShowFilterPanel { get; set; } = true;
/// <inheritdoc />
public bool HasFilterPanel => FilterPanel != null;
/// <inheritdoc />
public bool IsFilterPanelVisible { get; private set; } = true;
/// <inheritdoc />
public void ToggleFilterPanel()
{
IsFilterPanelVisible = !IsFilterPanelVisible;
InvokeAsync(StateHasChanged);
}
/// <summary> /// <summary>
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}" /// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
/// </summary> /// </summary>
@ -406,6 +443,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
if (Logger == null) if (Logger == null)
throw new NullReferenceException($"[{GetType().Name}] Logger == null"); throw new NullReferenceException($"[{GetType().Name}] Logger == null");
IsFilterPanelVisible = ShowFilterPanel;
if (SignalRClient == null) if (SignalRClient == null)
{ {
Logger.Error($"[{GetType().Name}] SignalRClient == null"); Logger.Error($"[{GetType().Name}] SignalRClient == null");

View File

@ -1,32 +0,0 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace AyCode.Blazor.Components.Components.Grids;
/// <summary>
/// Base for a grid quick-filter panel — a styled container placed in a grid's <c>FilterPanel</c> slot,
/// hosting <see cref="MgTagBox{TData,TValue}"/> (or other) per-field quick-filter controls above the grid.
/// <para>
/// Framework base. Projects derive a (typically empty) <c>GridFilterPanel</c> per the Mg* seam policy
/// (cf. <c>MgGridBase</c> → project grid). The grid reference reaches the child controls via the grid's own
/// <see cref="IMgGridBase"/> cascade (<c>MgGridBase</c> provides it) — this panel is layout only, no wiring.
/// </para>
/// </summary>
public class MgGridFilterPanelBase : ComponentBase
{
/// <summary>The quick-filter controls (e.g. <see cref="MgTagBox{TData,TValue}"/>) to render in the panel.</summary>
[Parameter] public RenderFragment? ChildContent { get; set; }
/// <summary>Extra CSS classes appended to the panel container.</summary>
[Parameter] public string? CssClass { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", string.IsNullOrWhiteSpace(CssClass)
? "mg-grid-filter-panel"
: $"mg-grid-filter-panel {CssClass}");
builder.AddContent(2, ChildContent);
builder.CloseElement();
}
}

View File

@ -30,6 +30,7 @@
</Items> </Items>
</DxToolbarItem> </DxToolbarItem>
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Reload data")" BeginGroup="true" Click="ReloadData_Click" IconCssClass="grid-refresh" Enabled="@(!IsSyncing && !_isReloadInProgress && !IsEditing)" /> <DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Reload data")" BeginGroup="true" Click="ReloadData_Click" IconCssClass="grid-refresh" Enabled="@(!IsSyncing && !_isReloadInProgress && !IsEditing)" />
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : FilterPanelButtonText)" Click="FilterPanel_Click" IconCssClass="grid-filter" Visible="@Grid.HasFilterPanel" Enabled="@(!IsEditing)" />
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : FullscreenButtonText)" Click="Fullscreen_Click" IconCssClass="@FullscreenIconCssClass" Enabled="@(!IsEditing)" /> <DxToolbarItem Text="@(ShowOnlyIcon ? "" : FullscreenButtonText)" Click="Fullscreen_Click" IconCssClass="@FullscreenIconCssClass" Enabled="@(!IsEditing)" />
@ToolbarItemsExtended @ToolbarItemsExtended
} }
@ -82,6 +83,16 @@
/// </summary> /// </summary>
private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen"; private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen";
/// <summary>
/// Whether the quick-filter panel is currently shown
/// </summary>
private bool IsFilterPanelVisible => Grid?.IsFilterPanelVisible ?? false;
/// <summary>
/// Button text for the filter-panel toggle
/// </summary>
private string FilterPanelButtonText => IsFilterPanelVisible ? "Hide Filters" : "Show Filters";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_hasUserLayout = await Grid.HasUserLayoutAsync(); _hasUserLayout = await Grid.HasUserLayoutAsync();
@ -145,6 +156,11 @@
Grid.ToggleFullscreen(); Grid.ToggleFullscreen();
} }
void FilterPanel_Click()
{
Grid.ToggleFilterPanel();
}
async Task ExportXlsxItem_Click() async Task ExportXlsxItem_Click()
{ {
await Grid.ExportToXlsxAsync(ExportFileName); await Grid.ExportToXlsxAsync(ExportFileName);

View File

@ -4,10 +4,10 @@ Core grid system built on DevExpress `DxGrid`. For the full technical reference
## Key Files ## Key Files
- **`MgGridBase.cs`** — `MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle. - **`MgGridBase.cs`** — `MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>`, the main abstract grid component. Extends `DxGrid` with SignalR CRUD, layout persistence, master-detail hierarchy, edit state tracking, fullscreen toggle, and a quick-filter `FilterPanel` slot (renders `MgTagBox` controls above the grid in a styled `.mg-grid-filter-panel` container; toolbar Show/Hide toggle).
- **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence. - **`MgGridWithInfoPanel.razor`** — `DxSplitter` wrapper: grid (left) + InfoPanel (right), fullscreen overlay, splitter size persistence.
- **`MgGridToolbarBase.cs`** — Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters. - **`MgGridToolbarBase.cs`** — Extends `DxToolbar` with `Grid`, `RefreshClick`, and `ShowOnlyIcon` parameters.
- **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, fullscreen. Extensible via `ToolbarItemsExtended`. - **`MgGridToolbarTemplate.razor`** — Full toolbar template: New/Edit/Delete/Save/Cancel, row navigation, layout menu (Load/Save/Reset), column chooser, export, reload, filter-panel toggle (visible only when the grid has a `FilterPanel`), fullscreen. Extensible via `ToolbarItemsExtended`.
- **`MgGridDataColumn.cs`** — Extends `DxGridDataColumn` with InfoPanel parameters (`ShowInInfoPanel`, `InfoPanelOrder`, `InfoPanelDisplayFormat`) and `UrlLink` template with `{Property}` placeholder substitution via compiled accessors. - **`MgGridDataColumn.cs`** — Extends `DxGridDataColumn` with InfoPanel parameters (`ShowInInfoPanel`, `InfoPanelOrder`, `InfoPanelDisplayFormat`) and `UrlLink` template with `{Property}` placeholder substitution via compiled accessors.
- **`MgGridInfoPanel.razor`** / **`.razor.cs`** — `MgGridInfoPanel` implementing `IInfoPanelBase`. Responsive column layout (1-4 columns with breakpoints), edit/view mode with typed editors, template system, sticky positioning via JS interop. - **`MgGridInfoPanel.razor`** / **`.razor.cs`** — `MgGridInfoPanel` implementing `IInfoPanelBase`. Responsive column layout (1-4 columns with breakpoints), edit/view mode with typed editors, template system, sticky positioning via JS interop.
- **`MgGridSignalRDataSource.cs`** — `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache for seen filter criteria, background refresh. - **`MgGridSignalRDataSource.cs`** — `GridCustomDataSource` wrapping `AcSignalRDataSource`. Local cache for seen filter criteria, background refresh.

View File

@ -34,32 +34,31 @@ public class MgTagBox<TData, TValue> : DxTagBox<TData, TValue>
/// </summary> /// </summary>
[CascadingParameter] public IMgGridBase? Grid { get; set; } [CascadingParameter] public IMgGridBase? Grid { get; set; }
private bool _initialized;
private IEnumerable<TValue>? _selected;
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
// Own the selection internally so the consumer needs no @bind-Values. // Route the tag box's selection through our handler so a change applies the grid filter.
// COMPILE-VERIFY (DevExpress version specific): Blazor re-applies base.Values / base.ValuesChanged from the // (The consumer does not bind Values; the selection is owned internally — see OnSelectionChanged.)
// consumer markup on every parameter set, so we re-assert ours here each time. First set adopts any initial Values.
if (!_initialized)
{
_selected = Values;
_initialized = true;
}
base.Values = _selected;
base.ValuesChanged = EventCallback.Factory.Create<IEnumerable<TValue>>(this, OnSelectionChanged); base.ValuesChanged = EventCallback.Factory.Create<IEnumerable<TValue>>(this, OnSelectionChanged);
} }
private void OnSelectionChanged(IEnumerable<TValue>? values) private void OnSelectionChanged(IEnumerable<TValue>? values)
{ {
_selected = values; // DevExpress tracks its parameters; setting Values imperatively (outside markup, e.g. from this event
base.Values = values; // reflect the new selection in the editor // callback) must be wrapped in BeginUpdate/EndUpdate — otherwise DevExpress throws
// "A parameter value is specified outside a component's markup."
BeginUpdate();
try
{
Values = values;
}
finally
{
EndUpdate();
}
ApplyQuickFilter(values); ApplyQuickFilter(values);
StateHasChanged();
} }
private void ApplyQuickFilter(IEnumerable<TValue>? values) private void ApplyQuickFilter(IEnumerable<TValue>? values)
@ -68,8 +67,7 @@ public class MgTagBox<TData, TValue> : DxTagBox<TData, TValue>
var selected = values?.Where(v => v is not null).Cast<object>().ToArray() ?? []; var selected = values?.Where(v => v is not null).Cast<object>().ToArray() ?? [];
// COMPILE-VERIFY: confirm the InOperator(string, IEnumerable / params object[]) overload on your // Empty selection => null => clears this field's filter. Otherwise IN(FieldName, selected...).
// DevExpress.Data version. Empty selection => null => clears this field's filter.
CriteriaOperator? criteria = selected.Length > 0 ? new InOperator(FieldName, selected) : null; CriteriaOperator? criteria = selected.Length > 0 ? new InOperator(FieldName, selected) : null;
Grid.SetFieldQuickFilter(FieldName, criteria); Grid.SetFieldQuickFilter(FieldName, criteria);

View File

@ -1,6 +1,6 @@
# Components # Components
DevExpress component wrappers and grid infrastructure for the AyCode Blazor component library. Each `Ac*` class extends a DevExpress Blazor control to allow project-wide customization from a single point. DevExpress component wrappers and grid infrastructure for the AyCode Blazor component library. Each wrapper class extends a DevExpress Blazor control to allow project-wide customization from a single point (`Mg*` = current prefix; `Ac*` = legacy prefix, kept as-is).
## Key Files ## Key Files
@ -13,6 +13,7 @@ DevExpress component wrappers and grid infrastructure for the AyCode Blazor comp
- **`AcMaskedInput.cs`** -- Generic wrapper for `DxMaskedInput<T>`. - **`AcMaskedInput.cs`** -- Generic wrapper for `DxMaskedInput<T>`.
- **`AcMemo.cs`** -- Extends `DxMemo`. - **`AcMemo.cs`** -- Extends `DxMemo`.
- **`AcSpinEdit.cs`** -- Generic wrapper for `DxSpinEdit<T>`. - **`AcSpinEdit.cs`** -- Generic wrapper for `DxSpinEdit<T>`.
- **`MgTagBox.cs`** -- Generic wrapper for `DxTagBox<TData, TValue>` with grid quick-filter support: a `FieldName` parameter + the grid cascade let a selection apply a per-field `IN` filter via `IMgGridBase.SetFieldQuickFilter` (see `Grids/` and `docs/MGGRID/MGGRID_PARAMETERS.md` → Quick-Filter Panel).
- **`MgComponentsHelper.cs`** -- Placeholder helper class (currently empty). - **`MgComponentsHelper.cs`** -- Placeholder helper class (currently empty).
## Subfolders ## Subfolders

View File

@ -41,6 +41,26 @@ These are bundled into a `SignalRCrudTags` during `OnInitializedAsync`. See `AyC
| `AutoSaveLayoutName` | `string?` | `"Grid{TDataItem.Name}"` | Base name for layout storage keys | | `AutoSaveLayoutName` | `string?` | `"Grid{TDataItem.Name}"` | Base name for layout storage keys |
| `EnableServerLayouts` | `bool` | `true` | Roams the UserSave layout tier via the SignalR server endpoint (see `MGGRID_LAYOUT.md` → Server Roaming). Server calls happen only on the Save/Load layout buttons. | | `EnableServerLayouts` | `bool` | `true` | Roams the UserSave layout tier via the SignalR server endpoint (see `MGGRID_LAYOUT.md` → Server Roaming). Server calls happen only on the Save/Load layout buttons. |
## Quick-Filter Panel
| Parameter | Type | Default | Description |
|---|---|---|---|
| `FilterPanel` | `RenderFragment?` | `null` | Quick-filter controls rendered above the grid; the slot wraps them in a styled `.mg-grid-filter-panel` container. When set, the toolbar shows a Show/Hide Filters toggle (`MGGRID_TOOLBAR.md`). |
| `ShowFilterPanel` | `bool` | `true` | Initial visibility of the filter panel; the toolbar toggle flips it at runtime (`IsFilterPanelVisible` / `ToggleFilterPanel`). |
Place `MgTagBox` controls (from `Components/`) in the slot. Each control applies a per-field `IN(FieldName, selected...)` criteria via `IMgGridBase.SetFieldQuickFilter` → DevExpress `SetFieldFilterCriteria`; DevExpress AND-combines the per-field criteria with each other and with the user's filter row automatically (no clobber). Empty selection clears that field's filter.
```razor
<FilterPanel>
<MgTagBox TData="string" TValue="string" FieldName="Status"
Data="@_statusOptions" NullText="All statuses" />
</FilterPanel>
```
Implementation note: imperative `Values` writes inside DevExpress editors must be wrapped in `BeginUpdate()`/`EndUpdate()` (the parameter tracker throws otherwise) — `MgTagBox` handles this internally.
Design + the server-side forward path (`FilterMode.Server`): AyCode.Core repo `docs/adr/0004-signalr-datasource-server-side-filtering.md`.
## Event Callbacks ## Event Callbacks
All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `DxGrid` base events: All grid events are re-exposed with `OnGrid` prefix to avoid collisions with `DxGrid` base events:

View File

@ -15,6 +15,7 @@ The standard toolbar rendered inside grid's `ToolbarTemplate`. Provides all stan
| **Navigation** | Prev Row, Next Row | When NOT editing | | **Navigation** | Prev Row, Next Row | When NOT editing |
| **Layout** | Column Chooser, Layout (Load/Save/Reset) | When `OnlyGridEditTools=false` | | **Layout** | Column Chooser, Layout (Load/Save/Reset) | When `OnlyGridEditTools=false` |
| **Actions** | Export (CSV/XLSX/XLS/PDF), Reload Data, Fullscreen | When `OnlyGridEditTools=false` | | **Actions** | Export (CSV/XLSX/XLS/PDF), Reload Data, Fullscreen | When `OnlyGridEditTools=false` |
| **Quick-filter** | Show Filters / Hide Filters (toggles the `FilterPanel`) | When `OnlyGridEditTools=false` AND `Grid.HasFilterPanel` |
### Parameters ### Parameters
@ -37,3 +38,4 @@ The standard toolbar rendered inside grid's `ToolbarTemplate`. Provides all stan
| `IsSyncing` | `Grid.IsSyncing` | | `IsSyncing` | `Grid.IsSyncing` |
| `HasFocusedRow` | `Grid.GetFocusedRowIndex() >= 0` | | `HasFocusedRow` | `Grid.GetFocusedRowIndex() >= 0` |
| `IsFullscreenMode` | `Grid.IsFullscreen` | | `IsFullscreenMode` | `Grid.IsFullscreen` |
| `IsFilterPanelVisible` | `Grid.IsFilterPanelVisible` |

View File

@ -14,12 +14,13 @@
- **InfoPanel integration** — side panel shows focused row details, supports edit mode - **InfoPanel integration** — side panel shows focused row details, supports edit mode
- **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper - **Fullscreen mode** — standalone overlay or via `MgGridWithInfoPanel` wrapper
- **Change tracking** — client-side tracking with server sync via `SaveChangesAsync` - **Change tracking** — client-side tracking with server sync via `SaveChangesAsync`
- **Quick-filter panel**`FilterPanel` slot above the grid hosting `MgTagBox` controls; per-field criteria AND-combined by DevExpress with the user's filter row; toolbar Show/Hide toggle (see `MGGRID_PARAMETERS.md` → Quick-Filter Panel; design: AyCode.Core repo `docs/adr/0004-signalr-datasource-server-side-filtering.md`)
## Detailed Documentation ## Detailed Documentation
| File | Topics | | File | Topics |
|---|---| |---|---|
| `MGGRID_PARAMETERS.md` | Component parameters (required, CRUD tags, data & context, display), event callbacks, default grid settings | | `MGGRID_PARAMETERS.md` | Component parameters (required, CRUD tags, data & context, display), quick-filter panel (`FilterPanel` slot + `MgTagBox`), event callbacks, default grid settings |
| `MGGRID_CRUD.md` | Lifecycle, CRUD operations, ID generation, edit flow, disposal | | `MGGRID_CRUD.md` | Lifecycle, CRUD operations, ID generation, edit flow, disposal |
| `MGGRID_LAYOUT.md` | Layout persistence (storage keys, three tiers, operations, persisted state) | | `MGGRID_LAYOUT.md` | Layout persistence (storage keys, three tiers, operations, persisted state) |
| `MGGRID_DETAIL.md` | Master-detail hierarchy | | `MGGRID_DETAIL.md` | Master-detail hierarchy |
@ -96,6 +97,10 @@ The public contract exposed to companion components (toolbar, InfoPanel, wrapper
| `IsFullscreen` | `bool` | Current fullscreen state | | `IsFullscreen` | `bool` | Current fullscreen state |
| `AutomaticLayoutStorageKey` | `string` | Current auto-save storage key | | `AutomaticLayoutStorageKey` | `string` | Current auto-save storage key |
| `ToggleFullscreen()` | `void` | Toggle fullscreen mode | | `ToggleFullscreen()` | `void` | Toggle fullscreen mode |
| `SetFieldQuickFilter(fieldName, criteria)` | `void` | Apply/clear a per-field quick-filter (delegates to DevExpress `SetFieldFilterCriteria`; `null` criteria clears the field) |
| `HasFilterPanel` | `bool` | Whether the grid has a `FilterPanel` (drives toolbar-toggle visibility) |
| `IsFilterPanelVisible` | `bool` | Current quick-filter panel visibility |
| `ToggleFilterPanel()` | `void` | Show/hide the quick-filter panel |
| `SaveUserLayoutAsync()` | `Task` | Save layout manually | | `SaveUserLayoutAsync()` | `Task` | Save layout manually |
| `LoadUserLayoutAsync()` | `Task` | Load manually saved layout | | `LoadUserLayoutAsync()` | `Task` | Load manually saved layout |
| `ResetLayoutAsync()` | `Task` | Reset to default layout | | `ResetLayoutAsync()` | `Task` | Reset to default layout |

View File

@ -5,6 +5,18 @@
These are defined on .dxbl-theme-fluent class and inherited by child elements. These are defined on .dxbl-theme-fluent class and inherited by child elements.
*/ */
/* Grid quick-filter panel (rendered above the grid; hosts MgTagBox quick-filter controls) */
.mg-grid-filter-panel {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--DS-sizing-80, 0.5rem);
padding: var(--DS-sizing-80, 0.5rem) var(--DS-sizing-160, 1rem);
background-color: var(--DS-color-surface-neutral-subdued-rest, #f8f9fa);
border: 1px solid var(--DS-color-border-neutral-default-rest, #dee2e6);
border-bottom: none;
}
/* Main panel - uses DevExpress Design System variables */ /* Main panel - uses DevExpress Design System variables */
.mg-grid-info-panel { .mg-grid-info-panel {
background-color: var(--DS-color-surface-neutral-subdued-rest, #f8f9fa); background-color: var(--DS-color-surface-neutral-subdued-rest, #f8f9fa);