Shell Plugin
The Shell plugin wraps the grid with an optional header bar (title + toolbar) and a
collapsible tool panel sidebar. Features like visibility, filtering, and pivot
register their tool panels into this shell.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/shell';The feature import registers the ShellPlugin and augments features with a typed shell
option. Shell configuration types (ShellConfig, ToolPanelDefinition, …) are exported from
@toolbox-web/grid/plugins/shell.
Basic Usage
Section titled “Basic Usage”Enable the shell by setting features: { shell } with a header.title. Features like
visibility automatically register a tool panel when the shell is active.
import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/shell';import '@toolbox-web/grid/features/visibility';
const grid = queryGrid('tbw-grid');grid.gridConfig = { features: { shell: { header: { title: 'Employee Data' } }, visibility: true, },};import '@toolbox-web/grid-react/features/visibility';import { DataGrid } from '@toolbox-web/grid-react';
// Shell auto-registers in v2.x. For v3, opt in explicitly:// import '@toolbox-web/grid-react/features/shell';const config = { shell: { header: { title: 'Employee Data' } } };
function MyGrid({ data }) { return <DataGrid rows={data} gridConfig={config} visibility />;}<script setup lang="ts">import '@toolbox-web/grid-vue/features/visibility';import { TbwGrid } from '@toolbox-web/grid-vue';
// Shell auto-registers in v2.x. For v3, opt in explicitly:// import '@toolbox-web/grid-vue/features/shell';const config = { shell: { header: { title: 'Employee Data' } } };</script>
<template> <TbwGrid :rows="data" :grid-config="config" visibility /></template>// Enables the [visibility] input; the shell auto-registers in v2.x.// For v3, opt in explicitly: import '@toolbox-web/grid-angular/features/shell';import { GridVisibilityDirective } from '@toolbox-web/grid-angular/features/visibility';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { GridConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-shell-grid', imports: [Grid, GridVisibilityDirective], template: ` <tbw-grid [rows]="rows" [gridConfig]="config" [visibility]="true" style="height: 400px; display: block;"> </tbw-grid> `,})export class ShellGridComponent { rows = []; config: GridConfig = { shell: { header: { title: 'Employee Data' } } };}<tbw-grid style="height: 420px;"></tbw-grid> import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/shell';import '@toolbox-web/grid/features/visibility';
const grid = queryGrid('tbw-grid');
if (container && grid) { const departments = ['Engineering', 'Sales', 'Marketing', 'Support']; const names = ['Alice', 'Bob', 'Carol', 'Dan', 'Eve', 'Frank', 'Grace', 'Henry']; function generateRows(count: number) { return Array.from({ length: count }, (_, i) => ({ id: i + 1, name: names[i % names.length] + ' ' + (Math.floor(i / names.length) + 1), department: departments[i % departments.length], salary: 50000 + Math.floor(Math.random() * 50000), active: i % 3 !== 0, })); }
const shellColumns: ColumnConfig[] = [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name', minWidth: 150 }, { field: 'department', header: 'Department', width: 150 }, { field: 'salary', header: 'Salary', type: 'number', width: 120 }, { field: 'active', header: 'Active', type: 'boolean', width: 80 }, ]; const sampleData = generateRows(20);
interface ShellValues { showTitle: boolean; showHeaderContent: boolean; showToolbarButton: boolean; showVisibilityPlugin: boolean; showCustomPanel: boolean; panelPosition: string; panelMode: string; }
function rebuild(v: ShellValues) { const shellConfig: ShellConfig = { header: v.showTitle ? { title: 'Employee Data' } : {}, toolPanel: { position: v.panelPosition, mode: v.panelMode, }, }; grid.gridConfig = { columns: shellColumns, features: { shell: shellConfig, ...(v.showVisibilityPlugin ? { visibility: true } : {}), }, }; grid.rows = sampleData;
const shell = grid.getPluginByName('shell'); if (v.showHeaderContent) { shell?.registerHeaderContent({ id: 'row-count', order: 10, render: (el) => { const span = document.createElement('span'); span.style.cssText = 'font-size:13px;color:var(--sl-color-gray-3);padding:4px 8px;background:var(--sl-color-gray-6);border-radius:4px;'; const update = () => { span.textContent = `${grid.rows.length} rows`; }; const unsub = grid.on('data-change', update); el.appendChild(span); return () => { unsub(); span.remove(); }; }, }); } else { shell?.unregisterHeaderContent('row-count'); }
if (v.showCustomPanel) { shell?.registerToolPanel({ id: 'custom-info', title: 'Info Panel', icon: 'ℹ', tooltip: 'Info Panel', render: (el) => { el.innerHTML = '<div style="padding:16px;"><h4 style="margin:0 0 8px;">Custom Panel</h4><p style="margin:0;font-size:13px;">This panel was added via registerToolPanel().</p></div>'; return () => { el.innerHTML = ''; }; }, }); } else { shell?.unregisterToolPanel('custom-info'); }
if (v.showToolbarButton) { shell?.registerToolbarContent({ id: 'refresh-btn', render: (el) => { const btn = document.createElement('button'); btn.className = 'tbw-toolbar-btn'; btn.title = 'Refresh Data'; btn.setAttribute('aria-label', 'Refresh Data'); btn.textContent = '↻'; btn.addEventListener('click', () => { grid.rows = generateRows(20); }); el.appendChild(btn); return () => btn.remove(); }, }); } else { shell?.unregisterToolbarContent('refresh-btn'); } }
rebuild({ showTitle: true, showHeaderContent: true, showToolbarButton: true, showVisibilityPlugin: true, showCustomPanel: false, panelPosition: 'right', panelMode: 'overlay', });
container.addEventListener('control-change', ((e: CustomEvent) => { rebuild(e.detail.allValues); })); }Light DOM Configuration
Section titled “Light DOM Configuration”Configure the shell declaratively using <tbw-grid-header> and <tbw-grid-header-content>
elements. Still import '@toolbox-web/grid/features/shell' so the plugin is present:
<tbw-grid> <tbw-grid-header title="Employee Directory"> <tbw-grid-header-content> <span id="row-count">0 employees</span> </tbw-grid-header-content> </tbw-grid-header></tbw-grid>
Shell configured via <tbw-grid-header> light DOM elements — no JavaScript shell config needed.
import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/shell';
const grid = queryGrid('tbw-grid'); const departments = ['Engineering', 'Sales', 'Marketing', 'Support']; const names = ['Alice', 'Bob', 'Carol', 'Dan', 'Eve', 'Frank', 'Grace', 'Henry'];
grid.columns = [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name', minWidth: 150 }, { field: 'department', header: 'Department', width: 150 }, { field: 'salary', header: 'Salary', type: 'number', width: 120 }, { field: 'active', header: 'Active', type: 'boolean', width: 80 }, ];
grid.rows = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, name: names[i % names.length] + ' ' + (Math.floor(i / names.length) + 1), department: departments[i % departments.length], salary: 50000 + Math.floor(Math.random() * 50000), active: i % 3 !== 0, }));<tbw-grid style="height: 420px;"> <tbw-grid-header title="Employee Directory"> <tbw-grid-header-content> <span style="color: var(--sl-color-gray-3); font-size: 13px; padding: 4px 8px; background: var(--sl-color-gray-6); border-radius: 4px;">20 employees</span> </tbw-grid-header-content> </tbw-grid-header> </tbw-grid>Multiple Tool Panels
Section titled “Multiple Tool Panels”Register multiple tool panels — each gets a tab in the sidebar. Panels can be registered via
configuration, the framework wrapper components, or the plugin’s runtime registerToolPanel()
API. Each panel requires a title.
import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');shell?.registerToolPanel({ id: 'filter-panel', title: 'Filters', icon: '🔍', render(container) { container.innerHTML = '<p>Filter controls here</p>'; },});import { DataGrid, GridToolPanel } from '@toolbox-web/grid-react';
function MyGrid({ rows }) { return ( <DataGrid rows={rows} gridConfig={config}> <GridToolPanel id="filter-panel" title="Filters" icon="🔍"> {({ grid }) => <p>Filter controls here</p>} </GridToolPanel> </DataGrid> );}<script setup lang="ts">import { TbwGrid, TbwGridToolPanel } from '@toolbox-web/grid-vue';</script>
<template> <TbwGrid :rows="rows" :grid-config="config"> <TbwGridToolPanel id="filter-panel" label="Filters" icon="🔍"> <template #default="{ grid }"> <p>Filter controls here</p> </template> </TbwGridToolPanel> </TbwGrid></template><tbw-grid [rows]="rows" [gridConfig]="config"> <tbw-grid-tool-panel id="filter-panel" title="Filters" icon="🔍"> <ng-template> <p>Filter controls here</p> </ng-template> </tbw-grid-tool-panel></tbw-grid>Multiple tool panels (Columns, Filter, Settings). Click toolbar icons to toggle each.
<tbw-grid style="height: 420px;"></tbw-grid> import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/shell';import '@toolbox-web/grid/features/visibility';
const grid = queryGrid('tbw-grid');
if (grid) { const departments = ['Engineering', 'Sales', 'Marketing', 'Support']; const names = ['Alice', 'Bob', 'Carol', 'Dan', 'Eve', 'Frank', 'Grace', 'Henry'];
grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name', minWidth: 150 }, { field: 'department', header: 'Department', width: 150 }, { field: 'salary', header: 'Salary', type: 'number', width: 120 }, { field: 'active', header: 'Active', type: 'boolean', width: 80 }, ], features: { shell: { header: { title: 'Multi-Panel Demo' } }, visibility: true, }, };
grid.rows = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, name: names[i % names.length] + ' ' + (Math.floor(i / names.length) + 1), department: departments[i % departments.length], salary: 50000 + Math.floor(Math.random() * 50000), active: i % 3 !== 0, }));
const shell = grid.getPluginByName('shell');
// Custom filter panel shell?.registerToolPanel({ id: 'filter', title: 'Filter', icon: '🔍', tooltip: 'Filter data', order: 20, render: (el) => { el.innerHTML = ` <div style="padding:0.75rem;"> <div style="margin-bottom:16px;"> <label style="display:block;margin-bottom:4px;font-size:12px;color:var(--sl-color-gray-3);">Name contains</label> <input type="text" placeholder="Search..." style="width:100%;padding:6px 8px;border:1px solid var(--sl-color-gray-5);border-radius:4px;box-sizing:border-box;background:var(--sl-color-gray-6);color:var(--sl-color-text);" /> </div> <div style="margin-bottom:16px;"> <label style="display:block;margin-bottom:4px;font-size:12px;color:var(--sl-color-gray-3);">Department</label> <select style="width:100%;padding:6px 8px;border:1px solid var(--sl-color-gray-5);border-radius:4px;box-sizing:border-box;background:var(--sl-color-gray-6);color:var(--sl-color-text);"> <option value="">All</option> <option value="Engineering">Engineering</option> <option value="Sales">Sales</option> <option value="Marketing">Marketing</option> <option value="Support">Support</option> </select> </div> </div> `; return () => { el.innerHTML = ''; }; }, });
// Custom settings panel shell?.registerToolPanel({ id: 'settings', title: 'Settings', icon: '⚙', tooltip: 'Grid settings', order: 50, render: (el) => { el.innerHTML = ` <div style="padding:0.75rem;"> <label style="display:flex;align-items:center;gap:8px;margin-bottom:12px;"><input type="checkbox" checked /><span>Row hover effect</span></label> <label style="display:flex;align-items:center;gap:8px;margin-bottom:12px;"><input type="checkbox" checked /><span>Alternating row colors</span></label> <label style="display:flex;align-items:center;gap:8px;"><input type="checkbox" /><span>Compact mode</span></label> </div> `; return () => { el.innerHTML = ''; }; }, }); }Toolbar Buttons
Section titled “Toolbar Buttons”Add custom buttons to the shell toolbar using framework wrapper components or light-DOM
<tbw-grid-tool-buttons>:
<tbw-grid> <tbw-grid-tool-buttons> <button onclick="exportData()">📥 Export</button> <button onclick="printGrid()">🖨️ Print</button> </tbw-grid-tool-buttons></tbw-grid>import { DataGrid, GridToolButtons } from '@toolbox-web/grid-react';
function MyGrid({ rows }) { return ( <DataGrid rows={rows} gridConfig={config}> <GridToolButtons> <button onClick={exportData}>📥 Export</button> <button onClick={printGrid}>🖨️ Print</button> </GridToolButtons> </DataGrid> );}<script setup lang="ts">import { TbwGrid, TbwGridToolButtons } from '@toolbox-web/grid-vue';</script>
<template> <TbwGrid :rows="rows" :grid-config="config"> <TbwGridToolButtons> <button @click="exportData">📥 Export</button> <button @click="printGrid">🖨️ Print</button> </TbwGridToolButtons> </TbwGrid></template><tbw-grid [rows]="rows" [gridConfig]="config"> <tbw-grid-tool-buttons> <button (click)="exportData()">📥 Export</button> <button (click)="printGrid()">🖨️ Print</button> </tbw-grid-tool-buttons></tbw-grid>Custom toolbar buttons via light DOM. The grid provides the container — you provide the HTML.
import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/shell';
const grid = queryGrid('tbw-grid');
if (grid && wrap) { const names = ['Alice', 'Bob', 'Carol', 'Dan', 'Eve', 'Frank', 'Grace', 'Henry']; const depts = ['Engineering', 'Sales', 'Marketing', 'Support'];
grid.columns = [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name', minWidth: 150 }, { field: 'department', header: 'Department', width: 150 }, { field: 'salary', header: 'Salary', type: 'number', width: 120 }, ];
grid.rows = Array.from({ length: 15 }, (_, i) => ({ id: i + 1, name: names[i % names.length] + ' ' + (Math.floor(i / names.length) + 1), department: depts[i % depts.length], salary: 50000 + Math.floor(Math.random() * 50000), }));
const exportBtn = document.querySelector('[title="Export"]'); exportBtn?.addEventListener('click', () => alert('Export clicked!'));
const printBtn = document.querySelector('[title="Print"]'); printBtn?.addEventListener('click', () => alert('Print clicked!')); }<tbw-grid style="height: 380px;"> <tbw-grid-header title="Toolbar Demo"></tbw-grid-header> <tbw-grid-tool-buttons> <button class="tbw-toolbar-btn" title="Export" aria-label="Export">📥</button> <button class="tbw-toolbar-btn" title="Print" aria-label="Print">🖨️</button> </tbw-grid-tool-buttons> </tbw-grid>Shell Configuration Reference
Section titled “Shell Configuration Reference”All options live under features.shell and are typed by ShellConfig
(from @toolbox-web/grid/plugins/shell):
| Option | Type | Default | Description |
|---|---|---|---|
header.title | string | — | Title text in the header bar |
header.visible | boolean | true | Render the header bar. Set false to suppress the entire .tbw-shell-header and own all chrome yourself |
header.toolPanelToggle | boolean | true | Render the built-in panel toggle button + separator |
toolPanel.position | 'left' | 'right' | 'right' | Sidebar position |
toolPanel.width | number | 280 | Sidebar width in pixels |
toolPanel.defaultOpen | string | — | Accordion section to auto-expand on first open. Deprecated: in v2.x this also opens the sidebar; in v3.0.0 it will only pre-select the section (see #259). Combine with initialState: 'open' for forward-compatible behavior. |
toolPanel.initialState | 'open' | 'closed' | 'closed' | Whether the sidebar starts open on grid load. Overrides the legacy defaultOpen open-on-load behavior when set. |
toolPanel.locked | boolean | false | Lock the sidebar open. Implies initialState: 'open', makes closeToolPanel() a no-op, and hides the built-in toggle button. |
toolPanel.persistState | boolean | false | Remember open/closed state |
toolPanel.closeOnClickOutside | boolean | false | Close on outside click (ignored in mode: 'push' or when locked: true; 'dropdown' always light-dismisses) |
toolPanel.mode | 'overlay' | 'push' | 'dropdown' | 'overlay' | 'overlay' floats over the grid; 'push' reflows the grid sideways so all cells stay visible; 'dropdown' renders the whole sidebar as an anchored popover |
Tool-Panel Layout Modes
Section titled “Tool-Panel Layout Modes”The tool panel supports three layout modes — pick whichever fits the viewport you’re targeting.
'overlay'(default): the panel is positioned over the grid content. Opening it does not change the grid’s available width. Best for narrow viewports where shrinking the grid would leave too little room for the data.'push': the panel is laid out as a sibling of the grid content in a flex row. Opening it shrinks the grid’s available width; the grid’sResizeObserverre-runs column virtualization, so cells that an overlay panel would hide remain reachable. Best for desktop layouts.'dropdown': the entire sidebar (the full accordion) renders as an anchored popover built on the native Popover API. It floats above all other content in the top layer, is dismissed onEscapeor click-outside, and is positioned via CSS anchor positioning where supported (with a JS bounding-rect fallback). The anchor is resolved by priority: an explicitanchorpassed toopenToolPanel()→ the built-in toggle button → the grid corner. Best for compact toolbars and bring-your-own trigger buttons (e.g. a “columns” button in a custom column header).
import '@toolbox-web/grid/features/shell';
grid.gridConfig = { features: { shell: { toolPanel: { position: 'right', mode: 'push', // grid reflows when the panel opens/closes }, }, },};In 'push' mode the user-facing resize handle is hidden (the panel’s width is part of the layout, so drag-to-resize would fight the flex container) and closeOnClickOutside is treated as a no-op (there is no overlap to dismiss against). Drive the width via the --tbw-tool-panel-width CSS variable instead.
Dropdown mode + a custom column-header trigger
Section titled “Dropdown mode + a custom column-header trigger”'dropdown' mode shines when you want the column chooser to hang off your own button rather than the built-in toolbar toggle. Pass that button as the anchor when you open the panel, and the popover positions itself directly below it:
const shell = grid.getPluginByName('shell');// Mark your trigger so the popover can re-find it after a structural re-render.columnsButton.setAttribute('data-panel-toggle', '');columnsButton.addEventListener('click', () => { shell?.openToolPanel('columns', { anchor: columnsButton });});The demo below renders a utility-type column whose header hosts a custom ▥ button. Clicking it opens the full tool panel as a dropdown anchored to that button — no toolbar required.
mode: 'dropdown' renders the full tool panel as an anchored popover.
Here it is opened from a custom ▥ button in the header of a utility
column — a bring-your-own trigger anchored via openToolPanel('columns', { anchor }).
In 'dropdown' mode the resize handle is hidden, the popover is capped to the viewport (max-height: min(70vh, 480px)) with internal scrolling for tall accordions, and dismissal (Escape / click-outside) is always active regardless of closeOnClickOutside.
Styling the Shell
Section titled “Styling the Shell”<tbw-grid> renders into the light DOM, so the shell’s internals are reachable from any outer stylesheet — no ::part() needed. Theme-level overrides applied to a top-level CSS file affect every grid in the application uniformly.
Stable selectors:
| Selector | Element |
|---|---|
tbw-grid .tbw-shell-header | Header bar (title + content + toolbar) |
tbw-grid .tbw-shell-title | Title element |
tbw-grid .tbw-shell-toolbar | Toolbar (flex container; custom content + toggle) |
tbw-grid .tbw-toolbar-btn | Buttons inside the toolbar (incl. panel toggle) |
tbw-grid [data-panel-toggle] | The single panel toggle button |
tbw-grid .tbw-toolbar-separator | Separator between custom content and the toggle |
tbw-grid .tbw-tool-panel | Tool-panel sidebar |
tbw-grid .tbw-accordion-header | Tool-panel section header (one per registered panel) |
Recipe — move the panel toggle to the start of the toolbar (left in LTR):
The toolbar is a flex row, so reordering is purely a CSS concern — use order on both the toggle and its separator so they stay grouped together:
/* App-level theme — affects every <tbw-grid> uniformly */tbw-grid [data-panel-toggle],tbw-grid .tbw-toolbar-separator { order: -1;}No grid configuration is required; this is the recommended way to standardize toolbar layout across an application’s grids.
Bring Your Own Tool-Panel Toggle
Section titled “Bring Your Own Tool-Panel Toggle”If you need to replace the built-in toggle button entirely — for example, to use a design-system button instead of styling .tbw-toolbar-btn — set header.toolPanelToggle: false. The grid will not render the built-in button or the auto-inserted separator next to it; tool panels themselves remain fully functional and can be toggled from any element via the plugin’s toggleToolPanel(), opened directly on a specific section via openToolPanel(panelId), or driven section-by-section with toggleToolPanelSection(id).
import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');
const config = { features: { shell: { header: { title: 'Employees', toolPanelToggle: false, // suppress built-in <button data-panel-toggle> + separator toolbarContents: [ { id: 'my-buttons', render: (container) => { const filters = document.createElement('my-button'); filters.textContent = 'Filters'; filters.addEventListener('click', () => shell?.openToolPanel('filters'));
const settings = document.createElement('my-button'); settings.textContent = 'Settings'; settings.addEventListener('click', () => shell?.openToolPanel('settings'));
container.append(filters, settings); }, }, ], }, }, },};Passing a panelId to openToolPanel() jumps straight to that accordion section (one click, no double-tap). It takes precedence over toolPanel.defaultOpen; if the ID isn’t registered, the call falls back to default behavior and emits a TBW072 warning.
Hide the Shell Header Bar Entirely
Section titled “Hide the Shell Header Bar Entirely”toolPanelToggle: false removes the built-in toggle button but keeps the header bar (with its title and any toolbar contents). To suppress the entire .tbw-shell-header bar — for example when you want to open tool panels from a custom column header icon and own all chrome yourself — set header.visible: false.
import '@toolbox-web/grid/features/shell';
const shell = grid.getPluginByName('shell');
const config = { columns: [ // … { field: '__actions', width: 56, utility: true, // excluded from print, export, reorder, visibility, etc. resizable: false, sortable: false, headerRenderer: () => { const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = '⚙'; btn.setAttribute('aria-label', 'Open tool panel'); btn.addEventListener('click', () => shell?.toggleToolPanel()); return btn; }, }, ], features: { shell: { header: { visible: false }, toolPanel: { closeOnClickOutside: true, // optional — see below }, }, },};When header.visible: false, tool panels remain fully functional and are opened with the plugin’s toggleToolPanel() / openToolPanel(panelId) from any element you like. Because there is no header toggle to close the panel, the grid provides three additional dismissal affordances:
- Close button — a small
✕button (exposed as thetool-panel-closeCSS part) is rendered to dismiss the panel. With a single panel it sits inline on the first accordion header row; with multiple panels it gets its own row at the top of the panel (exposed as thetool-panel-headerCSS part). It is omitted whentoolPanel.lockedistrue(a locked panel cannot be closed) and indropdownmode, where the popover already light-dismisses on Esc / click-outside. - Escape key — pressing Esc closes an open overlay panel. It yields to more specific handlers (e.g. an active cell editor that has already handled the key), and is a no-op for
push-mode panels, which are persistent sidebars. - Click-outside — set
toolPanel.closeOnClickOutside: trueto dismiss an open overlay panel when clicking anywhere in the window outside the panel. This is opt-in and, like Esc, is a no-op inpushmode.
header.visible: false keeps the shell active (the panel overlay still mounts); it only suppresses the header bar element. This is the supported alternative to hiding .tbw-shell-header with CSS.
Migrating to the Feature API
Section titled “Migrating to the Feature API”The shell used to be part of grid core: auto-registered with no import, driven by
grid.register* / openToolPanel element delegates, with its config types exported from the
package root. Those surfaces are deprecated and will be removed in v3.0.0. The shell
configuration object itself is augmented onto gridConfig by the plugin and is not deprecated.
Migrate as follows:
| Deprecated (v2.x, removed at v3) | Use instead |
|---|---|
| Implicit shell (no import) | import '@toolbox-web/grid/features/shell' |
grid.registerToolPanel(panel) | grid.getPluginByName('shell')?.registerToolPanel(panel) |
grid.unregisterToolPanel(id) | grid.getPluginByName('shell')?.unregisterToolPanel(id) |
grid.registerHeaderContent(c) | grid.getPluginByName('shell')?.registerHeaderContent(c) |
grid.registerToolbarContent(c) | grid.getPluginByName('shell')?.registerToolbarContent(c) |
grid.openToolPanel(id) / closeToolPanel() / toggleToolPanel() | grid.getPluginByName('shell')?.openToolPanel(id) (same method names) |
Shell types from @toolbox-web/grid | Shell types from @toolbox-web/grid/plugins/shell |
Notes:
- In v2.x the shell is still auto-registered, so unmigrated code keeps working — the
imperative
grid.*shell delegates emit a one-time TBW076 deprecation warning. The top-levelconfig.shellfield is a plugin augmentation (not deprecated) andrefreshShellHeader()is merged/handled silently. - For advanced/explicit control you can register the plugin directly:
plugins: [new ShellPlugin(shellConfig)](from@toolbox-web/grid/plugins/shell). - Adapters auto-register the
ShellPluginin v2.x, so React/Vue/Angular users need no change; this auto-registration is removed at v3.0.0 (opt-in). The forward-compatible opt-in is the adapter-specific side-effect importimport '@toolbox-web/grid-{react,vue,angular}/features/shell'. - From v3.0.0 the shell is opt-in: shell usage without the feature/plugin throws a clear error with a migration link.
How the Shell Wraps the Grid
Section titled “How the Shell Wraps the Grid”The shell is unusual among plugins: most plugins enrich the grid (tagging rows, decorating
cells), but the shell wraps it — it consumes the freshly built grid DOM and renders its own
chrome (header bar + tool-panel sidebar) around it. It does this from the
afterStructuralRender() lifecycle hook, relocating the existing .tbw-grid-content node into
the shell wrapper (preserving the subtree, its listeners, and the grid’s cached refs) rather than
re-creating it.
If you want to build another grid-wrapping plugin, this pattern is documented as a reusable recipe in Plugin Architecture → Wrapping plugins.
See Also
Section titled “See Also”- Filtering — Registers a filter tool panel into the shell
- Pivot — Registers a pivot configuration tool panel
- Core Features — Columns, renderers, formatters, events, and methods
- Plugins Overview — Plugin compatibility and combinations