List and detail panels

ListViewPage creates a list from a definition and manages its selected and non-selected sidebar content. Give its host a height so the panes can scroll independently. Drag the divider to resize the sidebar; focused dividers also support Left/Right and Home/End. Narrow containers stack the panes.

@using Benevia.Core.Blazor.ListViews
@using Benevia.Core.Blazor.Panels
@using Benevia.Core.Client.GraphDefinitions.Builders

<ListViewPage Definition="customerList" RowUrl="CustomerUrl">
    <NonSelected><MudText Class="pa-4">Choose a customer.</MudText></NonSelected>
    <Selected Context="row">
        <DataGraphPanel Definition="BuildPreview" EntityGuid="row.RowGuid" Outlined="false">
            <Header Context="dataSet">Customer details</Header>
        </DataGraphPanel>
    </Selected>
</ListViewPage>

@code {
    private string CustomerUrl(Guid guid) => $"/customers/{guid}";

    private static readonly ListViewDefinition customerList = new GraphBuilder("Customer")
        .Property("Name").Property("Phone").Property("Email")
        .Build().ToListView().OrderBy("Name asc").Build();

    private static GraphBuilder BuildPreview() => new GraphBuilder("Customer", "Sales.Customer.Preview")
        .Group("Overview", group => group
            .Property("Name").Property("Phone").Property("Email"));
}

Register Core services with AddCoreBlazor() and use your application's entity names and paths. The sample route /panels/list-detail demonstrates customers and sales orders.

Selection and navigation

NonSelected stays mounted behind the Selected content. SelectedHeader adds content beside the toolbar buttons. Close returns to NonSelected but keeps the row active, so Enter still opens it. A button above NonSelected, beside the SelectedHeader, reopens the preview; it takes focus, so Enter reopens it too. ShowPreviewLabel sets its text. Selecting a different row also reopens the preview; a refresh or opening the same row keeps it closed. ClearActiveRowAsync() clears the selection, and ClosePreviewAsync() closes the preview programmatically. Edit appears when the root entity has an updateable, writable field and opens RowUrl. Use AdditionalEditEntities for related entities, OnEdit for custom navigation, or CanEdit to override the default policy. Record-specific read-only rules still apply in editors.

Double-click or Enter invokes OnRowDoubleClick, then falls back to permitted OnEdit or RowUrl. Ctrl/Cmd-click and middle-click on a RowUrl retain native browser behavior. ChildContent supports column and filter templates; ListParameters supplies additional ListView parameters, while page-owned parameters take precedence. Use List for refresh and custom-filter APIs.

Selection is stored in the URL as <ViewKey>-row=<guid> (or row without a key), so reloading and returning from editing restore it. Row changes replace the history entry and preserve other query parameters. Clearing, resetting, or filtering out the row clears selection; closing the preview keeps it. A preserving refresh reloads the selected row. Use distinct ViewKey values on shared pages; StateQueryPrefix overrides the prefix and RemembersState=false disables address persistence.

Selection notifications

ListView.ActiveRowChanged and ListViewPage.ActiveRowChanged are async notification delegates (Func<ListViewActiveRowContext?, Task>), so they do not automatically rerender the parent page. Pass a handler directly, for example ActiveRowChanged="OnActiveRowChanged"; synchronous handlers return Task.CompletedTask. When migrating from EventCallback, remove EventCallback.Factory.Create and EventUtil wrappers. A handler that changes the parent's own visible content should explicitly request that parent's render.

DataGraphPanel

Preview loading waits for a 250 ms quiet period after selection. Rapid row changes load only the final record; clearing the selection or leaving the page during that wait sends no request. Row highlighting and URL selection remain immediate. Requests already started are still cancelled when their preview session is disposed.

DataGraphPanel loads an existing record in DisplayMode.View, retaining DataGraph's loading, error, metadata, feature and permission handling. With no ChildContent, it renders the graph's groups. Header, Actions and ChildContent receive the loaded dataset; EmptyContent replaces the instruction shown when EntityGuid is null. Use PropertyComponent and PropertyGroup for permission-aware fields.

Each selection owns a dataset and transient OData client. Replacing or clearing it disposes the client and cancels reads. An unchanged guid and equivalent definition retain the loaded preview; a changed definition starts a new session. Use stable graph factories and distinct extension IDs for different previews. The panel does not start an edit workflow; use the editing page for saving and navigation protection.

Custom composition

Use ListPanelLayout for a custom list/sidebar layout. When composing ListView directly, use ActiveRowChanged to select content and OnRowDoubleClick to open it. Arrow keys change the active row, Escape clears it, and RowUrl supports native tab navigation. Disable inline editing for preview lists so editable cells keep their own double-click gesture. Without OnRowDoubleClick, existing single-click opening remains unchanged.

PreviewPanelHost preserves its default content while a PreviewPanel covers it. Give each pair the same SectionId; OnClose must remove the preview. The fixed toolbar remains available during loading or errors. Use CloseLabel for its accessible name, Navigation for list/record navigation, and PreviewPanelActions for loaded headings and actions. Actions keep their data graph context.

EntityPanelLayout provides header, actions, scrolling body and optional fixed footer slots. Padded and Outlined default to true; disable them when the surrounding surface or application content supplies those styles.

PreviewActionsMenu provides permission-aware deletion for records, dismisses the preview after success, and refreshes entity lists. Use OnDeleted to override dismissal, ChildContent for other actions, and BusyChanged to disable sibling actions. Editing pages keep their workflow-aware DataGraphPage deletion.

After a dataset command raises questions, await DataGraphContext.WaitForPendingQuestionsAsync(). Continue only when it returns true and the dataset has no remaining error prompts.