Active Foundation

Search documentation

Search all pages, headings and text, in the current package or topic or in all documentation.

Filter builder

Composing AND/OR filter expressions across columns with the opt-in filter builder.

@workspace/active-grid 11.0.0Status unknowncontent differs from the recorded release 11.0.0baseline ce081dc3

The package's own guide

This guide is the package's docs/guides/filter-builder.md, included unchanged from the package as it is in Foundation. Links to the package's internal design documents keep their text.

The filter-builder is an advanced UI for composing AND/OR filter expressions across multiple columns. It lives alongside the regular per-column filter row and is opt-in via enableFilterBuilder.

Mode support — client only#

The filter-builder works in mode="client" only. The composed expression is evaluated in the browser into a row-predicate; nothing about it is serialised onto the wire. ServerFetchParams carries filters, sorting and globalFilter — it has no expression field, and the resource layer in @workspace/active-resources has no counterpart to receive one.

Consequently, on a mode="server" grid the toolbar button and the panel are not rendered, and setting enableFilterBuilder emits a dev-mode warning (once per grid, silent in production). Withholding the UI is deliberate: a rendered builder would invite a user to compose an expression that the grid then discards without saying so. In server mode, use the per-column filter row — those filters do reach the server.

Wiring the expression tree into the server query is roadmap item F-12 (an expression field on ServerFetchParams plus a recursive translation with the same allowlist and type validation that individual filters already get). Until that lands, this is a client-mode feature.

The same applies to externalFilter, which shares the predicate path: it is applied in client mode only, and dev-warns in server mode. See ../api/grid-props.md.

When to use it#

Use the regular per-column filter row when consumers compose filters one column at a time (the default for most data-grids). Use the filter-builder when consumers need:

  • OR-logic between conditions: "name contains 'smith' OR email ends with '@acme.com'"

  • Mixed AND/OR composition: "(role = admin AND status = active) OR (createdAt > 2024 AND status = trial)"

  • Persistent named filters: the filter-tree serializes to a stable shape for URL-sync or saved-views

Enabling#

TSX
import {ActiveGrid} from '@workspace/active-grid';
<ActiveGrid  gridId="users"  mode="client"  data={users}  columns={columns}  enableFilterBuilder/>;

The filter-builder panel appears in the toolbar. The regular per-column filter row remains active — the two are combined with AND.

A condition's value uses the grid's own controls. A number is typed in the notation of the grid's presentation (the same input as the number filter and the number editor) and stored as a number; half-typed or unreadable text leaves the condition's value as it was. A date is typed in the presentation's date notation or picked from a calendar behind its own button, and stored as an ISO calendar day (YYYY-MM-DD). Emptying a number or date field makes the value absent, as in a new condition. Other columns take text.

A condition filters only once it is complete. Until its operator has every value it reads, it is ignored: adding a condition, or emptying its value, leaves the rows as they were. A missing value is undefined, null, '' or a non-finite number; 0 and false are values. A range (between, inRange) needs both ends. The no-value operators isEmpty and isNotEmpty are complete without a value. An AND or OR group judges only its complete conditions, and a group with none matches every row. The toolbar button follows the same rule: its count and its active state show only the conditions that filter, so an unfinished condition can stay in the panel without the grid looking filtered. The panel's own count and its clear action cover every condition in it.

On mode="server" this same configuration renders no button and no panel, and dev-warns instead. See Mode support.

The expression tree#

A filter expression is a tree of FilterCondition-leaves wrapped in FilterGroup-nodes with combinator: 'and' | 'or':

typescript
import type {  FilterExpression,  FilterCondition,  FilterGroup,  FilterExpressionNode,} from '@workspace/active-grid';
// A filter condition on one columninterface FilterCondition {  id: string;  field: string; // column id  operator: FilterOperator;  value?: unknown;  valueTo?: unknown; // for range operators}
// A group with AND/OR logicinterface FilterGroup {  id: string;  combinator: 'and' | 'or';  children: FilterExpressionNode[]; // can contain conditions or nested groups}
// FilterExpressionNode is a discriminated uniontype FilterExpressionNode = FilterCondition | FilterGroup;
// The root is always a grouptype FilterExpression = FilterGroup;

The isFilterGroup(node) type-guard narrows a FilterExpressionNode to FilterGroup (it returns true when the node has a combinator field).

Example: composed expression#

typescript
const expression: FilterExpression = {  id: 'root',  combinator: 'or',  children: [    {      id: 'group-1',      combinator: 'and',      children: [        {id: 'c1', field: 'role', operator: 'equals', value: 'admin'},        {id: 'c2', field: 'status', operator: 'equals', value: 'active'},      ],    },    {      id: 'group-2',      combinator: 'and',      children: [        {          id: 'c3',          field: 'createdAt',          operator: 'greaterThan',          value: new Date('2024-01-01'),        },        {id: 'c4', field: 'status', operator: 'equals', value: 'trial'},      ],    },  ],};

This represents (role = admin AND status = active) OR (createdAt > 2024 AND status = trial).

Initial expression and change observation#

Let the grid manage its own expression-state. Optionally seed it with initialFilterExpression.

TSX
<ActiveGrid  enableFilterBuilder  initialFilterExpression={savedExpression}  // .../>

Observe changes#

Subscribe to changes via onFilterExpressionChange for a host-owned saved-view integration. initialFilterExpression seeds the grid; it is not a controlled value prop. Observing the expression does not transfer its runtime ownership to the host.

TSX
const [expression, setExpression] = useState<FilterExpression | null>(null);
<ActiveGrid  enableFilterBuilder  initialFilterExpression={savedExpression}  onFilterExpressionChange={(expr) => {    setExpression(expr);    // Persist to URL or saved-view storage  }}  // .../>;

Evaluating expressions client-side#

The grid evaluates the expression internally. If you need to filter rows yourself (e.g., for a custom row-list outside the grid), use evaluateFilterExpression:

typescript
import {evaluateFilterExpression} from '@workspace/active-grid';
const filtered = rows.filter((row) =>  evaluateFilterExpression(row, expression),);

That evaluator is exported independently of the grid, so a consumer who needs AND/OR composition against a server dataset can build the expression tree themselves and translate it in their own query layer. The grid does not do that translation for them today — that is F-12.

Combining with per-column filters#

The per-column filter row stays active when enableFilterBuilder is on. The two are combined with AND. A user can pin a per-column filter (e.g. "status = active") while building a complex expression over other columns.

Persistence — not built in#

The filter-expression is not persisted by the grid. Neither driver carries it: enableUrlSync serialises page, pageSize, sort, filters and search (src/state/url-driver.ts), and enablePersistence validates and stores sorting, columnFilters, columnOrder, columnSizing and globalFilter (src/state/validate-persisted-state.ts). There is no filterExpression key in either.

To persist an expression, own it in the consumer: subscribe with onFilterExpressionChange, write it wherever you keep view-state, and seed it back through initialFilterExpression on mount. Keep the tree JSON-serializable (no Date-objects — use ISO strings) if you round-trip it through a URL or storage.

A11y#

The filter-builder panel uses the shared Base UI primitives and provides full keyboard navigation. Tab through the group/condition tree; Enter to open operator-selects; Escape to close popovers.

Reference#

  • FilterExpression, FilterCondition, FilterGroup, FilterExpressionNode, isFilterGroup — exported from @workspace/active-grid

  • evaluateFilterExpression(row, expression) — client-side row evaluator

  • Server mode: the builder is withheld and dev-warns; per-column filters are the server-capable route (see ../api/resource-mode.md)

  • API reference: ../api/filters.md

This page as Markdown