npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apex-grid-enterprise

v0.7.0

Published

Enterprise (pro-licensed) features for apex-grid.

Downloads

295

Readme

apex-grid-enterprise

Pro-licensed enterprise features for apex-grid.

apex-grid-enterprise extends the community grid and registers as <apex-grid-enterprise>, layering enterprise-only features on top of everything apex-grid already does. Use it as a drop-in replacement for <apex-grid>: the configuration API, theming, and events are identical, plus the additions below.

Requires a valid license key for production use. Without one, the grid keeps working but renders a watermark and logs a console notice. Set a key with ApexGridEnterprise.setLicense(...); see Licensing below.

Enterprise features

  • Column aggregations: sum / avg / min / max / count per column.
  • Row grouping: collapsible groups, nested to any depth, with live per-group subtotals.
  • Pivoting: reshape rows into a cross-tab with aggregated cells.
  • Set filter: Excel-style column value checklist with search and select-all.
  • Columns tool panel: drag-and-drop panel for visibility, ordering, grouping, and pivot.
  • Cell range selection & status bar: Excel-style range select with live aggregates.
  • Excel (XLSX) export: native-typed .xlsx export plus a toolbar menu entry.
  • Master / detail grids: embed a child grid in each expandable row.
  • Integrated charts: render the grid's data as an ApexCharts chart, live-bound to the view (redraws on sort / filter / edit), with a data-mapping definition, a format popover (colors, number format, axis titles, trend / reference lines & bands, forecasting), image export, save / restore, and optional chart-driven cross-filtering.
  • Context menu: right-click or the header kebab button for sort / pin / hide / group / copy, a "Chart range" submenu, and custom items.
  • Infinite (server-side) row model: stream large remote datasets, block by block.
  • AI Toolkit: natural-language grid control and read-only Q&A on a built-in deterministic rule engine (offline, no key), with optional LLM escalation and a first-class Claude reasoner included.
  • Formulas: spreadsheet-style cell formulas with relative/absolute A1 references, drag-to-fill, editor autocomplete and click-to-insert, a broad built-in function set (plus custom functions), dependency-graph recalculation, a show-formulas view, and formula-aware export.

Install

npm install apex-grid-enterprise apex-grid lit

apex-grid and lit are peer dependencies shared with the community package: install a single copy of each.

apexcharts is an optional peer dependency, used only by the integrated charts (renderChart()). It is loaded through a dynamic import(), so a grid that never charts neither bundles nor downloads it. Install it only when you chart:

npm install apexcharts

ApexCharts 5.x, 6.x and 7.x are supported (peer range ^5.15.0 || ^6.0.0 || ^7.0.0); a single copy serves the grid's integrated charts and any charts you draw yourself with apexcharts / react-apexcharts.

Usage

import 'apex-grid-enterprise/define'; // registers the enterprise element set

/define is the batteries-included entry: it opts the grid into every built-in feature module and registers the grid plus its companion elements (<apex-grid-enterprise>, <apex-grid-tool-panel>, <apex-grid-status-bar>, <apex-grid-set-filter>). The configuration API is identical to apex-grid; see the apex-grid README for column configuration, theming, and events.

Composing only the features you use

Importing from the package root wires in no feature modules by default, so each one is tree-shaken unless you opt into it via ApexGridEnterprise.use(). Call it once at startup, before registering the element:

import { ApexGridEnterprise, pivotModule, rangeSelectionModule } from 'apex-grid-enterprise';

ApexGridEnterprise.use(pivotModule, rangeSelectionModule); // only these are bundled + wired
ApexGridEnterprise.register();

Available modules: aggregationModule, groupingModule, pivotModule, rangeSelectionModule, contextMenuModule, formulaModule. The enterpriseModules array is the full set (it is what /define passes to use()). Modules a grid is not opted into add nothing to your bundle.


Column aggregations

Request sum / avg / min / max / count per column via the aggregations property, then read the computed values with getAggregations():

grid.aggregations = { price: ['sum', 'avg'], sold: ['sum', 'max'] };

const totals = grid.getAggregations();
// → { price: { sum: 657.92, avg: 109.65 }, sold: { sum: 347, max: 91 } }

AggregationConfig is Record<string, AggregationFn[]>, where AggregationFn is 'sum' | 'avg' | 'min' | 'max' | 'count'. Non-numeric values are ignored. The same engine powers group subtotals and pivot cells, so numbers are computed consistently everywhere.

Grand-total row

totalRow adds one synthesized row showing aggregations across the whole view:

grid.totalRow = { aggregations: { price: ['sum'], sold: ['sum'] } };
grid.getTotals(); // → { price: { sum: 657.92 }, sold: { sum: 347 } }

This is not the same thing as the per-group aggregates groupBy renders, which total a group's own leaves. The total row totals the view, so it answers "what is the sum of this column" without grouping anything, and it follows filtering — a filtered grid totals what it shows. In a grouped view the group-header rows are excluded, so their aggregates are not counted twice.

position: 'top' moves it above the rows, label overrides the leading text (default: the localized "Grand Total"), and null removes it. Style it through the total-row, total-row-content, total-row-label, total-row-values, and total-row-value parts. Pivot brings its own grand total, so this covers the flat and grouped views.

Row grouping

Group rows by one or more columns. Group headers render full-width with a chevron toggle, a leaf count, and inline subtotals. Aggregates are computed over the filtered leaves, so they track what is on screen.

grid.groupBy = ['region', 'department'];          // ordered; [] = no grouping
grid.aggregations = { salary: ['sum', 'avg'] };   // shown on each group header
grid.groupingOptions = { defaultExpanded: true }; // true | false | depth number

grid.expandAllGroups();
grid.collapseAllGroups();
grid.expandGroup('/EMEA/Engineering');            // stable path key
grid.getGroups();                                 // GroupRowMeta<T>[]

Cancellable groupExpanding and follow-up groupExpanded events fire on toggle.

Pivoting

Reshape data into a cross-tab: pick the row dimension(s), the column(s) to pivot on, and the measures aggregated into each cell. Pivot columns are generated from the data and recompute on filter. Pivoting and groupBy are mutually exclusive (pivot wins).

grid.pivotRows = ['region'];            // down the left
grid.pivotOn = 'department';            // its distinct values become columns
grid.pivotValues = { salary: ['sum'] }; // measure(s) per cell (AggregationConfig)

grid.pivotOn = '';                      // disable, restoring the original columns

grid.isPivoting reflects whether a pivot view is currently active.

Spanning headers, totals, and multi-field column dimensions

With more than one measure, or more than one column-dimension field, the value columns group under spanning headers. pivotOn accepts an ordered list; the first field heads the spanning group (core column groups are one level deep, so inner fields fold into the leaf header). getPivotColumnGroups() returns the generated groups.

pivotOptions adds subtotals (a row after each first-dimension value; needs a multi-field row dimension) and a grand total:

grid.pivotRows = ['region', 'department'];
grid.pivotOn = ['level', 'quarter'];      // multi-field column dimension
grid.pivotValues = { salary: ['sum', 'avg'] };
grid.pivotOptions = { subtotals: true, grandTotal: 'bottom' };

Total rows are ordinary rows carrying the aggregate; getPivotMeta(row) returns { kind: 'subtotal' | 'grandTotal' } for them (and 'data' otherwise), so you can style or skip them. Both totals are off unless requested.

Expandable row groups

pivotOptions.expandable renders the row dimension as a nested, collapsible tree: the per-field leading columns collapse into one auto group column with indentation and chevrons, and each parent row carries the aggregate over its subtree.

grid.pivotRows = ['region', 'department'];
grid.pivotOn = 'level';
grid.pivotValues = { salary: ['sum'] };
grid.pivotOptions = { expandable: true, defaultExpanded: true, grandTotal: 'bottom' };

defaultExpanded accepts true / false / a depth threshold. Clicking a chevron toggles a node; total rows still compose. In expandable mode the subtotals option is ignored (parent rows already are the subtotals).

Set filter

An Excel-style value checklist for a column. A standalone companion element wired to the grid; every toggle applies immediately and composes with the grid's other filters.

<apex-grid-set-filter id="filter" column="department"></apex-grid-set-filter>
filter.grid = grid;                 // required
await filter.updateComplete;
filter.setSelectedTokens(['Engineering', 'Marketing']);

filter.selectAll();                 // select all values (clears the column filter)
filter.clearAll();                  // deselect all (hides every row)
filter.refresh();                   // re-read distinct values after data changes
filter.distinctValues;              // ReadonlyArray<{ token, value, label }>
filter.selectedTokens;              // string[]

The panel includes a search box, a (Select all) row, and folds empty values (null / undefined / '') into a single entry. CSS parts are exposed for styling.

Advanced filter builder

A nested AND / OR visual query builder — beyond the per-column header filters. Compose conditions and sub-groups like (region = EMEA OR region = AMER) AND salary > 80000.

<apex-grid-filter-builder id="builder"></apex-grid-filter-builder>
builder.grid = grid;                // required
// Add conditions / groups in the UI, then Apply. Or drive it headlessly:
grid.applyAdvancedFilter({
  kind: 'group',
  join: 'and',
  children: [
    {
      kind: 'group',
      join: 'or',
      children: [
        { kind: 'condition', column: 'region', operator: 'equals', value: 'EMEA' },
        { kind: 'condition', column: 'region', operator: 'equals', value: 'AMER' },
      ],
    },
    { kind: 'condition', column: 'salary', operator: 'greaterThan', value: 80000 },
  ],
});
grid.clearAdvancedFilter();         // remove it
grid.advancedFilterModel;           // the active model, or null

Operators come from the same operand tables as the header filters (operatorsForType(type)), so semantics match. Evaluation is client-side via the dataPipelineConfiguration.filter hook and composes with sort / pagination / grouping. While active, the advanced filter owns column filtering (it replaces the built-in filter). The model is plain JSON, so it round-trips through storage. The pure evaluator (filterRows, isEmptyModel) is exported for headless use.

Columns tool panel

A drag-and-drop side panel that turns the advanced features into a no-code surface: toggle column visibility, pin, reorder, and search; drag fields into Row Groups / Values to group and aggregate; flip Pivot mode to drive pivot rows, values, and column labels. Wire it to a grid and it drives the grid's public properties directly.

<apex-grid-tool-panel id="panel"></apex-grid-tool-panel>
document.getElementById('panel').grid = grid; // the only wiring needed

Cell range selection & status bar

Excel-style cell range selection is on by default; pair it with the status bar companion element to show live aggregates (count / sum / avg / min / max) over the current selection. Holding a drag at the top or bottom edge of the grid body auto-scrolls and keeps extending the selection past the visible rows.

<apex-grid-status-bar id="status"></apex-grid-status-bar>
status.grid = grid;

grid.rangeSelection = true;         // default; HTML attribute: range-selection
grid.selectRange(/* start, end */); // programmatic selection
// the grid emits 'apex-range-changed' with the current selection stats

Excel (XLSX) export

Export to a real .xlsx file where numbers, booleans, and Date values keep their native Excel cell types. Adds an "Export XLSX" entry to the toolbar's export menu, alongside the community grid's CSV (CSV export stays free).

const bytes = grid.exportToXLSX({ filename: 'users', sheetName: 'Users' });
// options: { filename?, sheetName?, source?, columns? }, returns Uint8Array

Master / detail grids

Give each expandable row its own embedded child grid. Provide the child columns and a (sync or async) data getter; detail grids are cached per row.

grid.masterDetail = {
  columns: [{ key: 'sku' }, { key: 'qty' }],       // or a (row) => columns fn
  getDetailData: (row) => fetchOrderLines(row.id),  // T[] | Promise<T[]>
  detailHeight: 240,                                // optional
};

grid.refreshDetail(row); // drop a row's cached detail grid to force a reload

Integrated charts

Render the grid's current data as an ApexCharts chart. ApexCharts is dynamically imported, so it only loads when a chart is actually drawn.

The chart model is derived by intent: a selected cell range wins, otherwise the current view (the flat grid, or a grouping/pivot aggregate). Friendly chart types (column, bar, line, area, pie, donut, scatter, radar, combo, or 'auto') map to the right ApexCharts shape.

const model = grid.getRangeChartModel();     // from the active cell selection
// or grid.getViewChartModel()               // from the current view (flat, or grouping/pivot)
// or grid.getChartModel()                   // selection if present, else view

const chart = grid.createRangeChart(container, {
  type: 'column',
  apexOptions: { /* deep-merged escape hatch */ },
});

Each getter takes an optional ChartDefinition to control which column is the category, which are the measures, and how rows are aggregated per category.

<apex-grid-chart> panel

A built-in chart panel with a type gallery that live-redraws as the selection or view changes. Set its grid property; choose mode="inline" (embedded) or mode="dialog" (a floating, draggable panel, the default). It renders in light DOM (ApexCharts cannot render inside a shadow root).

<apex-grid-chart mode="inline"></apex-grid-chart>
chart.grid = grid;
chart.source = 'selection'; // 'auto' (default) | 'selection' | 'view'
chart.type = 'column';      // gallery-switchable; 'auto' uses the recommended type
chart.theme = 'grid';       // 'grid' (sync palette to grid theme) | 'light' | 'dark'

With source="view" the panel becomes a live companion: it charts the whole current view and redraws automatically as you sort, filter, or edit the grid, with no cell selection required.

The enterprise grid also adds a "Create chart" toolbar button that opens the panel in a dialog. Each click mints an independent chart panel, so several charts (each a frozen snapshot of the data at creation) can sit side by side without disturbing one another. The panel and toolbar button require <apex-grid-chart> to be registered (the /define entry does this for you).

Ways to start a chart, and a few panel niceties:

  • On a selection a floating Chart button appears over the grid, and Alt+F1 charts the current cell range from the keyboard.
  • On a grouped / pivoted view the right-click menu offers "Chart this view" (it charts the group/pivot model); with a plain selection it offers "Chart range".
  • The type gallery is grouped (cartesian / circular / statistical) with an icon per type; Auto is a "Suggested" badge. A dialog chart's heading auto-titles from the mapping ("Revenue by Region") and is double-click-to-rename.

Data mapping

ChartDefinition (the panel's definition property, or an argument to the model getters) controls how the grid maps to the chart. Every field is optional: the empty default keeps the automatic mapping (first non-numeric column is the category, every numeric column a series, summed per category), so a mapping UI can seed itself from the default and override only what changes.

chart.definition = {
  category: 'region',            // column key for the X axis
  measures: ['revenue', 'deals'],// column keys plotted as series
  aggregation: 'sum',            // 'sum' | 'avg' | 'count' | 'min' | 'max' | 'median'
                                 // (or a per-measure map { revenue: 'sum', deals: 'avg' })
  secondaryMeasures: ['deals'],  // draw these on a secondary (opposite) value axis
};

The panel surfaces all of this in a Data popover in the toolbar: a category picker, a checkbox per numeric column (with a per-series 2nd axis toggle for measures on a different scale, e.g. revenue vs. headcount), and the aggregation. The columns it offers come from grid.getChartFields() ({ key, label, numeric }[]), so a custom mapping UI can read the same list. The popover is hidden for snapshot charts (staticModel) and while grouping/pivot is active (those views carry their own aggregation).

Calculated fields

A calculated field plots a series from a formula over the other columns, with no extra grid column. The formula uses the enterprise formula engine with A1 references, where the letters map to the numeric columns in display order (A1 = first numeric column, B1 = second, …; row is always 1 — one aggregated value per column per category):

chart.definition = {
  category: 'department',
  measures: ['salary'],
  calculatedFields: [{ name: 'Bonus %', formula: 'B1 / A1 * 100' }], // bonus / salary * 100
  secondaryMeasures: ['Bonus %'], // a calc field can sit on the secondary axis (by its name)
};

Each field is evaluated per category over the aggregated values (aggregate-then- evaluate, i.e. the ratio of totals — the convention in Excel PivotTable calculated fields, Power BI measures, and Tableau). The Data popover has an editor (name + a live-validated formula input with an A1 = column legend); calc fields ride through toJSON / restore in the definition. isValidChartFormula(formula) and computeCalculatedSeries(...) are exported for building your own UI.

Format popover

The toolbar's Format popover surfaces the handful of options users change most often, layered over apexOptions so it only touches the keys you set. All are available programmatically via the format property (ChartFormat):

chart.format = {
  colors: ['#2563eb', '#16a34a'], // per-series, in series order
  legend: true,
  dataLabels: false,
  gridlines: true,
  numberFormat: 'currency',       // 'none' | 'currency' | 'percent' | 'thousands'
  axisTitles: { x: 'Department', y: 'Salary (USD)' },
  referenceLine: 100_000,         // dashed target/threshold on the value axis
  referenceBand: { from: 80_000, to: 100_000 }, // shaded target range
  trendline: true,                // least-squares trend overlay on the first series
  forecast: 3,                    // project N future periods as a continuation line
  forecastBand: true,             // add a ~95% prediction band around the forecast
};

trendline, forecast, and forecastBand are also exposed as the pure functions linearTrend(), linearForecast(), and linearForecastBand(), and ChartFormat ↔ ApexCharts translation as formatToApexOptions(), so you can drive them without the panel.

Export

The toolbar's Export menu (and the matching methods) save the chart as a raster PNG, a scalable SVG, or copy it to the clipboard as an image:

await chart.exportImage('png'); // or 'svg'
await chart.copyImage();        // PNG to the clipboard

Save & restore

toJSON() returns a plain-JSON ChartConfig (type, source, mapping, format, heading, cross-filter, and any frozen snapshot) that an app can persist anywhere and reapply with restore(). apexOptions is excluded (it is author code and may hold functions), so bind it and grid separately.

const config = chart.toJSON();            // JSON-safe; JSON.stringify(chart) works too
localStorage.setItem('myChart', JSON.stringify(config));

// later
chart.grid = grid;
chart.restore(JSON.parse(localStorage.getItem('myChart')!));

Cross-filtering

Set cross-filter (the attribute or the crossFilter property) to turn the panel into a filter surface: clicking a chart segment filters the underlying grid rows to that category, and clicking it again toggles the filter off. The chart reads the grid's full, unfiltered data, so every category stays on the chart instead of collapsing to the filtered subset.

<apex-grid-chart cross-filter></apex-grid-chart>
chart.crossFilter = true;
chart.selectCategory(0); // programmatic form of clicking the first segment

The filter is applied with a self-contained, type-independent equality operation (a logic predicate rather than a named operand), so it matches regardless of the category column's declared type. Turning crossFilter off (or disconnecting the panel) drops any filter it applied.

Context menu

A right-click context menu on cells and column headers, also opened from each column header's kebab (three-dot) button so the actions are discoverable without a right-click. The kebab and the right-click menu show the same items. Enabled by default; set context-menu="false" (the attribute) to turn it off, or assign a ContextMenuConfig (via the contextMenu property) to replace the items.

grid.contextMenu = false; // disable

grid.contextMenu = {      // or replace the items
  items: (target) => [
    { id: 'sort-asc', label: 'Sort ascending', run: () => grid.sort([{ key: target.column.key, direction: 'ascending' }]) },
    { id: 'copy', label: 'Copy', separatorBefore: true, run: (t) => copyCell(t) },
  ],
};

ContextMenuConfig<T> is { items?: ContextMenuItem<T>[] | ((target: ContextMenuTarget<T>) => ContextMenuItem<T>[]) }, where a ContextMenuItem<T> is { id, label, run?, disabled?, submenu?, separatorBefore? } (provide a submenu for a nested menu, or a run callback for a leaf). The target is { kind: 'cell' | 'header', column, row?, rowIndex? }.

The built-in default items are sort ascending / descending / clear sort, pin to start / pin to end / unpin, hide column, and (on cell targets) copy. On header targets, when the grouping module is present, the menu also offers group by this column, un-group all, and expand / collapse all groups. When you leave contextMenu at its default (rather than supplying your own items), the menu also appends a "Chart range" submenu whose entries (Column, Bar, Line, Area, Pie, Donut, Combo, Auto) chart the current cell-range selection in the chart dialog.

To tweak the items per target without replacing the whole set, listen for the cancellable apex-context-menu-opening event: its detail.items array is prefilled with the resolved items (the defaults, unless you supplied your own) and is mutable, so you can push, splice, or preventDefault() before the menu opens.

grid.addEventListener('apex-context-menu-opening', (event) => {
  event.detail.items.push({ id: 'custom', label: 'My action', separatorBefore: true, run: () => {} });
});

The feature is the contextMenuModule (id context-menu); the /define entry includes it.

Infinite (server-side) row model

Stream large remote datasets without loading every row into memory. The grid fetches fixed-size blocks on scroll and delegates sort, filter, and quick-search to your backend.

grid.infiniteRowModel = {
  datasource: {
    async getRows({ startRow, endRow, sortModel, filterModel, quickFilter }) {
      const { rows, total } = await fetchPage(startRow, endRow, sortModel, filterModel, quickFilter);
      return { rows, rowCount: total }; // omit rowCount for "infinite" mode
    },
  },
  blockSize: 100, // default 100
};

grid.isRowLoading(row); // true for not-yet-loaded placeholder rows (render skeletons)
grid.refreshRows();     // refetch after a server-side mutation

The grid emits apex-rows-loaded (detail: { rowCount, exact, loadedBlocks, blockSize }) so you can show live load status.

Server-side grouping + aggregation

For the analytics case, serverSideRowModel groups and aggregates on the server: the grid asks for one group level at a time and lazily fetches a group's children when it is expanded. Group rows show server-computed aggregates under an auto group column.

grid.serverSideRowModel = {
  datasource: {
    async getRows({ groupKeys, rowGroupCols, valueCols, sortModel, filterModel, quickFilter }) {
      // groupKeys is the value path of the parent being expanded ([] = top level).
      // Return group rows (with the grouped value + aggregates) when
      // groupKeys.length < rowGroupCols.length, else the leaf rows.
      return { rows: await fetchLevel(groupKeys, rowGroupCols, valueCols, sortModel, filterModel, quickFilter) };
    },
  },
  rowGroupCols: ['region', 'department'],
  valueCols: { salary: ['sum'] },
};

grid.expandServerGroup(['EMEA']);          // programmatic expand/collapse
grid.collapseServerGroup(['EMEA']);
grid.refreshServerSide();                  // reload after a server-side mutation
grid.isServerSideRowModel;                 // boolean

Group rows carry aria-level / aria-expanded for screen readers. The grid emits apex-server-rows-loaded. Mutually exclusive with infiniteRowModel and client groupBy / pivotOn (the server owns shaping).

Server-side pivot. Add pivotCols to pivot on the server: the grid passes pivotCols + pivotMode to getRows, and the server returns the generated value columns in pivotResultFields (plus optional pivotResultGroups for spanning headers), which the grid installs.

grid.serverSideRowModel = {
  datasource,
  rowGroupCols: ['region'],
  pivotCols: ['department'],       // ⇒ pivotMode; server returns pivotResultFields
};

Intra-group pagination. By default a group's children load in one request. Set blockSize to load them a window at a time: the grid passes startRow / endRow to getRows, sizes the group from the returned rowCount, renders not-yet-loaded rows as placeholders, and fetches the next block as you scroll into it. Use grid.isRowLoading(row) to detect a placeholder (e.g. for a skeleton).

grid.serverSideRowModel = {
  datasource,                      // getRows now receives { startRow, endRow, ... }
  rowGroupCols: ['region', 'department'],
  valueCols: { salary: ['sum'] },
  blockSize: 100,                  // children fetched 100 at a time; return `rowCount`
};

AI Toolkit

Drive the grid in natural language. A prompt becomes a schema-validated state change applied through setState() with a one-click undo, plus a read-only Q&A mode. By default it runs on a built-in deterministic rule engine: offline, no key, no network. You can add an optional LLM (a first-class Anthropic/Claude reasoner is included) that the grid escalates to only when the rule engine is not confident. The whole toolkit is an enterprise feature; it builds on the community grid's getSchema() / setState() foundation.

What it understands

The rule engine covers a documented set of commands and questions. Use these and they work instantly and offline; anything outside the set escalates to your LLM reasoner (if you configured one) or is reported honestly (see below). It never guesses silently.

  • Sort: "sort by revenue, highest first", "order by name", "reverse the sort".
  • Group / ungroup: "group by region", "ungroup".
  • Filter: "filter status = open", "only show EMEA", "remove rows where salary is under 70000", "keep only rows with amount over 100". Removal keeps the complement (it inverts the operand, or reports that it cannot).
  • Columns: "hide the notes column", "show salary", "pin name to the left".
  • Search: "search Acme".
  • Pivot / aggregate: "pivot on region", "sum of revenue".
  • Pagination / export: "page 2", "page size 50", "export as csv".
  • Reset / undo: "reset", "undo".
  • Read-only questions: "how many rows", "highest / lowest / average salary", "min, max and median of bonus", "average salary by department", "who has the highest salary", "top 5 by revenue", "which region has the highest total sales".

Compound requests are supported in one sentence: "group by department, then sort by salary and remove all rows under 70000" applies three steps atomically with a single undo.

Running a prompt

const result = await grid.runPrompt('group by region, then sort by revenue, highest first');
if (result.mode === 'control') {
  console.log(result.applied);  // e.g. ['group', 'sort']
  console.log(result.warnings); // anything dropped or unmapped, each with a reason
  result.undo();                // one-click revert
}

// Read-only question; the grid is not changed.
const answer = await grid.runPrompt('which region has the highest average revenue?', { mode: 'ask' });
// answer.mode === 'ask'; answer.answer is the text reply

runPrompt validates every planned change against the schema (dropping anything out of vocabulary, with a reported reason), applies it via the defensive setState(), and returns an idempotent undo() that restores the prior snapshot. Ask mode never mutates the grid.

Honest fallback (no silent no-ops)

When a prompt cannot be mapped to a grid action and no LLM reasoner is configured, runPrompt abstains rather than silently doing nothing or guessing:

const result = await grid.runPrompt('teleport the widget sideways');
// result.mode === 'ask', result.abstained === true
// result.answer is a short "I could not turn that into a grid action" note

The panel renders this as a distinct message (with a hint) instead of an empty result. Confidence is calibrated on how much the prompt actually grounds against your schema, so a near-miss command scores low and either escalates to your LLM reasoner or abstains, instead of returning a confident wrong answer.

Optional LLM escalation

Leave runPrompt as is for the offline rule engine. To also handle prompts the rules cannot map, assign a Reasoner. The grid tries the rule engine first and escalates only when it is unsure, so simple requests stay instant and offline.

import { createClaudeReasoner, createLLMReasoner } from 'apex-grid-enterprise';

// Production: your backend holds the key and calls Anthropic; the browser never sees it.
grid.aiReasoner = createClaudeReasoner({ endpoint: '/api/grid-ai' });

// Development only: call Anthropic from the browser. This exposes the key to the page.
grid.aiReasoner = createClaudeReasoner({ apiKey: '...', dangerouslyAllowBrowser: true });

// Or bring any provider by implementing a single completion function.
grid.aiReasoner = createLLMReasoner({ complete: async (req) => ({ patch: /* ... */ }) });

The Claude direct transport dynamically imports @anthropic-ai/sdk (an optional peer dependency), defaults to claude-opus-4-8 (configurable), and uses tool use so the model returns a state patch shaped by the grid's schema (toJSONSchema). The proxy transport POSTs { prompt, mode, schema, data } to your endpoint, which returns { patch?, answer? }. Prefer the proxy for production. Install the SDK only when you use the direct transport:

npm install @anthropic-ai/sdk

The AI runtime is loaded on demand, on the first runPrompt / previewPrompt, so a grid that never runs a prompt bundles none of it.

Prompt panel

<apex-grid-ai> is a ready-made prompt UI. Bind it to a grid and it drives runPrompt for you, showing what changed (with an Undo button), the answer in ask mode, or the honest fallback when a prompt could not be mapped.

<apex-grid-ai mode="inline"></apex-grid-ai>
document.querySelector('apex-grid-ai').grid = grid;

mode="inline" renders in place; mode="dialog" (the default) is a floating, draggable panel. The enterprise grid also adds an "Ask AI" toolbar button that opens the panel in a dialog (the /define entry registers the element).

How it stays safe

The control path is guarded in layers: an LLM is constrained by the grid's schema (toJSONSchema), anything out of vocabulary is stripped before it is applied (each drop reported), the defensive setState() drops and reports the rest, and every change is one click undoable. Ask mode is read-only, and an unmappable prompt abstains instead of acting.


Formulas

Spreadsheet-style formulas in a cell. Mark a column allowFormula (and editable); a cell whose edited text starts with = is parsed, stored, and its computed result becomes the cell value. Because the value stays canonical in row[key], sorting, filtering, aggregation, export, and charts all keep working on the result.

grid.columns = [
  { key: 'qty', headerText: 'Qty', type: 'number', editable: true },
  { key: 'price', headerText: 'Price', type: 'currency', editable: true },
  { key: 'total', headerText: 'Total', type: 'currency', editable: true, allowFormula: true },
];

// Type "=B1*C1" into a Total cell, or set it programmatically:
grid.setFormula(grid.data[0], 'total', '=B1*C1');
grid.getFormula(grid.data[0], 'total'); // '=B1*C1'
grid.clearFormula(grid.data[0], 'total');
grid.recalculateFormulas();

References (A1 over the data)

Columns map to letters by configuration order (A is the first column, including hidden ones); rows are 1-based over the source data. So B2 is the second data row, second column. References bind to the data, not the rendered view, so a formula keeps its meaning across sort, filter, column reorder, and paging. Ranges (A1:B3) expand to value lists for functions.

References are relative by default; a $ fixes an axis ($A$1 fixes both, $A1 the column only, A$1 the row only). Relative references shift when a formula is filled or pasted to another cell; absolute axes stay put. A reference always resolves to a concrete cell, so recalculation itself is unaffected by relative-ness.

Identity: formula attachment is durable when you set a grid rowId (it survives reload through getState / setState); without one it is positional.

Authoring

While editing a formula (after typing =):

  • Autocomplete: typing a function name shows a suggestion list (built-ins + your custom functions). Up/Down to move, Enter/Tab or click to accept (inserts NAME() with the caret between the parentheses), Escape to dismiss.
  • Click-to-insert: click any grid cell to insert its reference at the caret; Shift-click inserts an absolute $A$1.
  • Drag-to-insert a range: press on a cell and drag across the grid to insert a live A1:C3 range reference; a dashed marquee tracks the cells as you drag. Clicking again re-picks the reference (replaces it); type an operator to add a second one. Escape backs out of a just-picked reference without leaving the edit.
  • F4 absolute/relative: with the caret on a reference, press F4 to cycle its $ markers: A1 -> $A$1 -> A$1 -> $A1 -> A1.

Point-and-click reference entry is mouse-driven: while editing, the arrow keys always move the text caret and never start referencing cells.

Drag-to-fill and paste rewrite a formula's relative references by the row/column delta to the new cell (absolute $ axes are preserved):

// Drag the fill handle, or do it in code: copy total[0]'s formula down.
grid.selectRange({ row: 0, column: 'total' });
grid.fillTo({ row: 4, column: 'total' }); // =B1*C1 becomes =B2*C2, =B3*C3, ...

Copying a range and pasting it back inside the same grid re-offsets the source formulas (a plain-text clipboard cannot carry formulas, so cross-app paste stays literal).

Reference highlighting

While a formula cell is being edited, every cell the formula references lights up in the grid, and each distinct reference gets its own color (a small palette cycles), so =B2*C2 highlights B2 and C2 differently, spreadsheet-style. The highlight tracks the text as you type (invalid mid-edit input keeps the last good highlight) and clears when the editor closes. It is applied as a data-formula-ref cell decoration, so you can theme the colors with CSS.

To make those references easy to read, spreadsheet coordinates (a row-number gutter plus A / B / C column letters) are shown by default for any grid that has allowFormula columns. Reserving the gutter up front means entering a formula never shifts the layout: the gutter would otherwise pop in on the first edit and nudge every column. An explicit coordinateHints you set afterwards is left untouched.

Functions

Built in:

  • Math: SUM, AVERAGE (alias AVG), MIN, MAX, COUNT, COUNTA, ROUND, ROUNDUP, ROUNDDOWN, ABS, MOD, POWER, SQRT, INT, SIGN.
  • Logical: IF, AND, OR, NOT.
  • Text: CONCAT (alias CONCATENATE), LEN, LEFT, RIGHT, MID, TRIM, UPPER, LOWER.

Operators: + - * / ^ %, comparisons = <> < > <= >=, and text concatenation with &. IF short-circuits, so IF(B1=0, 0, A1/B1) is safe. Register your own:

grid.registerFormulaFunction('TAX', (args) =>
  typeof args[0] === 'number' ? args[0] * 0.2 : 0
);
// =TAX(B1)

Error values

Errors are first-class cell values that render as their code and are excluded from numeric aggregates: #REF! (bad reference), #NAME? (unknown function), #DIV/0!, #VALUE! (type error), and #CYCLE! (circular reference). An error operand propagates.

Recalculation and persistence

Editing any referenced cell recomputes its dependents in dependency order (a real dependency graph with cycle detection, not a full sweep); undo or redo of a value edit recomputes too. Formulas serialize under modules.enterprise.formulas in getState() and restore (and recompute) on setState().

Show formulas

Set showFormulas (the show-formulas attribute or the property) to display each allowFormula cell's source instead of its computed value, the spreadsheet "show formulas" view. The computed values are untouched, so turning it off restores the normal display; a user-provided cellTemplate is never overridden.

grid.showFormulas = true; // reveal sources; set back to false to restore

Export formulas

exportToCSV and exportToXLSX accept a formulas option: when set, cells that hold a formula export their source (=A1*B1) rather than the computed value; other cells export normally.

grid.exportToCSV({ filename: 'budget', formulas: true });
grid.exportToXLSX({ filename: 'budget', formulas: true });

Deferred

Range+criteria functions (SUMIF / COUNTIF / AVERAGEIF) need a different argument-grouping convention and are deferred to a later tier, along with array formulas and cross-sheet references.


Licensing

Licensing is offline and non-hostile: without a valid key the grid keeps working but renders a watermark and logs a console notice, with no network calls and no hard blocking. Set the key once (globally) before or after the grid renders:

import { ApexGridEnterprise } from 'apex-grid-enterprise';

ApexGridEnterprise.setLicense('APEX-…'); // removes the watermark on all instances

LicenseManager (re-exported from apex-commons) is also available for advanced use, but setLicense is the supported entry point.

Keys are ECDSA-signed. Verification is asynchronous (WebCrypto has no synchronous API) while the watermark decision is synchronous, so a structurally valid key is accepted provisionally and the grid re-checks when the signature verdict settles: a tampered key lifts the watermark for a microtask and then gets it back. The overlay itself is apex-commons' shared Watermark, the same one the other Apex products paint, and it keeps the license-watermark part for styling.

See also

  • apex-grid: the community grid with column configuration, virtualization, sorting, filtering, pagination, pinning, reordering, resizing, inline editing, selection, tree data, CSV export, theming, and accessibility.