The @toolbox-web/grid-angular package provides base classes that eliminate boilerplate when building custom editors and filter panels. Each class handles common infrastructure — lifecycle, cleanup, positioning, form integration — so you can focus on your component’s UI and logic.
BaseGridEditor ← common inputs/outputs, validation helpers
├── BaseGridEditorCVA ← + ControlValueAccessor (dual grid/form use)
└── BaseOverlayEditor ← + floating overlay panel infrastructure
Class Purpose Extend when… BaseGridEditorInline cell editors You need a simple input that renders inside the cell BaseGridEditorCVADual grid + form editors The same component is used inside <tbw-grid> and standalone <form> BaseOverlayEditorOverlay/popup editors You need a floating panel (date picker, autocomplete, dropdown) BaseFilterPanelColumn filter panels You need a custom filter UI for the FilteringPlugin
The foundation class for all Angular cell editors. Provides common inputs, outputs, validation state, and automatic cleanup.
Member Type Description valueinput<TValue>()Cell value (fallback when no FormControl) rowinput<TRow>()Row data object columninput<ColumnConfig>()Column configuration controlinput<AbstractControl>()Cell’s FormControl (when using FormArray) commitoutput<TValue>()Emitted when value is committed canceloutput<void>()Emitted when edit is cancelled currentValue()Signal<TValue>Resolved value (prefers control.value over value) isInvalid()Signal<boolean>Whether control has validation errors hasErrors()Signal<boolean>Alias for isInvalid() firstErrorMessage()Signal<string>Human-readable first error message commitValue(v)Method Emit commit event + DOM CustomEvent cancelEdit()Method Emit cancel event + DOM CustomEvent isCellFocused()Method Whether the parent cell has the cell-focus class
import { Component } from ' @angular/core ' ;
import { BaseGridEditor } from ' @toolbox-web/grid-angular ' ;
selector: ' app-text-editor ' ,
[class.is-invalid]="isInvalid()"
(input)="commitValue($event.target.value)"
(keydown.escape)="cancelEdit()"
<div class="error">{{ firstErrorMessage() }}</div>
export class TextEditorComponent extends BaseGridEditor < MyRow , string > {
protected override getErrorMessage ( errorKey : string ) : string {
if (errorKey === ' required ' ) return ' This field is required ' ;
return super . getErrorMessage (errorKey);
Combines BaseGridEditor with Angular’s ControlValueAccessor interface. Use this when a single component needs to work both as a grid cell editor and as a standalone form control.
Member Type Description cvaValueSignal<TValue | null>Value written by the form control disabledStateSignal<boolean>Tracks setDisabledState() calls displayValueComputed<TValue | null>Prefers grid value, falls back to CVA value commitBoth(v)Method Commits via both CVA onChange and grid commitValue writeValue / registerOn* / setDisabledStateCVA methods Full ControlValueAccessor implementation
import { Component, forwardRef } from ' @angular/core ' ;
import { NG_VALUE_ACCESSOR } from ' @angular/forms ' ;
import { BaseGridEditorCVA } from ' @toolbox-web/grid-angular ' ;
selector: ' app-date-picker ' ,
provide: NG_VALUE_ACCESSOR ,
useExisting: forwardRef ( () => DatePickerComponent),
[disabled]="disabledState()"
(change)="commitBoth($event.target.value)"
(keydown.escape)="cancelEdit()"
export class DatePickerComponent extends BaseGridEditorCVA < MyRow , string > {}
< tbw - grid - column field = " startDate " editable >
< app - date - picker * tbwEditor = " let value " [value] = " value " />
// Inside a standalone form
< app - date - picker formControlName = " startDate " />
Base class for editors that display a floating overlay panel (date pickers, autocomplete dropdowns, color pickers, etc.). Handles all the positioning, focus gating, click-outside detection, and cleanup infrastructure.
CSS Anchor Positioning with JS getBoundingClientRect() fallback for Firefox/Safari
Focus gating — in row editing mode, only the focused cell’s overlay is shown
Click-outside detection — configurable via onOverlayOutsideClick()
Keyboard routing — Enter/Space/ArrowDown/F2 open, Escape closes
Automatic teardown — panel removed from <body> + all listeners cleaned up on destroy
Member Type Description overlayPositionOverlayPositionPosition relative to cell: 'below' (default), 'above', 'below-right', 'over-top-left', 'over-bottom-left' initOverlay(panel)Method Initialize with the panel element. Call in an effect() or afterNextRender(). showOverlay()Method Show the overlay panel hideOverlay(suppressTab?)Method Hide the overlay panel reopenOverlay()Method Close and re-open (repositions after content change) teardownOverlay()Method Remove panel from DOM + cleanup (auto-called on destroy) onInlineKeydown(e)Method Keydown handler for the inline input onInlineClick()Method Click handler for the inline input (toggle overlay) handleEscape(e)Method Close overlay or cancel edit advanceGridFocus(backward?)Method Dispatch synthetic Tab to move to next cell getInlineInput()Abstract Return the inline <input> element (for focus return) onOverlayOutsideClick()Abstract Called on click outside — typically call hideOverlay() onOverlayOpened()Hook Called after overlay opens (focus inner element, etc.)
import { Component, viewChild, ElementRef, effect } from ' @angular/core ' ;
import { BaseOverlayEditor } from ' @toolbox-web/grid-angular ' ;
selector: ' app-date-editor ' ,
(click)="onInlineClick()"
(keydown)="onInlineKeydown($event)"
<div #panel class="tbw-overlay-panel" style="width: 280px; padding: 12px;">
(change)="selectAndClose($event.target.value)"
<button (click)="hideOverlay()">Cancel</button>
export class DateEditorComponent extends BaseOverlayEditor < MyRow , string > {
panelRef = viewChild . required < ElementRef < HTMLElement >>( ' panel ' );
inputRef = viewChild . required < ElementRef < HTMLInputElement >>( ' inlineInput ' );
protected override overlayPosition = ' below ' as const ;
const panel = this . panelRef () . nativeElement ;
if ( this . isCellFocused ()) this . showOverlay ();
protected getInlineInput () : HTMLInputElement | null {
return this . inputRef () ?. nativeElement ?? null ;
protected onOverlayOutsideClick () : void {
selectAndClose ( date : string ) : void {
Value Description 'below'Panel below the cell, left-aligned (default). Flips above if viewport overflows. 'above'Panel above the cell, left-aligned. Flips below if off-screen. 'below-right'Panel below the cell, right-aligned. Flips above if viewport overflows. 'over-top-left'Panel top-left aligns with cell top-left (opens downward). No flip. 'over-bottom-left'Panel bottom-left aligns with cell bottom-left (opens upward). No flip.
The overlay panel uses CSS custom properties from the grid theme:
Variable Default Description --tbw-overlay-bg#fffPanel background --tbw-overlay-border#cccPanel border color --tbw-overlay-radius4pxPanel border radius --tbw-overlay-shadow0 4px 12px rgba(0,0,0,0.15)Panel box shadow
Base class for custom column filter panels used with the FilteringPlugin. Provides the params input and lifecycle helpers so you only need to implement your filter logic.
Member Type Description paramsinput.required<FilterPanelParams>()Injected by the grid’s filtering infrastructure applyFilter()Abstract Implement your filter logic here applyAndClose()Method Calls applyFilter() then closePanel() clearAndClose()Method Calls clearFilter() then closePanel()
The params input provides these members:
Property Type Description fieldstringColumn field name columnColumnConfigFull column configuration uniqueValuesunknown[]Distinct values in the column excludedValuesSet<unknown>Currently excluded values (set filter) searchTextstringCurrent search text applySetFilter(excluded)Method Apply include/exclude filter applyTextFilter(op, value, valueTo?)Method Apply text/number filter clearFilter()Method Clear column filter closePanel()Method Close filter panel
import { Component, viewChild, ElementRef, afterNextRender } from ' @angular/core ' ;
import { BaseFilterPanel } from ' @toolbox-web/grid-angular ' ;
selector: ' app-text-filter ' ,
<div class="filter-panel">
(keydown.enter)="applyAndClose()"
<button (click)="applyAndClose()">Apply</button>
<button (click)="clearAndClose()">Clear</button>
export class TextFilterComponent extends BaseFilterPanel {
input = viewChild . required < ElementRef < HTMLInputElement >>( ' input ' );
this . input () . nativeElement . focus ();
this . params () . applyTextFilter ( ' contains ' , this . input () . nativeElement . value );
import { Component, signal } from ' @angular/core ' ;
import { BaseFilterPanel } from ' @toolbox-web/grid-angular ' ;
selector: ' app-set-filter ' ,
<div class="filter-panel">
@for (val of params().uniqueValues; track val) {
[checked]="!excluded().has(val)"
<button (click)="applyAndClose()">Apply</button>
<button (click)="clearAndClose()">Clear</button>
export class SetFilterComponent extends BaseFilterPanel {
excluded = signal ( new Set < unknown >());
toggle ( value : unknown ) : void {
this . excluded . update ( set => {
const next = new Set (set);
next . has (value) ? next . delete (value) : next . add (value);
this . params () . applySetFilter ( this . excluded ());
import type { GridConfig } from ' @toolbox-web/grid-angular ' ;
import ' @toolbox-web/grid-angular/features/filtering ' ;
import { TextFilterComponent } from ' ./text-filter.component ' ;
import { SetFilterComponent } from ' ./set-filter.component ' ;
const gridConfig : GridConfig = {
{ field: ' name ' , filterable: true , filterPanel: TextFilterComponent },
{ field: ' status ' , filterable: true , filterPanel: SetFilterComponent },
features: { filtering: true },
Scenario Base Class Simple text/number input in cell BaseGridEditorDate picker input used in both grid and forms BaseGridEditorCVADropdown/autocomplete with floating panel BaseOverlayEditorCustom column filter UI BaseFilterPanelDate picker overlay + form control Compose: extend BaseOverlayEditor and implement ControlValueAccessor manually
All base classes are exported from the main package:
} from ' @toolbox-web/grid-angular ' ;
AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://raw.githubusercontent.com/OysteinAmundsen/toolbox/main/llms-full.txt