# RevoGrid Pro Documentation
> Full normalized Markdown export of the RevoGrid Pro portal documentation.
Source: https://pro.rv-grid.com
---
# Welcome to the RevoGrid Pro documentation!
URL: https://pro.rv-grid.com/guides/
Source: src/content/docs/guides/index.mdx
Description: Learn how to leverage the advanced features of RevoGrid Pro, access detailed documentation, and make the most of our support and resources.
This guide is designed to help you understand and utilize the advanced features of RevoGrid Pro.
We are going to provide you with the tools and knowledge needed to build complex, high-performance grid applications. Our goal is to:
1. **Save Integration Time**: Provide you with comprehensive examples and documentation to streamline the integration of advanced features into your grid applications.
2. **Offer Pro-Level Examples**: Present real-world examples that demonstrate how to implement complex Pro-level features effectively.
3. **Enhance Customization**: Show you how to extend our existing plugins and create your own custom functionalities to suit your specific needs.
4. **Enable Advanced Understanding**: Open up the Grid Core functionality and provide deep insights for those who are interested in customizing and extending the grid at a fundamental level.
## How to Get Started
Most of the information you need is available in our docs portal.
Start with [Installation](/guides/installation), then review [key concepts from the main docs](https://rv-grid.com/guide/).
From there, jump to the available features, plugins, and examples.
To jump start your project, you can use our [RevoGrid Pro boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git), which include examples for all major frameworks.
These product examples provide additional tips and insights to help you manage complex data workflows.
## Use AI with RevoGrid Pro
If you are using Codex, Cursor, Claude Code, or VS Code MCP clients, start with the hosted RevoGrid MCP guide:
- [How to use RevoGrid MCP](/guides/ai-mcp)
- [AI Agent Documentation Sources](/guides/ai-agent-documentation)
### Repository layout
The source repository is a pnpm workspace. Pro and standalone product packages live under `packages/`, the documentation and runnable demo apps live under `apps/`, shared example sources live under `examples/`, and Playwright suites live under `tests/`.
- apps/
- portal/ Documentation website (Astro/Starlight)
- src/
- components/ Components rendered by documentation pages
- content/docs/ MDX documentation files
- demos/ Runnable demo catalog and framework previews
- src/
- catalog/ Demo catalog metadata
- preview/ Framework preview lifecycles
- examples/
- components/ Shared demo components used by docs and demos
- src/components/ Vanilla, React, Vue, and Angular examples
- angular/ Angular preview app and wrapper examples
- react/ React preview app and wrapper examples
- core/ Core example helpers
- packages/
- pro/ RevoGrid Pro package
- plugins/ Pro plugin source code
- src/ Package entrypoints and shared exports
- pivot/ Standalone Pivot package
- gantt/ Standalone Gantt package
- scheduler/ Standalone Scheduler package
- kanban/ Standalone Kanban package
- src/ Package entrypoints and shared exports
- revogrid/ Core grid library (Git submodule)
- tests/
- e2e-pro/ Pro browser tests
- e2e-enterprise/ Product integration browser tests
- e2e-demos/ Demo browser tests
- helpers/ Shared E2E helpers
- pnpm-workspace.yaml Workspace configuration
### Installation and Usage
1. Installation Instructions
To get started, follow the installation process in our [Installation Guide](/guides/installation).
This guide includes private npm, GitHub npm, pre-built release, and source-build options for Pro and standalone product packages.
2. Import Pro and Product Packages and CSS
After installation, import the packages and CSS based on the features you need.
Use Pro for advanced grid features, then add Pivot, Gantt, Scheduler, or Kanban from its standalone package.
Here is a typical setup:
```ts
// Pro
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import { RowOddPlugin } from '@revolist/revogrid-pro';
// Standalone Pivot product
import '@revolist/pivot/styles.css';
import { PivotPlugin } from '@revolist/pivot';
```
Make sure package styles are included so features render correctly.
3. Review the Documentation
Begin by exploring our detailed Pro documentation. We advice to start from simpler examples, then move to more advanced scenarios:
- [Data Synchronization](/guides/faq/data-sync)
- [Odd Rows](/guides/rows/row-odd/)
- [Custom Row Header](/guides/rows/row-advanced-header/)
- [Selection & Focus](/guides/selection/)
- [Column Selection](/guides/selection/column-selection/)
- [Multi-Range Selection](/guides/selection/multi-range-selection/)
- [Event Manager](/guides/data-manage/event-manager-explanation)
- [Collaborative Editing](/guides/data-manage/collaborative-editing/)
- [Collaborative Presence](/guides/data-manage/collaborative-presence/)
- [Pagination](/guides/grid-utilities/pagination)
- [Formula](/guides/formula/overview/)
- [Merge Cells](/guides/cells/cell-merge/)
- …
4. Implement Examples
Follow the provided examples to see how advanced features are implemented. Use these examples as a starting point for your own customizations.
5. Utilize Support Resources
If you encounter challenges or have questions, make use of our support options. Reach out for personalized assistance or schedule a call with one of our creators.
## Conclusion
With detailed examples, extensive documentation we aim to provide everything you need to build powerful, customized grid applications efficiently. Dive in, explore the possibilities, and take your grid solutions to the next level with RevoGrid Pro.
For any questions or additional support, don't hesitate to reach out.
---
# AI Agent Documentation Sources
URL: https://pro.rv-grid.com/guides/ai-agent-documentation/
Source: src/content/docs/guides/ai-agent-documentation.mdx
Description: Choose the right RevoGrid Pro documentation source for an AI agent: llms.txt, the full corpus, split skill references, an installable skill, or MCP.
RevoGrid Pro publishes several documentation formats for AI agents. They all describe the same portal documentation, but serve different retrieval workflows. Start with the smallest source that answers the task, then retrieve more context only when needed.
These resources provide guidance and examples. When an agent can inspect the project, local source code and public TypeScript types remain the final authority for implementation details.
## Choose a source
| Source | Best for | What the agent receives |
| --- | --- | --- |
| [`llms.txt`](https://pro.rv-grid.com/llms.txt) | First lookup and URL discovery | A compact Markdown index of high-signal guides, API references, demos, and links to larger resources. |
| [`llms-full.txt`](https://pro.rv-grid.com/llms-full.txt) | Offline context or a one-shot research task | The complete normalized Markdown documentation corpus. |
| [`llms-skill.txt`](https://pro.rv-grid.com/llms-skill.txt) | Skill-aware agents that fetch only relevant references | A `SKILL.md` index that routes the agent to topic-specific reference files. |
| [`llms-skill/`](https://pro.rv-grid.com/llms-skill/) | Selective, URL-based retrieval | The same split skill index and individual Markdown references under `llms-skill/references/`. |
| [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) | Installing reusable RevoGrid documentation context in a supported agent | A downloadable skill archive containing `SKILL.md` and its split reference files. |
| [RevoGrid MCP](/guides/ai-mcp) | Task-oriented structured retrieval | Core, Pro, and Enterprise docs, examples, feature resolution, migration notes, and typed plugin context. |
## Start with `llms.txt`
Fetch [`https://pro.rv-grid.com/llms.txt`](https://pro.rv-grid.com/llms.txt) when the agent needs to find the right RevoGrid page before reading it. The index favors high-signal pages, groups the remaining documentation by topic, and points to the full corpus and skill formats.
This is the best default for an agent that can follow links. It avoids placing the entire documentation corpus into context before the agent knows whether the task concerns Pivot, Gantt, filtering, or a core API.
```text
Read https://pro.rv-grid.com/llms.txt. Find the relevant RevoGrid Pro documentation,
then fetch only the pages needed for this task before proposing an implementation.
```
## Use the full corpus for broad context
[`https://pro.rv-grid.com/llms-full.txt`](https://pro.rv-grid.com/llms-full.txt) contains every portal documentation page as normalized Markdown. Each section includes its portal URL and source path, so an agent can trace guidance back to its documentation page.
Use it for a research or offline workflow that cannot follow many links. Avoid loading it by default if the agent has a limited context window; `llms.txt` or the split skill is usually more efficient.
## Use the split skill for selective retrieval
The split skill is designed for agents that understand `SKILL.md` instructions or can fetch Markdown files by URL.
- [`llms-skill.txt`](https://pro.rv-grid.com/llms-skill.txt) returns the root `SKILL.md` as plain text.
- [`llms-skill/`](https://pro.rv-grid.com/llms-skill/) returns the same index and exposes the individual references it links to.
- [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) packages that index and all reference files as a downloadable skill archive.
The index groups references by product area. Read the index first, then fetch only the referenced Markdown files that match the task. This gives an agent structured guidance without sending unrelated documentation into its context.
```text
Read https://pro.rv-grid.com/llms-skill.txt first. Follow only the references for
the feature I am implementing, and validate API details against local TypeScript types.
```
If your agent supports skill archives, install [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) to make the split documentation available as a reusable local skill. Otherwise, use the public `llms-skill.txt` URL and reference links directly.
## Use MCP for structured retrieval
The public files are documentation exports suited to direct HTTP retrieval. MCP
adds structured tools for agents that need examples, feature resolution, migration
notes, or typed plugin context. Both are token-free; MCP results label Pro and
Enterprise requirements explicitly.
For setup, supported clients, and example prompts, see [How to use RevoGrid MCP](/guides/ai-mcp).
## Recommended agent workflow
1. Fetch [`llms.txt`](https://pro.rv-grid.com/llms.txt) or read the installed split skill index.
2. Retrieve the smallest set of relevant guides and API references.
3. Query MCP for relevant Core or Pro examples, migrations, and typed context.
4. Inspect local source and public TypeScript types before writing or changing code.
5. Keep the implementation aligned with the retrieved documentation and the local project version.
This sequence gives agents quick discovery, focused context, and a final implementation check against the code they are changing.
---
# How to use RevoGrid MCP
URL: https://pro.rv-grid.com/guides/ai-mcp/
Source: src/content/docs/guides/ai-mcp.mdx
Description: Use the hosted RevoGrid MCP in Codex, Cursor, Claude Code, or VS Code to work with Core, Pro, and Enterprise docs, examples, plugins, migration notes, and typed API context.
RevoGrid MCP gives AI coding tools structured access to the unified RevoGrid
knowledge catalog:
```text
https://mcp.rv-grid.com
```
No MCP access token or authorization header is required. The same endpoint
searches Core, Pro, and Enterprise knowledge. Results retain `requiresPro`
metadata so agents can explain package and license requirements accurately.
RevoGrid has a wide API and plugin surface, with advanced features such as pivot,
event manager, history, specialized renderers, and custom editors. MCP helps an
AI tool retrieve the right implementation guidance before it writes code.
## What you can use it for
Use RevoGrid MCP when you want an AI tool to:
- find the best Core or Pro example for a specific framework
- determine whether a feature requires a Pro package before suggesting code
- build around typed plugin APIs instead of guessing event names or option shapes
- gather migration notes before upgrading a RevoGrid implementation
- work from real examples for pivot, export, filters, history, validation, or custom renderers
## Agent-readable documentation files
For public, text-only documentation sources—including the `llms.txt` index, full
corpus, split skill references, and downloadable skill—see
[AI Agent Documentation Sources](/guides/ai-agent-documentation).
Use MCP when your tool supports structured retrieval and typed context. Use the
public agent-readable files when it needs documentation it can fetch directly.
## Recommended setup
1. Add `https://mcp.rv-grid.com` to your AI coding tool.
2. Ask the agent to search RevoGrid MCP before writing code.
3. Ask it to validate the final implementation against RevoGrid types and examples.
4. If your project must remain Core-only, say so explicitly in the prompt.
## Install in AI coding tools
### Claude Code
```bash
claude mcp add --transport http revogrid https://mcp.rv-grid.com
```
### Codex
```bash
codex mcp add revogrid --url https://mcp.rv-grid.com
```
### Cursor
```json
{
"mcpServers": {
"RevoGrid": {
"url": "https://mcp.rv-grid.com",
"type": "http"
}
}
}
```
### VS Code
Install the `RevoGrid DataGrid MCP` extension, or configure the server directly:
```json
{
"servers": {
"RevoGrid MCP": {
"url": "https://mcp.rv-grid.com",
"type": "http"
}
},
"inputs": []
}
```
## How to prompt effectively
The best prompts tell the agent:
1. your framework
2. whether the solution may use Pro features
3. which RevoGrid behavior matters most
### Good prompt examples
- `Build a Vue RevoGrid Pro pivot table. Search MCP first, use the best matching example, and explain the required plugin configuration.`
- `Create a React grid with RevoGrid Pro history and validation. Use MCP docs and types before writing code.`
- `Find the best RevoGrid example for advanced filter headers in Angular and adapt it to server-side filtering.`
- `Check if this feature is available in Core or Pro, then recommend the best matching implementation path.`
- `Find a Core-only fallback for this Pro feature.`
## Best practices
Prompt the agent to inspect RevoGrid MCP before generating code. This reduces
invented APIs and helps it start from real examples.
Ask the agent to verify plugin names, event payloads, config fields, and feature
usage against RevoGrid types.
MCP knowledge is token-free, but using a Pro or Enterprise package still requires
the appropriate product access. Ask for a Core-only fallback when needed.
Ask the agent to use the best matching demo or guide as the starting point, then
adapt it to your app.
## Example workflow
1. Add `https://mcp.rv-grid.com` to your MCP client.
2. Open your RevoGrid project.
3. Ask the agent to find the best matching examples and docs.
4. Ask it to adapt the result to your framework and data model.
5. Ask it to verify the result against RevoGrid types before finalizing.
---
# Flashing Cells
URL: https://pro.rv-grid.com/guides/cells/cell-flash/
Source: src/content/docs/guides/cells/cell-flash.mdx
Description: Highlight edited or programmatically updated cells and rows in RevoGrid.
Cell flashing makes recent changes visible in editable grids, live dashboards, and audit workflows. The Pro `CellFlashPlugin` can react to edits from `EventManagerPlugin`, undo/redo changes from `HistoryPlugin`, manual `flashcell` events, or direct plugin method calls.
The demo below updates market-style rows, supports burst/live updates, row flashing, custom duration, manual row flash, and undo/redo.
## Enable Cell Flash
Add `CellFlashPlugin` and mark columns with `flash`. Existing boolean predicates still work, and `EventManagerPlugin` keeps edit events connected to the source update flow.
```ts
import { CellFlashPlugin, EventManagerPlugin } from '@revolist/revogrid-pro';
grid.plugins = [EventManagerPlugin, CellFlashPlugin];
grid.eventManager = { applyEventsToSource: true };
grid.columns = [
{
name: 'Price',
prop: 'price',
flash: () => true,
},
];
```
## Context-Aware Flashing
The `flash` callback receives the new value and a context object with the previous value, row model, row index, column, row type, and edit metadata. Return a boolean for simple behavior or a decision object to customize the flash.
```ts
grid.columns = [
{
name: 'Price',
prop: 'price',
flash: (value, context) => ({
flash: true,
rowFlash: Math.abs(Number(value) - Number(context.previousValue)) > 2,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
duration: 800,
rowDuration: 1200,
className: 'price-flash',
rowClassName: 'large-move-row',
}),
cellTemplate: cellFlashArrowTemplate({
symbols: { up: '+', down: '-', changed: '~' },
className: 'price-flash-arrow',
arrowClassName: 'price-flash-symbol',
directionClassNames: { up: 'positive', down: 'negative', changed: 'neutral' },
}),
},
];
```
## Configure Timing And Queueing
Use `grid.cellFlash` or `additionalData.cellFlash` to tune behavior. Defaults preserve legacy behavior: enabled, cell-only, 1000ms duration, replace active flashes on each flash event, and clear on source reset.
```ts
grid.cellFlash = {
duration: 700,
rowDuration: 1000,
mode: 'cell-and-row',
queue: 'merge',
maxActive: 300,
clearOnSourceChange: true,
respectReducedMotion: true,
aria: {
enabled: true,
live: 'polite',
atomic: true,
liveRegionClassName: 'rv-cell-flash-live-region',
},
labels: {
cellUpdated: state => `Updated ${String(state.prop)} in row ${state.rowIndex + 1}`,
rowUpdated: state => `Updated row ${state.rowIndex + 1}`,
},
className: 'cell-flash-active',
rowClassName: 'row-flash-active',
};
```
Queue modes:
- `replace` clears current flash state before applying the next batch.
- `merge` keeps existing flashes active until their own timers expire.
Announcement priority is: per-change `ariaLabel`, `aria.label`, `labels.cellUpdated` or `labels.rowUpdated`, then the built-in defaults. Use `labels` for normal localization and `aria.label` when one callback should fully own every announcement. The live-region politeness, atomic flag, and live-region class are also configurable through `aria`.
## Manual Flash API
Manual methods are available from the plugin instance. This is useful when updates come from a websocket, polling loop, or external calculation.
```ts
const plugins = await grid.getPlugins();
const cellFlash = plugins.find(plugin => plugin.constructor.name === 'CellFlashPlugin');
cellFlash.flashCell({
rowIndex: 0,
prop: 'price',
previousValue: 100,
value: 104,
direction: 'up',
});
cellFlash.flashRows({
rowIndex: 0,
rowClassName: 'manual-row-flash',
});
cellFlash.clearFlash({ rowIndex: 0, prop: 'price' });
```
The existing event API is still supported:
```ts
grid.dispatchEvent(new CustomEvent('flashcell', {
detail: {
data: { 0: { price: 104 } },
previousData: { 0: { price: 100 } },
type: 'rgRow',
},
}));
```
## Styling
The plugin sets `flash`, `flash-direction`, `data-flash-direction`, and duration CSS variables on active cells. Row flashes receive `flash-row` and the configured row class.
```css
revo-grid {
--rv-change-highlight: rgba(255, 238, 0, 0.6);
--rv-row-change-highlight: rgba(255, 238, 0, 0.22);
--rv-cell-flash-up-color: #15803d;
--rv-cell-flash-down-color: #dc2626;
--rv-cell-flash-arrow-transition-duration: 0.3s;
--rv-cell-flash-arrow-transition-timing: ease-in-out;
}
revo-grid[theme^='dark'] {
--rv-change-highlight: rgba(250, 204, 21, 0.34);
--rv-row-change-highlight: rgba(250, 204, 21, 0.16);
--rv-cell-flash-up-color: #4ade80;
--rv-cell-flash-down-color: #f87171;
}
revo-grid .rgCell[flash][flash-direction="up"] {
color: var(--rv-cell-flash-up-color);
}
revo-grid .rgRow[flash-row].large-move-row {
font-weight: 500;
}
```
When `respectReducedMotion` is enabled, users with reduced-motion preferences keep the flash attributes and final highlight state without the animation.
---
# Merge Cells
URL: https://pro.rv-grid.com/guides/cells/cell-merge/
Source: src/content/docs/guides/cells/cell-merge.mdx
Description: Combine adjacent cells into a single cell.
The Cell Merge feature allows you to combine adjacent body or pinned data cells into a single, larger cell, much like Excel's merge functionality. Use it to visually group related data within your grid.
For spreadsheet-style headers that span multiple rows or columns, use the [Multi Row Headers](/guides/columns/multi-row-header/) plugin instead.
### Why Use Cell Merge?
Cell Merge is a powerful tool that can help you:
- **Group Data Visually**: Highlight or group related data by merging cells, which improves the readability and interpretability of your grid.
### Example Use Cases
- **Data Grouping**: Use cell merging to visually segment related data or to emphasize specific sections of your grid.
### How It Works
The Cell Merge feature enables you to specify which cells should be merged into a larger cell. This larger cell will span across multiple columns and/or rows. The content of the merged cell will be centrally displayed, enhancing the layout of your grid.
Since this is a Pro feature, ensure that you have access to the Pro version of RevoGrid to utilize this functionality.
```javascript
// fixing render for multiframework
```
---
# Highlight Cells and Styling
URL: https://pro.rv-grid.com/guides/cells/cell-style/
Source: src/content/docs/guides/cells/cell-style.mdx
Description: Learn how to style cells dynamically based on their values and implement custom event handling.
Highlighting and styling cells dynamically, allows you to create visually appealing and informative data grids.
By utilizing cell properties and custom templates, you can style cells based on their values, manage interactions, and enhance user experience.
## Dynamic Cell Styling
One of the easiest ways to provide visual feedback to users is through dynamic cell styling.
You can apply styles based on the content of the cells, such as changing the background color depending on the value.
You can also manage various events directly within the cell using the `cellProperties`.
This allows you to add interactivity to your cells, such as handling clicks or double clicks.
### Interactive Example
Below is a live example demonstrating dynamic cell styling and event handling:
This example shows:
- Dynamic background colors based on cell values
- Click event handling
- Custom cell templates
In this example, the **Price** column's background color changes based on its value:
- **Grey** for the first column,
- **Red** for prices below 10,
- **Orange** for prices between 10 and 20,
- **Green** for prices above 20,
- Clicking on the **Price** cell will log the value to the console, while double-clicking will display an alert with the price.
## Using Cell Templates for Advanced Styling
If you need more control over the rendering of cells, you can utilize cell templates. This allows for complex HTML structures within cells while maintaining the ability to style dynamically.
In this case, the **Price** cell uses a custom template to render a `div` element, allowing for more advanced styling and content manipulation.
## Conclusion
Capabilities for highlighting and styling cells enhance the user experience by providing meaningful visual feedback and interactivity. By utilizing dynamic styling, event handling, and custom templates, you can create a rich and engaging data grid that meets your specific requirements.
---
# Chart and Visualization
URL: https://pro.rv-grid.com/guides/cells/charts/
Source: src/content/docs/guides/cells/charts.mdx
Description: Discover the wide range of charts and visual renderers available in RevoGrid.
RevoGrid supports a variety of visual renderers and chart types, enabling users to present data in visually engaging and intuitive formats. Below is a description of each available feature:
## Chart Configuration API
Each chart type supports the following common configuration options:
```typescript
interface ChartConfig {
name: string; // Column name
prop: string; // Data property name
minValue?: number; // Minimum value for normalization
maxValue?: number; // Maximum value for normalization
thresholds?: { // Visual thresholds for value ranges
value: number | string | boolean; // Threshold value
className: string; // CSS class for styling
}[];
progress?: (config: { model: any; data: any; prop: string }) => number; // Custom progress calculation
}
```
## Progress Line Renderer
Displays a horizontal progress bar within a cell to visually represent a value's progression relative to a maximum limit. Useful for showing percentages or task completion statuses.
### Configuration
```typescript
{
name: 'Linear',
prop: 'num',
minValue: 0,
maxValue: 100,
thresholds: [
{ value: 90, className: 'high' },
{ value: 50, className: 'medium' },
{ value: 0, className: 'low' }
],
cellTemplate: progressLineRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
}
```
## Progress Line with Value Renderer
Combines the progress line visualization with a numerical value label, offering both a visual and textual representation of the data.
### Configuration
```typescript
{
name: 'Linear with Value',
prop: 'num',
minValue: 0,
maxValue: 40,
thresholds: [
{ value: 90, className: 'high' },
{ value: 30, className: 'medium' },
{ value: 0, className: 'low' }
],
cellTemplate: progressLineWithValueRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
}
```
## Circular Progress Renderer
Displays a circular progress bar within a cell to visually represent a value's progression relative to a maximum limit. Useful for showing percentages or task completion statuses.
### Configuration
```typescript
{
name: 'Circular Progress',
prop: 'num',
cellTemplate: circularProgressRenderer
}
```
## Sparkline Renderer
Renders mini inline charts directly within cells to display trends or patterns in data over time, ideal for quick overviews of performance metrics.
### Configuration
```typescript
{
name: 'Sparkline',
prop: 'trend',
minValue: 0,
maxValue: 100,
cellTemplate: sparklineRenderer
}
```
## Bar Chart Renderer
Generates horizontal or vertical bar charts inside cells to compare values across categories, making it perfect for categorical or segmented data.
### Configuration
```typescript
{
name: 'Bars',
prop: 'trend',
minValue: 0,
maxValue: 100,
cellTemplate: barChartRenderer
}
```
## Timeline Renderer
Visualizes timelines within cells, enabling users to see task durations or event periods in an intuitive linear format.
### Configuration
```typescript
{
name: 'Timeline',
prop: 'timeline',
timelineRange: { start: 0, end: 100 },
cellTemplate: timelineRenderer
}
```
## Rating Star Renderer
Renders star icons to represent ratings, making it ideal for reviews or score-based data.
### Configuration
```typescript
{
name: 'Rating',
prop: 'rating',
maxStars: 5,
cellTemplate: ratingStarRenderer
}
```
## Badge Renderer
Displays badges with customizable fill and optional text colors. Data Grid Formatting can override the text color.
### Configuration
```typescript
{
name: 'Badge',
prop: 'status',
badgeStyles: {
Active: { backgroundColor: '#008620', color: '#fff' },
Pending: { backgroundColor: '#ff9800', color: '#fff' },
Completed: { backgroundColor: '#0068ba', color: '#fff' },
Failed: { backgroundColor: '#f44336', color: '#fff' }
},
cellTemplate: badgeRenderer
}
```
## Change Renderer
Highlights changes in data with visual cues such as arrows or color-coded indicators, helping users track trends or differences over time.
### Configuration
```typescript
{
name: 'Change',
prop: 'num',
cellTemplate: changeRenderer
}
```
## Thumbs Renderer
Displays thumbs-up or thumbs-down icons to indicate approvals, votes, or boolean-like states in a simple and user-friendly format.
### Configuration
```typescript
{
name: 'Thumbs',
prop: 'thumbs',
cellTemplate: thumbsRenderer
}
```
## Pie Chart Renderer
Renders pie charts inside cells to display proportional data distribution, ideal for visualizing parts of a whole within a dataset.
### Configuration
```typescript
{
name: 'Pie',
prop: 'trend',
cellTemplate: pieChartRenderer
}
```
---
# Heatmap/Coldmap Feature
URL: https://pro.rv-grid.com/guides/cells/heatmap/
Source: src/content/docs/guides/cells/heatmap.mdx
Description: Enhance your RevoGrid with dynamic heatmap and coldmap visualizations.
The **Heatmap Feature** provides dynamic color-based visualizations for grid cells, making it easy to interpret numerical data at a glance. By mapping cell values to color gradients, this feature enhances data visibility and helps users quickly identify patterns.
---
### Key Highlights
- **Heatmap and Coldmap Modes**:
- **Heatmap**: Red-to-green gradient for highlighting data intensity.
- **Coldmap**: Shades of blue for cooler, more subtle visualizations.
- **Customizable Value Ranges**: Automatically normalizes values between a defined `minValue` and `maxValue`.
- **WCAG-Compliant Font Colors**: Ensures text remains readable by dynamically adjusting font color (black/white) based on the background's luminance.
---
### Example Configuration
To enable the heatmap feature, set a custom cell template on the desired column:
```javascript
import { heatmapRenderer } from './heatmap';
grid.columns = [
{
name: 'Score',
prop: 'score',
minValue: 0, // Minimum value for normalization
maxValue: 100, // Maximum value for normalization
colorMap: 'heatmap', // Choose 'heatmap' or 'coldmap'
cellTemplate: heatmapRenderer, // Apply the heatmap renderer
},
];
```
---
# Multi Renderer
URL: https://pro.rv-grid.com/guides/cells/multi-renderer/
Source: src/content/docs/guides/cells/multi-renderer.mdx
Description: Display different types of data in the same column using multiple renderers.
The Multi Renderer feature allows you to display different types of data in the same column using different renderers based on the data type. This creates a more flexible and dynamic way to present your data.
### Why Use Multi Renderer?
Multi Renderer is particularly useful when you want to:
- **Handle Mixed Data Types**: Display different types of data (numbers, dates, sliders) in the same column
- **Create Dynamic Presentations**: Automatically choose the best way to display each value
- **Improve Data Visualization**: Use appropriate visualizations for different data types
### Example Use Cases
- **Mixed Data Columns**: When a column contains different types of data (e.g., numbers, dates, percentages)
- **Dynamic Formatting**: Automatically format values based on their type or content
- **Conditional Rendering**: Show different visualizations based on data conditions
### How It Works
The Multi Renderer works by:
1. Defining a set of renderers with conditions that determine when to use them
2. For each cell value, checking the conditions to determine which renderer to use
3. Applying the appropriate renderer to display the data
4. If no condition matches, using a default renderer
### Configuration
To use Multi Renderer, configure your column with the appropriate renderers and conditions:
```typescript
import { MultiColumn } from '@revolist/revogrid-pro';
import { editorSlider, editorCheckbox } from '@revolist/revogrid-pro';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import DateColumnType from '@revolist/revogrid-column-date';
const numberType = new NumberColumnType('0.00');
const dateType = new DateColumnType();
const grid = document.createElement('revo-grid');
grid.columnTypes = {
multi: MultiColumn,
};
grid.columns = [
{
prop: 'data',
name: 'Mixed Data',
columnType: 'multi',
multiColumn: {
defaultRenderer: numberType.cellTemplate,
defaultEditor: numberType.editor,
renderers: [
{
// Use dateRenderer for date strings
condition: (value) => value instanceof Date ||
(typeof value === 'string' && !isNaN(Date.parse(value))),
renderer: dateType.cellTemplate,
editor: dateType.editor,
},
{
// Use sliderRenderer for numbers between 0 and 100
condition: (value) => typeof value === 'number' && value >= 0 && value <= 100,
renderer: editorSlider,
editor: editorSlider,
},
{
// Use checkboxRenderer for boolean values
condition: (value) => typeof value === 'boolean',
renderer: editorCheckbox,
editor: editorCheckbox,
},
],
},
},
];
```
### Configuration Options
The MultiColumn configuration accepts the following options:
```typescript
interface MultiColumnConfig {
/**
* Default renderer to use if no condition matches
*/
defaultRenderer: ColumnRegular['cellTemplate'];
/**
* Default editor to use if no condition matches
*/
defaultEditor?: EditorCtr;
/**
* Array of renderers with conditions
*/
renderers: Array<{
/**
* Condition function that returns true if the renderer should be used
*/
condition: (value: any) => boolean;
/**
* Renderer to use if the condition is true
*/
renderer: ColumnRegular['cellTemplate'];
/**
* Optional editor to use if the condition is true
*/
editor?: EditorCtr;
}>;
}
```
### Example Data
```typescript
const data = [
{ data: 42 }, // Will use numberRenderer
{ data: new Date() }, // Will use dateRenderer
{ data: 75 }, // Will use sliderRenderer
{ data: '2024-03-15' }, // Will use dateRenderer
{ data: true }, // Will use checkboxRenderer
];
```
### Best Practices
1. **Order Matters**: Place more specific conditions before general ones
2. **Default Renderer**: Always provide a default renderer for unmatched cases
3. **Type Checking**: Use proper type checking in conditions
4. **Editor Matching**: Match editors with their corresponding renderers
5. **Performance**: Keep condition functions simple and efficient
### Common Patterns
#### Date Detection
```typescript
condition: (value) => value instanceof Date ||
(typeof value === 'string' && !isNaN(Date.parse(value)))
```
#### Numeric Range
```typescript
condition: (value) => typeof value === 'number' && value >= 0 && value <= 100
```
#### Boolean Values
```typescript
condition: (value) => typeof value === 'boolean'
```
#### String Pattern
```typescript
condition: (value) => typeof value === 'string' && value.startsWith('http')
```
---
# Same Value Merge
URL: https://pro.rv-grid.com/guides/cells/same-value-merge/
Source: src/content/docs/guides/cells/same-value-merge.mdx
Description: Automatically merge cells with identical values in a column.
The Same Value Merge feature automatically combines cells in a column when they contain identical values. This creates a cleaner, more organized view of your data by visually grouping repeated values.
### Why Use Same Value Merge?
Same Value Merge is particularly useful when you want to:
- **Reduce Visual Clutter**: Hide repeated values in a column, making the data easier to read
- **Highlight Data Patterns**: Quickly identify groups of identical values
- **Improve Data Presentation**: Create a cleaner, more professional look for your grids
### Example Use Cases
- **Category Grouping**: When displaying items grouped by category, merge cells with the same category name
- **Status Display**: Merge cells with the same status to make state changes more apparent
- **Hierarchical Data**: Visually group related items in hierarchical data structures
### How It Works
The Same Value Merge plugin works by:
1. Checking each cell against the nearest comparable visible rows above and below it in columns marked with `merge: true`
2. If the values are identical, repeated values are hidden and internal borders are removed
3. This creates a visual effect of merged cells while maintaining the original data structure
When row grouping or Pivot row drill-down inserts group rows, those rows participate in the comparison only when they carry the merged column's value. Deeper group rows without that column value are skipped.
Same Value Merge is a visual merge. It does not create real merged grid ranges and does not change selection, focus, copy, or edit behavior. Use the Cell Merge plugin when you need explicit row or column spans.
### Configuration
To enable Same Value Merge for specific columns, set the `merge` property to `true` in your column configuration:
```javascript
const columns = [
{ prop: 'id', name: 'ID' },
{ prop: 'category', name: 'Category', merge: true }, // Enable merging for this column
{ prop: 'name', name: 'Name' }
];
```
### Sticky Merge Start Cells
Use `merge: { sticky: true }` when the first visible cell in a same-value run should stay visible while users scroll. This marks only the merge start cell as sticky; it does not pin every cell in that row.
```ts
import {
SameValueMergePlugin,
StickyCellsPlugin,
} from '@revolist/revogrid-pro';
const columns = [
{ prop: 'id', name: 'ID' },
{
prop: 'region',
name: 'Region',
merge: { sticky: true },
},
{ prop: 'category', name: 'Category' },
];
grid.plugins = [SameValueMergePlugin, StickyCellsPlugin];
grid.columns = columns;
```
`SameValueMergePlugin` can register Sticky Cells automatically for `merge.sticky`, but adding `StickyCellsPlugin` explicitly keeps the dependency visible and matches the standalone Sticky Cells setup.
### Grouped Merge Boundaries
Some datasets need repeated values to merge only inside another column's run. For example, a `quantity` column may contain the same value across many rows, but it should stop merging when the controlling `workcenter` or operation changes.
Use a merge group with one leader column:
```javascript
const columns = [
{ prop: 'batch', name: 'Batch', merge: true },
{ prop: 'material', name: 'Material' },
{ prop: 'operationNo', name: 'Operation' },
{
prop: 'workcenter',
name: 'Work Center',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['material', 'operationNo'],
},
},
{
prop: 'quantity',
name: 'Quantity',
merge: { mergeGroup: 'operation' },
},
{
prop: 'tool',
name: 'Tool',
merge: { mergeGroup: 'operation' },
},
];
```
`workcenter` is the leader for the `operation` group. It merges only while its own value, `material`, and `operationNo` all match. `quantity` and `tool` are followers: they still require their own values to match, but they cannot merge beyond the leader's boundary.
### Merge Configuration
```ts
type SameValueMergeConfig = {
mergeGroup?: string;
mergeLeader?: boolean;
mergeDependsOn?: ColumnProp[];
sticky?: boolean;
};
```
- `mergeGroup` connects columns that should share a leader boundary.
- `mergeLeader` marks the column that defines the boundary for a group.
- `mergeDependsOn` adds row fields that must match before the configured column can merge.
- `sticky` marks merge start cells for Sticky Cells. Only cells rendered as the start of a merge run become sticky.
If a column uses `merge: { mergeGroup: '...' }` and no matching leader is found, it falls back to ordinary same-value merge behavior.
---
# Sticky Cells
URL: https://pro.rv-grid.com/guides/cells/sticky-cells/
Source: src/content/docs/guides/cells/sticky-cells.mdx
Description: Pin selected row cells near the top after their source row starts scrolling out of view.
Sticky Cells keep important checkpoint cells visible while users scroll through a virtualized grid. Mark cells with `stickyCell`; when the row starts crossing the top visibility edge, the marked row renders through the pinned top row area and is trimmed from the main viewport until the next sticky row replaces it.
## Usage
Add `StickyCellsPlugin` to the grid and define a column `stickyCell` predicate.
```ts
import { StickyCellsPlugin } from '@revolist/revogrid-pro';
const stickyCell = ({ model }) => model.isCheckpoint === true;
const columns = [
{
name: 'Account',
prop: 'account',
stickyCell,
},
{
name: 'Status',
prop: 'status',
stickyCell,
},
];
grid.plugins = [StickyCellsPlugin];
```
If any cell in a row is marked as sticky, that row can become the active sticky row.
## With Other Header Plugins
Sticky Cells render through the header layer, so the plugin can be combined with grouped headers and filter headers. When using the filter header plugin, register filtering before sticky cells so Sticky Cells wraps the final header content:
```ts
import {
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
];
grid.filter = {};
grid.additionalData = {
stretch: 'all',
};
```
## Behavior
- One sticky row is active by default. Set `stickyCells.maxRows` to keep multiple recent sticky rows pinned.
- A marked row becomes sticky once 10% of that source row has scrolled above the viewport top.
- Sticky cells work across `colPinStart`, `rgCol`, and `colPinEnd`.
- Custom `cellTemplate` output is reused in the pinned sticky row.
- Existing `pinnedTopSource` rows remain before plugin-managed sticky rows.
- Active sticky source rows are trimmed out of the main row viewport.
- Plugin-managed sticky pinned rows receive the `sticky-cells-pinned-row` class and attribute. They also expose `data-sticky-cells-pinned-row="true"`, `data-sticky-cells-source-index`, and `data-sticky-cells-mode`.
```css
revo-grid .sticky-cells-pinned-row {
font-weight: 500;
}
```
`data-sticky-cells-mode` is `"row"` for ordinary Sticky Cells rows and `"cell"` for integrations that pin only selected cells, such as `merge: { sticky: true }` in Same Value Merge.
Sticky Cells are a display layer. Selection, editing, and focus behavior still belong to the underlying grid cells.
---
# Column Collapse
URL: https://pro.rv-grid.com/guides/columns/column-collapse/
Source: src/content/docs/guides/columns/column-collapse.mdx
Description: Make grouped columns collapsible with the ability to show/hide child columns
The Column Collapse plugin allows you to make grouped columns collapsible.
When a group is collapsed, only columns marked as "sealed" remain visible, while other columns are hidden.
If no sealed column exists, the group falls back to a single placeholder column that keeps the expand affordance visible.
## Features
- Add collapse/expand functionality to column groups
- Maintain "sealed" columns visible even when group is collapsed
- Fall back to a single placeholder column when no sealed child exists
- Support for nested column groups
- Customizable initial collapse state
- Smooth animations and intuitive UI
## Installation
The Column Collapse plugin is included in the RevoGrid Pro package. To use it, simply import and add it to your grid's plugins:
```javascript
import { ColumnCollapsePlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnCollapsePlugin];
```
## Usage
To make a column group collapsible:
1. Add the `collapsible` property to the group configuration
2. Mark important columns as `sealed` to keep them visible when collapsed
3. Optionally provide `placeholder` on the group to customize the fallback placeholder header or cell
```javascript
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnCollapsePlugin];
// Configure columns with collapsible groups
grid.columns = [
{
id: 'personalInfo',
name: 'Personal Information',
collapsible: true, // Enable collapse for this group
children: [
{
prop: 'name',
name: 'Name',
sealed: true // This column will stay visible when collapsed
},
{
prop: 'email',
name: 'Email'
},
{
prop: 'phone',
name: 'Phone'
}
]
},
{
id: 'addressInfo',
name: 'Address',
collapsible: true,
children: [
{
prop: 'street',
name: 'Street',
sealed: true
},
{
prop: 'city',
name: 'City'
},
{
prop: 'country',
name: 'Country'
}
]
}
];
// Optional placeholder customization when no sealed column exists
grid.columns.push({
name: 'Contact',
collapsible: true,
collapsed: true,
placeholder: {
header: 'Contact hidden',
cellTemplate: (h) => h('span', undefined, 'Expand to view'),
},
children: [
{ prop: 'altEmail', name: 'Alt Email' },
{ prop: 'mobile', name: 'Mobile' },
],
});
// Sample data
grid.source = [
{
name: 'John Doe',
email: 'john@example.com',
phone: '123-456-7890',
street: '123 Main St',
city: 'New York',
country: 'USA'
},
// ... more rows
];
```
## Best Practices
1. Use `sealed` for essential columns that should always be visible
2. Keep group names concise to avoid UI clutter
3. Use `placeholder` when you want custom fallback content for groups without sealed children
4. Use nested groups sparingly to maintain clarity
5. Consider your users' needs when deciding which columns to make collapsible
The Column Collapse plugin helps manage complex data grids by allowing users to focus on relevant information while keeping essential data visible at all times.
---
# Column Hide
URL: https://pro.rv-grid.com/guides/columns/column-hide/
Source: src/content/docs/guides/columns/column-hide.mdx
Description: Implementing Column Hiding
RevoGrid provides the ability to hide specific columns in your grid, allowing you to focus on the most relevant data for your users.
This feature is particularly useful when working with large datasets where not all columns need to be visible at once.
`Column hiding` is a feature that allows you to hide specific columns in your grid, making them invisible to users. This is useful for:
- Focusing on specific data points
- Reducing visual clutter
- Creating different views of the same dataset
- Implementing column visibility toggles
The `ColumnHidePlugin` supports three ways to hide columns:
## Using the `hide-columns` attribute
You can specify which columns to hide using the `hide-columns` attribute on your RevoGrid element:
```html
```
## Using the `hideColumns` property
Framework wrappers typically bind properties instead of attributes:
```tsx
```
You can also update it at runtime:
```javascript
grid.hideColumns = ['name', 'age'];
```
## Using `additionalData.hiddenColumns`
You can also hide columns programmatically using the `additionalData` property:
```javascript
const grid = document.querySelector('revo-grid');
grid.additionalData = {
hiddenColumns: ['name', 'age']
};
```
This approach is particularly useful when you need to dynamically update which columns are hidden based on user interactions or application state.
## Events
The `ColumnHidePlugin` emits a `hiddencolumnsupdated` event whenever the hidden columns are updated:
```javascript
grid.addEventListener('hiddencolumnsupdated', (event) => {
const { hiddenColumns } = event.detail;
console.log('Hidden columns updated:', hiddenColumns);
});
```
This event can be used to synchronize the UI with the current state of hidden columns.
## Implementation Details
The `ColumnHidePlugin` works by:
1. Tracking which columns should be hidden in a `Set`
2. Updating the column visibility when the configuration changes
3. Preserving the hidden state when columns are updated
4. Supporting attribute, property, and programmatic configuration
---
# Column Move With Groups
URL: https://pro.rv-grid.com/guides/columns/column-move-with-groups/
Source: src/content/docs/guides/columns/column-move-with-groups.mdx
Description: Move grouped column headers without breaking the column-group structure.
`ColumnMoveAdvancedPlugin` enables drag-and-drop for grouped column headers. It builds on RevoGrid's core column move behavior and adds the group-aware rules needed for `ColumnGrouping` columns.
Use it when your grid has nested column groups and users need to reorder whole header bands without manually rebuilding the `columns` array.
## Setup
Import the plugin from `@revolist/revogrid-pro` and register it in the grid `plugins` array.
```ts
import { ColumnMoveAdvancedPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnMoveAdvancedPlugin];
grid.columns = [
{
name: 'Personal Information',
children: [
{ prop: 'firstName', name: 'First Name' },
{ prop: 'lastName', name: 'Last Name' },
{ prop: 'age', name: 'Age' },
],
},
{
name: 'Address',
children: [
{ prop: 'street', name: 'Street' },
{ prop: 'city', name: 'City' },
{ prop: 'country', name: 'Country' },
],
},
];
```
`ColumnMoveAdvancedPlugin` extends the core `ColumnMovePlugin` implementation, so you do not need a separate column-move plugin entry for the common setup.
## Column Groups
Groups use the standard RevoGrid `ColumnGrouping` shape: a header object with a `name` and a `children` array. Children can be regular columns or another nested group.
```ts
grid.columns = [
{
name: 'Customer',
children: [
{ prop: 'name', name: 'Name' },
{
name: 'Contact',
children: [
{ prop: 'email', name: 'Email' },
{ prop: 'phone', name: 'Phone' },
],
},
],
},
{
name: 'Location',
children: [
{ prop: 'city', name: 'City' },
{ prop: 'country', name: 'Country' },
],
},
];
```
Leaf columns keep their normal column behavior: data binding, templates, sorting, filtering, resizing, and editing still belong to the regular column definitions.
## Move Behavior
Dragging a group header moves all leaf columns owned by that group as one block. During the drag, the drop indicator snaps to valid group boundaries instead of arbitrary leaf-column positions.
When the move completes, the plugin remaps the internal group indexes for every group level. This is the part plain `ColumnMovePlugin` does not handle for grouped headers.
Dragging an individual leaf column still works, but the plugin keeps the column inside its current group at the active grouping depth. This prevents a single leaf column from being dropped into a different group and leaving the header tree inconsistent.
Move a group by dragging the group header. Move a single leaf column by dragging the leaf header. Leaf-column moves are constrained to the compatible group area.
## Events
The plugin uses the standard column drag event names.
| Event | When it fires | Cancelable |
| --- | --- | --- |
| `columndragstart` | Before drag state is created. The detail is the dragged column or group data. | Yes |
| `beforecolumndragend` | Before the order is committed. The detail includes `startPosition`, `newPosition`, and moved items. | Yes |
| `columndragend` | After a successful drag flow finishes. | No |
```ts
grid.addEventListener('columndragstart', (event) => {
const data = event.detail;
if ('children' in data) {
console.log('Group drag started:', data.name);
}
});
grid.addEventListener('beforecolumndragend', (event) => {
const { startPosition, newPosition } = event.detail;
if (!canMoveColumns(startPosition, newPosition)) {
event.preventDefault();
}
});
```
## Framework Usage
For framework wrappers, pass the plugin class through the wrapper's `plugins` prop or input.
```tsx
import { RevoGrid } from '@revolist/react-datagrid';
import { ColumnMoveAdvancedPlugin } from '@revolist/revogrid-pro';
export function CustomerGrid({ rows, columns }) {
return (
);
}
```
```vue
```
## Compatibility
- Works with the standard nested `ColumnGrouping` structure.
- Supports nested group levels by remapping indexes across all group levels after a group move.
- Preserves regular leaf-column drag behavior, constrained to compatible group boundaries.
- Ignores row-header drag attempts.
- Uses the same `columndragstart`, `beforecolumndragend`, and `columndragend` event names as core column moving.
Use [Multi Row Headers](/guides/columns/multi-row-header/) when you need spreadsheet-style multi-level header spanning. Use [Column Collapse](/guides/columns/column-collapse/) when the same grouped headers should collapse or expand.
---
# Column Stretch
URL: https://pro.rv-grid.com/guides/columns/column-stretch/
Source: src/content/docs/guides/columns/column-stretch.mdx
Description: Implementing Column Stretching.
RevoGrid provides the ability to automatically adjust the width of columns to fit the table's width.
This is particularly useful when you have fewer columns than needed to enable the horizontal scrollbar, and you want to distribute the available space evenly or according to specific criteria.
In this article, we’ll walk you through the process of implementing column stretching in RevoGrid using the `ColumnStretchPlugin`. We'll also explore the different configurations available for stretching the columns.
## What is it?
`Column stretching` is a feature which allows you to automatically adjust the width of columns to fit the entire width of the table. The width of each column is calculated based on the number of columns and their respective sizes, ensuring the grid's width is fully utilized. This feature is especially useful when working with a small number of columns.
### Stretch Configurations
The Column Stretch plugin may conflict with other column size configurations. The following cases will prevent column stretching:
- Columns with explicit `size` property set
- Columns with `autoSize` enabled
- When the total width of all columns exceeds the available space (negative remaining width)
In these cases, the stretch configuration will be ignored for those specific columns to preserve their intended sizing behavior.
RevoGrid supports different types of stretch configurations through the `StretchConfig` type:
- **`'none'`**: No column stretching will be applied. Columns retain their default width.
- **`'last'`**: Only the last column will stretch to fill the remaining width.
- **`'all'`**: All columns will stretch evenly to fill the table's width.
- **`number`**: A specific column (by index) will stretch to fill the remaining width.
- To stretch only the last column:
```javascript
grid.additionalData = { stretch: 'last' };
```
- To stretch a specific column by index (e.g., the second column):
```javascript
grid.additionalData = { stretch: 1 };
```
- To stretch all columns evenly:
```javascript
grid.additionalData = { stretch: 'all' };
```
By using the `ColumnStretchPlugin`, you can easily manage the width of columns in RevoGrid, making sure your table layout looks clean and is fully utilized. Whether you want to stretch all columns evenly or just the last one, this plugin offers flexibility to suit your needs.
Experiment with the different stretch configurations to see which works best for your data grid!
---
# Column Type Renderer
URL: https://pro.rv-grid.com/guides/columns/column-type-renderer/
Source: src/content/docs/guides/columns/column-type-renderer.mdx
Description: Show type icons in column headers with columnTypeRenderer.
`columnTypeRenderer` is a Pro column header template that displays an icon next to the header label based on the column's `columnType`.
Use it when users should quickly recognize typed fields, such as IDs, numeric values, currency amounts, decimals, booleans, or text columns. It is assigned through `columnTemplate`, while `columnType` tells the renderer which icon to show.
```typescript
import { columnTypeRenderer } from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'id',
name: 'Identifier',
columnType: 'id',
columnTemplate: columnTypeRenderer,
},
{
prop: 'amount',
name: 'Amount',
columnType: 'currency',
columnTemplate: columnTypeRenderer,
},
];
```
Supported built-in icons are selected for `id`, `integer`, `currency`, `decimal`, and `boolean`. Any other value, or a missing `columnType`, falls back to the string icon.
`columnTypeRenderer` only changes the column header rendering. Cell formatting, editing, validation, and export behavior still come from the column definition or from entries registered in `grid.columnTypes`.
---
# Multi Row Headers
URL: https://pro.rv-grid.com/guides/columns/multi-row-header/
Source: src/content/docs/guides/columns/multi-row-header.mdx
Description: Render spreadsheet-style multi-level headers from nested column groups.
The Multi Row Header plugin renders banded, multi-level headers from the same nested `ColumnGrouping` structure RevoGrid already uses for grouped columns. Use it when shallow leaf columns should fill the unused header depth while deeper branches continue to show stacked group bands.
This is the header-focused feature to use for AG Grid or DevExtreme-style multi-level headers. It is separate from `CellMergePlugin`, which only merges body and pinned data cells.
## Setup
```ts
import { MultiRowHeaderPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [MultiRowHeaderPlugin];
grid.multiRowHeader = true;
```
## Nested Columns
Define headers with the existing `children` column-group structure. Leaf columns still own data binding, sorting, filtering, editing, templates, and resizing.
```ts
grid.columns = [
{
name: 'Group A',
children: [
{ name: 'Athlete 1', prop: 'athlete' },
{
name: 'Group B',
children: [
{ name: 'Country 1', prop: 'country' },
{
name: 'Group C',
children: [
{ name: 'Sport 1', prop: 'sport' },
],
},
],
},
],
},
];
```
## Header Spanning
By default, shallow leaf headers span unused group rows. This matches the common spreadsheet behavior where a leaf header under a shallower branch fills the remaining vertical header space.
Disable spanning for a single column when you want balanced empty group space above the leaf:
```ts
grid.columns = [
{
name: 'Balanced',
children: [
{
name: 'Flat Leaf',
prop: 'flat',
suppressSpanHeaderHeight: true,
},
{
name: 'Nested',
children: [{ name: 'Nested Leaf', prop: 'nested' }],
},
],
},
];
```
You can also disable spanning for the whole plugin:
```ts
grid.multiRowHeader = {
spanLeafHeaders: false,
};
```
## Group Header Spanning
Set `spanHeaderHeight` on a column group when that group should fill every
remaining grouped-header row plus the leaf-header row:
```ts
grid.columns = [
{
name: 'January',
spanHeaderHeight: true,
children: [
{ name: 'Period Total', prop: 'januaryTotal' },
],
},
{
name: 'February',
children: [
{ name: 'Sales', prop: 'februarySales' },
{ name: 'Profit', prop: 'februaryProfit' },
],
},
];
```
The descendant leaf remains the data column, but its header is covered by the
spanning group. Explicit group spans are independent of `spanLeafHeaders`.
Pivot uses this option for generated collapsed column groups. With
`MultiRowHeaderPlugin` enabled, the collapsed group label fills the remaining
header depth. Without it, the regular collapsed placeholder header remains
visible as a fallback.
When Pivot row-axis headers contain sorting, selection counts, or filter
controls, keep those leaf headers at their normal height while retaining
collapsed group spanning:
```ts
grid.multiRowHeader = {
spanLeafHeaders: false,
};
```
`spanHeaderHeight` remains active because explicit group spanning is
independent of `spanLeafHeaders`.
## Configuration
```ts
grid.multiRowHeader = {
spanLeafHeaders: true,
};
```
| Option | Default | Description |
| --- | --- | --- |
| `spanLeafHeaders` | `true` | Lets shallow leaf headers fill unused group depth. |
## Compatibility
- Works with nested `ColumnGrouping` definitions.
- Leaf headers keep sorting, filtering, custom templates, and resize behavior.
- Group headers continue through RevoGrid's normal group header render hooks.
- `ColumnCollapsePlugin` can be registered with `MultiRowHeaderPlugin` to add expand/collapse controls to groups.
- `spanHeaderHeight` lets a terminal group fill the remaining header depth while retaining its descendant data column.
- Pinned columns can split a group into pinned and unpinned visible bands, matching the behavior used by major data grids.
---
# Blank Values
URL: https://pro.rv-grid.com/guides/data-filter/blank-values/
Source: src/content/docs/guides/data-filter/blank-values.mdx
Description: Configure source-preserving blank semantics for Core and Pro filters without changing saved operator IDs.
RevoGrid filtering preserves the source value and own-property presence. It does
not implicitly turn `null`, `undefined`, `false`, `0`, arrays, or missing
properties into an empty display string before evaluating filters.
## Default policy
| Source state | Blank by default |
| --- | --- |
| `null` | Yes |
| Owned `undefined` | Yes |
| Exactly `""` | Yes |
| Missing own property | Yes |
| Whitespace-only string | No |
| `false` | No |
| `0` | No |
| `NaN` | No |
| `[]` | No |
| Non-empty array | No |
| Object or other value | No |
Only an own property counts as present. A value inherited through the row
prototype is treated as missing. `null` and `undefined` can both match **Is
blank** while remaining distinct raw values.
The stable saved-state IDs remain `empty` and `notEmpty`; no state migration is
required. Their default labels are **Is blank** and **Is not blank**, and **Is
not blank** is the exact inverse of the resolved blank predicate.
## Configuration and precedence
Set the grid policy in the filter configuration. A column can override any
individual field; resolution is Core defaults, then grid policy, then column
policy.
```ts
grid.filter = {
blankSemantics: {
whitespaceOnlyString: true,
emptyArray: true,
isBlank(value, context, fallbackResult) {
// Final application override after the configured fallback.
return value === 'N/A' || fallbackResult;
},
},
};
grid.columns = [{
prop: 'tags',
filter: ['array'],
blankSemantics: {
// Other fields continue to inherit from the grid policy.
emptyArray: false,
},
}];
```
The callback receives the raw source value, row model, column, property, parsed
value, own-property status, effective policy, and fallback result.
## Parsers and typed operators
A `cellParser` still supplies the value used by ordinary comparisons. Blank
evaluation uses the source value and missing-property context captured before
the parser runs.
Typed operators remain strict. `isTrue` matches only `true`; `isFalse` matches
only `false`; `isEmptyArray` and `isNotEmptyArray` match only arrays. Enabling
`emptyArray: true` intentionally allows `[]` to match both **Is blank** and **Is
empty array**. Invalid nonblank date values, including `false` and `NaN`, do not
match date comparisons and are not blank by default.
## Related guides
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Custom Filter Operators
URL: https://pro.rv-grid.com/guides/data-filter/custom-filter-operators/
Source: src/content/docs/guides/data-filter/custom-filter-operators.mdx
Description: Add application-specific filter predicates, value controls, parsed-value normalization, AST conditions, and remote filtering workflows.
Surface: grid
Use a custom filter operator when the standard text and number comparisons do not express an application rule. Custom operators use the Core `customFilters` configuration and work with both `FilterPlugin` and Pro's `AdvanceFilterPlugin`.
A custom operator has three parts:
1. A stable operator ID, used by saved filter state and Pro `FilterAst` conditions.
2. A `columnFilterType` identifying the column family that can show the operator.
3. A synchronous predicate that returns `true` when a row should remain visible.
## Quick start: add an operator to a built-in family
This operator appears with the ordinary string operators because its `columnFilterType` is `string` and the column uses `filter: 'string'`:
```ts
import type { LogicFunction } from '@revolist/revogrid';
const endsWithCode: LogicFunction = (value, expected) => {
const suffix = String(expected ?? '')
.trim()
.toLowerCase();
if (!suffix) return true;
return String(value ?? '')
.toLowerCase()
.endsWith(suffix);
};
endsWithCode.extra = 'input';
grid.columns = [
{ prop: 'sku', name: 'SKU', filter: 'string' },
{ prop: 'name', name: 'Product', filter: 'string' },
];
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
};
```
Open the SKU filter, choose **Ends with code**, and enter a suffix. Returning `true` for an empty input keeps dynamic editing from temporarily hiding every row.
Use a custom family when an operator should appear only on explicitly opted-in columns:
```ts
const matchesPriority: LogicFunction = (value, expected) =>
!expected || String(value).toLowerCase() === String(expected).toLowerCase();
matchesPriority.extra = 'input';
grid.columns = [
{ prop: 'priority', name: 'Priority', filter: 'ticketPriority' },
];
grid.filter = {
customFilters: {
matchesPriority: {
columnFilterType: 'ticketPriority',
name: 'Matches priority',
func: matchesPriority,
},
},
};
```
The family ID must match exactly. An operator registered for `ticketPriority` is not offered by a column configured with `filter: 'string'`.
## Choose the value control
Set `func.extra` to tell the standard filter panel how to collect the operator value:
| `func.extra` | Panel control | Typical use |
| --------------- | --------------------------------- | -------------------------------------------------- |
| `'input'` | Text input | Codes, thresholds, and search values |
| `'datepicker'` | Native date input | One calendar-date value |
| Render function | Application-defined VNode content | Selects or compound values |
| Omitted | No value control | Valueless rules whose predicate ignores `expected` |
For example, change the quick-start operator to a date input without replacing the filter panel:
```ts
const onOrAfter: LogicFunction = (value, expected) => {
if (!expected) return true;
return new Date(value).getTime() >= new Date(String(expected)).getTime();
};
onOrAfter.extra = 'datepicker';
```
A custom renderer receives the current value and controlled callbacks. Call `onInput()` with the next filter value; do not mutate the filter item directly:
```ts
import type { ExtraField } from '@revolist/revogrid';
const priorityControl: ExtraField = (h, context) =>
h(
'select',
{
value: context.value ?? '',
onInput: (event: Event) =>
context.onInput((event.target as HTMLSelectElement).value),
onFocus: context.onFocus,
},
[
h('option', { value: '' }, 'Any priority'),
h('option', { value: 'low' }, 'Low'),
h('option', { value: 'high' }, 'High'),
],
);
matchesPriority.extra = priorityControl;
```
Use `include` only when the entire filter popup should be restricted to a known list of operator IDs. It is global to the filter configuration, so omitting another required built-in or custom ID removes that operation from the available families.
## Parsed values and evaluation context
RevoGrid calls a predicate as `func(parsedValue, conditionValue, context)`. When the column defines `cellParser`, both built-in and custom comparisons receive its result:
```ts
const within: LogicFunction = (value, expected) => {
const target = Number(expected);
return !Number.isFinite(target) || Math.abs(Number(value) - target) <= 5;
};
within.extra = 'input';
grid.columns = [
{
prop: 'price',
name: 'Price',
filter: 'number',
cellParser: (model) =>
Number(String(model.price ?? '').replace(/[^0-9.-]/g, '')),
},
];
grid.filter = {
customFilters: {
withinFive: {
columnFilterType: 'number',
name: 'Within 5',
func: within,
},
},
};
```
Use `cellParser` for reusable normalization rather than repeating source conversion in every predicate. The optional third argument provides source-aware details when a rule needs them:
```ts
const isOriginalNull: LogicFunction = (_value, _expected, context) =>
context?.hasOwnProperty === true && context.sourceValue === null;
```
The context includes `model`, `column`, `property`, `sourceValue`, `parsedValue`, `hasOwnProperty`, and the resolved blank policy. Ordinary custom predicates receive `parsedValue`; Core blank operators intentionally use the original source value. See [Blank Values](/guides/data-filter/blank-values/) for that distinction.
## Restore and combine custom conditions
Use the operator ID in the same compatible state model as a built-in condition:
```ts
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
multiFilterItems: {
sku: [
{
id: 1,
type: 'endsWithCode',
value: '-EU',
relation: 'and',
},
],
},
};
```
Keep `customFilters` present whenever restoring its conditions. Unknown operation IDs cannot be evaluated and are not a portable replacement for registering the predicate.
## Use the operator in Filter AST
Pro accepts an application-defined AST operator only when the same ID is registered in `customFilters`. The condition value must also be JSON-safe:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
};
const plugins = await grid.getPlugins();
const filter = plugins.find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
await filter.setFilterAst({
type: 'condition',
field: 'sku',
operator: 'endsWithCode',
valueType: 'string',
value: '-EU',
});
```
Registering the predicate supplies both validation and execution for that custom AST operator. Without the registration, `setFilterAst()` rejects the tree as containing an unknown operator and leaves the previous filter active.
## Predicate or structured filter type?
| Requirement | Extension point |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Add one Boolean row test using the standard condition UI | `customFilters` predicate |
| Normalize stored data for several operators | Column `cellParser` |
| Add a small input, date, or custom field beside one condition | `func.extra` |
| Build a purpose-specific Pro popup body that owns several operators or a structured JSON value | `StructuredFilterType` plus matching `customFilters` predicates |
| Load the filtered dataset from a backend | Cancel `beforefilterapply` and reload `source` |
A custom structured type does not replace its evaluator. Its `operatorIds` define the UI-owned conditions, while matching `customFilters` entries define how those conditions evaluate. See [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/) for the registry and commit contract.
## Keep asynchronous work outside predicates
Predicates are synchronous and may run once per row for every active condition. Keep them pure and fast: avoid requests, DOM work, shared-state mutation, and large allocations. Declaring a predicate `async` returns a `Promise`, not a deferred Boolean result, and therefore does not implement asynchronous filtering.
When the backend owns filtering, cancel local evaluation synchronously in `beforefilterapply`, serialize the requested state, and let the application's data controller replace the source:
```ts
let latestRequest = 0;
grid.addEventListener('beforefilterapply', (event) => {
event.preventDefault();
const request = ++latestRequest;
const detail = (
event as CustomEvent<{ filterItems: Record }>
).detail;
void fetch('/api/orders/filter', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(detail.filterItems),
})
.then((response) => response.json())
.then((rows) => {
if (request === latestRequest) grid.source = rows;
});
});
```
Production controllers should also cancel stale requests and guard source replacement from triggering the same remote request again. For Pro, prefer the canonical `FilterAst` payload when the query can contain cross-column OR, nested groups, or NOT. See [Filter Events](/guides/data-filter/filter-events/) for a guarded remote example.
The direct `JSON.stringify()` above is appropriate for this scalar custom value. Compatible filter state from other filter families can contain values such as `Set`; use the persistence guidance in [Filter State and Presets](/guides/data-filter/filter-state/) before treating arbitrary `multiFilterItems` as a wire payload.
## Common mistakes
- Registering the operator under `customFilters` but using a different `columnFilterType` from the column's `filter` value.
- Forgetting `func.extra`, then expecting the standard popup to render an input.
- Returning a truthy `Promise` from an `async` predicate instead of handling remote work through `beforefilterapply`.
- Filtering formatted source strings in every predicate instead of normalizing once with `cellParser`.
- Mutating rows, filter state, or external state from a predicate that can run many times.
- Restoring a custom `multiFilterItems` or `FilterAst` operator without registering its predicate first.
- Using a custom renderer for a complete multi-operator popup experience; use a structured filter type for that scope.
- Putting `Date`, `Set`, functions, cycles, or non-finite numbers in a public `FilterAst` value; AST input must be JSON-safe.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Core filtering guide](https://rv-grid.com/guide/filters#create-a-custom-filter)
---
# Canonical Filter AST
URL: https://pro.rv-grid.com/guides/data-filter/filter-ast/
Source: src/content/docs/guides/data-filter/filter-ast.mdx
Description: Build, validate, edit, persist, and transport one canonical RevoGrid Pro filter tree.
Surface: grid
`FilterAst` is RevoGrid Pro's validated, JSON-safe Boolean filter tree. Use the same tree for local row evaluation, the grouped visual editor, saved views, and remote requests.
New to ASTs? Start with [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/) for a plain-language explanation, business use cases, and a smaller first example. This page is the complete technical contract.
> **Choose the simplest state model that fits.** Use `multiFilterItems` for ordinary per-column filtering and existing column controls. Use `FilterAst` for cross-column `OR`, nested groups, `NOT`, canonical saved views, or a transport-safe remote query. Canonical execution requires `AdvanceFilterPlugin`; Core `FilterPlugin` remains the right choice for ordinary Core filtering.
## Try the complete lifecycle
Start with **Nested routing**, inspect the effective tree and transport envelope, then type a quick search and apply another preset with **Preserve quick search** off and on. **Projectable** demonstrates a tree that ordinary column controls can reopen. **Try invalid AST** proves that validation leaves the active tree and visible rows unchanged. Open any column filter and select **Groups** to edit the canonical tree visually.
## Register the Pro plugin
```ts
import { AdvanceFilterPlugin, type FilterAst } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = { groupedFilter: {} }; // optional Groups launcher
grid.columns = columns;
grid.source = rows;
const plugin = (await grid.getPlugins()).find(
(item) => item instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
```
Retrieve the runtime plugin from `await grid.getPlugins()`. Do not attach application callbacks or custom methods to the grid element.
## Build a tree
A condition is the smallest tree:
```ts
const openTickets: FilterAst = {
type: 'condition',
field: 'status',
operator: 'equal',
valueType: 'string',
value: 'Open',
};
```
Join children with `and` or `or`, then negate any subtree with `not`:
```ts
const routingRule: FilterAst = {
type: 'group',
operator: 'or',
children: [
{
type: 'group',
operator: 'and',
children: [
openTickets,
{
type: 'condition',
field: 'priority',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 3,
},
],
},
{
type: 'not',
child: {
type: 'condition',
field: 'vip',
operator: 'isTrue',
valueType: 'boolean',
},
},
],
};
```
Groups must contain at least one child. Evaluation short-circuits: `and` stops on the first false child, and `or` stops on the first true child.
## Node and value contract
| Node | Required shape | Meaning |
| ----------- | ---------------------------------------------- | ---------------------------------------------------------------------- |
| `condition` | `{ type, field, operator, valueType, value? }` | Evaluates one field. `field` accepts a string or numeric `ColumnProp`. |
| `group` | `{ type, operator: 'and' \| 'or', children }` | Joins one or more subtrees. |
| `not` | `{ type, child }` | Negates exactly one subtree. |
`valueType` is one of `string`, `number`, `boolean`, `date`, `datetime`, `array`, or `unknown`. It describes the operand representation; it does not cast source values. Validation enforces scalar/date/datetime representations directly, while `array` and `unknown` rely on the selected operator's shape rules plus JSON safety. Values may contain only finite JSON primitives, arrays, and plain objects. Public input rejects functions, `undefined` operands, `Date`, `Set`, cycles, non-plain objects, and `NaN`/infinities.
- Date operands use `YYYY-MM-DD`.
- Ordinary datetime operands use UTC ISO strings ending in `Z`, such as `2026-09-03T08:30:00Z`.
- Use `unknown` for an opaque, JSON-safe structured operand. It does not disable JSON validation.
- Omit `evaluationMode` in application-authored canonical state. `selectionExclusion`, `dateObject`, `coercedNumericRange`, and `localTemporal` are compatibility metadata emitted when older UI/config models need evaluator-specific behavior.
When `field` matches a grid column, the local evaluator uses the column's parsed cell value. A field without a column reads the raw row property, which makes hidden model fields usable without adding hidden grid columns.
## Built-in operator reference
Every ID below is exported through `CANONICAL_FILTER_OPERATORS`.
| Family | Operators | `valueType` and operand |
| ---------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Text and equality | `equal`, `notEqual`, `beginsWith`, `contains`, `quickContains`, `notContains` | Usually `string`; one required JSON value matching the declared type. Equality may use another declared scalar type. |
| Ordered and numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` | Usually `number`; one required finite number. |
| Range | `between` | Usually `number`; a required two-item array. Numeric row values are strict in canonical mode. |
| Membership | `in`, `notIn` | `array`; a required array. An empty array is structurally valid. |
| Blank, Boolean, array | `isBlank`, `isNotBlank`, `isTrue`, `isFalse`, `isEmptyArray`, `isNotEmptyArray` | No operand. Keep the field's semantic `valueType`, but omit `value`. |
| Explicit date/datetime | `dateEquals`, `dateBefore`, `dateAfter`, `dateOnOrBefore`, `dateOnOrAfter`, `dateNotEqual` | `date` or `datetime`; one required date/datetime string. |
| Date range | `dateBetween` | `date` or `datetime`; a required two-item array of valid date/datetime strings. |
| Relative day/window | `today`, `yesterday`, `last7Days`, `next30Days` | `date` or `datetime`; omit `value`. |
| Relative week/month | `thisWeek`, `lastWeek`, `nextWeek`, `thisMonth`, `lastMonth` | `date` or `datetime`; omit `value`. |
| Relative quarter/year | `thisQuarter`, `nextQuarter`, `previousQuarter`, `thisYear`, `nextYear`, `previousYear` | `date` or `datetime`; omit `value`. |
| Fiscal quarter | `thisFiscalQuarter`, `nextFiscalQuarter`, `previousFiscalQuarter` | `date` or `datetime`; omit `value`. |
| Fiscal year | `thisFiscalYear`, `nextFiscalYear`, `previousFiscalYear` | `date` or `datetime`; omit `value`. |
Important operator boundaries:
- Valueless operators must omit `value`. Even `value: ''` is rejected.
- Canonical `in`/`notIn` use strict `Object.is` membership; an array cell matches when any member matches. `selectionExclusion` is a normalized, exclusion-oriented compatibility mode and is not the same contract as hand-authored canonical `notIn`.
- Numeric `between` accepts only numeric row values in canonical mode. Blank-like values and numeric strings are not numbers. Compatibility state may carry `coercedNumericRange` to preserve older slider behavior.
- `quickContains` is the canonical form of global quick search and retains quick-filter string coercion. Use ordinary `contains` for Core text-filter semantics.
- Relative operators use one `now` captured for the whole apply operation plus the resolved IANA timezone and fiscal policy. See [Date Filter](/guides/data-filter/filter-types/date-filter/) and [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) for temporal policy.
- A custom or structured operator ID is valid only when the resolved plugin configuration registers its evaluator. Its operand must still be JSON-safe.
## Apply, read, initialize, and clear
```ts
await plugin.setFilterAst(routingRule);
const effective = plugin.getFilterAst(); // defensive clone
await plugin.setFilterAst(undefined); // clear canonical state
```
`setFilterAst()` validates first and replaces the tree atomically. `getFilterAst()` returns a defensive clone of the effective tree, including an applied quick-filter branch. Mutating either the input object after application or a returned object does not update grid state; construct a replacement and call `setFilterAst()` again.
For initial state, assign the AST through `grid.filter`:
```ts
grid.filter = {
filterAst: routingRule,
multiFilterItems: previousColumnState,
collection: legacyCollection,
};
```
When `filterAst`, `multiFilterItems`, and `collection` coexist, `filterAst` wins.
## Projectable and canonical-only trees
`projectable` means “can be represented as `MultiFilterItem` column controls,” not “valid.” A root `and` with independent single-field clauses can usually project:
```json
{
"type": "group",
"operator": "and",
"children": [
{
"type": "condition",
"field": "status",
"operator": "equal",
"valueType": "string",
"value": "Open"
},
{
"type": "condition",
"field": "priority",
"operator": "greaterThan",
"valueType": "number",
"value": 2
}
]
}
```
A cross-field `or`, `not`, or unsupported shape remains canonical-only:
```json
{
"type": "group",
"operator": "or",
"children": [
{
"type": "condition",
"field": "status",
"operator": "equal",
"valueType": "string",
"value": "Open"
},
{
"type": "not",
"child": {
"type": "condition",
"field": "vip",
"operator": "isTrue",
"valueType": "boolean"
}
}
]
}
```
Both trees are locally executable and transport-safe. Only the first can round-trip through ordinary independent column controls.
## Enable the visual grouped editor
```ts
grid.filter = { groupedFilter: {} };
```
`groupedFilter` opts the filter popup into a **Groups** launcher. The editor supports nested groups, `NOT`, rule reordering, rule/text mirrors, validation, preview counts, **Apply**, **Cancel**, and **Reset**. Edits are transactional: Apply replaces the active tree, Cancel leaves it untouched, and Reset clears the draft until applied.
Local preview counts stop at 25,000 eligible rows. For partial or remote data, never present the loaded page as the full dataset:
```ts
grid.filter = {
groupedFilter: {
remote: true,
preview: async ({ ast, signal }) => {
const response = await fetch('/tickets/filter-preview', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ filter: ast }),
});
return response.json() as Promise<{ matching: number; total: number }>;
},
},
};
```
The callback receives an `AbortSignal`; abort superseded requests and let abort errors propagate normally. `remote: true` without a callback displays a remote preview label rather than a misleading local total.
## Save, restore, and update by replacement
```ts
const saved = JSON.stringify(plugin.getFilterAst());
localStorage.setItem('ticket-filter', saved);
try {
const json = localStorage.getItem('ticket-filter');
await plugin.setFilterAst(json ? JSON.parse(json) : undefined);
} catch (error) {
console.error('The saved filter is invalid.', error);
}
```
To update one part, clone/build the next tree and reapply it:
```ts
const current = plugin.getFilterAst();
if (current?.type === 'group') {
await plugin.setFilterAst({
...current,
children: [...current.children, anotherCondition],
});
}
```
Do not mutate applied or returned objects. Compatibility conversion helpers preserve today's existing filter behavior, but they are not a guarantee that every future Pro operator will losslessly round-trip through an older saved-view schema. Persist the canonical tree you actually apply.
## Compose quick search
```ts
grid.quickFilter = { text: 'timeout', debounceMs: 0 };
await plugin.setFilterAst(routingRule);
// Default: clears the current or pending quick filter.
grid.quickFilter = { text: 'timeout', debounceMs: 0 };
await plugin.setFilterAst(routingRule, { preserveQuickFilter: true });
// Effective tree is routingRule AND the generated quickContains branch.
```
`getFilterAst()` returns that effective composition. Preserve mode uses the latest applied or pending `grid.quickFilter` value.
## Events and remote ownership
`filterastchange` fires immediately before the existing cancelable `beforefilterapply` event. Its detail contains `{ filterAst, executionContext, origin, projectable }`; `origin` is `config`, `api`, `ui`, `quickFilter`, or `clear`.
```ts
import type {
FilterAstChangeEventDetail,
FilterExecutionContext,
} from '@revolist/revogrid-pro';
type FilterRequest = {
filter: FilterAstChangeEventDetail['filterAst'];
context: FilterExecutionContext;
};
let request: FilterRequest | undefined;
grid.addEventListener('filterastchange', (event) => {
const { filterAst, executionContext, origin, projectable } = event.detail;
request = { filter: filterAst, context: executionContext };
console.log({ origin, projectable, request });
});
grid.addEventListener('beforefilterapply', (event) => {
// This event carries matching filterAst/executionContext semantic inputs.
// Cancel when the remote datasource owns filtering and row replacement.
event.preventDefault();
void fetch('/tickets/search', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
});
```
`executionContext` contains one ISO `now`, an IANA `timeZone`, and JSON-safe resolved blank semantics per column. A non-serializable custom `isBlank` callback appears only as `customEvaluator: true`; the backend must implement an equivalent policy. RevoGrid intentionally does not compile AST to SQL, REST, or GraphQL—that translation belongs to the application/backend boundary.
## Atomic validation and diagnostics
Invalid input rejects without partially updating rows or state:
```ts
grid.addEventListener('filterasterror', (event) => {
const { attemptedAst, diagnostics, origin } = event.detail;
console.table(diagnostics); // [{ path, code, message }]
reportInvalidSavedView({ attemptedAst, diagnostics, origin });
});
try {
await plugin.setFilterAst({
type: 'group',
operator: 'and',
children: [],
} as FilterAst);
} catch {
// Previous AST and visible rows are still active.
}
```
Paths use JSONPath-like locations such as `$.children[0].value`.
| Code | Cause |
| ------------------------ | ------------------------------------------------------------------------- |
| `invalid-node` | A node is not an object. |
| `max-depth` | Nesting exceeds 100 levels. |
| `cycle` | The AST node graph contains a cycle. |
| `invalid-group-operator` | A group operator is not `and` or `or`. |
| `empty-group` | A group has no children. |
| `missing-child` | A `not` node has no child. |
| `invalid-discriminator` | `type` is not `condition`, `group`, or `not`. |
| `invalid-field` | `field` is not a string or number. |
| `invalid-operator` | `operator` is missing or empty. |
| `unknown-operator` | No built-in or registered evaluator matches the ID. |
| `invalid-value-type` | `valueType` is not supported. |
| `unexpected-value` | A valueless operator received `value`. |
| `missing-value` | A value-taking operator omitted `value`. |
| `invalid-array-value` | A range is not a two-item array, or membership is not an array. |
| `value-type-mismatch` | The operand does not match its declared type. |
| `non-finite-number` | A number is `NaN` or infinite. |
| `non-json-value` | A value contains a function, `undefined`, or another non-JSON value. |
| `value-cycle` | An operand array/object contains a cycle. |
| `non-plain-object` | An operand is a class instance, `Date`, `Set`, or other non-plain object. |
Registered structured filter types may also return `invalid-structured-value` when their own operand validator rejects a value.
## Parsed fields, blanks, dates, and custom operators
Parsed columns evaluate their parsed cell value; hidden/raw model fields evaluate the raw property. Blank operators additionally preserve Core's source identity, property-presence check, and resolved grid/column blank policy. If local policy uses `blankSemantics.isBlank`, remote code sees `customEvaluator: true` and must reproduce it.
Relative time uses the operation's captured clock rather than calling `new Date()` for every row. Configure timezones, week starts, and fiscal periods through the [Date Filter](/guides/data-filter/filter-types/date-filter/) and [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) settings.
Register an evaluator before applying its canonical ID:
```ts
grid.filter = {
customFilters: {
riskAtLeast: {
columnFilterType: 'number',
name: 'Risk at least',
func: (value, operand) => {
const minimum = Number((operand as { minimum: number }).minimum);
return typeof value === 'number' && value >= minimum;
},
},
},
};
await plugin.setFilterAst({
type: 'condition',
field: 'internalRisk', // not a displayed column
operator: 'riskAtLeast',
valueType: 'unknown',
value: { minimum: 7 },
});
```
Custom predicates run synchronously in the filtering hot path. Keep them pure: avoid DOM access, requests, mutation, and avoidable allocation. Group ordering matters for short-circuit performance, so put cheap/selective clauses early when practical. A remote or virtual datasource must evaluate the canonical request against the whole backend dataset—not only the page currently loaded in the browser.
## Common mistakes
- Using `FilterAst` for a simple column filter that `multiFilterItems` already represents.
- Supplying `value: ''` to a valueless operator instead of omitting `value`.
- Expecting `valueType` to cast source data.
- Sending `Date`, `Set`, `undefined`, non-finite numbers, class instances, or cyclic objects.
- Treating `projectable: false` as invalid instead of canonical-only.
- Mutating the original or returned AST instead of applying a replacement.
- Forgetting that `setFilterAst()` clears quick search unless preservation is requested.
- Evaluating only the currently loaded page for a remote dataset.
- Translating relative dates remotely without the event's exact execution context.
Continue with [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/) for the conceptual and business overview, [Filtering Quick Start](/guides/data-filter/filter-basics/) for column-state basics, [Advanced Filtering Overview](/guides/data-filter/filter-showcase/) for the wider Pro filtering surface, [Filter State and Presets](/guides/data-filter/filter-state/) for view persistence, [Filter Events](/guides/data-filter/filter-events/) for lifecycle integration, and [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) for custom UI and evaluator patterns.
---
# Filter AST Side Panel
URL: https://pro.rv-grid.com/guides/data-filter/filter-ast-side-panel/
Source: src/content/docs/guides/data-filter/filter-ast-side-panel.mdx
Description: Mount RevoGrid Pro's canonical grouped filter editor in an application side panel.
Surface: grid
The grouped Filter AST editor is not limited to a column popup. Mount the same editor in a persistent side panel, drawer, or inspector and keep the grid plugin responsible for configured fields and operators, validation, preview, and applying the canonical tree.
Resolve the installed plugin after the grid initializes, mount the editor into a connected host, and destroy the returned handle with your framework component:
```ts
const plugin = (await grid.getPlugins()).find(
item => item instanceof AdvanceFilterPlugin,
);
const editor = plugin.mountFilterAstEditor(editorHost, {
preset: 'fixed-list',
conditionSlots: initialAst.children,
});
// Component teardown
editor.destroy();
```
When the mounted editor is the only filtering surface, set
`filter.columnFilterButton` to `false`. This removes the compact button and its
hover/focus layout reservation from every column header while leaving filtering,
the active AST, and the mounted editor enabled.
The `fixed-list` preset accepts a flat root **AND** tree of predeclared conditions. Pass those definitions as `conditionSlots` so their field and predicate remain visible after clearing. Fields stay fixed, while each predicate badge can select another operator supported by that column. Each active row has a compact, accessible clear action. Clearing removes its value from the draft and omits the condition from the canonical AST; entering or selecting a new valid value applies it again. A cleared valueless predicate, such as “VIP is false,” makes its predicate badge the explicit Apply action. **Clear all** clears every slot, applies `undefined`, and restores every grid row without removing the predefined controls.
Choose reusable value controls in the column configuration. The example renders
Priority’s compatible scalar numeric predicates with the shared single-value
slider without adding editor configuration at the mount site:
```ts
{
name: 'Priority',
prop: 'priority',
filter: 'number',
filterAstValueEditor: { kind: 'slider', min: 1, max: 5, step: 1 },
}
```
Omitted slider bounds are derived from the grid source. Use mount-level
`valueEditors` only when a particular surface needs a field- or
field/operator-specific override; those overrides take precedence over the
column default without changing the canonical predicate.
The preset keeps each field fixed as a plain anchor, presents each read-only operator phrase as a compact theme-aware badge, exposes only the configured value editor, omits placeholder boxes for operators that need no value, and applies effective changes after a short debounce. Builder chrome, grouping, add/remove/reorder actions, mode switch, and footer are intentionally absent. `editor.reset(ast)` remains the explicit synchronization boundary: it maps conditions in the supplied effective AST back onto the stable slots and clears omitted slots. Successful changes continue through the existing `filterastchange` event.
Use the default `builder` preset for the complete nested AND/OR/NOT rule builder, or `read-only` to inspect a tree without mutation. Nested configuration can override structure, capabilities, presentation, workflow, and interaction independently; incompatible flat trees are rejected rather than silently flattened.
This surface reuses the grouped editor's configured operators, selection sources, structured filter mounts, localization, validation, preview, and canonical apply path. It does not embed every complete classic per-column popup; reusable controls converge through the same lower-level filter contracts.
The mounted editor belongs to its application host and inherits the public `--rv-filter-ast-editor-*` variables defined there. Use `surface-bg`, `surface-color`, `control-bg`, `text-color`, and the corresponding `control-border`, `divider`, `active-*`, or `muted-color` variables to align it with the surrounding application theme. Unset variables use the editor's standalone defaults.
For the underlying model and business case, read [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/). For the complete API and operator contract, continue with [Canonical Filter AST](/guides/data-filter/filter-ast/).
---
# Filter Badges
URL: https://pro.rv-grid.com/guides/data-filter/filter-badges/
Source: src/content/docs/guides/data-filter/filter-badges.mdx
Description: Show synchronized active-filter badges, customize their labels and details, or replace their complete rendering.
Surface: grid
Filter badges give users an always-visible summary of the active filter model. `AdvanceFilterPlugin` owns the badge list, refreshes it when filters change, and routes remove and clear actions through the same filtering workflow as the popup and programmatic APIs.
## Quick start
Register `AdvanceFilterPlugin`, then enable its plugin-owned badge list through the grid property:
```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = true;
grid.filterBadges = true;
```
The plugin mounts the list in the grid's header slot. Set `grid.filterBadges = false` to remove it. You can also assign an options object instead of `true`:
```typescript
grid.filterBadges = {
ariaLabel: 'Order filters',
emptyLabel: 'All orders are visible',
className: 'order-filter-list',
badgeClassName: 'order-filter-badge',
};
```
The list starts with **No active filters** by default. Each active condition becomes its own badge; multiple conditions for one column remain separate and include their `AND` or `OR` relation in the default label.
## Remove and clear filters
Every default badge has a remove button. Removing one condition preserves the remaining conditions and deletes the column entry when its last condition is removed. The badge list stays synchronized even when `beforefilterapply` is canceled for remote filtering.
The default renderer does not add a **Clear all** button. To clear filters programmatically, use the active plugin:
```typescript
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin | undefined;
await filterPlugin?.clearFiltering();
```
A complete custom renderer also receives a `clear()` action. Its `items` expose individual `remove()` actions:
```typescript
grid.filterBadges = {
render: ({ items, clear }) => {
const region = document.createElement('section');
region.setAttribute('aria-label', 'Active filters');
const list = document.createElement('div');
list.setAttribute('role', 'list');
for (const item of items) {
const entry = document.createElement('div');
entry.setAttribute('role', 'listitem');
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.textContent = `${item.label} — remove`;
removeButton.addEventListener('click', () => void item.remove());
entry.append(removeButton);
list.append(entry);
}
const clearButton = document.createElement('button');
clearButton.type = 'button';
clearButton.textContent = 'Clear all filters';
clearButton.disabled = items.length === 0;
clearButton.addEventListener('click', () => void clear());
region.append(list, clearButton);
return region;
},
};
```
## Default labels and details
Default labels use the column name, localized operator name, and a compact value summary. Selection filters report the number of excluded values, ranges show their endpoints, and structured filter types can supply their own compact summaries.
When a condition has a longer explanation, the badge includes an info button. It toggles a plain-text tooltip, exposes `aria-controls` and `aria-expanded`, and closes on Escape. Unknown object values are summarized without exposing transport JSON.
Use `formatLabel` and `formatDetails` when the default badge structure is correct but its text is application-specific:
```typescript
grid.filterBadges = {
formatLabel: ({ column, prop, operatorName, filter, index }) => {
const name = column?.name ?? String(prop);
const relation = index ? `${filter.relation?.toUpperCase() ?? 'AND'} ` : '';
return `${name}: ${relation}${operatorName}`;
},
formatDetails: ({ filter }) =>
`Applied value: ${String(filter.value ?? 'none')}`,
removeAriaLabel: (item) => `Remove condition ${item.label}`,
detailsAriaLabel: (item) => `Explain condition ${item.label}`,
};
```
Both format callbacks receive `prop`, `filter`, the condition `index`, the matching `column` when available, and `operatorName`. `formatDetails` should return an empty string to suppress the info control; returning `undefined` allows the built-in details to be used.
## Accessibility and styling options
The default renderer provides these semantics:
- The root has `aria-label="Active filters"` and `aria-live="polite"`.
- An empty list uses `role="status"`; an active list uses `role="list"` with `role="listitem"` badges.
- Remove buttons default to `Remove {label} filter`.
- Details buttons default to `Show details for {label}`.
Customize the root label with `ariaLabel`, the empty message with `emptyLabel`, and the two button labels with `removeAriaLabel` and `detailsAriaLabel`. Add classes without replacing the built-in styles through `className`, `badgeClassName`, `removeButtonClassName`, and `emptyClassName`.
The default shell owns these semantics. A complete `render` callback replaces the list and releases its managed `role`, so your renderer must provide suitable roles, labels, focus behavior, and controls.
## Customize badge content
`renderBadge` replaces only the label content inside the default badge shell. The plugin still renders the list semantics, optional details control, and remove button:
```typescript
grid.filterBadges = {
renderBadge: ({ item }) => {
const strong = document.createElement('strong');
strong.textContent = item.label;
return strong;
},
};
```
Use `renderEmpty` to replace only the content inside the default empty-state element:
```typescript
grid.filterBadges = {
renderEmpty: () => {
const message = document.createElement('span');
message.textContent = 'No filters — showing every row';
return message;
},
};
```
`renderBadge`, `renderEmpty`, and `render` can return DOM nodes, a `DocumentFragment`, numbers, or nested arrays of supported values. Returned strings are always inserted as text, never parsed as HTML:
```typescript
grid.filterBadges = {
renderBadge: ({ item }) => [item.label, 'text, not markup '],
};
```
To render HTML or Markdown, sanitize it with your application's renderer and return the resulting DOM nodes or fragment. RevoGrid does not parse renderer strings as markup.
## Observe badge-model changes
`onChange` runs after each badge refresh, including the initial render. It receives the current read-only badge items:
```typescript
grid.filterBadges = {
onChange: (items) => {
activeFilterCount.textContent = String(items.length);
},
};
```
Each item contains a render key, label, optional details, source filter context, and `remove()` action. Treat the array and items as read-only; update filtering through the provided actions or the filter plugin APIs.
## Options reference
| Option | Purpose |
| ----------------------- | --------------------------------------------------------------------------------- |
| `className` | Adds classes to the plugin-owned root. |
| `badgeClassName` | Adds classes to each default badge. |
| `removeButtonClassName` | Adds classes to each default remove button. |
| `emptyClassName` | Adds classes to the default empty-state element. |
| `ariaLabel` | Labels the live badge region. |
| `emptyLabel` | Replaces the default **No active filters** message. |
| `removeAriaLabel` | Produces the accessible label for a remove button. |
| `detailsAriaLabel` | Produces the accessible label for a details button. |
| `formatLabel` | Produces the badge's plain-text label. |
| `formatDetails` | Produces the complete plain-text details explanation. |
| `renderBadge` | Replaces label content while retaining the default shell and actions. |
| `renderEmpty` | Replaces content inside the default empty-state shell. |
| `render` | Replaces the complete list; use `item.remove()` and `clear()` to retain behavior. |
| `onChange` | Observes the current badge items after every refresh. |
## Common mistakes
- Registering `filterBadges` without `AdvanceFilterPlugin`. The property is implemented and managed by that plugin.
- Expecting a string returned by a render callback to become HTML. Strings are intentionally text-safe; return trusted DOM nodes for rich content.
- Using full `render` without wiring `item.remove()` and `clear()`. Replacing the complete UI also makes your application responsible for its controls.
- Assuming the default renderer includes a clear-all action. It provides per-condition removal; add a custom control or call `clearFiltering()`.
- Omitting accessibility from a full renderer. Unlike `renderBadge` and `renderEmpty`, full `render` replaces the list structure and its managed role.
- Mutating the items received by `onChange`. Use the filter APIs so the grid, remote filtering, and badges stay synchronized.
- Treating badge actions as an editor for every canonical AST. Badges can summarize a grouped AST that cannot project to `MultiFilterItem`, but per-condition removal is available only for projectable state; update that tree with `setFilterAst()` or clear it with `clearFiltering()`.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Events](/guides/data-filter/filter-events/)
---
# Quick Start
URL: https://pro.rv-grid.com/guides/data-filter/filter-basics/
Source: src/content/docs/guides/data-filter/filter-basics.mdx
Description: Enable RevoGrid filtering, choose text and number operators, set initial conditions, clear filters, and react to filter changes.
Surface: grid
Use this guide when you need the shortest path from an unfiltered grid to a useful filtering workflow. RevoGrid Core already includes text and number conditions. Add `AdvanceFilterPlugin` only when you need selection lists, sliders, dates, expressions, grouped logic, quick filtering, or the [structured filter types](/guides/data-filter/filter-types/overview/).
## Choose a filter model
Use the smallest model that expresses the product requirement:
| Need | Recommended model | Why |
| --- | --- | --- |
| Ordinary local column filters | `multiFilterItems` | The smallest Core-compatible model; columns combine with AND. |
| Existing Core or legacy remote integration | `multiFilterItems` | Keeps the familiar column-condition format. |
| Saved views, remote requests, cross-column OR, nested groups, or NOT | `FilterAst` | JSON-safe canonical tree with one portable meaning. |
For a new remote integration, start with `FilterAst` and the accompanying `executionContext`; do not build a new backend contract around runtime-only values in `multiFilterItems`. The [Filter State and Presets](/guides/data-filter/filter-state/) guide shows both shapes, and the [Canonical Filter AST](/guides/data-filter/filter-ast/) guide defines the remote contract.
## 1. Enable filtering
Set the grid-level `filter` property explicitly, then choose a filter family on each column that needs filtering:
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'name', name: 'Name', filter: 'string' },
{ prop: 'status', name: 'Status', filter: 'string' },
{ prop: 'amount', name: 'Amount', filter: 'number' },
{ prop: 'internalId', name: 'Internal ID', filter: false },
];
grid.filter = true;
grid.source = [
{ name: 'Anna', status: 'Open', amount: 120 },
{ name: 'John', status: 'Pending', amount: 80 },
{ name: 'Steve', status: 'Closed', amount: 240 },
];
```
The grid-level property enables the filter plugin. It defaults to `true`; setting it explicitly makes application intent clear. The column-level property controls the button and available operators for that column. Do not initialize the grid with `filter = false` and expect a later `true` assignment to install the plugin dynamically.
| Column `filter` value | Use it for |
| --------------------- | -------------------------------------------- |
| `true` or `'string'` | Text values |
| `'number'` | Numeric comparisons |
| `false` | No filter button |
| `string[]` | Several Core, Pro, or custom filter families |
## 2. Choose an operator
Core string columns provide these saved operator IDs:
| Operator | Meaning |
| -------------------------- | ------------------------- |
| `contains` / `notContains` | Includes or excludes text |
| `begins` | Starts with text |
| `eq` / `notEq` | Equal or not equal |
| `empty` / `notEmpty` | Is blank or is not blank |
Core number columns provide `eqN`, `neqN`, `gt`, `gte`, `lt`, `lte`, `empty`, and `notEmpty`. Operator IDs are used in saved state, configuration, and event payloads; the popup shows readable labels such as **Contains**, **>=**, and **Is blank**.
## 3. Start with an applied filter
Use `multiFilterItems` for both single and multiple conditions. The following configuration shows rows whose status is Open and whose amount is from 100 through 500:
```ts
grid.filter = {
multiFilterItems: {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
amount: [
{ id: 2, type: 'gte', value: 100, relation: 'and' },
{ id: 3, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
Conditions inside one column can use `and` or `or`. Different columns combine with AND. Use the [Canonical Filter AST](/guides/data-filter/filter-ast/) when you need cross-column OR, nested groups, or NOT.
### Update conditions reactively without replacing configuration
The `filter` property is reactive. When the grid already has localization, Pro options, or custom filters, spread the current object into a new configuration before replacing its active conditions:
```ts
const currentFilter = typeof grid.filter === 'object' ? grid.filter : {};
grid.filter = {
...currentFilter,
multiFilterItems: {
amount: [
{ id: 1, type: 'gte', value: 100, relation: 'and' },
{ id: 2, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
Assign a new object rather than mutating `grid.filter.multiFilterItems` in place; the new reference triggers the reactive update. Framework applications should use the equivalent state update. For a Pro tree with cross-column logic, use the same pattern with `filterAst` instead of `multiFilterItems`.
## 4. Apply only after confirmation
Filtering updates while the user edits by default. For a Save/Cancel workflow, disable dynamic filtering:
```ts
grid.filter = {
disableDynamicFiltering: true,
};
```
## 5. Read and clear the result
Read the currently visible models with `getVisibleSource()`:
```ts
const visibleRows = await grid.getVisibleSource();
```
Clear through the active plugin to preserve configuration such as localization, selection options, expressions, and structured type registrations:
```ts
import { FilterPlugin } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
await filterPlugin?.clearFiltering();
```
`AdvanceFilterPlugin` extends `FilterPlugin`, so the same lookup works for Pro and also clears its quick-filter and canonical AST state. If your application controls `grid.filter` by assignment instead, recreate the complete configuration with `multiFilterItems: {}`; assigning only the empty model would replace the other options.
## 6. React to changes
Use `afterfilterapply` for UI that depends on the filtered result. Use the cancelable `beforefilterapply` event when the application needs to inspect, rewrite, or delegate a request to a backend.
```ts
grid.addEventListener('afterfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
console.log(event.detail.multiFilterItems, visibleRows.length);
});
grid.addEventListener('beforefilterapply', (event) => {
console.log('About to apply', event.detail.filterItems);
});
```
## 7. Add Pro filter controls
Register `AdvanceFilterPlugin`, then add the filter type to each matching column:
```ts
import {
AdvanceFilterPlugin,
FIlTER_SELECTION,
FIlTER_SLIDER,
FILTER_DATE,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'status', filter: ['string', FIlTER_SELECTION] },
{ prop: 'amount', filter: ['number', FIlTER_SLIDER] },
{ prop: 'createdAt', filter: [FILTER_DATE] },
];
grid.filter = true;
```
## Customize the experience
Each concern has a focused customization boundary; combine only the ones your UI needs.
| Need | Configure or call |
| --- | --- |
| Application-specific operator | `filter.customFilters` |
| Purpose-built filter popup | `filter.structuredFilterTypes` |
| Server-backed selection values | `filter.selection.getItems(prop, { search, signal })` |
| Global toolbar search | `grid.quickFilter` |
| Visual nested-rule editor | `filter.groupedFilter` or `plugin.mountFilterAstEditor()` |
| Saved view or remote query | `plugin.getFilterAst()` and `plugin.setFilterAst()` |
Use the [Filter Events](/guides/data-filter/filter-events/) guide when the application owns remote loading. Its example covers canceling local evaluation, sending the canonical request, and accepting only the latest response.
Choose a focused guide next:
- [Text Filter](/guides/data-filter/filter-types/text-filter/) for contains, equality, and begins-with conditions
- [Number Filter](/guides/data-filter/filter-types/number-filter/) for comparisons and numeric ranges
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/) for checklists of values
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) for staged Apply/Cancel checklists
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/) for numeric ranges
- [Date Filter](/guides/data-filter/filter-types/date-filter/) for calendar-day rules
- [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) for exact instants
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/) for strict Yes/No conditions
- [Array Filter](/guides/data-filter/filter-types/array-filter/) for empty and non-empty arrays
- [Structured Filter Types](/guides/data-filter/filter-types/overview/) for purpose-built popup controls
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/) for multi-word search across columns
- [Filter Events](/guides/data-filter/filter-events/) for observation and remote delegation
- [Filter Badges](/guides/data-filter/filter-badges/) for synchronized active-filter summaries
- [Filter State and Presets](/guides/data-filter/filter-state/) for reusable and persisted views
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) for application-specific predicates
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/) for quick filtering, badges, expressions, presets, and grouped logic
## Common mistakes
- Setting a column's `filter` but not enabling `grid.filter` or registering the Pro plugin.
- Using string operators such as `eq` on a numeric column; use `eqN` for Core numeric equality.
- Expecting `multiFilterItems` to express cross-column OR; use `FilterAst` for that shape.
- Filtering formatted display text without a `cellParser`; conditions evaluate the parsed value when a parser exists.
- Treating `0` or `false` as blank. The default blank policy does not.
## Related guides
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Clear Filtering demo](https://demo.rv-grid.com/clear-filtering/ts)
- [Core filtering guide](https://rv-grid.com/guide/filters)
---
# Filter Events
URL: https://pro.rv-grid.com/guides/data-filter/filter-events/
Source: src/content/docs/guides/data-filter/filter-events.mdx
Description: Observe, modify, cancel, or delegate Core and Pro filtering through runtime events.
Surface: grid
Filter events let application code react after visible rows change, adjust the rows that will be trimmed, or cancel local evaluation and delegate a request to a backend.
## Local Core event sequence
A normal Core filter run emits these filter-plugin events in order:
| Order | Event | Cancelable | Important runtime detail |
| ----- | --------------------- | ---------- | ------------------------------------------------------------------- |
| 1 | `beforefilterapply` | Yes | `collection`, full `filterItems`, source, and columns |
| 2 | `beforefiltertrimmed` | Yes | Filter model plus physical `itemsToFilter` indexes |
| 3 | `afterfilterapply` | No | `multiFilterItems`, original source, and first-condition collection |
Canceling `beforefilterapply` stops evaluation. Canceling `beforefiltertrimmed` stops the calculated filter trim from being installed. `beforetrimmed` and `aftertrimmed` are general grid events and can also run for grouping, tree visibility, or application-owned trims.
The Core filter plugin writes its calculated trim through the data provider, so its normal apply path does not emit the public `beforetrimmed` and `aftertrimmed` element events. Those events wrap calls through the grid's general `setTrimmed()` method; do not include them when reasoning about the filter-plugin sequence.
`filterconfigchanged` is different: it reports a new grid filter configuration and is not a per-run completion event.
## Read visible rows after filtering
`afterfilterapply.detail.source` is the original local source, not the filtered result. Read the provider-backed visible models with `getVisibleSource()`:
```ts
grid.addEventListener('afterfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
const detail = (
event as CustomEvent<{
multiFilterItems: Record;
}>
).detail;
console.log('Active filters', detail.multiFilterItems);
console.log('Visible rows', visibleRows.length);
});
```
The cast is intentional. The runtime Core plugin emits `afterfilterapply`, but the generated Core element event map in some current packages does not declare that event yet, so TypeScript may otherwise infer only the base `Event` type. Applications can also augment `HTMLRevoGridElementEventMap` centrally with the runtime detail type.
## Modify the calculated trim
Use `beforefiltertrimmed` when the application needs final control over physical row indexes:
```ts
grid.addEventListener('beforefiltertrimmed', (event) => {
// Keep physical row 0 visible even when ordinary conditions reject it.
delete event.detail.itemsToFilter[0];
});
```
Use this hook sparingly. A custom filter predicate or canonical AST condition is easier to persist and transport when the requirement is ordinary business logic.
## Cancel and delegate to a backend
Prevent `beforefilterapply` to stop local trimming, then load rows from the server. Guard the source replacement so reapplying the same active model does not create a request loop:
```ts
let loadedSignature = '';
let requestId = 0;
grid.addEventListener('beforefilterapply', async (event) => {
const detail = (
event as CustomEvent<{
filterItems: Record;
filterAst?: unknown;
executionContext?: unknown;
}>
).detail;
const transportState = detail.filterAst
? {
filterAst: detail.filterAst,
executionContext: detail.executionContext,
}
: { filterItems: detail.filterItems };
const signature = JSON.stringify(transportState, (_key, value) =>
value instanceof Set ? [...value] : value,
);
if (signature === loadedSignature) return;
event.preventDefault();
const activeRequest = ++requestId;
const response = await fetch('/api/orders/filter', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: signature,
});
const rows = await response.json();
if (activeRequest !== requestId) return;
loadedSignature = signature;
grid.source = rows;
});
```
With `AdvanceFilterPlugin`, set `filter.external: true` when the backend always owns filtering. The plugin then prevents local trimming itself while still emitting the same `beforefilterapply` payload. Your handler only needs to send the model and install the latest response; it should not emit `afterfilterapply`, because that event remains a local-filter completion signal.
```ts
grid.filter = { external: true };
grid.addEventListener('beforefilterapply', async (event) => {
const { filterAst, executionContext } = event.detail;
grid.source = await loadOrders({ filterAst, executionContext });
});
```
In production, also handle request failures and abort stale network work. The Pro Pagination, Infinity Scroll, and Server-Side Grouping plugins already implement this delegation flow.
Prefer Pro's canonical `filterAst` for new remote integrations. Compatible `filterItems` can contain runtime values such as the selection filter's excluded-value `Set`; the fallback above converts sets to arrays, but applications should still define and validate their complete wire contract. See [Filter State and Presets](/guides/data-filter/filter-state/).
## Pro AST events
`AdvanceFilterPlugin` emits canonical events in addition to the Core lifecycle:
| Event | Detail | Use it for |
| ----------------- | -------------------------------------------------------- | ---------------------------------------------------------- |
| `filterastchange` | `filterAst`, `executionContext`, `origin`, `projectable` | Persisting or transporting the effective canonical tree |
| `filterasterror` | Attempted AST, diagnostics, and origin | Showing validation errors without applying an invalid tree |
For a local Pro run, `filterastchange` is emitted before `beforefilterapply`. Pro also adds `filterAst`, `executionContext`, and temporal transport context to the Core before/after event details at runtime. Origins distinguish `config`, `api`, `ui`, `quickFilter`, and `clear` changes.
```ts
grid.addEventListener('filterastchange', (event) => {
console.log(event.detail.origin, event.detail.filterAst);
});
grid.addEventListener('filterasterror', (event) => {
console.error(event.detail.diagnostics);
});
```
## Quick Filter events
Changing `grid.quickFilter` wraps the local filter run with two Pro events:
```text
beforequickfilterapply
→ filterastchange
→ beforefilterapply
→ beforefiltertrimmed
→ afterfilterapply
afterquickfilterapply
```
`beforequickfilterapply` is cancelable. Its detail and `afterquickfilterapply` contain the normalized `quickFilter`, source, and columns. Remote Pro data plugins cancel the local quick-filter run and emit the completion event after the latest remote response wins.
## Common mistakes
- Do not treat `aftertrimmed` as filter-only; other grid features also trim rows.
- Do not read the original `source` field as the filtered result; call `getVisibleSource()`.
- Do not perform a remote request without `preventDefault()` or local filtering will run too.
- Do not assign remote rows without a loop guard and stale-request protection.
- Do not mutate `beforequickfilterapply.detail.quickFilter` expecting it to replace the property value; observe it or cancel and assign a new `grid.quickFilter` value.
- Do not assume generated element typings expose every plugin-emitted runtime field. Narrow or augment event detail types where needed.
## Related guides
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Infinity Scroll](/guides/infinity-scroll/)
---
# Filter Expressions
URL: https://pro.rv-grid.com/guides/data-filter/filter-expression/
Source: src/content/docs/guides/data-filter/filter-expression.mdx
Description: Enable a current-column expression editor for RevoGrid Pro advanced filters.
The Pro advanced filter can expose an optional expression editor inside each filter popup. It is disabled by default. When enabled, the popup adds an **Expression** button and expandable editor panel at the bottom, after the regular filter controls. Users can type current-column filter logic as text; valid expressions apply live and compile into the grid's standard `multiFilterItems` model.
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'status', name: 'Status', filter: ['string', 'selection'] },
{ prop: 'score', name: 'Score', filter: ['number', 'slider'] },
{ prop: 'updated', name: 'Updated', filter: ['date'] },
];
grid.filter = {
expressions: {
enabled: true,
applyDebounceMs: 300,
buttonLabel: 'Expression',
placeholder: 'between 10 and 50\ncontains "north" AND not empty',
},
};
```
## Current-column scope
Expressions are scoped to the column whose filter popup is open. You can omit the column name:
```txt
between 2024-01-01 and 2024-12-31 AND after 2024-06-01
```
You can also include the current column prop or name for readability:
```txt
updated between 2024-01-01 and 2024-12-31 AND updated after 2024-06-01
```
References to another column are invalid and do not change active filters.
For application-defined cross-column `OR`, nested groups, and `NOT`, use the public [Canonical Filter AST](/guides/data-filter/filter-ast/). The popup editor remains deliberately scoped to its current column.
## Parsed cell values
Expressions evaluate the current column's parsed value when `cellParser` is defined, just like ordinary comparisons. Blank expression operators use the raw source value and property-presence context from Core. See [Blank Values](/guides/data-filter/blank-values/) for the source-preserving contract.
## Syntax
Use `AND`, `OR`, and parentheses to group expressions:
```txt
(contains "north" OR contains "west") AND not empty
```
String columns support:
```txt
empty
not empty
= "Active"
!= "Archived"
begins "A"
contains "north"
not contains "draft"
```
Array columns support strict, valueless length checks:
```txt
empty array
not empty array
is empty array
is not empty array
```
Number and slider columns support:
```txt
> 10
>= 25
< 100
<= 500
between 250 and 640.42
```
Date columns support:
```txt
equals 2024-06-01
before 2024-12-31
after 2024-01-01
on or before 2024-12-31
on or after 2024-01-01
not equal 2024-07-04
between 2024-01-01 and 2024-12-31
today
yesterday
last 7 days
this month
last month
this quarter
next quarter
previous quarter
this year
next year
previous year
```
Selection-style text supports:
```txt
is "Open"
in ("Open", "Pending")
not in ("Closed", "Archived")
```
Registered structured filters use the same lossless mirror without exposing their transport JSON.
Objects, arrays, strings, numbers, booleans, and blank values remain typed:
```txt
structured "arrayTagsMatch" {
mode: "none",
values: ["renewal", "priority"],
emptyOnly: false,
exact: false
}
```
The editor highlights keywords, operators, properties, strings, numbers, booleans, and invalid
tokens. The grouped filter's Text view uses the same token presentation; when a structured filter
provides `describeCondition`, that filter-owned readable description replaces its transport value.
Legacy saved expressions containing a quoted JSON payload remain accepted.
## Validation and applying
The editor validates while users type. Valid expressions apply after `applyDebounceMs`; invalid expressions show diagnostics and keep the previous valid filters unchanged. Press `Ctrl+Enter` or `Cmd+Enter` to apply immediately.
The expression panel does not replace the normal popup content. Selection lists, slider controls, date inputs, and condition controls remain visible while the expression panel is expanded.
Expression filters are stored as hidden Pro filter items, so reset, active header state, filter events, and server-side filter payload handling continue to use the standard `multiFilterItems` flow.
## Localization
Expression labels use the existing filter localization path:
```ts
grid.filter = {
expressions: true,
localization: {
captions: {
expressionButton: 'Expression',
expressionTitle: 'Filter expression',
expressionPlaceholder: 'contains "north"',
expressionApply: 'Apply',
expressionInvalid: 'Invalid expression',
},
},
};
```
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# Filter Header
URL: https://pro.rv-grid.com/guides/data-filter/filter-header/
Source: src/content/docs/guides/data-filter/filter-header.mdx
Description: Add accessible text inputs, range controls, popup triggers, and optional compact templates directly to column headers.
`FilterHeaderPlugin` places common filter controls directly below each column label. Use it when users need fast, always-visible filtering; the normal filter icon remains available for the complete advanced-filter popup.
## Quick start
Register `FilterHeaderPlugin`, then configure filter families on the columns. It installs `AdvanceFilterPlugin` automatically:
```typescript
import {
FilterHeaderPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [FilterHeaderPlugin];
grid.columns = [
{ name: 'Name', prop: 'name', filter: 'string' },
{ name: 'Status', prop: 'status', filter: ['selection'] },
{ name: 'Age', prop: 'age', filter: ['number', 'slider'] },
{ name: 'Tags', prop: 'tags', filter: ['array'] },
];
```
Text columns receive a debounced input. Popup-style and registered structured filters receive a compact trigger. Slider columns receive either an inline range control or a formatted range summary.
| Column filter family | Header control |
| -------------------- | ------------------------------------------------- |
| Text and number | Debounced inline input |
| Selection | Popup trigger with included-value summary |
| Date | Popup trigger with active-state indicator |
| Boolean | Popup trigger for the valueless Yes/No operators |
| Array | Popup trigger for empty/non-empty array operators |
| Slider | Inline range control or formatted range summary |
| Structured types | Popup trigger using the type's readable summary |
## Slider headers
A column containing the `slider` filter type renders the shared range slider directly in the header. The regular filter icon remains available for opening the full advanced-filter popup.
```ts
const columns = [{ name: 'Age', prop: 'age', filter: ['number', 'slider'] }];
```
Moving either handle applies the range immediately. Returning both handles to the full data range clears the slider filter.
## Popup trigger headers
For selection, date, boolean, and array popup filters, the header renders a popup trigger instead of a free text input. Clicking it opens the same advanced-filter popup available from the normal filter icon.
Selection triggers show `All` while no values are excluded. Opening a selection popup and closing it without changes keeps the header value as `All`; it does not expand to every selected item.
Customize that fallback through the standard advanced-filter localization captions:
```ts
const filter = {
localization: {
captions: {
selectionAll: 'Any value',
},
},
};
```
When values are unchecked in the selection popup, the trigger shows a compact selected/total badge such as `8/9`. This is the default presentation and requires no additional configuration. Hover the badge to see the full selected-value summary. By default, that summary includes the number of matching rows for each value; set `hideFilterHeaderCount` on the column to hide those row counts.
Date triggers become active when a date condition has a value. Boolean and array triggers become active when one of their valueless operators is applied. Array support requires the array family in the column's filter array:
```ts
grid.columns = [{ name: 'Tags', prop: 'tags', filter: ['array'] }];
```
The array popup offers **Is empty array** and **Is not empty array**. See [Array Filter](/guides/data-filter/filter-types/array-filter/) for their strict array-value behavior.
## Custom header presentation
Every popup-style filter uses the same `FilterHeaderTemplateFunc` contract. A column template has the highest priority:
```tsx
import type { FilterHeaderTemplateFunc } from '@revolist/revogrid-pro';
const compactStatusHeader: FilterHeaderTemplateFunc = (h, { presentation }) => (
{presentation.summary}
);
const columns = [{
name: 'Status',
prop: 'status',
filter: ['selection'],
filterHeaderTemplate: compactStatusHeader,
}];
```
The grid owns the actual button, focus behavior, tooltip, keyboard activation, and accessible name. A template only renders non-interactive visual content inside that button. Use `presentation.summary` for compact text and `presentation.details` for the full human-readable meaning. Returning `undefined` keeps the standard text or count fallback.
A custom registered structured type may provide the same template once for every column using that type:
```tsx
const temperatureFilter: StructuredFilterType = {
id: 'temperatureRange',
operatorIds: ['temperatureBetween'],
render: context => ,
describeCondition: ({ condition }) => ({
summary: `${condition.value.min}–${condition.value.max} °C`,
details: `Temperature from ${condition.value.min} to ${condition.value.max} °C`,
}),
headerControl: {
kind: 'popup',
template: (h, { presentation }) => (
{presentation.summary}
),
},
};
```
Use `kind: 'popup'` when the filter needs custom badge, star, or summary content inside the grid-owned trigger. Use a `headerControl` resolver with `kind: 'inline'` when the type needs an interactive control such as a range slider. The resolver receives the same controlled body context as the popup, including aggregates and `replaceConditions()`, while the grid owns the outer non-sorting interaction boundary.
`describeCondition` is the source of truth for filter badges, grouped-filter text, header labels, and accessibility text. Keep it data-independent: it receives the condition and column configuration, not grid rows. This makes compact presentation safe for remote and very large datasets. A column-level `filterHeaderTemplate` overrides a type-owned popup template.
The template props also expose the column, canonical conditions, selection compatibility values, and the complete `presentation` object. The former top-level `active` and `text` fields remain as compatibility aliases; new code should use `presentation`.
Localize the surrounding accessible text with the normal filter captions:
```ts
const filter = {
localization: {
captions: {
filterHeaderLabel: column => `Filter ${column.name ?? column.prop}`,
filterHeaderSelectionSummary: (selected, total, details) =>
`${selected} of ${total} selected${details ? `: ${details}` : ''}`,
},
},
};
```
## When to use the popup instead
Header controls prioritize quick changes and limited space. Keep the normal popup workflow for multiple conditions, expressions, Excel-style staged selection, grouped AST editing, or any control that needs explanatory content.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Overview
URL: https://pro.rv-grid.com/guides/data-filter/filter-showcase/
Source: src/content/docs/guides/data-filter/filter-showcase.mdx
Description: Combine RevoGrid Core column filters with Pro structured controls, selection, slider, date, expressions, presets, and quick-filter workflows.
RevoGrid filtering narrows the visible row set without replacing the original source. Core provides the filter model and standard text/number operators; RevoGrid Pro adds richer inputs around the same model.
New to filtering? Begin with the [Filtering Quick Start](/guides/data-filter/filter-basics/) to enable filters, choose text and number operators, apply initial conditions, listen for changes, and clear the result.
For a scenario-led tour, read [Advanced Data Grid Filtering for E-commerce and Project Management](https://rv-grid.com/blog/advanced-data-grid-filtering-ecommerce-project-management) to see how the same capabilities support customer segmentation, order operations, and project-delivery queues.
[Open the standalone Advanced Filtering showcase](https://demo.rv-grid.com/filter-showcase/ts) to compare the TypeScript, React, Vue, and Angular implementations.
## Core and Pro at a glance
| Capability | Core | Pro |
| -------------------------------------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- |
| Text and number operators | Yes | Uses the same operators |
| Multiple conditions per column | Yes | Enhanced popup and header workflows |
| Programmatic `multiFilterItems` | Yes | Used by selection, slider, date, and expressions too |
| Parsed values through `cellParser` | Yes | Shared by advanced filters |
| Canonical JSON-safe filter tree | — | `FilterAst` with nested `and`, `or`, and `not` |
| Global multi-word quick filter | — | `AdvanceFilterPlugin` |
| Selection and cascading option lists | — | Yes |
| Editable numeric range slider | — | Yes |
| Calendar and relative-date operators | — | Yes |
| Structured filter bodies for tokens, fuzzy search, regex, facets, histograms, ratings, timelines, matrices, booleans, and arrays | — | Yes |
| Inline header controls | — | `FilterHeaderPlugin` |
| Current-column expression editor | — | Optional |
| Pagination, infinite scroll, and server-side grouping adapters | — | Yes |
## Choose the right filtering workflow
Start with the smallest workflow that matches the user's task:
| User need | Start with | Add when needed |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Text or numeric conditions in a popup | [Text](/guides/data-filter/filter-types/text-filter/) or [Number](/guides/data-filter/filter-types/number-filter/) filters | `AdvanceFilterPlugin` for richer controls and a canonical model |
| A checklist of known values | [Selection Filter](/guides/data-filter/filter-types/filter-selection/) | [Excel mode](/guides/data-filter/filter-types/filter-selection-excel-mode/) for staged Apply/Cancel; cascade for dependent lists |
| A numeric interval | [Slider Filter](/guides/data-filter/filter-types/filter-slider/) | Editable inputs or inline header controls |
| Calendar days or exact instants | [Date](/guides/data-filter/filter-types/date-filter/) or [Datetime](/guides/data-filter/filter-types/datetime-filter/) filter | Timezone, week, and fiscal-calendar policy |
| Fast controls inside headers | [Filter Header](/guides/data-filter/filter-header/) | Selection, slider, date, or boolean popup triggers |
| Current-column power-user syntax | [Filter Expressions](/guides/data-filter/filter-expression/) | Use AST instead for logic across columns |
| Cross-column OR, nested groups, NOT, or remote transport | [Canonical Filter AST](/guides/data-filter/filter-ast/) | The grouped editor for a visual workflow |
| A data-shaped popup control | [Structured Filter Types](/guides/data-filter/filter-types/overview/) | Register a custom type when the built-ins do not fit |
| Search words across several columns | [Quick Filter](/guides/data-filter/filter-types/quick-filter/) | Restrict columns or tune the debounce |
| Show removable active-filter summaries | [Filter Badges](/guides/data-filter/filter-badges/) | Custom labels, details, rendering, and clear actions |
| Save, restore, or package reusable views | [Filter State and Presets](/guides/data-filter/filter-state/) | Canonical `FilterAst` for JSON-safe nested queries |
| Add an application-specific row test | [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) | Structured types for purpose-built popup bodies |
## How Filter v2 improves existing filtering
“Filter v2” is an informal name for the canonical filtering architecture in Pro's `AdvanceFilterPlugin`; it is not a separate `FilterV2` class or a replacement Core plugin. Existing text, number, selection, slider, date, expression, and quick filters continue to use their current column configuration and UI. The advanced plugin converts their state into a JSON-safe `FilterAst` automatically.
This gives existing filtering a consistent model for local evaluation, saved views, and remote requests. It also adds nested `and`/`or` groups, cross-column `or`, `not`, atomic validation, and a shared date/time execution context. Simple trees are projected back into `multiFilterItems`, so existing integrations remain compatible; applications only need to use the AST API when they need the more expressive tree model.
This is primarily an improvement in capability, validation, and transport consistency. It does not promise that ordinary column filters will execute faster. Simple filtering still participates in the existing Core trimming and large-source lifecycle. See [Canonical Filter AST](/guides/data-filter/filter-ast/) for the tree shape, API, grouped editor, and remote event payloads.
## Explore the three presets
The Order Explorer opens with the **High-value Europe** filters already applied, making the active chips and filtered result count immediately visible. Use **Clear All** to reveal all 1,000 deterministic orders. Its presets are ordinary `multiFilterItems` models, so they can also be saved as views or created by application code.
- **High-value Europe** combines a Region selection with a Total slider range.
- **Recent expedited** combines the relative `last7Days` date operator with the Expedited selection.
- **Review queue** includes selected Status values and adds `gte` plus `lte` conditions to Total.
The result count comes from `grid.getVisibleSource()` after filtering. The grid-synced badge component mirrors the current model; removing a badge updates the plugin's canonical filter state, while **Clear All** assigns an empty filter model.
The toolbar's global search uses the public `grid.quickFilter` property. Try `Lisbon pending`: each word can match a different visible column in the same row. Search is whitespace-normalized, case-insensitive, and uses parsed cell values. Every word must match somewhere in the row, and the result is ANDed with all active column filters.
Scroll horizontally through the expanded order schema to try all fourteen [structured filter types](/guides/data-filter/filter-types/overview/) in context. Order, Customer, SKU, Region, Status, Total, Rating, Margin Change, Order Date, Renewal Date, Created At, Activity Time, Verified, and Tags each expose the matching purpose-built body while continuing to serialize into the same condition model.
In the compatible `multiFilterItems` model, relations belong to conditions
within one column. Conditions for the same column can use `and` or `or`, while
different columns combine with AND. The expression editor is also
current-column only. Use `FilterAst` or its grouped editor for cross-column
OR, nested groups, and NOT.
## Configure the showcase building blocks
The showcase combines documented public features rather than introducing another filter API:
- Header controls come from [Filter Header](/guides/data-filter/filter-header/).
- Status and Region use [Selection Filter](/guides/data-filter/filter-types/filter-selection/) with [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/).
- Total combines the [Number Filter](/guides/data-filter/filter-types/number-filter/) and [Slider Filter](/guides/data-filter/filter-types/filter-slider/).
- Order Date uses the [Date Filter](/guides/data-filter/filter-types/date-filter/), while Created At uses [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/).
- The toolbar search uses [Quick Filter](/guides/data-filter/filter-types/quick-filter/).
- Active chips use [Filter Badges](/guides/data-filter/filter-badges/).
- Presets follow [Filter State and Presets](/guides/data-filter/filter-state/).
Default popup controls apply while users edit. Set `disableDynamicFiltering: true` for the ordinary popup's Save/Cancel workflow. The separate [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) always uses Apply/Cancel and also provides mini-filter actions, blanks, and date hierarchies.
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Filter Badges](/guides/data-filter/filter-badges/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Current-column expressions](/guides/data-filter/filter-expression/)
- [Canonical local and remote Filter AST](/guides/data-filter/filter-ast/)
- [Clear Filtering demo](https://demo.rv-grid.com/clear-filtering/ts)
---
# Filter State and Presets
URL: https://pro.rv-grid.com/guides/data-filter/filter-state/
Source: src/content/docs/guides/data-filter/filter-state.mdx
Description: Initialize, update, save, restore, clear, and package reusable RevoGrid filter state without losing filter configuration.
Surface: grid
RevoGrid has two filter-state shapes. Choose the smallest one that represents the query you need:
| State | Use it for |
| ----------------- | ------------------------------------------------------------------------------------------ |
| `MultiFilterItem` | Core compatibility, ordinary column controls, and conditions that combine columns with AND |
| `FilterAst` | Pro saved views, remote requests, cross-column OR, nested groups, and NOT |
`MultiFilterItem` is runtime-compatible, but some controls use values such as `Set`. `FilterAst` is the canonical JSON-safe choice for new persistence and transport integrations.
## Set initial column filters
Supply `multiFilterItems` inside the complete grid filter configuration. Each property is a column property, and each array contains that column's conditions:
```ts
import type { ColumnFilterConfig, MultiFilterItem } from '@revolist/revogrid';
const initialFilters: MultiFilterItem = {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
total: [
{ id: 2, type: 'gte', value: 100, relation: 'and' },
{ id: 3, type: 'lte', value: 500, relation: 'and' },
],
};
const filterConfig: ColumnFilterConfig = {
disableDynamicFiltering: false,
multiFilterItems: initialFilters,
};
grid.filter = filterConfig;
```
Conditions within one column can use `and` or `or`. Different columns in `multiFilterItems` always combine with AND. Use `FilterAst` when the query requires cross-column OR, nested groups, or NOT.
## Update applied column state
Call the active filter plugin's `onFilterChange()` method to replace the complete compatibility model without replacing localization, custom operators, selection settings, or other filter configuration:
```ts
import { FilterPlugin, type MultiFilterItem } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
const nextFilters: MultiFilterItem = {
priority: [{ id: 1, type: 'eq', value: 'High', relation: 'and' }],
};
await filterPlugin?.onFilterChange(nextFilters);
```
`AdvanceFilterPlugin` extends Core's `FilterPlugin`, so the same lookup works when Pro filtering is installed. Passing `{}` removes all column conditions.
Use `afterfilterapply` to observe the applied compatibility state:
```ts
import type { MultiFilterItem } from '@revolist/revogrid';
grid.addEventListener('afterfilterapply', (event) => {
const detail = (event as CustomEvent<{ multiFilterItems: MultiFilterItem }>)
.detail;
const appliedFilters = detail.multiFilterItems;
console.log(appliedFilters);
});
```
## Create reusable presets
A preset can be a `MultiFilterItem`, but reassigning `grid.filter` with only that object would drop the rest of the configuration. Keep one factory that recreates every option:
```ts
import type { ColumnFilterConfig, MultiFilterItem } from '@revolist/revogrid';
function createFilterConfig(
multiFilterItems: MultiFilterItem = {},
): ColumnFilterConfig {
return {
allowDuplicateOperators: true,
disableDynamicFiltering: false,
localization: {
captions: {
save: 'Apply',
reset: 'Reset',
},
},
multiFilterItems,
};
}
const presets: Record = {
openHighValue: {
status: [{ id: 101, type: 'eq', value: 'Open', relation: 'and' }],
total: [{ id: 102, type: 'gte', value: 1000, relation: 'and' }],
},
needsReview: {
status: [
{ id: 201, type: 'eq', value: 'Pending', relation: 'or' },
{ id: 202, type: 'eq', value: 'On Hold', relation: 'or' },
],
},
};
function applyPreset(name: keyof typeof presets) {
grid.filter = createFilterConfig(presets[name]);
}
function clearPreset() {
grid.filter = createFilterConfig({});
}
```
When the grid is already initialized, `onFilterChange(presets[name])` is usually simpler because it preserves the active configuration without recreating it.
## Save and restore canonical state
For new saved-view and backend integrations, use Pro's JSON-safe `FilterAst`. Register `AdvanceFilterPlugin`, read a defensive clone with `getFilterAst()`, and restore it atomically with `setFilterAst()`:
```ts
import { AdvanceFilterPlugin, type FilterAst } from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
const plugin = (await grid.getPlugins()).find(
(item) => item instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin | undefined;
function saveView(storageKey: string) {
const ast = plugin?.getFilterAst();
if (ast) {
localStorage.setItem(storageKey, JSON.stringify(ast));
} else {
localStorage.removeItem(storageKey);
}
}
async function restoreView(storageKey: string) {
const saved = localStorage.getItem(storageKey);
const ast = saved ? (JSON.parse(saved) as FilterAst) : undefined;
await plugin?.setFilterAst(ast);
}
```
`setFilterAst()` validates the whole tree before applying it. Invalid state rejects without replacing the previous valid filter. Passing `undefined` clears filtering. You can also set initial canonical state with `grid.filter = { filterAst }`; when initial `filterAst`, `multiFilterItems`, and the legacy `collection` are supplied together, `filterAst` takes precedence.
The canonical tree represents conditions, groups, and negation with JSON values. Do not put `Date`, `Set`, functions, cycles, or non-finite numbers in a public AST.
## Compatibility state and JSON
Do not send `MultiFilterItem` directly through `JSON.stringify()` without checking its values. For example, the selection filter stores excluded checklist values in a `Set`, and JSON serializes a `Set` as `{}`.
```ts
const compatibilityState = {
status: [
{
id: 1,
type: 'selection',
value: new Set(['archived']),
relation: 'and',
},
],
};
```
Pro exports compatibility transport helpers that convert values such as `Set` and `Date` to JSON-friendly arrays and ISO strings. They are useful when maintaining an existing `MultiFilterItem` integration, but the compatibility format spans filter-specific runtime contracts. Do not assume those helpers provide a lossless saved-view format for every current or future Pro operator. Prefer `getFilterAst()` for new persistence and remote-filter payloads.
## Clear all filter state
Clear through the plugin so configuration stays installed:
```ts
import { FilterPlugin } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
await filterPlugin?.clearFiltering();
```
With `AdvanceFilterPlugin`, `clearFiltering()` also clears quick-filter and canonical AST state. `await plugin.setFilterAst(undefined)` is the equivalent canonical operation and also clears the quick filter.
## Common mistakes
- Assigning `grid.filter = { multiFilterItems }` after configuring localization, custom filters, selection options, or structured filter types. Use `onFilterChange()` or recreate the complete configuration with a factory.
- Treating `multiFilterItems` as cross-column Boolean logic. Different columns combine with AND; use `FilterAst` for cross-column OR and nested groups.
- Mutating a previously applied object and expecting a reactive update. Create the next state and pass it to `onFilterChange()` or `setFilterAst()`.
- Serializing selection-filter `Set` values directly. Use canonical `FilterAst` for new saved views.
- Restoring untrusted JSON without handling rejection from `setFilterAst()`.
- Clearing state by disabling `grid.filter`. Call `clearFiltering()` to keep filtering installed and configured.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Filter types
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/
Source: src/content/docs/guides/data-filter/filter-types/index.mdx
Description: Choose a RevoGrid Pro structured filter type and open a focused setup, state, and configuration guide for that control.
---
# Array Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/array-filter/
Source: src/content/docs/guides/data-filter/filter-types/array-filter.mdx
Description: Filter array-valued cells with strict empty-array and non-empty-array operators.
The Pro Array Filter adds two valueless operators for array-valued cells. Add
`AdvanceFilterPlugin` and use the `array` family:
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_ARRAY } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'tags',
name: 'Tags',
filter: [FILTER_ARRAY],
},
];
```
## Operators
| Default label | Saved operator ID | Matches |
| ------------------ | ----------------- | -------------------------- |
| Is empty array | `isEmptyArray` | Arrays with `length === 0` |
| Is not empty array | `isNotEmptyArray` | Arrays with `length > 0` |
Non-arrays match neither operator. Sparse arrays use their actual `length`, so
`new Array(3)` is non-empty. The operators need no filter value and remain
visibly active when restored from `multiFilterItems`.
```ts
grid.filter = {
multiFilterItems: {
tags: [
{
id: 0,
type: 'isEmptyArray',
relation: 'and',
},
],
},
};
```
Expressions support `empty array`, `not empty array`, `is empty array`, and `is
not empty array`. See [Blank Values](/guides/data-filter/blank-values/) for the
separate configurable blank policy. With `emptyArray: true`, `[]` explicitly
matches both the Core blank operator and `isEmptyArray`.
## Related guides
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/)
---
# Array and Tags Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/array-tags/
Source: src/content/docs/guides/data-filter/filter-types/array-tags.mdx
Description: Filter array-valued cells by any, all, none, exact-set, or empty-array semantics.
Surface: grid
Use the array and tags filter for labels, skills, categories, flags, or nested models that resolve to a tag array. Tag identity is typed, so `1`, `'1'`, `true`, and `null` remain distinct values.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_ARRAY_TAGS } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'tags',
name: 'Tags',
filter: ['array', FILTER_ARRAY_TAGS],
},
];
grid.source = [{ tags: ['UI', 'API'] }, { tags: ['Docs'] }, { tags: [] }];
grid.filter = true;
```
The popup flattens provider-backed array values into at most 200 typed tag options. The base `'array'` family also keeps the ordinary empty/not-empty operators available.
When rows store tag IDs and a `ColumnDropdown` editor uses `{ value, label }`
options, keep the IDs in the row and filter model. The filter automatically
reuses the normalized dropdown labels in its popup, header summary, and
condition description.
```ts
const tagOptions = [
{ value: 1, label: 'UI' },
{ value: 2, label: 'API' },
];
grid.columns = [{
prop: 'tags',
columnType: 'dropdown',
filter: ['array', FILTER_ARRAY_TAGS],
dropdown: { source: tagOptions, config: { multiSelect: true, search: true } },
}];
```
For remote labels that are not present in the dropdown source, use
`arrayTags.formatLabel` globally or under `arrayTags.columns[prop]`. An explicit
formatter takes precedence over the dropdown label. Presentation never changes
identity: numeric ID `1` remains distinct from string ID `'1'` during matching
and saved-state restore.
## Operator and saved state
- Structured type: `FILTER_ARRAY_TAGS` (`'arrayTags'`)
- Operator: `ARRAY_TAGS_MATCH` (`'arrayTagsMatch'`)
- Value: `{ mode, values, emptyOnly, exact }`
- Modes: `'any' | 'all' | 'none'`
```ts
grid.filter = {
multiFilterItems: {
tags: [
{
id: 1,
type: 'arrayTagsMatch',
value: {
mode: 'all',
values: ['UI', 'API'],
emptyOnly: false,
exact: false,
},
relation: 'and',
},
],
},
};
```
This JSON-safe object is the programmatic restore shape. `any` matches at least one selected tag, `all` requires every selected tag, and `none` rejects rows containing any selected tag. `exact` compares the complete normalized set and is ignored for `none`. `emptyOnly` takes precedence and matches only an actual empty array.
## Read tags from nested values
Configure an accessor globally or by column property through `filter.arrayTags`. It receives the cell value plus `{ property, column, model }` context and must return the array to treat as tags.
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'metadata',
name: 'Labels',
filter: ['array', FILTER_ARRAY_TAGS],
},
];
grid.filter = defineAdvancedFilterConfig({
arrayTags: {
columns: {
metadata: {
accessor: (value) => value?.labels,
},
},
},
});
grid.source = [
{ metadata: { labels: ['Priority', 'Customer'] } },
{ metadata: { labels: ['Internal'] } },
];
```
Use `arrayTags.accessor` for one global rule and `arrayTags.columns[prop].accessor` for exceptions. The per-column accessor wins. The same accessor is used to collect popup choices and evaluate rows.
Accessor functions are not serializable. Server-side filtering must implement the same extraction rule. For a high-cardinality or partially loaded remote source, provide complete array values or prepared `{ kind: 'arrayTags', values }` data rather than deriving choices from one page.
## Common mistakes
- Do not restore `type: 'arrayTags'`; saved conditions use `type: 'arrayTagsMatch'`.
- Do not return a scalar from the accessor. It must return the tag array.
- Do not expect string coercion: numeric `1` and string `'1'` are different tags.
- Do not combine `mode: 'none'` with `exact`; normalization disables exact mode for none.
- Do not use `emptyOnly` for `null`, missing, or sparse arrays. It delegates to strict empty-array semantics (`Array.isArray(value) && value.length === 0`).
- Do not forget to reproduce a custom accessor in a remote filter backend.
## Related guides
- [Structured Filter Types](./)
- [Array Filters](/guides/data-filter/filter-types/array-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Boolean Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/boolean-filter/
Source: src/content/docs/guides/data-filter/filter-types/boolean-filter.mdx
Description: Filter boolean columns with dedicated Yes and No operators, with customizable labels for application-specific wording.
The Boolean Filter adds dedicated, valueless operators for columns whose source
values are boolean primitives. By default, users see **Yes** and **No** in the
advanced-filter popup.
## Quick start
Add `AdvanceFilterPlugin`, then assign `FILTER_BOOLEAN` to the column:
```typescript
import { AdvanceFilterPlugin, FILTER_BOOLEAN } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Enabled',
prop: 'enabled',
filter: [FILTER_BOOLEAN],
},
];
grid.source = [{ enabled: true }, { enabled: false }];
```
The string form is equivalent:
```typescript
{
name: 'Enabled',
prop: 'enabled',
filter: ['boolean'],
}
```
## Operators
The displayed names and stored operator keys are intentionally separate:
| Default name | Operator key | Matches |
| ------------ | ------------ | ----------------------------- |
| Yes | `isTrue` | The boolean primitive `true` |
| No | `isFalse` | The boolean primitive `false` |
The filter uses strict boolean matching. Values such as `'true'`, `'false'`,
`1`, `0`, `null`, and `undefined` are not coerced into boolean matches.
Configuring blank semantics does not change this typed behavior.
## Customize Yes and No
Use `defineAdvancedFilterConfig` to change the displayed operator names. The
operator keys remain `isTrue` and `isFalse`, so saved filter models and matching
behavior do not change.
```typescript
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
filterNames: {
isTrue: 'Enabled',
isFalse: 'Disabled',
},
},
});
```
Labels can use any application-specific wording, such as `Active` / `Inactive`
or localized translations.
## Programmatic filter model
Use the stable operator keys when restoring or sending a filter model:
```typescript
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
enabled: [
{
id: 0,
type: 'isTrue',
relation: 'and',
},
],
},
});
```
## Related guides
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Filter Selection](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filter Showcase](/guides/data-filter/filter-showcase/)
---
# Date range filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/calendar-range/
Source: src/content/docs/guides/data-filter/filter-types/calendar-range.mdx
Description: Filter date columns with an inclusive, accessible calendar range picker.
Use the calendar range for booking periods, reporting windows, delivery dates, and other workflows where users choose a fixed start and end day. It shares RevoGrid Pro's calendar navigation, range preview, keyboard focus, disabled-date behavior, and accessibility semantics.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_CALENDAR_RANGE,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'order', name: 'Order' },
{
prop: 'deliveryDate',
name: 'Delivery',
filter: ['date', FILTER_CALENDAR_RANGE],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ order: 'A-100', deliveryDate: '2026-09-02' },
{ order: 'A-101', deliveryDate: '2026-09-14' },
{ order: 'A-102', deliveryDate: '2026-10-01' },
];
```
## Operator and state
- Type ID: `FILTER_CALENDAR_RANGE` (`'calendarRange'`)
- Operator ID: `CALENDAR_RANGE_BETWEEN` (`'calendarRangeBetween'`)
- Value: inclusive ISO date-only bounds `{ from, to, inclusive: true }`
Both dates use `YYYY-MM-DD`. Partial or invalid ranges are not committed. The lower bound must not be after the upper bound in restored state; the interactive editor normalizes a reversed selection.
## Restore a range programmatically
```ts
import {
CALENDAR_RANGE_BETWEEN,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
deliveryDate: [
{
id: 1,
type: CALENDAR_RANGE_BETWEEN,
value: { from: '2026-09-01', to: '2026-09-30', inclusive: true },
relation: 'and',
},
],
},
});
```
## Calendar configuration
```ts
import {
createCalendarRangeStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const calendar = createCalendarRangeStructuredFilterType({
weekStartsOn: 1,
timeZone: 'Europe/Lisbon',
locale: 'pt-PT',
minDate: '2026-01-01',
maxDate: '2026-12-31',
disabledDates: ['2026-12-25'],
showOperator: true,
showSelectionSummary: true,
showClearButton: true,
showCalendar: true,
calendar: {
views: ['month', 'year'],
defaultView: 'month',
showHeader: true,
showNavigation: true,
showPreviousButton: true,
showNextButton: true,
showMonthLabel: true,
showWeekdays: true,
showOutsideDays: true,
allowOutsideMonthSelection: false,
fixedWeeks: true,
showTodayButton: true,
isMonthDisabled: month => month === '2026-07-01',
dayClassName: date => date.endsWith('-25') ? 'holiday' : undefined,
},
rangeValidation: {
minDays: 2,
maxDays: 31,
allowSameDay: false,
allowDisabledDates: false,
validate: (range, context) =>
range.from.slice(0, 7) === range.to.slice(0, 7)
|| 'Choose dates within one month',
},
onInvalidRange: (range, reason) => {
console.info('Range rejected', range, reason);
},
dateFormat: { day: '2-digit', month: 'short', year: 'numeric' },
formatDate: (date, context) =>
new Intl.DateTimeFormat(context.locale, context).format(
new Date(`${date}T12:00:00Z`),
),
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [calendar],
});
```
`disabledDates` can also be a callback receiving the ISO date and resolved locale/timezone context. When `weekStartsOn` or `timeZone` is omitted, it inherits the resolved date-filter policy for the column. The calendar renders a bounded month view, so a disabled-date callback does not scan grid rows.
### Filter visibility
| Option | Behavior |
| --- | --- |
| `showOperator` | Shows or hides the operator selector. The first configured operator remains active when hidden. |
| `showSelectionSummary` | Shows or hides the selected-range status chip. |
| `showClearButton` | Shows or hides the clear action. |
| `showCalendar` | Shows or hides the complete calendar body. |
| `operators` | Ordered allow-list for between and single-date operators. |
### Range validation
Validation runs before a completed range is committed. An invalid end date keeps the current start date, displays an accessible error, and leaves the previous filter condition unchanged.
| Option | Behavior |
| --- | --- |
| `rangeValidation.minDays` | Minimum inclusive number of selected calendar days. |
| `rangeValidation.maxDays` | Maximum inclusive number of selected calendar days. |
| `rangeValidation.allowSameDay` | Allows a one-day range. Defaults to `true`. |
| `rangeValidation.allowDisabledDates` | Allows unavailable dates inside a range. Defaults to `true` for compatibility. Endpoints must always be selectable. |
| `rangeValidation.validate` | Final application validator. Return `true`, `false`, or a user-facing error string. |
| `onInvalidRange` | Receives the normalized attempted range, reason, and resolved column/locale/timezone context. |
Invalid, zero, or negative `minDays` and `maxDays` values are ignored. Restored conditions remain a data contract: selection UI validation prevents new invalid interactions but does not silently delete saved filters.
## Independent Calendar component
The visual calendar is an independent Pro UI component exported from `@revolist/revogrid-pro`. Date parsing and calendar arithmetic remain in the `dates` module, but calendar rendering is no longer owned or exported by `plugins/dates/calendar`.
The filter's nested `calendar` object accepts the reusable component's presentation and interaction options:
| Option | Behavior |
| --- | --- |
| `views` | Available `month` and/or `year` views. |
| `defaultView` | Initial view when it is included in `views`. |
| `showHeader` | Hides the complete heading/navigation row. |
| `showNavigation` | Convenience switch for both navigation actions. |
| `showPreviousButton`, `showNextButton` | Independently control navigation actions. |
| `showMonthLabel` | Hides the month/year heading while preserving navigation geometry. |
| `showWeekdays` | Shows or hides weekday labels. |
| `showOutsideDays` | Renders adjacent-month days. |
| `allowOutsideMonthSelection` | Allows rendered adjacent-month days to be selected. |
| `fixedWeeks` | Uses a stable six-week grid when `true`. |
| `showTodayButton` | Shows the Today shortcut. |
| `isMonthDisabled` | Disables complete months in navigation and year view. |
| `dayClassName`, `renderDay` | Customize day presentation from ISO date and state context. |
| `formatMonth`, `formatWeekday`, `formatDayLabel` | Customize visible and accessible date text. |
| `translations` | Overrides any subset of calendar interface labels. |
| `validateSelection`, `onInvalidSelection` | Validates individual selections before `onSelect`. |
| `onViewChange` | Reports month/year view changes. |
The standalone component is controlled: consumers provide `month`, `focusedDate`, optional `selection`/`preview`, and the corresponding change callbacks. Bounds (`minDate`, `maxDate`) and `isDateDisabled` apply to mouse, keyboard, Today, navigation, and year-view interactions.
The previous flat `showOutsideDays`, `allowOutsideMonthSelection`, `fixedWeeks`, and `showTodayButton` filter options remain compatibility aliases. Nested `calendar` values take precedence.
## Localization
Date text uses the configured `locale`. Interface and accessibility text use the normal advanced-filter localization contract. `CALENDAR_RANGE_LOCALIZATION` exports the complete English fallback and stable keys:
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
filterNames: {
calendarRangeBetween: 'Está no intervalo',
},
captions: {
calendarRangeTitle: 'Intervalo de datas',
calendarRangeBetween: 'entre',
calendarRangeChooseEnd: 'Escolha a data final',
calendarRangeEmpty: 'Nenhum intervalo selecionado',
calendarRangeClear: 'Limpar intervalo de datas',
calendarRangePreviousMonth: 'Mês anterior',
calendarRangeNextMonth: 'Mês seguinte',
calendarRangeCalendar: 'Calendário',
calendarRangeToday: 'Hoje',
calendarRangeInvalid: 'Escolha um intervalo válido',
calendarRangeSameDay: 'Escolha duas datas diferentes',
calendarRangeTooShort: 'Escolha um intervalo maior',
calendarRangeTooLong: 'Escolha um intervalo menor',
calendarRangeContainsDisabledDate: 'O intervalo contém uma data indisponível',
},
},
});
```
The independent `Calendar` also exports `CalendarTranslations` and `DEFAULT_CALENDAR_TRANSLATIONS`; its `translations` property accepts a partial override. Application-provided custom validation messages are displayed as supplied, so localize those strings in the application.
## Common mistakes
- Passing datetimes or locale-formatted strings in restored state; use ISO date-only values.
- Omitting `inclusive: true`; other values are rejected.
- Configuring only `locale` and expecting it to set timezone policy; locale formats text, while `timeZone` decides the civil date.
- Assuming disabled dates change source data or reject every programmatic condition; they primarily constrain calendar selection.
- Replacing `grid.filter` for localization without also retaining a configured `structuredFilterTypes` registration.
## Related guides
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Relative or Rolling Window](/guides/data-filter/filter-types/relative-window/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# Chip and Badge Toggle Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/chip-badge-toggles/
Source: src/content/docs/guides/data-filter/filter-types/chip-badge-toggles.mdx
Description: Present a small typed status set as configurable visual toggle chips.
Surface: grid
Use Chip and Badge Toggles when a column has a small, recognizable status set that is faster to scan as colored pills than as a long checklist.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_CHIP_BADGE_TOGGLES,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'status',
name: 'Status',
filter: ['string', FILTER_CHIP_BADGE_TOGGLES],
},
];
grid.filter = true;
```
The built-in derives its choices from parsed unique values. It supports finite numbers, booleans, and strings while keeping their types distinct.
When the column uses `ColumnDropdown` object options, badge labels automatically
reuse the matching option label while saved filter values remain the original
typed IDs. A configured `badge` result takes precedence over the dropdown label.
## Operator and state
| Body ID | Saved operator | Value shape |
| ------------------ | -------------------- | --------------------------------------------------------------------- |
| `chipBadgeToggles` | `chipBadgeSelection` | `{ values: (string \| number \| boolean)[], includeBlanks: boolean }` |
`includeBlanks` uses the grid's configured blank semantics. Selecting every available value and including blanks removes the no-op condition.
## Restore selected badges
```ts
grid.filter = {
multiFilterItems: {
status: [
{
id: 1,
type: 'chipBadgeSelection',
value: {
values: ['Open', 'Blocked'],
includeBlanks: false,
},
relation: 'and',
},
],
},
};
```
## Configure labels, colors, and order
Replace the built-in registration with a configured version. The stable type and operator IDs do not change:
```ts
import {
FILTER_CHIP_BADGE_TOGGLES,
createChipBadgeStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const statusBadges = createChipBadgeStructuredFilterType({
order: ['Blocked', 'Open', 'Done'],
maxVisibleBadges: 20,
badge: ({ value }) => ({
label: String(value),
color: value === 'Blocked' ? '#ef4444' : '#22c55e',
}),
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [statusBadges],
});
grid.columns = [
{ prop: 'status', filter: ['string', FILTER_CHIP_BADGE_TOGGLES] },
];
```
`badge` may return a plain label or safe `{ label, className, color }` metadata. Labels render as text, not HTML. The default visible limit is 200 pills; the complete value set still controls matching when the mounted UI is limited.
## Common mistakes
- Do not use presentation labels as saved values. Filtering uses the original typed scalar value.
- Do not pass `null`, `undefined`, or `''` in `values`; represent blanks with `includeBlanks`.
- Do not create hundreds of status chips without setting a useful `maxVisibleBadges`; use the [Faceted List filter](../faceted-list/) for high-cardinality values.
- Do not replace the registration with a different ID. Keep `FILTER_CHIP_BADGE_TOGGLES` so column configuration and saved state remain compatible.
## Related guides
- [Structured Filter Types](./../)
- [Faceted List Filter](../faceted-list/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Date Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/date-filter/
Source: src/content/docs/guides/data-filter/filter-types/date-filter.mdx
Description: Filter calendar days with absolute comparisons, relative periods, timezones, fiscal calendars, and remote transport.
Surface: grid
Use the Pro date filter when a value represents a calendar day: due dates, business dates, holidays, or reporting periods. Use the [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) when values represent exact instants such as audit timestamps or scheduled executions.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_DATE } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [{ prop: 'dueDate', name: 'Due date', filter: [FILTER_DATE] }];
grid.filter = true;
grid.source = [
{ dueDate: '2026-08-25' },
{ dueDate: '2026-08-26' },
{ dueDate: null },
];
```
A source `YYYY-MM-DD` keeps its written calendar date. A `Date`, epoch value, or ISO datetime with `Z` or an explicit offset is projected into the resolved timezone before the calendar day is compared.
## Advanced example
Start with browser-local behavior. Add a timezone, week-start, or fiscal-calendar policy only when the application has a defined business requirement.
## Filter a day or range
Date equality includes the complete civil day. A `between` range includes both selected days; internally it is evaluated as a half-open range whose end is the start of the following day.
```ts
grid.filter = {
multiFilterItems: {
dueDate: [
{
id: 1,
type: 'between',
value: {
fromDate: '2026-08-01',
toDate: '2026-08-31',
},
relation: 'and',
},
],
},
};
```
Use ISO calendar strings in persisted state so the intended day remains explicit.
## Configure timezone and calendar policy
```ts
grid.filter = {
date: {
timezoneMode: 'organization',
organizationTimeZone: 'America/New_York',
weekStartsOn: 1, // Monday; Sunday is 0
fiscalYearStart: { month: 2, day: 1 },
// Ordered allowlist used by every date and datetime menu.
operators: ['equals', 'before', 'after', 'between', 'today', 'thisFiscalYear'],
columns: {
accountingDate: {
timezoneMode: 'utc',
// A column entry replaces the global operator list for this field.
operators: ['between', 'thisFiscalYear'],
},
warsawDate: {
timezoneMode: 'explicit',
timeZone: 'Europe/Warsaw',
},
},
},
};
```
Settings resolve field by field: defaults, grid policy, then the partial entry in `date.columns[prop]`.
The `operators` array is an ordered allowlist, so it also controls menu order. A column-level array
replaces the grid-level array for that field. The same date IDs configure datetime menus; the plugin
maps them to the corresponding datetime operators internally.
| Mode | Resolved timezone |
| -------------- | -------------------------------------------------------------- |
| `user` | `userTimeZone`, otherwise the browser's resolved IANA timezone |
| `organization` | Required `organizationTimeZone` |
| `utc` | `UTC` |
| `explicit` | Required `timeZone` |
With no date configuration, behavior remains browser-local and the week starts on Monday. Fiscal
operators are hidden until an effective `fiscalYearStart` is configured globally or for that column.
Once configured, all fiscal quarter and fiscal year operators are available by default; use
`operators` to show only the fiscal choices appropriate for the application. Date calculations still
use January 1 internally when evaluating existing persisted fiscal conditions without an authored
fiscal policy. Initialization rejects unknown operator IDs, invalid IANA names, week indexes outside
`0…6`, missing organization or explicit zones, February 29, and other fiscal dates that do not recur
every year.
The same instant can fall on different calendar days in Warsaw and New York. Calendar strings do not shift: `2026-08-05` remains August 5 under every timezone policy.
## Operators
The date filter provides:
- Comparisons: Equals, Before, After, On or before, On or after, Between, Not equal
- Blank policy: Is blank, Is not blank
- Rolling days: Today, Yesterday, Last 7 days, Next 30 days
- Weeks: This week, Last week, Next week
- Months: This month, Last month
- Calendar periods: This, Previous, and Next quarter or year
- Fiscal periods (when `fiscalYearStart` is set): This, Previous, and Next fiscal quarter or fiscal year
`last7Days` includes today plus the preceding six civil days. `next30Days` includes today plus the following 29 days. Calendar quarters remain January/April/July/October quarters; only fiscal operators use `fiscalYearStart`.
The expression editor uses the same evaluator and captured clock. Examples include `today`, `this week`, `next 30 days`, `this fiscal quarter`, and `between "2026-08-01" and "2026-08-31"`. Expressions still reference only their current column.
## Blank and invalid values
`isEmpty` and `isNotEmpty` use Core's configured blank policy. Invalid nonblank temporal values fail comparisons but are not automatically blank. `false` and `NaN` therefore do not become blank dates.
Read [Blank Values](/guides/data-filter/blank-values/) for policy precedence and missing-property behavior.
## Remote filtering
Each filtering run captures one reference instant. `beforefilterapply` exposes `temporalContext`, and pagination or infinity-scroll `loadData` receives it as the optional seventh argument; the first six arguments are unchanged.
```ts
loadData: async (
skip,
take,
order,
singleConditionFilters,
multiConditionFilters,
quickFilter,
temporalContext,
) =>
fetch('/api/rows', {
method: 'POST',
body: JSON.stringify({
skip,
take,
order,
multiConditionFilters,
quickFilter,
temporalContext,
}),
}).then((response) => response.json());
```
The JSON-safe envelope contains `referenceInstant`, each filtered temporal column's resolved timezone, and condition ranges keyed by `:`. UTC boundaries use `start` and `endExclusive`; an unbounded side is `null`.
## Common mistakes
- Using `date` for timestamps that must retain time-of-day precision; use [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/).
- Assuming browser-local time matches the organization's reporting timezone.
- Treating the end of a date range as exclusive; the selected final day is included.
- Sending a relative operator to a server without the captured `temporalContext`.
- Treating invalid nonblank values as blanks.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/#date-hierarchy)
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Infinity Scroll](/guides/infinity-scroll/)
---
# Datetime Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/datetime-filter/
Source: src/content/docs/guides/data-filter/filter-types/datetime-filter.mdx
Description: Filter exact instants with second precision, explicit timezone policy, absolute ranges, relative periods, and programmatic state.
Surface: grid
Use the Pro datetime filter for timestamps that represent exact instants: created times, audit events, log entries, and scheduled executions. Use the [Date Filter](/guides/data-filter/filter-types/date-filter/) instead when the source represents a calendar day without a time.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_DATETIME } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'createdAt',
name: 'Created',
filter: [FILTER_DATETIME],
},
];
grid.filter = true;
grid.source = [
{ createdAt: '2026-08-26T10:30:00Z' },
{ createdAt: new Date('2026-08-26T14:45:00Z') },
{ createdAt: 1787741100000 },
];
```
Accepted source values are `Date`, epoch milliseconds, and ISO datetimes with `Z` or an explicit offset. Date-only and offset-free source strings are invalid for this type.
## Datetime operators
Datetime saved IDs prefix the shared temporal operator name with `datetime`:
| Example saved ID | Meaning |
| -------------------------------------------------------- | -------------------------------------------------- |
| `datetimeEquals` | Exact selected second |
| `datetimeBefore` / `datetimeAfter` | Strict instant comparison |
| `datetimeOnOrBefore` / `datetimeOnOrAfter` | Inclusive comparison |
| `datetimeBetween` | Inclusive selected seconds |
| `datetimeNotEqual` | Not the selected second |
| `datetimeIsEmpty` / `datetimeIsNotEmpty` | Shared blank policy |
| `datetimeToday`, `datetimeLast7Days`, `datetimeThisWeek` | Relative calendar periods in the resolved timezone |
The popup also provides month, quarter, year, and fiscal-period operators. Read the complete list in the [Date Filter operator reference](/guides/data-filter/filter-types/date-filter/#operators).
## Start with a range
```ts
grid.filter = {
multiFilterItems: {
createdAt: [
{
id: 1,
type: 'datetimeBetween',
value: {
fromDate: '2026-08-26T09:00',
toDate: '2026-08-26T17:00',
},
relation: 'and',
},
],
},
};
```
The `datetime-local` values are interpreted in the resolved timezone. Equality selects one second; between includes the final selected second. Internally these become half-open UTC ranges for reliable local and remote evaluation.
## Set an explicit timezone
```ts
grid.filter = {
date: {
timezoneMode: 'organization',
organizationTimeZone: 'America/New_York',
columns: {
createdAt: {
timezoneMode: 'explicit',
timeZone: 'Europe/Lisbon',
},
},
},
};
```
With no date policy, the datetime control uses the browser's local timezone. Configure an IANA timezone when all users must interpret relative periods and wall-clock input consistently.
## Invalid and DST values
- Offset-free source strings such as `2026-08-26T10:30:00` are invalid because they do not identify an instant.
- A wall-clock value inside a daylight-saving gap does not exist and is rejected.
- A repeated wall-clock value is ambiguous and is also rejected.
- Invalid nonblank values fail comparisons; they do not become blank automatically.
## Common mistakes
- Do not use `datetime` for source values such as `2026-08-26`; use `date`.
- Do not restore datetime state with unprefixed date operator IDs.
- Do not assume the browser timezone matches the organization's reporting timezone.
- Use ISO strings in persisted or remote state; JavaScript `Date` objects are not JSON-safe AST input.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Calendar Range](/guides/data-filter/filter-types/calendar-range/)
- [Timeline Brush](/guides/data-filter/filter-types/timeline-brush/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Faceted List Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/faceted-list/
Source: src/content/docs/guides/data-filter/filter-types/faceted-list.mdx
Description: Filter typed scalar values with searchable all-row and visible-row counts.
Surface: grid
Use the Faceted List filter for a searchable value checklist that also shows how many rows contain each value and how many remain visible after other filters.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'region', name: 'Region', filter: ['string', FILTER_FACETED_LIST] },
];
grid.filter = true;
```
The body reads both all-row and visible-row value counts. The two counts make it easier to understand which options remain after other column filters are active.
## Operators and state
| Saved operator | Value shape | Matches |
| -------------------- | ----------------------------------------- | ----------------------------- |
| `facetedListInclude` | `(string \| number \| boolean \| null)[]` | Values in the saved list |
| `facetedListExclude` | `(string \| number \| boolean \| null)[]` | Values outside the saved list |
Typed identity is preserved: `1`, `'1'`, and `true` are different options. `null` is shown as **(Blank)** and `''` as **(Empty)**. Non-finite numbers, arrays, objects, and `undefined` are not facet values.
## Display labels for stored IDs
If the column uses `ColumnDropdown` with object options, the facet editor,
condition description, and header selection automatically show each option's
label while saved conditions continue to contain its typed value.
```ts
import { ColumnDropdown } from '@revolist/revogrid-pro';
const statuses = [
{ value: 10, label: 'Open' },
{ value: 20, label: 'Closed' },
];
grid.columnTypes = { dropdown: ColumnDropdown };
grid.columns = [{
prop: 'statusId',
columnType: 'dropdown',
filter: ['number', FILTER_FACETED_LIST],
dropdown: { source: statuses },
}];
grid.source = [{ statusId: 10 }, { statusId: 20 }];
```
For remote or separately loaded labels, configure `facetedList.formatLabel`
globally or under `facetedList.columns[prop]`. The explicit formatter wins over
the dropdown label and affects presentation only.
```ts
grid.filter = {
facetedList: {
columns: {
statusId: { formatLabel: value => statusLabels.get(value as number) },
},
},
};
```
## Restore selected facets
```ts
grid.filter = {
multiFilterItems: {
region: [
{
id: 1,
type: 'facetedListInclude',
value: ['EMEA', 'APAC'],
relation: 'and',
},
],
},
};
```
The editor may serialize the smaller equivalent include or exclude list. Selecting every available value removes the condition; selecting none is represented as a non-empty exclusion list so local and remote behavior remain consistent.
## Counts and remote data
Facets use the `valueCounts` aggregate in two scopes:
- `all` describes the complete source available to the filter.
- `visible` applies all active filters except the current facet's own condition.
Local grids derive both from provider-backed row state. Pagination and infinite-scroll grids should supply complete structured-filter aggregates when browser rows are only a window of the remote result.
## Common mistakes
- Do not stringify typed values before restoring them unless the source column is also text; numeric `1` and string `'1'` are intentionally distinct.
- Do not put whole option objects in facet conditions. Store their scalar `value`; use dropdown options or `formatLabel` for presentation.
- Do not assume the UI always saves an include condition. It chooses a smaller equivalent include or exclude representation.
- Do not use this type for arbitrary array-valued cells. Use the [Array and Tags filter](../array-tags/) for arrays.
- Do not treat the visible count as the total source count; it reflects other active filters.
## Related guides
- [Structured Filter Types](./../)
- [Token List Filter](../token-list/)
- [Chip and Badge Toggles](../chip-badge-toggles/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Selection Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection.mdx
Description: Add searchable value checklists with live updates, custom option sources, templates, grouping, and cascading selection lists.
Selection filters let users include or exclude values through a searchable checkbox list. The list is a virtualized nested `revo-grid`, so large option sets render only the visible rows while preserving sorting, search, and checkbox behavior.
Use the default interaction for immediate checkbox updates. When users should review a staged checklist and choose **Apply**, use the dedicated [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) preset.
## Quick start
Add `AdvanceFilterPlugin`, then include `selection` in the column's filter families:
```typescript
import { AdvanceFilterPlugin, FIlTER_SELECTION } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Fruit',
prop: 'name',
filter: ['string', FIlTER_SELECTION],
},
];
grid.filter = true;
grid.source = [{ name: 'Apple' }, { name: 'Banana' }, { name: 'Pear' }];
```
By default, checked values are included and unchecked values are stored as an excluded-value `Set`. Opening and closing a fully selected list does not create a filter.
Add `grid.filter = { selection: { sortDirection: 'asc' } }` when the options should be sorted rather than kept in source order.
- Keep the default mode for immediate checkbox updates and optional live row
filtering while users search. - Use [`selection.excelMode:
'windows'`](/guides/data-filter/filter-types/filter-selection-excel-mode/) for a
transactional checklist with Apply, Cancel, tri-state Select All, blanks, and
date hierarchies.
## Windows Excel mode
`selection.excelMode: 'windows'` changes selection-filter columns into a staged, spreadsheet-style checklist. It keeps the same excluded-value `Set` model but adds Apply, Cancel, tri-state Select All, blanks, search actions, and optional date hierarchies.
See [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) for the interactive four-framework example and complete behavior reference. This heading remains available for links created before the guide was separated.
## How the option list behaves
The selection popup keeps the search row fixed at the top and renders option rows through an internal `revo-grid`.
- Search still matches normalized option `value`.
- Select-all only affects currently searched and visible option rows.
- Checked state still means the value is included; unchecked values are stored in the selection filter's excluded-value `Set`.
- Opening and closing a fully selected popup does not create an empty selection filter, so `FilterHeaderPlugin` keeps showing `All`.
- Group rows are visual only; option checkboxes still belong to leaf option rows.
The old `.filter-list li` DOM shape is not part of the public API. Use the visible option behavior or `.filter-list-option` when tests need to interact with option rows.
## Control option values and sources
### Parsed cell values
When a column defines `cellParser`, the built-in selection option loader uses the parsed value for option labels, quick search, and row comparison. This lets rows keep identifiers or structured values while the filter presents and compares the normalized value returned by the parser. Context-aware cascading options use the same parsed values.
```typescript
const statusLabels = {
1: 'Active',
2: 'Paused',
};
grid.columns = [
{
prop: 'status',
name: 'Status',
filter: ['selection'],
cellParser: (model, column) => statusLabels[model[column.prop]],
},
];
```
If you provide `selection.getItems`, that custom loader remains the explicit source for option values and labels. See the Core guide on [filtering parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) for the underlying `cellParser` contract.
### Extend source-row eligibility
The built-in selection and slider option loaders emit the cancelable `beforefilteroptionsourcerow` event before a source row contributes a value. Cancel the event to exclude application- or plugin-owned synthetic rows without coupling Advanced Filter to their data shape.
```typescript
import { BEFORE_FILTER_OPTION_SOURCE_ROW_EVENT } from '@revolist/revogrid-pro';
grid.addEventListener(BEFORE_FILTER_OPTION_SOURCE_ROW_EVENT, (event) => {
if (event.detail.row.__summaryRow) {
event.preventDefault();
}
});
```
Grouping rows are excluded before the event is emitted. The `@revolist/pivot` plugin uses this extension automatically for subtotal and grand-total rows. A custom `selection.getItems` loader remains fully application-owned and does not scan or emit events for grid source rows.
## Search and context-aware options
### Quick-search filtering
By default, the selection popup search input does two things:
- narrows the option rows shown inside the popup
- applies a hidden `quickSearch` filter to the grid rows
This preserves the existing behavior for users who expect the grid to narrow while they type. Set `selection.quickSearchFiltering` to `false` when search should only help users find values in the popup and should not change the grid rows until they check or uncheck selection values.
```typescript
grid.filter = {
selection: {
quickSearchFiltering: false,
},
};
```
When selection values are IDs but the popup renders names, use `selection.quickSearchFilter` to match the text users see. The matcher receives normalized `search`, normalized option `value`, display `label`, original `item`, and `columnProp`. If `quickSearchFiltering` remains enabled, grid rows are filtered by the matched option values, so row data can still store IDs.
```typescript
grid.filter = {
selection: {
getItems: {
ownerId: () => [
{ value: 'usr-1', label: 'Ana Silva', email: 'ana@example.com' },
{ value: 'usr-2', label: 'Maks Doe', email: 'maks@example.com' },
],
},
quickSearchFilter: {
ownerId: ({ search, label, item }) =>
`${label} ${item.email}`.toLowerCase().includes(search),
},
},
};
```
### Context-aware cascading options
Enable context-aware selection options with `selection.cascadeOptions.enabled`.
- For column **X**, options are built from rows matching all active filters **except filters for X**.
- This keeps current-column values reversible while still narrowing related column options.
- `optionVisibility` supports `hide`, `disable`, and `show`; `hide` is the default.
- Header totals and tooltips always describe context-valid values, even when the popup shows the complete domain.
- The feature is opt-in, and default behavior is unchanged when omitted.
```typescript
grid.filter = {
selection: {
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
},
},
};
```
See [Cascading Selection Filters](/guides/data-filter/filter-types/filter-selection-cascade/) for a detailed comparison of all three modes and preserved exclusion behavior.
## Custom option sources
By default, the selection filter builds its checkbox list from the current grid data. Use `filter.selection.getItems` when the list should come from somewhere else, for example:
- server-side or infinite-scroll datasets
- a curated allow-list of accepted values
- normalized values that differ from the rendered cell text
Default lists are rebuilt when the popup opens. If a cell value is edited, rows are removed, or `grid.source` is replaced, the next selection popup reflects the latest column values. Values that no longer exist in the source are not shown unless you provide them through a custom `selection.getItems` loader.
When pinned rows should not contribute checkbox values, keep the default loader and restrict its row stores:
```typescript
grid.filter = {
selection: {
sourceRowTypes: ['rgRow'],
},
};
```
The loader receives the current column `prop` and may return data synchronously or asynchronously.
```typescript
grid.filter = {
selection: {
getItems: async (prop) => {
if (prop !== 'name') {
return [];
}
const response = await fetch('/api/filter-options/names');
const items = await response.json();
return items.map((item: { id: string; title: string }) => ({
value: item.id.toLowerCase(),
label: item.title,
}));
},
},
};
```
If only some columns need custom values, pass a record keyed by column prop:
```typescript
grid.filter = {
selection: {
getItems: {
name: async () => [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
],
category: () => [
{ value: 'fresh', label: 'Fresh' },
{ value: 'frozen', label: 'Frozen' },
],
},
},
};
```
Keep `value` aligned with the value used during filter comparison. The built-in selection filter compares lower-cased string values, so custom lists should usually provide lower-cased `value` fields.
## Customize option presentation
### Custom option templates
Use `selection.itemTemplate` to render badges, icons, or richer labels inside the selection popup. The checkbox remains controlled by the filter plugin; the template only replaces the content next to it.
To reuse an existing column renderer without defining a second template, enable `selection.syncCellTemplate`. The option item is exposed as a synthetic row model, including metadata returned by `selection.getItems`, and the current column prop is set to the normalized filter value.
```typescript
grid.filter = {
selection: {
syncCellTemplate: {
availability: true,
},
getItems: {
availability: () => [
{ value: 'in stock', label: 'In stock', tone: 'green' },
{ value: 'backorder', label: 'Backorder', tone: 'amber' },
],
},
},
};
```
```typescript
grid.filter = {
selection: {
getItems: {
availability: () => [
{ value: 'in stock', label: 'In stock', tone: 'green' },
{ value: 'backorder', label: 'Backorder', tone: 'amber' },
{ value: 'seasonal', label: 'Seasonal', tone: 'indigo' },
],
},
itemTemplate: {
availability: (h, { item, label, checked }) =>
h(
'span',
{
class: `availability-badge availability-badge--${item.tone}`,
'data-selected': String(checked),
},
label,
),
},
},
};
```
`itemTemplate` receives:
- `columnProp`: Current column property.
- `item`: Original item returned by `selection.getItems` or the default source loader.
- `value`: Normalized value used by selection filtering.
- `label`: Display label.
- `checked`: Whether the option is currently included in the result.
### Option progress
Use `selection.optionProgress` for a responsive distribution bar inside the selection-owned option
row. The label, track, and formatted value share the available width, and unchecked options switch
to the muted visual state automatically. Values read item metadata returned by `selection.getItems`.
```typescript
grid.filter = {
selection: {
getItems: {
status: () => [
{ value: 'processing', label: 'Processing', count: 42 },
{ value: 'shipped', label: 'Shipped', count: 28 },
],
},
optionProgress: {
status: {
valueProp: 'count',
getMax: ({ values }) => Math.max(1, ...values),
formatValue: value => `${value} rows`,
},
},
},
};
```
Use `getValue` instead of `valueProp` for derived values, and `max` or `getMax` to control the range.
`showValue: false` hides the trailing value while keeping the accessible progressbar. `ariaLabel`
accepts a string or formatter.
For secondary metadata that is not progress, `selection.optionColumns` still appends ordinary
read-only RevoGrid columns after the selection-owned checkbox and label column.
### Grouped option rows
Selection option rows can be grouped by fields on the option item. This is useful for large curated lists where users need a hierarchy such as family and color, region and country, or category and status.
```typescript
grid.filter = {
selection: {
grouping: {
name: {
props: ['family', 'color'],
expandedAll: true,
},
},
getItems: {
name: () => [
{ value: 'apple', label: 'Apple', family: 'Tree fruit', color: 'Red' },
{ value: 'pear', label: 'Pear', family: 'Tree fruit', color: 'Green' },
{ value: 'lemon', label: 'Lemon', family: 'Citrus', color: 'Yellow' },
],
},
},
};
```
`selection.grouping` can also be a single `GroupingOptions` object when every selection-filter column should use the same grouping configuration.
### Nested-list plugins and settings
The selection list is a nested grid, and you can pass plugins or grid settings to that nested grid when you need additional rendering behavior.
```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
class SelectionListPlugin {
constructor(selectionGrid) {
selectionGrid.setAttribute('data-selection-list-plugin', 'mounted');
}
}
grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
selection: {
plugins: {
name: [SelectionListPlugin],
},
gridSettings: {
name: {
theme: 'compact',
rowSize: 32,
frameSize: 2,
hideAttribution: true,
},
},
},
};
```
`selection.plugins` and `selection.gridSettings` accept either a global value or a record keyed by column `prop`.
The filter list owns `source`, `columns`, and `grouping` so filtering, checkbox state, and virtualization stay consistent. Use `selection.grouping` for grouped option rows instead of `gridSettings.grouping`.
## Restore selection state
Selection filters store unchecked values as an excluded-value `Set`. To predefine a selection filter, add a hidden `selection` filter with values that should be excluded.
```typescript
grid.filter = {
multiFilterItems: {
name: [
{
id: 0,
type: 'selection',
value: new Set(['banana', 'lemon']),
relation: 'and',
hidden: true,
},
],
},
};
```
The [Excel-style demo](/guides/data-filter/filter-types/filter-selection-excel-mode/) uses this pattern for the Fruit column, so two fruit values are already unchecked when it loads. See [Filter State and Presets](/guides/data-filter/filter-state/) before persisting this model because a `Set` is not directly JSON-safe.
## Configuration reference
| Option | Purpose |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `sortDirection` | Sort options with `'asc'`, `'desc'`, or `'none'`. |
| `excelMode` | Set to `'windows'` for the dedicated [Excel-style checklist](/guides/data-filter/filter-types/filter-selection-excel-mode/). |
| `quickSearchFiltering` | Choose whether popup search also filters grid rows; defaults to `true`. |
| `quickSearchFilter` | Match search text against labels or custom item metadata. |
| `sourceRowTypes` | Choose which row stores feed the default option loader. |
| `getItems` | Provide synchronous or asynchronous custom options globally or by column. |
| `itemTemplate` / `syncCellTemplate` | Customize option content while the plugin retains checkbox behavior. |
| `optionProgress` | Add selection-owned responsive progress with checked and unchecked visual states. |
| `optionColumns` | Append read-only metadata columns for non-progress option details. |
| `grouping` | Group option rows using fields returned by `getItems`. |
| `plugins` / `gridSettings` | Configure the nested option grid without replacing its owned source, columns, or grouping. |
| `cascadeOptions` | Build options from rows matching filters in other columns. |
Default-mode localization includes `selectionTitle` and `selectionSearchPlaceholder`. Excel mode adds the action and blank captions documented in [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/#localize-the-checklist).
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Selection Cascade Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection-cascade/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection-cascade.mdx
Description: Enable context-aware cascading options for RevoGrid Pro selection filters.
Use `filter.selection.cascadeOptions.enabled` to make selection options context-aware.
In cascade mode, RevoGrid builds each column's selection list from rows matching all active filters **except filters for the current column**. This keeps the current column reversible while still narrowing options in related columns.
Use `optionVisibility` to control how values outside that context appear. A value is **context-valid** when at least one source row containing that value matches the active filters in the other columns.
| Mode | Options in the popup | Context-invalid options | Best suited for |
| --- | --- | --- | --- |
| `hide` | Context-valid values only | Not rendered | Compact, spreadsheet-style cascading filters |
| `disable` | Complete column domain | Visible but disabled | Showing users why a known value is currently unavailable |
| `show` | Complete column domain | Visible and interactive | Workflows where users must edit future filter state across the complete domain |
`hide` is the default when cascading is enabled. The modes change option presentation and interaction only; they do not change how rows are filtered.
## `hide`: show only relevant options
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
}
```
Use `hide` for the most direct cascading experience. If the Country filter leaves only Germany, the City popup contains only Berlin and Munich. Paris, Lyon, Warsaw, and Krakow are omitted because they cannot affect the rows in the current country context.
The current column's own filter is still ignored while its option list is built. For example, opening Country continues to show every country allowed by the other active columns, so users can reverse or broaden the Country selection.
## `disable`: reveal unavailable options
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'disable',
}
```
Use `disable` when users benefit from seeing the complete domain. With Germany active, Berlin and Munich remain interactive while cities from France and Poland remain visible with disabled checkboxes.
- Disabled options remain searchable and follow the configured sort order.
- Clicking a disabled option does not change the filter model.
- **Select All** and tree-selection cascades operate only on context-valid options.
- A custom `itemTemplate` receives `disabled: true` for unavailable options.
This mode is useful when an unavailable value should be discoverable without allowing it to change the current cascade.
## `show`: keep the complete domain interactive
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'show',
}
```
Use `show` when users must manage selections across the complete domain even when some values cannot currently affect visible rows. With Germany active, every city remains visible and interactive.
Changing Paris while Germany is active updates the stored City exclusion, but it does not immediately change the displayed rows because no German row has City = Paris. The change becomes observable if the Country filter is later broadened to include France. This makes `show` useful for preparing filter state ahead of an upstream change, but it is less compact than `hide` and offers less availability guidance than `disable`.
## Preserved exclusions and header summaries
Selection filters store excluded values. Every visibility mode preserves those exclusions when an option becomes context-invalid; cascading never silently prunes them.
For example, if Paris is excluded and Country is narrowed to Germany:
- `hide` temporarily removes Paris from the popup.
- `disable` keeps Paris visible and disabled.
- `show` keeps Paris visible and interactive.
When France becomes valid again, Paris returns with its previous unchecked state. The FilterHeader count and tooltip always summarize context-valid values, independently of popup mode. Consequently, `disable` and `show` can display more popup options than the denominator shown in the header badge.
Columns with `cellParser` build and compare their cascading options from parsed values, consistent with the Core [filter parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) behavior.
Set `filter.selection.cascadeOptions.showDependencyNumbers` to `true` to show `1`, `2`, and later badges on active header filter icons in the order those filters are applied. The badges are hidden by default; omit this option or set it to `false` to keep them hidden.
- If `country = Germany` is active, opening the **city** filter shows `Berlin` and `Munich`.
- Opening the **country** filter still shows `Germany`, `France`, and `Poland`.
- The example uses `hide`; switch `optionVisibility` to `disable` or `show` to expose the complete City domain.
- The feature is opt-in and does not change behavior when cascading is disabled.
## Configuration
```ts
grid.filter = {
multiFilterItems: {
department: [{ id: 0, type: 'selection', value: ['sales', 'support'], relation: 'and' }],
country: [{ id: 1, type: 'selection', value: ['france', 'poland'], relation: 'and' }],
},
selection: {
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
showDependencyNumbers: true,
},
},
};
```
## Compatibility with custom getItems
If `selection.getItems` is provided, that custom source is used as-is.
Cascade visibility modes are only applied to the built-in source loader; RevoGrid does not infer contextual availability for custom items.
## Related guides
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filter Header](/guides/data-filter/filter-header/)
---
# Excel-Style Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection-excel-mode/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection-excel-mode.mdx
Description: Configure the Windows Excel-style staged checklist, search actions, blanks, custom values, and date hierarchies.
Surface: grid
Windows Excel mode turns every selection-filter column into a staged checklist modeled after the Windows Excel AutoFilter menu. Users search or check values, review the draft, and then choose **Apply** or **Cancel**.
Excel mode does not read or write XLSX files and does not connect RevoGrid to
Microsoft Excel. It only changes the interaction presented by the Pro
selection filter.
## Quick start
Register `AdvanceFilterPlugin`, enable the selection family on a column, and set the global selection preset to `windows`:
```ts
import { AdvanceFilterPlugin, FIlTER_SELECTION } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ name: 'Status', prop: 'status', filter: [FIlTER_SELECTION] },
{ name: 'Owner', prop: 'owner', filter: ['string'] },
];
grid.filter = {
selection: {
excelMode: 'windows',
sortDirection: 'asc',
},
};
grid.source = [
{ status: 'Open', owner: 'Ana' },
{ status: 'Closed', owner: 'Maks' },
];
```
`selection.excelMode` is grid-wide selection configuration, but it only changes columns that include the `selection` filter. The Owner column above keeps its ordinary text-filter popup.
The example uses the same setup in Vanilla TypeScript, Vue, React, and Angular. Open the Fruit or Availability filter for a flat checklist, or Harvest date for the date hierarchy.
## Apply, cancel, and close
Excel mode is always transactional, even when `disableDynamicFiltering` is `false` or omitted:
- Checking values and typing in the mini-filter only changes the popup draft.
- **Apply** commits the draft and closes the popup.
- **Cancel**, Escape, outside click, the close button, and switching to another popup discard the draft.
- Replacing the grid source or filter configuration while the popup is open also discards its draft.
- Pressing Enter in the mini-filter applies the current search result and closes the popup.
The popup is checklist-only; it does not show the normal operator dropdown. Use the standard [Selection Filter](/guides/data-filter/filter-types/filter-selection/) when changes should apply immediately or users need the ordinary filter-family workflow.
Rows remain unchanged while the checklist is edited and update only after
Apply.
## Search and Select All
Typing a non-empty mini-filter selects the matching results and changes the parent action to **Select All Search Results**. Applying immediately keeps the matches and discards selections hidden by the search.
Enable **Add current selection to filter** when the result should instead be the union of the last applied selection and the current matches. Clearing the search returns the checklist to the last applied state.
**Select All** affects the currently visible or searched options. Parent checkboxes show a mixed state when only some of their leaf values are selected.
Search selects its matches; Add current selection preserves the previously
applied values as well.
### Clearing the condition
Checked values are included. Internally, the selection filter stores unchecked values in an excluded-value `Set`. Applying either of these boundary states removes the selection condition:
- every available value is checked
- every available value is unchecked
This normalization avoids leaving a hidden condition that represents the complete option set.
## Blanks and option sources
Empty strings, `null`, and other normalized blank options share the **(Blanks)** item. It remains searchable and sorts after nonblank options in Excel mode.
By default, options are derived from the current grid rows and account for compatible filters on other columns. An explicit synchronous or asynchronous `selection.getItems` is authoritative:
```ts
grid.filter = {
selection: {
excelMode: 'windows',
getItems: {
status: async () => {
const response = await fetch('/api/filter-options/status');
return response.json(); // [{ value: 'open', label: 'Open' }, ...]
},
},
},
};
```
Keep each `value` aligned with the normalized value used for row comparison. Custom item templates, grouping, nested-grid settings, parsed cell values, and cascading options work as described in the general [Selection Filter guide](/guides/data-filter/filter-types/filter-selection/).
## Date hierarchy
Excel mode creates a local-calendar **Year → Month → Day** tree only when the column provides all three signals:
```ts
grid.columns = [
{
name: 'Created',
prop: 'createdAt',
columnType: 'date',
filter: ['date', 'selection'],
},
];
```
The hierarchy starts collapsed and uses browser-locale month names. Multiple timestamps on the same local day share one day node. Parent and Select All checkboxes derive their mixed state from the raw leaf values. Invalid nonblank values remain selectable as flat root options, followed by **(Blanks)**.
The hierarchy groups raw values by their browser-local calendar day while
preserving each raw leaf for filtering.
Setting only `columnType: 'date'`, or only including `date` in `filter`, is not enough. Without the full configuration, the column uses a flat selection list.
## Restore applied selection
Restore the same state model used by the default selection filter. The `Set` contains normalized values that should be unchecked:
```ts
grid.filter = {
multiFilterItems: {
status: [
{
id: 1,
type: 'selection',
value: new Set(['closed', 'archived']),
relation: 'and',
hidden: true,
},
],
},
selection: {
excelMode: 'windows',
},
};
```
Do not replace the full `filter` object with only `multiFilterItems` when the grid also uses localization or selection settings. Build the complete configuration or use the active plugin APIs described in [Filter State and Presets](/guides/data-filter/filter-state/).
## Localize the checklist
Override only the captions the application needs:
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
captions: {
selectionSearchPlaceholder: 'Search values...',
selectionApply: 'Use values',
selectionCancel: 'Discard',
selectionSelectAll: '(All values)',
selectionSelectAllSearchResults: '(All search results)',
selectionAddCurrentSelection: 'Keep the current selection too',
selectionBlanks: '(Empty)',
},
},
selection: {
excelMode: 'windows',
},
});
```
`defineAdvancedFilterConfig` fills the remaining default filter names and captions.
## Common mistakes
- Expecting Excel mode on a column that does not include the `selection` filter.
- Treating the mini-filter as a live grid search; its draft is committed only by Apply or Enter.
- Expecting `disableDynamicFiltering` to remove Apply and Cancel from Excel mode.
- Saving `multiFilterItems` directly as JSON without converting selection `Set` values.
- Configuring only one of the two required date signals and expecting a hierarchy.
- Returning display labels from `getItems` that do not map to the values compared with row data.
## Related guides
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Slider Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-slider/
Source: src/content/docs/guides/data-filter/filter-types/filter-slider.mdx
Description: Add a numeric range slider with parsed bounds, editable inputs, formatted values, and optional inline header controls.
The slider filter gives numeric columns a two-handle range control. It derives minimum and maximum bounds from eligible row values and stores the active interval in the advanced filter model.
For a fuller example with formatting and additional slider options:
## Quick start
Add `AdvanceFilterPlugin`, then combine the `number` and `slider` filter families on a numeric column:
```ts
import { AdvanceFilterPlugin, FIlTER_SLIDER } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Price',
prop: 'price',
filter: ['number', FIlTER_SLIDER],
},
];
grid.filter = true;
grid.source = [{ price: 25 }, { price: 80 }, { price: 140 }];
```
Returning both handles to the complete data range clears the slider condition. Add `FilterHeaderPlugin` when the same range control should also appear directly in the column header.
Add options only when the default control needs customization:
```ts
grid.filter = {
slider: {
showRangeInputs: true,
formatValue: (value) => `$${value.toFixed(2)}`,
},
};
```
## Parsed cell values and bounds
When the slider column defines `cellParser`, the slider calculates its minimum and maximum bounds from parsed values and applies range filtering to those same values. Use this when the stored value needs normalization before numeric filtering.
Grouping rows and non-finite parsed values are excluded from the bound calculation. The `@revolist/pivot` plugin excludes its total rows through Advanced Filter's cancelable `beforefilteroptionsourcerow` extension event. If no finite values remain, the slider falls back to `0` for both bounds.
```typescript
grid.columns = [
{
name: 'Price',
prop: 'priceInCents',
filter: ['number', 'slider'],
cellParser: (model, column) => Number(model[column.prop] ?? 0) / 100,
},
];
```
See the Core guide on [filtering parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) for the underlying `cellParser` contract.
## Configuration reference
The slider filter can be customized using the following configuration options:
| Option | Type | Default | Description |
| ------------------ | --------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `headerView` | `'slider' \| 'range'` | `'slider'` | With `FilterHeaderPlugin`, shows either the interactive slider or a compact formatted `from–to` range in the column header |
| `showTooltips` | boolean | true | Shows tooltips when hovering over the slider handles |
| `showRangeDisplay` | boolean | true | Displays the current range values above the slider |
| `showRangeInputs` | boolean | false | Shows numeric inputs for editing the selected range |
| `formatValue` | function | `(value) => value.toFixed(2)` | Custom function to format the displayed values |
| `formatInputValue` | function | `(value) => String(value)` | Custom function to format values inside editable range inputs |
| `parseInputValue` | function | `(value) => Number(value.replace(',', '.'))` | Custom function to parse editable range input text |
## Styling
The slider component supports custom styling through CSS variables:
```css
revo-grid {
--slider-color: #c6c6c6; /* Color of the inactive slider track */
--range-color: #0068f0; /* Color of the active slider range */
--tooltip-bg: #333333; /* Tooltip background color */
--tooltip-color: #ffffff; /* Tooltip text color */
}
/* Dark theme support */
revo-grid[theme^='dark'] {
--slider-color: #c6c6c6;
--range-color: #0068f0;
--filter-input-bg: #333333;
--filter-input-color: #ffffff;
--tooltip-bg: #f2f2f6;
--tooltip-color: #333333;
}
```
## Interaction behavior
1. **Range Selection**:
- The slider provides two handles for selecting a range of values
- Values are automatically normalized between the minimum and maximum values in your dataset
- Real-time filtering as users adjust the range
2. **Tooltips**:
- Interactive tooltips show the exact value when hovering over slider handles
- Tooltips can be customized using the `formatValue` function
- Position updates dynamically as the handles move
3. **Range Display**:
- Optional display of current range values above the slider
- Formatted values using the same `formatValue` function
- Clear visual feedback of the selected range
4. **Theme Integration**:
- Automatically adapts to light and dark themes
- Customizable colors through CSS variables
- Consistent look and feel with the rest of your grid
## Combine with other filters
The slider filter can be combined with other filter types for more complex filtering scenarios:
```typescript
grid.columns = [
{
name: 'Price',
prop: 'price',
filter: ['number', 'slider', 'selection'], // Combine with selection filter
},
];
```
## Usage guidance
1. **User experience**:
- Use `showTooltips` for precise value selection
- Implement `formatValue` for meaningful value display
- Enable `showRangeDisplay` for clear range visualization
2. **Accessibility**:
- The handles are native range inputs and remain keyboard-operable.
- Use `showRangeDisplay` or editable range inputs when users also need exact textual values.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Fiscal Date Filters
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/fiscal-date-filters/
Source: src/content/docs/guides/data-filter/filter-types/fiscal-date-filters.mdx
Description: Configure fiscal years and quarters for Date, Datetime, Relative Window, expressions, and remote filtering.
Surface: grid
Fiscal filters answer business-calendar questions such as “orders in this fiscal quarter” or “events in the previous fiscal year.” Configure the first day of the fiscal year once, then use dynamic operators that move forward with the current date.
For example, with a fiscal year beginning on February 1, September 3, 2026 belongs to:
- the fiscal year from February 1, 2026 through January 31, 2027;
- the fiscal quarter from August 1 through October 31, 2026.
Calendar operators do not change meaning. “This quarter” in the Date or Datetime filter still uses January, April, July, and October boundaries; “This fiscal quarter” uses the configured fiscal calendar.
## Configure the fiscal year
`month` is one-based. The start must be a real calendar date that occurs every year, so February 29 is not accepted.
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'accountingDate', name: 'Accounting date', filter: 'date' },
{ prop: 'postedAt', name: 'Posted at', filter: 'datetime' },
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
timezoneMode: 'organization',
organizationTimeZone: 'Europe/Lisbon',
},
};
```
Once `fiscalYearStart` is configured, Date and Datetime menus include all six fiscal operators by default. The timezone decides which civil date is “today” when the period is resolved.
## Supported filters
| Filter | Fiscal behavior |
| --- | --- |
| [Date Filter](../date-filter/) | Supports This, Previous, and Next fiscal quarter and fiscal year. |
| [Datetime Filter](../datetime-filter/) | Supports the same periods for exact instants, resolved in the column timezone. Saved IDs use the `datetime` prefix. |
| [Relative Window](../relative-window/) | `thisQuarter`, `yearToDate`, and non-rolling custom quarter/year windows align to `fiscalYearStart`. |
| [Filter Expressions](/guides/data-filter/filter-expression/) | Supports phrases such as `this fiscal quarter` and `previous fiscal year` on date-aware columns. |
| [Date range filter](../calendar-range/) | Does not create fiscal periods automatically; users can select the fiscal boundary dates explicitly. |
| [Timeline Brush](../timeline-brush/) | Supports an opt-in `fiscalYear` mode that groups source values at the configured fiscal-year boundary. |
| [Time Matrix](../time-matrix/) | Does not use fiscal periods; it filters weekday and hour combinations. |
`fiscalYearStart` is shared date policy, but only controls that define aligned quarter or year semantics consume it.
## Group a Timeline Brush by fiscal year
Add `fiscalYear` to the Timeline Brush's enabled granularities to show fiscal-year bins. The mode uses the effective `fiscalYearStart` for the column, so a February 1 anchor produces bins from February 1 through January 31. It is opt-in to avoid duplicating the ordinary calendar-year mode.
```ts
import {
FILTER_TIMELINE_BRUSH,
createTimelineBrushStructuredFilterType,
} from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'accountingDate',
name: 'Accounting date',
filter: ['date', FILTER_TIMELINE_BRUSH],
},
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
structuredFilterTypes: [
createTimelineBrushStructuredFilterType({
granularity: 'fiscalYear',
granularities: ['year', 'fiscalYear'],
}),
],
};
```
Use `format` when you want fiscal-specific labels such as `FY 2026`; the formatter receives the resolved fiscal-year start in its context.
## Choose the available fiscal operators
Use `date.operators` as an ordered allowlist. The same unprefixed IDs configure both Date and Datetime menus; Datetime maps them to its prefixed saved IDs internally.
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
operators: [
'thisFiscalQuarter',
'previousFiscalQuarter',
'nextFiscalQuarter',
'thisFiscalYear',
'previousFiscalYear',
'nextFiscalYear',
],
},
};
```
| Menu label | Date saved ID | Datetime saved ID |
| --- | --- | --- |
| This fiscal quarter | `thisFiscalQuarter` | `datetimeThisFiscalQuarter` |
| Previous fiscal quarter | `previousFiscalQuarter` | `datetimePreviousFiscalQuarter` |
| Next fiscal quarter | `nextFiscalQuarter` | `datetimeNextFiscalQuarter` |
| This fiscal year | `thisFiscalYear` | `datetimeThisFiscalYear` |
| Previous fiscal year | `previousFiscalYear` | `datetimePreviousFiscalYear` |
| Next fiscal year | `nextFiscalYear` | `datetimeNextFiscalYear` |
These operators need no value. They are resolved from the captured reference instant, timezone, and fiscal-year start every time filtering runs.
## Configure individual columns
Put exceptions in `date.columns`. A column inherits the grid policy, then applies its partial override. A column-level `operators` array replaces the global list for that field.
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 1, day: 1 },
operators: ['thisFiscalQuarter', 'thisFiscalYear'],
columns: {
accountingDate: {
fiscalYearStart: { month: 2, day: 1 },
operators: ['thisFiscalQuarter', 'previousFiscalQuarter'],
},
subscriptionDate: {
fiscalYearStart: { month: 7, day: 1 },
operators: ['thisFiscalYear', 'previousFiscalYear'],
},
},
},
};
```
This is useful when subsidiaries, products, or reporting columns follow different calendars.
## Restore fiscal filter state
Date and Datetime fiscal conditions are value-free:
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
multiFilterItems: {
accountingDate: [
{
id: 1,
type: 'thisFiscalQuarter',
relation: 'and',
},
],
postedAt: [
{
id: 2,
type: 'datetimePreviousFiscalYear',
relation: 'and',
},
],
},
};
```
The canonical Filter AST uses the unprefixed operator with the field's temporal value type:
```ts
{
type: 'condition',
field: 'accountingDate',
operator: 'thisFiscalQuarter',
valueType: 'date',
}
```
Do not save resolved start and end dates for these operators. Keeping the relative operator makes a saved view advance into the next fiscal period automatically.
## Use fiscal Relative Windows
Relative Window uses the fiscal calendar for its quarter and year alignment:
```ts
import {
FILTER_RELATIVE_WINDOW,
createRelativeWindowStructuredFilterType,
} from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'dueDate',
name: 'Due date',
filter: ['date', FILTER_RELATIVE_WINDOW],
},
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
structuredFilterTypes: [
createRelativeWindowStructuredFilterType({
presets: ['thisQuarter', 'yearToDate'],
customWindow: {
units: ['quarter', 'year'],
showRolling: false,
defaultValue: {
direction: 'last',
amount: 1,
unit: 'quarter',
rolling: false,
},
},
}),
],
};
```
- `thisQuarter` resolves the current fiscal quarter.
- `yearToDate` begins at the current fiscal-year start.
- A custom quarter or year with `rolling: false` follows complete fiscal periods.
- With `rolling: true`, the window is a duration from today and fiscal boundaries do not affect it.
Relative Window saves a versioned expression such as `{ version: 1, mode: 'preset', preset: 'thisQuarter' }`, not the resolved dates.
## Remote filtering
Fiscal periods depend on one reference instant and the effective policy for each column. When filtering is server-side, send the `temporalContext` supplied to `beforefilterapply`, pagination, or infinity-scroll loading. It contains:
- the captured `referenceInstant`;
- each temporal column's resolved timezone and `fiscalYearStart`;
- the resolved UTC `start` and exclusive `endExclusive` boundaries.
Using this envelope prevents the browser and server from resolving “this fiscal quarter” at different times or in different timezones.
## Common mistakes
- Configuring only `operators` without `fiscalYearStart`; fiscal operators stay hidden.
- Using zero-based months; January is `1`, not `0`.
- Expecting `thisQuarter` in the Date filter to become fiscal; use `thisFiscalQuarter`.
- Expecting Calendar Range to generate fiscal periods, or Timeline Brush to do so without enabling `fiscalYear`.
- Persisting resolved dates instead of the relative fiscal operator or Relative Window expression.
- Recalculating the period independently on a remote server instead of using `temporalContext`.
## Related guides
- [Date Filter](../date-filter/)
- [Datetime Filter](../datetime-filter/)
- [Relative or Rolling Window Filter](../relative-window/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Fuzzy Search Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/fuzzy-search/
Source: src/content/docs/guides/data-filter/filter-types/fuzzy-search.mdx
Description: Match scalar text approximately with a configurable similarity threshold and ranked preview.
Surface: grid
Use the Fuzzy Search filter for names, titles, and other text where misspellings or transposed characters should still find a result. It applies deterministic Damerau-Levenshtein similarity and previews the best matching unique values.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_FUZZY } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'customer', name: 'Customer', filter: ['string', FILTER_FUZZY] },
];
grid.filter = true;
```
The popup lets users enter a term and adjust its similarity threshold. Keeping `'string'` also exposes exact text operators.
## Operator and state
| Body ID | Saved operator | Value shape |
| ------- | -------------- | ------------------------------------- |
| `fuzzy` | `fuzzy` | `{ term: string, threshold: number }` |
The term is trimmed. The threshold is clamped to `0..1` and defaults to `0.55` when the saved value is missing or invalid. Matching is case-insensitive; exact and contained text score highly, while word-level similarity also contributes.
## Restore a fuzzy filter
```ts
grid.filter = {
multiFilterItems: {
customer: [
{
id: 1,
type: 'fuzzy',
value: { term: 'Jon Smth', threshold: 0.7 },
relation: 'and',
},
],
},
};
```
An empty or whitespace-only term removes the condition.
## Key behavior
- The preview ranks at most five matches by default and considers at most 2,000 unique candidates.
- The predicate and preview use the same deterministic score.
- `null`, `undefined`, objects, and functions do not match; other scalar values are converted to text.
- The preview reads parsed unique column values. With remote or partially loaded rows, provide complete structured-filter aggregates when the preview must represent the full dataset.
The built-in has no per-column option object. Store the desired threshold in the condition value; create or replace a structured registration only when you need a different editor experience.
## Common mistakes
- Do not use a percentage such as `70`; thresholds use the `0..1` range, so use `0.7`.
- Do not expect the preview to scan an unlimited high-cardinality column; its candidate work is deliberately bounded.
- Do not use fuzzy search for exact identifiers. Use the [Token List filter](../token-list/) instead.
## Related guides
- [Structured Filter Types](./../)
- [Token List Filter](../token-list/)
- [Regular Expression Filter](../regular-expression/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Histogram Brush Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/histogram-brush/
Source: src/content/docs/guides/data-filter/filter-types/histogram-brush.mdx
Description: Filter numeric RevoGrid columns by brushing a range over their value distribution.
The histogram brush is useful for prices, durations, revenue, latency, and other numeric fields where users benefit from seeing the distribution before choosing a range. It reads finite parsed values from the column and commits an inclusive numeric interval.
## Quick start
`AdvanceFilterPlugin` registers the built-in type automatically. Add its stable ID to a numeric column:
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_HISTOGRAM_BRUSH,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'product', name: 'Product' },
{
prop: 'revenue',
name: 'Revenue',
filter: ['number', FILTER_HISTOGRAM_BRUSH],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ product: 'Alpha', revenue: 120 },
{ product: 'Beta', revenue: 480 },
{ product: 'Gamma', revenue: 950 },
];
```
## Operator and state
- Type ID: `FILTER_HISTOGRAM_BRUSH` (`'histogramBrush'`)
- Operator ID: `HISTOGRAM_BRUSH_BETWEEN` (`'histogramBrushBetween'`)
- Value: `{ min: number, max: number, inclusive: true }`
Only finite parsed values participate in the histogram and range match. Returning both handles to the complete numeric domain removes the no-op condition. A logarithmic scale falls back to linear when the data includes zero or negative values.
## Restore a range programmatically
Use the same operator and JSON-safe value in `multiFilterItems`:
```ts
import {
HISTOGRAM_BRUSH_BETWEEN,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
revenue: [
{
id: 1,
type: HISTOGRAM_BRUSH_BETWEEN,
value: { min: 250, max: 800, inclusive: true },
relation: 'and',
},
],
},
});
```
## Configuration
Replace the default registration with a configured instance. Passing only the replacement is enough; the plugin keeps the other built-ins.
```ts
import {
createHistogramBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const histogram = createHistogramBrushStructuredFilterType({
bins: 12, // a positive number or 'auto'
scale: 'log',
formatValue: (value) => `$${value.toLocaleString()}`,
formatMatchCount: (count) => `${count.toLocaleString()} rows`,
chart: {
type: 'line', // 'bar' | 'line' | 'area'; default is 'bar'
showPoints: true,
formatTooltip: (bin, { minLabel, maxLabel, selected }) => ({
title: `${minLabel} – ${maxLabel}`,
details: [`${bin.count} rows`, selected ? 'Selected' : 'Outside selection'],
}),
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [histogram],
});
```
`bins: 'auto'` uses a deterministic Sturges-rule count, capped for predictable rendering. A fixed count is normalized to the supported range. Use `cellParser` on the column when stored values need conversion before numeric filtering.
The chart and range slider are separate shared controls. Default per-bin tooltips are keyboard
reachable and localize through `histogramBrushChart`, `histogramBrushItem`, `histogramBrushItems`,
`histogramBrushSelected`, and `histogramBrushOutsideSelection` captions. `formatTooltip` can return
different title/detail content for every bin.
## Common mistakes
- Omitting `AdvanceFilterPlugin`; the Core filter plugin does not render structured bodies.
- Adding the operator ID to `column.filter`; columns opt in with `FILTER_HISTOGRAM_BRUSH`, not `HISTOGRAM_BRUSH_BETWEEN`.
- Restoring strings such as `'250'`; the saved bounds must be finite numbers and `inclusive` must be `true`.
- Expecting `scale: 'log'` with zero or negative data; the editor intentionally uses linear geometry instead.
- Reassigning `grid.filter` with only `multiFilterItems` after configuring a custom registration; include `structuredFilterTypes` again or update the plugin state through its public API.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/)
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Number Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/number-filter/
Source: src/content/docs/guides/data-filter/filter-types/number-filter.mdx
Description: Configure Core numeric equality, comparisons, ranges, blank checks, initial state, and parsed values.
Surface: grid
Use the Core number filter for prices, quantities, percentages, scores, and other numeric fields. It needs no Pro plugin.
## Quick start
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'price', name: 'Price', filter: 'number' },
{ prop: 'quantity', name: 'Quantity', filter: 'number' },
];
grid.filter = true;
grid.source = [
{ price: 120, quantity: 2 },
{ price: 80, quantity: 10 },
];
```
## Operators
| Saved ID | Popup label | Meaning |
| ---------- | ------------ | --------------------------- |
| `eqN` | `=` | Equal |
| `neqN` | `!=` | Not equal |
| `gt` | `>` | Greater than |
| `gte` | `>=` | Greater than or equal |
| `lt` | `<` | Less than |
| `lte` | `<=` | Less than or equal |
| `empty` | Is blank | Uses the blank policy |
| `notEmpty` | Is not blank | Inverse of the blank policy |
Numeric comparisons require the parsed cell value to be a number. Use `cellParser` when source values arrive as formatted strings.
## Start with a numeric range
Use two conditions joined by `and`:
```ts
grid.filter = {
multiFilterItems: {
price: [
{ id: 1, type: 'gte', value: 100, relation: 'and' },
{ id: 2, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
## Parse formatted source values
```ts
grid.columns = [
{
prop: 'price',
name: 'Price',
filter: 'number',
cellParser: (model, column) =>
Number(String(model[column.prop] ?? '').replace(/[$,]/g, '')),
},
];
```
Filtering and bounds-based Pro controls use the parsed number. Blank checks still use the original source value and own-property presence.
## Choose Core conditions or a Pro control
Use Core operators when users know the exact threshold they want. Choose a visual control when the data shape should guide them:
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/) for a simple inclusive interval
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/) for a range over a visible distribution
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/) for stars, percentages, or scores
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/) for relative population rules
## Common mistakes
- Use `eqN` for numeric equality and `eq` for text equality.
- Convert numeric strings with `cellParser`; `gt`, `gte`, `lt`, and `lte` do not treat arbitrary strings as numbers.
- Keep `0` distinct from blank unless your application explicitly changes the blank policy.
- Use two conditions for a range; there is no Core `between` operator in the popup.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Text Filter](/guides/data-filter/filter-types/text-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
---
# Overview
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/overview/
Source: src/content/docs/guides/data-filter/filter-types/overview.mdx
Description: Choose a Core or Pro filter type and open the focused setup, state, and configuration guide for that control.
Surface: grid
Filter types cover Core condition families, Pro value controls, and global quick filtering. Choose the control that matches the column data and the way users need to narrow it.
## Common filter controls
| Type | Best for |
| -------------------------------------------------------- | ----------------------------------------- |
| [Text](../text-filter/) | Names, labels, codes, and descriptions |
| [Number](../number-filter/) | Equality, comparisons, and numeric ranges |
| [Selection](../filter-selection/) | Searchable checklists of known values |
| [Excel-style Selection](../filter-selection-excel-mode/) | Staged Apply/Cancel checklists |
| [Selection Cascade](../filter-selection-cascade/) | Context-aware dependent value lists |
| [Slider](../filter-slider/) | Visual numeric interval selection |
| [Date](../date-filter/) | Calendar days and relative periods |
| [Datetime](../datetime-filter/) | Exact instants and timezone-aware ranges |
| [Boolean](../boolean-filter/) | Strict Yes/No values |
| [Array](../array-filter/) | Empty and non-empty arrays |
| [Quick Filter](../quick-filter/) | Search words across several columns |
Need reporting periods that do not start in January? Configure [Fiscal Date Filters](../fiscal-date-filters/) for Date, Datetime, and aligned Relative Window conditions.
## Structured control quick start
Structured filter types add purpose-built controls to the Pro filter popup while keeping the standard RevoGrid filter model. `AdvanceFilterPlugin` registers all built-ins, but each column opts into the controls it needs.
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
FILTER_HISTOGRAM_BRUSH,
FILTER_TOKEN_LIST,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'sku', filter: ['string', FILTER_TOKEN_LIST] },
{ prop: 'region', filter: ['string', FILTER_FACETED_LIST] },
{ prop: 'revenue', filter: ['number', FILTER_HISTOGRAM_BRUSH] },
];
grid.filter = true;
```
The first array entry keeps the ordinary string or number operators available. The structured type adds its specialized body above the ordinary condition list. Conditions still serialize as `multiFilterItems` and compile into the canonical `FilterAst`.
## Structured controls
| Type | Best for | Reads column data |
| ------------------------------------------------------------- | ------------------------------------- | ---------------------------- |
| [Token List](../token-list/) | Pasted exact values | No |
| [Fuzzy Search](../fuzzy-search/) | Approximate text matches | Unique values for preview |
| [Regular Expression](../regular-expression/) | Pattern-based text matching | Unique values for preview |
| [Faceted List](../faceted-list/) | Searchable values with counts | All and visible value counts |
| [Chip and Badge Toggles](../chip-badge-toggles/) | Small visual status sets | Unique values |
| [Histogram Brush](../histogram-brush/) | Numeric distributions and ranges | Numeric values and bounds |
| [Rating or Progress Threshold](../rating-progress-threshold/) | Stars, percentages, or scores | No |
| [Statistical Presets](../statistical-presets/) | Top 10%, average, quartiles, outliers | Numeric values and bounds |
| [Calendar Range](../calendar-range/) | Inclusive date-only ranges | No |
| [Relative Window](../relative-window/) | Rolling date periods | No |
| [Timeline Brush](../timeline-brush/) | Date or datetime density and ranges | Temporal values |
| [Time Matrix](../time-matrix/) | Weekday and hour schedules | No |
| [Tri-state Boolean](../tri-state-boolean/) | All, Yes, No, and optional blanks | Value counts |
| [Array and Tags](../array-tags/) | Any, all, none, exact, or empty tags | Array values |
## Common setup pattern
Every focused guide uses the same three steps:
1. Register `AdvanceFilterPlugin` once on the grid.
2. Put the structured type constant in the matching column's `filter` array.
3. Use `multiFilterItems` only when the application needs an initial or restored condition.
```ts
grid.filter = {
multiFilterItems: {
sku: [
{
id: 1,
type: 'tokenListAnyOf',
value: ['EU-100', 'EU-200'],
relation: 'and',
},
],
},
};
```
The saved `type` is the operator ID, not the structured body ID. Each type guide lists both IDs and the exact JSON-safe value shape.
## Configuration and remote data
Built-in factory options belong to the structured type registration, not to an individual column. Replace a built-in registration when you need custom formatting, limits, locale, colors, or other presentation policy.
For pagination and infinite-scroll datasets, the browser may hold only part of the result. Supply complete server aggregates when a data-derived control must represent the entire remote dataset rather than the loaded page.
Read [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/) for registration replacement, lazy aggregates, remote prepared data, and building a custom body.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Quick Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/quick-filter/
Source: src/content/docs/guides/data-filter/filter-types/quick-filter.mdx
Description: Add a debounced global search that composes with RevoGrid Pro column filters.
Surface: grid
Quick Filter is RevoGrid Pro's global row search. Use it for a toolbar search box that should look across several columns without creating one filter condition per field.
## Quick start
Register `AdvanceFilterPlugin`, configure the searchable columns, and assign the search text to `grid.quickFilter`:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'customer', name: 'Customer' },
{ prop: 'city', name: 'City' },
{ prop: 'status', name: 'Status', filter: 'string' },
];
grid.filter = true;
grid.quickFilter = 'Lisbon pending';
```
The direct `quickFilter` property is preferred. The older `additionalData.quickFilter` path remains available for compatibility.
## String and object forms
Use a string for the default 150 ms debounce and all visible regular data columns:
```ts
grid.quickFilter = 'Lisbon pending';
```
Use the object form to choose exact column properties or change the debounce:
```ts
grid.quickFilter = {
text: 'Lisbon pending',
columns: ['city', 'status'],
debounceMs: 250,
};
```
| Field | Default | Behavior |
| ------------ | ---------------------------- | ----------------------------------------- |
| `text` | Required | Trimmed and collapsed to single spaces |
| `columns` | Visible regular data columns | Exact, duplicate-free property list |
| `debounceMs` | `150` | Finite values are clamped to at least `0` |
Omitting `columns` searches visible regular columns. Providing `columns: []` deliberately searches no fields and therefore matches no row while the query contains a token.
## How tokens match
Search is case-insensitive. Normalized text is split on spaces, and every token must appear in at least one searched column. Different tokens may match different columns in the same row.
For example, `Lisbon pending` matches a row when `Lisbon` appears in its city and `pending` appears in its status. This is equivalent to:
```text
(customer contains Lisbon OR city contains Lisbon OR status contains Lisbon)
AND
(customer contains pending OR city contains pending OR status contains pending)
```
Quick Filter reads parsed cell values. A column `cellParser` or Pro value-resolution hook therefore affects both global search and advanced filter option values. Dropdown columns are searched by their visible option labels, including every label in a multi-select value, even when the row stores option IDs through `valueKey`.
## Clear the search
Assign an empty or whitespace-only string:
```ts
grid.quickFilter = '';
```
Whitespace normalization means `' '` also clears the active quick filter.
## Observe or intercept changes
Quick-filter changes emit a cancelable event before filtering and a completion event afterward:
```ts
grid.addEventListener('beforequickfilterapply', (event) => {
console.log('Applying', event.detail.quickFilter);
});
grid.addEventListener('afterquickfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
console.log('Applied', event.detail.quickFilter, visibleRows.length);
});
```
Both event details contain the normalized `quickFilter`, current `source`, and columns. Clearing emits the same events with `quickFilter` set to `undefined`. If `beforequickfilterapply` is prevented, local filtering and `afterquickfilterapply` do not run.
## Combine with column filters
Quick Filter is ANDed with active column conditions and the canonical Filter AST:
```ts
grid.filter = {
multiFilterItems: {
amount: [{ id: 1, type: 'gte', value: 200, relation: 'and' }],
},
};
grid.quickFilter = 'Lisbon pending';
```
A row must satisfy the amount condition and every quick-filter token. Quick Filter does not add a synthetic column entry to `multiFilterItems`; Pro composes it into the effective `FilterAst` with `quickContains` conditions.
## Remote data
The Pro Pagination, Infinity Scroll, and Server-Side Grouping plugins intercept `beforequickfilterapply`, prevent local filtering, and pass the normalized quick filter to their remote loaders. Pagination and Infinity Scroll receive it as the optional sixth `loadData` argument. They emit `afterquickfilterapply` only after the winning remote request updates the source.
For a custom remote adapter, prevent `beforequickfilterapply`, cancel or ignore stale requests, send `event.detail.quickFilter` to the backend, replace `grid.source`, and emit a completion signal only for the latest response.
## Common mistakes
- Do not expect `columns: []` to mean all columns; omit `columns` for the default search set.
- Do not split the query yourself. RevoGrid normalizes whitespace and applies its AND-across-tokens semantics.
- Do not expect each token to match the same column; tokens may match different fields in one row.
- Do not read `event.detail.source` as the filtered result. Use `getVisibleSource()` after local completion.
- Do not use the deprecated `additionalData.quickFilter` path in new code.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Rating or Progress Threshold Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/rating-progress-threshold/
Source: src/content/docs/guides/data-filter/filter-types/rating-progress-threshold.mdx
Description: Filter ratings, percentages, and bounded scores with an accessible threshold control.
Use this filter for star ratings, completion percentages, health scores, and other bounded numeric values. Users choose greater-than-or-equal, equal, or less-than-or-equal and then set one threshold.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_RATING_PROGRESS_THRESHOLD,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'task', name: 'Task' },
{
prop: 'rating',
name: 'Rating',
filter: ['number', FILTER_RATING_PROGRESS_THRESHOLD],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ task: 'Design', rating: 4.5 },
{ task: 'Build', rating: 3 },
{ task: 'Review', rating: 5 },
];
```
## Operators and state
| Operator | Stable ID | Meaning |
| -------- | --------------------- | ----------------------------------------------- |
| At least | `RATING_PROGRESS_GTE` | Value is greater than or equal to the threshold |
| Exactly | `RATING_PROGRESS_EQ` | Value equals the threshold |
| At most | `RATING_PROGRESS_LTE` | Value is less than or equal to the threshold |
The condition value is one finite number. The type ID is `FILTER_RATING_PROGRESS_THRESHOLD` (`'ratingProgressThreshold'`). Values are clamped to `0…max` and aligned to the configured step.
## Restore a threshold programmatically
```ts
import {
RATING_PROGRESS_GTE,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
rating: [
{
id: 1,
type: RATING_PROGRESS_GTE,
value: 4,
relation: 'and',
},
],
},
});
```
## Configuration
The default presentation is stars with `max: 5` and `step: 1`. Percent and score presentations default to a maximum of `100`.
```ts
import {
createRatingProgressThresholdStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const progress = createRatingProgressThresholdStructuredFilterType({
unit: 'percent', // 'stars', 'percent', or 'score'
max: 100,
step: 5,
formatValue: (value) => `${value}%`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [progress],
});
```
`max` and `step` must be positive. A step larger than the maximum is normalized to the maximum. `formatValue` changes the displayed value text; it does not transform source values.
## Common mistakes
- Using `FILTER_RATING_PROGRESS_THRESHOLD` as the condition operator; saved conditions use one of the three `RATING_PROGRESS_*` operator IDs.
- Supplying a string threshold such as `'80'`; restore a finite number.
- Using `formatValue` to convert a `0…1` source into a percentage. Use `cellParser` for conversion, then configure the visual domain.
- Choosing `RATING_PROGRESS_EQ` with fractional data while using a step that cannot select those fractions.
- Omitting the configured registration when replacing the complete `grid.filter` object later.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/)
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Regular Expression Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/regular-expression/
Source: src/content/docs/guides/data-filter/filter-types/regular-expression.mdx
Description: Match or exclude scalar text with a validated regular expression and bounded preview.
Surface: grid
Use the Regular Expression filter when users need an explicit text pattern, such as an invoice prefix, an email domain, or a controlled identifier format.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_REGEX } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'invoiceId', name: 'Invoice ID', filter: ['string', FILTER_REGEX] },
];
grid.filter = true;
```
The popup validates the pattern before committing it and previews matching unique values.
## Operators and state
| UI mode | Saved operator | Value shape |
| ---------------------- | ----------------- | ------------------------------------ |
| Matches pattern | `regexMatches` | `{ pattern: string, flags: string }` |
| Does not match pattern | `regexNotMatches` | `{ pattern: string, flags: string }` |
Supported flags are `i` for case-insensitive matching and `m` for multiline anchors. Flags are normalized into stable `im` order and duplicates are removed.
## Restore a regular expression
```ts
grid.filter = {
multiFilterItems: {
invoiceId: [
{
id: 1,
type: 'regexMatches',
value: { pattern: '^INV-\\d{6}$', flags: 'i' },
relation: 'and',
},
],
},
};
```
An empty pattern removes the condition. Invalid patterns remain in the editor without replacing the last valid condition.
## Validation and preview
RevoGrid rejects invalid expressions and common nested-quantifier patterns that can cause catastrophic backtracking. The preview shows two matching unique values by default and scans at most 10,000 unique scalar values. Matching uses parsed scalar values; `null`, `undefined`, objects, and functions do not match either operator.
For remote or partially loaded rows, provide complete unique-value aggregates when the preview must describe the full result set. The saved pattern and flags remain the remote execution contract.
## Common mistakes
- Escape backslashes for JavaScript strings. A regex digit class is written as `\\d` inside the saved string.
- Do not add unsupported flags such as `g`, `s`, or `u`; only `i` and `m` are retained.
- Do not expect an invalid edit to clear a working filter; the last valid condition stays active.
- Do not use arbitrary user-supplied patterns without considering backend regex compatibility when filtering remotely.
## Related guides
- [Structured Filter Types](./../)
- [Fuzzy Search Filter](../fuzzy-search/)
- [Token List Filter](../token-list/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Relative or Rolling Window Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/relative-window/
Source: src/content/docs/guides/data-filter/filter-types/relative-window.mdx
Description: Filter dates with saved expressions such as last seven days, this quarter, or a custom rolling window.
Use relative windows for dashboards, queues, renewals, overdue work, and saved views that should move with the current date. Unlike a fixed calendar range, the saved state is an expression that resolves against the filter run's captured reference date.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_RELATIVE_WINDOW,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'task', name: 'Task' },
{ prop: 'dueDate', name: 'Due', filter: ['date', FILTER_RELATIVE_WINDOW] },
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ task: 'Renew license', dueDate: '2026-08-24' },
{ task: 'Send report', dueDate: '2026-08-26' },
{ task: 'Plan launch', dueDate: '2026-09-10' },
];
```
## Operator and state
Both the type and operator use the stable ID `'relativeWindow'`, exported as `FILTER_RELATIVE_WINDOW` and `RELATIVE_WINDOW_OPERATOR`.
Saved values are versioned expressions:
```ts
{ version: 1, mode: 'preset', preset: 'last7Days' }
{
version: 1,
mode: 'custom',
direction: 'next',
amount: 2,
unit: 'week',
rolling: false,
}
```
Preset IDs are `last7Days`, `today`, `thisMonth`, `thisQuarter`, `yearToDate`, and `overdue`. Custom directions are `last` and `next`; units are `day`, `week`, `month`, `quarter`, and `year`.
## Restore a relative window
```ts
import {
RELATIVE_WINDOW_OPERATOR,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
dueDate: [
{
id: 1,
type: RELATIVE_WINDOW_OPERATOR,
value: { version: 1, mode: 'preset', preset: 'last7Days' },
relation: 'and',
},
],
},
});
```
The expression remains relative. Each filtering run captures one reference instant, so every row and date-aware condition in that run uses the same “today.”
## Configuration
The shortcut row and custom-window controls are independently configurable. Built-in preset IDs can be hidden, reordered, or mixed with application-specific shortcuts:
```ts
import {
createRelativeWindowStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const relativeWindow = createRelativeWindowStructuredFilterType({
locale: 'en-GB',
dateFormat: { day: '2-digit', month: 'short', year: 'numeric' },
presets: [
'today',
'thisMonth',
{
id: 'next-fortnight',
label: 'Next 14 days',
title: 'Rolling from today',
expression: {
version: 1,
mode: 'custom',
direction: 'next',
amount: 14,
unit: 'day',
rolling: true,
},
},
'overdue',
],
customWindow: {
directions: ['last', 'next'],
units: ['day', 'week', 'month'],
showRolling: true,
defaultValue: {
direction: 'next',
amount: 2,
unit: 'week',
rolling: false,
},
},
showHeading: true,
showPreview: true,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [relativeWindow],
date: {
timezoneMode: 'organization',
organizationTimeZone: 'Europe/Lisbon',
weekStartsOn: 1,
fiscalYearStart: { month: 2, day: 1 },
},
});
```
Custom shortcuts do not extend or bypass the date-expression model. Each button is backed by a normal validated `RelativeDateWindowExpression`, and selecting it persists only that expression. Its `id` identifies the button; `label`, `ariaLabel`, `title`, and `disabled` control its presentation. A matching custom label is also used in the compact condition summary.
### Visibility and allowed values
| Option | Behavior |
| --- | --- |
| `presets` | Ordered built-in IDs and custom shortcuts. Use `false` or `[]` to hide the shortcut row. |
| `customWindow` | Custom editor settings. Use `false` to hide the whole Last/Next, amount, unit, and rolling section. |
| `customWindow.directions` | Allowed direction choices: `last`, `next`, or both. |
| `customWindow.units` | Allowed unit choices: `day`, `week`, `month`, `quarter`, and/or `year`. |
| `customWindow.showRolling` | Set to `false` to hide the rolling switch and keep the configured/default rolling policy. |
| `customWindow.defaultValue` | Initial direction, amount, unit, and rolling policy when no custom expression is saved. |
| `showHeading` | Set to `false` to hide the in-body title and description while retaining an accessible section label. |
| `showPreview` | Set to `false` to hide the resolved absolute-date preview. |
| `locale`, `dateFormat` | Format preview text only. |
For example, a compact preset-only editor can omit every other section:
```ts
createRelativeWindowStructuredFilterType({
presets: ['today', 'last7Days', 'overdue'],
customWindow: false,
showHeading: false,
showPreview: false,
});
```
The default remains unchanged when these options are omitted: all six built-ins, the complete custom editor, rolling switch, heading, and preview are shown. Duplicate shortcut IDs or expressions and invalid custom expressions are ignored. An empty direction or unit list hides the custom editor because it cannot produce a valid expression.
`locale` and `dateFormat` affect preview text only. Timezone decides the reference civil date, `weekStartsOn` controls calendar-week windows, and `fiscalYearStart` controls quarter and year policy where applicable.
For a custom expression, `amount` must be an integer from `1` through `10,000`. `rolling: true` uses a rolling duration; `rolling: false` resolves complete calendar units.
## Common mistakes
- Saving the preview's absolute `{ from, to }` range instead of the versioned expression; that turns a moving saved view into a fixed range.
- Omitting `version: 1` or using unsupported preset, direction, or unit strings.
- Treating `last7Days` as seven 24-hour durations; it is a civil-date window resolved in the column timezone.
- Using an offset-free datetime source for date filtering without first deciding the intended timezone and source contract.
- Assuming `locale` changes week start, fiscal policy, or timezone; configure those under `filter.date`.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Calendar Range](/guides/data-filter/filter-types/calendar-range/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Statistical Presets Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/statistical-presets/
Source: src/content/docs/guides/data-filter/filter-types/statistical-presets.mdx
Description: Filter numeric columns with stable top-percentile, average, quartile, outlier, and negative-value presets.
Statistical presets turn common analytical questions into one-click filters. Use them for metrics where users frequently ask for the top 10%, values above average, the bottom quartile, two-sigma outliers, or negative values.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_STATISTICAL_PRESETS,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'team', name: 'Team' },
{
prop: 'margin',
name: 'Margin',
filter: ['number', FILTER_STATISTICAL_PRESETS],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ team: 'North', margin: 18 },
{ team: 'South', margin: -4 },
{ team: 'West', margin: 31 },
{ team: 'East', margin: 9 },
];
```
## Operator and state
- Type ID: `FILTER_STATISTICAL_PRESETS` (`'statisticalPresets'`)
- Operator ID: `STATISTICAL_PRESET_OPERATOR` (`'statisticalPreset'`)
- Presets: `top10Percent`, `aboveAverage`, `bottomQuartile`, `outliersTwoSigma`, and `negativeOnly`
- Value: `{ preset, lower, upper, lowerInclusive, upperInclusive, outside }`
The popup computes statistics from the current finite parsed values. When a preset is selected, the resolved numeric boundaries are serialized into the condition. Those boundaries stay stable when the source later changes; reopen and select the preset again to recompute it.
## Restore a preset programmatically
Restore the resolved boundaries, not only the preset name:
```ts
import {
STATISTICAL_PRESET_OPERATOR,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
margin: [
{
id: 1,
type: STATISTICAL_PRESET_OPERATOR,
value: {
preset: 'negativeOnly',
lower: null,
upper: 0,
lowerInclusive: false,
upperInclusive: false,
outside: false,
},
relation: 'and',
},
],
},
});
```
`null` represents an unbounded side. `outside: true` matches values outside the serialized lower and upper boundaries and is used by the two-sigma outlier preset.
## Configuration
```ts
import {
createStatisticalPresetsStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const presets = createStatisticalPresetsStructuredFilterType({
formatValue: (value) => `${value.toFixed(1)}%`,
formatMatchCount: (count) => `${count.toLocaleString()} matches`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [presets],
});
```
Both callbacks change presentation only. Use `cellParser` when the stored value must be normalized before statistics and comparisons are calculated.
## Common mistakes
- Saving only `{ preset: 'top10Percent' }`; all boundary and inclusion fields are required for a valid restored condition.
- Expecting an applied preset to move automatically as rows change; its serialized boundaries are deliberately stable.
- Including `NaN`, `Infinity`, or nonnumeric strings in expected statistics; only finite parsed values participate.
- Treating `negativeOnly` as “zero or below”; its upper boundary is exclusive, so zero does not match.
- Recomputing boundaries independently with different quantile or variance rules when the application can save the condition emitted by the grid.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/)
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Text Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/text-filter/
Source: src/content/docs/guides/data-filter/filter-types/text-filter.mdx
Description: Configure Core text filtering with contains, begins-with, equality, blank checks, initial state, and parsed values.
Surface: grid
Use the Core text filter for names, labels, codes, descriptions, and other values that users should match by typing. It needs no Pro plugin.
## Quick start
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'name', name: 'Name', filter: 'string' },
{ prop: 'status', name: 'Status', filter: 'string' },
];
grid.filter = true;
grid.source = [
{ name: 'Anna Silva', status: 'Open' },
{ name: 'John Smith', status: 'Pending' },
];
```
Open a column's filter button, choose **Contains**, enter text, and apply. Text equality, begins-with, and contains matching are case-insensitive.
## Operators
| Saved ID | Popup label | Needs a value |
| ------------- | ---------------- | ------------- |
| `contains` | Contains | Yes |
| `notContains` | Does not contain | Yes |
| `begins` | Begins with | Yes |
| `eq` | Equal | Yes |
| `notEq` | Not equal | Yes |
| `empty` | Is blank | No |
| `notEmpty` | Is not blank | No |
An empty text input is a no-op. Blank operators use the grid's source-aware [blank policy](/guides/data-filter/blank-values/), not a display-string conversion.
## Start with a condition
```ts
grid.filter = {
multiFilterItems: {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
},
};
```
Combine alternatives within one column with `or`:
```ts
grid.filter = {
multiFilterItems: {
status: [
{ id: 1, type: 'eq', value: 'Open', relation: 'or' },
{ id: 2, type: 'eq', value: 'Pending', relation: 'and' },
],
},
};
```
Different columns always combine with AND in `multiFilterItems`. Use [Filter AST](/guides/data-filter/filter-ast/) for cross-column OR.
## Filter a normalized value
Conditions receive `cellParser` output when the stored model needs normalization:
```ts
grid.columns = [
{
prop: 'customerCode',
name: 'Customer code',
filter: 'string',
cellParser: (model, column) => String(model[column.prop] ?? '').trim(),
},
];
```
## Common mistakes
- Use `eq`, not the numeric `eqN`, for a text column.
- Do not expect text conditions on different columns to combine with OR.
- Use [Selection Filter](/guides/data-filter/filter-types/filter-selection/) when users should choose from repeated known values instead of typing.
- Use [Token List](/guides/data-filter/filter-types/token-list/) when users paste several exact values at once.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Number Filter](/guides/data-filter/filter-types/number-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Header](/guides/data-filter/filter-header/)
---
# Time Matrix Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/time-matrix/
Source: src/content/docs/guides/data-filter/filter-types/time-matrix.mdx
Description: Filter timestamp columns by selected weekday and whole-hour combinations in a resolved timezone.
Surface: grid
Use the time matrix for activity schedules, support coverage, usage windows, or any timestamp question expressed as weekday plus hour. Users can select individual cells or start from Business hours, Weekends, and Nights presets.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TIME_MATRIX,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'occurredAt',
name: 'Activity time',
filter: ['datetime', FILTER_TIME_MATRIX],
},
];
grid.source = [
{ occurredAt: '2026-08-24T09:30:00Z' },
{ occurredAt: '2026-08-25T22:15:00Z' },
];
grid.filter = true;
```
Row values must resolve to instants (`Date`, an ISO timestamp, or a numeric timestamp). The filter converts each instant to weekday and hour in the condition's timezone.
## Operator and saved state
- Structured type: `FILTER_TIME_MATRIX` (`'timeMatrix'`)
- Operator: `TIME_MATRIX_OPERATOR` (`'timeMatrix'`)
- Value: `{ version: 1, timeZone, ranges }`
Each range uses Sunday-based `weekday` (`0` is Sunday), an inclusive `startHour`, and an exclusive `endHour`.
```ts
grid.filter = {
multiFilterItems: {
occurredAt: [
{
id: 1,
type: 'timeMatrix',
value: {
version: 1,
timeZone: 'Europe/Lisbon',
ranges: [
{ weekday: 1, startHour: 9, endHour: 17 },
{ weekday: 2, startHour: 9, endHour: 17 },
],
},
relation: 'and',
},
],
},
};
```
Use that JSON-safe value for programmatic restore. Adjacent selected hours are compacted into ranges. Selecting no cells or every hour of every day removes the no-op condition.
## Configure schedule presentation
```ts
import {
createTimeMatrixStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const activityTime = createTimeMatrixStructuredFilterType({
timeZone: 'Europe/Lisbon',
locale: 'en-GB',
weekStartsOn: 1,
useAmPm: false,
// Context shading in the matrix.
workingHours: { startHour: 8, endHour: 18 },
weekendDays: [5, 6],
// Shortcut buttons are configured independently.
presets: {
businessHours: {
days: [1, 2, 3, 4],
startHour: 9,
endHour: 17,
label: 'Core hours',
description: 'Monday–Thursday, 09:00–17:00',
},
weekends: {}, // Inherits weekendDays ([5, 6]).
nights: {
days: [0, 1, 2, 3, 4, 5, 6],
startHour: 20,
endHour: 6, // An end before the start creates an overnight window.
},
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [activityTime],
});
```
`workingHours` and `weekendDays` shade schedule context; selected cells alone determine filtering. Defaults are 06:00–22:00 and Sunday/Saturday. If `timeZone` or `weekStartsOn` is omitted, it inherits the column's resolved date-filter settings.
The built-in shortcut defaults remain Business hours (Monday–Friday, 08:00–20:00), Weekends (the configured `weekendDays`, all day), and Nights (every day, 22:00–06:00).
| Option | Purpose |
| --- | --- |
| `workingHours` | Start/end boundaries for outside-working-hours shading. |
| `weekendDays` | Sunday-based days used for weekend shading and, by default, the Weekends shortcut. |
| `workingHours: false` | Disable outside-working-hours shading and remove that legend item. |
| `weekendDays: []` | Disable weekend shading and remove that legend item. |
| `showLegend: false` | Hide the complete schedule-context legend. |
| `presets.businessHours` | Configure `days`, `startHour`, `endHour`, `label`, and `description`, or use `false` to hide it. |
| `presets.weekends` | Configure `days`, `label`, and `description`, or use `false` to hide it. An empty object inherits `weekendDays`. |
| `presets.nights` | Configure `days`, an overnight-capable hour window, `label`, and `description`, or use `false` to hide it. |
| `presets: false` | Hide the complete shortcut row while retaining direct matrix selection. |
All days use Sunday-based numbering (`0`–`6`), and all hour boundaries are whole hours (`0`–`24`). A preset window whose `endHour` is earlier than `startHour` wraps across midnight. Invalid days or hour windows fall back to that preset's defaults.
For example, keep Business hours and hide the other shortcuts:
```ts
const activityTime = createTimeMatrixStructuredFilterType({
presets: {
weekends: false,
nights: false,
},
});
```
## Localization
`TIME_MATRIX_LOCALIZATION` exports the complete English fallback and stable keys. Override the operator through `filterNames` and authored UI/accessibility text through `captions`.
```ts
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [activityTime],
localization: {
filterNames: {
timeMatrix: 'Matches schedule',
},
captions: {
timeMatrixTitle: 'Coverage schedule',
timeMatrixDescription: 'Select included weekday and hour combinations.',
timeMatrixClear: 'Clear schedule',
'timeMatrixPreset.businessHours': 'Office hours',
'timeMatrixPresetDescription.businessHours':
'Monday–Friday office coverage',
},
},
});
```
Other stable captions cover the grid and legend, selected/unselected cell states, summaries, and all preset labels/descriptions. Per-instance `label` and `description` values on a preset take precedence over localization captions. Dynamic messages use named placeholders including `{day}`, `{start}`, `{end}`, `{state}`, `{hours}`, `{days}`, and `{timeZone}`. Weekday and clock text comes from `locale` and `useAmPm`, not fixed translated strings.
## Common mistakes
- Do not store local clock text such as `'09:30'`; the predicate needs an instant.
- Do not treat `endHour` as inclusive. `{ startHour: 9, endHour: 17 }` covers 09:00 through 16:59.
- Do not use locale weekday numbering in saved state; persisted weekday is always Sunday-based.
- Do not expect `workingHours` or `weekendDays` shading to add selected cells. Configure shortcut selections through `presets`.
- Use a valid IANA timezone. Invalid `timeZone` configuration throws.
## Related guides
- [Structured Filter Types](./)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Timeline Brush Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/timeline-brush/
Source: src/content/docs/guides/data-filter/filter-types/timeline-brush.mdx
Description: Filter date and datetime columns by brushing an inclusive range over a compact temporal distribution.
Surface: grid
Use the timeline brush when users need to see where date or datetime values are concentrated before choosing a range. It bins parsed column values by day, week, or month and keeps the committed condition independent from the chart UI.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TIMELINE_BRUSH,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'createdAt',
name: 'Created',
filter: ['datetime', FILTER_TIMELINE_BRUSH],
},
];
grid.source = [
{ createdAt: '2026-08-20T09:30:00Z' },
{ createdAt: '2026-08-24T16:15:00Z' },
];
grid.filter = true;
```
Use `'date'` instead of `'datetime'` for ISO date-only values such as `2026-08-20`. Selecting the complete displayed domain is a no-op and removes the timeline condition.
## Operator and saved state
- Structured type: `FILTER_TIMELINE_BRUSH` (`'timelineBrush'`)
- Operator: `TIMELINE_BETWEEN` (`'timelineBetween'`)
- Value: `{ from, to, inclusive: true }`
Date columns store inclusive ISO dates. Datetime columns store inclusive ISO instants.
```ts
grid.filter = {
multiFilterItems: {
createdAt: [
{
id: 1,
type: 'timelineBetween',
value: {
from: '2026-08-20T00:00:00.000Z',
to: '2026-08-24T23:59:59.999Z',
inclusive: true,
},
relation: 'and',
},
],
},
};
```
This is also the programmatic restore shape. Keep the operator ID in `type`; the structured type ID belongs in the column definition.
## Configure the timeline
Replace the built-in registration with a configured instance. The stable ID stays the same, so every column using `FILTER_TIMELINE_BRUSH` receives these options.
```ts
import {
createTimelineBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const timeline = createTimelineBrushStructuredFilterType({
granularity: 'week', // 'day' | 'week' | 'month'
binCap: 48,
timeZone: 'Europe/Lisbon',
locale: 'en-GB',
format: { day: '2-digit', month: 'short', year: 'numeric' },
chart: {
type: 'area', // 'bar' | 'line' | 'area'; default is 'bar'
height: 72,
showPoints: true,
formatTooltip: (bin, { fromLabel, toLabel, selected }) => ({
title: fromLabel === toLabel ? fromLabel : `${fromLabel} – ${toLabel}`,
details: [`${bin.count} events`, selected ? 'Selected' : 'Outside selection'],
}),
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [timeline],
date: { weekStartsOn: 1 },
});
```
`timeZone` and `weekStartsOn` inherit the column's resolved `filter.date` settings when the factory does not override them. Dense spans are deterministically coarsened to stay within `binCap`.
The chart is a reusable presentation control; the dual-range slider remains independent. Its default
tooltips are keyboard reachable and localize through `timelineBrushChart`, `timelineBrushItem`,
`timelineBrushItems`, `timelineBrushSelected`, and `timelineBrushOutsideSelection` captions. Use
`formatTooltip` for domain-specific rows or terminology.
For a partially loaded remote source, provide complete temporal aggregates or exported `TimelineBrushPreparedData`; otherwise the distribution can only describe rows loaded in the browser.
## Common mistakes
- Do not restore `type: 'timelineBrush'`; conditions use `type: 'timelineBetween'`.
- Do not mix date-only and datetime values in one column. Declare the base family explicitly.
- Do not pass display-formatted dates unless a `cellParser` converts them into valid date or instant values.
- Do not expect a full-domain selection to remain in state; it intentionally clears the no-op condition.
- For remote pagination, do not present page-local bins as the complete dataset distribution.
## Related guides
- [Structured Filter Types](./)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Token List Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/token-list/
Source: src/content/docs/guides/data-filter/filter-types/token-list.mdx
Description: Filter scalar values against an exact list pasted or entered as tokens.
Surface: grid
Use the Token List filter when users already have an exact list of identifiers, SKUs, email addresses, or other scalar values. They can paste comma-, newline-, or tab-separated text instead of selecting values from the column.
## Quick start
`AdvanceFilterPlugin` registers the built-in body. Add `FILTER_TOKEN_LIST` to the column's filter families:
```ts
import { AdvanceFilterPlugin, FILTER_TOKEN_LIST } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'sku', name: 'SKU', filter: ['string', FILTER_TOKEN_LIST] },
];
grid.filter = true;
```
Keeping `'string'` in the array also exposes the ordinary text operators. Use only `FILTER_TOKEN_LIST` when the popup should contain only the token workflow.
## Operators and state
| UI mode | Saved operator | Value shape | Matches |
| --------- | ----------------- | ----------- | --------------------------------------------------------- |
| Is any of | `tokenListAnyOf` | `string[]` | A non-null scalar whose string value is one of the tokens |
| None of | `tokenListNoneOf` | `string[]` | Any value that does not match the **Is any of** rule |
Input is trimmed, empty tokens are removed, duplicates keep their first position, and comparisons are exact and case-sensitive. Objects and functions never match **Is any of**. The body does not scan source rows, so it is also suitable when the complete value list comes from outside the grid.
## Restore a token list
Use the operator ID—not `FILTER_TOKEN_LIST`—inside `multiFilterItems`:
```ts
grid.filter = {
multiFilterItems: {
sku: [
{
id: 1,
type: 'tokenListAnyOf',
value: ['EU-100', 'EU-200', 'US-500'],
relation: 'and',
},
],
},
};
```
Assign an empty `multiFilterItems` object to clear restored filters:
```ts
grid.filter = { multiFilterItems: {} };
```
## Key behavior
- Pasting `EU-100, EU-200\nUS-500` creates three tokens.
- Numeric and boolean cell values are compared through their string representation.
- The condition value is a JSON-safe array and can be transported with the canonical filter AST.
- Removing every token removes the owned condition instead of saving an empty no-op filter.
## Common mistakes
- Do not save `type: 'tokenList'`; that is the body ID. Save `tokenListAnyOf` or `tokenListNoneOf`.
- Do not expect fuzzy or case-insensitive matching. Use the [Fuzzy Search filter](../fuzzy-search/) for approximate text.
- Do not pass one comma-containing value when commas are meaningful inside the value; pasted commas are delimiters.
## Related guides
- [Structured Filter Types](./../)
- [Faceted List Filter](../faceted-list/)
- [Regular Expression Filter](../regular-expression/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Tri-state Boolean Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/tri-state-boolean/
Source: src/content/docs/guides/data-filter/filter-types/tri-state-boolean.mdx
Description: Filter strict boolean values with All, Yes, No, and an optional blanks-as-No policy.
Surface: grid
Use the tri-state boolean filter when a boolean column needs one compact control for **All**, **Yes**, and **No**. It is especially useful when missing values may optionally belong to the No result.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TRI_STATE_BOOLEAN,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'approved',
name: 'Approved',
filter: ['boolean', FILTER_TRI_STATE_BOOLEAN],
},
];
grid.source = [{ approved: true }, { approved: false }, { approved: null }];
grid.filter = true;
```
The editor reports Yes, No, and blank counts from provider-backed `valueCounts`. Choosing All removes its condition.
## Operator and saved state
- Structured type: `FILTER_TRI_STATE_BOOLEAN` (`'triStateBoolean'`)
- Operator: `TRI_STATE_BOOLEAN_OPERATOR` (`'triStateBoolean'`)
- Value: `{ state: 'yes' | 'no', coerceBlank: boolean }`
Yes matches strict `true`. No matches strict `false`; with `coerceBlank: true`, it also matches values classified as blank by the active Core blank policy.
```ts
grid.filter = {
multiFilterItems: {
approved: [
{
id: 1,
type: 'triStateBoolean',
value: { state: 'no', coerceBlank: true },
relation: 'and',
},
],
},
};
```
Use the same condition for programmatic restore. There is no saved `{ state: 'all' }` condition because All means no filtering.
## Configure blank behavior
The blank toggle delegates to the shared Core blank-semantics policy. Configure that policy at grid level and override individual columns only when necessary.
```ts
grid.filter = {
blankSemantics: {
null: true,
undefined: true,
emptyString: true,
whitespaceOnlyString: false,
missingProperty: true,
},
};
grid.columns = [
{
prop: 'approved',
filter: ['boolean', FILTER_TRI_STATE_BOOLEAN],
blankSemantics: {
emptyString: false,
},
},
];
```
The structured body is already built in and has no presentation factory options. Customize its operator label with `localization.filterNames.triStateBoolean` and body captions through the standard advanced-filter localization config.
## Common mistakes
- Do not use strings such as `'true'` and `'false'`; Yes and No match boolean primitives strictly.
- Do not persist All as an active condition; remove the column's condition instead.
- Do not assume `coerceBlank` means every falsy value. It uses configured blank semantics, not JavaScript truthiness.
- Do not duplicate blank rules in application code; set grid- or column-level `blankSemantics`.
- For remote data, provide complete `valueCounts` if the browser holds only a page of rows.
## Related guides
- [Structured Filter Types](./)
- [Boolean Filters](/guides/data-filter/filter-types/boolean-filter/)
- [Blank Value Semantics](/guides/data-filter/blank-values/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Custom Structured Filter Types
URL: https://pro.rv-grid.com/guides/data-filter/structured-filter-types/
Source: src/content/docs/guides/data-filter/structured-filter-types.mdx
Description: Configure the structured filter registry, lazy and remote aggregates, built-in replacements, and custom popup bodies.
Surface: grid
Structured filter types add purpose-built controls to the Pro column-filter popup without replacing RevoGrid's filter model. A type is one **popup body plus the operator IDs it owns**. The grid still owns the popup shell, header, active-state icon, close behavior, **Groups** launcher, ordinary condition list, and optional **Expression** editor.
Start with the [Structured Filter Types overview](/guides/data-filter/filter-types/overview/) when choosing or configuring a built-in. This guide covers the shared registry contract, aggregate access, remote data, built-in replacement, and application-defined types.
The [`Advanced Filtering: Order Explorer`](https://demo.rv-grid.com/filter-showcase/ts) exposes every built-in on a dedicated order column and lets you switch among the TypeScript, React, Vue, and Angular implementations.
## Register and select a type
`AdvanceFilterPlugin` registers all 14 built-ins. Registration alone does not change a column: opt in through the column's existing `filter` field. A column can combine its base family with one or more structured types.
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
FILTER_HISTOGRAM_BRUSH,
FILTER_TOKEN_LIST,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'project', filter: ['string', FILTER_TOKEN_LIST] },
{ prop: 'region', filter: ['string', FILTER_FACETED_LIST] },
{ prop: 'revenue', filter: ['number', FILTER_HISTOGRAM_BRUSH] },
];
grid.filter = defineAdvancedFilterConfig({
allowDuplicateOperators: false,
});
```
When a structured type is selected, its body appears above the ordinary condition list. Its owned operators are omitted from that list so the popup does not expose duplicate controls, while operators from the column's base family remain available. Multiple structured IDs can be placed in the same `filter` array when their bodies should appear together.
## State, AST, and expression ownership
Every editor reads and commits ordinary `MultiFilterItem` conditions. A condition has the same stable fields used elsewhere by filtering: `id`, `type`, `value`, and `relation`. The registry limits a body to its declared `operatorIds`, preserves unrelated conditions, reuses condition IDs where possible, and removes the owned condition when the control returns to its no-op state.
`AdvanceFilterPlugin` converts those conditions into canonical `FilterAst` condition nodes. Structured values are JSON-safe, so operator IDs and values survive AST and saved-view serialization without an opaque UI-only state layer. The current-column Expression editor and the grouped AST editor remain grid-owned surfaces; a custom structured body does not need to recreate either one.
Operator IDs must be unique across registered structured types. Registering a
second type that claims an operator owned by a different type throws.
Registering the same stable type ID replaces that type, which is how a
configured built-in overrides its default presentation.
## Lazy aggregate access
A body declares only the provider-backed projections it may use through `aggregateNeeds`:
| Need | Result |
| -------------- | -------------------------------------------------- |
| `values` | Parsed column values in row order |
| `uniqueValues` | First-seen unique parsed values |
| `valueCounts` | `{ value, count }` entries |
| `numericRange` | Finite numeric `values`, `count`, `min`, and `max` |
Registering a type and creating its aggregate accessor do not scan rows. The first `context.aggregates.get(need, scope)` call performs the scan and caches it for that popup-body render; built-in bodies that need a projection call it as they render. The default scope is `'all'`; pass `'visible'` to use the provider's currently visible item indexes. Both scopes exclude synthetic rows and resolve values through the grid's parsed-value path.
```ts
const allCounts = context.aggregates?.get('valueCounts');
const visibleCounts = context.aggregates?.get('valueCounts', 'visible');
```
Asking for an undeclared need throws, which keeps data dependencies explicit.
### Complete aggregates for remote rows
Pagination and infinite-scroll grids only keep a window of rows in the browser. Supply server-produced aggregates through `structuredFilterAggregates` so counts, previews, histograms, and presets describe the complete remote result set instead of the loaded page. The callback is lazy and cached once per need, scope, type, and open popup. Return `undefined` when a request should use the local-row fallback.
```ts
import type { StructuredFilterAggregateProvider } from '@revolist/revogrid-pro';
const remoteAggregates: StructuredFilterAggregateProvider = {
complete: true,
get({ typeId, column, need, scope }) {
if (
typeId === FILTER_FACETED_LIST &&
column.prop === 'region' &&
need === 'valueCounts'
) {
return scope === 'visible'
? response.visibleRegionCounts
: response.allRegionCounts;
}
if (
typeId === FILTER_HISTOGRAM_BRUSH &&
column.prop === 'revenue' &&
need === 'numericRange'
) {
return response.revenueRange;
}
return undefined;
},
};
grid.filter = defineAdvancedFilterConfig({
structuredFilterAggregates: remoteAggregates,
});
```
Set `complete: true` for a partially loaded remote row source. A missing aggregate then fails explicitly instead of silently deriving an incomplete result from the loaded page. For very large numeric, timeline, or array columns, implement `getPrepared()` and return the exported compact `HistogramPreparedData`, `StatisticalPresetsPreparedData`, `TimelineBrushPreparedData`, or `{ kind: 'arrayTags', values }` shape. Those bodies use server bins, resolved preset boundaries/counts, timeline buckets, or unique tags directly and do not request row-level arrays.
The filter condition itself remains the remote execution contract: stable operator IDs plus JSON-safe values are forwarded in `multiConditionFilters`. Date-aware filters also include resolved half-open UTC ranges, the captured reference instant, timezone, week start, and fiscal-year anchor in `temporalContext`.
Canonical `filterAst` and its `executionContext` are forwarded by pagination, infinite scroll, and server-side grouping. The execution context includes each column's resolved JSON-safe blank policy. If a column uses a JavaScript `isBlank` callback or an array/tags accessor function, configure the equivalent rule in the backend; functions are intentionally never serialized.
## Built-in guides
Each built-in now has a focused setup, saved-state, configuration, and troubleshooting guide:
| Built-in | Focused guide | Aggregate need |
| ---------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
| Token list | [Token List Filter](/guides/data-filter/filter-types/token-list/) | None |
| Fuzzy search | [Fuzzy Search Filter](/guides/data-filter/filter-types/fuzzy-search/) | `uniqueValues` |
| Regular expression | [Regular Expression Filter](/guides/data-filter/filter-types/regular-expression/) | `uniqueValues` |
| Faceted list | [Faceted List Filter](/guides/data-filter/filter-types/faceted-list/) | `valueCounts` |
| Chip and badge toggles | [Chip and Badge Toggle Filter](/guides/data-filter/filter-types/chip-badge-toggles/) | `uniqueValues` |
| Histogram brush | [Histogram Brush Filter](/guides/data-filter/filter-types/histogram-brush/) | `numericRange` |
| Rating or progress threshold | [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/) | None |
| Statistical presets | [Statistical Presets Filter](/guides/data-filter/filter-types/statistical-presets/) | `numericRange` |
| Calendar range | [Calendar Range Filter](/guides/data-filter/filter-types/calendar-range/) | None |
| Relative window | [Relative Window Filter](/guides/data-filter/filter-types/relative-window/) | None |
| Timeline brush | [Timeline Brush Filter](/guides/data-filter/filter-types/timeline-brush/) | `values` |
| Time matrix | [Time Matrix Filter](/guides/data-filter/filter-types/time-matrix/) | None |
| Tri-state boolean | [Tri-state Boolean Filter](/guides/data-filter/filter-types/tri-state-boolean/) | `valueCounts` |
| Array and tags | [Array and Tags Filter](/guides/data-filter/filter-types/array-tags/) | `values` |
## Configure built-ins by replacing registrations
Factory options belong to a structured type registration, not to the column. Replace the matching built-in in `structuredFilterTypes`, then opt columns into the same stable ID.
```ts
import {
BUILT_IN_STRUCTURED_FILTER_TYPES,
FILTER_HISTOGRAM_BRUSH,
createHistogramBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const histogram = createHistogramBrushStructuredFilterType({
bins: 16,
formatValue: (value) => `${value.toFixed(1)} ms`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: BUILT_IN_STRUCTURED_FILTER_TYPES.map((type) =>
type.id === FILTER_HISTOGRAM_BRUSH ? histogram : type,
),
});
```
You can also pass only the replacement in `structuredFilterTypes`; `AdvanceFilterPlugin` registers the built-ins first and then replaces the matching stable ID.
## Create a custom structured type
A custom type needs two coordinated registrations:
1. `StructuredFilterType` defines the popup body and the operator IDs it may commit.
2. `customFilters` defines the predicate for each operator and maps it back to the same `columnFilterType` ID.
```ts
import {
AdvanceFilterPlugin,
defineAdvancedFilterConfig,
structuredFilterButton,
structuredFilterSection,
type StructuredFilterType,
} from '@revolist/revogrid-pro';
const PRIORITY_PICKER = 'priorityPicker';
const PRIORITY_IS = 'priorityIs';
const priorityPicker: StructuredFilterType = {
id: PRIORITY_PICKER,
operatorIds: [PRIORITY_IS],
headerControl: {
kind: 'popup',
template: (h, { presentation }) => h(
'span',
{ class: 'priority-header-badge' },
presentation.summary,
),
},
render(context) {
const selected = context.conditions[0]?.value;
return structuredFilterSection(
'Priority',
['low', 'medium', 'high'].map((value) =>
structuredFilterButton({
label: value,
pressed: selected === value,
onClick: () =>
void context.commit(
selected === value ? [] : [{ type: PRIORITY_IS, value }],
),
}),
),
'Choose one priority.',
);
},
};
grid.plugins = [AdvanceFilterPlugin];
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [priorityPicker],
customFilters: {
[PRIORITY_IS]: {
columnFilterType: PRIORITY_PICKER,
name: 'Is priority',
func: (cellValue, expected) => cellValue === expected,
},
},
localization: {
filterNames: { [PRIORITY_IS]: 'Is priority' },
},
});
grid.columns = [{ prop: 'priority', filter: ['string', PRIORITY_PICKER] }];
```
Declare `aggregateNeeds` only if the body derives choices or summaries from grid data, and call `context.aggregates.get()` only for those declared needs. Use `context.labels.operator()` and `context.labels.caption()` for localized text, and use `context.commit()` (or its equivalent alias `replaceConditions()`) rather than mutating filter state directly. `headerControl` is optional: a popup control supplies non-interactive trigger content, while a resolver may return an inline interactive control using the same controlled context.
The built-in types also implement the optional `mount(host, context)` adapter. It mounts the same controlled editor inside a grouped-filter rule, with the same aggregates, remote `preparedData`, date settings, labels, and condition contract as the regular popup. Custom structured types should implement `mount` when their JSON value needs an interactive grouped editor; without it, grouped mode preserves the value and shows an explicit unavailable-editor state instead of coercing an object to text.
Selection rules are handled similarly by the grouped builder itself: `in` / `notIn` use the existing provider-backed option source as an autocomplete with removable tags. Values are never parsed by commas, so a value such as `"North, America"` remains one value.
The plugin also exposes `registerStructuredFilterType()`, `unregisterStructuredFilterType()`, and `getStructuredFilterType()` on an `AdvanceFilterPlugin` instance for runtime registration. Configuration is usually simpler because `initConfig()` can rebuild the complete registry consistently.
## Related filtering guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# What Is a Filter AST?
URL: https://pro.rv-grid.com/guides/data-filter/what-is-filter-ast/
Source: src/content/docs/guides/data-filter/what-is-filter-ast.mdx
Description: Understand Filter AST in plain language, why teams use it, and when it is the right filtering model.
Surface: grid
A Filter AST is a portable description of a filter. It records **what the filter means** as data, without tying that meaning to one filter popup, framework, database, or API.
You do not need compiler knowledge to use it. If you can read a sentence such as “show open, high-priority tickets or any ticket from a VIP customer,” you already understand the idea.
## AST in plain language
AST stands for **Abstract Syntax Tree**:
- **Abstract** means it keeps the meaning and leaves out presentation details such as buttons, labels, and popup layout.
- **Syntax** means it follows a small set of structural rules.
- **Tree** means a rule can contain smaller rules, like branches containing more branches and leaves.
In a RevoGrid Filter AST, the leaves are field conditions and the branches join them with `and`, `or`, or `not`.
Consider this business rule:
> Show a ticket when it is both open and high priority, or when the customer is a VIP.
Its tree reads like this:
```text
OR
├─ AND
│ ├─ status equals "Open"
│ └─ priority is at least 3
└─ customerTier equals "VIP"
```
The same rule as a RevoGrid `FilterAst` is ordinary JSON-shaped data:
```ts
import type { FilterAst } from '@revolist/revogrid-pro';
const ticketQueue: FilterAst = {
type: 'group',
operator: 'or',
children: [
{
type: 'group',
operator: 'and',
children: [
{
type: 'condition',
field: 'status',
operator: 'equal',
valueType: 'string',
value: 'Open',
},
{
type: 'condition',
field: 'priority',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 3,
},
],
},
{
type: 'condition',
field: 'customerTier',
operator: 'equal',
valueType: 'string',
value: 'VIP',
},
],
};
```
The AST describes the question. It does not contain the rows, the matching results, or database-specific instructions.
## What “canonical” means
Applications often create filters in several places: column menus, a visual rule builder, quick search, saved views, and backend APIs. Without a shared format, each feature can describe the same rule differently.
RevoGrid's canonical `FilterAst` is the common contract between those paths. It is:
- **Meaningful:** nested Boolean logic is explicit rather than implied by UI state.
- **JSON-safe:** it can be serialized, stored, logged, and sent in a request.
- **Validated:** malformed trees are rejected before they replace the active filter.
- **Portable:** the same tree can drive local rows or be interpreted by your backend.
- **UI-independent:** a filter can survive a redesign or move between different applications.
“Canonical” does not mean RevoGrid automatically converts the tree to SQL, REST parameters, or GraphQL. Your backend owns that translation and must allow-list fields and operators before building a query.
## The business case
Filter AST becomes valuable when a filter is more than a temporary choice in one column menu.
| Need | Business outcome |
| ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Saved views and reusable segments | Users can return to the same definition instead of rebuilding it. |
| Shared rules across screens | A queue, dashboard, and export can use one filter contract. |
| Remote or virtual data | The backend can evaluate the complete dataset instead of only the loaded page. |
| Cross-column `OR`, nested groups, and `NOT` | Business rules are represented without flattening or changing their meaning. |
| Visual and programmatic editing | A person can use the grouped editor while an application can generate the same structure. |
| Validation and diagnostics | Invalid saved or generated rules fail as one atomic update, leaving the previous view active. |
| Auditing and troubleshooting | A JSON filter is easier to inspect, compare, version, and log than transient UI state. |
Typical examples include support queues, approval routing, fraud-review segments, inventory alerts, compliance views, and report definitions.
## When to use it
Use `FilterAst` when at least one of these is true:
- The rule needs `OR` across different fields, nested groups, or `NOT`.
- Users save, share, or reopen complex views.
- Filtering is executed by a remote data service.
- A visual rule builder and application code must exchange the same filter.
- You need a stable, JSON-safe filtering contract.
Use ordinary `multiFilterItems` instead when users only filter individual columns and all columns combine with `AND`. It is simpler and works with existing Core column controls. `FilterAst` is a Pro capability and requires `AdvanceFilterPlugin` for canonical execution.
## The three building blocks
Every tree is made from only three node types:
| Node | What it says | Everyday example |
| ----------- | ------------------------------------------------ | --------------------------- |
| `condition` | Test one field with one operator. | Status equals Open. |
| `group` | Join one or more child rules with `and` or `or`. | Open **and** high priority. |
| `not` | Reverse the result of one child rule or group. | **Not** archived. |
A condition names the row field, operator, and operand type:
```ts
const condition: FilterAst = {
type: 'condition',
field: 'amount',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 1000,
};
```
`valueType` describes the operand; it does not convert the values in your rows. For example, `valueType: 'number'` expects a real number rather than the string `'1000'`.
## Use a Filter AST in RevoGrid
Register `AdvanceFilterPlugin`, get its runtime instance, and apply the complete tree:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = columns;
grid.source = rows;
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
await filterPlugin.setFilterAst(ticketQueue);
```
Read the effective filter when you need to save or send it:
```ts
const currentFilter = filterPlugin.getFilterAst();
if (currentFilter) {
localStorage.setItem('ticket-queue', JSON.stringify(currentFilter));
}
```
Restore the saved tree by parsing it and applying it again:
```ts
const savedFilter = localStorage.getItem('ticket-queue');
if (savedFilter) {
await filterPlugin.setFilterAst(JSON.parse(savedFilter));
}
```
Clear it with `undefined`:
```ts
await filterPlugin.setFilterAst(undefined);
```
`setFilterAst()` replaces the tree as one atomic operation. `getFilterAst()` returns a defensive copy, so changing that returned object does not change the grid. Build the next tree and call `setFilterAst()` again.
## Let users build the tree visually
The AST is the data model, not a required user interface. A familiar filter rail, active chips, search field, ranges, and toggles can all compile into the same tree. This lets people filter in business language while the application gets a validated, portable definition.
[Open the Filter AST Side Panel article](/guides/data-filter/filter-ast-side-panel/). It mounts the same canonical grouped editor used by the filter dialog and exposes Vanilla TypeScript, Vue, React, Angular, shared grid setup, and styling sources.
Each interaction in the example rebuilds and applies the complete AST. Removing a chip removes its branch; **Clear all** applies `undefined`. The side panel owns presentation, while `AdvanceFilterPlugin` owns validation and execution.
For unrestricted rule creation, enable the grouped editor so users can create nested rules without writing JSON:
```ts
grid.filter = {
groupedFilter: {},
};
```
The editor and the API work with the same canonical tree. A user can create a rule visually, your application can read it with `getFilterAst()`, and the saved result can be restored later with `setFilterAst()`.
## Send the same meaning to a backend
Listen for `filterastchange` to receive the effective AST and its execution context. The context fixes time-sensitive details such as the instant and timezone used for relative date rules.
For a remote data source, cancel local application in `beforefilterapply`, send the AST and execution context to your service, and replace the grid rows with the server result. The backend must evaluate the rule against the full dataset, not only the page loaded in the browser.
The [Canonical Filter AST reference](/guides/data-filter/filter-ast/) has the complete event example, remote-preview flow, operator table, value rules, and diagnostics.
## A safe implementation checklist
1. Start with `multiFilterItems` unless the product needs canonical or nested logic.
2. Treat the AST as untrusted input when it comes from storage, a URL, or another service.
3. Apply it through `setFilterAst()` so RevoGrid validates it atomically.
4. Store or transport the tree returned by `getFilterAst()`.
5. Send `executionContext` with remote requests, especially for relative dates and blank-value policy.
6. On the server, allow-list fields and operators and use parameterized database queries.
7. Test the same saved rule locally and remotely to confirm that both sides return the same rows.
## Common questions
### Is a Filter AST a string expression?
No. It is structured data. A string such as `status = "Open" AND priority >= 3` can be convenient for people, but an AST makes the grouping and types explicit for software.
### Does it replace column filters?
No. Simple column filtering remains the easiest choice for ordinary grid interactions. The AST is the shared model for cases that need richer logic, persistence, or transport.
### Can I store it in a database or URL?
Yes, if the surrounding storage or URL encoding is suitable for your size and security requirements. The AST is JSON-safe, but you should still validate restored state and avoid treating client-provided fields or operators as trusted database instructions.
### What does `projectable` mean?
Some ASTs can be shown again as independent column filters; those are projectable. A cross-column `OR` or `NOT` cannot be represented faithfully by ordinary column controls, so it is canonical-only. It is still a valid filter and can be edited in the grouped editor.
### Where is the complete technical contract?
Continue to [Canonical Filter AST](/guides/data-filter/filter-ast/) for every supported operator, exact value shapes, quick-filter composition, events, remote ownership, validation codes, and custom evaluators. See [Filter State and Presets](/guides/data-filter/filter-state/) for persistence patterns and [Filtering Quick Start](/guides/data-filter/filter-basics/) for simpler column filtering.
---
# Data Grid Formatting
URL: https://pro.rv-grid.com/guides/data-grid/context-menu/
Source: src/content/docs/guides/data-grid/context-menu.mdx
Description: Add spreadsheet-style context-menu defaults for cells, rows, groups, and columns while preserving application menu contributions.
`DataGridContextMenuPlugin` provides a complete, task-oriented menu without requiring every grid to define copy, edit, inspection, row, filter, formatting, sizing, grouping, column-management, and export actions from scratch. It auto-installs `ContextMenuPlugin`, `ColumnDialogPlugin`, Column Hide support, the cell inspector, and the spreadsheet formatting runtime. Add `AutoFillPlugin` and `EventManagerPlugin` explicitly when dragging a range handle should also stretch advanced formats such as Rating together with the values.
[Open the four-framework standalone example](https://example.rv-grid.com/data-grid-context-menu/)
or browse its source in
[`revogrid-demos/pro-data-grid-context-menu`](https://github.com/revolist/revogrid-demos/tree/main/pro-data-grid-context-menu).
## Enable the preset
```ts
import { DataGridContextMenuPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridContextMenuPlugin];
```
Installing the plugin enables its defaults. Set the property to `false` to keep the plugin registered while suppressing the preset:
```ts
grid.dataGridContextMenu = false;
```
The preset is a prepended context-menu contribution. Existing `rowContextMenu`, `columnContextMenu`, and independent plugin contributions remain after it by default.
## Customize by stable item ID
Hide or disable one generated command without rebuilding its parent submenu:
```ts
grid.dataGridContextMenu = {
hiddenItems: {
'row.delete': true,
},
disabledItems: {
'row.duplicate': context => context.rows.length > 20,
},
};
```
Resolvers receive a semantic `DataGridContextMenuContext` with the active `surface`, target rows, range, leaf columns, column group, and low-level menu context.
The surfaces are `cell`, `rowHeader`, `rowGroup`, `columnHeader`, and `columnGroupHeader`. Disable a whole surface with `areas`:
```ts
grid.dataGridContextMenu = {
areas: { rowGroup: false },
};
```
## Extend or replace generated items
Use `items` for a simple extension. Use `getItems` when a surface needs complete replacement or transformation.
```ts
grid.dataGridContextMenu = {
items: context => [{
id: 'app.audit',
name: 'Open audit history',
action: () => openAuditHistory(context.rows[0]?.model),
}],
getItems: (context, defaults) =>
context.surface === 'columnGroupHeader'
? [{ id: 'app.groupSummary', name: 'Open group summary' }]
: defaults,
};
```
Set `includeHostItems: false` to suppress the application-provided base menu on enabled preset surfaces. Other plugin contributions still compose independently.
## Command lifecycle and remote data
Every built-in command first emits a cancelable `datagrid-context-menu-command` event:
```ts
grid.addEventListener('datagrid-context-menu-command', event => {
if (event.detail.id === 'row.delete' && usesRemoteRows) {
event.preventDefault();
void deleteRowsOnServer(event.detail.context.rows);
}
});
```
A `commandHandlers` entry also replaces local behavior:
```ts
grid.dataGridContextMenu = {
commandHandlers: {
'row.delete': context => api.deleteRows(
context.rows.map(row => row.model.id),
),
},
};
```
Local row mutation is hidden for remote/server-grouped runtimes until a handler is provided. Clipboard permission and export failures emit `datagrid-context-menu-error`; Cut clears cells only after the clipboard write succeeds.
## Undo and redo with History
Add `HistoryPlugin` to make built-in mutating context-menu commands participate
in the same chronological undo/redo stack as direct edits and row ordering:
```ts
import {
DataGridContextMenuPlugin,
HistoryPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [HistoryPlugin, DataGridContextMenuPlugin];
```
Each command is one atomic history step, including multi-cell Cut, Paste, cell
Clear, and row Clear. Structural tracking covers row insert, duplicate, delete,
pin, and unpin commands, plus column insert, duplicate, delete, pin, unpin, and
move commands. Undo and redo restore the complete affected row sources or
authored column tree.
Copy and Copy with headers are intentionally excluded because they do not
mutate grid state. Canceled, unavailable, and no-op commands create no entry.
Commands replaced through `commandHandlers` are application-owned and are not
snapshotted automatically, so remote side effects remain under application
control.
In-memory history preserves row objects and function-valued column definitions,
including nested groups, renderers, and editors. Persistent History adapters
require serializable row and column definitions; JSON storage cannot recreate
functions. See [History Undo/Redo](../data-manage/history/) for stack,
persistence, and replay details.
## Optional capabilities
Commands appear only when their runtime exists. Add the matching plugin to expose filtering, Excel export, row selection, column auto-size, column collapse, tree, or server-grouping actions. Set `unavailableItems: 'disable'` to show unavailable commands in a disabled state instead.
```ts
grid.dataGridContextMenu = {
unavailableItems: 'disable',
rowPinning: true,
};
```
Row pinning is opt-in. Column insertion, duplication, and deletion are also opt-in and require an application factory so every new column receives a unique schema key:
```ts
grid.dataGridContextMenu = {
columnSchema: {
createColumn({ action, sourceColumn }) {
return {
...sourceColumn,
prop: `${String(sourceColumn.prop)}-${action}-${crypto.randomUUID()}`,
};
},
},
};
```
## Spreadsheet-style formatting
Cell and column-header menus include a type-aware **Format** submenu. Native
numeric values expose Number, Currency, Accounting, Percentage, and Scientific
presets; native `Date` values expose Date, Date & time, and Time. **More
formats…** opens a separate Format Cells dialog for locale, decimal and date
styles, font and fill, alignment, wrapping, borders, and a live preview.
See [Format Cells](./format-cells/) for the complete popup guide, including
selection behavior, physical coordinates, render-time application, direct plugin
usage, and runtime persistence limits.
Formatting changes presentation only. Raw source values used by editors,
sorting, filtering, and formulas are not rewritten. Cell commands target the
active range; column-header commands create a true column formatting default.
Map application column types explicitly when their values do not carry native
number or `Date` types:
```ts
grid.dataGridContextMenu = {
formatting: {
locale: 'en-GB',
currencies: ['GBP', 'EUR', 'USD'],
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
},
};
```
Set `formatting: false` to remove the whole formatting submenu. Formatting
commands respect grid and per-cell readonly rules. Value presets preserve
authored custom cell templates; the rich dialog can still compose appearance
through `cellProperties` without replacing interactive content.
## Inspect raw cell and column data
**Inspect cell** opens a compact read-only popup for the exact invoked cell. It
shows the column label and property, row dimension and authored source index,
plus the raw value and its JavaScript type. The bounded serializer displays
dates, `undefined`, `bigint`, symbols, functions, accessors, and circular
references as selectable JSON-like inspection text without invoking getters or
`toJSON`.
Full enumerable row snapshots are opt-in because source models can contain
hidden or internal fields. **Inspect column** is available from leaf column
headers and shows the property, pinned dimension, visible and physical indexes,
type, readonly mode, sort order, filter configuration, rendered width, and
authored width bounds. The complete resolved column definition is also opt-in
because schema objects can contain private application metadata.
Limits and an application redaction hook are available when complete row or
column inspection is appropriate:
```ts
grid.dataGridContextMenu = {
inspection: {
includeRowData: true,
includeColumnData: true,
serialization: { maxDepth: 5, maxEntries: 100, maxOutputLength: 20_000 },
redact(value, { kind }) {
return kind === 'row' || kind === 'column'
? redactPrivateFields(value)
: value;
},
localeText: { title: 'Cell details' },
},
};
```
The limits bound recursive traversal and rendered output after JavaScript has
discovered an object's own keys. They are not a sandbox for hostile Proxy traps
or extremely wide objects; use `redact` to replace untrusted complex values.
Both inspectors remain available for readonly cells and readonly grids. Hide
them like any other built-in item with `hiddenItems: { 'cell.inspect': true,
'column.inspect': true }`, or replace their behavior through the matching
`commandHandlers` entry.
Grid readonly mode blocks all local data and schema mutations. Static and
functional column readonly rules hide Edit for the invoked cell; mixed Clear,
Cut, and Paste targets remain usable but skip every readonly cell. Copy and
non-mutating view commands remain available. Synthetic row-group records never
receive destructive row commands.
---
# Format Cells
URL: https://pro.rv-grid.com/guides/data-grid/format-cells/
Source: src/content/docs/guides/data-grid/format-cells.mdx
Description: Configure declarative and interactive spreadsheet-style value, visual presentation, appearance, and editor behavior for RevoGrid cells, rows, and columns.
The **Format Cells** dialog provides spreadsheet-style presentation controls
without changing the values stored in the grid source. Use it for number,
currency, date, and time display as well as typography, colors, alignment,
wrapping, and borders.
The dialog and the quick **Format** submenu are powered by
`DataGridFormattingPlugin`. Registering it also installs
`DataGridContextMenuPlugin`, which supplies the selection opened from cell and
column-header menus. Registering `DataGridContextMenuPlugin` as the entry point
continues to work and installs the same formatting runtime.
Formatting created by the **Format Cells** dialog is runtime presentation
metadata. The plugin does not write it into source row objects, change the raw
cell value, or store an Excel-style format string beside the value.
Declarative formatting is covered first below.
## Declarative cell, row, and column formatting
Register `DataGridFormattingPlugin`, then use either of these declarative
inputs:
- `column.dataGridFormat` accepts either a format for the whole column or a
function that chooses a format for each cell.
- `grid.dataGridFormatting` defines reactive whole-column, whole-row, and cell
presets. Reassigning this property replaces the declarative preset state and
refreshes the grid.
This complete example formats positive amounts as GBP currency and negative
amounts as EUR accounting values. The resolver receives the current row and
cell value, so no external lookup function is needed. The example does not
retrieve the plugin or call `apply()`:
```ts
import type { ColumnRegular } from '@revolist/revogrid';
import {
DataGridFormattingPlugin,
type DataGridCellFormat,
} from '@revolist/revogrid-pro';
type InvoiceRow = {
id: string;
customer: string;
amount: number;
};
const gbpFormat: DataGridCellFormat = {
// Controls how the raw cell value is displayed.
value: {
// Uses the built-in currency formatter.
kind: 'preset', preset: 'currency',
// Applies UK number and currency conventions.
locale: 'en-GB',
// Displays the value as British pounds.
currency: 'GBP',
// Always displays two digits after the decimal point.
decimalPlaces: 2,
// Adds thousands separators, for example £1,200.00.
useGrouping: true,
},
// Controls the visual styling of the formatted cell.
appearance: {
// Aligns the displayed value to the right of the cell.
horizontal: 'right',
},
};
const euroWarningFormat: DataGridCellFormat = {
value: {
kind: 'preset', preset: 'accounting',
locale: 'en-GB',
currency: 'EUR',
decimalPlaces: 2,
},
appearance: {
bold: true,
textColor: '#b91c1c',
fillColor: '#fef2f2',
horizontal: 'right',
},
};
const columns: ColumnRegular[] = [
{ prop: 'customer', name: 'Customer' },
{
prop: 'amount',
name: 'Amount',
// Called independently for every cell in this column.
dataGridFormat: ({ model }) =>
model.amount < 0 ? euroWarningFormat : gbpFormat,
},
];
const source: InvoiceRow[] = [
{ id: 'invoice-1', customer: 'Acme Ltd', amount: 1200 },
{ id: 'invoice-2', customer: 'Northwind', amount: -350.5 },
];
// Installs formatting, the Format Cells dialog, and its context-menu commands.
grid.plugins = [DataGridFormattingPlugin];
grid.columns = columns;
grid.source = source;
```
The resolver argument contains `{ model, value, prop, rowType }`. Return a
`DataGridCellFormat` to format the cell, or `undefined` when the column does not
define a format for that cell. For one format across the entire column, assign
the object directly instead:
```ts
dataGridFormat: gbpFormat;
```
When formatting is application state rather than column configuration, keep the
same presets in the reactive grid property:
```ts
grid.dataGridFormatting = {
columns: [{ column: 1, format: gbpFormat }],
rows: [{ row: 0, format: { appearance: { bold: true } } }],
cells: [{
range: { start: { row: 1, column: 1 } },
format: euroWarningFormat,
}],
};
```
Coordinates are zero-based physical indexes in the row and column source
arrays. A row preset applies to every current and future column at that row
coordinate. A column preset applies to every current and future row at that
column coordinate. Presets use regular body sources by default. Set `rowType`
or `colType` when targeting a pinned source.
The declarative precedence is cell preset, then row preset, then grid column
preset, then `column.dataGridFormat`. Runtime operations through
`DataGridFormattingPlugin.apply()`, `applyValueFormat()`, or `clear()` take
precedence over every declarative source. The raw values remain `1200` and
`-350.5` for editing, sorting, filtering, and application code.
Framework wrappers use the same grid property. In React and Vue, bind
`dataGridFormatting` directly. If a generated Angular wrapper version does not
yet declare that plugin property as an `@Input`, set
`gridElement.dataGridFormatting = formattingState` on the underlying
`HTMLRevoGridElement`. Do not put formatting state in `additionalData`; that
compatibility path is deprecated.
## Format a whole column, whole row, or individual cells
The mutation API uses the same zero-based physical coordinates as declarative
state. Complete-row and complete-column targets are explicit, so their intent
is not lost when the grid currently has no rows or columns:
```ts
const formatting = (await grid
.getPlugins())
.find(plugin => plugin instanceof DataGridFormattingPlugin);
if (!formatting) throw new Error('Formatting plugin is not installed.');
// The entire Amount column, including rows added later.
formatting.apply(
{ scope: 'columns', ranges: { start: 1 } },
gbpFormat,
);
// The entire first body row, including columns added later.
formatting.apply(
{ scope: 'rows', ranges: { start: 0 } },
{ appearance: { bold: true, fillColor: '#fef3c7' } },
);
// One cell at physical row 1, physical column 1.
formatting.apply(
{ start: { row: 1, column: 1 } },
euroWarningFormat,
);
```
`end` is inclusive and may be before `start`; the plugin normalizes reversed
ranges. Supply an array to format multiple independent ranges. Row targets may
set `rowType`, column targets may set `colType`, and cell ranges may set both.
The same targets work with `applyValueFormat()`, `patch()`, `clear()`,
`openDialog()`, and `inferValueKind()`.
## Enable Format Cells
Register the formatting plugin to make formatting available from cell and
column-header menus. It installs the context-menu integration and dialog
automatically:
```ts
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
The one-line formatting panel is installed with the formatting runtime but is
disabled by default. Enable or disable it reactively with its dedicated grid
property:
```ts
grid.dataGridFormattingPanel = true;
// Hide it again without removing the formatting plugin.
grid.dataGridFormattingPanel = false;
```
Assign a `DataGridFormattingPanelConfig` object instead of `true` when the
toolbar also needs localized labels.
Do not register `DataGridContextMenuPlugin` separately. If your grid already
uses it as its main menu plugin, keep that setup; it installs
`DataGridFormattingPlugin` in the opposite direction and reuses one instance of
each plugin.
Right-click a writable cell or column header and open **Format**. The submenu
offers type-aware quick presets and two general actions:
- **More formats…** opens the complete Format Cells dialog.
- **Clear formatting** removes both value formatting and appearance formatting
from the selected target.
For the complete context-menu configuration and command customization contract,
see [Data Grid Formatting](./context-menu/).
## Configure formatting defaults
Formatting options belong under `grid.dataGridContextMenu.formatting`:
```ts
type InvoiceRow = {
id: string;
customer: string;
amount: number;
issuedAt: string;
};
grid.dataGridContextMenu = {
formatting: {
autoAlignNumericValues: true,
locale: 'en-GB',
locales: ['en-GB', 'de-DE', 'fr-FR'],
currencies: ['GBP', 'EUR', 'USD'],
fontFamilies: ['Inter', 'Georgia', 'IBM Plex Mono'],
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
// Keep this column's active column-level format authoritative.
protectedColumnProps: ['amount'],
resolveStructuralRowIdentity: (row) => row.id,
},
};
```
| Option | Purpose |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autoAlignNumericValues` | Right-aligns finite number and bigint values, plus Number, Currency, Accounting, Percentage, and Scientific formats, when horizontal alignment is Automatic. Effective advanced presentations keep their own layout. Defaults to `true`; set to `false` to disable this derived alignment. |
| `locale` | Initial locale used by `Intl.NumberFormat` and `Intl.DateTimeFormat`. The browser locale is used when omitted. |
| `locales` | Locale choices shown in the dialog. A built-in list is used when omitted. |
| `currencies` | Currency choices shown by Currency and Accounting formats. The first entry is used by the dialog and quick presets. |
| `fontFamilies` | Application font choices shown in Typography. |
| `columnTypeKinds` | Maps application column types to `number`, `date`, `text`, `boolean`, `mixed`, or `unknown` for command availability. It does not convert values. |
| `resolveValueKind` | Overrides value-kind inference for the complete selected target. It does not convert values. |
| `resolveStructuralRowIdentity` | Retains formatting across length-changing immutable source updates by matching old and new row objects with an application-owned stable identity. Object identity is used when omitted. |
| `scopes` | Limits formatting to `cell`, `columnHeader`, or both. |
| `protectedColumnProps` | Column `prop` values whose active column-level format masks cell and row formats. The masked state is retained and becomes effective again if protection is removed. |
| `advancedFormats` | Configures compatible visual formats, individual format availability, custom definitions, and optional editor references. Set it to `false` to disable advanced formats. |
| `localeText` | Replaces dialog, preset, option, palette, and advanced-format registry labels for localization. |
### Protect a column format from overrides
Use `protectedColumnProps` when a column's value, appearance, or presentation
must remain uniform even when users or application code apply formats to a row
or individual cell. The setting is selected by stable column `prop`, not by a
physical column index:
```ts
grid.dataGridContextMenu = {
formatting: {
protectedColumnProps: ['amount'],
},
};
```
Protection applies only while the column has an active column-owned format
from `column.dataGridFormat`, `dataGridFormatting.columns`, or a whole-column
runtime operation. Cell and row formats are still retained in formatting state;
they become effective again if the property is removed from the configuration.
Clearing the column format restores the normal newest-runtime-operation
precedence.
### Physical coordinates and source replacement
Formatting belongs to a physical source position, not to a row object or
business key:
```ts
grid.dataGridFormatting = {
cells: [{
range: {
start: { row: 1, column: 1 },
end: { row: 3, column: 2 },
},
format: euroWarningFormat,
}],
};
// New row objects at the same source positions receive the same formatting.
grid.source = invoices.map((invoice) => ({ ...invoice }));
```
Replacing every record without changing source length preserves formats at the
same coordinates. Structural source changes retain formats with surviving row
objects, remove formats owned by deleted rows, and shift or pin retained
formats with their rows. If an application recreates row objects while also
inserting or deleting records, configure `resolveStructuralRowIdentity` with a
stable unique business key so the old and new rows can be matched:
```ts
grid.dataGridContextMenu = {
formatting: {
resolveStructuralRowIdentity: (row) => row.id,
},
};
```
Filtering only changes virtual order and does not change physical formatting
addresses.
## Accessibility and localization
Format Cells uses the shared modal focus trap and restores focus to the grid
when it closes. Every native input and select has a programmatic label;
icon-only actions expose a localized accessible name, pressed or expanded
state, and a tooltip on both hover and keyboard focus. Escape dismisses an open
control tooltip before a second Escape closes the dialog. Preset lists,
segmented controls, color palettes, and expandable sections support their
native or ARIA keyboard patterns, visible focus, forced-colors mode, and reduced
motion preferences.
Dialog labels, actions, tooltips, validation feedback, the plain-text example,
and palette color names come from `formatting.localeText`. Section templates
replace `{section}` with the localized section name:
```ts
grid.dataGridContextMenu = {
formatting: {
localeText: {
title: 'Formatear celdas',
close: 'Cerrar formato de celdas',
bold: 'Negrita',
expandSection: 'Abrir {section}',
collapseSection: 'Cerrar {section}',
invalidColor: 'Introduce un color hexadecimal válido.',
textExample: 'Ejemplo de texto',
colorLabels: {
'#be185d': 'Rosa',
'#2563eb': 'Azul',
},
advancedGroupLabels: {
Indicators: 'Indicadores',
},
advancedFormatLabels: {
heatmap: 'Mapa térmico',
},
advancedControlLabels: {
'heatmap.midValue': 'Punto medio',
'heatmap.highColor': 'Color alto',
},
},
},
};
```
`localeText` is partial. Unspecified strings and palette names retain the
built-in English defaults. Every label map is merged by key, including
`presetLabels`, `optionLabels`, `colorLabels`, `advancedGroupLabels`,
`advancedFormatLabels`, `advancedFormatDescriptions`,
`advancedControlLabels`, and `advancedOptionLabels`.
Advanced controls use `.` keys. Select options append
the option value: `..`. These same stable
keys localize application-defined formats; their registry-authored label or
description remains the fallback when a translation is omitted.
Set `formatting: false` to remove the formatting submenu while keeping the rest
of the context menu:
```ts
grid.dataGridContextMenu = {
formatting: false,
};
```
This hides formatting commands; it does not remove declarative formats already
provided through `dataGridFormat` or `dataGridFormatting`.
## Value formatting
Value formats change only the string rendered in a cell. They do not replace
the raw value used by editing, sorting, filtering, formulas, or application
code.
The available presets are:
| Preset | Display behavior |
| ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| Automatic | Leaves value display to the column's existing renderer. |
| Number | Locale-aware numeric output with decimal and grouping controls. |
| Currency | Locale-aware currency output using the selected ISO currency. |
| Accounting | Currency output with accounting-style negative values. |
| Percentage | Locale-aware percentage output. With the default two decimal places, a raw value of `0.25` displays as `25.00%` in `en-US`. |
| Scientific | Locale-aware scientific notation. |
| Date | Date output with short, medium, long, or full date style. |
| Time | Time output with short, medium, or long time style. |
| Date & time | Combined date and time output. |
| Plain text | Displays the value as text. |
Numeric formats support decimal places, thousands grouping, and negative-number
styles. Currency formats also expose the configured currency list. Date and
time formats support style controls and use the configured locale.
Quick menu commands are type-aware. Native JavaScript numbers and `bigint`
values expose numeric commands, while native `Date` values expose date and time
commands. Use `columnTypeKinds` or `resolveValueKind` to classify custom column
types and parseable date strings:
```ts
const issuedAtColumn = {
prop: 'issuedAt',
name: 'Issued',
columnType: 'applicationDate',
};
grid.columns = [
{ prop: 'customer', name: 'Customer' },
{ prop: 'amount', name: 'Amount', columnType: 'money' },
issuedAtColumn,
];
grid.dataGridContextMenu = {
formatting: {
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
},
};
```
Type classification controls which quick commands are available; it does not
coerce the source value. Numeric presets format only `number` and `bigint`
values, so convert numeric strings to numbers before formatting them. Date
presets accept valid `Date` objects, parseable date strings, and non-negative
Excel serial-date numbers.
If a value cannot be converted by the selected formatter, the renderer falls
back to the original value's string representation instead of throwing.
Selecting `preset: 'currency'` is sufficient to activate the built-in
`Intl.NumberFormat` currency renderer. You do not install a separate currency
formatter. `currency`, `locale`, `decimalPlaces`, and `useGrouping` configure
that renderer.
## Exact Excel-compatible format codes
Choose **Custom** in Format Cells to edit an Excel-compatible number-format
code with a live preview. Invalid codes disable Apply. The exact code is stored
as a separate discriminated variant, so it cannot conflict with preset options:
```ts
const exactExcelFormat: DataGridCellFormat = {
value: {
kind: 'code',
formatCode: '[Red][<0]#,##0.00;#,##0.00;"-";@',
locale: 'en-GB',
},
};
```
The internal formatter supports positive/negative/zero/text sections,
conditions and colors, locale/currency tokens, dates and elapsed time,
fractions, scientific notation, percentages, scaling, grouping, text
placeholders, quoted/escaped literals, and Excel spacing/fill instructions.
Raw source values are never replaced by their display text. Excel serial dates
use the shared dates module, including serial 60, while application `Date`
values remain `Date` values.
## Advanced visual formats
**Advanced format** is a category in the existing Value formatting list. Select
it to reveal a grouped icon list of compatible visual formats and the options
declared by the selected format. Every row remains visible in the details pane;
built-ins use Font Awesome SVG icons, and custom definitions without an icon
receive the standard formatting icon. The category includes only formats
compatible with every non-null value in the current selection. Choosing one
stores only its stable ID and serializable options in
`DataGridCellFormat.presentation`; source values remain unchanged. The
appearance inspector stays available, so inherited text color, fill,
typography, alignment, and borders can still be adjusted.
You can predefine the same format on a column or choose it per cell:
```ts
const columns: ColumnRegular[] = [
{
prop: 'completion',
name: 'Completion',
dataGridFormat: {
presentation: {
id: 'progress-line',
options: { minValue: 0, maxValue: 100 },
},
},
},
{
prop: 'metric',
name: 'Metric',
dataGridFormat: ({ model }) =>
model.metricKind === 'history'
? { presentation: { id: 'sparkline' } }
: {
presentation: {
id: 'heatmap',
options: { minValue: 0, maxValue: 100 },
},
},
},
];
```
Built-in formats and their required source shapes are:
| Value shape | Format IDs |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Finite number | `progress-line`, `progress-line-value`, `circular-progress`, `heatmap`, `rating`, `change`, `threshold` |
| Boolean | `boolean`, `thumbs` |
| String, number, or boolean | `badge`, `avatar`, `avatar-with-text` |
| Non-empty number array | `sparkline`, `bar`, `pie` |
| Non-empty `{ value, color?, name? }[]` | `pie` |
| Non-empty `{ start, end, label? }[]` | `timeline` |
| Non-empty record of numeric values | `summary-percentage`, `summary-aggregate` |
The chart-only column-header renderer is not a cell format and is intentionally
excluded. Mixed selections show a format only when every selected non-null
value matches its shape.
When edited, Sparkline and Bar chart cells accept comma-separated numbers or a
JSON number array. Timeline opens as compact JSON and saves a validated
`{ start, end, label? }[]` value, so structured data never becomes
`[object Object]` or a display string.
The built-in `boolean` format displays `Yes`/`No`, while `thumbs` is its visual
alternative. Both open the same typed `Yes`/`No`/`Not set` dropdown on edit.
Their dropdown values and options reuse the active cell template, so switching
one cell between Boolean and Thumbs changes both its grid presentation and its
editor presentation without adding a column type.
### Configure or disable formats
Compatible built-ins are enabled by default. Disable one format, change its
application defaults, or disable the Advanced category under the
context-menu formatting config:
```ts
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
formats: {
pie: false,
heatmap: {
defaults: { minValue: -20, maxValue: 50, colorMap: 'coldmap' },
},
},
},
},
};
// Removes the Advanced category but keeps value and appearance formats.
grid.dataGridContextMenu = {
formatting: { advancedFormats: false },
};
```
Application-owned data such as badge styles, threshold definitions, dropdown
choices, and timeline bounds belongs in `defaults` or the column. The dialog
shows only controls declared by the format definition.
For the built-in Badge format, **Fill color** controls the badge background and
**Text color** controls the badge label. These colors do not paint the complete
cell. A matching `badgeStyles` entry supplies the fill fallback, while the label
inherits ordinary theme text until the user chooses an explicit **Text color**.
### Register an application format
Custom formats use the same registry and dialog controls as built-ins:
```ts
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
customFormats: [
{
id: 'risk-meter',
label: 'Risk meter',
group: 'Application',
valueKind: 'number',
defaults: { maximum: 10, color: '#dc2626' },
controls: [
{ type: 'number', key: 'maximum', label: 'Maximum', min: 1 },
{ type: 'color', key: 'color', label: 'Color' },
],
cellTemplate: (h, { value, column }) =>
h('meter', {
min: 0,
max: column.maximum,
value,
style: { accentColor: column.color },
}),
},
],
},
},
};
```
Use `isCompatible({ value, model, column, rowType })` instead of `valueKind`
when application data needs a more specific predicate.
## Editors for formatted cells
Formatting can select an editor for the actual cell being edited. The source
value preset can supply an editor, and an advanced-format definition can expose
its own `editor` reference. Editors remain optional dependencies supplied by
the application:
```ts
import DateColumnType from '@revolist/revogrid-column-date';
import { badgeRenderer, ColumnDropdown } from '@revolist/revogrid-pro';
const dateType = new DateColumnType();
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
presetEditors: {
date: dateType.editor,
datetime: dateType.editor,
time: dateType.editor,
},
customFormats: [
{
id: 'status-badge',
label: 'Status badge',
valueKind: 'scalar',
cellTemplate: badgeRenderer,
editor: ColumnDropdown.editor,
},
],
},
},
};
```
Editor resolution happens for each cell, so two cells in one column can use
different editors when `dataGridFormat` returns different value presets or
presentations. If a configured editor reference cannot be resolved, the plugin
uses the column's original editor and then the grid's text editor.
An application-authored `editor` is protected by default. Set
`replaceAuthoredEditors: true` only when formatted cells should take ownership
globally. Advanced presentation renderers also protect an authored
`cellTemplate` by default; configure `replaceAuthoredTemplates` globally or
`replaceAuthoredTemplate` on one definition when that advanced renderer should
replace it. Ordinary value categories are an explicit presentation choice: a
non-Automatic category replaces the authored visual template, while Automatic
restores it.
## Appearance formatting
Numeric cells use spreadsheet-style right alignment by default while the
formatting plugin is installed. This applies to finite number and bigint source
values even when they have no stored format, and to Number, Currency,
Accounting, Percentage, and Scientific value formats. An explicit **Left**,
**Center**, or **Right** appearance choice overrides the default.
Advanced presentations such as Progress line and Rating stars retain their
renderer-owned layout while alignment is Automatic; an explicit appearance
alignment still overrides that layout.
To retain the grid's existing alignment instead, disable automatic numeric
alignment in the formatting configuration:
```ts
grid.dataGridContextMenu = {
formatting: {
autoAlignNumericValues: false,
},
};
```
The appearance side of the dialog contains a live preview and these groups:
- **Typography:** font family, font size, bold, italic, underline, and
strikethrough.
- **Colors:** text color and fill color, including predefined and custom colors.
- **Alignment:** horizontal alignment, vertical alignment, and text wrapping.
- **Borders:** border style and border color.
Appearance is composed with the column's existing `cellProperties`. Apart from
the enabled-by-default numeric alignment described above, formatting adds or
overrides only explicitly selected style values, so unrelated authored classes
and styles remain in place.
Columns with an authored `cellTemplate`, including selection-style columns,
keep the complete Format Cells category list. Choosing a concrete value format
switches the cell to that plain value presentation; choosing Automatic restores
the authored template. Row-drag content remains appearance-only. For an
advanced format supplied declaratively or through the plugin API, set
`replaceAuthoredTemplates` globally, or `replaceAuthoredTemplate` on that
definition, only when its renderer should explicitly own those cells.
## Copy and paste formatting
`DataGridFormattingPlugin` installs one `ClipboardPlugin` automatically. The
formatting plugin remains the owner of the format throughout copy and paste:
1. Copy asks the formatting runtime for the effective `DataGridCellFormat` at
every selected physical cell.
2. RevoGrid-to-RevoGrid copy stores that canonical format in the versioned rich
clipboard payload, together with typed values, formulas, range geometry,
merges, and explicit dimensions.
3. Excel HTML import converts allowlisted Excel declarations into the same
`DataGridCellFormat` model. Clipboard code never installs raw Excel CSS on a
cell. Excel CSS escapes and named formats such as `Short Date` are normalized
by the formatting plugin.
4. Paste sends values and formulas through the regular grid edit, readonly, and
validation pipeline. Only accepted destinations receive formatting, applied
as one formatting batch.
5. Copy back to Excel converts the canonical format into Excel HTML declarations
and number-format codes without changing the source row value.
This keeps formatting created at every supported scope in sync: individual
cells, rectangular or independent ranges, complete rows, and complete columns.
The effective resolved format is copied, including inherited row or column
formatting; the destination receives a cell-level snapshot of that appearance.
Date handling follows the same non-destructive rule. Excel serial dates stay
typed numbers with a date/time format after import. When copying an application
`Date` or valid date-like value to Excel, only the outbound HTML representation
is converted to an Excel 1900-system serial. RevoGrid source data is not
rewritten.
Some Excel Desktop versions, including Excel for macOS, omit the typed `x:num`
attribute and expose only formatted display text. When that text is accompanied
by a recognized Accounting, Percentage, or date format, the formatting plugin
recovers the typed numeric or serial-date value before applying the canonical
format. It does not infer a type from unformatted visual text.
The same conversion covers the full Excel number-format family: Number,
Currency, Accounting, Percentage, Fraction, Scientific, Date, Time, combined
date-time, elapsed time, Special, and numeric/date/time Custom codes. General
and Text remain text without an explicit typed clipboard value. Conditions,
four-section positive/negative/zero/text rules, locale tokens, scaling commas,
literal affixes, spacing, and fill are compiled by the same formatting engine
used for cell rendering. See the
[display-only category matrix](/guides/grid-utilities/clipboard-json/#display-only-excel-category-coverage)
for the exact fallback policy.
The [native Excel interoperability matrix](/guides/grid-utilities/clipboard-json/#native-excel-interoperability-matrix)
shows the real Excel-to-RevoGrid rendering for the full tested number-format
family and representative appearance options. The formats shown there are
stored and rendered by this formatting plugin; clipboard code does not attach
Excel CSS to cells.
For the exact directional support matrix, custom clipboard handlers, rich MIME
contract, formulas, merges, dimensions, and the browser/native Excel boundary,
see [Smart Excel-Compatible Clipboard](/guides/grid-utilities/clipboard-json/).
## Range fill formatting
Register `AutoFillPlugin` and `EventManagerPlugin` alongside
`DataGridFormattingPlugin` to stretch the source range's effective formatting
together with the value sequence selected by AutoFill. A single Rating cell fills
Rating cells; a two-row or two-column source repeats its formats as a tile over the
extended range.
```ts
import {
AutoFillPlugin,
DataGridFormattingPlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, AutoFillPlugin, EventManagerPlugin];
```
Formatting remains owned by this plugin rather than being converted into cell CSS.
The source format is resolved from the same cell, row, column, and authored-column
precedence used during rendering. Destination formats are written as cell-level
runtime operations in one batch after the range edit is accepted. A source slot
with no effective format explicitly clears the corresponding destination slot, so
old destination formatting does not leak into the filled result.
See the [Autofill guide](/guides/data-manage/autofill/#filling-formatted-cells)
for value strategies, preview behavior, and range-fill setup.
## Excel export
Add `ExportExcelPlugin` when Format Cells presentation should be retained in an
`.xlsx` workbook. The Excel plugin discovers the formatting runtime during
export:
```ts
import {
DataGridFormattingPlugin,
ExportExcelPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, ExportExcelPlugin];
```
The integration exports the current formatting automatically:
- Number, Currency, Accounting, Percentage, Scientific, Date, Time, Date &
time, and Plain text presets become native Excel number-format codes.
- Numeric and date cells remain typed workbook values rather than preformatted
strings, so Excel formulas, editing, and sorting continue to work.
- Font family and size, emphasis, text and fill colors, alignment, wrapping,
and borders become workbook cell styles.
- Group rows become merged, indented workbook headings. Export follows the
visible grouped projection, so collapsed descendants are not included.
Advanced visual formats are not converted into Excel chart drawings. A format
definition can provide `exportValue` to convert its structured data to a
workbook-safe scalar while leaving the grid source unchanged. The built-in
Timeline format does this automatically: multiple events export as readable
text such as `Build: 8–12; Review: 14–18` instead of `[object Object]`.
Advanced formats without `exportValue` continue to export their raw cell value,
ordinary value format, and appearance.
Authored `column.excelExport` properties remain the base export configuration.
An explicitly selected Format Cells value or appearance setting overrides the
corresponding workbook field for that target without replacing unrelated
column-owned export properties.
## Selection scope
The menu surface decides which formatting scope is created:
- From a **cell**, formatting applies to the active writable range. If the
multi-range plugin owns several selected ranges and the menu opens inside
them, all resolved writable cells in those ranges are targeted.
- From a **column header**, formatting creates a column-wide default at the
physical column address. It applies to body and pinned rows rendered for that
column.
Readonly behavior is resolved before the command runs:
- Grid-level readonly mode prevents formatting changes.
- Readonly cells are skipped in mixed cell selections.
- A formatting command is unavailable when no writable target remains.
- Synthetic grouping rows are not assigned cell formatting.
## Apply, Cancel, and Clear
The dialog edits a temporary draft:
- **Apply** commits the complete value, advanced presentation, and appearance
draft to the selected cells or columns, closes the dialog, and refreshes the
grid.
- **Cancel**, the close button, backdrop dismissal, and Escape close the dialog
without committing the draft.
- **Clear formatting** commits an explicit clear operation for the selected
target and refreshes the grid.
Clear formatting does not clear the source value. It removes only the rendered
format. An explicit clear also participates in cell-versus-column precedence,
which is important when a cell override and a column default both exist.
## Where formatting is stored
Formatting can come from declarative configuration or runtime operations:
- `column.dataGridFormat` remains part of the column definition.
- `grid.dataGridFormatting` is application-owned grid state. Reassigning it
replaces all declarative grid presets.
- Formats created through the dialog or plugin API are held privately by the
current `DataGridFormattingPlugin` instance; source rows are not modified.
The effective precedence is:
1. A runtime format or explicit clear created through `apply()`,
`applyValueFormat()`, `clear()`, or the context menu.
2. A physical range preset in `grid.dataGridFormatting.cells`.
3. A row preset in `grid.dataGridFormatting.rows`.
4. A column preset in `grid.dataGridFormatting.columns`.
5. The column's static or function-based `dataGridFormat`.
Within runtime state, each apply or clear operation receives a new revision.
When matching runtime cell, row, and column operations overlap, the newest
revision wins. For example:
1. Format the `amount` column as Number.
2. Format one `amount` cell as Currency; that newer cell override wins.
3. Clear the entire `amount` column; the newer column clear wins over the older
cell override.
4. Format the individual cell again; the newest cell format wins.
A whole-row operation follows the same rule. It is stored once for the physical
row, rather than expanded into the currently visible cells, so it also formats
columns added later.
A `DataGridCellFormat` used by any of these sources has up to three independent
layers: ordinary value formatting, an advanced visual presentation, and
appearance. Omit any layer that the cell does not need:
```ts
const format = {
value: {
kind: 'preset', preset: 'currency',
locale: 'en-GB',
currency: 'GBP',
decimalPlaces: 2,
useGrouping: true,
negativeStyle: 'minus',
},
appearance: {
bold: true,
textColor: '#0f172a',
fillColor: '#fef3c7',
horizontal: 'right',
wrap: false,
borderStyle: 'solid',
borderColor: '#f59e0b',
},
presentation: {
id: 'progress-line',
options: { minValue: 0, maxValue: 100 },
},
} as const;
```
No `format` property is added to the example source row. Reading
`row.amount` still returns the original value.
## How formatting reaches rendered cells
After Apply, Clear, or a `grid.dataGridFormatting` assignment, the plugin asks
RevoGrid to refresh all rendered cells. For each non-grouping cell, it resolves
the effective format during `beforecellrender`:
1. Runtime and declarative sources are resolved using the precedence described
above.
2. Appearance settings are composed into `cellProperties`.
3. A compatible advanced presentation renderer is supplied when one is
configured. An authored `cellTemplate` or row-drag renderer remains protected
unless replacement was explicitly enabled.
4. When there is no advanced renderer, a value-format renderer is supplied for
a non-Automatic preset. This explicit choice replaces an authored visual
template; Automatic leaves the authored template in place.
5. The underlying row model and raw value remain unchanged.
Because lookup happens during rendering, formatting follows virtualization,
scrolling, sorting, filtering, pinning, and ordinary grid refreshes. The plugin
does not inspect rendered DOM to reconstruct state.
## Save and restore formatting
Storage belongs to the application. After every runtime formatting change, the
plugin emits `datagridformattingchange` with the complete versioned state. Save
`event.detail.state` wherever the application already stores user preferences,
then pass it back through `grid.dataGridFormatting.state` when creating the
grid.
For example, use `localStorage` without adding storage behavior to the plugin:
```ts
const storageKey = 'invoice-grid-formatting';
const savedState = localStorage.getItem(storageKey);
grid.plugins = [DataGridFormattingPlugin];
grid.dataGridFormatting = {
// Restore runtime formats once; omit state when nothing has been saved.
state: savedState ? JSON.parse(savedState) : undefined,
};
grid.addEventListener('datagridformattingchange', ({ detail }) => {
// detail.state is the complete snapshot; no merge with older state is needed.
localStorage.setItem(storageKey, JSON.stringify(detail.state));
});
```
The event fires after dialog **Apply**, **Clear formatting**, `apply()`,
`applyValueFormat()`, `clear()`, `setState()`, undo, and redo. Assigning
`dataGridFormatting.state` restores state without emitting another event, so it
does not create a save loop or a history entry.
Declarative `columns`, `rows`, and `cells` remain application defaults. The runtime
snapshot contains only user or API operations that override them, including
explicit clears and cell/row/column operation order. Source rows and raw values
remain unchanged.
### Save to a server or application store
Load state first, assign it to the grid, and save the same complete event
payload. Wrap it with your dataset version (or another stable layout token) so
the server can reject stale coordinates:
```ts
const saved = await formattingApi.load(documentId);
grid.dataGridFormatting = {
state: saved?.formatting,
};
let documentVersion = saved?.version;
let pendingSave = Promise.resolve();
grid.addEventListener('datagridformattingchange', ({ detail }) => {
// Serialize writes so an older response cannot overwrite a newer edit.
pendingSave = pendingSave.then(async () => {
const result = await formattingApi.save(documentId, {
version: documentVersion,
datasetVersion: currentDatasetVersion,
formatting: detail.state,
});
documentVersion = result.version;
});
});
```
`detail.state` is already a complete JSON-serializable snapshot: save it as a
replacement, not as a partial patch. On reload, restoring the snapshot does not
emit another persistence event. The application owns request batching,
authentication, retries, error messages, and conflict handling; the formatting
plugin only reports state changes.
Formatting addresses are physical indexes in the corresponding body or pinned
source arrays. They remain valid when row objects are replaced, edited, or
loaded again in the same order. If server sorting, filtering, pagination, or a
new dataset version changes that physical order, do not apply the old snapshot
blindly: reject it by `datasetVersion`, or migrate its coordinates before
assigning `grid.dataGridFormatting.state`. No row key callback is required or
consulted by the formatting runtime.
### Undo and redo
Add History when formatting changes should use the same undo and redo stack as
data edits. `HistoryPlugin` installs Event Manager automatically:
```ts
import {
DataGridFormattingPlugin,
HistoryPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, HistoryPlugin];
```
Each Apply or Clear operation is one history entry. Undo and redo restore the
complete previous or next formatting state and emit
`datagridformattingchange`, so the application saves the replayed state through
the same listener. No additional history configuration is required.
### Manual state control
The plugin instance also exposes `getState()` and `setState()`:
```ts
const saved = formatting.getState();
formatting.setState(saved);
```
State version 2 includes runtime row, column, and physical-cell operations,
explicit clears, dimension types, and revision order. It is JSON-safe when custom
presentation options are JSON-safe.
## Programmatic formatting API
`DataGridFormattingPlugin` also provides methods for application-driven
formatting. It still installs the context menu and dialog; you only need to
retrieve the instance when application code must apply, inspect, clear, or open
formatting programmatically. Use the grid's plugin collection rather than
attaching custom methods to the grid element:
```ts
import {
DataGridFormattingPlugin,
type DataGridFormattingSelection,
} from '@revolist/revogrid-pro';
type InvoiceRow = { id: string; amount: number };
const invoice: InvoiceRow = { id: 'invoice-1', amount: 1200 };
grid.columns = [{ prop: 'amount', name: 'Amount' }];
grid.plugins = [DataGridFormattingPlugin];
grid.source = [invoice];
const formatting = (await grid.getPlugins()).find(
(plugin) => plugin instanceof DataGridFormattingPlugin,
) as DataGridFormattingPlugin | undefined;
formatting?.configure({
locale: 'en-GB',
currencies: ['GBP', 'EUR'],
});
// Zero-based physical indexes into the rgRow and rgCol source arrays.
const selection: DataGridFormattingSelection = {
start: { row: 0, column: 0 },
};
formatting?.apply(selection, {
value: {
kind: 'preset', preset: 'currency',
locale: 'en-GB',
currency: 'GBP',
decimalPlaces: 2,
},
appearance: {
bold: true,
horizontal: 'right',
},
});
```
`start` identifies the first physical source cell. The optional inclusive
`end` defaults to `start`, so one coordinate formats one cell. `rowType` and
`colType` default to `rgRow` and `rgCol`.
```ts
// Entire physical row across four columns.
formatting?.patch({
start: { row: 3, column: 0 },
end: { row: 3, column: 3 },
}, {
appearance: { bold: true },
});
// Three rows by two columns.
formatting?.apply({
start: { row: 3, column: 1 },
end: { row: 5, column: 2 },
}, {
appearance: { fillColor: '#fef3c7' },
});
// Independent or overlapping ranges are accepted as an array.
formatting?.clear([
{ start: { row: 0, column: 0 }, end: { row: 1, column: 1 } },
{ start: { row: 8, column: 4 } },
]);
```
Physical indexes address the unfiltered `source` arrays directly. Sorting,
filtering, and viewport order do not change which source models are targeted.
The plugin stores those coordinates directly and resolves the current source
row and column only when it needs rendering or callback context.
The public methods are:
| Method | Purpose |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `configure(config)` | Sets dialog and rendering options such as locale, currencies, fonts, and localized labels. |
| `apply(selection, format)` | Replaces the selected target's complete value, advanced presentation, and appearance format. |
| `applyValueFormat(selection, valueFormat)` | Selects an ordinary value presentation, retains appearance, and replaces an active advanced visualization. |
| `clear(selection)` | Applies an explicit clear that suppresses lower-priority declarative formats for the selected target. |
| `getFormat(address)` | Resolves the effective format at a physical cell address. |
| `getColumnFormat(address)` | Resolves the effective format for a complete physical column. |
| `getState()` | Returns a portable, versioned snapshot of runtime Apply and Clear operations. |
| `setState(state)` | Replaces runtime operations, refreshes the grid, and emits `datagridformattingchange`. |
| `openDialog(selection, valueKind, valueFormatting?)` | Opens Format Cells for an application-provided selection. Pass `false` as the third argument for appearance-only editing. |
For example, open the same dialog programmatically:
```ts
formatting?.openDialog(selection, 'number');
```
Or inspect and clear the known target:
```ts
const current = formatting?.getFormat({ row: 0, column: 0 });
if (current?.value?.preset === 'currency') {
formatting?.clear(selection);
}
```
## Practical recommendations
- Use physical coordinates for persisted formatting. Replacing source objects
at the same positions preserves formatting; moving records does not move it.
- Keep source values typed and unformatted; let the renderer own presentation.
- Use `columnTypeKinds` to control type-aware command availability for custom
column types and parseable date strings; it does not coerce numeric strings.
- Use column-header formatting for broad defaults and cell formatting for
exceptions.
- Choose Automatic to restore a custom interactive template after displaying a
plain value format.
- Listen to `datagridformattingchange` and persist `detail.state` in the
application-owned browser store, state manager, or server API.
---
# Cell and Row Notes
URL: https://pro.rv-grid.com/guides/data-grid/notes/
Source: src/content/docs/guides/data-grid/notes.mdx
Description: Add versioned cell and row notes with severity, mentions, filtering, offline drafts, and XLSX export.
`GridNotesPlugin` adds metadata notes without changing source rows. Every note is addressed by a stable host row ID and, for cell notes, a column prop. Sorting, filtering, pinning, grouping, clipboard values, and CSV output therefore remain independent from note content.
## Configure the plugin
```ts
import {
GridNotesPlugin,
InMemoryGridNoteAdapter,
} from '@revolist/revogrid-pro';
const adapter = new InMemoryGridNoteAdapter();
grid.plugins = [GridNotesPlugin];
grid.gridNotes = {
adapter,
getRowId: row => row.id,
getRowLabel: row => row.status,
getCurrentUser: () => ({
id: session.user.id,
name: session.user.name,
avatarUrl: session.user.avatarUrl,
}),
mentions: {
search: (query, signal) => directory.searchUsers(query, { signal }),
},
can: ({ action, target, user }) => permissions.can(action, target, user),
excel: { includeNotesSheet: true },
};
```
`getRowId` is required. It must be pure, stable, and globally unique across body and pinned row stores. A visual row index is not a valid ID. Duplicate visual presentations of the same authored row should resolve to the same ID; synthetic group and subtotal rows should not receive an authored ID.
`getRowLabel` is optional and controls the human-readable value displayed in a note title. Use it to bind a field such as `status`; the stable `getRowId` value is never shown in the popover.
The bundled in-memory adapter is a reference implementation for demos and tests. Production applications should implement `GridNoteStorageAdapter` against their API. Bulk `loadSummaries()` responses contain marker metadata and a 120-character excerpt only. Full bodies come from `getNote()`, while version bodies are paged through `listVersions()`.
Set `GridNoteUser.avatarUrl` on authors and users returned by the mention directory to show their image beside their name in note metadata and mention suggestions. If an image cannot load, the plugin retains an accessible initials avatar, using `GridNoteUser.color` when supplied.
## Interaction
- Open the row context menu for **Add cell note** or **Add row note**.
- Press Shift+F2 on the focused cell.
- Select a coloured corner marker without entering cell edit mode.
- Select the row-gutter bar or dot to open the row note and the other cell notes on that row.
- Save with Ctrl+Enter or Command+Enter.
The portalled dialog closes on Escape, outside pointer interaction, or when its virtualized anchor disappears. Unsaved text is retained in local storage and dirty close attempts use `confirmDiscard`.
Selecting **Delete** opens a confirmation panel inside that same dialog. It does not call the browser-native confirmation API.
## Adapter concurrency
`upsert`, `delete`, and `restoreVersion` receive an idempotent mutation ID and expected version. An adapter should throw `GridNoteConflictError` when the current version changed. Restore creates a new current version with `restoredFromVersionId`; it never rewinds or mutates the append-only log.
Empty text is treated as a confirmed delete. Transient saves retry with jittered exponential backoff, pause while the browser is offline, and clear the persisted draft only after success.
## Severity filter and export
The context menu includes a multi-select **Filter by note severity** submenu. Matching is row-wide: a row remains when its row note or any cell note has a selected severity. The plugin applies its own trimmed-state category, so ordinary filters keep their state.
When `ExportExcelPlugin` is also installed and `excel.includeNotesSheet` is true, XLSX output gains a Notes worksheet. It contains one row per current note, including target identity, severity, body, mentions, author, timestamps, and version count. Clipboard and CSV values are unchanged.
## Events
Listen for `gridnotechange`, `gridnotemention`, `gridnoteconflict`, `gridnoteerror`, and `gridnotefilterchange`. Mention events contain only users introduced relative to the previous version, including a restore that reintroduces a mention.
---
# Audit Trail History
URL: https://pro.rv-grid.com/guides/data-manage/audit-history/
Source: src/content/docs/guides/data-manage/audit-history.mdx
Description: Track, review, persist, restore, and export accountable RevoGrid data changes.
`AuditHistoryPlugin` records accountable data-change transactions for spreadsheet-like business workflows. Use it when the grid edits important records and your application needs to answer who changed what, when it changed, and how to recover it.
`HistoryPlugin` owns local undo and redo. `AuditHistoryPlugin` owns the reviewable change log, user metadata, persistence hooks, restore actions, and audit panel. Audit History automatically reuses an existing `HistoryPlugin` or installs one when needed; use `grid.history` when you need to configure History behavior.
## What To Try
The demo is wired like a production audit surface: edit cells, select a changed cell, filter the history, export records, and restore previous values from the panel.
Change a cell and watch the panel add a timestamped record with user and before/after value details.
Hover a marked cell to see the latest change summary while keeping the full audit trail in the panel.
Click a cell or row to switch between focused cell history, row history, and full-grid history.
Use restore actions to roll back a cell, row snapshot, or transaction while recording the restore itself.
## Why Use It
When users edit operational data, the final cell value is not enough. Audit history keeps the context around each change so business users, admins, and support teams can review the full lifecycle of a value.
Record the current user, timestamp, action type, row identity, changed column, previous value, and new value.
Group paste and range-edit operations into one transaction instead of treating every changed cell as an unrelated record.
Restore a single cell, a row snapshot, or a complete transaction after accidental edits.
Keep records in memory, browser `localStorage`, or a custom backend adapter.
## When To Use It
Audit history is useful when RevoGrid edits real business data, not temporary UI state.
Track changes to orders, invoices, stock levels, delivery statuses, prices, and approvals.
Review manual adjustments to budgets, forecasts, risk values, and reporting tables.
Log edits to permissions, limits, subscriptions, account status, and configuration records.
Explain why a value changed without guessing from the current dataset.
## Basic Setup
### Cell Change Indicators
The Audit History examples also mark edited cells directly in the grid. When a cell has recorded changes, the cell shows a small upper-right corner marker aligned to the cell edge. Hovering the cell uses `TooltipPlugin` to show the change count, latest before/after value, user, and timestamp.
This is built on existing plugin APIs: resolve the `AuditHistoryPlugin` instance, call `getCellHistory(rowId, column)` from cell rendering hooks, add tooltip attributes through `cellProperties`, and refresh visible rows when audit events fire.
```ts
const records = auditPlugin.getCellHistory(row.id, column.prop);
```
The marker is only a visual affordance. The audit panel remains the complete review and restore surface.
Use `AuditHistoryPlugin` with `EventManagerPlugin`. The event manager normalizes cell edits, range edits, paste operations, undo, redo, and audit restore replays into edit events the audit plugin and other edit integrations can observe.
Audit History delegates range-shaped restore updates through the existing History replay path. That means cell restore, row replacement restore, and transaction restore emit `beforehistedit`; `EventManagerPlugin` turns that into the normal `gridedit` flow; and `CellFlashPlugin` can flash restored cells from the same edit pipeline. Row insertion and snapshot restore still use direct source/data updates because they are not pure range patches.
1. Import the plugins, panel helper, and config type.
```ts
import {
AuditHistoryPlugin,
CellFlashPlugin,
EventManagerPlugin,
createLocalStorageAuditHistoryAdapter,
defineAuditHistoryPanel,
type AuditHistoryConfig,
} from '@revolist/revogrid-pro';
```
2. Provide the audit config.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => ({
id: 'avery-stone',
name: 'Avery Stone',
email: 'avery.stone@company.com',
}),
rowIdProp: 'id',
storage: 'memory',
onAuditRecord(record) {
// Persist to your API, Supabase, Firebase, PostgreSQL, etc.
console.log(record);
},
};
```
3. Register plugins and bind the config directly on the grid.
```ts
grid.plugins = [EventManagerPlugin, AuditHistoryPlugin, CellFlashPlugin];
grid.auditHistory = auditHistory;
```
`HistoryPlugin` does not need to be listed for normal Audit History restore behavior. Add it explicitly only when the page also has direct History UI controls or you want to control plugin order yourself.
4. Mount the optional review panel.
```ts
const auditPanel = defineAuditHistoryPanel(panelElement, grid, {
pageSize: 25,
allowExport: true,
placement: 'right',
restoreActions: ['cell', 'row'],
formatDate: (value, { mode }) =>
new Intl.DateTimeFormat(undefined, mode === 'day'
? { weekday: 'short', month: 'short', day: 'numeric' }
: { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(value)),
events: {
beforeExport: ({ format }) => format !== 'json',
beforeRestore: ({ type }) => type !== 'transaction',
},
});
// Later:
auditPanel.setPlacement('bottom');
auditPanel.refresh();
```
The plugin still observes `additionalData.auditHistory` for older integrations, but new code should use the direct `grid.auditHistory` property.
## Stable Row Identity
Audit records should point to business records, not visual row positions. Row indexes can change after sorting, filtering, grouping, or lazy loading, so every row should expose a stable identity.
```ts
const rows = [
{ id: 'invoice-123', status: 'Draft', amount: 1200 },
{ id: 'invoice-124', status: 'Paid', amount: 800 },
];
const auditHistory: AuditHistoryConfig = {
rowIdProp: 'id',
};
```
If your row identity is derived, use `getRowId` instead:
```ts
const auditHistory: AuditHistoryConfig = {
getRowId: (row) => `${row.tenantId}:${row.invoiceNumber}`,
};
```
## What Gets Recorded
Each top-level audit item is an `AuditRecord`. A record contains one transaction ID and one or more `AuditChange` entries.
```ts
{
id: 'audit-001',
transactionId: 'tx-001',
type: 'cell-change',
changedAt: '2026-05-08T12:00:00Z',
changedBy: {
id: 'avery-stone',
email: 'avery.stone@company.com',
},
changes: [
{
id: 'change-001',
rowId: 'invoice-123',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
},
],
}
```
For a paste operation, the same record can contain many changes:
```ts
{
transactionId: 'tx-002',
type: 'bulk-paste',
changedAt: '2026-05-08T12:05:00Z',
changedBy: { id: 'avery-stone' },
changes: [
{
id: 'change-002',
rowId: 'invoice-123',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
},
{
id: 'change-003',
rowId: 'invoice-124',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Rejected',
},
],
}
```
## Storage Options
Use `storage: 'memory'` for demos, temporary sessions, and test fixtures.
Use `storage: 'localStorage'` when browser persistence is enough for the workflow.
Use `storage: 'custom'` with `storageAdapter` when your backend should load, append, save, or clear records.
Use `storageProviders` to list cache or server providers. Provider loads refresh the panel automatically.
Use `maxRecords` to bound retained records. The implementation defaults to `1000`.
For production systems, prefer a trusted server provider. If you include several providers, the default is intentionally isolated: the first provider that returns records owns the loaded audit trail, and later providers with records are ignored with a console warning. Set `mergeStorageProviders: true` only when your app deliberately wants to merge records by id across providers.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => currentUser,
rowIdProp: 'id',
storageProviders: [
{
async load() {
const response = await fetch('/api/audit-history');
return response.json();
},
async append(record) {
await fetch('/api/audit-history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(record),
});
},
async save(records) {
await fetch('/api/audit-history', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(records),
});
},
async clear() {
await fetch('/api/audit-history', { method: 'DELETE' });
},
},
],
};
```
Provider write failures emit `auditerror` but do not block editing. Configuring `storageProviders` together with `storage` or `storageAdapter` shows a console warning because `storageProviders` takes precedence.
When you need a deliberate cache-plus-server merge, opt in explicitly:
```ts
const auditHistory: AuditHistoryConfig = {
rowIdProp: 'id',
mergeStorageProviders: true,
storageProviders: [
createLocalStorageAuditHistoryAdapter('invoices:audit-history'),
serverAuditHistoryAdapter,
],
};
```
### Provider Load Flow
Provider chains are useful when the user should see cached history quickly while a server-backed provider loads the durable records.
1. The panel mounts and reads the current in-memory records.
2. Storage providers load in order. By default, the first provider that returns records owns the loaded audit trail.
3. The plugin emits `auditrecordsloaded` whenever loaded records are applied.
4. The built-in panel refreshes on `auditrecordsloaded`, `auditrecord`, and `auditrestore`, so async loads and external restore controls stay visible without polling.
Client-side records are excellent for review panels and recovery UX. Compliance-grade audit trails should be written to a trusted backend.
## Privacy And Policy Controls
Production audit logs often need more than raw before/after values. Use `ignoredColumns`, `sanitizeValue`, `getMetadata`, `canRestore`, and `immutable` to align the client behavior with your business rules.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => currentUser,
rowIdProp: 'id',
ignoredColumns: ['internalNotes'],
sanitizeValue(value, context) {
if (context.column === 'amount') {
return '[redacted]';
}
return value;
},
getMetadata() {
return {
workspaceId: currentWorkspace.id,
requestId: crypto.randomUUID(),
};
},
canRestore({ type }) {
return currentUser.role === 'admin' && type !== 'transaction';
},
};
```
Use action-type policy when entire classes of audit records should be included or ignored:
```ts
const auditHistory: AuditHistoryConfig = {
ignoredActionTypes: ['undo', 'redo'],
trackedActionTypes: ['cell-change', 'bulk-paste', 'approval-submitted'],
shouldTrackRecord({ type, changes, metadata }) {
if (type === 'server-validation' && metadata?.severity === 'debug') {
return false;
}
return changes.length > 0 || type === 'approval-submitted';
},
};
```
For compliance-style screens where the client must not mutate local audit records, set `immutable: true`. This disables `clear`, `replaceRecords`, and restore actions in the plugin. Server-side immutability should still be enforced by your backend.
```ts
const auditHistory: AuditHistoryConfig = {
storage: 'custom',
storageAdapter,
immutable: true,
};
```
## Review And Restore
The audit panel lets users inspect grid changes without leaving the page. Common review flows include full-grid history, selected-row history, selected-cell history, user filtering, date filtering, action-type filtering, and old/new value comparison.
Switch between full-grid, selected-row, and selected-cell history without changing the underlying audit records.
The panel refreshes after new audit records, provider-loaded batches, and restore events from other UI controls.
`canRestore`, `beforeauditrestore`, and `immutable` let the app decide which restore actions users can run.
Generate filtered CSV or JSON exports for reporting, support workflows, or backend handoff.
Use the plugin instance when you need audit data in custom UI:
```ts
const plugins = await grid.getPlugins();
const auditHistoryPlugin = plugins.find(
(plugin) => plugin instanceof AuditHistoryPlugin,
);
const records = auditHistoryPlugin?.getRecords({ userId: 'avery-stone' });
const cellHistory = auditHistoryPlugin?.getCellHistory('invoice-123', 'status');
const rowHistory = auditHistoryPlugin?.getRowHistory('invoice-123');
const lastStatusChange = auditHistoryPlugin?.getLastChange('invoice-123', 'status');
const stats = auditHistoryPlugin?.getStats();
```
Restore helpers return `true` when a restore was applied:
```ts
auditHistoryPlugin?.restoreCell(change);
auditHistoryPlugin?.restoreRow('invoice-123');
auditHistoryPlugin?.restoreTransaction(transactionId);
```
Cell, row replacement, and transaction restores are replayed through `HistoryPlugin.replayChange()`. The restore itself is still recorded as `restore-cell`, `restore-row`, or `restore-transaction`, but the replay is guarded so it does not create a second normal edit record.
Use `recordEvent` for application-driven records that do not come from direct grid editing:
```ts
auditHistoryPlugin?.recordEvent({
type: 'api-sync',
changedBy: { id: 'webhook', name: 'Webhook' },
presentation: {
source: 'api',
targetLabel: 'F1:F8',
detailLabel: '8 cells recalculated',
},
});
```
Custom action types are plain strings. When the record carries normal cell or row changes, existing restore helpers can use it for revert workflows:
```ts
auditHistoryPlugin?.recordEvent({
type: 'approval-submitted',
transactionId: 'approval-42',
changedBy: currentUser,
changes: [{
rowId: 'invoice-123',
rowIndex: 0,
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
}],
presentation: {
source: 'api',
verb: 'submitted approval',
targetLabel: 'Invoice 123',
},
});
auditHistoryPlugin?.restoreTransaction('approval-42');
```
Summary-only custom records are ignored unless you opt in with `allowEmpty: true`.
Use snapshots when users need a named restore point:
```ts
const snapshot = auditHistoryPlugin?.createSnapshot('Before Q4 close');
if (snapshot) {
auditHistoryPlugin?.restoreSnapshot(snapshot);
}
```
Query helpers return defensive copies, so application code cannot accidentally mutate the plugin's internal audit log.
## Loading And Export
Use `refreshRecords` when your custom storage adapter can load existing records from a backend. A single `storageAdapter` still supports merge-by-id refreshes by default; pass `{ merge: false }` to replace the local set. Provider chains follow the configured provider policy: isolated by default, merged only with `mergeStorageProviders: true`.
```ts
await auditHistoryPlugin?.refreshRecords();
await auditHistoryPlugin?.replaceRecords(serverRecords, { merge: false });
```
Use `exportRecords` for custom export buttons or reporting workflows:
```ts
const csv = auditHistoryPlugin?.exportRecords({
format: 'csv',
filter: { actionType: 'bulk-paste' },
includeMetadata: true,
});
const json = auditHistoryPlugin?.exportRecords({ format: 'json' });
```
Export failures are reported through `auditerror` with phase `export` and return an empty string. That keeps export buttons from crashing the page while still giving your app a single error channel for observability.
The built-in panel docks to the right of the grid by default and can be pinned with `placement: 'left' | 'right' | 'top' | 'bottom' | 'none'`. It includes timeline day grouping, search, user/column/action/date filter chips, scoped cell and row views, pagination, CSV/JSON export controls, hover restore/jump actions, row-click compare mode, optional mini close state, and i18n-friendly labels. Use `restoreActions`, callback-capable `allowCompare`, `formatDate`, and `events` to control panel actions. Pass `tooltips: false` to hide all help icons, or override/hide individual entries with `tooltips: { searchFilter: 'Find a record', clearFilters: false }`. Pass `labels` to rename visible text, placeholders, aria labels, action names, source labels, and dynamic strings such as pagination or selected row/cell summaries.
Listen for `beforeauditrestore` to validate or block a restore, and `auditrestore` after a restore succeeds. New audit records are emitted through `auditrecord`. Storage and hook failures are emitted through `auditerror`; loaded record batches are emitted through `auditrecordsloaded`.
## Production Flow
```txt
User edits grid
-> EventManagerPlugin normalizes the edit
-> AuditHistoryPlugin creates an audit transaction
-> the record is stored locally or sent to your backend
-> users review changes in the audit panel
-> admins restore cells, rows, or transactions when needed
-> range-shaped restores replay through HistoryPlugin and EventManagerPlugin
```
## Summary
`AuditHistoryPlugin` adds accountability to editable RevoGrid applications. Pair it with stable row IDs, a current-user provider, and a trusted persistence path when users edit important operational data.
---
# Autofill
URL: https://pro.rv-grid.com/guides/data-manage/autofill/
Source: src/content/docs/guides/data-manage/autofill.mdx
Description: Fill ranges with spreadsheet-like copy and sequence behavior.
The `AutoFillPlugin` adds spreadsheet-style drag fill to RevoGrid. Users select one
or more cells, drag the fill handle, and RevoGrid fills the target range with the
best matching strategy.
For a richer spreadsheet experience, pair it with `AutoFillPreviewPlugin`. The
preview plugin shows ghost values while the user is dragging, but it does not write
anything to the data store until the drop-time autofill is accepted.
Select one or more seed cells in the demo below, then drag the fill handle to
preview the values before releasing the mouse to commit them.
## Usage
```ts
import { AutoFillPlugin, AutoFillPreviewPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin, AutoFillPreviewPlugin];
```
Use only `AutoFillPlugin` when you want values to appear only after mouse release.
Add `AutoFillPreviewPlugin` when users should see the predicted values before they
drop the mouse.
### Formula autofill ownership
`AutoFillPlugin` remains the single owner of fill-handle interaction, strategy
selection, preview matrices, and committed range edits. When `FormulaPlugin` is
used, it automatically installs or reuses that same `AutoFillPlugin` and adds a
result transformer for raw `=...` cells. It does not implement a second fill
pipeline. A host-provided AutoFill instance, including its custom strategies, is
therefore preserved.
The fill handle still requires range selection:
```ts
import {
AutoFillPreviewPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin, AutoFillPreviewPlugin];
```
`AutoFillPreviewPlugin` is optional. When present, preview and commit pass through
the same Formula transformer, so the formula shown during the drag is the formula
stored after release.
One row down:
| Seed formula | Filled formula |
| --- | --- |
| `=A1` | `=A2` |
| `=$A$1` | `=$A$1` |
| `=A$1` | `=A$1` |
| `=$A1` | `=$A2` |
| `=SUM(A1:B2,$C1:D$4)` | `=SUM(A2:B3,$C2:D$4)` |
One column right:
| Seed formula | Filled formula |
| --- | --- |
| `=A1` | `=B1` |
| `=$A$1` | `=$A$1` |
| `=A$1` | `=B$1` |
| `=$A1` | `=$A1` |
| `=SUM(A1:B2,$C1:D$4)` | `=SUM(B1:C2,$C1:E$4)` |
Each endpoint of a range moves independently. Quoted and unquoted sheet
qualifiers are retained, so copying `='Product List'!A1:B2` down produces
`='Product List'!A2:B3`. Multi-cell selections tile from the corresponding seed
cell in every direction. Moving a relative coordinate before row 1 or column A
produces `#REF!`.
## Filling formatted cells
Register AutoFill and Event Manager with the formatting plugin when the fill handle
should stretch both values and formatting. Event Manager confirms the accepted range
edit before formatting is applied:
```ts
import {
AutoFillPlugin,
AutoFillPreviewPlugin,
DataGridFormattingPlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
DataGridFormattingPlugin,
AutoFillPlugin,
EventManagerPlugin,
AutoFillPreviewPlugin,
];
```
The formatting plugin resolves the effective format of every source cell,
including cell-, row-, and column-level formatting, and repeats that format as a
two-dimensional tile over the added cells. Advanced presentations such as Rating,
Progress, Badge, fonts, fills, borders, alignment, and number formats therefore
follow the same fill-handle gesture as the values. Existing source cells are not
rewritten, and formatting is applied only when the unified range edit is accepted.
See [Format Cells: range fill and clipboard](/guides/data-grid/format-cells/#range-fill-formatting)
for formatting ownership and persistence details.
### Crossing Pinned And Main Viewports
Add `MultiRangeSelectionPlugin` when users should be able to drag the autofill
handle from a pinned row or column into the main viewport, or from the main
viewport into a pinned section. Cross-viewport autofill is enabled by default
with multi-range selection.
```ts
import {
AutoFillPlugin,
MultiRangeSelectionPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [AutoFillPlugin, MultiRangeSelectionPlugin];
```
Cross-viewport autofill is scoped to visible viewport segments. If the main
viewport is scrolled, the plugin does not treat the pinned section and main data
as one hidden continuous range, and it does not write rows or columns behind the
scroll gap. The final selection is represented as multiple ranges so each visible
source and target segment remains explicit.
Disable the cross-viewport drag integration while keeping multi-range selection
enabled with:
```ts
grid.multiRangeSelection = { crossViewportAutofill: false };
```
## What Users See
1. Select a source cell or range.
2. Drag the small autofill handle from the selection corner.
3. Ghost values appear in the temporary target range when preview is enabled.
4. Release the mouse to commit the same values that were previewed.
5. Pressing escape, clearing the temporary range, or changing the source clears the preview.
Existing selected source cells are not visually replaced by preview values. The
preview only appears in the autofill target range.
## Built-In Strategies
AutoFill strategies are checked from most specific to most general. If no sequence
strategy can safely infer a pattern, RevoGrid repeats the selected values with copy
fallback.
| Strategy | User behavior | Example |
| --- | --- | --- |
| Linear numeric sequence | Continues a selected numeric interval. | `1, 3` -> `5, 7` |
| Single numeric step | Extends one selected number by `+1`. | `10` -> `11, 12` |
| Number in text | Continues labels with embedded numbers and preserves padding. | `INV-099` -> `INV-100`; `1st Floor` -> `2nd Floor` |
| Weekday names | Continues English weekday labels and wraps the week. | `Mon, Tue` -> `Wed` |
| Month names | Continues English month labels and wraps the year. | `Jan, Feb` -> `Mar` |
| Date unit sequence | Continues daily, weekly, monthly, quarterly, and yearly date intervals. | `2024-12-04, 2024-12-05` -> `2024-12-06` |
| Time sequence | Continues `HH:mm` and `HH:mm:ss` time intervals. | `09:00, 09:30` -> `10:00` |
| Boolean cycle | Repeats boolean-like toggle pairs. | `Yes, No` -> `Yes, No` |
| Text suffix sequence | Keeps compatibility with existing `Book 1`, `Book 2` fills. | `Book 1, Book 2` -> `Book 3` |
| Copy fallback | Repeats selected values when the pattern is plain or ambiguous. | `Draft, Approved` -> `Draft, Approved` |
## Strategy Notes
### Numeric Values
Select two or more numeric cells to preserve an interval. For example, selecting
`100` and `110` continues with `120`, `130`, and so on. Selecting one numeric cell
uses a simple `+1` sequence.
### Text With Numbers
AutoFill understands numbers inside labels, not only numbers at the end. It keeps
the visible text shape stable when the selected values are consistent.
```txt
Item 001 -> Item 002
INV-099 -> INV-100
1st Floor, 2nd Floor -> 3rd Floor
```
If labels use different prefixes, suffixes, or number formats, RevoGrid falls back
to copying the selected values instead of guessing.
### Dates And Times
Dates preserve the selected visible format when possible:
```txt
2024-12-04 -> 2024-12-05
2024/12/04 -> 2024/12/05
12/04/2024 -> 12/05/2024
```
Monthly, quarterly, and yearly fills are inferred from consistent month steps. Time
fills preserve `HH:mm` or `HH:mm:ss` and wrap across midnight.
### Weekdays And Months
Weekday and month strategies support English full names and three-letter labels.
The output format follows the source format:
```txt
Monday, Tuesday -> Wednesday
Mon, Tue -> Wed
January, February -> March
Jan, Feb -> Mar
```
### Boolean-Like Values
Boolean cycle supports `true/false`, `yes/no`, and `on/off`. String pairs require
both values in the selected range, so a single `Yes` cell is copied instead of
turning into an inferred toggle.
### Copy Fallback
Copy fallback is intentional. It handles plain text, mixed values, and unsupported
patterns by repeating the selected values exactly. This keeps AutoFill predictable
for status columns and other business data where guessing a sequence would be
surprising.
## Custom Strategies
Developers can register custom strategies for domain-specific patterns. Custom
strategies run before copy fallback, so they can override generic copy behavior.
```ts
import type { AutoFillStrategy } from '@revolist/revogrid-pro';
const priorityStrategy: AutoFillStrategy = (selectedData, direction, targetRange, context) => {
// Return a matrix when your strategy supports the selection.
// Return null to let the next strategy try.
return null;
};
AutoFillPlugin.registerStrategy(priorityStrategy);
```
Return `null` when a strategy is not confident. Preview and final apply share the
same strategy list, so a custom strategy automatically affects both behaviors.
---
# Collaborative Editing
URL: https://pro.rv-grid.com/guides/data-manage/collaborative-editing/
Source: src/content/docs/guides/data-manage/collaborative-editing.mdx
Description: Learn how to add shared cell editing, presence, pending review, permissions, server commits, conflicts, and audit attribution to RevoGrid Pro.
Collaborative Editing is separately distributed to keep Yjs out of applications
that do not need it. Trial users should install
`@revolist/revogrid-collaborative-editing-trial` alongside
`@revolist/rv-pro-trial`. Collaborative Presence remains available from the
main Pro trial package.
`CollaborativeEditingPlugin` lets several users work on the same grid data at the same time. One user edits a cell, the edit is written into a shared Yjs document, and the other connected grids can receive the same change.
You do not need to know Yjs before using the plugin. Think of Yjs as the shared notebook where the browser stores collaborative changes. RevoGrid still renders the grid, your app still owns users and business rules, and your backend still decides what is finally saved.
Install collaborative editing as a separate package so Yjs is only loaded by applications that need realtime editing:
```sh
pnpm add @revolist/revogrid-collaborative-editing @revolist/revogrid-pro
```
Trial equivalent:
```sh
pnpm add @revolist/revogrid-collaborative-editing-trial @revolist/rv-pro-trial
```
## Live Demo: Split Collaborative Editing
The demo catalog includes a complete split-view example with two users editing the same shared document. Open it when you want to see the moving parts together instead of reading isolated snippets.
[Open the Collaborative Editing demo](https://demo.rv-grid.com/collaborative-editing/ts)
The demo shows:
- two grids on the same page using one collaborative document;
- Avery and Grace rendered with matching presence colors and avatars;
- live remote focus and selection markers through `CollaborativePresencePlugin`;
- the first grid with the `Task` column pinned while the second grid keeps it unpinned;
- `EventManagerPlugin` with `applyEventsToSource: false`;
- an in-memory presence transport that stands in for a realtime provider;
- a mock `commitAdapter` that logs server commits so the backend boundary is visible.
## The Mental Model
Collaborative editing has a few moving parts. The names are technical, but the ideas are simple.
A user changes a cell. `EventManagerPlugin` normalizes that edit so collaborative editing sees cell edits, paste, range edits, undo, and redo through one pipeline.
Presence means "where are the other users looking or editing?" Add `CollaborativePresencePlugin` when you want remote cursors, ranges, colors, and labels.
In review mode, edits stay pending until your UI commits or discards them. This is useful when a manager, workflow, or server must approve changes.
A commit is the point where your app asks the backend to accept changes. The plugin calls your `commitAdapter`; your backend can accept, rewrite, or reject each edit.
A conflict happens when users edit the same row and column concurrently. The default policy keeps the change pending so your UI can show a decision.
Audit records explain who changed what and when. Add `AuditHistoryPlugin` when committed collaborative edits should be logged.
The plugin coordinates grid edits, shared Yjs state, pending changes, conflicts, presence bridging, and audit integration. Your app still owns authentication, user identity, backend persistence, authorization, and the review UI.
## Beginner Setup
Start with the smallest local setup, then add the pieces your product needs.
### 1. Minimal Local Grid
This example needs no server and no transport. It is useful for learning the plugin and verifying that local cell edits enter the collaborative pipeline.
1. Import the base plugins.
```ts
import {
EventManagerPlugin,
} from '@revolist/revogrid-pro';
import {
CollaborativeEditingPlugin,
type CollaborativeEditingConfig,
} from '@revolist/revogrid-collaborative-editing';
```
2. Give every row a stable id.
```ts
const rows = [
{ id: 'task-1', task: 'Revenue forecast', owner: 'Avery', status: 'Draft' },
{ id: 'task-2', task: 'Vendor renewal', owner: 'Mina', status: 'Review' },
{ id: 'task-3', task: 'Headcount plan', owner: 'Jon', status: 'Approved' },
];
const columns = [
{ prop: 'task', name: 'Task' },
{ prop: 'owner', name: 'Owner' },
{ prop: 'status', name: 'Status' },
];
```
3. Register plugins and bind configuration.
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = columns;
grid.source = rows;
grid.plugins = [
EventManagerPlugin,
CollaborativeEditingPlugin,
];
grid.eventManager = { applyEventsToSource: false };
const collaborativeEditing: CollaborativeEditingConfig = {
documentId: 'local-learning-session',
user: {
id: 'avery',
name: 'Avery Stone',
color: '#2563eb',
permissionLevel: 'editor',
},
rowIdProp: 'id',
};
grid.collaborativeEditing = collaborativeEditing;
```
`grid.eventManager = { applyEventsToSource: false }` is important. It lets collaborative editing own how edits are applied, reviewed, committed, or rolled back instead of letting each raw grid edit immediately mutate source data twice.
### 2. Two Grids On The Same Page
Use a shared Yjs document when two grids on one page should represent the same collaborative session. This is the easiest way to understand how two users share edits before connecting a real transport.
```ts
import * as Y from 'yjs';
import {
CollaborativePresencePlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
import {
CollaborativeEditingPlugin,
} from '@revolist/revogrid-collaborative-editing';
const doc = new Y.Doc();
const plugins = [
EventManagerPlugin,
CollaborativePresencePlugin,
CollaborativeEditingPlugin,
];
function configureGrid(grid: HTMLRevoGridElement, user: { id: string; name: string; color: string }) {
grid.columns = columns;
grid.source = rows.map(row => ({ ...row }));
grid.plugins = plugins;
grid.eventManager = { applyEventsToSource: false };
grid.collaborativeEditing = {
documentId: 'same-page-demo',
user,
rowIdProp: 'id',
doc,
presence: {
enabled: true,
showLabels: true,
staleAfterMs: 15_000,
},
};
}
configureGrid(gridA, { id: 'avery', name: 'Avery Stone', color: '#2563eb' });
configureGrid(gridB, { id: 'grace', name: 'Grace Hopper', color: '#16a34a' });
```
Both grids use the same `documentId` and the same `doc`, so they are intentionally part of the same collaborative document.
`CollaborativeEditingPlugin` maps awareness state into presence users. `CollaborativePresencePlugin` renders those users as colored focus and range markers. For deeper cursor and range behavior, read [Collaborative Presence](/guides/data-manage/collaborative-presence/).
### 3. Review Mode With Pending Changes
Use review mode when users should make changes locally, inspect the pending list, then commit or discard those changes from your UI.
```ts
grid.collaborativeEditing = {
documentId: `budget:${budgetId}`,
user: currentUser,
rowIdProp: 'id',
mode: 'review',
conflictPolicy: 'manual',
};
```
Read the plugin instance when your UI needs pending changes or review actions.
```ts
const plugins = await grid.getPlugins();
const collaborative = plugins.find(
plugin => plugin instanceof CollaborativeEditingPlugin,
) as CollaborativeEditingPlugin | undefined;
const pending = collaborative?.getPendingChanges() ?? [];
await collaborative?.commitPendingChanges(pending.map(change => change.id));
collaborative?.discardPendingChanges();
```
Review mode is a good default for finance, procurement, admin, support, planning, and any workflow where users should not silently overwrite each other.
### 4. Commit Changes To A Server
Use `commitAdapter` when your backend must validate, persist, approve, reject, version, or rewrite edits.
```ts
grid.collaborativeEditing = {
documentId: `forecast:${forecastId}`,
user: currentUser,
rowIdProp: 'id',
mode: 'autoCommit',
commitAdapter: {
async commit(changes, context) {
const response = await fetch(`/api/forecasts/${forecastId}/changes`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: context.documentId,
user: context.user,
changes,
}),
});
return response.json();
},
},
};
```
The response should describe which changes were committed and which were rejected. Rejected changes are rolled back to their previous local value.
```ts
[
{ id: 'change-1', status: 'committed' },
{ id: 'change-2', status: 'rejected', reason: 'Budget is locked' },
]
```
### 5. Add Business Permissions
Use `permissions.canEditCell` for user, role, row, tenant, or workflow-specific edit rules.
```ts
grid.collaborativeEditing = {
documentId: `pricing:${priceBookId}`,
user: {
id: currentUser.id,
name: currentUser.name,
roles: currentUser.roles,
permissionLevel: currentUser.permissionLevel,
},
rowIdProp: 'sku',
permissions: {
canEditCell({ user, row, prop, newValue }) {
if (row?.locked) return false;
if (prop === 'approvedPrice') return user.roles?.includes('pricing-admin') ?? false;
if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager';
return user.permissionLevel === 'editor';
},
},
};
```
Client permissions improve UX and prevent obvious invalid edits early. They are not a security boundary; repeat important checks in your backend through `commitAdapter`.
## Connect More Than One Browser
A shared local `Y.Doc` is enough for demos and same-page tests. For real users in different browsers, add a transport.
```ts
import {
createYjsWebSocketTransport,
} from '@revolist/revogrid-collaborative-editing';
grid.collaborativeEditing = {
documentId: `project:${projectId}:tasks`,
user: currentUser,
rowIdProp: 'id',
transport: createYjsWebSocketTransport({
url: 'wss://collaboration.example.com',
params: { token: sessionToken },
}),
};
```
Use WebSocket when your product needs a controlled server, authentication, observability, and enterprise networking. Use WebRTC for peer-to-peer collaboration where your deployment can support signaling and peer connectivity. Use a custom transport when your application already wraps Yjs providers.
## Listen For Conflicts
The default `conflictPolicy` is `manual`. When two users edit the same row and column concurrently, the plugin emits `collaborativeconflict` and keeps the local change pending.
```ts
grid.addEventListener('collaborativeconflict', (event) => {
const conflict = event.detail;
showConflictDialog({
rowId: conflict.local.rowId,
column: conflict.local.prop,
localValue: conflict.local.newValue,
remoteValue: conflict.remoteValue,
user: conflict.remoteUser,
});
});
```
Set `conflictPolicy: 'lastWriteWins'` only for low-risk cells such as notes, draft comments, or transient planning values where silent overwrite is acceptable.
## Configuration Reference
### Business-Critical Properties
| Property | Type | Default | First-time guidance |
|:--|:--|:--|:--|
| `documentId` | `string` | `'revogrid-collaborative-document'` | The shared session name. Use the same value for every user editing the same business object, such as `budget:2026` or `project:alpha:tasks`. |
| `user` | `CollaborativeEditingUser` | `undefined` | The current editor. Provide at least `id` and `name`; without a user, local edits are ignored because edits must be attributable. |
| `rowIdProp` | `string` | `'id'` | The row field that uniquely identifies the business record. Use your real primary key, such as `invoiceId`, `sku`, `taskId`, or `employeeId`. |
| `commitAdapter` | `CollaborativeEditingCommitAdapter` | `undefined` | Your backend boundary. Add it when changes must be saved, validated, approved, rejected, versioned, or audited by a server. |
| `permissions` | `CollaborativeEditingPermissions` | `undefined` | Client-side business rules. Use this for role checks, locked rows, approval status, tenant rules, or field-level permissions. |
| `audit` | `CollaborativeEditingAuditConfig \| boolean` | enabled | Audit integration. Keep it enabled for operational, financial, admin, support, or regulated workflows. |
| `conflictPolicy` | `'manual' \| 'lastWriteWins'` | `'manual'` | How same-cell concurrent edits are handled. Keep `manual` for business data. |
### Runtime And Collaboration Properties
| Property | Type | Default | First-time guidance |
|:--|:--|:--|:--|
| `enabled` | `boolean` | `true` | Turns collaboration on or off without changing the plugin list. Useful for feature flags or read-only screens. |
| `doc` | `Y.Doc` | new isolated document | Pass this when your app owns the Yjs document or when several grids should share one local document. |
| `mode` | `'autoCommit' \| 'review'` | `'autoCommit'` | Use `autoCommit` for immediate server reconciliation. Use `review` when users need commit and discard buttons. |
| `transport` | `CollaborativeEditingTransport` | `undefined` | Connects different browsers. Without transport, collaboration is local to the current page or shared `doc`. |
| `presence` | `{ enabled, showLabels, staleAfterMs }` | enabled, labels shown | Controls the bridge into `CollaborativePresencePlugin`. Use it when users should see who is focused or editing. |
### `user`
`user` tells the plugin who is editing. It is used for presence labels, permission checks, commit requests, audit records, and conflict metadata.
```ts
const user = {
id: 'u-123',
name: 'Avery Stone',
email: 'avery@example.com',
initials: 'AS',
color: '#2563eb',
roles: ['planner', 'budget-editor'],
permissionLevel: 'editor',
};
```
Use a stable application user id. Avoid session ids or display names as `id`, because audit history and server reconciliation need durable identity.
### `documentId`
`documentId` means "which shared document is this grid editing?"
```ts
documentId: `forecast:${forecastId}`
documentId: `project:${projectId}:schedule`
documentId: `tenant:${tenantId}:price-book:${priceBookId}`
```
Use the same `documentId` for every client editing the same dataset. Use a different `documentId` when two grids should not share edits or presence.
### `rowIdProp`
Collaborative editing stores cell changes by stable row identity. If `rowIdProp` is wrong, edits may apply to the wrong row after sorting or filtering.
```ts
const rows = [
{ invoiceId: 'INV-1001', status: 'Draft', total: 1200 },
];
grid.collaborativeEditing = {
documentId: 'invoices:open',
user,
rowIdProp: 'invoiceId',
};
```
Use `id` only when every row really has a durable `id`. For generated, aggregated, or temporary rows, provide a stable key before enabling collaboration.
### `commitAdapter`
`commitAdapter` means "ask my backend whether these edits are accepted."
```ts
commitAdapter: {
async commit(changes, context) {
const response = await fetch('/api/reconcile-grid-changes', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: context.documentId,
user: context.user,
changes,
}),
});
return response.json() as Promise;
}>>;
},
}
```
Return `committed` when the server accepts a change. Return `rejected` when validation, permissions, workflow status, version checks, or business rules fail.
### `permissions`
`permissions.canEditCell` runs before a local edit is accepted. It complements RevoGrid's `readonly` support:
- use column `readonly` for static UI-level rules
- use `permissions.canEditCell` for user, row, tenant, or workflow-specific rules
```ts
permissions: {
canEditCell({ user, row, prop, newValue }) {
if (row?.approvalStatus === 'Approved') return false;
if (prop === 'budgetLimit') return user.roles?.includes('finance-admin') ?? false;
if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager';
return user.permissionLevel === 'editor';
},
}
```
### `audit`
When `AuditHistoryPlugin` is registered, collaboration commits are recorded as audit events by default.
```ts
audit: {
enabled: true,
actionType: 'collaborative-price-change',
source: 'api',
}
```
Disable audit only for temporary or non-business data:
```ts
audit: false
```
## Commit Modes
Use `mode: 'autoCommit'` when the server should accept or reject each edit as soon as it is made.
Use `mode: 'review'` when users need an explicit commit/discard workflow. In review mode, edits are optimistic locally and remain in `getPendingChanges()` until the host commits or discards them through the plugin instance.
## Audit Trail
When `AuditHistoryPlugin` is registered and `audit.enabled !== false`, committed collaborative changes call `AuditHistoryPlugin.recordEvent()` with:
- `type: 'collaborative-edit'`
- current collaborative user
- transaction id
- row id, row index, column, old value, and new value
- collaborative metadata and document id
Use the Audit History storage hooks to persist those records to your backend.
## Common First-Time Mistakes
| Mistake | What happens | Fix |
|:--|:--|:--|
| Missing `EventManagerPlugin` | Edits do not enter the normalized collaborative edit pipeline. | Register `EventManagerPlugin` before `CollaborativeEditingPlugin`. |
| Missing `user` | Local edits are ignored because the plugin cannot attribute them. | Pass a stable `user` with at least `id` and `name`. |
| Missing or unstable `rowIdProp` | Edits can target the wrong row after sorting, filtering, or reordering. | Use a durable business id field. |
| Different `documentId` values | Users do not share the same collaborative document. | Use one stable `documentId` for every client in the same session. |
| Expecting automatic persistence | Local/Yjs state changes, but your backend is not updated. | Add a `commitAdapter`. |
| Expecting visible labels without presence plugin | Editing works, but remote cursors and ranges do not render. | Add `CollaborativePresencePlugin` and enable `presence`. |
## What To Read Next
- [Collaborative Presence](/guides/data-manage/collaborative-presence/) for remote cursors, selections, labels, stale users, and pinned-area behavior.
- [Audit Trail History](/guides/data-manage/audit-history/) for persisted change logs and restore workflows.
- [Event Manager](/guides/data-manage/event-manager-explanation/) for the edit event pipeline collaborative editing uses.
---
# Collaborative Presence
URL: https://pro.rv-grid.com/guides/data-manage/collaborative-presence/
Source: src/content/docs/guides/data-manage/collaborative-presence.mdx
Description: Render remote collaborator focus cells, selected ranges, labels, colors, stale-user cleanup, and source-index row patches in RevoGrid Pro.
`CollaborativePresencePlugin` shows where other users are looking or editing inside the grid. It renders colored focus rectangles, selected ranges, avatars, and user labels without changing the local user's focus, selection, editor, data history, or keyboard state.
Use it by itself when your app already has presence data, or use it with [Collaborative Editing](/guides/data-manage/collaborative-editing/) when Yjs awareness should drive remote cursors and ranges automatically.
## What It Does
Show the single cell another user is focused on.
Show another user's selected or active range.
Render initials, names, and stable colors so users can identify each other quickly.
Resolve marker positions from RevoGrid viewport state, including pinned columns, instead of querying physical cell DOM nodes.
Presence markers are overlays. They do not move local focus, create local selections, open editors, or persist data by themselves.
## Basic Setup
1. Import and register the plugin.
```ts
import {
CollaborativePresencePlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
CollaborativePresencePlugin,
];
```
2. Provide users through `grid.collaborativePresence`.
```ts
grid.collaborativePresence = {
users: [
{
id: 'avery',
name: 'Avery Stone',
initials: 'AS',
color: '#2563eb',
activity: 'editing',
focus: { x: 0, y: 1 },
range: { x: 0, y: 1, x1: 2, y1: 3 },
lastActiveAt: Date.now(),
},
{
id: 'grace',
name: 'Grace Hopper',
initials: 'GH',
color: '#16a34a',
activity: 'viewing',
focus: { x: 1, y: 0 },
lastActiveAt: Date.now(),
},
],
};
```
The plugin observes `grid.collaborativePresence`, `grid['collaborative-presence']`, and `additionalData.collaborativePresence`.
## Cell And Range Coordinates
Presence coordinates use grid source indexes:
- `x` is the source column index in the target column area.
- `y` is the source row index in the target row area.
- `x1` and `y1` are the opposite range corner.
- `colType` defaults to `'rgCol'`.
- `rowType` defaults to `'rgRow'`.
```ts
grid.collaborativePresence = {
users: [
{
id: 'avery',
name: 'Avery Stone',
color: '#2563eb',
focus: { x: 0, y: 2 },
range: { x: 0, y: 2, x1: 3, y1: 4 },
},
],
};
```
For pinned columns, pass the matching column type. This keeps the marker in the pinned viewport instead of the main viewport.
```ts
grid.collaborativePresence = {
users: [
{
id: 'grace',
name: 'Grace Hopper',
color: '#16a34a',
focus: {
x: 0,
y: 1,
colType: 'colPinStart',
rowType: 'rgRow',
},
},
],
};
```
The plugin resolves source indexes through RevoGrid stores and viewport structures. This is important when sorting, filtering, pinning, or virtualization changes which physical cells are mounted.
## Configuration Reference
| Property | Type | Default | What it controls |
|:--|:--|:--|:--|
| `enabled` | `boolean` | `true` | Turns all presence markers on or off without removing the plugin. |
| `showLabels` | `boolean` | `true` | Shows or hides user name labels. Focus and range colors still render when labels are hidden. |
| `staleAfterMs` | `number` | `undefined` | Hides users whose `lastActiveAt` is older than the configured timeout. |
| `users` | `CollaborativePresenceUser[]` | `[]` | The remote users to render. Each user can provide `focus`, `range`, or both. |
| `remoteEdits` | `CollaborativePresenceRowPatch[]` | `[]` | Optional source-row patches for remote data changes that should update grid rows without resetting focus or selection. |
## User Shape
```ts
type CollaborativePresenceUser = {
/**
* Stable user id used to distinguish one collaborator from another.
* Use your application user id, not a temporary session id or display name.
*/
id: string;
/**
* Human-readable name shown in the presence label when `showLabels` is true.
*/
name: string;
/**
* Optional short avatar text. If omitted, the label can derive initials from `name`.
*/
initials?: string;
/**
* Optional marker color used for focus borders, range overlays, avatar, and label.
* Keep this stable per user so collaborators are easy to recognize.
*/
color?: string;
/**
* Optional activity state for your own presence model.
* The plugin accepts viewing, editing, and idle states.
*/
activity?: 'viewing' | 'editing' | 'idle';
/**
* Optional focused cell for this user.
* `x` and `y` are source indexes in the specified column and row areas.
* Set this to `null` when the user has no active focus to render.
*/
focus?: {
/** Source column index inside `colType`. */
x: number;
/** Source row index inside `rowType`. */
y: number;
/**
* Column area that owns the focused cell.
* Defaults to `rgCol`; use pinned column types for pinned cells.
*/
colType?: 'colPinStart' | 'rgCol' | 'colPinEnd';
/**
* Row area that owns the focused cell.
* Defaults to `rgRow`; use pinned row types for pinned rows.
*/
rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd';
} | null;
/**
* Optional selected or active range for this user.
* `x`, `y` and `x1`, `y1` are opposite corners in source indexes.
* Set this to `null` when the user has no range to render.
*/
range?: {
/** Source column index for one range corner. */
x: number;
/** Source row index for one range corner. */
y: number;
/** Source column index for the opposite range corner. */
x1: number;
/** Source row index for the opposite range corner. */
y1: number;
/**
* Column area that owns the range.
* Defaults to `rgCol`; use pinned column types for pinned ranges.
*/
colType?: 'colPinStart' | 'rgCol' | 'colPinEnd';
/**
* Row area that owns the range.
* Defaults to `rgRow`; use pinned row types for pinned ranges.
*/
rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd';
} | null;
/**
* Last known activity time for stale-user cleanup.
* Accepts a timestamp, date string, or Date object.
* Used only when `staleAfterMs` is configured.
*/
lastActiveAt?: number | string | Date;
};
```
Use stable `id` values and consistent colors so the same person looks the same across grids and sessions.
## Labels And Stale Users
Use `showLabels: false` for dense grids where full labels would cover too much content.
```ts
grid.collaborativePresence = {
showLabels: false,
users,
};
```
Use `staleAfterMs` to hide users who disconnect or stop sending presence updates.
```ts
grid.collaborativePresence = {
staleAfterMs: 15_000,
users: remoteUsers.map(user => ({
...user,
lastActiveAt: user.lastSeenAt,
})),
};
```
## Remote Row Patches
`remoteEdits` can apply remote row updates directly into the RevoGrid data store without resetting the full source or disturbing the local editor state.
```ts
grid.collaborativePresence = {
remoteEdits: [
{
rowIndex: 2,
rowType: 'rgRow',
data: {
status: 'Approved',
owner: 'Grace',
},
},
],
users,
};
```
`rowIndex` is a source row index, not the currently visible row position. That keeps patches stable when the grid is sorted or filtered.
## With Collaborative Editing
When `CollaborativeEditingPlugin` has presence enabled, it maps Yjs awareness state into `CollaborativePresencePlugin`.
```ts
grid.plugins = [
EventManagerPlugin,
CollaborativePresencePlugin,
CollaborativeEditingPlugin,
];
grid.collaborativeEditing = {
documentId: `budget:${budgetId}`,
user: currentUser,
rowIdProp: 'id',
presence: {
enabled: true,
showLabels: true,
staleAfterMs: 15_000,
},
};
```
Use direct `grid.collaborativePresence` updates when your app owns the realtime presence source. Use collaborative editing presence when Yjs awareness owns the realtime presence source.
---
# Event Manager
URL: https://pro.rv-grid.com/guides/data-manage/event-manager-explanation/
Source: src/content/docs/guides/data-manage/event-manager-explanation.mdx
Description: Get comprehensive documentation on the event management.
Learn how to handle, customize, and optimize events to create complex interactions and workflows with the Event Manager Plugin for RevoGrid. The main purpose of this plugin is to collect and dispatch edit events in one centralized location, simplifying event management.
The Event Manager Plugin aggregates various editing events into a single event, `gridedit`. This approach reduces the complexity of handling multiple event types individually, such as `clipboardrangepaste`, `beforeedit`, and `beforerangeedit`. By using the Event Manager Plugin, you can catch all these events through one unified event, making it a robust starting point for building your custom event management system.
## Key Features
- **Centralized Event Handling:** Collects multiple edit events into a single `gridedit` event.
- **Simplified Event Management:** Reduces the need to manage numerous event types individually.
- **Customizable Workflows:** Provides a foundation to create complex interactions and workflows.
## Unified payload
`data`, `previousData`, and `models` always use RevoGrid's normal numeric-row
and field-patch format. Feature plugins can add semantic information through
the ordered `domainChanges` array without changing that grid-data contract.
Replay-only feature state is kept separately in keyed `history` snapshots, so
internal undo data never appears as a synthetic grid field.
For example, a Kanban move can expose a normal `status` patch in `data`, identify
the operation as a card move in `domainChanges`, and keep its compact undo state
in `history`.
```ts
grid.addEventListener('gridedit', (event) => {
const { data, previousData, domainChanges = [], eventTypes } = event.detail;
// Common RevoGrid patches, regardless of which feature produced the edit.
saveGridChanges({ data, previousData });
// Optional feature semantics. One transaction may contain several entries.
for (const change of domainChanges) {
if (change.type === 'kanban-card') {
const { action, cardId, previousCard, card } = change.detail;
saveCardChange({ action, cardId, previousCard, card });
}
}
if (eventTypes.includes('kanban-card-change')) {
refreshKanbanSummary();
}
});
```
The currently exported domain types and tags are:
| Feature | Domain type | `eventTypes` tag |
| --- | --- | --- |
| Gantt task | `gantt-task` | `gantt-task-change` |
| Gantt dependency | `gantt-dependency` | `gantt-dependency-change` |
| Gantt assignment | `gantt-assignment` | `gantt-assignment-change` |
| Kanban card | `kanban-card` | `kanban-card-change` |
| Scheduler event | `event-scheduler-event` | `event-scheduler-event-change` |
Domain changes are ordered. Bulk operations and scheduling cascades include all
affected entities, not only the item directly manipulated. Structural changes
can have no safe numeric row patch, so `data` may be empty while
`domainChanges` still fully describes the committed operation.
Use feature-specific types when you only consume one producer:
```ts
import type { KanbanEventManagerEvent } from '@revolist/kanban';
interface MyCard {
id: string;
status: string;
}
grid.addEventListener('gridedit', (event) => {
const detail = event.detail as KanbanEventManagerEvent;
detail.domainChanges?.forEach(({ detail: cardChange }) => {
console.log(cardChange.action, cardChange.cardId);
});
});
```
### Cancellation and source ownership
For ordinary grid edits, EventManager owns applying `data`; canceling
`gridedit` prevents that write. A feature event with
`sourceMutation: 'producer'` was already committed by Gantt, Kanban, or
Scheduler. Its `gridedit` is therefore observational and canceling it does not
roll the mutation back. Use the producer's cancelable `before` event for
validation instead.
When collection is enabled, consecutive compatible changes become one ordered
`gridedit` transaction and one undo step:
```ts
grid.eventManager = {
collectEvents: true,
collectEventsDelay: 100,
};
```
The transaction preserves the earliest previous state and latest committed
state for each entity. Do not rely on exact equality of `eventTypes`; use
`eventTypes.includes(...)` because a transaction may contain several tags.
Plugins that submit producer-owned history must also handle `beforehistoryedit`.
After successfully restoring their state, they call
`markEventManagerHistoryReplayHandled(detail, ownedHistoryKeys)` and cancel
that replay event.
This keeps malformed or unavailable producer history on its current undo/redo
stack instead of silently consuming it. Applications normally do not need to
read `history`; it is replay state owned by `HistoryPlugin` and the producer.
## Implementation
Here's a brief overview of how to implement the Event Manager Plugin in your RevoGrid setup.
### Step-by-Step Guide
1. **Import Required Modules:**
Import the necessary modules from the RevoGrid library to start setting up the Event Manager Plugin.
2. **Define Edit Details Type:**
Create a type for edit details that will help structure the event data.
3. **Create EventManagerPlugin Class:**
Develop the `EventManagerPlugin` class to handle and dispatch edit events. This class listens for various edit events and dispatches a unified `gridedit` event.
- **Collect Edit Events:** Listens for `clipboardrangepaste`, `beforeedit`, and `beforerangeedit` events.
- **Dispatch Unified Event:** Dispatches the `gridedit` event after collecting details from the various edit events.
4. **Add Plugin to RevoGrid:**
Attach the Event Manager Plugin to your RevoGrid instance and start listening for the `gridedit` event.
```javascript
...
grid.plugins = [EventManagerPlugin];
...
```
For detailed implementation, please refer to the full code below.
The Event Manager Plugin is a powerful tool for managing edit events.
By consolidating multiple event types into a single, manageable event, it simplifies event handling and provides a solid foundation for developing custom workflows and interactions.
---
# Grouping with Aggregation
URL: https://pro.rv-grid.com/guides/data-manage/grouping-aggregation/
Source: src/content/docs/guides/data-manage/grouping-aggregation.mdx
Description: Learn how to implement data grouping with custom aggregation functions in RevoGrid
## Overview
The Grouping with Aggregation feature allows you to organize your data into hierarchical groups and display custom aggregated values for each group. This is particularly useful for displaying summary statistics, totals, averages, or custom calculations for grouped data.
## Key Features
- **Hierarchical Grouping**: Group data by multiple columns in a tree-like structure
- **Custom Aggregations**: Define custom aggregation functions for different data types
- **Visual Indicators**: Expandable/collapsible groups with aggregation values displayed
- **Flexible Templates**: Customize how group headers and aggregations are displayed
## Basic Usage
## Implementation
### 1. Import Required Components
```typescript
import { groupingAggregation } from '@revolist/revogrid-pro';
```
### 2. Define Aggregation Functions
Create custom aggregation functions for different columns. **Important**: Aggregation functions receive an array of complete data objects, not just the column values. You need to extract the specific property you want to aggregate:
```typescript
const aggregations = {
price: (values: any[]) => {
const prices = values.map(item => item.price);
const sum = prices.reduce((acc, val) => acc + val, 0);
const avg = sum / prices.length;
return `Avg: $${avg.toFixed(2)}`;
},
quantity: (values: any[]) => {
const quantities = values.map(item => item.quantity);
const total = quantities.reduce((acc, val) => acc + val, 0);
return `Total: ${total}`;
},
product: (values: any[]) => {
return `${values.length} products`;
}
};
```
### 3. Create Group Template
Use the `groupingAggregation` function to create a template that displays aggregations:
```typescript
const groupTemplate = (h: any, props: any) => {
return groupingAggregation(h, props, aggregations);
};
```
### 4. Configure Columns
Apply the group template to columns that will be used for grouping:
```typescript
const columns = [
{ name: '🆔 ID', prop: 'id', size: 60 },
{ name: '📂 Category', prop: 'category', size: 120, template: groupTemplate },
{ name: '📁 Subcategory', prop: 'subcategory', size: 120, template: groupTemplate },
{ name: '📦 Product', prop: 'product', size: 150 },
{ name: '💰 Price', prop: 'price', size: 100 },
{ name: '📊 Quantity', prop: 'quantity', size: 100 },
{ name: '📅 Order Date', prop: 'orderDate', size: 120 },
{ name: '🚚 Delivery Date', prop: 'deliveryDate', size: 120 },
{ name: '👤 Customer', prop: 'customer', size: 150 },
{ name: '🌍 Region', prop: 'region', size: 100 },
{ name: '📋 Status', prop: 'status', size: 100 },
];
```
### 5. Set Up Grouping Configuration
Define which columns should be used for grouping:
```typescript
const grouping = {
props: ['orderDate', 'category', 'subcategory']
};
```
### 6. Initialize the Grid
```typescript
const grid = document.createElement('revo-grid');
grid.source = yourData;
grid.columns = columns;
grid.grouping = grouping;
// Grouping is a built-in feature, no plugin needed
```
## Aggregation Function Examples
### Numeric Aggregations
```typescript
// Average
price: (values: any[]) => {
const prices = values.map(item => item.price);
const avg = prices.reduce((acc, val) => acc + val, 0) / prices.length;
return `Avg: $${avg.toFixed(2)}`;
}
// Sum
quantity: (values: any[]) => {
const quantities = values.map(item => item.quantity);
const total = quantities.reduce((acc, val) => acc + val, 0);
return `Total: ${total}`;
}
// Min/Max
price: (values: any[]) => {
const prices = values.map(item => item.price);
const min = Math.min(...prices);
const max = Math.max(...prices);
return `Range: $${min} - $${max}`;
}
```
### String Aggregations
```typescript
// Count
product: (values: any[]) => {
return `${values.length} products`;
}
// Concatenation
names: (values: any[]) => {
const names = values.map(item => item.name);
return names.join(', ');
}
// Unique count
categories: (values: any[]) => {
const categories = values.map(item => item.category);
const unique = new Set(categories);
return `${unique.size} unique categories`;
}
```
### Date Aggregations
```typescript
// Date range
orderDate: (values: any[]) => {
const dates = values.map(item => new Date(item.orderDate));
const sorted = dates.sort((a, b) => a.getTime() - b.getTime());
const earliest = sorted[0].toLocaleDateString();
const latest = sorted[sorted.length - 1].toLocaleDateString();
return `${earliest} - ${latest}`;
}
```
## Advanced Configuration
### Custom Group Header Styling
You can customize the appearance of group headers by modifying the template function:
```typescript
const customGroupTemplate = (h: any, props: any) => {
const aggregationValue = getAggregationValue(props);
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '4px 8px',
backgroundColor: '#f0f0f0',
borderRadius: '4px'
}
}, [
h('span', { style: { fontWeight: 'bold' } }, props.name),
h('span', { style: { fontSize: '12px', color: '#666' } }, `(${aggregationValue})`)
]);
};
```
### Conditional Aggregations
You can apply different aggregation logic based on the column or group:
```typescript
const conditionalAggregations = {
price: (values: any[]) => {
const prices = values.map(item => item.price);
const sum = prices.reduce((a, b) => a + b, 0);
const avg = sum / prices.length;
return `Avg: $${avg.toFixed(2)}`;
}
};
```
## Best Practices
1. **Performance**: Keep aggregation functions lightweight, especially with large datasets
2. **User Experience**: Provide clear, meaningful aggregation labels
3. **Data Types**: Ensure aggregation functions handle the correct data types
4. **Error Handling**: Add validation for edge cases (empty arrays, null values)
5. **Accessibility**: Use descriptive text for screen readers
## Common Use Cases
- **Sales Reports**: Group by region/category with revenue totals
- **Inventory Management**: Group by department with stock quantities
- **Financial Data**: Group by account type with balance summaries
- **Project Management**: Group by team with task counts and completion rates
---
# History Undo/Redo
URL: https://pro.rv-grid.com/guides/data-manage/history/
Source: src/content/docs/guides/data-manage/history.mdx
Description: History and undo and redo capabilities in data grid, making it easier to manage user edits
The History Plugin brings powerful undo and redo capabilities to your data grid, making it easier to manage user edits and maintain data integrity.
Keyboard shortcuts like `Ctrl+Z` (or `Cmd+Z` on macOS) for undo and `Ctrl+Y` (or `Cmd+Y` on macOS) / or `Ctrl+Shift+Z` (or `Cmd+Shift+Z` on macOS) for redo are supported out of the box.
Shortcuts work from document keydown events by default, even when the grid does not currently have a focused cell. Set `keyboardShortcutsWithoutFocus: false` to require grid focus.
You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks.
- Undo/Redo Management: Automatically tracks user changes with the ability to undo/redo actions using keyboard shortcuts.
- Configurable Stack Size: Tracks up to 200 changes by default, and accepts partial `history` config when you only need to override one option.
- Provider Persistence: Can load and save stack state through local cache and custom server providers for same-dataset sessions.
- Custom Event Hooks: Supports custom behavior via beforeundo and beforeredo events, allowing full control over the undo/redo process.
- Pro Editing Coverage: Tracks regular cell edits, Pro editor templates, textarea editors, range edits, clipboard paste, autofill, row-edit saves, and audit restore replays. `HistoryPlugin` installs its Event Manager dependency automatically.
- Context-menu Transactions: Tracks each built-in Cut, Paste, Clear, row mutation, or column-schema mutation as one chronological undo/redo action. Copy remains non-mutating and is not recorded.
### Additional details
Register `HistoryPlugin` to enable undo/redo. It automatically installs
`EventManagerPlugin` when an Event Manager instance is not already present, so
normal edit events enter the undo stack without a second undocumented plugin.
```ts
import {
HistoryPlugin,
createLocalStorageHistoryAdapter,
} from '@revolist/revogrid-pro';
plugins = [HistoryPlugin];
```
Undo, redo, and public replay operations emit `beforehistedit`. The Event
Manager installed by History handles that event and emits the normal `gridedit`
flow. If your application already registers `EventManagerPlugin`, History
reuses that instance rather than creating a duplicate.
`AuditHistoryPlugin` automatically reuses or installs `HistoryPlugin` for restore replay, keyboard shortcuts, and existing History integrations. Keep History configuration on `grid.history`; Audit History does not define a nested History config.
### Context-menu history
When `HistoryPlugin` and `DataGridContextMenuPlugin` are installed together,
each built-in mutating command is atomic: a multi-cell Cut, Paste, cell Clear,
or row Clear creates one history entry, and undo restores all affected cells in
reverse edit order. Redo reapplies them in forward order.
History also snapshots the affected authored structure for these commands:
- Rows: insert above/below, duplicate, delete, pin top/bottom, and unpin.
- Columns: insert left/right, duplicate, delete, pin left/right, unpin, and move
left/right/start/end.
Plain Copy and Copy with headers do not create entries because they do not
mutate the grid. A `commandHandlers` override is application-owned and is also
excluded: if a custom handler changes remote or application state, record and
restore that state in the application instead. Canceled, unavailable, and no-op
commands leave both stacks unchanged.
In-memory structural entries retain row object identity, nested column groups,
renderers, editors, and other function-valued column fields. Persisted entries
should use serializable row and column definitions; storage formats such as
JSON cannot restore functions. Context-menu replay temporarily bypasses
`clearOnSourceChange` only for its own row-source replacements. Unrelated
application source replacements continue to use the configured clearing policy.
`grid.history` is optional. Add it only when you need custom stacks, a smaller
or larger stack limit, or an initially disabled plugin.
```ts
grid.history = {
maxStackSize: 100,
keyboardShortcutsWithoutFocus: false,
};
```
### Persisting History
Undo and redo stacks are index-based, so persisted history is safe only when the user returns to the same dataset, row order, and compatible column schema. Structural context-menu entries additionally require serializable row and column definitions. For long-term recovery across changing data, use `AuditHistoryPlugin`, which restores by stable row identity.
Use `sourceId` to reject stale server states. With multiple providers, the plugin loads the first provider immediately, then applies later providers only when their `updatedAt` state is newer and their `sourceId` is compatible.
When storage is configured, `clearOnSourceChange` defaults to `false` so the initial data load does not erase the restored stack. Set it to `true` only when your app wants every source replacement to clear persisted undo/redo state.
```ts
grid.history = {
sourceId: `invoices:${workspaceId}:${datasetVersion}`,
storageProviders: [
createLocalStorageHistoryAdapter('invoices:history'),
{
async load() {
const response = await fetch('/api/grid-history');
return response.json();
},
async save(state) {
await fetch('/api/grid-history', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
});
},
async clear() {
await fetch('/api/grid-history', { method: 'DELETE' });
},
},
],
};
```
Listen to `historyerror` when you want to surface provider failures or skipped states:
```ts
grid.addEventListener('historyerror', (event) => {
console.warn(event.detail.phase, event.detail.error);
});
```
To call `undo()`, `redo()`, `clear()`, `disable()`, or state helpers, get plugin instances
from the grid and find the `HistoryPlugin` instance.
```ts
async undo() {
// get plugins from the grid
const plugins = await this.gridRefNativeElement.getPlugins();
const history = plugins.find((plugin) => plugin instanceof HistoryPlugin);
if (history) {
if (history.canUndo()) {
history.undo();
}
}
}
```
For custom controls, listen to `historychanged` instead of reading plugin
internals. The event detail includes `undoStackSize`, `redoStackSize`,
`canUndo`, `canRedo`, and `disabled`.
### Row reorder history
When `HistoryPlugin` and `RowOrderPlugin` are installed together, every
successful row drop is one undo/redo action by default. This includes selected
row blocks and grouped drops that update group fields.
```ts
grid.history = {
rowOrderHistory: 'track', // default
};
```
Use `rowOrderHistory: 'clear'` to discard existing history after a committed
drop, or `rowOrderHistory: 'preserve'` when another integration records a richer
domain snapshot. Gantt uses `preserve` and records canonical tasks and
dependencies together.
### Troubleshooting
- Undo/redo does not record changes: ensure `HistoryPlugin` is present in the
grid's `plugins` array. Event Manager setup and keyboard shortcuts are
automatic.
- Restore flashes are missing in Audit History: install `EventManagerPlugin` and `CellFlashPlugin`; Audit restore replay flows through `beforehistedit` and the normal edit event path.
- Calling plugin methods does not work: call `getPlugins()` first, then find
`HistoryPlugin`.
- History clears after sort, filter, or source replacement: this is intentional
because those operations can change visible row indexes. Successful row
reorders are tracked unless `rowOrderHistory` is `clear` or `preserve`.
- Persisted history was skipped: check the `historyerror` event for a
`storage-source-mismatch` phase and update the configured `sourceId`.
- Need a known-good reference: see code in:
`revogrid-pro/src/components/history/**.**`.
### Key methods
The History Plugin provides four key methods to enhance control over `undo/redo` functionality:
- `clear()`: Resets both the undo and redo stacks, clearing all recorded changes.
- `disable(disable = true)`: Temporarily disables the plugin, preventing changes from being recorded or undo/redo operations from being triggered.
- `undo()`: Reverts the last change recorded in the undo stack and moves it to the redo stack for potential reapplication.
- `redo()`: Reapplies the most recent change from the redo stack, moving it back to the undo stack for further management.
- `canUndo()` / `canRedo()`: Returns whether undo or redo is currently available.
- `getUndoStackSize()` / `getRedoStackSize()`: Returns stack counts for custom controls.
- `isDisabled()`: Returns whether history is disabled.
These methods give developers flexibility in managing user actions and tailoring the plugin’s behavior to specific application requirements.
### Behind the Scenes
The plugin listens to edit events (onEditEvent) and tracks changes in undo and redo stacks. Keyboard shortcuts like Ctrl+Z for undo and Ctrl+Y (or Ctrl+Shift+Z) for redo are supported out of the box. You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks.
Here’s a quick snippet showing how the plugin processes undo actions:
```ts
undo() {
if (this.undoStack.length === 0) return;
const lastChange = this.undoStack.pop();
if (lastChange) {
const event = this.emit(BEFORE_UNDO_EVENT, {
data: lastChange.previousData,
type: lastChange.type,
lastChange,
});
if (!event.defaultPrevented) {
const data = event.detail.data || lastChange.previousData;
this.replayChange({
data,
type: event.detail.type || lastChange.type,
models: lastChange.models,
previousData: lastChange.data,
});
this.redoStack.push(lastChange);
}
}
}
```
`replayChange()` emits `beforehistedit` first. If no integration handles that event, History falls back to `providers.data.setRangeData(...)`.
Try it Out!
Add the History Plugin to your RevoGrid instance and experience seamless undo/redo functionality. This plugin is especially useful in applications that handle complex data operations, ensuring users can easily revert or reapply changes as needed.
```javascript
// fixing render for multiframework
```
---
# Nested Grid
URL: https://pro.rv-grid.com/guides/data-manage/nested-grid/
Source: src/content/docs/guides/data-manage/nested-grid.mdx
Description: Learn how to use the Nested Grid to display hierarchical data within RevoGrid cells.
The Nested Grid allows you to display hierarchical data by embedding RevoGrids inside cells. This is particularly useful when you need to show detailed, structured data within a cell, such as project details, task lists, or any other nested data structure.
## Row Expansion
The Nested Grid combined with the Row Expansion plugin to provide a more interactive experience. Row expansion allows users to expand a row to reveal additional details or nested grids.
### Enabling Row Expansion
To enable row expansion, you can configure the grid as follows:
```typescript
import { RowExpandPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowExpandPlugin];
```
### Example Configuration
Here's an example of how to set up row expansion with nested grids:
```typescript
grid.columns = [
{ prop: 'id', name: 'ID', expand: true }, // Enable row expansion for this column
{ prop: 'name', name: 'Name' },
{
prop: 'details',
name: 'Details',
}
];
// Additional configuration for row expansion
grid.additionalData = {
rowExpand: {
expandedRows: new Set([1]), // Example of expanded rows
expandedRowHeight: 200, // Height for the expanded row
// Hide expand toggle based on row data
hideExpand: (rowIndex, model) => {
// Example: Hide expand toggle for rows without tasks
return !model.tasks || model.tasks.length === 0;
},
template: (h, props) => {
return h('revo-grid', {
key: 'nested-grid',
class: 'mt-1',
style: {
height: '100%',
maxHeight: '90%',
maxWidth: '95%',
},
theme: isDark() ? 'darkCompact' : 'compact',
resize: true,
readonly: true,
columns: [
{ prop: 'id', name: '#', size: 50 },
{ prop: 'task', name: 'Task', size: 220 },
{
prop: 'status',
name: 'Status',
size: 150,
cellProperties: ({ value }) => ({
style: {
color: '#fff',
backgroundColor: value === 'Complete' ? '#00c874' : value === 'In Progress' ? '#fdaa3d' : '#e2435c',
},
}),
},
],
source: props.model.tasks, // Source data for the nested grid
hideAttribution: true,
});
},
},
};
```
## Configuration Options
The nested grid configuration supports all standard RevoGrid properties. Here are some commonly used options:
- `columns`: Array of column definitions for the nested grid
- `source`: Function that returns the data array for the nested grid
- `theme`: Theme to apply to nested grids
- `readonly`: Whether the nested grid should be read-only
- `rowSize`: Height of rows in the nested grid
- `resize`: Enable column resizing in nested grid
- Any other RevoGrid properties you want to apply to nested grids
### Row Expansion Options
The row expansion configuration supports several options to control expansion behavior:
- `expandedRows`: Set of row IDs that should be expanded by default
- `expandedRowHeight`: Height for expanded rows (in pixels)
- `hideExpand`: Controls visibility of expand toggle buttons, can be:
- A boolean: `true` to hide all expand toggles
- A function: `(rowIndex: number, model: any) => boolean` for conditional control
Example of conditional expand toggle visibility:
```typescript
grid.additionalData = {
rowExpand: {
// Hide expand toggle for rows that don't have nested data
hideExpand: (rowIndex, model) => {
return !model.hasNestedData || model.tasks?.length === 0;
},
// ... other configuration options
}
};
```
## Best Practices
1. **Data Structure**: Organize your data hierarchically with clear parent-child relationships.
2. **Performance**: Consider the following for optimal performance:
- Limit the number of nested grids visible at once
- Keep nested data sets reasonably sized
- Use read-only mode for nested grids if editing isn't required
3. **Responsive Design**: Ensure columns with nested grids have sufficient width.
4. **User Experience**: Use row expansion judiciously to enhance user interaction without overwhelming them with too much information at once.
## Advanced Usage
### Custom Cell Templates
You can combine nested grids with custom cell templates for rich data visualization:
```typescript
{
prop: 'progress',
name: 'Progress',
cellTemplate: (h, props) => {
const progress = props.model[props.prop] || 0;
return h('div', {
style: {
width: '100%',
height: '100%'
}
}, [
h('div', {
style: {
width: `${progress}%`,
backgroundColor: progress === 100 ? '#4CAF50' : '#2196F3'
}
})
]);
}
}
```
---
# Row Expand Plugin
URL: https://pro.rv-grid.com/guides/data-manage/row-expand/
Source: src/content/docs/guides/data-manage/row-expand.mdx
Description: Learn how to use RowExpandPlugin for inline expandable row details.
The `RowExpandPlugin` adds inline expandable detail rows to RevoGrid. It is useful when a row needs a compact
secondary area for a form, nested grid, notes, preview content, or any row-local custom template.
Unlike the [Master Rows](/guides/data-manage/row-master/) feature, `RowExpandPlugin` does not render a
master-detail overlay. It inserts an expanded row into the regular row viewport and renders your
`rowExpand.template` inside that row.
Use `RowExpandPlugin` for inline detail rows that should stay in the normal grid flow. Use
[`MasterRowPlugin`](/guides/data-manage/row-master/) when the detail area must span the grid, stay aligned
through overlay rendering, or work as a full master-detail view across pinned areas.
## Basic Setup
Import the plugin and add it to the grid:
```ts
import { RowExpandPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [RowExpandPlugin];
```
Add `expand: true` to the column that should render the expand toggle:
```ts
grid.columns = [
{ prop: 'name', name: 'Name', expand: true },
{ prop: 'status', name: 'Status' },
];
```
Configure expanded row behavior with `grid.rowExpand`:
```ts
grid.rowExpand = {
expandedRows: new Set([0]),
expandedRowHeight: 220,
template: (h, props) =>
h('div', { class: 'row-detail' }, [
h('strong', null, props.model.name),
h('p', null, props.model.description),
]),
};
```
## Configuration
| Option | Type | Description |
| ------ | ---- | ----------- |
| `expandedRows` | `Set` | Physical row indexes that should be expanded by default. |
| `expandedRowHeight` | `number` | Height, in pixels, for each expanded detail row. |
| `hideExpand` | `boolean \| ((rowIndex: number, model: any) => boolean)` | Hides the expand toggle for all rows or for specific rows. Return `true` to hide the toggle. |
| `template` | `(h: any, props: any) => VNode` | Renders the expanded row content. The template receives the row model and render context. |
`additionalData.rowExpand` is still supported for compatibility, but prefer the direct `grid.rowExpand`
property.
## Programmatic Expansion
The plugin listens for row expansion events:
```ts
import {
ROW_COLLAPSE,
ROW_COLLAPSE_ALL,
ROW_EXPAND,
ROW_EXPAND_ALL,
} from '@revolist/revogrid-pro';
grid.dispatchEvent(new CustomEvent(ROW_EXPAND, { detail: { rowIndex: 0 } }));
grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE, { detail: { rowIndex: 0 } }));
grid.dispatchEvent(new CustomEvent(ROW_EXPAND_ALL));
grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE_ALL));
```
The `rowIndex` value is the virtual row index at the time the event is dispatched.
## Difference From Master Rows
`RowExpandPlugin` and `MasterRowPlugin` both expose expandable row experiences, but their rendering models are
different:
| Capability | RowExpandPlugin | MasterRowPlugin |
| ---------- | --------------- | --------------- |
| Rendering model | Inline expanded row in the regular row viewport | Overlay-rendered master-detail row |
| Required plugins | `RowExpandPlugin` | `MasterRowPlugin` (`OverlayPlugin` is auto-installed) |
| Toggle setup | Set `expand: true` on a column | Use `EXPAND_COLUMN` or dispatch `ROW_MASTER` |
| Best for | Nested grids, row forms, compact detail rows | Full-width master-detail content and pinned-area spanning layouts |
| Layout scope | Constrained by regular grid row and columns | Can span across the grid through overlay rendering |
For the overlay-based master-detail implementation, read the
[Master Rows guide](/guides/data-manage/row-master/) or the [Row Master API](/api/row-master/).
## Example Use Cases
- Inline edit forms, as shown in the [Form Edit example](/guides/form-edit/).
- Nested grids inside expanded rows, as shown in the [Nested Grid guide](/guides/data-manage/nested-grid/).
- Compact row detail panels that do not need to span pinned columns or the full grid.
---
# Master Rows
URL: https://pro.rv-grid.com/guides/data-manage/row-master/
Source: src/content/docs/guides/data-manage/row-master.mdx
Description: A guide to using the Master Row Plugin.
The **Master Row Plugin** enhances the [RevoGrid](https://rv-grid.com/) grid component by enabling the creation and management of master-detail rows.
It is an extremely **complex and advanced feature**, despite its seemingly simple nature. Read more below.
1. Dynamic Overlay Synchronization: The Master Row Plugin creates a dynamic overlay that tracks grid scrolling.
This ensures that master-detail information remains perfectly aligned and visible as you navigate through large datasets.
2. Expansive Master Detail Context: Unlike tree view, group view, or merge cell approaches, this plugin uniquely expands the master row context into the pinned area, allowing it to fill the entire grid. This capability provides a richer, more immersive data presentation and is exceptionally challenging to achieve.
3. Virtualized Performance: Despite the complexity of rendering an overlay that spans the grid, the layer remains virtualized. This ensures high performance and responsiveness, even when handling extensive and detailed master row content.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Configuration](#configuration)
- [Custom Templates](#custom-templates)
- [Programmatic Expand and Collapse](#programmatic-expand-and-collapse)
- [API Reference](#api-reference)
---
## Features
- **Expandable Rows:** Easily expand rows to reveal additional details.
- **Custom Templates:** Define how master rows are rendered using customizable templates.
---
## Basic Setup
1. **Import Necessary Plugins and Utilities:**
```javascript
import { EXPAND_COLUMN, MasterRowPlugin, CellColumnFocusVerifyPlugin } from '@revolist/revogrid-pro';
import type { RowMasterConfig } from '@revolist/revogrid';
import './row-master.style.css';
```
2. **Define Columns:**
```javascript
const columns: HTMLRevoGridElement['columns'] = [
{
prop: 'expand',
...EXPAND_COLUMN,
},
// ...
];
```
3. **Initialize the Grid with Plugins:**
```javascript
const grid = document.querySelector('revo-grid');
grid.source = makeData(10, 20);
grid.columns = columns;
// Define plugin
grid.plugins = [
MasterRowPlugin,
];
const masterRow: RowMasterConfig = {
rowHeight: 100,
template: (h, data) => {
return data.model.subRows?.map((subRow: Person) =>
h('span', { class: { 'master-row': true } }, [
h('img', { src: subRow.avatar, width: '20', height: '20' }),
`${subRow.firstName} ${subRow.lastName} `,
]),
);
},
};
grid.masterRow = masterRow;
}
```
### Configuration
The Master Row Plugin can be customized by assigning a `RowMasterConfig` object to `grid.masterRow`. The primary configuration option is the `template` function, which defines how the master row content is rendered.
#### RowMasterConfig
| Property | Type | Description |
| -------- | ---- | ----------- |
| `template` | `(h: Function, data: any) => JSX.Element` | A function that returns the JSX structure for the master row. It receives a rendering function `h` and the data context `data`. |
| `allowDetailEvents` | `readonly string[]` | Event names from the master detail area that are allowed to bubble to the parent grid. All RevoGrid events inside the detail area are stopped by default. |
### Custom Templates
Customize the master row by defining a `template` function that returns the desired JSX structure. This allows for flexible and dynamic rendering based on the row data.
```javascript
const masterRow: RowMasterConfig = {
template: (h, data) => {
return data.model.subRows?.map((subRow: Person) =>
h('div', { class: { 'custom-master-row': true } }, [
h('img', { src: subRow.avatar, width: '30', height: '30' }),
`${subRow.firstName} ${subRow.lastName}`,
]),
);
},
};
```
To let selected events from nested detail content reach the parent grid, add them by name:
```javascript
const masterRow: RowMasterConfig = {
allowDetailEvents: ['afteredit'],
template: (h, data) => h('revo-grid', { source: data.model.details }),
};
```
### Programmatic Expand and Collapse
`EXPAND_COLUMN` toggles a single master row by dispatching the `ROW_MASTER` event with the current cell template payload. You can dispatch the same event directly when a toolbar, external control, or custom cell template needs to control master-row state.
For one row, pass the same cell template payload shape used by cell templates. The plugin treats this as a toggle:
```ts
import { ROW_MASTER } from '@revolist/revogrid-pro';
grid.dispatchEvent(new CustomEvent(ROW_MASTER, {
detail: cellTemplateData,
}));
```
For multiple rows, pass a batch payload with a `rows` array. Each row target declares its desired final state with `expanded: true` or `expanded: false`, so one event can open and close different rows at the same time:
```ts
import { ROW_MASTER } from '@revolist/revogrid-pro';
import type { RowMasterBatchEvent } from '@revolist/revogrid-pro';
const detail: RowMasterBatchEvent = {
rows: [
{ rowType: 'rgRow', rowIndex: 1, expanded: true },
{ rowType: 'rgRow', rowIndex: 3, expanded: false },
{ rowType: 'rowPinStart', rowIndex: 0, expanded: true },
],
};
grid.dispatchEvent(new CustomEvent(ROW_MASTER, { detail }));
```
`rowIndex` is the current virtual row index for the given `rowType`. The plugin converts every target to a stable internal row reference before applying the batch, so one event can safely mix opened and closed rows without later changes shifting earlier targets. If multiple targets resolve to the same row, the last entry wins.
### Automatic Collapse
Expanded master rows are cleared when the grid is about to change row visibility or row structure in a way that can invalidate the detail overlay position.
The plugin calls its internal `clearAllRows()` cleanup when:
- the grid source is replaced
- filtering is about to trim rows through `beforefiltertrimmed`
- row dragging starts
During that cleanup, expanded master rows are collapsed, detail overlay nodes are removed, duplicated detail rows are removed from the row item lists, and custom detail row heights are cleared. This prevents a filtered or replaced row from leaving stale detail height behind after the row itself is no longer visible.
To stop this automatic cleanup for one of these moments, listen for `BEFORE_ROW_MASTER_COLLAPSE` and call `preventDefault()`:
```ts
import { BEFORE_ROW_MASTER_COLLAPSE } from '@revolist/revogrid-pro';
grid.addEventListener(BEFORE_ROW_MASTER_COLLAPSE, (event) => {
if (event.detail.reason === 'filter') {
event.preventDefault();
}
});
```
The event detail includes `reason` (`'source'`, `'filter'`, or `'drag'`) and `rows`, the expanded master rows that are about to be cleared.
Preventing the default cleanup means your application is responsible for handling any stale detail overlays or custom row heights if the related source row is no longer visible.
---
## API Reference
### MasterRowPlugin
A plugin class that manages master-detail rows within the RevoGrid component.
#### Methods
- **constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders)**
Initializes the plugin with the RevoGrid instance and plugin providers.
- **expandRow(type: DimensionRows, virtRowIndex: number)**
Expands a specific row to show its master details.
- **collapseRow(type: DimensionRows, rowVIndexes: number[])**
Collapses one or more rows, hiding their master details.
- **destroy()**
Cleans up event listeners and other resources when the plugin is destroyed.
#### Events
The plugin listens to various RevoGrid events to manage the state of master rows, including:
- `AFTER_GRID_RENDER_EVENT`
- `BEFORE_ROW_RENDER_EVENT`
- `BEFORE_ROW_MASTER_COLLAPSE` - cancelable event fired before automatic master-row cleanup collapses expanded rows.
- `ROW_MASTER` - toggles one row from a cell template payload, or applies a batch of desired row states with `{ rows: [{ rowType, rowIndex, expanded }] }`.
- `OVERLAY_NODE`
- ...and more.
---
# Tree Data Grid
URL: https://pro.rv-grid.com/guides/data-manage/tree/
Source: src/content/docs/guides/data-manage/tree.mdx
Description: Enhance your RevoGrid with hierarchical data visualization using the TreeData Plugin.
The **TreeData Plugin** transforms parent-child relationships data into a hierarchical tree structure and provides the ability to group rows by `parentId` and is optimized for the best performance, making it suitable for large datasets.
It enables features such as expandable rows, and level indicators, making it ideal for applications that handle nested data.
## Tree Plugin vs Key Grouping
RevoGrid has two different ways to show hierarchical rows, and they solve different problems:
- **Key grouping** uses the grid's built-in `grouping` configuration, for example `grid.grouping = { props: ['country', 'city'] }`. The grid reads one or more column values, creates synthetic group header rows for each matching key, and places source rows under those generated groups. Use it when the hierarchy should be calculated from repeated values such as category, region, status, or date.
- **TreeDataPlugin** uses explicit row relationships from your data, normally `id` and `parentId`. The plugin keeps your original rows as the tree nodes, computes tree metadata such as level, expanded state, and visibility, and trims collapsed descendants from the viewport. Use it when the hierarchy already exists in the data, such as folders, tasks and subtasks, organization charts, bill of materials, or nested records.
In short, key grouping answers "which rows share the same values?", while the tree plugin answers "which row is the parent of this row?". Key grouping is value-driven and creates group rows; tree data is relationship-driven and renders your existing rows as parent and child nodes.
It is an extremely **complex and advanced feature**, despite its seemingly simple nature.
1. **Group Rows by Parent ID**: build hierarchical structures without modifying the original data, ensuring smooth performance even with large datasets.
2. **Complicated Drag-and-Drop**: The plugin supports **drag-and-drop between parent nodes** and automatically updates the `parentId` of the moved rows.
3. **Advanced Proxy System**: The plugin uses an advanced **proxy system** to handle sorting and filtering while keeping track of hierarchical relationships. This ensures that **all other plugins** remain unaffected by tree view manipulations, allowing for seamless integration.
4. **Metadata Computation**: The plugin computes tree metadata (e.g., level, expanded state, visibility) without modifying the underlying data. It uses a **recursive approach** to compute tree levels and visibility based on the parent-child relationships, and it leverages **row trimming** to optimize performance without altering the source data.
5. **Crucial for Other Plugins**: Beyond its core functionality, the Tree View plugin plays a **crucial role** in supporting other RevoGrid plugins. It facilitates **data transfer** between different components, ensuring compatibility with features like filtering, sorting, and row selection.
- **Hierarchical Data Support**: Automatically organizes flat data into a tree structure based on customizable `id`, `parentId`, and `level` fields.
- **Expandable Rows**: Expand and collapse rows dynamically to reveal or hide child rows.
- **Customizable Templates**: Supports tree-specific cell templates for better customization.
- **Root Parent Support**: Define custom root parent identifiers for flexibility in tree structure.
## Data Structure
To build the hierarchical tree structure, each data item must include the following fields:
- **`id`**: A unique identifier for each row.
- **`parentId`**: Links the item to its parent row. Root-level rows should use the `rootParentId` value (default: `'root'`).
Without these fields, the plugin cannot establish the necessary parent-child relationships for the tree structure.
```javascript
const data = [
{ id: '1', parentId: 'root', name: 'Parent 1' },
{ id: '2', parentId: '1', name: 'Child 1.1' },
{ id: '3', parentId: '1', name: 'Child 1.2' },
{ id: '4', parentId: 'root', name: 'Parent 2' },
];
```
## Additional Configuration Options
The plugin supports the following options for customization:
- idField: Defines the field representing unique row identifiers. Default: 'id'.
- parentIdField: Specifies the field indicating parent row IDs. Default: 'parentId'.
- levelField: Sets the field to store hierarchy levels. Default: 'level'.
- rootParentId: Defines the identifier for root-level rows. Default: 'root'.
- expandedRowIds: A Set of row IDs to predefine expanded rows.
### Example Configuration
You can provide these options via revogrid.additionalData?.tree to customize the plugin’s behavior:
```javascript
grid.additionalData = {
tree: {
idField: 'customId',
parentIdField: 'customParentId',
levelField: 'depth',
rootParentId: null,
expandedRowIds: new Set(['row1', 'row2']), // Pre-expanded rows
},
};
```
By setting these options, you can adapt the plugin to different data structures and define default states for the tree.
### Animated Collapse and Expand
Tree rows can animate while they are trimmed during collapse and restored during expand. Enable `tree.animation`; TreeDataPlugin registers `DimensionAnimationPlugin` automatically when it is not already present.
```javascript
import { TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin];
grid.tree = {
animation: true,
};
grid.dimensionAnimation = {
duration: 180,
};
```
When `tree.animation` is not enabled, tree collapse and expand use the existing immediate trim behavior.
---
# Checkbox Editor
URL: https://pro.rv-grid.com/guides/editors/editor-checkbox/
Source: src/content/docs/guides/editors/editor-checkbox.mdx
The `editorCheckbox` is a custom cell editor for RevoGrid that provides a checkbox input
to edit boolean values directly within the grid cells.
* **Features**:
- Renders a checkbox input element for cells, allowing users to toggle boolean values (`true/false`)
with a simple click.
- Automatically dispatches a `celledit` event upon change, updating the grid's data model with
the new value.
- Ensures seamless integration with RevoGrid by providing row and column details in the event payload.
- The demo combines checkbox editing with `RowOddPlugin` zebra striping and the
`material`/`darkMaterial` themes.
---
# Counter Editor
URL: https://pro.rv-grid.com/guides/editors/editor-counter/
Source: src/content/docs/guides/editors/editor-counter.mdx
Description: A plus/minus counter editor for numeric values
The `editorCounter` is a custom cell editor for RevoGrid that provides plus and minus buttons
to increment/decrement numeric values within a specified range directly within the grid cells.
**Features**:
- Renders plus and minus buttons for easy value adjustment
- Supports customizable minimum and maximum values through column properties
- Configurable step size for increments/decrements
- Shows current value with optional display toggle
- Automatically dispatches a `celledit` event upon change
- Seamless integration with RevoGrid's data model
- Modern, responsive design with customizable theming
## Usage
Import the `editorCounter` and assign it to the `cellTemplate` property of a column in the RevoGrid:
```typescript
import { editorCounter } from '@revolist/revogrid-pro'
grid.columns = [
{
prop: 'quantity',
name: 'Quantity',
cellTemplate: editorCounter,
min: 0, // Minimum value (default: 0)
max: 100, // Maximum value (default: 100)
step: 5, // Step size for increments/decrements (default: 1)
hideValue: false, // Set to true to hide the value display
},
];
```
## Configuration Options
The counter editor supports several configuration options through column properties:
- `min` (number): The minimum allowed value (default: 0)
- `max` (number): The maximum allowed value (default: 100)
- `step` (number): The increment/decrement step size (default: 1)
- `hideValue` (boolean): Whether to hide the numeric value display (default: false)
## Event Handling
The counter editor dispatches a `celledit` event whenever the value changes. The event detail contains:
- `rgCol`: The column index of the edited cell
- `rgRow`: The row index of the edited cell
- `type`: The type of the cell
- `prop`: The property of the cell being edited
- `val`: The new numeric value after the change
- `preventFocus`: A flag to control grid focus behavior (default: true)
## Best Practices
1. **Step Size**: Choose a step size that makes sense for your data. For example:
- Use 1 for integer values like quantities
- Use 0.1 or 0.01 for decimal values like prices
- Use 5 or 10 for larger ranges
2. **Value Range**: Set appropriate min and max values to prevent invalid data:
- For quantities: min = 0, max = maximum stock level
- For percentages: min = 0, max = 100
- For ratings: min = 1, max = 5 (or your rating scale)
3. **Visual Feedback**: The editor provides visual feedback through:
- Disabled buttons when at min/max values
- Hover effects on buttons
- Clear value display (unless hidden)
4. **Accessibility**: The editor supports keyboard interaction:
- Tab navigation between buttons
- Button states clearly indicated through styling
- Numeric value visible by default
---
# Dropdown Editor
URL: https://pro.rv-grid.com/guides/editors/editor-dropdown/
Source: src/content/docs/guides/editors/editor-dropdown.mdx
The `ColumnDropdown` column type renders the Pro dropdown editor inside a grid cell. Use it for single-select values, or enable `config.multiSelect` when the cell value is an array.
Register `ColumnDropdown` in `columnTypes`, set `columnType: 'dropdown'` on a column, and provide a `dropdown.source` list with `{ value, label }` options.
**Features**:
- Supports single-select cells with scalar values
- Supports multi-select cells with array values through `config.multiSelect: true`
- Can enable search with `config.search: true`
- Saves changes through RevoGrid cell edit events, so the grid data model stays in sync
- Supports custom option and selected-value templates through the dropdown render callbacks
- Can mirror the column cell template in options and selected values with `dropdown.syncCellTemplate`
### Keep cell and dropdown templates in sync
Set `dropdown.syncCellTemplate` to `true` to reuse the column `cellTemplate` for both option rows and selected values. Object option fields are merged into the synthetic template model, so the same renderer can use metadata such as a status tone, avatar URL, or icon. Explicit `renderOption` and `renderValue` callbacks remain authoritative.
```typescript
const columns = [{
prop: 'status',
columnType: 'dropdown',
cellTemplate: (h, { model, value }) => h('span', {
class: `status-badge status-badge--${model.tone}`,
}, model.label ?? value),
dropdown: {
syncCellTemplate: true,
source: [
{ value: 'active', label: 'Active', tone: 'green' },
{ value: 'paused', label: 'Paused', tone: 'amber' },
],
},
}];
```
---
# Row Editor
URL: https://pro.rv-grid.com/guides/editors/editor-row/
Source: src/content/docs/guides/editors/editor-row.mdx
The `RowEditPlugin` is a custom plugin for RevoGrid that provides row-level editing capabilities,
allowing users to edit entire rows inline with dedicated action buttons for saving or canceling changes.
* **Features**:
- **Row Editing Mode:** Enables editing of entire rows by rendering editors for all cells in the selected row.
- **Inline Editors:** Supports inline editing of multiple cells in a row simultaneously.
- **Action Buttons:** Provides intuitive buttons for `Save` and `Cancel` actions:
- `Save`: Dispatches `CELL_EDIT_SAVE_EVENT` to persist changes.
- `Cancel`: Dispatches `CELL_EDIT_CANCEL_EVENT` to discard changes.
- **Event Handling:** Listens for key events to manage row editing:
- `CELL_EDIT_EVENT`: Initiates row editing mode.
- `CELL_EDIT_SAVE_EVENT`: Saves edited data and updates the grid model.
- `CELL_EDIT_CANCEL_EVENT`: Cancels editing and restores original data.
- `BEFORE_CELL_RENDER_EVENT`: Ensures cells in the editing row render with editors.
---
# Slider Editor
URL: https://pro.rv-grid.com/guides/editors/editor-slider/
Source: src/content/docs/guides/editors/editor-slider.mdx
The `editorSlider` is a custom cell editor for RevoGrid that provides a slider input
to edit numeric values within a specified range directly within the grid cells.
**Features**:
- Renders a slider input element for cells, allowing users to select numeric values by dragging a slider handle
- Supports customizable minimum and maximum values through column properties
- Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value
- Ensures seamless integration with RevoGrid by providing row and column details in the event payload
---
# Textarea Editor
URL: https://pro.rv-grid.com/guides/editors/editor-textarea/
Source: src/content/docs/guides/editors/editor-textarea.mdx
This editor is a custom cell editor for RevoGrid that allows users to handle multi-line text input, making it suitable for applications requiring
rich text editing capabilities within grid cells.
To move to the next line within the `TextAreaEditor`, the user should press `Shift + Enter`.
---
# Timeline Editor
URL: https://pro.rv-grid.com/guides/editors/editor-timeline/
Source: src/content/docs/guides/editors/editor-timeline.mdx
Description: A custom cell editor for managing date ranges with a visual timeline representation
The `editorTimeline` is a custom cell editor for RevoGrid that provides a visual timeline interface
for managing date ranges directly within grid cells.
**Features**:
- Visual timeline bar with progress indicator
- Date range selection with native date pickers
- Progress tracking based on current date
- Responsive design that fits within grid cells
---
# Customization
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/
Source: src/content/docs/guides/event-scheduler/customization/index.mdx
Description: Customize scheduler labels, renderers, properties, styles, visual hooks, and states.
Event Scheduler can be customized at several levels. Start with labels, formatters, CSS variables, and classes when you only need to change text or visual styling. Use `eventScheduler.customization` when a product needs RevoGrid-style render hooks for generated scheduler headers, cells, event sections, or create previews.
The scheduler still owns projection, selection, drag/drop, resize, keyboard behavior, permission checks, conflicts, and mutation events. Custom render hooks should describe what a section looks like, while the scheduler continues to manage how that section behaves.
## Customization layers
| Layer | Use it for |
| --- | --- |
| `labels` | Rename built-in UI text such as resource labels, editor fields, action labels, and validation messages. |
| Formatters | Change displayed text without replacing markup, for example resource labels, time labels, day headers, hover labels, create-range labels, and tooltips. |
| Direct properties hooks | Add classes, inline styles, attributes, data fields, CSS variables, status colors, or simple renderers to scheduler surfaces. |
| `customization` | Replace or annotate scheduler headers, cells, event sub-sections, resize handles, badges, and create previews. |
| Conflict and state hooks | Customize conflict badges/tooltips and empty/loading/error overlay states. |
| CSS variables/classes | Theme the scheduler and target built-in state classes such as selected, conflict, locked, status, type, weekend, today, or non-working time. |
## Customization areas
| Area | Use it for |
| --- | --- |
| [Labels and formatters](./labels-formatters/) | Text-only labels, date/time display, resource names, hover labels, and tooltips. |
| [Event rendering](./events/) | Event button properties, content templates, badges, resize handles, tooltips, and status colors. |
| [Layout, headers, and cells](./layout/) | Column sizes, generated headers, time rows, day cells, resource rows, and timeline cells. |
| [Slots, hours, and markers](./slots-hours/) | Create previews, empty slots, closed hours, availability styling, and current-time markers. |
| [Conflicts and states](./conflicts-states/) | Conflict indicators, warning/error treatment, remote loading, empty, and error overlays. |
| [Context menus](./context-menu/) | Event, slot, day-header, and background commands. |
| [Styling and themes](./styling/) | CSS variables, built-in state classes, and white-label themes. |
## Choosing the right hook
- Use `labels` or formatters when the result is plain text.
- Use CSS variables when the built-in structure is correct and the change is visual.
- Use `eventProperties` or `statusColorResolver` when styling depends on event data.
- Use `customization.*Properties` to add classes, styles, ARIA labels, or data attributes without replacing content.
- Use `customization.*Template` only when the visible markup for that section needs to change.
Template hooks replace only the section they target. Scheduler-owned interaction handlers, selection state, permission attributes, drag/resize behavior, generated data attributes, conflict classes, and core accessibility attributes remain managed by the plugin.
For interaction callbacks, cancelable DOM events, selection, clipboard, keyboard shortcuts, and custom editor workflows, see [Editing and Interaction](../event-editing/).
For compact copyable examples of each major customization surface, see [Examples](../examples/). That page includes basic weekly setup, event templates, day headers, slot context menus, current-time markers, closed hours, read-only mode, cancelable selection, and theme snippets.
---
# Conflicts and States
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/conflicts-states/
Source: src/content/docs/guides/event-scheduler/customization/conflicts-states.mdx
Description: Customize conflict warnings, empty overlays, loading overlays, and error states.
## Conflict display
Conflict customization is separate from general event rendering so products can apply a consistent warning/error treatment without replacing event content.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
conflicts: {
className: ({ severity }) => `booking-conflict booking-conflict--${severity}`,
properties: ({ conflictTypes }) => ({
'data-conflict-types': conflictTypes.join(' '),
}),
indicator: (h, { severity }) =>
h('span', { class: 'booking-conflict__badge' }, severity === 'error' ? 'Blocked' : 'Review'),
tooltip: ({ label }) => `Planning issue: ${label}`,
},
},
};
```
The scheduler still adds built-in conflict classes, data attributes, and severity attributes to event blocks. Custom conflict hooks run only for conflicted segments.
## Empty, loading, and error states
Remote mode and local empty states can use product-specific wording and markup through `labels`, `emptyStateText`, and `customization.emptyState`.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
emptyStateText: 'No resources match this view.',
labels: {
remoteLoading: 'Loading bookings...',
remoteError: 'Bookings could not be loaded.',
remoteEmpty: 'No bookings in this range.',
},
customization: {
emptyState: {
template: ({ kind, label }) =>
kind === 'error' ? `Action needed: ${label}` : label,
properties: ({ kind }) => ({
class: `booking-scheduler-state booking-scheduler-state--${kind}`,
}),
},
},
};
```
The template can return a string or a DOM node. Property hooks can add classes, style variables, ARIA attributes, and data attributes to the overlay element.
---
# Context Menus
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/context-menu/
Source: src/content/docs/guides/event-scheduler/customization/context-menu.mdx
Description: Customize scheduler context-menu actions for events, slots, headers, and background surfaces.
## Context menu actions
Use scheduler context-menu hooks when product actions depend on the clicked event, empty slot, resource, or scheduler surface. The default event and slot menus are generic conveniences; products can replace them, append shared actions, or keep built-ins and filter individual commands.
| Config | Purpose |
| --- | --- |
| `getSlotContextMenuItems` | Replaces the empty-slot menu for the clicked slot. |
| `getEventContextMenuItems` | Replaces the event-bar menu for the clicked event segment. |
| `getDayHeaderContextMenuItems` | Defines optional day-header menu commands for products that expose header actions. |
| `getBackgroundContextMenuItems` | Defines background/empty-scheduler-space commands. Slot backgrounds use this when no slot factory is supplied. |
| `contextMenuTemplate` | Replaces the rendered content for scheduler-specific menu items. |
| `onContextMenuAction` | Receives every scheduler-specific item selection with target context. |
| `hiddenItems` / `disabledItems` | Hide or disable built-in scheduler actions by id. |
| `items` / `itemFilter` | Compatibility hooks for appending raw Pro context-menu items or transforming built-ins. |
Scheduler-specific menu items support `id`, `label`, `icon`, `shortcut`, `disabled`, `hidden`, `danger`, `separator`, `group`, and `action`. Use `danger` for destructive actions, `separator` for visual breaks, and `disabled` / `hidden` functions when the command depends on event or slot state.
Menu item visibility and disabled state are resolved when the menu opens. If visibility depends on remote permissions or capacity checks, keep that state in application data and return it synchronously from `hidden` or `disabled`. Time-row context menus are optional; products that need them can use the underlying Pro row context-menu configuration until a dedicated scheduler time-row trigger is required.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
contextMenu: {
enabled: true,
hiddenItems: { confirm: true },
getEventContextMenuItems: ({ segment }) => [
{
id: 'open-record',
label: 'Open record',
icon: 'fa-solid fa-arrow-up-right-from-square',
shortcut: 'O',
action: () => openPlanningRecord(segment?.event.id),
},
{ id: 'event-edit-separator', separator: true },
{
id: 'duplicate-event',
label: 'Duplicate',
shortcut: 'Cmd+D',
disabled: ({ segment }) => segment?.event.locked === true,
action: ({ segment }) => duplicateBooking(segment?.event.id),
},
{
id: 'delete-event',
label: 'Delete',
danger: true,
disabled: ({ segment }) => segment?.event.readonly === true,
action: ({ segment }) => deleteBooking(segment?.event.id),
},
],
getSlotContextMenuItems: ({ slot }) => [
{
id: 'create-booking',
label: 'Create booking here',
icon: 'fa-solid fa-plus',
shortcut: 'C',
action: () => createBookingAt(slot?.startDateTime),
},
{
id: 'block-slot',
label: 'Block this time',
group: 'availability',
action: ({ slot }) => createHoldAt(slot?.startDateTime),
},
],
getBackgroundContextMenuItems: () => [
{ id: 'refresh-availability', label: 'Refresh availability' },
],
contextMenuTemplate: (h, item) =>
item.separator
? null
: h('span', { class: 'planner-menu-item' }, [
item.icon ? h('span', { class: item.icon }) : null,
h('span', null, item.label),
item.shortcut ? h('kbd', null, item.shortcut) : null,
]),
onContextMenuAction: ({ itemId, target, segment, slot }) => {
trackSchedulerMenuAction({ itemId, target, eventId: segment?.event.id, slot });
},
},
};
```
Built-in context-menu actions continue to respect scheduler permissions, locked resources, read-only mode, disabled/non-selectable slots, and conflict rules. The default built-in labels can be changed through `labels`, and all built-ins can be removed with `hiddenItems`, transformed with `itemFilter`, or replaced entirely with target-specific factories.
Built-in menu labels are generic by default: `createEventHereAction`, `assignOpenEventAction`, `editEventAction`, `confirmEventAction`, and `markPlannedAction`. Integrations should use these generic event labels or replace the menus with target-specific factories.
---
# Event Rendering
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/events/
Source: src/content/docs/guides/event-scheduler/customization/events.mdx
Description: Customize event colors, properties, content, badges, resize handles, tooltips, and accessible labels.
## Event branding
Event blocks support a lightweight styling layer before you use the grouped `customization.events` API.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
statusColorResolver: (event) => {
if (event.status === 'confirmed') return '#2563eb';
if (event.status === 'planned') return '#d97706';
if (event.status === 'blocked') return '#dc2626';
},
eventProperties: ({ event }) => ({
class: `scheduler-event--${event.type ?? 'default'}`,
style: {
'--shift-accent': event.status === 'blocked' ? '#dc2626' : '#2563eb',
},
'data-shift-status': event.status,
}),
eventTemplate: (h, { event }) =>
h('span', { class: 'shift-event-title' }, event.title),
};
```
`statusColorResolver` resolves the scheduler event accent color when the event does not already provide `event.color` and the status does not map to a built-in status class. `eventProperties` is merged onto the event button and can return `class`, `style`, attributes, data fields, or event handlers. `eventTemplate` replaces the default event content only; it does not replace the outer interactive event shell.
## Event section templates
Use direct event hooks when a product needs a simple public API for event bars in week and resource-timeline views.
| Config | Purpose |
| --- | --- |
| `eventTemplate` | Legacy top-level event content renderer. |
| `eventContentTemplate` | Replaces the default title, time, badge, and resource content block. |
| `eventProperties` | Adds classes, styles, attributes, data fields, or event handlers to the event button. |
| `eventTooltipTemplate` | Overrides the event `title` tooltip with custom details. |
| `eventBadgeTemplate` | Replaces individual status, type, category, or required-role badges. |
| `eventResizeHandleTemplate` | Replaces the visual content inside start/end resize handles. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
eventProperties: (context) => ({
class: [
'event-card',
context.isSelected ? 'event-card--selected' : '',
context.isLocked ? 'event-card--locked' : '',
context.hasConflict ? 'event-card--conflict' : '',
context.isShortEvent ? 'event-card--short' : '',
].filter(Boolean).join(' '),
style: {
'--event-accent': context.hasConflict ? '#dc2626' : context.event.color ?? '#2563eb',
},
'data-event-duration': context.duration,
}),
eventContentTemplate: (h, { event, resource, start, end, isFromPreviousDay, isContinuedToNextDay }) =>
h('span', { class: 'event-card__content' }, [
h('strong', { class: 'event-card__title' }, event.title),
h('span', { class: 'event-card__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`),
resource ? h('span', { class: 'event-card__resource' }, resource.name) : null,
isFromPreviousDay ? h('span', { class: 'event-card__continuation' }, 'From previous day') : null,
isContinuedToNextDay ? h('span', { class: 'event-card__continuation' }, 'Continues') : null,
]),
eventBadgeTemplate: (h, { kind, label, isLocked }) =>
h('span', { class: `event-card__badge event-card__badge--${kind}` }, isLocked ? 'Locked' : label),
eventResizeHandleTemplate: (h, { edge }) =>
h('span', { class: `event-card__resize event-card__resize--${edge}` }),
eventTooltipTemplate: ({ event, resource, duration, hasConflict }) =>
`${event.title} | ${resource?.name ?? 'Unassigned'} | ${duration} minutes${hasConflict ? ' | Conflict' : ''}`,
};
```
Event render contexts include `event`, `resource`, the first real projected `segment`, the ordered `segments` represented by the bar, aggregate `start`, `end`, and `duration`, plus `day`, `status`, `type`, `isSelected`, `isDragging`, `isResizing`, `isLocked`, `hasConflict`, `isReadonly`, `isFromPreviousDay`, `isContinuedToNextDay`, and `isShortEvent`. Dragging and resizing state are currently reserved and remain `false` until the scheduler exposes public pointer-state rendering.
The legacy `first`, `last`, and `sliceSlot` fields remain available for source compatibility but are deprecated. Because hooks now run once per continuous bar, `first` and `last` are always `true`, while `sliceSlot` is the primary segment's `startSlot`. New code should use `segment.startSlot`, `segments`, and the aggregate range fields instead.
`customization.events` provides the grouped form of the same customization surface and layers on top of direct event properties where both are supplied. It customizes the event button and its internal sections. It applies to both week and resource timeline render modes.
When `customization.events.content` is configured, it is the authoritative content hook. Returning `null` or `undefined` restores the built-in event content rather than invoking `eventContentTemplate` or legacy `eventTemplate`. Without the grouped hook, Scheduler tries `eventContentTemplate`, then `eventTemplate`, and finally the built-in content.
| Hook | What it customizes |
| --- | --- |
| `className` | Adds one or more classes to the event button. |
| `style` | Adds inline styles or CSS custom properties to the event button. |
| `properties` | Adds attributes, classes, styles, or data attributes to the event button. |
| `content` | Replaces the default title, time, badge, and resource content block. |
| `continuationLabels` | Replaces previous/next-day continuation labels. |
| `badgeTray` | Replaces the tray that holds badges for multi-slot events. |
| `badge` | Replaces individual status, type, category, or required-role badges. |
| `resizeHandle` | Replaces the visual content inside start/end resize handles. |
| `tooltip` | Overrides the event `title` tooltip. |
| `ariaLabel` | Overrides the event button accessible label. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
events: {
className: ({ event }) => `event-card event-card--${event.status ?? 'default'}`,
style: ({ event }) => ({
'--event-border-color': event.status === 'blocked' ? '#dc2626' : '#2563eb',
}),
properties: ({ event, segment }) => ({
'data-event-id': event.id,
'data-event-start-slot': segment.startSlot,
}),
content: (h, { event, resource, segment }) =>
h('span', { class: 'event-card__content' }, [
h('strong', { class: 'event-card__title' }, event.title),
h('span', { class: 'event-card__resource' }, resource?.name ?? 'Unassigned'),
segment.conflict ? h('span', { class: 'event-card__warning' }, 'Conflict') : null,
]),
badge: (h, { kind, label }) =>
h('span', { class: `event-card__badge event-card__badge--${kind}` }, label),
tooltip: ({ event, resource }) => `${event.title} - ${resource?.name ?? 'Unassigned'}`,
ariaLabel: ({ event }) => `Scheduled event ${event.title}`,
},
},
};
```
The outer event element remains a scheduler-managed button. It keeps click, double-click, pointer, selection, disabled move/resize state, conflict state, and accessibility wiring. Custom event content should avoid interactive controls inside the event button unless the product has tested the keyboard and pointer behavior carefully.
## Rendering content at the bottom of a bar
Each event assignment is rendered as one continuous visual bar. In vertical views the bar is anchored from the day's first slot cell; in resource timeline it is attached to the resource row. Event customization hooks therefore run once per visible bar rather than once per grid cell.
Use `context.segment` for the first real projected segment and `context.segments` when you need every day segment represented by a multi-day timeline bar. The aggregate `context.start`, `context.end`, and `context.duration` fields describe the complete visible bar.
To render content pinned to the bottom of a multi-slot bar — such as a status badge, category chip, or summary — use `badgeTray`. Its return value is placed directly inside the event button.
Include the `event-scheduler-event__badge-tray` wrapper class in the returned element to get the built-in absolute bottom positioning:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
events: {
content: (h, context) =>
h('span', { class: 'my-event' }, [
h('strong', { class: 'my-event__title' }, context.event.title),
h('span', { class: 'my-event__time' }, `${context.start.slice(11, 16)}-${context.end.slice(11, 16)}`),
h('span', { class: 'my-event__badge' }, String(context.event.status ?? '')),
]),
badgeTray: (h, context) => {
// Runs once for a multi-slot bar whose visible end is in this view.
const status = String(context.event.status ?? '');
if (!status) return null;
return h('span', { class: 'event-scheduler-event__badge-tray' }, [
h('span', { class: 'my-event__badge' }, status),
]);
},
},
},
};
```
`badgeTray` does not fire for single-slot bars or for a bar whose visible end continues beyond the current view (`isContinuedToNextDay`). When `badgeTray` returns `null` or `undefined`, the default badge tray — which displays type, category, and required-role chips — is rendered instead.
---
# Labels and Formatters
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/labels-formatters/
Source: src/content/docs/guides/event-scheduler/customization/labels-formatters.mdx
Description: Customize scheduler text, dates, resource labels, hover labels, and tooltips.
## Labels and formatters
Use labels and formatters for text-level customization. This keeps the built-in DOM, accessibility attributes, selection, tooltips, and editing behavior intact.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
locale: 'en-US',
timeZone: 'America/New_York',
labels: {
resourceColumn: 'Crew',
unassignedResource: 'Open demand',
newEvent: 'New assignment',
createEvent: 'Create shift',
editEvent: 'Edit shift',
},
dayHeaderFormatter: (date) =>
date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'America/New_York',
}),
timeLabelFormatter: (minutes) => `${Math.floor(minutes / 60)}:00`,
timelineHeaderFormatter: ({ startDateTime, endDateTime }) =>
`${startDateTime.slice(11, 16)}-${endDateTime.slice(11, 16)}`,
hoverTimeFormatter: (minutes) => `Create at ${String(Math.floor(minutes / 60)).padStart(2, '0')}:00`,
resourceLabelFormatter: (resource) => resource.name,
resourceMetaFormatter: (resource) =>
[resource.role, resource.metadata?.location].filter(Boolean).join(' - '),
tooltipFormatter: ({ event, resource }) =>
`${event.title}\n${resource?.name ?? 'Unassigned'}\n${event.startDateTime}`,
};
```
Built-in day, range, and timeline date labels use `locale` and `timeZone`. Formatter hooks take precedence when you need product-specific date or time strings, including date-only labels that should not shift when displayed in a non-UTC timezone.
Formatters are the best first choice when the content is still a string. Move to templates only when you need nested markup, custom badges, custom attributes, or section-specific replacement.
---
# Layout, Headers, and Cells
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/layout/
Source: src/content/docs/guides/event-scheduler/customization/layout.mdx
Description: Customize scheduler column sizing, generated headers, time rows, day cells, resource rows, and timeline cells.
## Grouped customization API
Use the grouped `customization` API when you need precise control over generated scheduler surfaces.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 96,
dayColumnSize: 220,
},
resourceTimeline: {
resourceColumnSize: 260,
timelineColumnSize: 144,
},
},
headers: {
dayTemplate: (h, context) =>
h('span', { class: 'scheduler-day-header' }, context.defaultLabel),
dayProperties: (context) => ({
class: context.today ? 'scheduler-day-header--brand-today' : '',
'data-scheduler-date': context.date,
}),
groupTemplate: (h, context) =>
h('strong', { class: 'scheduler-range-header' }, context.defaultLabel),
},
cells: {
resourceProperties: ({ resource }) => ({
'data-resource-role': resource?.role,
}),
timelineProperties: ({ date, slotIndex }) => ({
class: 'scheduler-timeline-slot',
'data-scheduler-slot-key': `${date}:${slotIndex}`,
}),
},
events: {
properties: ({ event }) => ({
'data-event-status': event.status,
}),
content: (h, { event, resource }) =>
h('span', { class: 'scheduler-event-title' }, [
h('strong', {}, event.title),
h('small', {}, resource?.name ?? 'Unassigned'),
]),
badge: (h, context) =>
h('span', {
class: `scheduler-event-chip scheduler-event-chip--${context.kind}`,
}, context.label),
resizeHandle: (h, context) =>
h('span', {
class: `scheduler-event-resize scheduler-event-resize--${context.edge}`,
}),
},
createPreview: {
createRangeTemplate: (context) =>
`${context.title}: ${context.startLabel}-${context.endLabel}`,
createRangeProperties: () => ({ class: 'scheduler-create-preview' }),
},
conflicts: {
className: ({ severity }) => `scheduler-conflict scheduler-conflict--${severity}`,
indicator: (h, context) =>
h('span', { class: 'scheduler-conflict-badge' }, context.severity),
tooltip: ({ label }) => `Review before saving: ${label}`,
},
emptyState: {
template: ({ kind, label }) => `${kind}: ${label}`,
properties: ({ kind }) => ({
class: `scheduler-state scheduler-state--${kind}`,
}),
},
},
};
```
Template hooks receive RevoGrid's `h` helper and a scheduler context object. Return a string, a VNode, `null`, or `undefined`. Returning `null` or `undefined` keeps the built-in section. Property hooks return attributes, `class`, `style`, or data attributes for the target element.
Custom properties are merged with built-in scheduler properties. Built-in interaction handlers and scheduler data attributes remain in place, so custom hooks should not attach drag, resize, selection, or mutation behavior directly.
## Column sizes
`customization.columnSizes` controls scheduler-owned column widths by view. These values are resolved before legacy top-level size props such as `timeColumnSize`, `dayColumnSize`, `resourceColumnSize`, and `timelineColumnSize`.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 88,
dayColumnSize: 180,
},
resourceTimeline: {
resourceColumnSize: 280,
timelineColumnSize: 120,
},
},
},
};
```
Use week sizes for `day`, `week`, and `month` style calendar grids. Use resource timeline sizes when the first column is the pinned resource column and the remaining columns are timeline slots.
## Header templates and properties
Header customization targets the generated scheduler columns. For day headers, use the direct week-scheduler hooks when you only need day-header customization. Use `customization.headers` when you need to customize every generated header type.
Direct day-header hooks:
| Hook | Surface |
| --- | --- |
| `dayHeaderFormatter` | Formats the built-in day header label. |
| `dayHeaderTemplate` | Replaces the day header content. |
| `dayHeaderProperties` | Adds classes, styles, attributes, data fields, or event handlers to day headers. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
dayHeaderFormatter: (date) =>
date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
}),
dayHeaderTemplate: (h, context) =>
h('span', { class: 'planner-day-header' }, [
h('span', { class: 'planner-day-header__label' }, context.defaultLabel),
context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null,
context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null,
h('span', { class: 'planner-day-header__count' }, `${context.eventCount ?? 0} events`),
]),
dayHeaderProperties: (context) => ({
class: [
context.today ? 'planner-day-header--today' : '',
context.weekend ? 'planner-day-header--weekend' : '',
context.holiday ? 'planner-day-header--holiday' : '',
].filter(Boolean).join(' '),
style: {
'--planner-day-event-count': context.eventCount ?? 0,
...(context.holiday ? { '--planner-day-state': 'holiday' } : {}),
},
'data-planner-day': context.date,
}),
};
```
Day header context includes `defaultLabel`, `date`, `dayIndex`, `eventCount`, `today`, `weekend`, `holiday`, and `holidayLabel`. Holiday state is date-level: it comes from the primary scheduler calendar or global `kind: 'holiday'` availability entries. Resource-specific holidays stay on the scheduler body cells.
`customization.headers.dayTemplate` takes precedence over `dayHeaderTemplate` when both are provided. This lets applications keep older grouped customization config while adding direct day-header hooks where convenient.
Grouped header customization has one template hook and one matching properties hook per generated header section.
| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Pinned time header in day/week/month style views. |
| `resourceTemplate` / `resourceProperties` | Pinned resource header in resource timeline views. |
| `dayTemplate` / `dayProperties` | Day header cells. |
| `timelineTemplate` / `timelineProperties` | Resource timeline slot headers. |
| `groupTemplate` / `groupProperties` | Grouped range/day/timeline headers when column grouping is enabled. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
headers: {
dayTemplate: (h, context) =>
h('span', { class: 'brand-day-header' }, [
h('strong', {}, context.defaultLabel),
context.today ? h('small', {}, 'Today') : null,
context.holiday ? h('small', {}, context.holidayLabel) : null,
context.eventCount ? h('small', {}, `${context.eventCount} events`) : null,
]),
dayProperties: (context) => ({
class: [
context.today ? 'brand-day-header--today' : '',
context.weekend ? 'brand-day-header--weekend' : '',
context.holiday ? 'brand-day-header--holiday' : '',
],
'data-brand-date': context.date,
}),
},
},
};
```
Header contexts include the active view, the default label, date or slot fields where relevant, event counts, today/weekend/holiday flags, holiday labels, and group kind for grouped headers.
## Cell templates and properties
Cell customization targets the scheduler body cells. Use property hooks when you only need classes, styles, or test/data attributes. Use templates when the default cell body should be replaced.
Use the direct time-axis hooks when the product only needs to control the pinned time column in day, week, or month-style views.
| Config | Purpose |
| --- | --- |
| `timeLabelFormatter` | Formats built-in time text such as `06:00`, `6 AM`, or `6:00`. |
| `timeLabelTemplate` | Replaces the pinned time-label cell content. |
| `timeRowProperties` | Adds classes, styles, attributes, data fields, or event handlers to time-label cells. |
| `timeRange` | Sets the visible start/end time and hides time outside that range. |
| `slotMinutes` | Sets row granularity with a 15-minute minimum, for example `15`, `30`, or `60`. When `snapMinutes` is omitted, interaction snapping follows this active value as zoom changes. |
| `rowSize` | Controls row density in pixels. |
| `nonWorkingTime` | Styles closed or outside-business-hours cells in the scheduler body. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
timeRange: { start: '06:00', end: '22:00' },
slotMinutes: 30,
rowSize: 40,
timeLabelFormatter: (minutes) => {
const hour = Math.floor(minutes / 60);
const minute = minutes % 60;
return `${hour % 12 || 12}:${String(minute).padStart(2, '0')} ${hour < 12 ? 'AM' : 'PM'}`;
},
timeLabelTemplate: (h, context) =>
h('span', { class: 'planner-time-label' }, context.timeLabel),
timeRowProperties: (context) => ({
class: context.currentTimeLabel ? 'planner-time-row--current' : 'planner-time-row',
style: {
'--planner-time-start': context.startMinutes,
},
'data-planner-time': context.timeLabel,
}),
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
className: 'planner-closed-hours',
},
};
```
`customization.cells.timeTemplate` and `customization.cells.timeProperties` layer over the direct `timeLabelTemplate` and `timeRowProperties` hooks when both are provided. Use `rowSize` for compact, dense, or comfortable density presets, and use `timeRange` when night hours should be hidden from the grid instead of only styled as closed.
| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Time-label rows in week-style grids. |
| `dayTemplate` / `dayProperties` | Day/time slots in week-style grids. |
| `resourceTemplate` / `resourceProperties` | Resource rows in resource timeline grids. |
| `resourceGroupTemplate` / `resourceGroupProperties` | Group rows when resource grouping is enabled. |
| `timelineTemplate` / `timelineProperties` | Timeline cells in resource timeline grids. |
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
cells: {
resourceProperties: ({ resource, unassigned, depth }) => ({
class: [
resource?.role ? `resource-row--${resource.role.toLowerCase()}` : '',
unassigned ? 'resource-row--open-demand' : '',
],
style: { '--resource-depth': depth },
'data-resource-role': resource?.role,
}),
timelineProperties: ({ date, slotIndex, segmentCount }) => ({
class: segmentCount > 0 ? 'timeline-slot--busy' : 'timeline-slot--open',
'data-slot-key': `${date}:${slotIndex}`,
}),
},
},
};
```
When a cell template is provided, the scheduler renders that template instead of the default cell content for that slot. Continuous event bars are rendered by the Scheduler's bar layer and remain independent of slot-cell content, so prefer cell properties when the goal is only styling or annotation.
---
# Slots, Hours, and Markers
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/slots-hours/
Source: src/content/docs/guides/event-scheduler/customization/slots-hours.mdx
Description: Customize create previews, empty slots, closed hours, availability states, and current-time markers.
## Create previews
Create preview customization affects empty-slot hover labels and the drag-create range preview.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
customization: {
createPreview: {
hoverTimeProperties: ({ startDateTime }) => ({
class: 'create-hover-slot',
'data-hover-start': startDateTime,
}),
createRangeTemplate: ({ title, startLabel, endLabel, durationLabel, resource }) =>
`${title} for ${resource?.name ?? 'Unassigned'}: ${startLabel}-${endLabel} (${durationLabel})`,
createRangeProperties: ({ durationMinutes }) => ({
class: durationMinutes >= 240 ? 'create-range--long' : 'create-range--short',
}),
},
},
};
```
Use `hoverTimeProperties` for attributes and styling on the slot while the pointer is over a creatable time. Use `createRangeTemplate` and `createRangeProperties` for the range ghost shown during drag-create interactions.
## Empty slot customization
Empty slots are a primary extension point for booking, staffing, room planning, maintenance windows, pricing, capacity, and availability workflows. Use direct slot hooks when a product needs custom empty-cell content or behavior without replacing event bars.
| Hook | Purpose |
| --- | --- |
| `slotTemplate` | Renders custom content inside empty week or resource-timeline slots. Occupied slots keep their event bars. |
| `slotProperties` | Adds classes, styles, attributes, data fields, or event handlers to scheduler slot cells. |
| `slotTooltip` | Overrides the native tooltip for disabled, closed, priced, or capacity-based slots. |
| `onSlotClick` | Runs custom click behavior before built-in editor-open behavior. |
| `onSlotDoubleClick` | Runs custom double-click behavior for immediate create or custom workflows. |
| `onSlotContextMenu` | Receives the resolved slot when the scheduler context menu opens. |
| `isSlotDisabled` | Marks slots as disabled and blocks built-in create/editor behavior. |
| `isSlotSelectable` | Marks slots as non-selectable for create/selection UX. |
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
slotTemplate: (h, context) =>
h('span', { class: 'booking-slot-capacity' }, [
context.disabled ? 'Closed' : `${getCapacity(context)} seats`,
]),
slotProperties: (context) => ({
class: [
context.disabled ? 'booking-slot--closed' : '',
!context.selectable ? 'booking-slot--invalid' : '',
context.segmentCount > 0 ? 'booking-slot--busy' : 'booking-slot--open',
].filter(Boolean).join(' '),
style: {
'--booking-slot-capacity': getCapacity(context),
},
'data-booking-slot': context.startDateTime,
}),
slotTooltip: (context) =>
context.disabled ? 'Outside working hours' : `Available from ${context.startDateTime}`,
isSlotDisabled: (context) => isClosedForBusiness(context),
isSlotSelectable: (context) => !hasBlockedAvailability(context),
onSlotClick: (context, event) => {
if (context.disabled) {
event.preventDefault();
showClosedSlotPopover(context);
}
},
onSlotDoubleClick: (context) => {
if (!context.disabled) createBookingImmediately(context);
},
onSlotContextMenu: (context) => {
trackSlotMenuOpened(context);
},
contextMenu: {
enabled: true,
hiddenItems: {
create: true,
'assign-open': true,
},
items: ({ target, slot }) => target === 'slot' && slot
? [
{ name: 'Create booking', action: () => createBookingImmediately(slot) },
{ name: 'Block this time', action: () => blockSlot(slot) },
]
: [],
},
};
```
`slotTemplate` is intentionally limited to empty cells so products can customize available space without removing visible event bars. Use `customization.cells.dayTemplate` or `customization.cells.timelineTemplate` only when the whole scheduler body cell, including event rendering, should be replaced. Use `isSlotDisabled` and `isSlotSelectable` with `allowCreate` or conflict rules when closed hours should block create, drag, paste, and editor flows.
## Working and closed hours
Use working and closed-hour APIs when availability is part of the scheduler contract: business hours, weekends, holidays, lunch breaks, maintenance windows, or capacity holds. These hooks build on the existing `nonWorkingTime`, calendars, availability data, and conflict rules.
| Config | Purpose |
| --- | --- |
| `workingHours` | Defines global or per-weekday working ranges. Use `false` in `byDay` to close a weekday. |
| `closedHours` | Defines closed ranges such as lunch, maintenance, holidays, or blocked planning windows. |
| `availabilityRules` | Applies product-specific availability rules from slot context. |
| `closedSlotTemplate` | Renders custom content inside closed slots. |
| `closedSlotProperties` | Adds classes, styles, attributes, data fields, or event handlers to closed slots. |
| `closedSlotTooltip` | Overrides the native tooltip/title for closed slots. |
| `isTimeSlotAvailable` | Blocks built-in create/editor behavior when it returns `false`. |
Use `workingHours` for global business hours, `workingHours.byDay` for weekday-specific hours or closed weekends, `closedHours` for explicit closed ranges such as lunch or maintenance, and date-specific `closedHours` entries for holidays. Closed slots can be styled with `closedSlotProperties`, replaced with `closedSlotTemplate`, and explained through `closedSlotTooltip`.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
workingHours: {
byDay: {
1: { start: '08:00', end: '18:00' },
2: { start: '08:00', end: '18:00' },
3: { start: '08:00', end: '18:00' },
4: { start: '08:00', end: '18:00' },
5: { start: '08:00', end: '16:00' },
0: false,
6: false,
},
},
closedHours: [
{
id: 'lunch',
days: [1, 2, 3, 4, 5],
ranges: { start: '12:00', end: '13:00' },
title: 'Closed',
reason: 'Lunch break',
className: 'planner-slot--lunch',
},
{
id: 'maintenance',
dates: ['2026-06-10'],
ranges: { start: '15:00', end: '17:00' },
reason: 'Maintenance window',
className: 'planner-slot--maintenance',
},
],
availabilityRules: ({ date, startMinutes }) =>
date === '2026-06-12' && startMinutes >= 14 * 60
? { available: false, reason: 'Capacity hold', className: 'planner-slot--held' }
: undefined,
isTimeSlotAvailable: ({ date, slotIndex }) =>
!isCompanyHoliday(date) && slotIndex !== 0,
closedSlotProperties: ({ availabilityKind, reason }) => ({
class: `planner-slot--closed planner-slot--${availabilityKind}`,
style: {
'--planner-closed-opacity': reason === 'Lunch break' ? 0.6 : 0.85,
},
}),
closedSlotTooltip: ({ title, reason }) =>
[title, reason].filter(Boolean).join(' - '),
closedSlotTemplate: (h, { reason }) =>
h('span', { class: 'planner-slot__closed-label' }, reason ?? 'Closed'),
};
```
Closed-slot context extends normal slot context with `available`, `availabilityKind`, `availabilityId`, `title`, and `reason`. Built-in create/editor behavior is blocked when `isTimeSlotAvailable` returns `false`, when `closedHours`/`availabilityRules` mark the slot unavailable, or when `isSlotDisabled` / `isSlotSelectable` deny the slot. For resource-specific calendars, remote availability, and holidays, continue using `calendars` and `eventSchedulerAvailability`; the closed-slot hooks style those states too.
## Current time marker
The current-time marker can use the built-in red line and badge, or it can be replaced by product-specific marker content. Week-style views render a horizontal marker on the active date. Resource timeline views render a vertical marker in the active timeline slot.
| Config | Purpose |
| --- | --- |
| `showCurrentTimeMarker` | Simple show/hide alias. |
| `currentTimeMarker` | Detailed marker config, including fixed `dateTime` for deterministic demos/tests. |
| `currentTimeUpdateInterval` | Live refresh interval in milliseconds. Defaults to `60000`; use `0` to disable automatic live refresh. |
| `currentTimeLabelFormatter` | Formats the marker label, such as `12:26`, `Now`, or a localized time. |
| `currentTimeMarkerTemplate` | Replaces marker content in week and resource timeline cells. |
| `currentTimeMarkerProperties` | Adds classes, styles, attributes, data fields, or event handlers to the marker cell. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
showCurrentTimeMarker: true,
currentTimeUpdateInterval: 60_000,
currentTimeLabelFormatter: ({ dateTime }) =>
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
}).format(new Date(dateTime)),
currentTimeMarkerProperties: ({ orientation }) => ({
class: orientation === 'horizontal'
? 'planner-now-marker planner-now-marker--horizontal'
: 'planner-now-marker planner-now-marker--vertical',
style: {
'--event-scheduler-current-time-color': '#dc2626',
'--planner-now-marker-thickness': '2px',
},
}),
currentTimeMarkerTemplate: (h, context) =>
h('span', { class: 'planner-now-marker__label' }, context.label),
};
```
Marker context includes `view`, `date`, `dayIndex`, `slotIndex`, `startMinutes`, `endMinutes`, `dateTime`, `label`, `position`, `orientation`, and, when available, `row`, `resourceId`, and `resource`. The marker is hidden when disabled, outside the visible date range, or outside the visible time range. Use `currentTimeMarker: { dateTime }` when the marker should point at a fixed instant rather than live `now`.
---
# Styling and Themes
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/styling/
Source: src/content/docs/guides/event-scheduler/customization/styling.mdx
Description: Theme Event Scheduler with CSS variables, built-in classes, state selectors, and white-label styles.
## CSS variables and classes
Use CSS when the scheduler structure is correct and only the theme needs to change. The scheduler exposes classes and CSS variables for common states, including today, weekend, current time, conflict, selected, locked, unassigned, status, type, category, non-working time, hover time, ghost ranges, create ranges, editor surfaces, and context menus.
```css
.event-scheduler-plugin {
--event-scheduler-accent: #0f766e;
--event-scheduler-accent-soft: #ccfbf1;
--event-scheduler-surface: #ffffff;
--event-scheduler-surface-sunken: #f8fafc;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-current-time-color: #dc2626;
--event-scheduler-weekend-bg: rgba(15, 118, 110, 0.04);
--event-scheduler-hover-time-bg: rgba(15, 118, 110, 0.12);
--event-scheduler-create-range-bg: rgba(15, 118, 110, 0.16);
}
.event-scheduler-event--status-confirmed {
--event-scheduler-event-color: #2563eb;
}
.event-scheduler-event--type-maintenance {
--event-scheduler-event-bg: #fff7ed;
--event-scheduler-event-color: #c2410c;
}
.event-scheduler-day-cell__slot--non-working {
opacity: 0.8;
}
```
Prefer CSS variables for broad theme changes. Prefer `eventProperties` and `customization.*Properties` when classes or attributes depend on event, resource, date, slot, conflict, or projection context.
## White-label themes
Scheduler styles are designed around CSS custom properties. Apply theme variables at the grid, plugin host, product shell, or global stylesheet level:
```css
.booking-scheduler revo-grid {
--event-scheduler-accent: #315f8c;
--event-scheduler-accent-soft: #e8f1f8;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-surface: #ffffff;
--event-scheduler-surface-sunken: #f6f8fb;
--event-scheduler-event-bg: #eef7f3;
--event-scheduler-event-color: #16825c;
--event-scheduler-conflict-warning-bg: #fff7ed;
--event-scheduler-conflict-warning-color: #c2410c;
--event-scheduler-remote-state-bg: rgba(255, 255, 255, 0.96);
}
```
Prefer CSS variables for broad white-label theming, and use render hooks only when the product needs different markup or data-driven content.
---
# Editing and Interaction
URL: https://pro.rv-grid.com/guides/event-scheduler/event-editing/
Source: src/content/docs/guides/event-scheduler/event-editing.mdx
Description: Create, move, resize, delete, validate, select, copy, paste, and handle interactions with the RevoGrid Scheduler lifecycle.
Enable editing with the master `editable` flag and the specific `allow*` permissions your product needs.
Editing is checked in layers. The scheduler first applies `editable`, `allowCreate`, `allowMove`, `allowResize`, `allowDelete`, event/resource locks, date locks, and permission hooks. It then projects the next schedule and rejects the mutation when conflicts resolve to `severity: 'error'`. Use this to block edits outside working hours.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
createEventDraft: (context) => ({
id: `event-${crypto.randomUUID()}`,
resourceId: context.resourceId,
title: 'New booking',
startDateTime: context.startDateTime,
endDateTime: context.endDateTime,
status: 'draft',
}),
validateMutation: (detail) => {
if (detail.event?.locked) {
return 'Locked events cannot be changed.';
}
},
};
```
To enforce working hours, define them with `calendars` or `eventSchedulerAvailability`; then block the `outside-availability` conflict as shown above. `nonWorkingTime` can make closed cells visible, but it does not by itself reject create, move, resize, or editor changes.
Conflicts are evaluated against the projected next event array before the local mutation is committed. This means move, resize, create, edit, delete, bulk, template, paste, and recurrence actions can all be stopped by the same conflict configuration. Use `conflicts.policy: 'prevent'` when every detected conflict should block by default, or keep `policy: 'mark'` and promote only selected rules such as `overlap`, `outside-availability`, or `blocked-time` to `error`.
The before-change detail includes the projected `conflicts` array. Use it when the UI needs custom messages or business-specific decisions on top of configured conflict rules.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
const blockingConflict = event.detail.conflicts?.find((conflict) =>
conflict.severity === 'error' &&
event.detail.eventId !== null &&
conflict.eventIds.includes(event.detail.eventId)
);
if (blockingConflict) {
event.preventDefault();
}
});
```
For one-off business rules that are not calendar rules, use `permissions.canCreate`, `permissions.canMutate`, or `validateMutation`. These hooks are useful for role checks, approval states, custom capacity rules, or backend-specific constraints.
If you prefer event-based validation, listen for `event-scheduler-before-event-change` and call `event.preventDefault()`. This is the same cancelable-before-event pattern used by RevoGrid editing events. Do not use `event-scheduler-event-changed` for cancellation; it fires after the scheduler has already accepted and applied the change.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.action === 'move' && event.detail.event?.locked) {
event.preventDefault();
}
});
```
## Unified committed changes
Every successful create, edit, move, resize, delete, paste, duplicate,
recurrence, or bulk mutation also reaches EventManager's shared `gridedit`
stream. Inspect `event-scheduler-event-change` in `eventTypes` and the typed,
ordered `domainChanges` entries:
```ts
grid.addEventListener('gridedit', (event) => {
if (!event.detail.eventTypes.includes('event-scheduler-event-change')) return;
for (const change of event.detail.domainChanges ?? []) {
if (change.type !== 'event-scheduler-event') continue;
saveEventChange(change.detail);
}
});
```
Each entry contains the Scheduler action, event ID, previous event, and
committed event. Creates have a null previous event; deletes have a null
committed event; bulk and server-canonical mutations include every affected
event. These snapshots are detached from Scheduler state.
`data`, `previousData`, and `models` contain normal row/field patches only when
an event keeps a stable canonical source index. Creates, deletes, and unstable
positions remain in `domainChanges`. The complete event collection used for
undo/redo is stored separately in `history`, never as a synthetic grid field.
Cancellation remains on `event-scheduler-before-event-change` and
`validateMutation`. Scheduler `gridedit` details have
`sourceMutation: 'producer'`, so they are observational: `preventDefault()`
does not roll back the committed schedule. With remote mutation hooks enabled,
Scheduler publishes only after ordered server commitment; rejected remote
mutations publish nothing.
When EventManager collection is enabled, consecutive Scheduler mutations merge
into one ordered transaction and one history step. Undo restores the earliest
snapshot and redo restores the latest committed snapshot.
## Undo and redo
`EventSchedulerPlugin` installs the shared `HistoryPlugin` automatically. Every
accepted create, edit, move, resize, delete, paste, duplicate, recurrence, or
bulk mutation is stored as one scheduler-owned event snapshot. Undo and redo
restore the complete event collection and rebuild the normal scheduler
projection, including conflicts, coverage, utilization, and event segments.
```ts
const plugins = await grid.getPlugins();
const history = plugins.find((plugin) =>
plugin.constructor?.name === 'HistoryPlugin'
);
history.undo();
history.redo();
```
The normal History keyboard shortcuts, stack limits, persistence adapters,
`beforeundo`, `beforeredo`, `historychanged`, and `historyerror` behavior remain
available. Canceled or rejected scheduler mutations do not create entries, and
multi-event operations remain atomic.
Scheduler rows and columns are derived projection data, so history stores event
entities rather than visible grid cells. If the host persists every state
transition, listen to `historychanged` as well as the scheduler's final mutation
events and read `scheduler.getProjection()?.events` after undo or redo.
## Data ownership and save flows
Event Scheduler does not require one persistence model. Choose the data ownership flow that matches your app: upload all events as host state, save the final projected schedule, save every accepted mutation, or let remote hooks load and commit data.
Use `source` to upload host-owned data into the scheduler:
```ts
grid.eventSchedulerResources = resources;
grid.source = events;
```
The scheduler updates its accepted event list and `grid.source` after permissions, before hooks, `validateMutation`, and conflict rules pass. Final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire afterward, so use them for persistence and framework state sync. Use before hooks or `validateMutation` for cancellation.
### Save the full projection after work
Use this flow when the user can make several local edits and your app saves the whole schedule later, for example from a toolbar Save button. Read `grid.source`; generated scheduler rows, columns, and segments remain internal render artifacts.
```ts
const plugins = await grid.getPlugins();
const scheduler = plugins.find((plugin) =>
plugin.constructor?.name === 'EventSchedulerPlugin'
);
scheduler.createEvent({
id: `event-${crypto.randomUUID()}`,
resourceId: 'room-a',
title: 'Design review',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T10:00:00.000Z',
});
scheduler.editEvent('booking-1', {
resourceId: 'room-b',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T11:00:00.000Z',
});
scheduler.deleteEvent('hold-1');
const savedEvents = grid.source;
await api.saveSchedule({ events: savedEvents });
// Later, or after clearing the scheduler, upload the saved data back.
grid.source = savedEvents;
```
### Save incrementally while work happens
Use this flow when the host app owns canonical state and wants every accepted scheduler change to update that state immediately. Each final event carries `detail.events`, the full next accepted event list after the mutation.
```ts
let persistedEvents = [...initialEvents];
function persistSchedulerEvents(event: CustomEvent) {
persistedEvents = event.detail.events;
void api.saveEvents(persistedEvents);
}
grid.addEventListener('event-scheduler-event-created', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-changed', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-deleted', persistSchedulerEvents);
// Later, upload the persisted host state into a clean scheduler.
grid.source = persistedEvents;
```
The final event detail also includes action-specific context such as `eventId`, `event`, `previousEvent`, `action`, and `conflicts`. Use those fields for audit logs or targeted API calls, but keep `detail.events` as the simplest full-list state sync contract.
### Save through remote APIs
Use remote mode when the server owns the visible range or when loading the whole schedule up front is too expensive. Remote loaders upload visible data into the scheduler. `commitMutation` receives the accepted projected mutation request and can return canonical server events.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
remote: {
enabled: true,
mode: 'remote',
loadResources: ({ signal }) =>
api.loadResources({ signal }),
loadEvents: ({ dateRange, resourceIds, signal }) =>
api.loadEvents({ dateRange, resourceIds, signal }),
validateMutation: (request) =>
api.validateScheduleChange(request),
commitMutation: async (request) => {
const result = await api.saveScheduleChange({
action: request.action,
eventId: request.eventId,
event: request.event,
previousEvent: request.previousEvent,
changes: request.changes,
events: request.events,
conflicts: request.conflicts,
});
return {
accepted: true,
events: result.events,
};
},
},
};
const plugins = await grid.getPlugins();
const scheduler = plugins.find((plugin) =>
plugin.constructor?.name === 'EventSchedulerPlugin'
);
// Reload the current visible range into a clean scheduler state.
await scheduler.refreshVisibleRange();
```
Remote `commitMutation` runs after the local mutation has been accepted. If the backend rejects the change, return `{ accepted: false, message }` or throw; with the default rollback behavior the scheduler restores the previous event list. See [Remote Data](/guides/event-scheduler/remote-data/) for paging, state events, errors, and refresh controls.
`validateMutation` is the config-level equivalent of the cancelable before-change event. It receives the same detail shape and can return `false` or a string to reject.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
validateMutation: ({ action, event, changes }) => {
if (action === 'delete' && event?.status === 'completed') {
return 'Completed events cannot be deleted.';
}
if (changes?.resourceId === 'locked-room') {
return 'That room is locked.';
}
},
};
```
## Built-in editor
The built-in create/edit dialog supports unassigned, single-resource, and
multi-resource events. It keeps the compact `resourceId` shape for one selected
resource and writes `resourceIds` when two or more resources are selected:
```ts
{
id: 'shared-maintenance',
resourceIds: ['crew-a', 'crew-b'],
title: 'Shared maintenance',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T10:00:00.000Z',
}
```
Multi-resource assignment is enabled by default. Disable it when each event
must belong to at most one resource:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
allowMultiResourceAssignment: false,
};
```
With this option disabled, selecting another resource replaces the current
selection, and the built-in editor saves the assignment through `resourceId`.
The option only constrains the built-in editor; custom editors and
host-provided `resourceIds` remain application-owned.
The resource selector edits the event's inline assignment fields. If your
backend owns relationships through `eventSchedulerAssignments`, provide a
custom editor and update that assignment collection in the host application.
## Custom editor
Set `eventEditor: false` to disable the built-in editor, or provide a custom editor hook when the host app owns a drawer, modal, or approval flow:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
eventEditor: (context) => {
openProductDrawer({
mode: context.mode,
event: context.event,
resources: context.resources,
readOnly: context.readonly,
validationMessage: context.validationMessage,
onSave: context.submit,
onDelete: context.delete,
onClose: context.close,
});
return true;
},
};
```
The editor helpers still route changes through permissions, conflicts, `event-scheduler-before-event-change`, local mutation, post-commit events, and remote mutation hooks.
## Interaction callbacks
Use named before hooks to validate or cancel user actions before local scheduler state changes. Return `false` to cancel with the default message, or return a string to cancel with a product-specific validation message.
| Before hook | Action |
| --- | --- |
| `beforeEventCreate` | Create from slot, editor create, template create, recurrence create |
| `beforeEventUpdate` | Editor/API updates and status changes |
| `beforeEventMove` | Drag move and bulk move |
| `beforeEventResize` | Drag resize |
| `beforeEventDelete` | Delete and bulk delete |
| `beforeEventDuplicate` | Duplicate selected events |
| `beforeEventLock` | Lock or unlock changes |
| `beforeSlotSelect` | Empty-slot selection/click before create behavior |
Final callbacks fire after the scheduler has committed local state and include enough context to update app state or persist to a backend.
| Final callback | Action |
| --- | --- |
| `onEventCreate` | Event created, including paste/duplicate/template-created events |
| `onEventUpdate` | Event updated or status changed |
| `onEventMove` | Event moved |
| `onEventResize` | Event resized |
| `onEventDelete` | Event deleted |
| `onEventDuplicate` | Duplicated event created |
| `onEventLock` | Event locked or unlocked |
| `onEventCopy` | Event copied into the scheduler clipboard |
| `onEventPaste` | Pasted event created |
| `onSelectionChange` | Event selection changed |
| `onSlotSelect` | Empty slot selected/clicked |
| `contextMenu.onContextMenuAction` | Scheduler context-menu item selected |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeEventMove: ({ event, changes }) => {
if (event?.locked) return 'Locked events cannot be moved.';
return validateMoveOnServerCache(event, changes);
},
beforeEventResize: ({ event, changes }) =>
isOutsidePolicyWindow(event, changes) ? 'Outside policy window.' : undefined,
beforeEventDelete: ({ event }) =>
event?.metadata?.protected ? 'Protected events cannot be deleted.' : undefined,
beforeSlotSelect: ({ date, startDateTime }) =>
isClosedForBooking(date, startDateTime) ? 'This slot is closed.' : undefined,
onEventMove: ({ event, previousEvent }) =>
saveEventMove({ event, previousEvent }),
onEventResize: ({ event, previousEvent }) =>
saveEventResize({ event, previousEvent }),
onEventDelete: ({ event }) =>
archiveDeletedEvent(event),
onSelectionChange: ({ eventIds }) =>
setSelectedPlannerEvents(eventIds),
contextMenu: {
onContextMenuAction: ({ itemId, target, segment, slot }) =>
trackSchedulerCommand({ itemId, target, eventId: segment?.event.id, slot }),
},
};
```
The lower-level `event-scheduler-before-event-change`, `event-scheduler-before-event-select`, `event-scheduler-before-slot-select`, `event-scheduler-event-created`, `event-scheduler-event-changed`, `event-scheduler-event-deleted`, and `event-scheduler-event-selected` DOM events remain available for integrations that prefer event listeners. Call `preventDefault()` on the before events to cancel the pending scheduler action. `validateMutation` still runs in the same mutation pipeline for shared validation logic.
## Selection, clipboard, and keyboard
Scheduler selection supports selected events, selected slots, selected time ranges, multi-select, range select, copy, paste, duplicate, and keyboard-driven commands. Use the cancelable `event-scheduler-before-event-select` event when the host application owns selected event state: prevent the pending selection, update application state, then pass the accepted ids back through `selection.selectedEventIds`.
```ts
let selectedEventIds: readonly string[] = [];
grid.addEventListener('event-scheduler-before-event-select', (event) => {
event.preventDefault();
selectedEventIds = event.detail.eventIds;
renderScheduler();
});
function renderScheduler() {
grid.eventScheduler = {
...grid.eventScheduler,
selectionMode: 'multiple',
selection: {
...grid.eventScheduler?.selection,
selectedEventIds,
selectedSlot,
selectedRange,
clipboard: true,
copyOffsetDays: 7,
onChange: ({ eventIds }) => setSelectedEventIds(eventIds),
},
onSelectionChange: ({ eventIds, events }) =>
syncInspectorSelection({ eventIds, events }),
onSlotSelect: (slot) =>
setSelectedSlot(slot),
};
}
```
Accepted selections still emit `event-scheduler-event-selected`, `onSelectionChange`, and `selection.onChange`. A prevented selection does not emit final selection callbacks; the host re-applies its accepted state through the config.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeSlotSelect: ({ date }) =>
isClosedForBooking(date) ? 'This slot is closed.' : undefined,
};
grid.addEventListener('event-scheduler-before-slot-select', (event) => {
if (isReadOnlyResource(event.detail.resourceId)) {
event.preventDefault();
}
});
```
Use `clipboardAdapter` when copy/paste must integrate with product state, permissions, or a custom clipboard store. Paste remains disabled when there is no internal payload and `clipboardAdapter.hasPayload()` is false.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
clipboardAdapter: {
hasPayload: () => appClipboard.has('scheduler-events'),
read: () => appClipboard.get('scheduler-events'),
write: (payload) => appClipboard.set('scheduler-events', payload),
clear: () => appClipboard.delete('scheduler-events'),
},
onCopy: ({ eventIds }) => trackCopy(eventIds),
onPaste: ({ event }) => persistCreatedEvent(event),
onDuplicate: ({ event }) => persistCreatedEvent(event),
};
```
Keyboard shortcuts can be disabled globally, disabled per command, or rebound. Use `Mod` for Cmd on macOS and Ctrl on Windows/Linux.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
keyboardShortcuts: {
enabled: true,
helpPanel: true,
shortcuts: {
copy: 'Mod+c',
paste: 'Mod+v',
duplicate: 'Mod+Shift+d',
delete: false,
lock: false,
today: ['t', 'Home'],
},
},
};
```
Available keyboard commands are `escape`, `help`, `today`, `search`, `previous`, `next`, `cyclePrevious`, `cycleNext`, `open`, `create`, `edit`, `lock`, `delete`, `copy`, `paste`, `duplicate`, and `viewWeek`.
---
# Examples
URL: https://pro.rv-grid.com/guides/event-scheduler/examples/
Source: src/content/docs/guides/event-scheduler/examples.mdx
Description: Working Event Scheduler examples for setup, templates, menus, availability, read-only mode, cancelable selection, and custom themes.
This page collects small, product-neutral Event Scheduler examples. The same APIs work for shifts, bookings, appointments, staffing, rooms, maintenance, tasks, equipment, and team planning.
The runnable demo catalog includes these complete scheduler stories:
| Demo | Use it for |
| --- | --- |
| Event Staff Scheduler | Resource timeline, coverage, conflicts, templates, saved views, and remote-mode controls. |
| Employee Shift Planner | Week view with days on the x-axis, time on the y-axis, drag-create, move, resize, delete, and closed hours. |
| Room Booking Scheduler | Resource booking with availability windows, blocked time, conflicts, and grouped resources. |
| Equipment / Machine Scheduler | Maintenance planning, utilization, capacity rows, and exportable workload data. |
## Basic weekly scheduler
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
} from '@revolist/scheduler';
const events: EventSchedulerEventEntity[] = [
{
id: 'booking-1',
title: 'Customer onboarding',
resourceId: 'room-a',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T11:00:00.000Z',
type: 'booking',
status: 'confirmed',
},
];
const eventScheduler: EventSchedulerConfig = {
view: 'week',
weekStartDate: '2026-06-08',
weekStartsOn: 1,
visibleDays: [1, 2, 3, 4, 5],
timeRange: { start: '06:00', end: '22:00' },
slotMinutes: 30,
locale: 'en-US',
timeZone: 'UTC',
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
todayHighlight: true,
showCurrentTimeMarker: true,
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.source = events;
```
## Custom event template
```ts
grid.eventScheduler = {
...grid.eventScheduler,
eventProperties: ({ event, isSelected, hasConflict, isLocked }) => ({
class: [
'planner-event',
isSelected ? 'planner-event--selected' : '',
hasConflict ? 'planner-event--conflict' : '',
isLocked ? 'planner-event--locked' : '',
].filter(Boolean).join(' '),
style: {
'--planner-event-accent': event.color ?? '#2563eb',
},
}),
eventContentTemplate: (h, { event, resource, start, end, isShortEvent }) =>
h('span', { class: 'planner-event__body' }, [
h('strong', { class: 'planner-event__title' }, event.title),
isShortEvent ? null : h('span', { class: 'planner-event__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`),
resource ? h('small', { class: 'planner-event__resource' }, resource.name) : null,
]),
eventTooltipTemplate: ({ event, resource, duration }) =>
`${event.title}\n${resource?.name ?? 'Unassigned'}\n${duration} minutes`,
};
```
## Custom day header
```ts
grid.eventScheduler = {
...grid.eventScheduler,
dayHeaderFormatter: (date) =>
new Intl.DateTimeFormat('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
}).format(date),
dayHeaderTemplate: (h, context) =>
h('span', { class: 'planner-day-header' }, [
h('strong', null, context.defaultLabel),
context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null,
context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null,
h('small', null, `${context.eventCount ?? 0} events`),
]),
dayHeaderProperties: ({ today, weekend, holiday }) => ({
class: [
today ? 'planner-day-header--today' : '',
weekend ? 'planner-day-header--weekend' : '',
holiday ? 'planner-day-header--holiday' : '',
].filter(Boolean).join(' '),
}),
};
```
## Custom slot context menu
```ts
grid.eventScheduler = {
...grid.eventScheduler,
contextMenu: {
enabled: true,
getSlotContextMenuItems: ({ slot }) => slot
? [
{
id: 'create-booking',
label: 'Create booking here',
shortcut: 'C',
action: () => createBooking(slot),
},
{
id: 'hold-slot',
label: 'Block this time',
group: 'availability',
action: () => createHold(slot),
},
]
: [],
onContextMenuAction: ({ itemId, target, slot }) =>
analytics.track('scheduler_menu_action', { itemId, target, slot }),
},
};
```
## Custom current time marker
```ts
grid.eventScheduler = {
...grid.eventScheduler,
showCurrentTimeMarker: true,
currentTimeUpdateInterval: 60_000,
currentTimeLabelFormatter: ({ dateTime }) =>
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
}).format(new Date(dateTime)),
currentTimeMarkerProperties: ({ orientation }) => ({
class: `planner-now planner-now--${orientation}`,
style: {
'--event-scheduler-current-time-color': '#dc2626',
},
}),
currentTimeMarkerTemplate: (h, { label }) =>
h('span', { class: 'planner-now__label' }, label),
};
```
## Closed hours and availability
```ts
grid.eventScheduler = {
...grid.eventScheduler,
workingHours: {
byDay: {
1: { start: '08:00', end: '18:00' },
2: { start: '08:00', end: '18:00' },
3: { start: '08:00', end: '18:00' },
4: { start: '08:00', end: '18:00' },
5: { start: '08:00', end: '16:00' },
0: false,
6: false,
},
},
closedHours: [
{
id: 'lunch',
days: [1, 2, 3, 4, 5],
ranges: { start: '12:00', end: '13:00' },
title: 'Closed',
reason: 'Lunch break',
},
],
closedSlotTooltip: ({ title, reason }) =>
[title, reason].filter(Boolean).join(' - '),
closedSlotTemplate: (h, { reason }) =>
h('span', { class: 'planner-slot__closed' }, reason ?? 'Closed'),
isTimeSlotAvailable: ({ date, startMinutes }) =>
date !== '2026-06-19' && startMinutes >= 8 * 60,
};
```
## Read-only mode
```ts
grid.eventScheduler = {
...grid.eventScheduler,
editable: false,
allowCreate: false,
allowMove: false,
allowResize: false,
allowDelete: false,
keyboardShortcuts: {
enabled: true,
shortcuts: {
copy: 'Mod+c',
paste: false,
duplicate: false,
delete: false,
lock: false,
},
},
};
```
Read-only mode keeps rendering, selection, tooltips, context menus, and custom templates active while blocking scheduler mutations.
## Cancelable selection state
```ts
let selectedEventIds: readonly string[] = [];
grid.addEventListener('event-scheduler-before-event-select', (event) => {
event.preventDefault();
selectedEventIds = event.detail.eventIds.map(String);
renderScheduler();
});
function renderScheduler() {
grid.eventScheduler = {
...grid.eventScheduler,
selectionMode: 'multiple',
selection: {
selectedEventIds,
clipboard: true,
onChange: ({ eventIds }) => {
selectedEventIds = eventIds.map(String);
renderScheduler();
},
},
onSelectionChange: ({ events }) =>
inspector.show(events),
onEventMove: ({ events }) => {
grid.source = [...events];
void saveEvents(events);
},
};
}
```
Use the before-select event when selection lives in application state. Prevent the pending scheduler selection, update host state, then pass the accepted ids back through `selection.selectedEventIds`. Event arrays, visible ranges, and filters follow the same ownership model: the scheduler emits callbacks and the host updates `eventScheduler`, `source`, and related props.
## Custom styling and theme
```css
.booking-scheduler revo-grid {
--event-scheduler-accent: #0f766e;
--event-scheduler-accent-soft: #ccfbf1;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-current-time-color: #dc2626;
--event-scheduler-weekend-bg: rgba(15, 118, 110, 0.05);
--event-scheduler-event-bg: #eef7f3;
--event-scheduler-event-color: #0f766e;
}
.planner-event--conflict {
--event-scheduler-event-color: #b45309;
--event-scheduler-event-bg: #fff7ed;
}
.planner-day-header--today {
box-shadow: inset 0 -2px 0 var(--event-scheduler-accent);
}
```
Use CSS variables for broad theme changes. Use `eventProperties`, `dayHeaderProperties`, `slotProperties`, `closedSlotProperties`, and `currentTimeMarkerProperties` when styling depends on scheduler state.
## Related guides
- [Week View](../week-view/) covers day/time configuration, visible range, week start, locale, timezone, and edit ownership.
- [Customization](../customization/) documents the full visual customization category. Use [Event Rendering](../customization/events/) for templates and tooltips, [Slots, Hours, and Markers](../customization/slots-hours/) for slot and current-time examples, [Context Menus](../customization/context-menu/) for commands, and [Styling and Themes](../customization/styling/) for CSS hooks.
- [Availability](../scheduling-rules/availability/) covers calendars, working hours, blocked time, breaks, holidays, and outside-hours styling.
- [Editing and Interaction](../event-editing/) covers read-only mode, permissions, cancelable hooks, mutation events, selection, clipboard, keyboard shortcuts, custom editors, and persistence.
---
# Export
URL: https://pro.rv-grid.com/guides/event-scheduler/export/
Source: src/content/docs/guides/event-scheduler/export.mdx
Description: Export scheduler events, workload, conflicts, coverage gaps, visible schedules, and print views.
The event scheduler exports data from the current projection, so filters, visible resources, conflicts, coverage, and utilization match what the user is reviewing.
```ts
const scheduler = (await grid.getPlugins()).find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
const projection = scheduler.getProjection();
const eventsCsv = scheduler.exportEvents({ format: 'csv' });
const workloadJson = scheduler.exportResourceWorkload({ format: 'json' });
const conflictsCsv = scheduler.exportConflicts({ format: 'csv' });
const coverageCsv = scheduler.exportCoverage({ format: 'csv', gapsOnly: true });
const visibleCsv = scheduler.exportVisibleSchedule({ format: 'csv' });
download(eventsCsv.content, eventsCsv.fileName, eventsCsv.mimeType);
```
For printable schedules, create an HTML view from the same projection.
```ts
const print = scheduler.createPrintView({
title: 'Weekly Staff Schedule',
includeEvents: true,
includeWorkload: true,
includeConflicts: true,
includeCoverage: true,
});
const printWindow = window.open('', '_blank');
printWindow?.document.write(print.html);
printWindow?.print();
```
## Production Checklist
- Use stable ids for events, resources, assignments, templates, and coverage requirements.
- Store scheduler datetimes as ISO strings and define a product time-zone policy before persisting user edits.
- Keep `source` controlled by app state and persist every created, changed, and deleted event array.
- Enable `allowCreate` intentionally. It defaults to false.
- Add permission rules for locked resources, read-only events, completed statuses, and protected date ranges.
- Choose conflict policy deliberately. Use `prevent` or error rules for hard booking constraints.
- Use availability for real scheduling constraints and `nonWorkingTime` for visual closed-time guidance.
- Use `resourceGrouping`, `filters`, and `savedViews` for planner navigation instead of mutating source arrays.
- Use `coverage` and `utilization` only when the resource metadata needed for planning is present.
- Add remote validation and rollback for server-backed schedulers.
- Keep keyboard shortcuts enabled unless the host app owns all scheduler keyboard behavior.
- Expose export or print workflows from the plugin projection when planners need to share schedules.
- Test drag-create, move, resize, delete, selection, keyboard shortcuts, conflicts, and remote failure paths in the target framework.
---
# Grouping
URL: https://pro.rv-grid.com/guides/event-scheduler/grouping/
Source: src/content/docs/guides/event-scheduler/grouping.mdx
Description: Group scheduler resources by team, department, location, parent tree, or custom business rules.
Use `resourceGrouping` for team, department, location, room-building, machine-line, or sprint-team grouping. Parent-child resources can render as a tree.
```ts
grid.eventSchedulerResources = [
{ id: 'north', name: 'North Campus', role: 'Location' },
{ id: 'room-a', parentId: 'north', name: 'Room A', role: 'Room', metadata: { floor: '1' } },
{ id: 'room-b', parentId: 'north', name: 'Room B', role: 'Room', metadata: { floor: '2' } },
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
resourceGrouping: {
enabled: true,
tree: true,
groupBy: (resource) => String(resource.metadata?.floor ?? resource.group ?? 'Other'),
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
};
```
Group rows aggregate workload, coverage, and utilization summaries when those features are enabled.
## Resource Planner Patterns
Use `resourceTimeline` when resources are the main rows and time moves horizontally.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-14' },
resourceColumnSize: 220,
timelineColumnSize: 72,
resourceGrouping: {
enabled: true,
groupBy: (resource) => resource.metadata?.location as string,
tree: true,
collapsed: false,
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
resourceMetaFormatter: (resource) =>
[resource.role, resource.metadata?.location].filter(Boolean).join(' - '),
};
```
Events without `resourceId`, `resourceIds`, or side-channel assignments render in the generated unassigned row when `unassigned.enabled` is not false.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
unassigned: {
enabled: true,
showCount: true,
requiredRoleField: 'metadata.requiredRole',
filter: { status: ['draft', 'planned'], role: 'Technician' },
counterFormatter: (summary) => `${summary.visibleCount} open requests`,
requiredRoleFormatter: ({ role }) => `Need ${role}`,
assignmentValidator: ({ event, resource }) => {
const required = event.metadata?.requiredRole;
const skills = Array.isArray(resource.metadata?.skills)
? resource.metadata.skills
: [];
return !required || skills.includes(required) || `${resource.name} cannot fill ${required}.`;
},
},
};
```
Coverage compares required capacity with assigned event capacity in the visible range. Utilization calculates scheduled workload by resource.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
coverage: {
enabled: true,
capacityField: 'capacity',
countEvent: ({ event, resource }) =>
event.status === 'cancelled' ? false : resource.capacity ?? 1,
},
utilization: {
enabled: true,
minHoursField: 'metadata.minHoursPerWeek',
targetHoursField: 'metadata.targetHoursPerWeek',
maxHoursField: 'metadata.maxHoursPerWeek',
excludeStatuses: ['cancelled', 'blocked'],
},
};
```
Use `filters` and `savedViews` to reduce projected data without mutating source arrays:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
filters: {
search: 'security',
resourceRoles: 'Security',
eventStatuses: ['planned', 'confirmed'],
conflictsOnly: false,
unassignedOnly: false,
},
activeSavedViewId: 'security-week',
savedViews: {
views: [
{ id: 'conflicts', label: 'Conflicts only', filters: { conflictsOnly: true } },
{ id: 'unassigned', label: 'Unassigned', filters: { unassignedOnly: true } },
{
id: 'security-week',
label: 'Security team',
dateRange: { start: '2026-06-08', end: '2026-06-12' },
filters: { resourceRoles: 'Security' },
},
],
},
};
```
The projection exposes `filterSummary` with visible and hidden resource/event counts.
---
# Getting Started
URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/
Source: src/content/docs/guides/event-scheduler/introduction/index.mdx
Description: Set up the standalone RevoGrid Event Scheduler for staff scheduling, shift planning, room booking, equipment planning, and team calendars.
`EventSchedulerPlugin` from `@revolist/scheduler` turns RevoGrid into a commercial scheduler for events on time grids. Use it for staff rosters, employee shifts, room reservations, machine plans, field crews, support queues, and sprint capacity.
The scheduler supports calendar and resource timeline views, editable event blocks, resources, assignments, calendars, availability, conflicts, grouping, templates, recurrence helpers, remote data, custom renderers, and export helpers.
## Install
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/scheduler
```
Import the Pro and Scheduler styles once in your application entry:
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/scheduler/styles.css';
```
Create an empty grid and provide scheduler data through side-channel props. The scheduler owns the generated grid projection; your app owns persistence.
## Minimal Astro setup
The only required event fields are `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `status`, `type`, `metadata`, and resources when your product needs labels, capacity rows, filtering, styling, or persistence fields.
## Minimal TypeScript setup
If the grid already exists in your app, set the same properties directly:
```ts
import { EventSchedulerPlugin } from '@revolist/scheduler';
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
};
grid.source = [
{
id: 'booking-1',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
},
];
```
## What Event Scheduler does
Event Scheduler uses RevoGrid as the rendering and interaction surface for time-based planning. Instead of building ordinary data rows and columns yourself, you give the grid scheduler configuration, events, resources, and optional planning datasets. The plugin projects that data into timeline columns, resource rows, event blocks, drag handles, conflict indicators, non-working time, and editor interactions.
Use it when the main question is "who or what is booked, when, and against which capacity?" Common use cases include:
- staff and employee shift scheduling
- room, desk, vehicle, or equipment booking
- machine and production line planning
- field crew, support queue, and service dispatch calendars
- sprint, delivery, or team-capacity calendars
## Core concepts
- `events` are the scheduled blocks. Each event needs an `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `resourceIds`, or `assignmentIds` when the event needs a label or belongs to a person, room, machine, team, or other capacity.
- `resources` are the schedulable rows in resource planning. A resource can be a person, room, vehicle, machine, location, team, department, or parent group.
- `views` decide how time is shown. Use `day`, `week`, or `month` for calendar-style time grids, and `resourceTimeline` when each resource needs its own row across a visible date or time range.
- `projection` is the derived scheduler model. The plugin combines config, events, resources, assignments, availability, templates, coverage requirements, and filters into visible rows, columns, event lanes, conflict markers, group summaries, and overlays.
- `source` is the host-owned event input. The scheduler emits authored `gridedit` changes after accepted local, history, and remote mutations so the application can update its source state without triggering source lifecycle events for Scheduler-internal rendering.
## Data ownership
Use the scheduler props as the public data boundary:
- **Events**: Every event needs `id`, `startDateTime`, and `endDateTime`. `title`, `resourceId`, `resourceIds`, `status`, and `type` are optional. Use resource fields for resource timelines or leave them out for unassigned demand.
- **Resources**: Use resources for people, rooms, vehicles, machines, teams, locations, or parent groups. `calendarId`, `capacity`, `group`, `role`, and `metadata` feed calendars, grouping, coverage, utilization, and filters.
- **Assignments**: Use assignments when your system stores event-to-resource links separately from the event record. They are merged with `resourceId` and `resourceIds` during projection.
- **Availability**: Use dated `working`, `blocked`, `holiday`, and `break` windows for operational exceptions such as PTO, maintenance, temporary opening hours, or one-off closures.
- **Templates and coverage**: Use templates for repeated event creation and coverage requirements for required capacity/headcount checks.
`locked` and `readonly` events or resources reject normal edit flows. `color`, `className`, `status`, `type`, `category`, and `metadata` let products style, classify, filter, or persist scheduler records without replacing the scheduler model.
## Main props
- **`eventScheduler`**: Main `EventSchedulerConfig`. Controls the active view, visible date range, slot size, visible hours, editing permissions, event layout, conflicts, calendars, filters, labels, formatters, templates, remote loading, and customization hooks.
- **`source`**: The event blocks to render. Events contain stable ids and start/end datetimes, plus optional titles, status/type metadata, and resource or assignment links.
- **`eventSchedulerResources`**: The schedulable resources shown in resource-timeline flows. Use it for people, rooms, machines, teams, locations, vehicles, or parent groups.
- **`eventSchedulerAssignments`**: Optional assignment link data when events and resources are connected by a separate system table instead of direct `resourceId` fields.
- **`eventSchedulerAvailability`**: Optional working, blocked, holiday, or break windows for resource-specific availability and outside-availability conflict checks.
- **`eventSchedulerTemplates`**: Optional reusable event templates for quick creation and recurrence helpers.
- **`eventSchedulerCoverageRequirements`**: Optional staffing or capacity requirements used by coverage summaries and gap highlighting.
- **`columns`**: Usually empty for scheduler pages. The scheduler owns visual rows and columns internally while `source` remains the host-owned event input.
If application records use different property names, map the shared view meanings
once in `eventScheduler.fields`. The same mapping vocabulary is intended for
Scheduler, Gantt, and Kanban:
```ts
grid.eventScheduler = {
weekStartDate: '2026-09-14',
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
};
```
Mappings select direct source properties and do not convert values. Scheduler
reads and writes the mapped fields while preserving other application fields.
`progress` is accepted so one mapping object can be shared by views; Scheduler
preserves it but does not render or edit progress.
Scheduler treats source rows as immutable. When it materializes an authored
mutation, unchanged rows keep their original references and only changed or
created rows are allocated. Replace application rows or the source array when
state changes; do not mutate source row objects in place. History uses shallow
array snapshots over those immutable rows so deleted records and custom fields
remain available to undo without cloning the complete schedule.
## Basic setup
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
type EventSchedulerResourceEntity,
} from '@revolist/scheduler';
const resources: EventSchedulerResourceEntity[] = [
{ id: 'room-a', name: 'Room A', role: 'Room', group: 'Conference' },
{ id: 'room-b', name: 'Room B', role: 'Room', group: 'Conference' },
];
const events: EventSchedulerEventEntity[] = [
{
id: 'booking-1',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
title: 'Customer workshop',
resourceId: 'room-a',
status: 'confirmed',
type: 'booking',
},
];
const eventScheduler: EventSchedulerConfig = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-08' },
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
conflicts: { enabled: true, policy: 'mark', scope: 'same-resource' },
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.eventSchedulerResources = resources;
grid.source = events;
grid.columns = [];
```
For framework wrappers, bind `eventScheduler`, `eventSchedulerResources`, and `source` as grid properties.
## Blocking overlap and conflicts
Existing data can still render with conflict markers so users can review and fix it. To block future overlapping create, move, resize, and edit actions for the same resource, keep the scheduler in mark mode and promote only `overlap` to an error:
```ts
grid.eventScheduler = {
...grid.eventScheduler,
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
rules: { overlap: 'error' },
},
};
```
Error conflicts render with the built-in red conflict state. The same conflict result is used by the mutation pipeline, so an overlap introduced by create, move, resize, or edit is rejected before local events are committed.
## Where to start
1. Choose the view. Use `week` for a shift-style calendar, or `resourceTimeline` when each resource needs a row.
2. Define the visible range with `weekStartDate`, `dateRange`, `visibleDays`, `slotMinutes`, and `timeRange`.
3. Provide `eventSchedulerResources` when events need to be placed against staff, rooms, equipment, teams, or other capacity.
4. Provide `source` with ISO datetime strings and stable ids.
5. Add `calendars` for recurring working rules and `eventSchedulerAvailability` for dated working windows, breaks, holidays, or blocked time.
6. Enable editing only when the host app handles emitted changes. Start with `editable: false` for read-only schedules, then turn on `allowCreate`, `allowMove`, `allowResize`, and `allowDelete` as your persistence flow is ready.
7. Add conflict rules, templates, remote data, custom labels, or renderers after the basic schedule renders.
## Handling edits
The scheduler emits a cancelable before-change event before it commits local event changes. This follows the RevoGrid event pattern: call `preventDefault()` on the before event to cancel the mutation.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.event?.locked) {
event.preventDefault();
}
});
```
`event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the mutation succeeds and after `grid.source` has been updated. Use them for persistence and framework state sync, not cancellation:
```ts
const syncEvents = (event: Event) => {
const events = (event as CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>).detail.events;
setApplicationEvents([...events]);
void saveSchedule(events);
};
grid.addEventListener('event-scheduler-event-created', syncEvents);
grid.addEventListener('event-scheduler-event-changed', syncEvents);
grid.addEventListener('event-scheduler-event-deleted', syncEvents);
```
If your toolbar owns the current range or active view, also handle navigation and view requests:
```ts
grid.addEventListener('event-scheduler-navigate-request', (event) => {
const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail;
// Update weekStartDate/dateRange in grid.eventScheduler here.
});
grid.addEventListener('event-scheduler-view-request', (event) => {
const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail;
// Update grid.eventScheduler.view here.
});
```
For remote products, use the same ownership model: the scheduler can optimistically emit changes, but your app or `remote` hooks decide when backend mutations are committed, rejected, or rolled back.
## Next guides
- [Vertical vs Horizontal Time](./vertical-vs-horizontal/) helps choose between calendar-style and resource-timeline scheduling.
- [Timeline Views](../timeline-views/) explains day, week, month, and resource timeline configuration.
- [Week View](../week-view/) is a practical recipe for shift-style vertical week calendars.
- [Editing and Interaction](../event-editing/) covers create, move, resize, delete, editors, selection, clipboard, keyboard shortcuts, permissions, and validation.
- [Conflicts](../scheduling-rules/conflicts/) covers overlap, availability, locked-change, duration, capacity, prevention events, and assignment checks.
- [Event Scheduler Calendars](../scheduling-rules/calendars/) covers working days, holidays, and resource-specific operating hours.
- [Scheduling Rules](../scheduling-rules/) explains how calendars, availability, visual non-working time, and conflict policies work together.
- [Remote Data](../remote-data/) covers loading ranges, optimistic mutations, rollback, and load-more flows.
- [Customization](../customization/) groups labels, event rendering, layout/cells, slots/hours, conflicts/states, context menus, styling, interaction, and editor hooks.
- [Examples](../examples/) collects small product-neutral examples for setup, event templates, day headers, slots, current-time markers, context menus, availability, read-only mode, cancelable selection state, and custom themes.
Start with the Event Staff Scheduler demo for the full business story, or the Employee Shift Planner demo for a focused week-view shift workflow.
---
# Vertical vs Horizontal Time
URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/vertical-vs-horizontal/
Source: src/content/docs/guides/event-scheduler/introduction/vertical-vs-horizontal.mdx
Description: Choose between calendar-style vertical scheduling and resource timeline scheduling.
Scheduling views usually answer one of three questions:
- "What happens during this day or week?"
- "What is happening across this month or year?"
- "Which resource is booked across this date range?"
Use a vertical time view for the first question, a calendar view for the second, and a horizontal resource timeline for the third.
## Vertical Time Scheduling
Vertical time scheduling is the familiar calendar layout. Time moves down the page, days are arranged across columns, and an event duration is shown by the event height.
This layout is best when users think in days, hours, and appointments:
| Good fit | Why |
| :-- | :-- |
| Staff shifts | Managers scan a day or week and adjust start/end times. |
| Appointments | Users compare open slots in a familiar calendar shape. |
| Room bookings for one location | The day is more important than a long resource list. |
| Personal or team calendars | The main task is finding a time window. |
Configure it with `day` or `week` views:
```ts
const verticalWeek = {
view: 'week',
weekStartDate: '2026-06-08',
visibleDays: [1, 2, 3, 4, 5],
slotMinutes: 30,
timeRange: { start: '08:00', end: '18:00' },
timeColumnSize: 88,
dayColumnSize: 180,
};
```
Choose vertical time when the primary action is creating, moving, or resizing events inside a day or week.
## Calendar Overview
Use `month` for a conventional seven-column calendar with whole-day creation and day-preserving move/resize interactions. Use `year` for a twelve-mini-month overview that drills down by emitting a controlled month-view request. Calendar rows fill the data viewport, while the shared scheduler model continues to supply recurrence, filtering, availability, conflicts, selection, and event customization.
```ts
const calendarOverview = {
view: 'month', // change to 'year' for the twelve-month overview
weekStartDate: '2026-07-15',
weekStartsOn: 1,
};
```
## Horizontal Resource Timeline
Horizontal scheduling puts resources on rows and time on columns. Time moves left to right, and an event duration is shown by the event width.
This layout is best when users compare many resources over the same window:
| Good fit | Why |
| :-- | :-- |
| Multi-room booking | Rooms stay fixed on the left while the schedule scrolls horizontally. |
| Equipment planning | Users compare utilization across machines, vehicles, or tools. |
| Field crews | Dispatchers see who is available across several days. |
| Open demand planning | Unassigned work can sit beside staffed resources. |
Configure it with `resourceTimeline`:
```ts
const horizontalTimeline = {
view: 'resourceTimeline',
dateRange: { start: '2026-06-08', end: '2026-06-12' },
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
resourceColumnSize: 240,
timelineColumnSize: 72,
};
```
Choose a horizontal timeline when the primary action is comparing capacity, coverage, and conflicts across resources.
## Same Data, Different Projection
Both orientations use the same scheduler data:
- events define the booked work
- resources define people, rooms, machines, or teams
- assignments connect events to resources when the model needs many-to-many scheduling
- availability, calendars, conflicts, templates, and remote loading work across both layouts
The difference is projection. Vertical views emphasize time within a day or week, calendar views emphasize dates across a month or year, and horizontal views emphasize resources across a date range.
## Choosing a Default
Start with vertical time scheduling when users mostly edit individual events and think in daily calendars.
Start with a horizontal resource timeline when users mostly compare many resources, load large schedules from a server, or need coverage and utilization signals.
Many applications expose both. For example, a shift planner can use a week view for fast editing and a resource timeline for staffing coverage.
---
# Remote Data
URL: https://pro.rv-grid.com/guides/event-scheduler/remote-data/
Source: src/content/docs/guides/event-scheduler/remote-data.mdx
Description: Load scheduler resources and events from a server, refresh visible ranges, and handle optimistic save failures.
Use `remote` when events or resources are too large to ship up front, or when the scheduler must load only the visible date range.
For a comparison of host-owned full saves, host-owned incremental saves, and remote-backed saves, see [Data ownership and save flows](/guides/event-scheduler/event-editing/#data-ownership-and-save-flows).
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
remote: {
enabled: true,
loadEvents: async ({ dateRange, resourceIds }) => {
const params = new URLSearchParams({
start: dateRange.start,
end: dateRange.end,
resources: resourceIds?.join(',') ?? '',
});
const response = await fetch(`/api/scheduler/events?${params}`);
return { events: await response.json() };
},
loadResources: async () => {
const response = await fetch('/api/scheduler/resources');
return { resources: await response.json() };
},
commitMutation: async (request) => {
const response = await fetch('/api/scheduler/mutations', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error('Save failed');
}
return { accepted: true, events: await response.json() };
},
},
};
```
The resource timeline demo includes remote-mode controls for refresh, load-more resources, and simulated save failure.
## Remote Options and State
Remote mode can load events, resources, availability, unassigned demand, and coverage for the visible range. It can also validate and commit accepted mutations.
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-14' },
remote: {
enabled: true,
mode: 'remote',
debounceMs: 150,
overscanDays: 1,
resourcePageSize: 50,
loadEvents: ({ dateRange, filters, signal }) =>
api.loadEvents({ dateRange, filters }, { signal }),
loadResources: ({ cursor, limit, signal }) =>
api.loadResources({ cursor, limit }, { signal }),
loadAvailability: ({ dateRange, signal }) =>
api.loadAvailability({ dateRange }, { signal }),
loadCoverage: ({ dateRange, signal }) =>
api.loadCoverage({ dateRange }, { signal }),
validateMutation: ({ action, event, previousEvent }) =>
api.validateScheduleChange({ action, event, previousEvent }),
commitMutation: ({ action, event, previousEvent, events }) =>
api.saveScheduleChange({ action, event, previousEvent, events }),
optimistic: true,
rollbackOnError: true,
refreshPolicy: 'visible-range',
},
};
```
Remote modes:
| Mode | Behavior |
| :-- | :-- |
| `local` | Remote controller is effectively off unless enabled hooks are used by the host. |
| `remote` | Visible data is loaded through remote hooks. |
| `hybrid` | Host can combine local side-channel data with remote range data. |
Remote state is available from `scheduler.getRemoteState()`. The plugin emits:
| Event | Purpose |
| :-- | :-- |
| `event-scheduler-remote-load-start` | A visible-range load begins. |
| `event-scheduler-remote-load-success` | A load target resolves. |
| `event-scheduler-remote-load-error` | A load target fails. |
| `event-scheduler-remote-mutation-pending` | A remote mutation starts. |
| `event-scheduler-remote-mutation-committed` | The backend accepts a mutation. |
| `event-scheduler-remote-mutation-rejected` | The backend rejects a mutation. |
| `event-scheduler-remote-state-change` | Loading, error, pending, paging, or empty state changes. |
Use `scheduler.refreshVisibleRange()` to reload the current range and `scheduler.loadMoreResources()` when paged resources report `hasMoreResources`.
---
# Scheduling Rules
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/index.mdx
Description: Understand how Event Scheduler combines availability, calendars, and conflict policies into scheduling rules.
Event Scheduler separates the source of a scheduling rule from the way that rule is enforced. Availability and calendars describe when a resource can be used. Conflict settings decide whether a violation is shown as a warning, ignored, confirmed, or rejected.
This matters because closed time is not a single setting. A lunch break, a public holiday, an operating calendar, and a one-off shift extension all participate in the same conflict model, but they do not have the same precedence.
## Start With The Outcome
Most applications choose one of these setups:
| Goal | Configuration |
| :-- | :-- |
| Show conflicts but allow the user to save | `conflicts: { enabled: true, policy: 'mark' }` |
| Block every detected conflict | `conflicts: { enabled: true, policy: 'prevent' }` |
| Block only selected conflict types | `conflicts: { enabled: true, policy: 'mark', rules: { ... } }` |
| Turn conflict detection off | `conflicts: { enabled: false }` |
Use `rules` when different mistakes need different behavior:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
},
},
};
```
In this example:
- `overlap: 'error'` rejects an event when it overlaps another event in the same resource row.
- `'outside-availability': 'error'` rejects an event when it is outside the resource's working time.
- `'blocked-time': 'error'` rejects an event when it touches a blocked, holiday, or break window.
`error` means the local scheduler mutation is not committed for the changed event. Create, move, resize, edit, paste, template, recurrence, and bulk changes are checked against the projected next schedule before the event list is updated. Existing unrelated conflicts can remain on screen; they do not block an unrelated edit unless the edited event is part of a blocking conflict.
If you only want to display a conflict, use `warning`. If you want to hide a conflict from scheduler results, use `ignore`. If you want to require a custom confirmation flow, use `confirm`.
## Rule Names
The three most common rule names answer different user questions:
| Rule | User-facing meaning | Triggered by | Typical setting |
| :-- | :-- | :-- | :-- |
| `overlap` | "This booking collides with another booking." | Two visible event segments overlap on the same date. By default this is checked within the same resource. | Use `warning` when overlaps are allowed but should be visible. Use `error` for rooms, equipment, or people that cannot be double-booked. |
| `outside-availability` | "This booking is outside allowed working time." | The event is not fully covered by a dated `kind: 'working'` availability entry, or by the resolved calendar when no dated working availability applies. | Use `error` when users must schedule only inside shifts, operating hours, or resource calendars. |
| `blocked-time` | "This booking touches unavailable time." | The event overlaps a matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability entry. | Use `error` for PTO, maintenance, lunch breaks, holidays, freezes, and other hard closures. |
The policy value attached to each rule controls enforcement:
| Rule policy | What the user sees | Does it block the local change? |
| :-- | :-- | :-- |
| `warning` | Conflict styling and conflict data with warning severity. | No. |
| `error` | Conflict styling and conflict data with error severity. | Yes, when the changed event is part of that conflict. |
| `confirm` | Conflict data with confirm severity for custom confirmation handling. | No automatic blocking by the conflict engine. Use mutation events or `validateMutation` if your app should stop the change until the user confirms. |
| `allow` | Conflict data stays visible as a warning. | No. |
| `ignore` | Conflict is removed from visible conflict results. | No. |
## Rule Inputs
The scheduler uses these inputs when it decides whether an event is valid for a resource and time range:
| Input | Purpose |
| :-- | :-- |
| `eventSchedulerAvailability` | Dated working, blocked, holiday, or break intervals. Use it for operational data that happens on specific dates. |
| `eventScheduler.calendars` | Recurring working days, holidays, working hours, and per-resource calendar resolution. Use it for the normal schedule baseline. |
| `eventScheduler.conflicts` | Converts detected conflicts into warnings, blocking errors, confirmation states, or ignored results. |
| `eventScheduler.nonWorkingTime` | Visual closed-cell styling for simple schedules. It does not create blocking conflicts by itself. |
Use availability when the answer depends on a date, such as PTO, maintenance, a temporary room closure, or an extra working shift. Use calendars when the answer is recurring, such as weekday business hours or a weekend crew calendar.
## Availability Kinds
`eventSchedulerAvailability` entries use `EventSchedulerAvailabilityKind`:
| Kind | Meaning | Conflict behavior |
| :-- | :-- | :-- |
| `working` | A dated interval where matching resources are available. | If any matching `working` availability exists for the resource/date, the event must be fully covered by matching working intervals or it receives `outside-availability`. |
| `blocked` | A dated unavailable interval, often for maintenance, holds, or PTO. | Events that overlap it receive `blocked-time`. |
| `holiday` | A dated unavailable interval for holiday closure or non-operating time. | Events that overlap it receive `blocked-time`. |
| `break` | A dated unavailable interval inside an otherwise working day. | Events that overlap it receive `blocked-time`. |
Unavailable entries are checked by overlap. If an event touches a matching `blocked`, `holiday`, or `break` interval, the scheduler raises a `blocked-time` conflict for the overlapping part.
Working entries are checked by coverage. If an event runs from `09:00` to `13:00`, the matching `working` availability must cover that whole range. A `09:00` to `12:00` working interval is not enough; the uncovered `12:00` to `13:00` portion creates an `outside-availability` conflict.
## Precedence
For each visible event segment, Event Scheduler applies rules in this order:
1. Check matching unavailable availability entries.
2. Check matching dated `kind: 'working'` availability for that resource/date.
3. If no dated working availability matches, check the resolved calendar.
4. Apply conflict policy and per-type rules to decide whether the detected conflict is shown or blocks the mutation.
Unavailable availability is always considered. Dated working availability is more specific than calendars for outside-availability checks. Calendars are the fallback only when no matching dated `kind: 'working'` availability exists for that resource/date.
This means a dated working entry can narrow or extend the calendar for that date. If a room normally follows a `08:00` to `18:00` calendar but has a dated `working` entry from `10:00` to `14:00`, events for that room/date must be covered by `10:00` to `14:00`. The calendar does not fill the rest of the day for outside-availability checks.
## Resource Matching
Availability can be global or resource-specific:
```ts
grid.eventSchedulerAvailability = [
{
id: 'all-hands-break',
kind: 'break',
title: 'Company break',
startDateTime: '2026-06-08T12:00:00.000Z',
endDateTime: '2026-06-08T13:00:00.000Z',
},
{
id: 'room-a-maintenance',
resourceId: 'room-a',
kind: 'blocked',
title: 'Projector maintenance',
startDateTime: '2026-06-08T15:00:00.000Z',
endDateTime: '2026-06-08T16:00:00.000Z',
},
{
id: 'training-team-shift',
resourceIds: ['trainer-a', 'trainer-b'],
kind: 'working',
title: 'Training shift',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
},
];
```
An availability entry without `resourceId` or `resourceIds` applies globally. An entry with `resourceId` applies to that one resource. An entry with `resourceIds` applies to any listed resource.
Calendars resolve per resource in this order:
1. `resource.calendarId`
2. `eventScheduler.calendars.resourceCalendarId(resource)`
3. `eventScheduler.calendars.primaryCalendarId`
If calendar resolution finds no calendar, calendar-based `outside-availability` checks are skipped for that resource/date. Availability rules can still create conflicts.
## Conflict Outcomes
Rule detection and mutation blocking are separate steps.
The scheduler first creates conflict records. Each conflict has a `type`, `policy`, `severity`, `message`, related event ids, related segment ids, and date/time metadata. Then the configured policy decides whether that conflict is just reported or whether it prevents a local mutation.
`blocked-time` is raised when an event overlaps matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability. The conflict includes the event id, segment id, resource id when available, date, overlapping time range, and the availability id.
`outside-availability` is raised when an event is outside allowed working time. This can come from dated `kind: 'working'` availability or from the resolved calendar fallback.
`overlap` is raised when two events overlap on the same date. With the default `scope: 'same-resource'`, the events must also share the same resource. With `scope: 'global'`, overlaps are detected across resources.
By default, conflict behavior depends on `eventScheduler.conflicts.policy`:
| Policy | Default result |
| :-- | :-- |
| `mark` | Conflicts are warnings. Mutations are allowed unless a rule overrides the type. |
| `prevent` | Conflicts are blocking errors by default. |
| `allow` | Conflicts are ignored unless a rule overrides the type. |
Use `rules` when some conflict types should be stricter than others:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'outside-availability': 'error',
'blocked-time': 'error',
overlap: 'warning',
},
},
};
```
With this configuration, outside availability and blocked time reject create, move, resize, and edit mutations. Overlaps remain visible warnings.
You can customize conflict text with `messages`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'blocked-time': 'error',
},
messages: {
'blocked-time': 'This slot is closed. Choose another time.',
},
},
};
```
## Custom Event Prevention
Conflict rules are the declarative way to stop known scheduler violations. For product-specific checks that do not fit calendars, availability, or built-in conflict types, use the scheduler mutation lifecycle.
The main event-based prevention point is `event-scheduler-before-event-change`. It fires before the local event array is updated. Call `preventDefault()` to cancel the pending create, move, resize, edit, delete, paste, template, recurrence, or bulk mutation:
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.action === 'move' && event.detail.event?.status === 'completed') {
event.preventDefault();
}
});
```
Use `validateMutation` when you prefer a config-level hook instead of a DOM event. Return `false` or a string message to reject the same pending mutation:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
validateMutation: ({ changes }) => {
if (changes?.resourceId === 'locked-room') {
return 'That room is locked.';
}
},
};
```
Do not use final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, or `event-scheduler-event-deleted` for prevention. Those fire after the scheduler has accepted the change and are meant for persistence and state sync.
For the full local mutation lifecycle, cancelable before events, permission hooks, and persistence events, see [Editing and Interaction](../event-editing/).
## Calendar Fallback
Calendars are the normal baseline when no dated working availability exists:
```ts
import {
DEFAULT_CALENDAR,
type CalendarEntity,
} from '@revolist/revogrid-pro';
const weekdayCalendar: CalendarEntity = {
...DEFAULT_CALENDAR,
id: 'weekday',
name: 'Weekday office hours',
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
holidays: ['2026-06-19'],
};
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
};
```
With no matching `kind: 'working'` availability for a resource/date, the calendar decides whether the event is inside working time. An event on a holiday, a closed weekday, or outside `workingHours` receives `outside-availability`.
## Dated Working Overrides Calendar Hours
A dated working entry becomes the outside-availability rule for its matching resource/date:
```ts
grid.eventSchedulerAvailability = [
{
id: 'room-a-short-day',
resourceId: 'room-a',
kind: 'working',
title: 'Short operating day',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T14:00:00.000Z',
},
];
```
If `room-a` also has a calendar with `08:00` to `18:00` working hours, the dated working entry wins for June 8, 2026. A `09:00` to `10:00` event and a `14:00` to `15:00` event both receive `outside-availability` because the event is not fully covered by the dated working interval. The calendar is not used to fill those hours.
## Lunch Breaks and Blocked Time
Breaks are unavailable windows, not holes in working coverage:
```ts
grid.eventSchedulerAvailability = [
{
id: 'nurse-a-shift',
resourceId: 'nurse-a',
kind: 'working',
title: 'Day shift',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T16:00:00.000Z',
},
{
id: 'nurse-a-lunch',
resourceId: 'nurse-a',
kind: 'break',
title: 'Lunch',
startDateTime: '2026-06-08T12:00:00.000Z',
endDateTime: '2026-06-08T12:30:00.000Z',
},
];
```
An event from `11:30` to `12:30` is covered by the working shift, but it overlaps the lunch break. The result is `blocked-time`, not `outside-availability`.
## Holidays and Maintenance
Use `holiday` and `blocked` for unavailable windows that should conflict even when the calendar or working availability would otherwise allow the event:
```ts
grid.eventSchedulerAvailability = [
{
id: 'clinic-holiday',
kind: 'holiday',
title: 'Clinic closed',
startDateTime: '2026-06-19T00:00:00.000Z',
endDateTime: '2026-06-20T00:00:00.000Z',
},
{
id: 'room-b-maintenance',
resourceId: 'room-b',
kind: 'blocked',
title: 'Equipment maintenance',
reason: 'Calibration',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T15:00:00.000Z',
},
];
```
The holiday applies globally because it has no resource id. The maintenance entry applies only to `room-b`. Events that overlap either entry receive `blocked-time`.
## Split Working Coverage
Multiple working entries can combine to cover one event, but there cannot be a gap:
```ts
grid.eventSchedulerAvailability = [
{
id: 'room-c-morning',
resourceId: 'room-c',
kind: 'working',
title: 'Morning setup',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
},
{
id: 'room-c-afternoon',
resourceId: 'room-c',
kind: 'working',
title: 'Afternoon setup',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T17:00:00.000Z',
},
];
```
An event from `09:00` to `11:00` is covered. An event from `11:00` to `14:00` is not covered because `12:00` to `13:00` is a gap between working intervals. The scheduler raises `outside-availability` for that event.
If you want the gap to be visibly unavailable and report a more specific conflict, add a `kind: 'break'` entry for the gap. The event will then receive `blocked-time` for the break overlap.
## Visual Non-Working Time
`nonWorkingTime` is useful for simple visual backgrounds:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
className: 'closed-cell',
},
};
```
This marks closed cells, but it is not the rule source for blocking conflicts. Use `eventScheduler.calendars` or `eventSchedulerAvailability` when outside-working-time edits must produce `outside-availability`, then configure `eventScheduler.conflicts` to decide whether that conflict blocks.
---
# Availability
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/availability/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/availability.mdx
Description: Show working time, blocked time, breaks, holidays, and resource-specific availability in Event Scheduler.
Availability entries describe time ranges that are working, blocked, break, or holiday periods. They can apply globally or to specific resources.
For the full precedence model between availability, calendars, and conflict policies, see the [Scheduling Rules overview](../).
```ts
grid.eventSchedulerAvailability = [
{
id: 'business-hours',
kind: 'working',
title: 'Business hours',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
},
{
id: 'room-a-maintenance',
resourceId: 'room-a',
kind: 'blocked',
title: 'Maintenance',
reason: 'Projector replacement',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T15:00:00.000Z',
},
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '19:00' },
className: 'outside-shift-hours',
},
};
```
Use `nonWorkingTime.workingHours` when the whole scheduler shares one operating window. Slots outside the configured windows are marked as closed/outside working hours and receive the scheduler non-working background. They also expose `data-event-scheduler-non-working="true"` and `data-event-scheduler-availability-reason="Outside working hours"` so apps can inspect or customize the cells without replacing the renderer.
Use named calendars when working hours differ by calendar or resource:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'hospital-weekday',
className: 'outside-shift-hours',
calendars: [{
id: 'hospital-weekday',
name: 'Hospital weekday',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '19:00' },
holidays: [],
hoursPerDay: 8,
}],
},
};
```
## Availability and Calendars
`eventSchedulerAvailability` and `eventScheduler.calendars` can be used together. They are resolved with these rules:
- `kind: 'blocked'`, `kind: 'holiday'`, and `kind: 'break'` entries are unavailable windows. Events that overlap them receive `blocked-time` conflicts.
- `kind: 'working'` entries are dated working windows. If matching working availability exists for a resource on a date, the event must be fully covered by those working intervals or it receives an `outside-availability` conflict.
- Calendars are the fallback for `outside-availability` checks when no matching dated `kind: 'working'` availability exists for that resource/date.
Use calendars for the recurring baseline, such as weekday hours, weekend crews, and resource calendars. Use availability for dated changes, such as extra opening hours, PTO, maintenance, one-off breaks, or temporary closures.
Timeline cells expose availability data attributes so apps can style or inspect availability state without replacing the scheduler renderer.
---
# Calendars
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/calendars/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/calendars.mdx
Description: Define working days, holidays, working hours, and per-resource calendars for Event Scheduler.
Calendars describe when work is normally allowed. Event Scheduler uses them to mark closed cells, validate drag/create targets, skip non-working days during rollover, create outside-availability conflicts, and resolve the calendar that applies to a resource.
Use calendars for repeating business rules such as weekday schedules, weekend crews, holiday closures, and resource-specific operating hours. Use availability entries for dated working windows or exceptions such as one-off maintenance, PTO, blocked rooms, or breaks.
For detailed calendar-vs-availability precedence, including how dated working windows override calendar fallback checks, see the [Scheduling Rules overview](../).
Calendars do not automatically reject every edit outside working hours. They provide the working-hours model and produce `outside-availability` conflicts. To make those conflicts block create, move, resize, and edit mutations, configure the conflict rule as an error:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
};
```
## Calendar Shape
Event Scheduler uses the same `CalendarEntity` shape as Gantt, so calendar definitions can be shared between planning views:
```ts
import { EventSchedulerPlugin } from '@revolist/scheduler';
import {
DEFAULT_CALENDAR,
type CalendarEntity,
} from '@revolist/revogrid-pro';
const weekdayCalendar: CalendarEntity = {
...DEFAULT_CALENDAR,
id: 'weekday',
name: 'Weekday coverage',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-06-19'],
hoursPerDay: 8,
workingHours: { start: '08:00', end: '18:00' },
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
};
```
`DEFAULT_CALENDAR` is a Monday-Friday UTC calendar with no holidays and `hoursPerDay: 8`. Spread it when you want the standard defaults and only need to change the id, label, hours, or holidays.
Calendar `workingDays` use ISO weekday numbers: Monday is `1` and Sunday is `7`. The older `nonWorkingTime.workingDays` option uses JavaScript day indexes, where Sunday is `0` and Saturday is `6`.
## Configuring Scheduler Calendars
Add calendars through `eventScheduler.calendars`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
timeRange: { start: '06:00', end: '22:00' },
calendars: {
enabled: true,
primaryCalendarId: 1001,
className: 'scheduler-closed-cell',
calendars: [
{
...DEFAULT_CALENDAR,
id: 1001,
name: 'Hospital weekday calendar',
workingHours: { start: '08:00', end: '18:00' },
},
{
...DEFAULT_CALENDAR,
id: 1002,
name: 'Weekend coverage',
workingDays: [5, 6, 7],
workingHours: { start: '10:00', end: '20:00' },
},
],
},
};
```
Calendar ids can be strings or numbers. Internally the scheduler normalizes ids for lookup, so `primaryCalendarId`, resource `calendarId`, and entries in the `calendars` array should refer to the same logical id.
## Per-Resource Calendars
Each resource can point at its own calendar:
```ts
grid.eventSchedulerResources = [
{ id: 'alex', name: 'Alex Kim', role: 'RN', calendarId: 1001 },
{ id: 'weekend-team', name: 'Weekend team', role: 'RN', calendarId: 1002 },
];
```
The resolver order is:
1. `resource.calendarId`
2. `calendars.resourceCalendarId(resource)`
3. `calendars.primaryCalendarId`
Use `resourceCalendarId` when the calendar comes from metadata or an external rules table:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars,
resourceCalendarId: (resource) =>
resource.metadata?.employmentType === 'weekend' ? 'weekend' : undefined,
},
};
```
## What Calendars Affect
Calendars are applied during projection and interaction:
- Closed days and holidays receive the calendar closed state.
- Slots outside `workingHours` are marked as outside working time.
- Events placed outside the resolved calendar produce an `outside-availability` conflict.
- Drag, resize, and create previews use the configured calendar when finding valid target dates.
- Moving an event past the bottom of a day can roll to the next schedulable day instead of stopping on a closed weekend or holiday.
- Conflict and permission hooks still run after calendar checks; calendars do not bypass app-level validation.
- Blocking is controlled by `eventScheduler.conflicts`, usually with `rules: { 'outside-availability': 'error' }` or `policy: 'prevent'`.
Calendar cells expose the same availability-style state used by the scheduler renderer. Use `className` on the calendars config for product-specific closed-cell styling:
```css
.scheduler-closed-cell {
background: #f4f6f8;
color: #8b929c;
}
```
## Working Hours
`workingHours` can be a single range or multiple ranges:
```ts
const splitShiftCalendar: CalendarEntity = {
...DEFAULT_CALENDAR,
id: 'split-shift',
name: 'Split shift',
workingHours: [
{ start: '07:00', end: '11:00' },
{ start: '15:00', end: '21:00' },
],
};
```
The scheduler treats a slot as working when it overlaps at least one configured working-hours range. If `workingHours` is omitted, every visible time slot on a working day is treated as working.
An event is considered outside availability when its visible segment is not covered by a working calendar or by matching `eventSchedulerAvailability` entries. With the default conflict policy, that state is shown as a warning. With `rules: { 'outside-availability': 'error' }`, the scheduler rejects the mutation before it updates `source`.
`nonWorkingTime.workingHours` is different: it is a visual fallback for closed cells. Use it for simple background styling when you do not need calendar/resource rules. Use `calendars` or `eventSchedulerAvailability` when outside-working-hours edits must participate in conflict detection.
## Calendars vs Availability
Calendars are recurring operating rules. Availability is dated operational data. They are not mutually exclusive: the scheduler can use a calendar as the baseline and dated availability as the more specific rule for a particular resource/date.
Use calendars for:
- Standard weekday business hours
- Weekend-only teams
- Region-specific public holidays
- Resource calendars copied from workforce or Gantt planning data
Use availability for:
- Extra dated working windows
- One-time room maintenance
- PTO or sick leave
- Lunch breaks that vary by date
- Temporary blocked equipment
When both are present, availability is evaluated first for visible slot state. Unavailable entries win in this order: `kind: 'holiday'`, then `kind: 'blocked'`, then `kind: 'break'`. If any matching `kind: 'working'` availability exists for the resource/date, the slot is open only when it overlaps one of those dated working intervals; otherwise it is treated as closed for that date. If no matching dated working availability exists, the scheduler falls back to the resolved calendar.
The same precedence applies to conflicts. If dated availability entries define `kind: 'working'` for a resource/date and an event is not fully covered by those working intervals, the scheduler raises `outside-availability` from availability instead of checking the calendar. If no dated working availability applies, the calendar can raise `outside-availability`. Availability entries with `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` raise `blocked-time` when events overlap them. Both conflict types can be made blocking from the conflicts configuration.
## Demo
The `event-scheduler-shift-week` demo includes a calendar selector with weekday, open, and training calendars. Switch between them to see closed hours, closed days, and holiday behavior update without changing the event source.
---
# Conflicts
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/conflicts/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/conflicts.mdx
Description: Configure warning and blocking conflict rules for overlapping scheduler events and invalid mutations.
Conflict detection can mark overlaps, warn users, or block changes through the mutation lifecycle. This is also how you forbid events outside working hours: configure working time with `calendars` or `eventSchedulerAvailability`, then make the resulting `outside-availability` conflict blocking.
For a detailed explanation of how availability and calendar inputs become `outside-availability` and `blocked-time`, see the [Scheduling Rules overview](../).
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
minDurationMinutes: 30,
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
},
},
validateMutation: (detail) => {
const blocking = detail.conflicts?.some((conflict) => conflict.severity === 'error');
if (blocking) {
return 'This change creates a blocking scheduling conflict.';
}
},
};
```
`policy` sets the default behavior for all conflict types:
| Policy | Behavior |
| :-- | :-- |
| `mark` | Show conflicts as warnings and allow the mutation unless a specific rule is blocking. |
| `prevent` | Treat conflicts as blocking errors by default. |
| `allow` | Ignore conflicts unless a specific rule overrides the type. |
`rules` override the default policy per conflict type. Use `error` to reject the mutation, `warning` to mark it but allow it, `confirm` when your product wants a confirmation flow, and `ignore` to hide the conflict.
Common blocking rules:
```ts
conflicts: {
enabled: true,
policy: 'mark',
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
'invalid-duration': 'error',
},
}
```
`outside-availability` is raised when an event falls outside calendar working days/hours or outside explicit `kind: 'working'` availability intervals. `blocked-time` is raised when an event overlaps explicit blocked, holiday, or break availability. `nonWorkingTime` alone is visual and does not create blocking conflicts.
## Conflict Appearance
Conflict detection and conflict presentation are configured separately. Keep rules under `conflicts`, and choose how detected conflicts look under `customization.conflicts.appearance`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-08-17',
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
},
customization: {
conflicts: {
appearance: 'fill',
},
},
};
```
| Appearance | Behavior |
| :-- | :-- |
| `outline` | Keeps the existing conflict rings around the event. This is the default for backward compatibility. |
| `fill` | Uses the conflict severity background and text colors inside the event bar without the oversized conflict rings. |
Filled conflicts keep the warning or conflict state icon and the normal event shadow. Selection and keyboard focus indicators are unchanged, so `fill` removes only the conflict decoration rings—not accessibility or interaction feedback. The option works across scheduler views; it is especially useful in dense resource timelines where multiple outlines would consume lane space.
You can combine the built-in appearance with `customization.conflicts.className`, `properties`, `indicator`, and `tooltip` when a product needs additional presentation hooks. These hooks do not change whether a conflict blocks a mutation.
## Preventing Mutations
Conflict rules are the declarative way to stop invalid changes. When a conflict resolves to `severity: 'error'`, the scheduler rejects the mutation before it updates `source`.
For business rules that need code, use the same cancelable event pattern as RevoGrid editing. Listen to `event-scheduler-before-event-change` and call `preventDefault()` before the local commit:
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
const hasBlockingConflict = event.detail.conflicts?.some((conflict) =>
conflict.severity === 'error' &&
event.detail.eventId !== null &&
conflict.eventIds.includes(event.detail.eventId)
);
if (hasBlockingConflict || event.detail.event?.locked) {
event.preventDefault();
}
});
```
`validateMutation` is the config-level equivalent for synchronous validation. Return `false` or a string to reject the same pending mutation.
```ts
const eventScheduler = {
validateMutation: (detail) => {
if (detail.changes.resourceId === 'locked-room') {
return 'That room cannot accept bookings.';
}
},
};
```
Do not use `event-scheduler-event-changed` for prevention. `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the scheduler has accepted the mutation; use those events to persist or sync the emitted `detail.events` array.
Listen to conflict updates for side panels, badges, filters, or exports.
```ts
grid.addEventListener('event-scheduler-conflicts-updated', (event) => {
renderConflictPanel(event.detail.conflicts);
});
```
Event blocks expose conflict classes and data attributes for custom styles.
---
# Templates and Recurrence
URL: https://pro.rv-grid.com/guides/event-scheduler/templates-recurrence/
Source: src/content/docs/guides/event-scheduler/templates-recurrence.mdx
Description: Create scheduler events from reusable templates and generate repeating shifts or bookings.
Templates define reusable event shapes. The plugin exposes helper methods for single events and recurring series.
```ts
grid.eventSchedulerTemplates = [
{
id: 'morning-shift',
title: 'Morning shift',
durationMinutes: 480,
status: 'planned',
type: 'shift',
color: '#2563eb',
},
];
const scheduler = (await grid.getPlugins()).find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
scheduler.createRecurringEvents({
templateId: 'morning-shift',
resourceId: 'emp-alex',
startDateTime: '2026-06-08T08:00:00.000Z',
recurrence: {
frequency: 'weekly',
weekdays: [1, 3, 5],
count: 6,
},
seriesId: 'alex-morning-series',
});
```
Generated events keep recurrence metadata so applications can present edit-series or delete-series workflows.
## Copy, Paste, Duplicate, and Bulk Helpers
Get the plugin instance to run imperative scheduler helpers:
```ts
const plugins = await grid.getPlugins();
const scheduler = plugins.find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
```
Create one event from a template:
```ts
scheduler?.createEventFromTemplate({
templateId: 'morning-shift',
startDateTime: '2026-06-08T08:00:00.000Z',
resourceId: 'team-a',
});
```
Copy a day or pattern into another date range:
```ts
scheduler?.duplicateScheduleRange({
sourceStartDateTime: '2026-06-08T00:00:00.000Z',
sourceEndDateTime: '2026-06-09T00:00:00.000Z',
targetStartDateTime: '2026-06-09T00:00:00.000Z',
});
```
Selection helpers support copy, paste, duplicate, bulk delete, and bulk status changes:
```ts
scheduler?.setSelectedEventIds(['shift-a', 'shift-b']);
scheduler?.copySelectedEvents();
scheduler?.pasteCopiedEvents({ startDateTime: '2026-06-10T08:00:00.000Z' });
scheduler?.bulkUpdateSelectedEventStatus('confirmed');
```
All helper actions run through the same permissions, conflict, before-change, local mutation, event emission, and remote mutation pipeline as pointer and editor interactions.
---
# Timeline Views
URL: https://pro.rv-grid.com/guides/event-scheduler/timeline-views/
Source: src/content/docs/guides/event-scheduler/timeline-views.mdx
Description: Configure day/week time grids, month/year calendars, and horizontal resource timelines.
Event Scheduler has three projection families: vertical `day` and `week` time grids, true `month` and `year` calendar layouts, and the horizontal `resourceTimeline`. All of them share the same events, resources, filtering, recurrence, conflicts, permissions, selection, history, remote loading, and mutation APIs. For the full grid bootstrap, start with [Getting Started](../introduction/).
## Framework bindings
React, Vue, and Angular wrappers bind the same grid props. In React, keep the plugin array stable and pass empty host rows and columns:
```tsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { EventSchedulerPlugin } from '@revolist/scheduler';
export function ResourceTimeline() {
const plugins = useMemo(() => [EventSchedulerPlugin], []);
return (
);
}
```
This example assumes `eventScheduler`, `events`, and `resources` are the same constants you would pass in Vanilla TS. Vue and Angular use the same prop names through their normal bindings. In Vue, use property bindings for direct grid properties, for example `:event-scheduler.prop="eventScheduler"`, `:source.prop="events"`, and `:event-scheduler-resources.prop="resources"`. In Angular, use `[eventScheduler]="eventScheduler"`, `[source]="events"`, and `[eventSchedulerResources]="resources"`.
## Choose a view
Use `day` and `week` when time should run vertically and days should be generated as columns. These views work well for shift calendars, appointment books, and detailed weekly planning.
Use `month` for a conventional seven-column calendar whose natural four to six week rows fill the available data viewport. Multi-day events are clipped into continuous week bars, adjacent-month dates remain visible, and dense days expose a `+N more` overlay. Use `year` for a responsive overview of twelve mini-months; choosing a month or day requests the detailed month view.
Use `resourceTimeline` when resources should be generated as rows and time slots should run horizontally. This layout is better for room booking, equipment planning, staff coverage, machine schedules, and custom multi-day resource timelines.
The main range controls are:
- `weekStartDate` anchors the active day, week, month, or year.
- `dateRange` sets an explicit visible range for timeline views. It does not reshape month/year calendars.
- `visibleDays` filters generated weekdays in time-grid/timeline views. Calendar month/year layouts keep seven weekday columns.
- `slotMinutes` controls visible slot granularity.
- `snapMinutes` controls drag, resize, paste, create, and keyboard snapping. When omitted, it follows the active `slotMinutes`, including zoom changes. An explicit value stays fixed across zoom levels.
- `timeRange` limits the visible hours within each day.
The main layout controls are:
- `timeColumnSize` controls the pinned time column in `day` and `week` views.
- `dayColumnSize` controls day columns in `day` and `week`; month columns are distributed across the viewport.
- `resourceColumnSize` controls the pinned resource column in `resourceTimeline`.
- `timelineColumnSize` controls each horizontal timeline slot in `resourceTimeline`.
- `columnGrouping` enables or disables grouped scheduler headers.
- `timelineHeaderRows` defines one to three resource-timeline header rows from coarse to fine.
- `eventLayout`, `maxStackedEvents`, and `compactThreshold` control overlapping event presentation.
- `rowSize` sets the baseline scheduler row height.
- `resourceTimelineRowSizing` lets dense resource rows grow with their simultaneous event lanes.
```ts
const resourceDayTimeline = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-08' },
slotMinutes: 60,
timeRange: { start: '6 AM', end: '10 PM' },
resourceColumnSize: 220,
timelineColumnSize: 120,
};
const resourceWeekTimeline = {
...resourceDayTimeline,
dateRange: { start: '2026-06-08', end: '2026-06-14' },
customization: {
columnSizes: {
resourceTimeline: {
resourceColumnSize: 240,
timelineColumnSize: 132,
},
},
headers: {
timelineTemplate: (h, context) =>
h('span', { class: 'scheduler-slot-header' }, context.defaultLabel),
timelineProperties: (context) => ({
'data-scheduler-date': context.date,
}),
},
},
};
const customRange = {
...resourceDayTimeline,
dateRange: { start: '2026-06-08', end: '2026-06-10' },
slotMinutes: 30,
timelineColumnSize: 132,
};
```
For a practical vertical week setup, continue to [Week View](../week-view/). For raw configuration types, see the [Event Scheduler API reference](/api/event-scheduler/).
```ts
const monthCalendar = {
view: 'month',
weekStartDate: '2026-07-15',
weekStartsOn: 1,
};
const yearOverview = {
...monthCalendar,
view: 'year',
};
```
`month` now means this conventional calendar layout. Earlier releases projected it as a 31-column vertical time grid; applications that depended on that legacy geometry should use an explicit timeline range instead. There is no compatibility flag for the former rendering.
## Demo Sources
The examples keep two focused Event Scheduler demos:
| Demo id | Implementation |
| :-- | :-- |
| `event-scheduler-shift-week` | Shift planner with day, week, month, year, and resource views. |
| `equipment-machine-scheduler` | Dedicated seven-day equipment view with continuous multi-day runs and context-menu zoom. |
Useful source references:
- `examples/revogrid-demos/pro-advanced-scheduler/src/data.ts`
- `examples/revogrid-demos/pro-advanced-scheduler/src/scheduler.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/base-config.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/data.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/index.ts`
## Column Widths
Scheduler column widths are controlled by the scheduler config and are applied when the view is projected into RevoGrid columns:
- `timeColumnSize` controls the pinned time column in day and week views.
- `dayColumnSize` controls time-grid day columns and supplies the initial calendar sizing hint.
- `resourceColumnSize` controls the pinned resource column in resource timeline views.
- `timelineColumnSize` controls each horizontal timeline slot in resource timeline day, week, and custom ranges.
Use wider `timelineColumnSize` values for custom ranges with fine-grained slots, such as 15-minute or 30-minute columns. Wider timeline slots improve header and event-label readability and naturally create more horizontal scroll space.
Use `customization.columnSizes` when the same scheduler instance switches modes and each mode needs a different width profile. Values under `customization.columnSizes[view]` take precedence over the legacy top-level width options:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 96,
dayColumnSize: 220,
},
resourceTimeline: {
resourceColumnSize: 260,
timelineColumnSize: 144,
},
},
},
};
```
## Timescale Zoom
Scheduler header context menus expose Zoom in and Zoom out when another configured level is available. Zoom changes the projected slot size without changing the configured `timeRange` or event dates. When `snapMinutes` is omitted, its effective value follows the active zoom slot, so zooming from 60-minute slots to 30-minute slots also changes interaction snapping from 60 to 30 minutes. An explicitly configured `snapMinutes` remains unchanged across zoom levels. If a level does not divide the visible time range evenly, Scheduler keeps a shorter final slot so the exact range end remains visible.
Use the built-in levels by omitting `zoom`, provide product-specific levels, or disable zoom:
```ts
const eventScheduler = {
view: 'week',
weekStartDate: '2026-07-27',
slotMinutes: 30,
snapMinutes: 10,
timeRange: { start: '06:00', end: '17:00' },
zoom: {
levels: [15, 30, 45, 90, 120],
},
};
const fixedTimescale = {
...eventScheduler,
zoom: false,
};
```
The active configured `slotMinutes` is always retained as a zoom level. Invalid, duplicate, and out-of-range level values are ignored. Setting `zoom: false` or `zoom: { enabled: false }` removes the built-in zoom actions and makes the plugin's `canZoomIn()`, `canZoomOut()`, `zoomIn()`, and `zoomOut()` methods return `false`.
## Grouped Headers
Scheduler views use grouped headers by default so day, week, month, and resource timeline ranges can show broader date context above the leaf columns. Disable them when the extra header row is not useful for a compact embedded scheduler:
```ts
const eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
columnGrouping: false,
};
```
When `columnGrouping` is `false`, the scheduler returns flat RevoGrid columns. Day/week/month views show only day leaf headers, and resource timeline views show only timeline slot leaf headers after the pinned resource column.
## Adaptive Resource Rows
Resource timelines can grow only the rows that need more vertical space. Enable `resourceTimelineRowSizing` when several events can overlap for the same resource and fixed-height lanes make their labels or status icons difficult to read:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-08-17',
dateRange: { start: '2026-08-17', end: '2026-08-30' },
rowSize: 44,
resourceTimelineRowSizing: {
enabled: true,
minEventHeight: 24,
},
};
```
`rowSize` remains the minimum row height. Scheduler calculates the maximum number of simultaneously overlapping lanes for each visible resource and grows that row enough to keep every bar at least `minEventHeight` pixels high. It does not use the resource's total event count, so sequential events that reuse one lane do not make the row taller.
Adaptive sizing is disabled by default. Use `resourceTimelineRowSizing: true` for the default `24` pixel minimum, or provide an object when the product needs a different minimum. Empty, grouped, sparse, and non-stacked rows keep `rowSize`; dense rows shrink back when filtering, reassignment, deletion, or another committed update reduces their lane count. Day, week, month, and year views continue to use their normal row behavior.
Row sizes come from projected scheduler lanes rather than DOM measurement, so vertical virtualization remains available. During a live drag or resize preview, row heights stay stable to avoid moving the pointer target; Scheduler recalculates them after the change is committed.
### Continuous multi-day bars
In `resourceTimeline`, an event spanning several dates is rendered as one continuous horizontal bar. Its top and bottom borders remain solid, its outer corners remain rounded, and it does not receive the dashed clipped-edge treatment used by vertically clipped day/week events. Continuation metadata is still retained for customization and accessibility.
Compact titles and state icons stay vertically centered when adaptive rows use the minimum event height. If a title is too wide, normal single-line ellipsis behavior still applies.
### Mode-aware resource timeline rows
Resource timelines use compact, scale-aware defaults based on the number of leaf columns per date. When a date spans multiple time columns, Scheduler combines its localized weekday, month, day, and year into one `day / time` hierarchy, such as `Thu, Dec 4, 2025` above hourly labels. When each date owns one column, it keeps separate `month / day / time` tiers so the shorter labels fit. Day-sized slots use `month / day`. A product can still define the hierarchy per mode, similar to a Gantt timescale:
```ts
const overviewMode = {
slotMinutes: 360,
timelineColumnSize: 64,
timelineHeaderRows: [
{ id: 'day', unit: 'day' },
{ id: 'time', unit: 'time' },
],
};
const planningMode = {
slotMinutes: 180,
timelineColumnSize: 76,
timelineHeaderRows: [
{ id: 'month', unit: 'month' },
{
id: 'day',
unit: 'day',
formatter: ({ defaultLabel }) => defaultLabel.toUpperCase(),
},
{ id: 'time', unit: 'time' },
],
};
```
Rows must be ordered from the broadest unit to the leaf unit. Sub-day scales end in `time`; a `1440`-minute scale ends in `day`. Each row accepts its own formatter, while the existing `timelineHeaderFormatter` remains a compatible shortcut for the leaf row.
Header customization hooks map to RevoGrid column templates and properties. Returning `null` or `undefined` from a template keeps the built-in header content:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
headers: {
groupTemplate: (h, context) =>
h('strong', { class: 'scheduler-date-group' }, context.defaultLabel),
timelineProperties: (context) => ({
class: 'scheduler-slot-header',
'data-slot-start': context.startDateTime,
}),
},
},
};
```
## Troubleshooting
- No rows or events render: check that `grid.plugins` includes `EventSchedulerPlugin`, `eventScheduler` is set, `source` is populated, and each event's `resourceId` matches an item in `eventSchedulerResources` for resource timelines.
- Events render but cannot be created, moved, or resized: enable `editable` and the needed `allowCreate`, `allowMove`, or `allowResize` flags. Permission callbacks, locked resources, and locked event statuses can still block a specific action.
- Custom `source` or `columns` disappear: this is expected while `eventScheduler` is active. The plugin projects scheduler rows and columns into RevoGrid and restores the host grid state when the scheduler config is removed.
- Range or slot labels look wrong: verify ISO `YYYY-MM-DD` dates, `dateRange`, `timeRange`, `slotMinutes`, and any `timelineHeaderFormatter` or `customization.headers` hooks.
---
# Week View
URL: https://pro.rv-grid.com/guides/event-scheduler/week-view/
Source: src/content/docs/guides/event-scheduler/week-view.mdx
Description: Build a practical calendar-style week scheduler for shifts, appointments, and short-range planning.
Use `view: 'week'` when users think in days and hours: employee shifts, appointment books, clinic rosters, classroom timetables, service windows, and team calendars. Time runs vertically, days are generated as columns, and event duration is shown by event height.
This page focuses on a vertical week scheduler. For the general setup, props, and data model, start with [Getting Started](../introduction/). For choosing between vertical time and horizontal resource rows, see [Vertical vs Horizontal Time](../introduction/vertical-vs-horizontal/). For `day`, `month`, `resourceTimeline`, column widths, and grouped headers, see [Timeline Views](../timeline-views/).
## Build a Week Planner
1. Configure the visible week.
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
type EventSchedulerResourceEntity,
} from '@revolist/scheduler';
const eventScheduler: EventSchedulerConfig = {
view: 'week',
weekStartDate: '2026-06-08',
weekStartsOn: 1,
locale: 'en-US',
timeZone: 'UTC',
visibleDays: [1, 2, 3, 4, 5],
slotMinutes: 30,
snapMinutes: 15,
timeRange: { start: '06:00', end: '22:00' },
timeColumnSize: 88,
dayColumnSize: 190,
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
};
```
`slotMinutes` controls visible row granularity and has a 15-minute minimum. Use values such as `15`, `30`, or `60` minutes depending on the planning detail your product needs. `snapMinutes` controls create, drag, resize, paste, and keyboard snapping. When omitted, it follows the active slot size as the Scheduler zooms. Set it explicitly when editing precision should remain fixed across zoom levels; for example, display 30-minute rows while allowing 5-minute edits.
`timeRange` controls the visible start/end time, so night hours can be hidden instead of rendered. `rowSize` controls density for compact, dense, or comfortable schedules, and `timeLabelFormatter` / `timeLabelTemplate` can replace the default `06:00` style labels.
`locale` and `timeZone` control built-in day and range labels. Use `dayHeaderFormatter`, `timeLabelFormatter`, and `timelineHeaderFormatter` when your product needs a fully custom display format.
2. Provide the events and optional resources.
```ts
const resources: EventSchedulerResourceEntity[] = [
{ id: 'alex', name: 'Alex Kim', role: 'Coordinator', group: 'Front desk' },
{ id: 'maria', name: 'Maria Cruz', role: 'Nurse', group: 'Clinical' },
];
const events: EventSchedulerEventEntity[] = [
{
id: 'shift-alex-mon',
resourceId: 'alex',
title: 'Front desk',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T14:00:00.000Z',
status: 'confirmed',
type: 'shift',
},
{
id: 'shift-maria-mon',
resourceId: 'maria',
title: 'Clinical floor',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
status: 'planned',
type: 'shift',
},
];
```
In week view, resources do not become rows; days and time slots define the grid. Resource ids still matter for ownership, permissions, conflicts, calendars, availability, filters, exports, and later switching the same data to `resourceTimeline`.
3. Attach the scheduler to an empty grid.
```ts
const grid = document.querySelector('revo-grid')!;
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.eventSchedulerResources = resources;
grid.source = events;
```
## Keep Edits Controlled
The scheduler updates `grid.source` and emits the next event array after accepted create, move, resize, edit, delete, template, paste, recurrence, and bulk actions. Persist that array and use it to update framework-owned state.
```ts
function syncEvents(event: CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>) {
void saveSchedule(event.detail.events);
}
grid.addEventListener('event-scheduler-event-created', syncEvents as EventListener);
grid.addEventListener('event-scheduler-event-changed', syncEvents as EventListener);
grid.addEventListener('event-scheduler-event-deleted', syncEvents as EventListener);
```
Use `event-scheduler-before-event-change`, `validateMutation`, or permission hooks when your product needs to reject a pending change before it is committed. The full lifecycle is covered in [Editing and Interaction](../event-editing/).
## Block Invalid Hours
To make a week planner practical, define working time with calendars or availability and make the resulting conflicts blocking.
```ts
grid.eventScheduler = {
...eventScheduler,
calendars: {
primaryCalendarId: 'weekday',
calendars: [{
id: 'weekday',
name: 'Weekday shifts',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
holidays: ['2026-06-19'],
hoursPerDay: 8,
}],
},
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'outside-availability': 'error',
'blocked-time': 'error',
overlap: 'warning',
},
},
};
```
Calendars are the recurring baseline. Use `eventSchedulerAvailability` for dated changes such as PTO, lunch breaks, holiday closures, maintenance, or temporary extra coverage. `nonWorkingTime` is useful for visual closed-cell styling, but it does not block edits by itself. The precedence model is covered in [Scheduling Rules](../scheduling-rules/).
## Switch the Visible Week
Keep `weekStartDate`, `visibleDays`, and `timeRange` in application state when the host toolbar owns navigation.
```ts
function setScheduler(next: Partial) {
grid.eventScheduler = {
...grid.eventScheduler,
...next,
};
}
grid.addEventListener('event-scheduler-navigate-request', (event) => {
const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail;
setScheduler({ weekStartDate: resolveNextWeekStart(grid.eventScheduler.weekStartDate, action) });
});
grid.addEventListener('event-scheduler-view-request', (event) => {
const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail;
setScheduler({ view });
});
```
Use `day` for a focused daily schedule, `week` for the main shift-planning workflow, and `month` for a higher-level calendar. Use `resourceTimeline` when resources need their own rows and users compare capacity horizontally.
## Production Checklist
- Use ISO datetime strings and stable event ids.
- Decide whether `weekStartsOn` and `visibleDays` follow locale, business rules, or user settings.
- Put canonical events in `source`; keep `columns` empty while the scheduler owns the visual projection.
- Enable only the edit permissions your persistence flow can handle.
- Configure calendars or availability before turning outside-hours conflicts into blocking errors.
- Add resources even in week view when conflicts, permissions, reporting, or future resource timelines need them.
- Use the Employee Shift Planner demo as a focused week-view reference.
## Next Pages
- [Timeline Views](../timeline-views/) covers all scheduler views, date ranges, column widths, framework bindings, grouped headers, and demo source locations.
- [Editing and Interaction](../event-editing/) covers host-owned persistence, mutation events, validation, permissions, selection, clipboard, keyboard shortcuts, and editor replacement.
- [Scheduling Rules](../scheduling-rules/) covers calendar, availability, non-working time, and conflict-rule precedence.
- [Grouping](../grouping/) covers resource timelines, unassigned demand, coverage, utilization, filters, and saved views.
- [Templates and Recurrence](../templates-recurrence/) covers template creation, recurrence, duplicate, paste, selection, and bulk helpers.
- [Remote Data](../remote-data/) covers server-backed loading, optimistic saves, refresh, paging, and remote state events.
- [Customization](../customization/) covers the full visual customization category. Start with [Labels and Formatters](../customization/labels-formatters/), [Event Rendering](../customization/events/), and [Layout, Headers, and Cells](../customization/layout/).
- [Examples](../examples/) has compact weekly scheduler examples for custom event bars, day headers, current-time markers, closed hours, read-only mode, cancelable selection state, and themes.
- [Export](../export/) covers export helpers and production readiness.
- [Event Scheduler API reference](/api/event-scheduler/) lists raw config, hook, and event types.
---
# Excel Export
URL: https://pro.rv-grid.com/guides/excel-export/
Source: src/content/docs/guides/excel-export/index.mdx
Description: Export XLSX files and import CSV data with RevoGrid Pro.
RevoGrid Pro exports grid data to `.xlsx` workbooks through `ExportExcelPlugin`. The same plugin also keeps the historical CSV drag-and-drop import path, so existing integrations can keep using the `ExportExcelPlugin`, `grid.exportExcel`, and `export-excel` event names.
Use this page for the quick setup. Provider adapters, cell formatting, CSV import, and migration details are split into focused pages linked below.
## Quick Setup
Attach `ExportExcelPlugin` with your grid plugins:
```ts
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ExportExcelPlugin];
```
Trigger export through the event API:
```ts
grid.dispatchEvent(new CustomEvent('export-excel', {
detail: {
workbookName: 'orders.xlsx',
sheetName: 'Orders',
},
}));
```
Or call the plugin instance directly:
```ts
const plugins = await grid.getPlugins();
const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin);
await excel?.export({
workbookName: 'orders.xlsx',
sheetName: 'Orders',
});
```
## Default Behavior
- The bundled provider is `write-excel-file` and exports `.xlsx` files only.
- Extensionless `workbookName` values receive `.xlsx`.
- `sheetName` is normalized for Excel worksheet rules.
- Export reads grid data stores, not rendered DOM rows, so virtualized offscreen rows are included.
- Body export follows the store's visible row projection, preserving current sort and filter order and the expanded/collapsed grouping view.
- Row order is pinned top rows, body rows, then pinned bottom rows.
- Columns follow `providers.column.getColumns('all')`.
- Grid-derived widths and freeze panes are inferred by default from column sizes, `colPinStart` columns, and pinned-top rows.
Direct export intentionally fails while `InfinityScrollPlugin` owns the row source because the browser holds only a moving window of remote rows. Fetch the complete dataset from your backend and export it from a temporary grid with a local source. See [Exporting to Excel with Infinity Scroll](/guides/infinity-scroll/#exporting-to-excel).
## Formatted And Grouped Exports
When `DataGridFormattingPlugin` and `ExportExcelPlugin` are active together,
Format Cells settings are transferred automatically to the workbook. This is
also the normal setup when `DataGridContextMenuPlugin` supplies Format Cells:
```ts
import {
DataGridContextMenuPlugin,
ExportExcelPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridContextMenuPlugin, ExportExcelPlugin];
```
The export keeps raw numeric and date values as workbook values and attaches
native Excel number formats for Number, Currency, Accounting, Percentage,
Scientific, Date, Time, Date & time, and Plain text presets. Excel can therefore
continue to calculate, sort, and edit those cells. Font family and size,
emphasis, text and fill colors, alignment, wrapping, and borders are mapped to
workbook cell styles.
Row grouping is exported from the visible provider projection. Group records
become merged, indented group-heading rows, while expanded descendants retain
their displayed order and descendants hidden by a collapsed group are omitted.
`context.exportMetadata.rowGrouping` also exposes each exported group row's
worksheet row, depth, label, and expanded state to custom providers.
Format Cells state is owned by the current `DataGridFormattingPlugin` instance.
Export it before destroying that plugin or the grid. Formatting follows
physical source coordinates, so replacing row objects at the same indexes does
not require identity configuration.
## Focused Guides
- [Export Providers](./providers/) explains provider precedence, default `.xlsx` limits, `providerOptions`, `deriveGridOptions`, optional ExcelJS and SheetJS adapters, workbook-module adapters, and custom providers.
- [Cell Formatting](./cell-formatting/) explains automatic Format Cells transfer, `column.excelExport`, formula/date/numeral/select helpers, grouped rows and headers, cell merge export support, and the provider context matrix.
- [CSV Import](./csv-import/) explains CSV-only drag/drop import, `allowDrag`, `allowedExtensions`, `skipColumnGeneration`, events, and plugin import helpers.
- [Migrating to v2.0](/guides/migration-v2/) explains the SheetJS removal, CSV-only default import, app-owned XLSX import, and provider migration paths as a breaking change from `2.0.8`.
## Related Export Guides
- [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) compares SheetJS, ExcelJS, read-excel-file, and write-excel-file for app-owned workbook workflows.
- [Gantt Grid, Export, Rendering](/guides/gantt/features/grid-export-rendering/) covers Gantt task-row export mapping and reporting recipes.
- [Pivot Export And State](/guides/pivot/concepts/export-state/) covers Pivot CSV, TSV, and state JSON helpers.
- [Export Excel API](/api/export-excel/) is the generated API reference for the complete public type surface.
---
# Cell Formatting
URL: https://pro.rv-grid.com/guides/excel-export/cell-formatting/
Source: src/content/docs/guides/excel-export/cell-formatting.mdx
Description: Format Excel export cells, formulas, column types, grouped headers, and merge spans.
Use `column.excelExport` when workbook output needs values or styles that differ from the rendered grid. Export formatting is data-source based and runs during export only.
When `DataGridFormattingPlugin` is also active, formatting applied through the
Format Cells UI is transferred automatically after column export callbacks.
The callback result remains the base cell configuration; explicitly stored
Format Cells value and appearance fields override only their corresponding
workbook fields. See [Format Cells](/guides/data-grid/format-cells/) for presets,
selection scope, and stable row identity.
## Column Export Callbacks
```ts
import type { ColumnRegular } from '@revolist/revogrid';
const columns: ColumnRegular[] = [
{
prop: 'price',
name: 'Price',
excelExport: {
columnProperties: ({ value }) => ({
value,
fontWeight: 'bold',
backgroundColor: 'E8EEF9',
}),
cellProperties: ({ value, rowType }) => ({
value,
type: Number,
format: '$#,##0.00',
align: 'right',
fontWeight: rowType === 'rowPinEnd' ? 'bold' : undefined,
}),
},
},
];
```
`columnProperties` formats the generated header cell. `cellProperties` formats body, pinned-top, and pinned-bottom cells.
`cellProperties` receives:
| Field | Meaning |
| --- | --- |
| `value` | Prepared export value after formula evaluation |
| `rawValue` | Original source value for the column |
| `model` | Original source row object |
| `rowIndex` | Index inside its source row segment |
| `rowType` | `rowPinStart`, `rgRow`, or `rowPinEnd` |
| `column` | Current column definition |
| `columnIndex` | Export column index |
| `revogrid` | Grid element |
| `providers` | Runtime plugin providers |
Callbacks can return:
- a primitive value to replace the exported value
- a cell object with `value` and style fields
- a style-only object, which keeps the prepared value
- `undefined`, which keeps the default prepared cell
## Style Fields
Common workbook style fields include:
| Field | Example |
| --- | --- |
| `fontFamily`, `fontSize`, `fontWeight`, `fontStyle` | text styling |
| `textColor`, `backgroundColor`, `fillPatternColor` | colors |
| `borderColor`, `leftBorderColor`, `rightBorderColor`, `topBorderColor`, `bottomBorderColor` | borders |
| `align`, `alignVertical`, `wrap`, `textRotation`, `indent` | alignment |
| `format` | number/date format |
| `height`, `columnSpan`, `rowSpan` | layout |
The bundled `write-excel-file` provider accepts `#E8EEF9` and bare RGB hex values such as `E8EEF9` for known color fields. Bare colors are prefixed before the provider calls `write-excel-file`.
Optional providers map this structural shape to their own workbook library. Unsupported fields may be ignored by a custom provider.
## Formula Export
Formula cells are evaluated by default. To preserve workbook formulas, use the final export-context transformer:
```ts
import { createFormulaExcelExportTransformer } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportTransformers: [
createFormulaExcelExportTransformer({
includeEvaluatedResult: true,
}),
],
};
```
Because this transformer runs after the final visible row projection, generated headers, grouping, merges, and Format Cells integration, it translates grid A1 references to their actual worksheet row and column coordinates. If filtering or collapsed groups remove a referenced row or range, the transformer safely keeps the evaluated scalar instead of emitting a formula that points at the wrong record. Formula names are expanded by default; set `expandFormulaNames: false` only when your provider creates matching workbook-defined names.
When `includeEvaluatedResult` is enabled, providers that support cached formula results receive both the translated formula and the prepared result. The lower-level `createFormulaExcelExportCellProperties()` helper preserves a raw formula string but cannot remap references after rows or columns move, so reserve it for layouts where exported coordinates are guaranteed to match the source grid exactly.
## External Column Type Helpers
Date, numeral, and select column packages are optional app dependencies, so Pro exposes explicit Excel export helpers instead of importing those packages automatically:
```ts
import {
createDateExcelExportColumnOptions,
createNumeralExcelExportColumnOptions,
createSelectExcelExportColumnOptions,
} from '@revolist/revogrid-pro';
const columns = [
{
prop: 'createdAt',
name: 'Created',
columnType: 'date',
excelExport: createDateExcelExportColumnOptions({ format: 'yyyy-mm-dd' }),
},
{
prop: 'amount',
name: 'Amount',
columnType: 'currency',
excelExport: createNumeralExcelExportColumnOptions({ format: '$#,##0.00' }),
},
{
prop: 'status',
name: 'Status',
columnType: 'select',
source: [
{ value: 'open', label: 'Open' },
{ value: 'closed', label: 'Closed' },
],
labelKey: 'label',
valueKey: 'value',
excelExport: createSelectExcelExportColumnOptions({
column: {
prop: 'status',
source: [
{ value: 'open', label: 'Open' },
{ value: 'closed', label: 'Closed' },
],
labelKey: 'label',
valueKey: 'value',
},
}),
},
];
```
Date helpers convert valid date-like values to `Date` cells. Numeral helpers convert finite numbers and numeric strings to number cells. Select helpers export the user-facing label for stored values.
## Grouped Rows, Grouped Headers, And Cell Merge
Row grouping follows the body store's visible item projection. Exported group
records become merged, indented heading rows, expanded descendants keep their
visible order, and descendants hidden by a collapsed group are omitted. Custom
providers can inspect `context.exportMetadata.rowGrouping` for exported
worksheet row numbers, group depth, labels, and expanded state.
Grouped column headers are applied automatically when the grid uses a grouped `columns` tree. Group header rows are inserted before the flattened leaf header row.
Cell merge spans are applied automatically when the grid has `cellMerge` data. The export transform maps grid row and column segment coordinates into provider matrix coordinates, accounting for generated header rows.
The built-in `write-excel-file` provider and ExcelJS adapter consume `columnSpan` and `rowSpan` values from `cellRows`. Custom providers can also read merge metadata from `context.exportMetadata.cellMerge`.
## Provider Context
Custom providers and transformers receive a prepared `ExcelExportProviderContext`:
| Field | Purpose |
| --- | --- |
| `cellRows` | Provider-ready matrix with header rows and formatted cells |
| `rows` | Plain row objects keyed by column prop, with formulas evaluated |
| `sourceRows` | Visible original row models with physical source index and row segment metadata |
| `headerRowsCount` | Number of generated header rows before data rows in `cellRows` |
| `gridDerivedOptions` | Width and freeze-pane hints inferred from current grid state |
| `exportMetadata` | Feature metadata such as row grouping, grouped headers, and cell merge ranges |
Use `cellRows` for formatted workbook output. Use `rows` for simple JSON-style providers that do not need formatting.
## Export Transformers
Use `exportTransformers` for final app-owned changes to the provider context:
```ts
grid.exportExcel = {
exportTransformers: [
(context) => ({
exportMetadata: {
generatedBy: 'orders-report',
},
providerOptions: {
hideGridLines: true,
},
}),
],
};
```
Grid-level transformers run before per-export transformers:
```ts
await excel.export({
exportTransformers: [
(context) => {
context.cellRows[0][0] = { value: 'Exported Orders', fontWeight: 'bold' };
},
],
});
```
---
# CSV Import
URL: https://pro.rv-grid.com/guides/excel-export/csv-import/
Source: src/content/docs/guides/excel-export/csv-import.mdx
Description: Import CSV files with ExportExcelPlugin drag/drop, events, and explicit import options.
`ExportExcelPlugin` imports CSV files by default. `.xlsx` and `.xls` parsing is app-owned; use a parser such as SheetJS, ExcelJS, or read-excel-file and assign normalized rows to the grid yourself.
For app-owned XLSX parsing choices, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/).
## Drag And Drop
By default, users can drop a `.csv` file onto the grid:
Download sample CSV
Download the sample, then drag it onto the grid to verify the import flow.
Control drag/drop import with `grid.exportExcel`:
```ts
grid.exportExcel = {
allowDrag: true,
allowedExtensions: ['.csv'],
};
```
The same options are still accepted through legacy `additionalData.excel`, but new code should prefer `grid.exportExcel`.
## Import Options
| Option | Default | Description |
| --- | --- | --- |
| `allowDrag` | `true` | Enables CSV drag/drop import unless the grid is readonly |
| `allowedExtensions` | `['.csv']` | Case-insensitive filename suffixes accepted in addition to files whose MIME type is `text/csv` |
| `skipColumnGeneration` | `false` | Keeps current grid columns and maps CSV cells by current column order |
When `skipColumnGeneration` is `false`, the first CSV row becomes generated grid columns:
```csv
Name,Status
Ada,Open
Grace,Closed
```
The imported grid receives columns similar to:
```ts
[
{ prop: 0, name: 'Name' },
{ prop: 1, name: 'Status' },
]
```
When `skipColumnGeneration` is `true`, all CSV rows are imported as data and mapped to the existing export column order:
```ts
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'status', name: 'Status' },
];
await excel.importFile(file, {
skipColumnGeneration: true,
});
```
## Plugin Methods
Use `importFile()` when import is triggered by an app-owned upload control:
```ts
const plugins = await grid.getPlugins();
const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin);
await excel?.importFile(file, {
allowedExtensions: ['.csv', '.txt'],
skipColumnGeneration: true,
});
```
Use `isCsv()` to validate a file before import:
```ts
if (excel?.isCsv(file, ['.csv'])) {
await excel.importFile(file);
}
```
`isExcel()` remains as a deprecated compatibility alias. It is CSV-only and returns `false` for `.xlsx` files unless the caller explicitly allows that extension. It does not parse workbook files.
Acceptance is MIME-or-suffix based: `isCsvFile()` returns `true` when `file.type` is `text/csv` **or** the lower-cased filename ends with one of `allowedExtensions`. The configured suffixes are not a strict allowlist for files already identified with the CSV MIME type. Adding `.txt` or `.xlsx` only permits that filename suffix to enter the CSV text parser; it does not add a new parser.
## Import Events
Before applying imported rows, the plugin dispatches CSV-oriented events:
| Event | Detail |
| --- | --- |
| `excel-before-import` | `file`, `sheetName`, `csv`, and parsed `rows` |
| `excel-before-set` | All import detail plus mapped `jsonData` |
`sheetName` is a compatibility label and contains the filename.
Prevent either event to cancel replacing the grid source:
```ts
grid.addEventListener('excel-before-set', (event) => {
const { jsonData } = event.detail;
if (!jsonData.length) {
event.preventDefault();
}
});
```
## Parser Behavior
The default importer reads files with `FileReader.readAsText` and parses CSV values. It handles BOMs, CRLF/LF line endings, quoted values, escaped quotes, empty cells, and trailing blank lines.
When a file matches neither the CSV MIME type nor an allowed suffix, the plugin logs `Only CSV files are supported.` and returns without changing the grid. File-reading or parsing failures are caught, logged as `Failed to load CSV file:`, and also resolve `importFile()` without throwing. If your UI needs a rejected promise or a user-visible error state, validate and read the file in app code before calling the plugin. If the grid is readonly or `allowDrag` is `false`, drag/drop import does not activate.
For `.xlsx` uploads, parse the workbook outside the plugin and assign normalized data:
```ts
const { columns, rows } = await parseWorkbook(file);
grid.columns = columns;
grid.source = rows;
```
---
# Excel Import and Export Libraries
URL: https://pro.rv-grid.com/guides/excel-export/excel-import-export-libraries/
Source: src/content/docs/guides/excel-export/excel-import-export-libraries.mdx
Description: Compare SheetJS, ExcelJS, read-excel-file, and write-excel-file for XLSX import/export workflows with RevoGrid.
RevoGrid Pro includes an Excel export plugin, but most production apps still need to choose how uploaded workbook files are parsed, validated, normalized, and streamed into the grid.
Use this guide when your workflow is:
1. User uploads an Excel file.
2. Your app parses `.xlsx` data.
3. Your app converts the first sheet, selected sheet, or validated sheet into JSON.
4. RevoGrid receives normalized `columns` and `source` data.
The bundled `ExportExcelPlugin` exports `.xlsx` files through `write-excel-file` and imports CSV files by default. XLSX import is app-owned: install the parser you want, parse the workbook, then assign normalized rows to RevoGrid. For plugin options, see the [Excel export guide](/guides/excel-export/).
## Quick Recommendation
| Goal | Recommended library | Why |
| --- | --- | --- |
| Fast `.xlsx` upload to grid JSON | SheetJS | Broad format support, direct `sheet_to_json()` conversion, strong compatibility with real-world workbooks. |
| Full workbook editing or styled report generation | ExcelJS | Good API for worksheets, rows, formatting, formulas, tables, merged cells, and generated reports. |
| Simple validated upload forms | read-excel-file | Small API surface with schema-based parsing and validation. |
| Default RevoGrid Pro `.xlsx` export | write-excel-file | Lightweight writer used by the bundled export provider. |
| Huge browser imports | SheetJS or read-excel-file in a Web Worker | Keep parse work away from the UI thread and load rows into RevoGrid incrementally. |
| Backend import/export processing | SheetJS or ExcelJS | Pick SheetJS for broad import compatibility; pick ExcelJS when workbook editing and formatting matter more. |
## SheetJS
[SheetJS](https://docs.sheetjs.com/) is the strongest general-purpose choice for Excel upload workflows. It works in the browser and Node.js, reads workbook bytes from an `ArrayBuffer`, supports common spreadsheet formats, and converts worksheets directly into JSON.
Use it when the import goal is "Excel file in, JSON rows out".
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import type { ColumnRegular, DataType } from '@revolist/revogrid';
export async function parseSheetJsFile(file: File): Promise<{
columns: ColumnRegular[];
rows: DataType[];
}> {
const data = await file.arrayBuffer();
const workbook = XLSX.read(data, { cellDates: true });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json>(worksheet, {
defval: '',
});
const columns = Object.keys(rows[0] ?? {}).map((key) => ({
prop: key,
name: key,
}));
return { columns, rows };
}
```
```ts
const { columns, rows } = await parseSheetJsFile(file);
grid.columns = columns;
grid.source = rows;
```
SheetJS is usually the best RevoGrid import fit for admin panels, ERP screens, analytics tools, and customer-provided enterprise spreadsheets where file shape is not perfectly controlled.
The tradeoff is that parsing large files on the main browser thread can freeze the UI. For large files, parse in a Web Worker and only send normalized data back to the page.
```ts title="xlsx-import.worker.ts"
import * as XLSX from 'xlsx';
self.onmessage = async (event: MessageEvent) => {
const workbook = XLSX.read(await event.data.arrayBuffer(), { cellDates: true });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json>(worksheet, {
defval: '',
});
const columns = Object.keys(rows[0] ?? {}).map((key) => ({
prop: key,
name: key,
}));
self.postMessage({ columns, rows });
};
```
```ts title="import-ui.ts"
const worker = new Worker(new URL('./xlsx-import.worker.ts', import.meta.url), {
type: 'module',
});
worker.onmessage = ({ data }) => {
grid.columns = data.columns;
grid.source = data.rows;
};
worker.postMessage(file);
```
## ExcelJS
[ExcelJS](https://github.com/exceljs/exceljs) is better when you need to read, edit, preserve, or generate richer workbook structures. It is a good fit for financial reports, styled exports, workbook templates, formulas, tables, and merged-cell workflows.
Use it when the app needs workbook manipulation, not only raw import speed.
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import type { ColumnRegular, DataType } from '@revolist/revogrid';
export async function parseExcelJsFile(file: File): Promise<{
columns: ColumnRegular[];
rows: DataType[];
}> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(await file.arrayBuffer());
const worksheet = workbook.worksheets[0];
const headerRow = worksheet.getRow(1);
const headerValues = Array.isArray(headerRow.values)
? headerRow.values.slice(1)
: Object.values(headerRow.values);
const headers = headerValues
.map((value) => String(value ?? '').trim())
.filter(Boolean);
const columns = headers.map((header) => ({
prop: header,
name: header,
}));
const rows: DataType[] = [];
worksheet.eachRow((row, rowNumber) => {
if (rowNumber === 1) {
return;
}
const model: DataType = {};
headers.forEach((header, index) => {
model[header] = row.getCell(index + 1).value ?? '';
});
rows.push(model);
});
return { columns, rows };
}
```
ExcelJS can also be used as an optional RevoGrid export provider when you need workbook features beyond the bundled provider.
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The tradeoff is size and speed. ExcelJS is usually heavier than SheetJS for pure "parse and convert to JSON" imports.
## read-excel-file
[read-excel-file](https://www.npmjs.com/package/read-excel-file) is the best fit when uploads are simple and validation matters more than workbook feature coverage. It reads `.xlsx` files in the browser or Node.js and can map columns into typed JSON with a schema.
```sh
pnpm add read-excel-file
```
```ts
import readXlsxFile from 'read-excel-file';
const schema = {
name: {
column: 'Name',
type: String,
required: true,
},
age: {
column: 'Age',
type: Number,
},
};
const { rows, errors } = await readXlsxFile(file, { schema });
if (errors.length) {
throw new Error(`Invalid spreadsheet: ${errors[0].error}`);
}
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'age', name: 'Age' },
];
grid.source = rows;
```
This is a strong choice for import forms, onboarding flows, and ETL-style screens where the workbook is really a typed table.
The tradeoff is that it is not intended to be a full workbook manipulation layer. If users upload complex workbooks with formulas, styles, merged headers, comments, or multiple business sheets, use SheetJS or ExcelJS instead.
## write-excel-file
[write-excel-file](https://www.npmjs.com/package/write-excel-file) is a lightweight `.xlsx` writer for browser and Node.js. It is also the default writer behind RevoGrid Pro's bundled Excel export provider.
For most RevoGrid exports, use `ExportExcelPlugin` directly:
```ts
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ExportExcelPlugin];
const plugins = await grid.getPlugins();
await plugins.find((plugin) => plugin instanceof ExportExcelPlugin)?.export({
workbookName: 'orders.xlsx',
sheetName: 'Orders',
});
```
Use `write-excel-file` directly when you are exporting app data that does not need the grid export pipeline.
```sh
pnpm add write-excel-file
```
```ts
import writeXlsxFile from 'write-excel-file';
await writeXlsxFile(
[
[
{ value: 'Name', fontWeight: 'bold' },
{ value: 'Revenue', fontWeight: 'bold' },
],
[
{ value: 'North', type: String },
{ value: 12000, type: Number, format: '$#,##0' },
],
],
{
fileName: 'summary.xlsx',
sheet: 'Summary',
},
);
```
The tradeoff is intentional simplicity. If export needs template workbooks, charts, complex formulas, images, or heavy workbook editing, use ExcelJS or a custom provider.
## RevoGrid Import Pipeline
For RevoGrid imports, keep the parser separate from the grid:
1. Read the uploaded file.
2. Parse workbook data with the selected library.
3. Normalize headers into stable `ColumnRegular` objects.
4. Normalize each row into a plain object keyed by `column.prop`.
5. Validate and coerce values before assigning them to the grid.
6. Assign `grid.columns` before `grid.source`.
```ts
type ImportedTable = {
columns: { prop: string; name: string }[];
rows: Record[];
};
async function importWorkbook(file: File): Promise {
return parseSheetJsFile(file);
}
const imported = await importWorkbook(file);
grid.columns = imported.columns;
grid.source = imported.rows;
```
Avoid putting the raw workbook object into reactive application state. Workbooks can be large and contain circular or library-specific objects. Store the normalized rows, validation errors, and lightweight import metadata instead.
## Performance Notes
Browser-side Excel parsing becomes visible to users once files are large enough. The exact threshold depends on column count, formulas, styles, browser, device, and parser options, but imports around `100k+` rows are where main-thread parsing commonly starts to feel expensive.
For larger files:
- Parse inside a Web Worker.
- Limit the first preview to a small row count.
- Normalize rows in chunks.
- Keep the original workbook out of framework state.
- Show validation errors separately from accepted rows.
- Let RevoGrid render the normalized dataset with virtualization.
For very large or regulated imports, parse on the backend and send RevoGrid paged or streamed JSON instead of asking the browser to own the whole workbook lifecycle.
## Practical Choice
For most RevoGrid apps:
- Use `ExportExcelPlugin` with the bundled `write-excel-file` provider for grid export.
- Use SheetJS for flexible `.xlsx` upload and header inference.
- Use `read-excel-file` for small validated upload forms.
- Use ExcelJS when the workbook itself is the product: formatted reports, templates, formulas, tables, and workbook editing.
That gives the fastest path for common XLSX-to-grid imports while keeping advanced export and workbook-editing cases open through RevoGrid's custom provider contract.
---
# Export Providers
URL: https://pro.rv-grid.com/guides/excel-export/providers/
Source: src/content/docs/guides/excel-export/providers.mdx
Description: Choose the default XLSX provider, optional adapters, or a custom Excel export provider.
`ExportExcelPlugin` prepares one provider context, applies built-in feature transforms, applies custom `exportTransformers`, then passes the context to the selected provider.
Provider resolution is:
1. `plugin.export({ provider })`
2. `grid.exportExcel.exportProvider`
3. bundled `'write-excel-file'`
## Default Provider
The bundled provider is backed by `write-excel-file/browser` and exports `.xlsx` only:
```ts
await excel.export({
workbookName: 'orders',
sheetName: 'Orders',
});
```
Extensionless workbook names receive `.xlsx`, so `orders` becomes `orders.xlsx`.
The default provider rejects incompatible workbook output clearly:
- `writingOptions.bookType` must be `xlsx`.
- `workbookName` must be extensionless or end in `.xlsx`.
- Use an optional adapter or custom provider for formats such as `.xlsb`, `.xls`, `.csv`, or `.ods`.
## Provider Options
`providerOptions` are forwarded to the selected provider. For the bundled provider, they are merged into the options passed to `write-excel-file`.
```ts
await excel.export({
workbookName: 'orders.xlsx',
providerOptions: {
fontFamily: 'Arial',
fontSize: 12,
hideGridLines: true,
zoomScale: 90,
},
});
```
By default, the export pipeline derives provider hints from the live grid:
| Derived option | Source |
| --- | --- |
| `columns` widths | Current grid column sizes |
| `stickyRowsCount` | Generated header row plus pinned-top rows |
| `stickyColumnsCount` | `colPinStart` columns |
Explicit `providerOptions` override derived values. Disable derivation for a single export with `deriveGridOptions: false`:
```ts
await excel.export({
deriveGridOptions: false,
providerOptions: {
stickyRowsCount: 0,
stickyColumnsCount: 2,
},
});
```
## Grid-Level Provider
Use `grid.exportExcel.exportProvider` when one grid should consistently use the same workbook library:
```ts
grid.exportExcel = {
exportProvider: myProvider,
};
```
Override it for one export call:
```ts
await excel.export({
provider: anotherProvider,
workbookName: 'report.xlsx',
});
```
## Optional ExcelJS Provider
ExcelJS is not bundled. Install it in your app and pass the module object to the provider factory:
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The ExcelJS adapter consumes the formatted `cellRows` matrix, maps common cell formatting to ExcelJS styles, applies grid-derived widths and freeze panes, and writes/downloads the workbook through ExcelJS.
Use ExcelJS when export needs richer workbook styling, merge support, row heights, freeze panes, or library-specific worksheet setup.
## Optional SheetJS Provider
SheetJS is not bundled. Install it in your app and pass the module object to the provider factory:
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createSheetJsExcelExportProvider(XLSX),
};
```
The SheetJS adapter writes plain row objects from `context.rows`. It does not consume formatted `cellRows`, grouped header rows, or merge spans. Use ExcelJS or a custom provider when workbook formatting must be preserved.
## Workbook Module Adapter
Use `createWorkbookModuleExcelExportProvider()` for simple workbook-like modules that can create a worksheet from plain row objects:
```ts
import { createWorkbookModuleExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createWorkbookModuleExcelExportProvider({
createWorkbook,
createWorksheet,
appendWorksheet,
writeFile,
}),
};
```
Like the SheetJS adapter, the workbook-module adapter uses `context.rows`. Use a full custom provider when the library needs formatted cells, grouped headers, merge metadata, or custom workbook setup.
## Custom Provider
Any object that implements `ExcelExportProvider` can write the workbook:
```ts
import type { ExcelExportProvider } from '@revolist/revogrid-pro';
const myProvider: ExcelExportProvider = {
id: 'app-provider',
async export(context) {
await writeWorkbook({
fileName: context.workbookName,
sheetName: context.sheetName,
cells: context.cellRows,
metadata: context.exportMetadata,
});
},
};
```
Custom providers should use the prepared context instead of reading rendered DOM rows. Virtualized rows are already included in `rows`, `sourceRows`, and `cellRows`.
Ordinary client-side virtualization includes every source row, but `InfinityScrollPlugin` deliberately retains only a moving window. `ExportExcelPlugin` therefore rejects direct export while Infinity Scroll owns the source. Fetch all rows from the backend and export them through a temporary grid with a local source; see [Exporting to Excel with Infinity Scroll](/guides/infinity-scroll/#exporting-to-excel).
## Provider Context Notes
The export context is built from RevoGrid data stores first, with grid element sources as a compatibility fallback. This matters for virtualized grids: providers should not query rendered rows because offscreen records may not be mounted in the DOM.
Pinned row segments are preserved in `sourceRows`:
- `rowPinStart` for pinned top rows
- `rgRow` for body rows
- `rowPinEnd` for pinned bottom rows
The prepared `rows` array keeps the same pinned-top, body, pinned-bottom order after formula evaluation. The formatted `cellRows` matrix adds generated header rows before those data rows.
For workbook providers:
- Use `cellRows` when you need formatted values, column-level cell properties, grouped headers, formulas, or merge spans.
- Use `sourceRows` when you need row-segment semantics, original models, or source indexes.
- Use `rows` only for plain row-object export where formatting and merge metadata do not matter.
If a grid data store is not initialized yet, export falls back to the grid element source for that segment. Missing pinned sources are treated as empty arrays.
For broader library selection guidance, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/).
---
# Managing Reference Data
URL: https://pro.rv-grid.com/guides/faq/data-reference/
Source: src/content/docs/guides/faq/data-reference.mdx
Reference data represents static data in a key/value pair format.
This type of data typically remains constant between server requests, making it ideal for scenarios where meaningful labels need to be displayed alongside coded values. For example, a car make might be represented as `'aud': 'Audi'`.
Reference data mapping is a Core RevoGrid pattern. It uses column value handlers such as `cellParser` and does not require Pro plugins.
{/*
### Example Data Structure
The data returned from the server usually consists of codes (keys) that need to be mapped to their corresponding names (values) for display in the grid.
Here’s an example of how this data can be structured:
```javascript
// Data from server
const rowData = [
{ prod: 'toy', price: 28000 },
{ prod: 'bmw', price: 55000 },
// ...
];
// Supporting reference data
const carMappings = {
'toy': 'Toyota',
'bmw': 'BMW',
'aud': 'Audi',
'hnd': 'Honda'
};
``` */}
## Strategies for Managing Reference Data
Value handlers provide a flexible way to map codes to display values. This method involves more coding but allows for greater customization in how data is formatted and saved.
- **Value Formatter**: Converts the key to a user-friendly value for display in the grid.
- **Value Parser**: Converts the user-selected value back into a key for saving in the underlying data.
{/* #### Example Configuration
Here’s how you can configure the grid to use value handlers:
```javascript
const gridOptions = {
columns: [
{
prop: 'prod',
cellTemplate: (h, params) => carMappings[params.value], // Convert code to name
},
cellCompare: (a, b) => { // Map data for sorting
return carMappings[a] - carMappings[b];
},
cellParser: (model, column) => { // Map data for filtering, editing and saving
return carMappings[model[column.prop]];
}
],
// Other grid options...
};
``` */}
## Conclusion
Effectively managing reference data is essential for ensuring that users see meaningful labels alongside coded values.
```javascript
// fixing render for multiframework
```
---
# Data Synchronization
URL: https://pro.rv-grid.com/guides/faq/data-sync/
Source: src/content/docs/guides/faq/data-sync.mdx
Description: A guide covers core concepts and advanced tips for handling data.
We assume that you have already set up the basic version of RevoGrid and are familiar with configuring the `source` and `columns`. It is crucial to always mutate the data you pass to the grid, as this ensures optimal performance.
RevoGrid offers several popular approaches for working with data:
1. ### Grid-Centric Data Management:
- You can work with the grid and, in the end, request the data directly from it.
This approach allows you to subscribe to the `afteredit` event, which is triggered after a cell edit or range edit occurs.
You can retrieve the current data from the `source` whenever you need it.
Try to modify the data in the grid and check how it updates in real time.
Check `Source code` for more details.
---
---
2. ### Source-Centric Data Management:
- This method involves managing your data independently and handling events before cell editing.
In this case, you need to pass your source array to the grid each time an edit occurs, which will trigger a grid re-render to display the updated data.
As part of the Pro version, we provide the [**EventManagerPlugin**](../data-manage/event-manager-explanation), which consolidates range, clipboard, and cell edit events into a single common event called `gridedit`.
You can utilize this plugin to catch the event, modify it, or even create your own custom event manager.
Try to modify the data in the grid and check how it updates in real time.
---
---
3. ### Pagination and Dynamic Data Loading:
- These two features are two sides of the same coin:
- Pagination allows you to break your data source into manageable parts and load data from a server or cache for each page. For more information, check out the [**PaginationPlugin** and its documentation](../grid-utilities/pagination).
- Dynamic data loading is an advanced version of pagination that loads data in chunks based on user interactions, such as scrolling, without displaying a page panel.
With this approach, you can combine the two methods mentioned above to work with a complete or partial grid dataset, updating the data and passing it to the grid as needed.
## Best Practices
For best practices on data management and manipulation, please refer to our [Best Practices Guide](../faq/patterns). This section will help you optimize your grid's performance and usability.
---
# Best Practices
URL: https://pro.rv-grid.com/guides/faq/patterns/
Source: src/content/docs/guides/faq/patterns.mdx
When working with advanced data grids designed for large datasets, several key UI patterns and practices should be followed to ensure a seamless user experience and maintain high performance.
These patterns help create an interface that is intuitive, responsive, and efficient, even with significant data volumes. Here are some of the most important patterns to follow:
1. **Virtual Scrolling**
- **Pattern**: Efficient handling of large datasets by rendering only visible rows and columns, reducing browser load.
- **Best Practice**: Ensure custom rendering or plugins are compatible with virtual scrolling. Avoid heavy DOM manipulations outside the visible viewport to optimize performance.
2. **Lazy Loading**
- **Pattern**: Load data as needed rather than all at once to improve performance.
- **Best Practice**: Implement lazy loading, particularly with external data sources or APIs, ensuring data is fetched and rendered efficiently in chunks.
3. **Data Virtualization**
- **Pattern**: Keep only a subset of data in memory, fetching and rendering only the data that is visible or near visible.
- **Best Practice**: Ensure features like sorting, filtering, and grouping are optimized for large datasets and compatible with data virtualization.
4. **Custom Cell Renderers**
- **Pattern**: Create custom renderers for complex cell content, such as images, buttons, or interactive elements.
- **Best Practice**: Keep custom cell renderers lightweight to avoid performance bottlenecks. Optimize expensive operations that are repeated across many cells. Use cache mechanisms to improve performance.
5. **Responsive Design**
- **Pattern**: Adapt grid layouts to varying screen sizes and resolutions.
- **Best Practice**: Ensure the grid adjusts to different screen sizes with resizable columns and scrollable content. Use responsive features to manage column visibility or prioritize important data on smaller screens.
6. **Keyboard Navigation**
- **Pattern**: Support keyboard navigation for seamless user interaction, especially for power users.
- **Best Practice**: Ensure custom features integrate well with keyboard navigation, maintaining or enhancing keyboard accessibility. Consider custom keyboard shortcuts where applicable.
7. **Infinite Scrolling**
- **Pattern**: Load additional data as the user scrolls down or across the grid.
- **Best Practice**: Implement or support infinite scrolling, especially for large datasets, providing a smoother user experience compared to traditional pagination.
8. **Column and Row Pinning**
- **Pattern**: Keep specific rows or columns visible while scrolling through the data.
- **Best Practice**: Support pinning in customizations, especially for grids where headers or key identifiers need to remain visible.
9. **Custom Sorting and Filtering**
- **Pattern**: Implement custom logic for sorting and filtering to provide more complex or domain-specific data interactions.
- **Best Practice**: Ensure custom sorting and filtering are optimized for performance, potentially applying logic on the server-side for large datasets.
10. **Editable Cells and Inline Editing**
- **Pattern**: Allow users to edit data directly within the grid for more intuitive data manipulation.
- **Best Practice**: Ensure inline editing is user-friendly, supports keyboard shortcuts, and includes robust data validation and error handling.
11. **Event Handling and Customization**
- **Pattern**: Hook into various events (e.g., cell click, row hover) for custom behaviors.
- **Best Practice**: Optimize event listeners and clean them up when no longer needed to avoid performance issues and memory leaks.
12. **Context Menus and Tooltips**
- **Pattern**: Enhance user interaction by providing additional options or information through context menus and tooltips.
- **Best Practice**: Implement contextually aware and lightweight context menus and tooltips to ensure they don't interfere with performance.
13. **Theming and Custom Styling**
- **Pattern**: Customize themes and styling to align the grid’s appearance with the overall application design.
- **Best Practice**: Use CSS variables and custom properties for consistent and easy-to-manage styles, ensuring custom styles do not conflict with core styles or degrade performance.
14. **State Management**
- **Pattern**: Manage the grid’s state (e.g., selected rows, applied filters) for consistency, especially in complex applications.
- **Best Practice**: Integrate with a state management solution if necessary, ensuring the grid’s state is preserved across sessions or navigation.
15. **Big O Notation Considerations**
- **Pattern**: Understand the computational complexity of operations to ensure performance remains optimal as data size increases.
- **Best Practice**: Evaluate and optimize algorithms, especially for sorting, filtering, and rendering, to ensure they are efficient and scale well with large datasets.
16. **Accessibility (a11y)**
- **Pattern**: Ensure the grid is accessible to all users, including those with disabilities.
- **Best Practice**: Follow accessibility best practices, such as providing proper ARIA labels, ensuring keyboard accessibility, and supporting screen readers. Test with accessibility tools to identify and fix issues.
By adhering to these patterns, you can create an implementation that is not only performant and scalable but also user-friendly and accessible. These practices will help you build grids that efficiently handle large datasets while providing a seamless and intuitive user experience.
---
# Form Edit Example
URL: https://pro.rv-grid.com/guides/form-edit/
Source: src/content/docs/guides/form-edit.mdx
Description: Learn how to implement form-based row editing in RevoGrid
This example demonstrates how to implement form-based row editing using RevoGrid's row expand functionality. Instead of editing cells directly in the grid, users can click on a row to expand it and edit the data in a form format.
## Implementation Details
The form edit functionality is implemented using the `RowExpandPlugin`. When a row is expanded, a custom template renders a form with the row's data. The form is split into two columns for better organization.
Key implementation points:
1. **Row Expansion**: Uses RevoGrid's `RowExpandPlugin` to show/hide the form
2. **Form Layout**: Implements a two-column grid layout using Tailwind/UnoCSS
3. **Data Handling**: Updates the grid's source data when the form is saved
4. **Validation**: Can be extended to add form validation (not implemented in this basic example)
## Features
- Form layout
- Save and Cancel buttons
- Responsive form design
- Custom form fields for different data types
- Automatic grid update on save
## Usage
To implement this in your own project:
1. Import required plugins
2. Configure the grid columns
3. Set up the row expand template with your form fields
4. Handle form submission and data updates
```typescript
grid.additionalData = {
rowExpand: {
expandedRowHeight: 300,
template(h, props) {
// Form template implementation
}
}
};
```
## Customization
You can customize the form by:
- Adding more form fields
- Implementing validation
- Changing the layout
- Adding custom styling
- Implementing different field types
---
# Formula
URL: https://pro.rv-grid.com/guides/formula/
Source: src/content/docs/guides/formula/index.mdx
Description: Build Excel-style calculations in RevoGrid with formulas, A1 references, named ranges, and workbook data.
---
# A1 References and Autofill
URL: https://pro.rv-grid.com/guides/formula/a1-references-autofill/
Source: src/content/docs/guides/formula/a1-references-autofill.mdx
Description: Understand Formula coordinates and translate formulas safely during fill, pinning, sorting, insertion, and deletion.
Formula references use authored row and column coordinates. Start with this compact fixture:
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'qty', name: 'Quantity' }, // A
{ prop: 'rate', name: 'Rate' }, // B
{ prop: 'charge', name: 'Charge' }, // C
];
grid.source = [
{ qty: 2, rate: 5, charge: '=A1*$B$1' },
{ qty: 4, rate: 8, charge: null },
];
```
**Expected result:** C1 displays `10`. Filling C1 down writes `=A2*$B$1` to C2 and displays `20`: A's row is relative, while `$B$1` remains fixed.
## Autofill formulas like Excel
Set `grid.range = true` to expose the fill handle. `FormulaPlugin` automatically
installs or reuses `AutoFillPlugin`; applications should not add another formula
fill strategy or duplicate the fill pipeline. AutoFill continues to own the drag,
base sequence, preview, and commit. Formula only translates A1 references in seed
cells whose raw source value starts with `=`.
```ts
import {
AutoFillPreviewPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin, AutoFillPreviewPlugin]; // Preview is optional.
```
Vertical copy by one row:
| Reference | Seed | Result |
| --- | --- | --- |
| Relative | `=A1` | `=A2` |
| Fixed column and row | `=$A$1` | `=$A$1` |
| Relative column, fixed row | `=A$1` | `=A$1` |
| Fixed column, relative row | `=$A1` | `=$A2` |
| Mixed range | `=SUM(A1:B2,$C1:D$4)` | `=SUM(A2:B3,$C2:D$4)` |
Horizontal copy by one column:
| Reference | Seed | Result |
| --- | --- | --- |
| Relative | `=A1` | `=B1` |
| Fixed column and row | `=$A$1` | `=$A$1` |
| Relative column, fixed row | `=A$1` | `=B$1` |
| Fixed column, relative row | `=$A1` | `=$A1` |
| Mixed range | `=SUM(A1:B2,$C1:D$4)` | `=SUM(B1:C2,$C1:E$4)` |
References translate in vertical, horizontal, upward, leftward, and
two-dimensional fills. Multi-cell source ranges tile from their corresponding
seed cells. Sheet qualifiers and anchor markers are preserved, including
`Products!A1` and `'Product List'!$A1:B$2`. Named ranges, text literals, and
existing `#REF!` values are not rewritten. If a relative reference moves before
A1, that endpoint becomes `#REF!`.
Pinning, sorting, and filtering do not change these offsets: Formula resolves
the source and destination in the authored A1 coordinate space described below.
For non-contiguous projected selections, seed tiling follows the visible source
order. Preview and commit use the identical translated formula matrix.
For a two-dimensional fill from `=A1*$B$1`, the exact destinations are:
| Destination | Raw formula |
| --- | --- |
| One row down | `=A2*$B$1` |
| One column right | `=B1*$B$1` |
| One row down and one column right | `=B2*$B$1` |
With a multi-cell seed, Formula tiles each destination from the corresponding seed rather than treating the whole selection as one formula.
## How A1 references behave when the grid changes
RevoGrid separates a cell's **formula identity** from its **rendered position**.
Formula letters and row numbers describe the authored data coordinate. Pinning,
hiding, sorting, filtering, grouping, sticky rows, and row drag-order can render
that cell somewhere else without changing what its A1 address means.
This is the same principle as Excel Freeze Panes: freezing a column changes what
stays visible while scrolling, but it does not rename the column or rewrite its
formulas.
```text
Authored formula coordinates
A B C D
Product Region Price Total
=C1*124
After pinning Price left (rendered order only)
C A B D
Price Product Region Total
=C1*124
C1 still means the authored Price column. The raw formula and result are unchanged.
```
### Authored order versus rendered order
Columns use the flattened leaf order supplied through `grid.columns`. In this
example the authored order is `[product, region, price, total]`, even while the
Price column is rendered in the left-pinned viewport.
| A1 letter | Authored property | Position after pinning Price left |
| --- | --- | --- |
| `A` | `product` | Second |
| `B` | `region` | Third |
| `C` | `price` | First, pinned left |
| `D` | `total` | Fourth |
Pinning or unpinning therefore never rewrites formula strings or named-range
definitions. A formula such as `=C1*124` remains bound to `price`, including
when the formula column itself is pinned separately from its precedents.
Column occurrences are matched by identity before RevoGrid considers their
`prop`. This matters when two authored columns intentionally use the same
property. If an application replaces every column object and those replacement
columns still have duplicate properties, there is no reliable way to tell the
occurrences apart after pinning. RevoGrid therefore leaves that mapping
unresolved instead of guessing from the rendered pin order. Preserve the column
objects across presentation changes, or use unique properties when columns need
independent A1 identities.
Rows use one initial authored sequence. Each source can contribute zero, one, or
many rows:
```text
pinnedTopSource → source → pinnedBottomSource
first rows next rows final rows
```
After initialization, moving an existing row between those partitions keeps
its A1 identity. The same applies to sorting, filtering, grouping, sticky-row
duplicates, and row drag-order projections. Tracking is automatic and does not
require an id property in application rows. Applications that replace rows with
new immutable objects can optionally provide a stable row key; see
[Immutable row replacement](#immutable-row-replacement-advanced).
```text
Authored identity After pinning Noah to the top
Row 1 Maya C1 = 10 Noah still C2 = 20
Row 2 Noah C2 = 20 Maya still C1 = 10
Row 3 Total =SUM(C1:C2) Total still =SUM(C1:C2) = 30
Rendered position changed; the underlying row addresses did not.
```
### Presentation changes and structural changes
| Operation | What happens to A1 identity? | Are raw formulas rewritten? |
| --- | --- | --- |
| Pin or unpin a row/column | Preserved | No |
| Hide a column | Preserved; the hidden column still calculates | No |
| Sort, filter, group, trim, or restore rows | Preserved | No |
| Render a sticky-row duplicate | Resolves to its underlying authored row | No |
| Drag-order existing rows | Preserved as a runtime projection | No |
| Insert or delete rows/columns | Following coordinates shift | Yes, unless `autoUpdateRefs` is `false` |
| Explicitly reorder the authored column schema | Letters follow the new authored order | Existing formulas are not currently rewritten for the move |
| Replace all rows with new immutable clones, without `rowIdProp` | Treated as a newly authored dataset | No speculative rewrite |
| Replace rows with immutable clones, with `rowIdProp` | Existing IDs retain A1 identity; genuine inserts/deletes remain structural | Only for genuine inserts/deletes |
| Call `evaluateRawValuesFormula` directly | Uses the row and column arrays supplied by the caller | Not applicable; the helper only evaluates |
### Immutable row replacement (advanced)
Most applications do not need row identity configuration. Built-in pinning,
sorting, filtering, grouping, sticky rows, and drag ordering reuse the existing
row models and are tracked automatically.
If an application replaces source rows with fresh immutable clones, object
identity is no longer available. Set `rowIdProp` only when the configured field
is present, stable, and unique for every row:
```ts
grid.formulaNames = {
rowIdProp: 'id',
names: [{ name: 'Revenue', kind: 'range', ref: 'C2:C20' }],
};
grid.source = previousRows.map(row => ({ ...row }));
```
With that key, a cloned row keeps its authored A1 identity even if the new source
order changes or the row moves between pinned and body sources. If the same
immutable update genuinely inserts or deletes IDs, references shift using the
normal structural-edit rules. Rows with a missing key are not assumed to match,
and duplicate keys are ambiguous; use a real record key rather than a row index.
### Explicit schema reorder and standalone evaluation
The explicit column-reorder case is intentionally separate from pinning. If the
authored schema changes from `[A, B, C]` to `[B, A, C]`, `A1` now means the first
column in that new schema. Existing formula text is currently retained; Excel-style
move rewriting is not part of the pinning contract.
The standalone `evaluateRawValuesFormula` helper has no grid viewport or authored
schema to consult. Its A1 coordinates always follow the row and column arrays
passed to that call.
### What deletion does to formulas and ranges
Deletion is structural, so surviving references are normalized to the new row
numbers. For example, begin with:
```text
Row 1 A1 = 10
Row 2 A2 = 20
Row 3 A3 = 30
Direct reference: =A3
Range formula: =SUM(A1:A3)
```
After deleting row 2, the old row 3 becomes row 2:
```text
Row 1 A1 = 10
Row 2 A2 = 30
=A3 → =A2
=SUM(A1:A3) → =SUM(A1:A2)
```
If the deleted row was itself the target of a direct reference, that reference
becomes `#REF!`. A range that only partially overlaps the deleted block shrinks
to its surviving cells. Named ranges and named formulas follow the same rules.
### Disable structural rewriting
Set `autoUpdateRefs: false` when the application owns structural ref updates:
```ts
grid.formulaNames = {
autoUpdateRefs: false,
names: [],
};
```
**Expected result:** inserting or deleting rows/columns does not rewrite existing raw formula strings or named refs. Presentation operations still behave as described above.
:::tip[Recommended mental model]
Treat pinning, hiding, and runtime projections as viewport mapping. Treat genuine
insertion and deletion as spreadsheet structure changes. This keeps formulas
stable during ordinary grid interaction while keeping their visible A1 text
correct after the dataset structure changes.
:::
## Next steps
**Previous:** [Quick Start](/guides/formula/quick-start/) · **Next:** [Named Ranges](/guides/formula/named-ranges/) · **Deeper reference:** [Autofill guide](/guides/data-manage/autofill/)
---
# Async Formula Functions
URL: https://pro.rv-grid.com/guides/formula/async-functions/
Source: src/content/docs/guides/formula/async-functions.mdx
Description: Register grid-local service functions with loading, errors, events, caching, invalidation, and cancellation.
Use an async formula function when a mounted cell needs data from an application service. Calls are grid-local, case-insensitive, and must be the top-level formula call.
## A complete service-backed function
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'sku', name: 'SKU' }, // A
{ prop: 'service', name: 'Service' }, // B
{ prop: 'price', name: 'Service price' }, // C
];
grid.formulaWorkbook = { sheetId: 'Orders', rowIdProp: 'id' };
const plugins = await grid.getPlugins();
const formulas = plugins.find(plugin => plugin instanceof FormulaPlugin);
if (!formulas) throw new Error('FormulaPlugin is not mounted');
const disposeServicePrice = formulas.registerAsyncFormulaFunction({
name: 'SERVICEPRICE',
async handler([sku, service], { signal, rawArgs }) {
const response = await fetch(
`/api/prices/${encodeURIComponent(String(sku))}?service=${encodeURIComponent(String(service))}`,
{ signal },
);
if (!response.ok) throw new Error(`Price request failed: ${response.status}`);
console.debug('Authored arguments:', rawArgs);
return (await response.json()).price;
},
});
grid.source = [{
id: 'order-1',
sku: 'B-200',
service: 'priority',
price: '=SERVICEPRICE(A1,B1)',
}];
// For example, dispose when this page is torn down.
window.addEventListener('pagehide', disposeServicePrice, { once: true });
```
**Expected result:** C1 first renders the default `#LOADING` state, then the returned price. The handler receives evaluated values (`B-200`, `priority`); `rawArgs` retains `A1` and `B1`.
## Map service errors
The default rejected value is `#ERROR`. Use `mapError` when users need a domain-specific state:
```ts
const dispose = formulas.registerAsyncFormulaFunction({
name: 'SERVICEPRICE',
handler: async ([sku], { signal }) => loadPrice(String(sku), signal),
mapError: error =>
error instanceof Error && error.message.includes('404') ? '#NOT_FOUND!' : '#SERVICE!',
});
```
**Expected result:** a missing SKU displays `#NOT_FOUND!`; other rejections display `#SERVICE!`. `mapError` takes precedence over `errorValue`.
## Observe and refresh states
```ts
const onStateChange = (event: CustomEvent) => {
const { name, rowId, status, value, error } = event.detail;
console.log({ name, rowId, status, value, error });
};
grid.addEventListener('asyncformulastatechange', onStateChange);
// Abort and clear cached SERVICEPRICE cells, then recalculate them on render.
formulas.invalidateAsyncFormulaFunction('SERVICEPRICE');
// Teardown
grid.removeEventListener('asyncformulastatechange', onStateChange);
dispose();
```
**Expected result:** a call emits `pending`, then `resolved` or `rejected`. Repeated renders of the same stable cell/formula/arguments share one request and cached result; invalidation causes a new request.
## Cancellation behavior
The handler's `signal` is aborted when its cell arguments or formula change, the function is invalidated/unregistered, or the plugin is destroyed. Pass that signal to `fetch` or your service client. A stale request's later resolution is ignored.
```ts
async handler([sku], { signal }) {
const response = await fetch(`/api/prices/${sku}`, { signal });
return (await response.json()).price;
}
```
**Expected result:** editing A1 from `B-200` to `A-100` aborts the B-200 request and starts one for A-100; only the current request can update the cell.
## Advanced: override a built-in name
Registering `VLOOKUP` intentionally shadows the synchronous built-in only for this mounted grid and only until its disposer runs. Other grids and `calculateFormulaWorkbook` retain the built-in implementation.
Async functions receive evaluated scalar arguments plus `rawArgs`. They cannot be nested inside another expression, and headless `calculateFormulaWorkbook` does not execute them. Load external service data before a backend calculation.
## Next steps
**Previous:** [Cross-sheet Formulas](/guides/formula/cross-sheet/) · **Next:** [Backend Calculation and Persistence](/guides/formula/backend-calculation/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Backend Calculation and Persistence
URL: https://pro.rv-grid.com/guides/formula/backend-calculation/
Source: src/content/docs/guides/formula/backend-calculation.mdx
Description: Recalculate workbooks without a grid and choose whether to persist formulas, caches, or resolved snapshots.
Formula-derived values are not written back into source rows. Formula-aware mounted rendering, sorting, and filtering can consume derived values, while `grid.source` remains the authoritative raw data.
## Choose what to persist
| Strategy | Persist | Use when |
| --- | --- | --- |
| Formulas only | Raw rows containing `=...` | Every consumer can recalculate and no queryable cache is needed. |
| Formulas + calculated cache | Raw formulas plus `values`/patches | APIs, reports, or searches need current values without losing expressions. |
| Resolved snapshot | A separate copy with formulas replaced by values | Exporting or handing data to a system that cannot evaluate formulas. |
## Recommended: calculate a workbook
`calculateFormulaWorkbook` is synchronous, does not mount a grid, and does not mutate supplied rows. Each sheet needs a case-insensitively unique id, unique column properties, a stable `rowIdProp`, and a valid id on every row.
```ts
import { calculateFormulaWorkbook } from '@revolist/revogrid-pro';
const orders = {
id: 'Orders',
rowIdProp: 'id',
columns: [
{ prop: 'sku', name: 'SKU' },
{ prop: 'qty', name: 'Quantity' },
{ prop: 'price', name: 'Price' },
{ prop: 'total', name: 'Total' },
],
rows: [{
id: 'order-1',
sku: 'B-200',
qty: 2,
price: '=VLOOKUP(A1,\'Product List\'!A1:B2,2,FALSE)',
total: '=B1*C1',
}],
};
const products = {
id: 'Product List',
rowIdProp: 'id',
columns: [
{ prop: 'sku', name: 'SKU' },
{ prop: 'price', name: 'Price' },
],
rows: [
{ id: 'product-1', sku: 'A-100', price: 10 },
{ id: 'product-2', sku: 'B-200', price: 25 },
],
};
const result = calculateFormulaWorkbook({
sheets: [orders, products],
previousValues,
});
if (result.errors.length) {
reportFormulaErrors(result.errors);
}
await database.transaction(async transaction => {
for (const patch of result.patches) {
await persistCalculatedCell(transaction, patch);
}
});
previousValues = result.values;
```
**Expected result:** `values.Orders['order-1']` is `{ price: 25, total: 50 }`. On the first run, patches identify `Orders!C1` and `Orders!D1`, including sheet id, row id, property, formula, and calculated value. Passing the returned `values` unchanged as the next `previousValues` baseline produces no patches.
## Isolate formula errors
Errors are cell-level. An unknown function or circular dependency yields an entry in `errors` and an error-bearing patch, while independent cells still calculate.
```ts
const result = calculateFormulaWorkbook({
sheets: [{
id: 'Orders',
rowIdProp: 'id',
columns: [
{ prop: 'broken', name: 'Broken' }, // A
{ prop: 'qty', name: 'Quantity' }, // B
{ prop: 'price', name: 'Price' }, // C
{ prop: 'total', name: 'Total' }, // D
],
rows: [{
id: 'order-1',
broken: '=UNKNOWN(A1)',
qty: 2,
price: 25,
total: '=B1*C1',
}],
}],
});
```
**Expected result:** `errors` contains A1 with `#ERROR`, while D1 still calculates to `50`. Decide explicitly whether your transaction persists successful patches, rejects the whole workbook, or queues a correction.
## Scoped browser snapshot
For a simple one-sheet browser snapshot, evaluate only formula cells against the exact flat row and column arrays you supply:
```ts
import {
evaluateRawValuesFormula,
isFormula,
} from '@revolist/revogrid-pro';
const source = await grid.getSource();
const columns = await grid.getColumns();
const snapshot = source.map(row => {
const resolved = { ...row };
for (const column of columns) {
const raw = row[column.prop];
if (isFormula(raw)) {
resolved[column.prop] = evaluateRawValuesFormula(raw, source, columns);
}
}
return resolved;
});
```
**Expected result:** `snapshot` contains calculated values and `source` remains unchanged. This utility does not automatically reproduce mounted pin partitions, external sheets, async functions, or the grid's authored-identity runtime; use the workbook API for production multi-sheet recalculation.
The application owns scheduling, transactions, authorization, retries, conflict handling, and persistence. RevoGrid calculates the supplied snapshot but does not watch or write your database.
## Next steps
**Previous:** [Async Formula Functions](/guides/formula/async-functions/) · **Next:** [Supported Functions](/guides/formula/supported-functions/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Cross-sheet Formulas
URL: https://pro.rv-grid.com/guides/formula/cross-sheet/
Source: src/content/docs/guides/formula/cross-sheet.mdx
Description: Look up values in external sheet datasets at mount time, after loading, and after updates.
External sheets are datasets available to the Formula engine; they are not additional mounted grids. Sheet ids are case-insensitively unique. Quote an id containing spaces, as in `'Product List'!A1:B2`, and give every sheet stable, unique row ids.
## Complete Orders and Product List fixture
Orders maps A=`sku`, B=`qty`, C=`catalogPrice`, D=`total`. Product List maps A=`sku`, B=`price`.
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
const orderColumns = [
{ prop: 'sku', name: 'SKU' },
{ prop: 'qty', name: 'Quantity' },
{ prop: 'catalogPrice', name: 'Catalog price' },
{ prop: 'total', name: 'Total' },
];
const productColumns = [
{ prop: 'sku', name: 'SKU' },
{ prop: 'price', name: 'Price' },
];
const productRows = [
{ id: 'product-1', sku: 'A-100', price: 10 },
{ id: 'product-2', sku: 'B-200', price: 25 },
];
const orderRows = [{
id: 'order-1',
sku: 'B-200',
qty: 2,
catalogPrice: '=VLOOKUP(A1,\'Product List\'!A1:B2,2,FALSE)',
total: '=B1*C1',
}];
grid.plugins = [FormulaPlugin];
grid.columns = orderColumns;
grid.formulaWorkbook = {
sheetId: 'Orders',
rowIdProp: 'id',
externalSheets: [{
id: 'Product List',
rowIdProp: 'id',
columns: productColumns,
rows: productRows,
}],
};
grid.source = orderRows;
```
**Expected result:** C1 displays `25` and D1 displays `50`. The raw formulas remain in `orderRows`.
## INDEX and MATCH
Use INDEX/MATCH when the lookup and return ranges should be explicit:
```ts
orderRows[0].catalogPrice =
'=INDEX(\'Product List\'!B1:B2,MATCH(A1,\'Product List\'!A1:A2,0))';
grid.source = [...orderRows];
```
**Expected result:** C1 still displays `25`, then D1 displays `50`.
The tested lookup subset is `LOOKUP`, `VLOOKUP`, `HLOOKUP`, and `INDEX`/`MATCH`. `XLOOKUP` and arbitrary Excel workbook features are not supported.
## Load an external sheet later
Mount Orders with no Product List, then register it when the application finishes loading:
```ts
grid.formulaWorkbook = {
sheetId: 'Orders',
rowIdProp: 'id',
externalSheets: [],
};
grid.source = orderRows;
const plugins = await grid.getPlugins();
const formulas = plugins.find(plugin => plugin instanceof FormulaPlugin);
if (!formulas) throw new Error('FormulaPlugin is not mounted');
formulas.upsertFormulaSheet({
id: 'Product List',
rowIdProp: 'id',
columns: productColumns,
rows: productRows,
});
```
**Expected result:** C1 first renders the mounted missing-sheet loading state, then changes to `25`; D1 changes to `50`. The synchronous backend calculator reports a missing sheet as an error instead of waiting.
## Update an external cell
```ts
formulas.updateFormulaSheetCell('Product List', 'product-2', 'price', 30);
```
**Expected result:** C1 changes from `25` to `30`, and D1 changes from `50` to `60`. The row id selects `product-2`; the final argument uses the column property `price`, not the A1 letter.
## Workbook lifecycle API
| Method | Use it when |
| --- | --- |
| `setFormulaWorkbook(config)` | Replacing the mounted sheet identity or the complete set of external sheets. |
| `upsertFormulaSheet(sheet)` | Adding a late-loaded sheet or replacing one dataset. |
| `updateFormulaSheetCell(sheetId, rowId, prop, value)` | Applying one known external cell update. |
| `removeFormulaSheet(sheetId)` | Making one external sheet unavailable. Dependent mounted formulas return to the missing-sheet loading state. |
| `setFormulaWorkbook(null)` | Detaching the mounted grid from workbook context. |
## Next steps
**Previous:** [Formula UI and Helpers](/guides/formula/formula-ui/) · **Next:** [Async Formula Functions](/guides/formula/async-functions/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Formula UI and Helpers
URL: https://pro.rv-grid.com/guides/formula/formula-ui/
Source: src/content/docs/guides/formula/formula-ui.mdx
Description: Add a Formula Bar, dependency highlights, named dropdowns, and formula-driven styling independently.
`FormulaPlugin` owns calculation. The integrations below are optional and independently configurable.
## 1. Formula Bar
```html
```
```ts
import {
FormulaBarPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
const grid = document.querySelector('#orders');
const host = document.querySelector('#formula-bar');
if (!grid || !host) throw new Error('Formula UI hosts are missing');
grid.plugins = [FormulaBarPlugin, FormulaPlugin];
grid.columns = [
{ prop: 'qty', name: 'Quantity' },
{ prop: 'price', name: 'Price' },
{ prop: 'total', name: 'Total' },
];
grid.formulaBar = { host };
grid.source = [{ qty: 3, price: 10, total: '=A1*B1' }];
// Remove the external editor without removing Formula.
grid.formulaBar = null;
```
**Expected result:** focusing C1 shows raw `=A1*B1` in the Formula Bar while C1 renders `30`. Text and number drafts commit on Enter; dropdown, boolean, and date/time choices commit when selected.
The adaptive host selects a formula/text, number, percent, dropdown, boolean, date/time, or fallback cell editor. Existing text-only integrations may pass `{ el: input }` instead. See the [Formula API](/api/formula/) for custom adapters and the typed `getFormulaBarState()` / `setFormulaBarValue()` runtime.
## 2. Dependency highlighting
Formula installs or reuses `FormulaDependencyHighlightPlugin`. By default it highlights references only while a formula is being edited. Set `highlightOnFocus: true` to also highlight when a formula cell is merely focused.
```ts
grid.plugins = [FormulaPlugin];
grid.formulaDependencyHighlight = {
highlightOnFocus: true,
includeNamedRanges: true,
dependencyClass: 'formula-source-cell',
formulaCellClass: 'formula-active-cell',
dependencyColors: ['#2563eb', '#dc2626', '#16a34a'],
};
```
**Expected result:** focusing C1 with raw `=A1*B1` marks A1 and B1 as dependencies and C1 as the active formula cell. Set `grid.formulaDependencyHighlight = false` to disable the behavior.
Highlights cover direct A1 cells/ranges and named ranges. They do not expand named formulas or a complete transitive dependency graph.
## 3. Named dropdown
`createNamedRangeDropdown` deduplicates a range into dropdown options. Register `ColumnDropdown` as the column type.
```ts
import {
ColumnDropdown,
FormulaPlugin,
createNamedRangeDropdown,
} from '@revolist/revogrid-pro';
const source = [
{ allowed: 'Open', selected: 'Open' },
{ allowed: 'Open', selected: 'Closed' },
{ allowed: 'Closed', selected: null },
];
const names = [{ name: 'AllowedStatuses', kind: 'range' as const, ref: 'A1:A3' }];
const columns = [
{ prop: 'allowed', name: 'Allowed status' }, // A
{
prop: 'selected',
name: 'Selected status',
columnType: 'statusDropdown',
dropdown: createNamedRangeDropdown('AllowedStatuses', {
allSources: source,
columns: [
{ prop: 'allowed', name: 'Allowed status' },
{ prop: 'selected', name: 'Selected status' },
],
names,
}),
},
];
grid.plugins = [FormulaPlugin];
grid.columnTypes = { statusDropdown: ColumnDropdown };
grid.formulaNames = { names };
grid.columns = columns;
grid.source = source;
```
**Expected result:** the Selected status editor offers `Open` and `Closed` once each, even though `Open` appears twice in the named range.
## 4. Conditional formatting
The helper receives the current cell through `cellvalue` and 1-based `row` / `column` tokens.
```ts
import { createFormulaConditionalCellProperties } from '@revolist/revogrid-pro';
const source = [
{ status: 'Open' },
{ status: 'Closed' },
];
const columns = [{ prop: 'status', name: 'Status' }];
columns[0].cellProperties = createFormulaConditionalCellProperties(
'=cellvalue="Closed"',
{ class: 'is-closed' },
{ allSources: source, columns },
);
grid.columns = columns;
grid.source = source;
```
```css
revo-grid .is-closed {
color: #166534;
background: #dcfce7;
font-weight: 600;
}
```
**Expected result:** only row 2 receives `is-closed`. `=row=2` matches the same row; `=column=1` matches cells in the first authored column. Comparisons support `>`, `>=`, `<`, `<=`, `=`, and `<>` for numeric and string values.
## Composition checklist
- Configure `FormulaPlugin` once.
- Add a Formula Bar only when users need an editor outside the cell.
- Use `highlightOnFocus: true` only when passive focus should reveal dependencies.
- Register `ColumnDropdown` before assigning a named-range dropdown column.
- Supply the same source, columns, and names to helper factories that the grid uses.
- Tear down external listeners and set `grid.formulaBar = null` when removing their hosts.
## Next steps
**Previous:** [Named Ranges](/guides/formula/named-ranges/) · **Next:** [Cross-sheet Formulas](/guides/formula/cross-sheet/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Named Ranges
URL: https://pro.rv-grid.com/guides/formula/named-ranges/
Source: src/content/docs/guides/formula/named-ranges.mdx
Description: Use workbook and sheet names for constants, ranges, reusable formulas, and runtime navigation.
Names replace repeated coordinates and values with business terms. `FormulaPlugin` installs or reuses the named-range runtime automatically. Register `NamedRangesPlugin` explicitly when your application needs its runtime methods, events, or Name Manager.
Names are case-insensitive. A name must start with a letter, underscore, or backslash; it cannot contain spaces, resemble an A1 cell such as `B12`, or match a FormulaJS function. Formula names must store a value beginning with `=`.
## 1. Named constant
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'subtotal', name: 'Subtotal' }, // A
{ prop: 'total', name: 'With tax' }, // B
];
grid.formulaNames = {
names: [{ name: 'TaxRate', kind: 'constant', value: 0.2 }],
};
grid.source = [{ subtotal: 100, total: '=A1*(1+TaxRate)' }];
```
**Expected result:** B1 displays `120`. Changing `TaxRate` to `0.25` and reassigning `grid.formulaNames` recalculates B1 to `125`.
## 2. Named range
```ts
grid.columns = [
{ prop: 'label', name: 'Period' }, // A
{ prop: 'amount', name: 'Amount' }, // B
];
grid.formulaNames = {
names: [{ name: 'SalesRevenue', kind: 'range', ref: 'B1:B3' }],
};
grid.source = [
{ label: 'Jan', amount: 100 },
{ label: 'Feb', amount: 200 },
{ label: 'Mar', amount: 300 },
{ label: 'Total', amount: '=SUM(SalesRevenue)' },
];
```
**Expected result:** B4 displays `600`.
:::note
`Revenue` is a FormulaJS function name, so this guide uses `SalesRevenue`. Name validation rejects function-name collisions instead of creating an ambiguous expression.
:::
## 3. Named formula
Build on the preceding range and add a reusable formula:
```ts
grid.formulaNames = {
names: [
{ name: 'TaxRate', kind: 'constant', value: 0.2 },
{ name: 'SalesRevenue', kind: 'range', ref: 'B1:B3' },
{
name: 'AfterTaxSales',
kind: 'formula',
value: '=SUM(SalesRevenue)-SUM(SalesRevenue)*TaxRate',
},
],
};
grid.source = [
{ label: 'Jan', amount: 100 },
{ label: 'Feb', amount: 200 },
{ label: 'Mar', amount: 300 },
{ label: 'Net', amount: '=AfterTaxSales' },
];
```
**Expected result:** B4 displays `480`. Circular names, including two formula names that refer to each other, resolve to `#ERROR`.
## 4. Workbook and sheet scope
An unqualified name first uses the active sheet's local definition, then the workbook definition. Qualify a name to bypass that precedence.
```ts
grid.formulaNames = {
activeSheetId: 'Q4',
names: [
{ name: 'TaxRate', scope: 'workbook', kind: 'constant', value: 0.2 },
{ name: 'TaxRate', scope: 'sheet', sheetId: 'Q4', kind: 'constant', value: 0.25 },
{ name: 'TaxRate', scope: 'sheet', sheetId: 'Q1', kind: 'constant', value: 0.1 },
],
};
grid.columns = [
{ prop: 'local', name: 'Local' },
{ prop: 'global', name: 'Workbook' },
{ prop: 'q1', name: 'Q1' },
];
grid.source = [{
local: '=TaxRate',
global: '=workbook!TaxRate',
q1: '=Q1!TaxRate',
}];
```
**Expected result:** A1 displays `0.25`, B1 displays `0.2`, and C1 displays `0.1`. Duplicate display names are allowed across distinct scopes, but not twice within the same scope.
## Runtime management
Register the runtime explicitly, then retrieve it with a typed guard:
```ts
import {
FormulaPlugin,
NamedRangesPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [NamedRangesPlugin, FormulaPlugin];
const plugins = await grid.getPlugins();
const namedRanges = plugins.find(plugin => plugin instanceof NamedRangesPlugin);
if (!namedRanges) throw new Error('NamedRangesPlugin is not mounted');
const validation = namedRanges.validateFormulaNameRef({
name: 'ForecastRange',
kind: 'range',
ref: 'D2:D20',
});
if (!validation.valid) throw new Error(validation.errors.join('; '));
namedRanges.upsertFormulaName({
name: 'ForecastRange',
kind: 'range',
ref: 'D2:D20',
});
await namedRanges.jumpToFormulaName('ForecastRange');
namedRanges.deleteFormulaName('ForecastRange', 'workbook');
```
Listen on the grid for `formulanameschange`, `formulanamevalidationerror`, and `formulanamejump` when application UI needs to react to those operations.
```ts
const onNamesChange = (event: CustomEvent) => console.log(event.detail);
grid.addEventListener('formulanameschange', onNamesChange);
// Teardown
grid.removeEventListener('formulanameschange', onNamesChange);
```
The bundled manager provides create, edit, delete, validation, and range navigation:
```ts
import { defineFormulaNameManager } from '@revolist/revogrid-pro';
const panel = document.querySelector('#name-manager');
if (panel) defineFormulaNameManager(panel, grid, { pageSize: 50 });
```
## Reference updates and immutable data
With `autoUpdateRefs` enabled (the default), deterministic row/column insertions and deletions update range refs, named formulas, and raw source formulas. If row 1 is inserted before `B1:B3`, the name becomes `B2:B4`; deleting a referenced row shrinks the range. Set `autoUpdateRefs: false` to preserve authored strings unchanged.
Pinning, sorting, filtering, hiding, and other presentation changes never rewrite refs. Applications that replace every row with immutable clones may set `rowIdProp` to a stable, present, unique record key so surviving rows retain their authored identity. See [A1 References and Autofill](/guides/formula/a1-references-autofill/) for the full structural model.
## Putting names into an application
Add only the surface the user needs: Formula Bar for direct editing, Name Manager for registry maintenance, dependency highlighting for inspection, named dropdowns for list choices, and conditional formulas for styling. Each integration has an independent copyable example in [Formula UI and Helpers](/guides/formula/formula-ui/).
## Next steps
**Previous:** [A1 References and Autofill](/guides/formula/a1-references-autofill/) · **Next:** [Formula UI and Helpers](/guides/formula/formula-ui/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Overview
URL: https://pro.rv-grid.com/guides/formula/overview/
Source: src/content/docs/guides/formula/overview.mdx
Description: Understand what Formula calculates, what remains in source data, and which guide to use next.
Formula adds spreadsheet-style calculations to RevoGrid. A source value beginning with `=` is stored as an expression, while the mounted grid renders its derived result. For example, source data can retain `=A1*B1` while the cell displays `30`.
The contract is deliberately narrower than Excel:
- A1 coordinates follow the authored row and column order, not the current sorted, filtered, pinned, or hidden presentation.
- Formula recalculates mounted cells when configured source or workbook data changes. It does not replace the raw formula in `grid.source` with the result.
- Invalid syntax, unknown functions, and circular references resolve to `#ERROR`. Autofill that moves a relative reference before A1 produces `#REF!`.
- Functions come from FormulaJS, but RevoGrid owns the expression parser. Use the [documented FormulaJS-backed subset](/guides/formula/supported-functions/), not assumed full Excel compatibility.
## Try the demo
The demo is an Orders grid connected to a Product List dataset. In the workbook legend, **Orders** is the mounted RevoGrid. **Product List** is external sheet data available to the formula engine; it is not a second mounted grid. On narrow screens the legend stacks these parts without changing that relationship.
| Control | What to observe |
| --- | --- |
| Select a formula cell | The Formula Bar shows the raw `=...` expression while the grid keeps showing the calculated value. |
| **Names on/off** | Formulas switch between readable names and direct A1 references without changing their results. |
| **Insert row** | References below the inserted row update to keep pointing at the same logical data. |
| **Name manager** | View and edit range, constant, and formula names used by the workbook. |
| **Raise catalog price** | Product List changes through the mounted Formula API and dependent Orders cells recalculate. |
| **Reload service lookup** | The async formula is invalidated, briefly shows its loading state, then renders the refreshed service value. |
## Choose your path
| Goal | Start here | Helpful first |
| --- | --- | --- |
| Add your first calculated column | [Quick Start](/guides/formula/quick-start/) | RevoGrid columns and source data |
| Copy formulas or understand A1 identity | [A1 References and Autofill](/guides/formula/a1-references-autofill/) | Quick Start |
| Replace coordinates with readable names | [Named Ranges](/guides/formula/named-ranges/) | A1 references |
| Add a Formula Bar, highlights, lists, or styling | [Formula UI and Helpers](/guides/formula/formula-ui/) | Quick Start |
| Look up values in another dataset | [Cross-sheet Formulas](/guides/formula/cross-sheet/) | A1 references |
| Call an application service from a formula | [Async Formula Functions](/guides/formula/async-functions/) | Quick Start |
| Recalculate and persist without a grid | [Backend Calculation and Persistence](/guides/formula/backend-calculation/) | Cross-sheet formulas |
| Check syntax and available functions | [Supported Functions](/guides/formula/supported-functions/) | Quick Start |
## Next steps
**Next:** [Quick Start](/guides/formula/quick-start/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Quick Start
URL: https://pro.rv-grid.com/guides/formula/quick-start/
Source: src/content/docs/guides/formula/quick-start.mdx
Description: Install Formula, calculate line totals, aggregate results, and fill formulas into new rows.
## Install
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro
```
## 1. Calculate line totals
This complete Vanilla TypeScript example maps A to `qty`, B to `unitPrice`, and C to `total`. Register the custom element, attach the grid, configure Formula, and assign `source` last.
```html
```
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { FormulaPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid/dist/revo-grid/revo-grid.css';
import '@revolist/revogrid-pro/styles.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.columns = [
{ prop: 'qty', name: 'Quantity' }, // A
{ prop: 'unitPrice', name: 'Unit price' }, // B
{ prop: 'total', name: 'Total' }, // C
];
grid.plugins = [FormulaPlugin];
document.querySelector('#grid')?.append(grid);
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: '=A2*B2' },
];
```
**Expected result:** C1 displays `30` and C2 displays `24`. Reading `grid.source` still returns the raw strings `=A1*B1` and `=A2*B2`.
## 2. Aggregate calculated cells
Add a third ordinary row whose C cell sums the first two calculated totals:
```ts
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: '=A2*B2' },
{ qty: '', unitPrice: 'Grand total', total: '=SUM(C1:C2)' },
];
```
**Expected result:** C3 displays `54`. Formula resolves C1 and C2 before calculating their range total.
:::note
This example uses an ordinary source row so its A1 address is unambiguous. Pinned-top, main, and pinned-bottom rows share one authored coordinate space; learn that ordering in [A1 References and Autofill](/guides/formula/a1-references-autofill/).
:::
## 3. Fill a formula
Enable range selection to show the fill handle. Seed C1, then drag its handle down to C2.
```ts
grid.range = true;
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: null },
];
```
**Expected result:** the fill writes raw `=A2*B2` into C2, which displays `24`. Relative references move with the destination; `$` anchors remain fixed.
`FormulaPlugin` installs or reuses its required Autofill, dependency-highlight, and named-range runtimes. You may register those plugins explicitly when you need their public APIs or custom configuration, but basic formulas do not require duplicate registrations.
## What happens when data changes?
Editing a referenced mounted cell causes dependent formula cells to recalculate. The source continues to own the expression, so persistence code sees `=A1*B1`, not `30`. Use [Backend Calculation and Persistence](/guides/formula/backend-calculation/) when you also need stored calculated values.
| Symptom | Cause |
| --- | --- |
| The cell displays text such as `A1*B1` | The source string is missing its leading `=`. |
| `#ERROR` | Syntax is invalid, a function/name is unknown, or the dependency is circular. |
| `#REF!` after fill | A translated relative reference moved before column A or row 1. |
## Framework examples
The complete demo has equivalent [Vanilla TypeScript](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.ts), [React](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.tsx), [Vue 3](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.vue), and [Angular](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/FormulaAngular.ts) sources.
## Next steps
**Previous:** [Overview](/guides/formula/overview/) · **Next:** [A1 References and Autofill](/guides/formula/a1-references-autofill/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Supported Functions
URL: https://pro.rv-grid.com/guides/formula/supported-functions/
Source: src/content/docs/guides/formula/supported-functions.mdx
Description: Learn Formula expression syntax and browse verified FormulaJS-backed grid patterns.
Formula uses functions from FormulaJS through RevoGrid's own expression parser. This is a documented FormulaJS-backed subset, not full Excel compatibility and not every call accepted by the standalone FormulaJS JavaScript API.
## Grid formula syntax
- Stored grid formulas begin with `=`.
- Function arguments use commas.
- Values can come from A1 cells, rectangular A1 ranges, names, numbers, quoted strings, and `TRUE` / `FALSE`.
- Sheet ids containing spaces use quotes: `'Product List'!A1:B3`.
- Arithmetic supports `+`, `-`, `*`, `/`, parentheses, and unary signs.
- Comparisons support `>`, `>=`, `<`, `<=`, `=`, and `<>`.
- Nested supported function calls are evaluated from the inside out.
JavaScript array literals such as `[1,2,3]` are not grid formula syntax. Put values in cells and pass an A1 range such as `A1:A3`, or use a named range. Low-level `evaluate()` examples in the FormulaJS project are JavaScript API calls and may not be copyable grid formulas.
## Verified patterns
The following catalog uses one deterministic fixture and is exercised by the Formula documentation test.
## Lookup support
The tested lookup set is `LOOKUP`, `VLOOKUP`, `HLOOKUP`, and `INDEX` with `MATCH`. `XLOOKUP` is not currently supported. For a complete external-sheet fixture, see [Cross-sheet Formulas](/guides/formula/cross-sheet/).
## Error values
| Situation | Result |
| --- | --- |
| Unknown syntax/function or circular formula/name | `#ERROR` |
| Autofill translates a relative reference before A1 | `#REF!` |
| A mounted formula explicitly references an external sheet that is not registered | `#LOADING` |
| An async formula rejects without a custom `errorValue` or `mapError` | `#ERROR` |
FormulaJS exposes more functions than are shown here, but parser compatibility and argument shapes still matter. Verify an unlisted function with your actual data before treating it as part of an application contract.
## Next steps
**Previous:** [Backend Calculation and Persistence](/guides/formula/backend-calculation/) · **Next:** [Formula API](/api/formula/) · **Deeper reference:** [Quick Start](/guides/formula/quick-start/)
---
# Gantt Overview
URL: https://pro.rv-grid.com/guides/gantt/
Source: src/content/docs/guides/gantt/index.mdx
Description: RevoGrid Gantt with deterministic scheduling, timeline editing, dependencies, resources, baselines, and diagnostics.
Surface: gantt
RevoGrid Gantt is a standalone commercial project-planning package built on RevoGrid. It combines a virtualized task table with a scheduling engine and interactive timeline for dependencies, resources, progress, critical path, baselines, and project analysis.
Create a `revo-grid` element and configure Gantt through public element properties.
[Start with TypeScript](/guides/gantt/introduction/vanilla-typescript/)
Bind Gantt properties through `@revolist/react-datagrid`.
[Start with React](/guides/gantt/introduction/react/)
Use `@revolist/vue3-datagrid` with reactive project collections.
[Start with Vue](/guides/gantt/introduction/vue/)
Use the standalone `RevoGrid` wrapper and property bindings.
[Start with Angular](/guides/gantt/introduction/angular/)
## How Gantt Fits Together
```txt
tasks + calendars + dependencies + resources + assignments
│
▼
deterministic scheduler
│
▼
projected task rows + timeline geometry
│
▼
RevoGrid rendering and interaction surface
```
Your application owns project data and persistence. `GanttPlugin` validates and schedules that data, projects it into task-table rows and timeline geometry, and routes supported edits back through Gantt mutation services. RevoGrid owns virtualization, columns, selection, keyboard navigation, and the browser rendering lifecycle.
## What You Can Build
- Work breakdown structures with tasks, summaries, and milestones.
- Automatic or manual schedules using calendars, dependencies, constraints, and deadlines.
- Resource assignments, capacity analysis, effort modes, and leveling.
- Baseline comparison, progress tracking, slack, and critical-path analysis.
- Editable timelines with drag, resize, progress, dependency, task-editor, and context-menu workflows.
- JSON and Microsoft Project interchange plus grid, Excel, and print-oriented export workflows.
## Recommended Path
1. Complete [Installation](/guides/gantt/introduction/installation/) and one framework quick start.
2. Follow the [project tutorial](/guides/gantt/tutorial/) to add dependencies, resources, editing, baselines, and export.
3. Learn the [data model](/guides/gantt/concepts/data-model/) and [scheduling model](/guides/gantt/scheduling/overview/).
4. Explore the searchable [Gantt examples](/guides/gantt/examples/).
5. Use the [Gantt API reference](/api/gantt/) for exact signatures.
These guides document capabilities present in this repository. See Limitations for intentional boundaries and Microsoft Project differences.
---
# Gantt Baselines
URL: https://pro.rv-grid.com/guides/gantt/concepts/baselines/
Source: src/content/docs/guides/gantt/concepts/baselines.mdx
Description: Capture a baseline snapshot of your schedule and overlay it beneath current task bars to visualise drift.
Surface: gantt
A **baseline** is a saved snapshot of task dates and progress at a specific point in time (e.g. when the project was approved).
The Gantt chart can overlay these baseline bars beneath the live bars, making schedule drift immediately visible.
---
## Adding a Baseline
Pass one or more `BaselineSnapshot` objects via `grid.ganttBaselines`:
```typescript
grid.ganttBaselines = [
{
id: 'baseline-approved',
name: 'Approved Plan',
capturedAt: '2026-04-01T08:00:00Z',
tasks: [
{ taskId: 't1', startDate: '2026-04-06', endDate: '2026-04-22', duration: 13, progressPercent: 0 },
{ taskId: 't2', startDate: '2026-04-06', endDate: '2026-04-10', duration: 5, progressPercent: 0 },
{ taskId: 't3', startDate: '2026-04-10', endDate: '2026-04-10', duration: 0, progressPercent: 0 },
{ taskId: 't4', startDate: '2026-04-13', endDate: '2026-04-22', duration: 8, progressPercent: 0 },
{ taskId: 't5', startDate: '2026-04-23', endDate: '2026-05-16', duration: 18, progressPercent: 0 },
{ taskId: 't6', startDate: '2026-04-23', endDate: '2026-05-09', duration: 13, progressPercent: 0 },
{ taskId: 't7', startDate: '2026-04-23', endDate: '2026-05-16', duration: 18, progressPercent: 0 },
{ taskId: 't8', startDate: '2026-05-16', endDate: '2026-05-16', duration: 0, progressPercent: 0 },
],
},
];
```
You can also capture a snapshot from the current scheduled state:
```typescript
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin.constructor?.name === 'GanttPlugin');
const baseline = gantt?.captureBaseline?.({
id: 'baseline-current',
name: 'Current Approved Plan',
capturedAt: '2026-04-15T08:00:00Z',
});
if (baseline) {
grid.ganttBaselines = [
...(grid.ganttBaselines ?? []),
baseline,
];
}
```
## Enabling the Overlay
Turn on baseline rendering through the `visuals` key in your project config:
```typescript
grid.gantt = {
// …required fields
visuals: {
showBaseline: true,
baselineId: 'baseline-approved', // optional — defaults to the latest snapshot
},
};
```
When `showBaseline` is `true`, a translucent bar is drawn behind each live task bar showing its baseline position.
Only tasks listed in `BaselineSnapshot.tasks` show a baseline bar. Tasks added after the snapshot was captured render without one.
## Multiple Baselines
You can store several snapshots and switch between them at runtime:
```typescript
// Initially show the approved plan
grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-approved' } };
// Switch to a re-baseline after scope change
grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-replan' } };
```
## Variance Columns
When a baseline is visible, each projected task row also exposes optional variance fields:
| Field | Description |
|---|---|
| `startVarianceDays` | Current scheduled start minus baseline start, in calendar days |
| `finishVarianceDays` | Current scheduled finish minus baseline finish, in calendar days |
| `durationVariance` | Current scheduled duration minus baseline duration |
| `progressVariancePercent` | Current progress minus baseline progress, in percentage points |
Add the default columns when users need to inspect drift numerically:
```typescript
grid.columns = [
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startVarianceDays'),
createDefaultTaskTableColumn('finishVarianceDays'),
createDefaultTaskTableColumn('durationVariance'),
createDefaultTaskTableColumn('progressVariancePercent'),
];
```
## BaselineSnapshot Fields
| Field | Type | Description |
|---|---|---|
| `id` | `BaselineId` | Unique identifier for this snapshot |
| `name` | `string` | Human-readable label (e.g. `"Q1 Approved Plan"`) |
| `capturedAt` | `ISODateTimeString` | When the snapshot was taken |
| `tasks` | `BaselineTaskSnapshot[]` | Per-task date and progress records |
### BaselineTaskSnapshot Fields
| Field | Type | Description |
|---|---|---|
| `taskId` | `TaskId` | References the live task |
| `startDate` | `ISODateString` | Baseline start date |
| `endDate` | `ISODateString` | Baseline end date |
| `duration` | `number` | Baseline task duration |
| `progressPercent` | `number` | Baseline progress (0–100) |
---
# Data Model
URL: https://pro.rv-grid.com/guides/gantt/concepts/data-model/
Source: src/content/docs/guides/gantt/concepts/data-model.mdx
Description: Required grid properties and Gantt entity collections.
Surface: gantt
This page is for wiring the minimum data surface.
Required entry points on `HTMLRevoGridElement`:
- `gantt`: project config (`GanttPluginConfig`)
- `source`: flat task rows
- `ganttCalendars`: `CalendarEntity[]`
Optional linked collections:
- `ganttDependencies`: `DependencyEntity[]`
- `ganttResources`: `ResourceEntity[]`
- `ganttAssignments`: `AssignmentEntity[]`
- `ganttBaselines`: `BaselineSnapshot[]`
Public shape is declared in:
- `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`
- `packages/gantt/src/gantt/core/domain.ts`
## Flat Task Source Rows
Gantt reads task data from flat grid rows. Required fields are `id`, `name`, `startDate`, and either `duration` or `endDate`.
The public source row names match task-table editing conventions:
- `duration` maps to internal `duration`.
- `percentDone` maps to internal `progressPercent`.
- `taskMode` maps to internal `manuallyScheduled` (`manual` or `auto`).
- `workflowStatus` stores the optional authored workflow key: `not-started`, `in-progress`, `done`, or `blocked`.
- `status` is calculated after scheduling from `% Done`, actual finish, scheduled dates, calendars, splits, and the project `statusDate`; it is not authored or editable.
When both `duration` and `endDate` are present, `duration` is the scheduling input and Gantt resolves `endDate` from `startDate + duration`. This matches task-table editing, where duration edits drive the finish date. Use `endDate` without `duration` when the finish date should be the authored input.
Projected grid rows remain display-ready. `GanttGridRow.status` is the calculated human-readable label, while `GanttGridRow.statusKey` and `statusIndicator` contain `complete`, `on-schedule`, `late`, or `future-task`. The authored workflow value is projected separately as `workflowStatus` and `workflowStatusKey`. Custom source fields are preserved by task id across recalculation and structural task changes unless the row is replaced without those fields.
Legacy source or JSON tasks that contain one of the old workflow values in `status` are migrated to `workflowStatus` when the new field is absent. New serialization emits only `workflowStatus`.
## Temporal Field Precision
Task temporal fields (`startDate`, `endDate`, `actualStartDate`, `actualFinishDate`) accept:
- ISO date: `YYYY-MM-DD`
- UTC ISO datetime: `YYYY-MM-DDTHH:mm:ss.sssZ`
Runtime normalization rules:
- Datetime input is preserved and used for intraday scheduling precision.
- Date-only input is normalized to `T00:00:00Z`.
For new integrations, prefer explicit UTC datetime values when you need hour/minute planning behavior.
---
# Dependencies
URL: https://pro.rv-grid.com/guides/gantt/concepts/dependencies/
Source: src/content/docs/guides/gantt/concepts/dependencies.mdx
Description: Dependency types, lag/lead, and validation behavior.
Surface: gantt
Dependencies constrain automatic successors using the relationship type and optional lag:
| Type | MSP token | Constraint |
|---|---|---|
| `finish-to-start` | `FS` | Successor starts after predecessor finishes. |
| `start-to-start` | `SS` | Successor starts no earlier than predecessor starts. |
| `finish-to-finish` | `FF` | Successor finishes no earlier than predecessor finishes. |
| `start-to-finish` | `SF` | Successor finishes no earlier than predecessor starts. |
## Using Lag and Lead
Use `lagDays` for lag or lead:
```typescript
grid.ganttDependencies = [
{
id: 'build-test',
predecessorTaskId: 'build',
successorTaskId: 'test',
type: 'finish-to-start',
lagDays: -2,
},
];
```
Positive `lagDays` delays the successor. Negative `lagDays` is lead time and allows overlap, matching Microsoft Project's negative lead syntax.
`lagCalendar` in scheduling config controls whether lag is interpreted as calendar days or working days.
## Inline Cell Syntax
Inline predecessor/successor cells accept MSP-style tokens:
```text
1FS
2SS+3
3FF-1d
task-4SF-2
```
Blank or invalid lag normalizes to `0`. The optional `d` suffix is accepted. Whitespace around tokens is ignored.
## Validation Behavior
Important dependency behavior:
- Circular dependency chains are invalid schedule data. RevoGrid reports them as `dependency-cycle` issues with the involved `taskIds` and `dependencyIds`.
- New dependency edits that would create a cycle are rejected before they change the Gantt store.
- Task row drops that would make dependency partners parent/child through the outline are rejected before the drop is committed.
- Imported projects that already contain a cycle still render, but automatic dependency propagation and drag/resize dependency clamping are not applied while the active dependency graph is cyclic.
- Missing predecessor/successor references produce issues.
- Dependency links are rendered in the overlay layer and can be created/edited interactively.
This matches the Microsoft Project scheduling model: a loop such as `Setup load balancer -> Configure firewall -> Setup load balancer` cannot produce a valid schedule. Remove or change one of the invalid links, then rerun scheduling.
When building diagnostics UI, inspect the scheduling result issues:
```typescript
const cycle = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle');
if (cycle) {
console.warn('Dependency cycle', {
taskIds: cycle.taskIds,
dependencyIds: cycle.dependencyIds,
});
}
```
## Examples
### Auto Successor Clamps to FS Dependency
An automatic successor cannot silently move before its predecessor allows:
```typescript
grid.source = [
{
id: 'design',
name: 'Design',
startDate: '2026-04-01',
duration: 3,
taskMode: 'auto',
},
{
id: 'build',
name: 'Build',
startDate: '2026-04-01',
duration: 2,
taskMode: 'auto',
},
];
grid.ganttDependencies = [{
id: 'design-build',
predecessorTaskId: 'design',
successorTaskId: 'build',
type: 'finish-to-start',
lagDays: 0,
}];
```
Expected result: `design` ends on `2026-04-03`, so `build` is scheduled no earlier than `2026-04-04`. If the user drags `build` left, the effective bar snaps back to the dependency-valid date.
### Manual Successor Keeps Date and Warns
Manual tasks preserve authored dates, but dependency violations remain visible:
```typescript
grid.source = [
{ id: 'design', startDate: '2026-04-01', duration: 3 },
{
id: 'review',
name: 'Manual Review',
startDate: '2026-04-02',
duration: 1,
taskMode: 'manual',
},
];
grid.ganttDependencies = [{
id: 'design-review',
predecessorTaskId: 'design',
successorTaskId: 'review',
type: 'finish-to-start',
lagDays: 0,
}];
```
Expected result: `review` stays on `2026-04-02` and receives a `manual-dependency-violation` warning because the dependency requires `2026-04-04`.
See the [Advanced dependencies demo](https://demo.rv-grid.com/gantt-dependencies-advanced/ts) for a runnable project containing FS, SS, FF, and SF links with lag and lead.
Implementation sources:
- `packages/gantt/src/gantt/engine/dependency-graph.ts`
- `packages/gantt/src/gantt/engine/scheduler-engine/validation.ts`
- `packages/gantt/src/gantt/features/dependencies/`
---
# Tasks, Summaries, And Milestones
URL: https://pro.rv-grid.com/guides/gantt/concepts/tasks/
Source: src/content/docs/guides/gantt/concepts/tasks.mdx
Description: Task types and hierarchy behavior.
Surface: gantt
Task hierarchy is represented by `parentId`.
- `type: 'task'`: normal scheduled item.
- `type: 'summary'`: rollup row; dates and progress come from children.
- `type: 'milestone'`: zero duration (`startDate === endDate`, `duration: 0`).
Scheduling-relevant source fields include `duration`, `durationUnit`, `durationIsElapsed`, `taskMode`, `constraintType`, `constraintDate`, `deadlineDate`, `effortMode`, `workHours`, `workUnit`, `percentDone`, `actualStartDate`, `actualFinishDate`, `remainingDuration`, `splitRanges`, `priority`, and `canLevel`. See [Duration Units and Conversion](/guides/gantt/scheduling/durations/) for mixed units and text input.
`duration`, `remainingDuration`, and `workHours` are stored in canonical hours. `durationUnit` is retained display/edit metadata for duration spans, while `workUnit` independently controls Work. Text quantities are converted to hours on input and converted from hours only when rendered.
Source row contract: `GanttTaskSourceRow` in `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`. Internal task entities use `manuallyScheduled`, `duration`, and `progressPercent` in `packages/gantt/src/gantt/core/domain.ts`.
---
# How Gantt Builds On RevoGrid
URL: https://pro.rv-grid.com/guides/gantt/customization/how-gantt-builds-on-revogrid/
Source: src/content/docs/guides/gantt/customization/how-gantt-builds-on-revogrid.mdx
Description: Division of responsibilities between RevoGrid and Gantt plugin layers.
Surface: gantt
RevoGrid provides row/column virtualization, editing, selection, scrolling, and base rendering. Gantt extends that with timeline projection, dependency overlays, scheduling, and resource planning.
Implementation layers:
- Grid/plugin orchestration: `packages/gantt/src/gantt/grid/gantt-plugin.ts`
- Scheduling engine: `packages/gantt/src/gantt/engine/`
- Projection and timeline model: `packages/gantt/src/gantt/projection/`
- Interaction features: `packages/gantt/src/gantt/features/`
Use this split when debugging behavior: if data rules changed, inspect `engine`; if visual output changed, inspect `projection/features`; if event routing changed, inspect `grid`.
---
# Gantt Timeline and Zoom
URL: https://pro.rv-grid.com/guides/gantt/customization/timeline-zoom/
Source: src/content/docs/guides/gantt/customization/timeline-zoom.mdx
Description: Configure timeline scale, zoom behavior, wheel interaction, and visual timeline overlays.
Surface: gantt
The Gantt timeline can be configured at two levels:
1. `zoomPreset` for fast setup with built-in levels.
2. `zoom` for full control over levels, wheel behavior, and anchor strategy.
Use `weekStartsOn` when week headers or week ticks should align to a specific
weekday. Gantt defaults to Sunday-start weeks.
---
## Quick Start with `zoomPreset`
Use a built-in preset when you only need a standard timeline scale:
```typescript
grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
};
```
Supported presets:
- `'minute-hour'` (15-minute ticks grouped by hour)
- `'hour-day'` (hour ticks grouped by day)
- `'day-week'`
- `'week-month'`
- `'month-quarter'`
- `'quarter-year'`
- `'year-quarter'`
- `'multi-year-quarter'`
By default, the interactive zoom ladder starts at `day-week` and excludes the intraday presets for performance. Use `zoomPreset: 'hour-day'`, `zoomPreset: 'minute-hour'`, or explicit `zoom.levels` when an intraday timeline is required.
## Week Start
Week-based timeline headers, week ticks, and padded week ranges use Sunday by default:
```typescript
grid.gantt = {
// ...required project fields
weekStartsOn: 0, // Sunday, default
};
```
Set `weekStartsOn: 1` when the Gantt should use Monday-start weeks:
```typescript
grid.gantt = {
// ...required project fields
weekStartsOn: 1, // Monday
zoomPreset: 'week-month',
};
```
`weekStartsOn` accepts `0` for Sunday or `1` for Monday. It affects timeline week boundaries only; task calendars still use their own `workingDays` configuration.
## Time Modes
Gantt timeline modes are defined by `tickUnit` + `headerRows` in each zoom level.
- `minute-hour`: 15-minute timeline resolution for intraday planning windows.
- `hour-day`: hourly timeline resolution grouped under day headers.
- `day-week`: daily planning view.
- `week-month`: weekly planning view.
- `month-quarter`: monthly planning view.
- `quarter-year`, `year-quarter`, `multi-year-quarter`: portfolio and long-range views.
Recommended usage pattern:
- Opt into `minute-hour` for short execution windows and handoffs.
- Opt into `hour-day` for same-day and next-day coordination.
- Use `day-week` or coarser for baseline and milestone planning.
Intraday modes (`minute-hour`, `hour-day`) control timeline granularity and interaction scale. Temporal fields still use the same names, but when you need intraday precision you should pass UTC datetime values (`...Z`) instead of date-only strings.
## Hour Mode
Use hour mode when task bars should move, resize, and create on hour boundaries
instead of whole-day boundaries. The important settings are:
- `timelinePrecision: 'hour'` makes timeline interactions use hour precision by default.
- `zoomPreset: 'hour-day'` starts the visible timeline with one column per hour.
- `snap.unit: 'hour'` forces task create, move, and resize snapping to hours even if the active zoom level changes later.
- `snap.workingTime` controls whether snapping can use working-time rules. Closed weekdays, holidays, and closed hours are skipped only when `scheduling.excludeHolidaysFromDuration` is also `true`.
```typescript
grid.gantt = {
// ...required project fields
timelinePrecision: 'hour',
zoomPreset: 'hour-day',
snap: {
unit: 'hour',
workingTime: true,
},
scheduling: {
excludeHolidaysFromDuration: true,
},
calendars: [
{
id: 'standard',
name: 'Standard',
workingDays: [1, 2, 3, 4, 5],
workingHours: [{ start: '09:00', end: '17:00' }],
hoursPerDay: 8,
},
],
tasks: [
{
id: 'task-1',
name: 'Same-day implementation',
startDate: '2026-04-06T09:00:00.000Z',
endDate: '2026-04-06T13:00:00.000Z',
},
],
};
```
`timelinePrecision` controls interaction precision. `zoomPreset` and `zoom.defaultLevelId`
control the initial visible scale. Keep them aligned for an hour-first experience,
or set `zoom.defaultLevelId: 'day-week'` if you want hour snapping while opening
on a coarser planning view.
When `snap.workingTime` is `true` or omitted and
`scheduling.excludeHolidaysFromDuration` is `true`, hour snapping uses the
primary project calendar:
- A drag before `09:00` snaps forward to `09:00`.
- A drag after `17:00` snaps forward to the next working opening.
- Weekend or holiday drags snap to the next or previous working opening depending on the interaction direction.
Set `snap.workingTime: false` when users should be able to place tasks in
non-working hours while still snapping to the start of each hour.
If `scheduling.excludeHolidaysFromDuration` is `false` or omitted, hour
snapping rounds to the start of the hour and does not move drags out of
non-working time.
## Full Zoom Configuration
Use `zoom` when you need custom levels, min/max bounds, locale, or wheel interaction rules.
```typescript
grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
zoom: {
enabled: true,
defaultLevelId: 'hour-day',
minLevelId: 'minute-hour',
maxLevelId: 'quarter-year',
locale: 'en-US',
zoomAnchorMode: 'pointer',
wheelZoomEnabled: true,
wheelZoomTrigger: 'ctrlKey',
wheelZoomMode: 'discrete',
invertWheelDirection: false,
},
};
```
If both `zoomPreset` and `zoom.defaultLevelId` are provided, the resolved zoom config uses the `zoom` values where specified.
## Custom Zoom Levels
You can define your own `zoom.levels` list from finest to coarsest.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
levels: [
{
id: 'minute-hour',
label: '15 Min / Hour',
tickUnit: 'minute',
tickCount: 15,
tickWidth: 44,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
{ id: 'minute', unit: 'minute', count: 15 },
],
},
{
id: 'hour-day',
label: 'Hour / Day',
tickUnit: 'hour',
tickWidth: 52,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
],
},
{
id: 'day-week',
label: 'Day / Week',
tickUnit: 'day',
tickWidth: 56,
headerRows: [
{ id: 'week', unit: 'week' },
{ id: 'day', unit: 'day' },
],
},
{
id: 'week-month',
label: 'Week / Month',
tickUnit: 'week',
tickWidth: 84,
headerRows: [
{ id: 'month', unit: 'month' },
{ id: 'week', unit: 'week' },
],
},
{
id: 'month-quarter',
label: 'Month / Quarter',
tickUnit: 'month',
tickWidth: 120,
headerRows: [
{ id: 'year', unit: 'year' },
{ id: 'month', unit: 'month' },
],
},
],
defaultLevelId: 'hour-day',
},
};
```
## Wheel Zoom Options
Control whether users can zoom with the mouse wheel and which modifier key is required.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
wheelZoomEnabled: true,
wheelZoomTrigger: 'metaKey', // macOS Cmd key
wheelZoomMode: 'smooth-discrete',
invertWheelDirection: false,
},
};
```
Available `wheelZoomTrigger` values:
- `'ctrlKey'`
- `'metaKey'`
- `'altKey'`
- `'shiftKey'`
- `'none'`
## Anchor Behavior During Zoom
`zoomAnchorMode` controls which point in the viewport stays stable when switching levels:
- `'pointer'`: keeps the date under the mouse pointer fixed.
- `'center'`: keeps the center date fixed.
- `'start'`: keeps the left edge date fixed.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
zoomAnchorMode: 'center',
},
};
```
## Timeline Overlays
Timeline visuals are configured in `visuals`.
```typescript
grid.gantt = {
// ...required project fields
visuals: {
projectLineDate: '2026-04-06',
timeRanges: [
{
id: 'sprint-1',
startDate: '2026-04-06',
endDate: '2026-04-17',
label: 'Sprint 1',
color: '#5B8DEF',
},
{
id: 'freeze',
startDate: '2026-05-18',
endDate: '2026-05-22',
label: 'Code Freeze',
color: '#F59E0B',
},
],
shadeNonWorkingTime: true,
},
};
```
`shadeNonWorkingTime` is accepted in both `zoom` and `visuals`. If both are set, zoom-level setting takes priority.
## Plugin Header Contributions
`GanttTimelineHeaderPlugin` owns the custom RevoGrid header template used by
the Gantt column. Other runtime plugins can discover it and append elements to
the resolved timeline header without creating another render root:
```typescript
import { GanttTimelineHeaderPlugin } from '@revolist/gantt';
// Inside another grid plugin, use its PluginProviders instance.
const timelineHeader = providers.plugins.getByClass(GanttTimelineHeaderPlugin);
const unregisterReleaseMarker = timelineHeader?.registerElement(
'release-marker',
(h) => h('div', {
class: { 'my-release-marker': true },
style: { left: '420px' },
}, 'Release'),
);
// Call after closure-backed contribution state changes.
timelineHeader?.refresh();
// Call from the contributing plugin's teardown.
unregisterReleaseMarker?.();
```
Contribution renderers receive RevoGrid's native `HyperFunc` and the
immutable, resolved header geometry. They render after the built-in header
cells, non-working ranges, interaction range, and flag caps, in registration
order. The contributing plugin owns its data and styling; timeline scale,
viewport slicing, and scroll state remain owned by Gantt.
Use `renderGanttTimelineHeader` directly only when building a custom RevoGrid
column template. Normal Gantt integrations should configure `zoom` and use the
discoverable header plugin only for cross-plugin UI contributions.
---
# Visual Configuration
URL: https://pro.rv-grid.com/guides/gantt/customization/visuals/
Source: src/content/docs/guides/gantt/customization/visuals.mdx
Description: Timeline overlays, labels, baseline, critical path, and marker hooks.
Surface: gantt
`gantt.visuals` controls overlays and rendering hooks:
- `percentDoneMode` selects the project-wide `number`, `bar` (default), or `circle` rendering for the managed `% Done` task-table column using the shared Pro progress renderers. An authored `{ prop: 'percentDone', mode: '...' }` column overrides it.
- `showBaseline`, `baselineId`
- `showCriticalPath`
- `showTaskLabels`
- `projectLineDate`, `showTodayLine`, `milestoneLines`
- `timeRanges`, `shadeNonWorkingTime`
- `taskMarkerHook`, `taskBarColorHook`, `taskBarContentHook`
- `taskTooltipFields`, `taskTooltipHook`
Use `taskTooltipFields` when you only need to choose which built-in fields appear in task-bar tooltips:
```ts
grid.gantt = {
...project,
visuals: {
taskTooltipFields: [
'name',
'startDate',
'endDate',
{ field: 'percentDone', label: 'Complete' },
'assignees',
],
},
};
```
## Task tooltip API
`taskTooltipFields` is an ordered array. Each entry can be a field id string or an object with a `field` and optional `label`:
```ts
type GanttTaskTooltipField =
| GanttTaskTooltipFieldId
| { field: GanttTaskTooltipFieldId; label?: string };
```
Object entries let you rename a built-in line. Set `label: ''` to render only the value without a `Label: ` prefix.
```ts
visuals: {
taskTooltipFields: [
{ field: 'name', label: '' },
{ field: 'percentDone', label: 'Done' },
],
}
```
Supported built-in fields:
| Field | Tooltip output |
|---|---|
| `summary` | Task name and completion percent, for example `Design - 50%`. |
| `name` | Task name. |
| `percentDone` | Completion percent. |
| `taskType` | Human-readable task type. |
| `status` | Human-readable task status. |
| `startDate` | Scheduled start date, formatted with `gantt.dateFormats.tooltip`. |
| `endDate` | Scheduled end date, formatted with `gantt.dateFormats.tooltip`. |
| `duration` | Formatted task duration in its retained or project-selected unit. |
| `taskMode` | `Auto Scheduled` or `Manually Scheduled`. |
| `schedulingWarnings` | Scheduling warnings joined into one line; skipped when empty. |
| `cost` | Formatted task cost; skipped when empty. |
| `assignees` | Comma-separated assignee names; skipped when empty. |
When `taskTooltipFields` is omitted, Gantt keeps the default tooltip: `summary`, `taskType`, `status`, `startDate`, `endDate`, `duration`, `taskMode`, `schedulingWarnings`, `cost`, and `assignees`. An empty array intentionally renders an empty tooltip.
`taskTooltipHook` has priority over `taskTooltipFields` and can replace the whole tooltip string:
```ts
visuals: {
taskTooltipFields: ['name'],
taskTooltipHook: ({ row }) => `${row.name}\n${row.status}`,
}
```
Return `null` or `undefined` from `taskTooltipHook` to fall back to `taskTooltipFields` or the default tooltip. Use the hook for custom row metadata that is not part of the built-in field list.
Type source: `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`.
Rendering sources: `features/timeline-background/*`, `grid/gantt-column-type.ts`.
---
# Gantt Examples
URL: https://pro.rv-grid.com/guides/gantt/examples/
Source: src/content/docs/guides/gantt/examples/index.mdx
Description: Search every runnable RevoGrid Gantt demo by framework and planning topic, then open its preview, source, guide, and API reference.
Surface: gantt
The example browser reads the shared demo catalog directly. Every registered Gantt demo and framework variant appears here automatically; there is no separate manual inventory to maintain.
---
# Application State And Persistence
URL: https://pro.rv-grid.com/guides/gantt/features/application-state/
Source: src/content/docs/guides/gantt/features/application-state.mdx
Description: Keep Gantt project collections synchronized with framework state, APIs, databases, and real-time application updates.
Surface: gantt
Your application owns persisted project data. Gantt owns scheduling, validation, projection, and supported local mutations while the grid is mounted.
## State Boundary
Keep these collections together as one logical project snapshot:
- `source`: authored task rows.
- `ganttDependencies`.
- `ganttCalendars`.
- `ganttResources`.
- `ganttAssignments`.
- `ganttBaselines`.
- `gantt`: project metadata and behavior configuration.
When a server response or collaborative update arrives, replace affected arrays and assign a new `gantt` object when configuration changed. Do not mutate an existing array in place and expect framework or custom-element change detection to observe it.
```ts
function applyProject(snapshot: ProjectSnapshot) {
grid.gantt = { ...snapshot };
grid.ganttCalendars = [...snapshot.calendars];
grid.ganttDependencies = [...snapshot.dependencies];
grid.ganttResources = [...snapshot.resources];
grid.ganttAssignments = [...snapshot.assignments];
grid.ganttBaselines = [...snapshot.baselines];
grid.source = snapshot.tasks.map(toSourceRow);
}
```
## Save Flow
1. Listen for the Gantt before-change events when the application needs authorization or validation.
2. Allow Gantt to commit supported local mutations.
3. Read the current project through the `GanttPlugin` instance or the application state updated by your integration.
4. Persist a complete snapshot or an application-defined patch.
5. Replace the public collections with the canonical server result when the server normalizes data.
Use [JSON project helpers](/guides/gantt/features/json-project-import-export/) for snapshots and [integration recipes](/guides/gantt/features/integrations-and-frameworks/) for REST, GraphQL, PostgreSQL, and Supabase-style persistence.
## Concurrent Updates
- Use stable entity IDs across every collection.
- Version project writes in your backend and reject stale updates.
- Re-run scheduling after dependency, calendar, task, resource, or assignment updates.
- Keep optimistic rollback data outside the rendered DOM.
- Use `gantt.readOnly` while a project is locked or awaiting approval.
See [Events And Methods](/guides/gantt/reference/events-methods/) for cancelable events and plugin access.
---
# Gantt Context Menu
URL: https://pro.rv-grid.com/guides/gantt/features/context-menu/
Source: src/content/docs/guides/gantt/features/context-menu.mdx
Description: Use the built-in Gantt row, column, and timeline context menus and extend them with RevoGrid Pro menu items.
Surface: gantt
Gantt contributes task and timeline actions through the RevoGrid Pro `DataGridContextMenuPlugin`; it does not replace your `rowContextMenu`, `columnContextMenu`, or `dataGridContextMenu` configuration. Gantt-compatible shared grid commands are composed first, then `dataGridContextMenu.getItems` and application items can extend the result.
The integration is enabled by default for every `GanttPlugin` instance. Set `gantt.contextMenu` to `false` to opt out of generated Gantt row, column, and timeline menu items while leaving application-provided RevoGrid context menu configuration untouched:
```ts
grid.gantt = {
...project,
contextMenu: false,
};
```
You can also disable one generated surface at a time:
```ts
grid.gantt = {
...project,
contextMenu: {
row: false,
column: true,
},
};
```
## Row Menu
Right-click a task-table row or task bar to open task actions:
- `Add`
- `Convert to milestone`
- `Indent`
- `Outdent`
- `Delete`
The default actions dispatch Gantt events:
```ts
grid.addEventListener('gantt-task-create', (event) => {
const { parentId, insertAfterTaskId } = event.detail ?? {};
});
grid.addEventListener('gantt-task-indent', (event) => {
indentTask(event.detail?.taskId);
});
grid.addEventListener('gantt-task-outdent', (event) => {
outdentTask(event.detail?.taskId);
});
grid.addEventListener('gantt-task-delete', (event) => {
deleteTask(event.detail?.taskId);
});
```
`Add` uses the focused row or clicked task bar as the insertion anchor. When the anchor has descendants, Gantt inserts the new sibling after the full descendant block so the task tree stays valid. Context-menu creation names the row `New task 1`, `New task 2`, and so on, and does not open the task editor.
The generated row menu is suppressed on the pinned add-task draft row because that row is owned by the internal `GanttAddTaskRowPlugin` and is not a task entity. If you disable Gantt context-menu integration and provide your own `rowContextMenu`, use `isGanttAddTaskRow()` or the draft cell marker to skip that row. See [Add Task Row](/guides/gantt/interaction/add-task-row/).
### Confirming Delete
The built-in **Delete** item dispatches the normal Gantt delete command, then Gantt emits the cancelable `gantt-before-task-change` event with `detail.action === 'delete'` immediately before it mutates any task. Call `preventDefault()` to keep the task (and its descendants) unchanged.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (event.detail.action !== 'delete') return;
const name = event.detail.previousValues.name ?? 'this task';
if (!window.confirm(`Delete ${name}?`)) {
event.preventDefault();
}
});
```
This protects deletion initiated from the default context-menu item and direct Gantt delete methods, so it is the preferred place for permission checks and a synchronous browser confirmation. It is called once per affected task; applications that allow bulk deletion should make their confirmation copy clear that descendants may also be removed.
`gantt-before-task-change` is synchronous. For an asynchronous application modal, prevent the original deletion, keep the selected task ids in application state, and call `gantt.deleteTasks(taskIds)` only after the user confirms. Guard the follow-up call so the same listener does not reopen the dialog, and recheck permissions before performing it.
## Column Menu
Right-click main grid headers to open column actions:
- `Sort ascending`
- `Sort descending`
- `Filter`
- `Hide column`
- `Columns`
Visibility follows the active column:
- Sort actions appear only when the target column has `sortable: true`.
- Filter appears only when the target column has `filter` configured.
- Hide column is available for a leaf task-table column; Columns opens the standard column-management dialog.
- If contextual filtering leaves no visible items, the menu is not opened.
Sort, filtering, hiding, and column management reuse the shared RevoGrid data-grid context-menu commands. Gantt removes incompatible generic commands such as row mutation, formatting, pinning, and schema operations.
## Timeline Menu
Right-click the pinned Gantt timeline/canvas header to open timeline actions:
- `Zoom`
- `Date range`
- `Show today`
- `Show flags` when milestone lines are configured
Zoom controls are hidden when the current zoom level cannot move further in either direction. Date range changes the visible timeline range; the visual toggles remain open after changing their checked state.
## Read-Only And Locked State
The row menu respects packaged Gantt protection state:
- `gantt.readOnly: true` hides mutating row actions.
- Selected locked tasks hide hierarchy actions.
- Mutation services still guard direct calls, so blocked task operations return `readonly-task`.
## Extending The Menu
Use the normal RevoGrid Pro menu config surfaces. Gantt keeps your custom items and contributes its generated items without mutating those configurations.
```ts
grid.rowContextMenu = {
items: [
{
name: 'Open task details',
action: (_event, focused, _range, _close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openTaskDetails(task?.id);
},
},
],
};
```
## Customization
Gantt context menu customization has two layers:
- `gantt.contextMenu` controls the generated Gantt row, column, and timeline actions.
- `rowContextMenu` and `columnContextMenu` hold application menu config that should remain in the grid even when generated Gantt actions are disabled.
Use `gantt.contextMenu = false` when the application should fully own the menu:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Open planning panel',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openPlanningPanel(task?.id);
close();
},
},
],
};
grid.gantt = {
...project,
contextMenu: false,
};
```
Use partial opt-out when you want custom row actions but still want generated column and timeline actions:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Assign owner',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openOwnerPicker(task?.id);
close();
},
},
],
};
grid.gantt = {
...project,
contextMenu: {
row: false,
column: true,
},
};
```
Keep `gantt.contextMenu` enabled when you want to append app actions after the default Gantt actions:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Show audit log',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
showTaskAuditLog(task?.id);
close();
},
},
],
};
grid.gantt = project;
```
You can also use a `resolve` function on the base RevoGrid menu config. Gantt preserves the resolver and the context menu plugin applies it to the composed menu:
```ts
grid.rowContextMenu = {
items: [
{ name: 'Open task details', action: openTaskDetailsFromMenu },
],
resolve(context) {
return context.target === 'row' ? undefined : null;
},
};
```
The packaged task editor item is controlled separately:
```ts
grid.ganttTaskEditorDialog = {
contextMenu: false,
};
```
Set `ganttTaskEditorDialog.contextMenu` to `true` when `gantt.contextMenu` is disabled but the application still wants the default **Edit...** item. Leave it unset to follow `gantt.contextMenu`.
For packaged extension items, use the built-in Gantt context-menu helpers:
```ts
import { createGanttContextMenuExtensionItems } from '@revolist/gantt';
const customItems = createGanttContextMenuExtensionItems({
duplicate: {
copyName: (task) => `${task.name ?? 'Task'} copy`,
},
assignResource: {
onAssignResource: (context) => {
if (context.taskId) {
openResourcePicker(context.taskId);
}
},
},
delete: false,
export: { name: 'Export task rows' },
});
grid.rowContextMenu = {
...grid.rowContextMenu,
items: [...(grid.rowContextMenu?.items ?? []), ...customItems],
};
```
## Legacy Context Menu
`rowContextMenu` and `columnContextMenu` remain application-owned low-level menu configuration. Use `dataGridContextMenu.getItems` when you need to transform the Gantt-compatible shared grid commands: registered Gantt contributions run first, then this application resolver receives the filtered items.
Gantt registers and disposes its own contribution with the plugin lifecycle. It does not rewrite host menu properties, so application-provided items are not duplicated during Gantt configuration updates.
The packaged task editor follows `gantt.contextMenu` by default for its **Edit...** row-menu item. Set `ganttTaskEditorDialog.contextMenu` to `false` to disable only the editor item, or `true` to keep the editor item when generated Gantt menus are disabled.
---
# Editing, Validation, Diagnostics
URL: https://pro.rv-grid.com/guides/gantt/features/editing-validation-diagnostics/
Source: src/content/docs/guides/gantt/features/editing-validation-diagnostics.mdx
Description: Wire Gantt permission checks, editor forms, popovers, validation, and diagnostics panels.
Surface: gantt
These APIs are presentation-free. They make decisions and shape data, while your application owns forms, popovers, panels, toasts, and persistence.
```ts
import {
GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT,
GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT,
GANTT_BEFORE_TASK_CHANGE_EVENT,
TASK_EDITOR_FIELD_SCHEMA,
buildDependencyValidationSummary,
buildResourceOverallocationSummary,
createApprovalGateValidator,
createForbiddenDateRangeValidator,
createGanttBeforeTaskChangeValidationHandler,
createGanttRoleEditabilityHelpers,
createLockedPhaseValidator,
createRoleGuardValidator,
createTaskDetailPopoverModel,
createTaskEditorFormValues,
normalizeTaskEditorSubmit,
} from '@revolist/gantt';
```
## Role-Based Editability
Before-change events are cancelable. Use role helpers to decide whether a task, dependency, or assignment mutation should proceed. Call `event.preventDefault()` to stop the packaged mutation before the Gantt store changes; timeline previews are cleared and the next render keeps the previous task, dependency, or assignment data.
```ts
const policy = {
roles: {
viewer: [],
planner: ['task:*', 'dependency:create', 'dependency:delete'],
staffing: ['assignment:edit'],
admin: ['*'],
},
fallback: 'deny',
} as const;
const editability = createGanttRoleEditabilityHelpers(policy);
const getRoleContext = () => ({ roles: currentUser.roles });
grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
if (!editability.canEditTask(event.detail, getRoleContext())) {
event.preventDefault();
}
});
grid.addEventListener(GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT, (event) => {
if (!editability.canEditDependency(event.detail, getRoleContext())) {
event.preventDefault();
}
});
grid.addEventListener(GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT, (event) => {
if (!editability.canEditAssignment(event.detail, getRoleContext())) {
event.preventDefault();
}
});
```
Task before-change actions cover `move`, `resize`, `create`, `edit`, `progress`, `split`, `indent`, `outdent`, and `delete`. Dependency before-change actions cover `create`, `delete`, `edit`, and `replace`; `replace` is emitted by predecessor/successor inline field edits and includes `previousDependencies` plus proposed `dependencies` because one cell edit can add, update, and remove multiple links. Assignment edits emit proposed `assignments` and `previousAssignments`.
For task edits, `event.detail.sourcePatch` contains the flat source-row fields that the packaged Gantt store will write. Use it when persisting custom row fields or when your backend uses public source names such as `duration`, `percentDone`, and `taskMode`.
```ts
grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
const { sourceIndex, sourcePatch, previousSourceValues } = event.detail;
if (sourceIndex === undefined || !sourcePatch) {
return;
}
queueTaskRowPersistence({
rowIndex: sourceIndex,
patch: sourcePatch,
previousValues: previousSourceValues ?? {},
});
});
```
## Custom Task Editor Forms
Use the field schema and initial value builder to render a framework form, then normalize submitted values into a `TaskUpdate` patch.
For a complete standalone UI example, see [Task Editing](../../interaction/task-editing/).
```ts
const fields = TASK_EDITOR_FIELD_SCHEMA.filter((field) =>
['name', 'startDate', 'endDate', 'progressPercent', 'resourceLabels'].includes(field.id),
);
const values = createTaskEditorFormValues(task, resources, assignments);
renderTaskEditor({
fields,
values,
onSubmit(nextValues) {
const result = normalizeTaskEditorSubmit(task, nextValues);
if (!result.ok) {
renderErrors(result.errors);
return;
}
updateTask(task.id, result.patch);
},
});
```
## Task Detail Popovers
`createTaskDetailPopoverModel()` returns sections, fields, warnings, and resource labels. Your app handles hover/focus, positioning, and rendering.
```ts
const model = createTaskDetailPopoverModel({
row,
task,
resources,
assignments,
});
renderPopover({
title: model.title,
subtitle: model.subtitle,
sections: model.sections,
resources: model.resourceAssignments.map((assignment) => assignment.displayValue),
});
```
## Dependency Diagnostics
Use dependency summaries to power badges, side panels, or task focus commands.
```ts
const summary = buildDependencyValidationSummary({
conflicts: schedulerResult.conflicts,
issues: schedulerResult.issues,
rows: projectedRows,
});
renderDiagnosticsPanel({
count: summary.totalCount,
groups: summary.groups,
});
```
## Resource Over-Allocation Diagnostics
Use resource summaries when staffing dashboards need to show who is overloaded and when.
```ts
const overloads = buildResourceOverallocationSummary({
rows: resourcePlanningRows,
loadSummaries: resourceLoadSummaries,
conflicts: schedulerResult.conflicts,
});
renderResourceHealth({
count: overloads.totalCount,
peakOverloadUnits: overloads.peakOverloadUnits,
groups: overloads.groups,
});
```
## Custom Validation Recipes
Compose validators and attach the generated handler to `gantt-before-task-change`.
```ts
const validators = [
createRoleGuardValidator({ policy }),
createForbiddenDateRangeValidator({
ranges: [{ startDate: '2026-05-20', endDate: '2026-05-27', label: 'release freeze' }],
}),
createLockedPhaseValidator({
lockedPhaseIds: ['closed'],
getPhaseId: (detail) => detail.taskId ? phaseByTaskId[detail.taskId] : null,
}),
createApprovalGateValidator({
actions: ['move', 'resize', 'progress'],
requiresApproval: (detail) => detail.changes.progressPercent === 100,
}),
];
grid.addEventListener(
GANTT_BEFORE_TASK_CHANGE_EVENT,
createGanttBeforeTaskChangeValidationHandler({
context: () => validationContext,
validators,
onReject: ({ decision }) => showValidationToast(decision.message ?? 'Change rejected'),
}),
);
```
---
# Grid, Export, Rendering
URL: https://pro.rv-grid.com/guides/gantt/features/grid-export-rendering/
Source: src/content/docs/guides/gantt/features/grid-export-rendering.mdx
Description: Use Gantt row export mapping, column presets, status indicators, and context-menu extension recipes.
Surface: gantt
These helpers package common grid-side Gantt work without coupling your application to internal timeline layout data.
```ts
import {
createGanttColumnPreset,
createGanttContextMenuExtensionItems,
createGanttExcelExportRecipe,
createGanttPdfExcelReportingRecipe,
createGanttTaskStatusIndicatorRecipe,
createTaskStatusIndicators,
mapGanttRowsToExcelExport,
} from '@revolist/gantt';
```
## Gantt Excel Export Mapping
Use `mapGanttRowsToExcelExport()` when exporting projected task rows. The mapper uses an allowlist of user-facing fields and skips renderer-only timeline metadata.
```ts
const rows = mapGanttRowsToExcelExport(taskRows, project, {
includeResourceRows: false,
});
downloadWorkbook({
sheetName: project.name,
rows,
});
```
For shells that prefer a recipe object:
```ts
const recipe = createGanttExcelExportRecipe({
rows: taskRows,
project,
});
downloadWorkbook({
fields: recipe.fieldNames,
rows: recipe.rows,
});
```
## PDF And Excel Reporting
Use the combined reporting recipe when your app has separate toolbar buttons for stakeholder PDF reports and spreadsheet handoff. PDF uses the browser print dialog, so users can choose **Save as PDF**. Excel dispatches the Pro `export-excel` event, so include `ExportExcelPlugin` with the grid plugins.
```ts
import {
GanttPlugin,
createGanttPdfExcelReportingRecipe,
} from '@revolist/gantt';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
const host = document.querySelector('#gantt-report');
if (!grid || !host) {
throw new Error('Gantt report target was not found');
}
grid.plugins = [GanttPlugin, ExportExcelPlugin];
grid.gantt = project;
const reportRange = {
startDate: '2026-05-01',
endDate: '2026-05-31',
};
const reporting = createGanttPdfExcelReportingRecipe({
grid,
target: host,
document,
window,
excel: {
workbookName: `${project.name}.xlsx`,
},
});
pdfButton.addEventListener('click', () => {
const result = reporting.exportPdf(project, reportRange);
if (!result.ok) {
showExportError(result.reason);
}
});
excelButton.addEventListener('click', () => {
reporting.exportExcel({
sheetName: 'Tasks',
});
});
```
For a single "export report" action, call both commands together:
```ts
const result = reporting.exportReport(project, {
range: { label: 'Executive status report' },
excel: { sheetName: 'Task register' },
});
if (!result.pdf.ok) {
showExportError(result.pdf.reason);
}
```
## Column Presets
Column presets group the default Gantt task-table columns into common planning views.
```ts
grid.columns = createGanttColumnPreset('schedule');
grid.source = [...taskRows];
```
Available presets include `core`, `schedule`, `dependencies`, `resources`, `baseline`, `cost`, `progress`, and `all`.
## Read-Only And Locked Indicators
Use indicator helpers to produce labels and class names for custom cell renderers.
```ts
const status = createTaskStatusIndicators({
taskId: row.id,
projectReadOnly,
taskLocked: lockedTaskIds.has(row.id),
});
renderIndicatorCell({
className: status.classNames.join(' '),
title: status.label,
indicators: status.indicators,
});
```
For multiple rows:
```ts
const indicatorRows = createGanttTaskStatusIndicatorRecipe({
rows: taskRows,
projectReadOnly,
lockedTaskIds,
});
```
## Context Menu Extensions
Gantt installs separate RevoGrid Pro row and column context menus. Row menus are task-oriented (`Add`, `Convert to milestone`, `Indent`, `Outdent`, `Delete`). Main grid column menus expose search, sort, and filter actions. The pinned Gantt canvas column menu exposes timeline zoom controls.
Use the built-in context-menu helpers with `rowContextMenu` to add duplicate, assign-resource, export, or custom task actions.
```ts
const contextMenuItems = createGanttContextMenuExtensionItems({
duplicate: {
copyName: (task) => `${task.name ?? 'Task'} copy`,
},
delete: {
confirm: (context) => window.confirm(`Delete ${context.task?.name ?? 'task'}?`),
onDeleteTask: (context) => {
if (context.taskId) {
deleteTask(context.taskId);
}
},
},
assignResource: {
onAssignResource: (context) => {
if (context.taskId) {
openResourcePicker(context.taskId);
}
},
},
export: {
exportOptions: (context) => ({
workbookName: 'gantt.xlsx',
sheetName: context.task?.name ?? 'Gantt',
}),
},
});
grid.rowContextMenu = {
...grid.rowContextMenu,
items: [...(grid.rowContextMenu?.items ?? []), ...contextMenuItems],
};
```
Disable individual items with `false`.
```ts
const exportOnly = createGanttContextMenuExtensionItems({
duplicate: false,
delete: false,
assignResource: false,
export: { name: 'Export visible Gantt tasks' },
});
```
---
# Integrations And Frameworks
URL: https://pro.rv-grid.com/guides/gantt/features/integrations-and-frameworks/
Source: src/content/docs/guides/gantt/features/integrations-and-frameworks.mdx
Description: Persist, import, export, print, and embed Gantt projects with framework-neutral recipes.
Surface: gantt
These recipes keep transport and framework ownership in your application. Inject `fetch`, state, database clients, DOM targets, and file readers/writers from your host app.
```ts
import {
ProjectSnapshotGraphqlAdapter,
ProjectSnapshotRestAdapter,
ProjectSnapshotSupabaseAdapter,
buildCreateProjectSnapshotTableSql,
buildLoadProjectSnapshotSql,
buildSaveProjectSnapshotSql,
cloneProjectSnapshot,
createJsonSnapshotRecipe,
createPrintPdfExportRecipe,
exportProjectSnapshot,
parseProjectSnapshotJson,
printGanttContainer,
} from '@revolist/gantt';
```
## JSON Snapshot Helpers
Use JSON helpers for local import/export, file handoff, tests, undo buffers, or backend payloads.
```ts
const draft = cloneProjectSnapshot(project);
const json = exportProjectSnapshot(draft, { space: 2 });
const parsed = parseProjectSnapshotJson(json);
if (parsed.ok) {
applyProject(parsed.project);
}
```
For file picker flows:
```ts
const recipe = createJsonSnapshotRecipe({
readText: () => fileInputText,
writeText: (_fileName, contents) => saveTextFile(contents),
});
await recipe.save(project, 2);
const imported = await recipe.load();
```
## REST Persistence
`ProjectSnapshotRestAdapter` loads and saves serialized project snapshots through an injected `fetchImpl`.
```ts
const adapter = new ProjectSnapshotRestAdapter({
url: '/api/gantt/project/demo',
headers: { authorization: 'Bearer token' },
fetchImpl,
space: 2,
});
const project = await adapter.load();
await adapter.save(project);
```
## GraphQL Persistence
Use the GraphQL adapter when the backend exposes project snapshots through operations.
```ts
const adapter = new ProjectSnapshotGraphqlAdapter({
url: '/graphql',
fetchImpl,
loadQuery: 'query LoadProject($id: ID!) { projectSnapshot(id: $id) }',
saveMutation: 'mutation SaveProject($id: ID!, $snapshot: String!) { saveProjectSnapshot(id: $id, snapshot: $snapshot) }',
});
const project = await adapter.load({ variables: { id: 'demo' } });
await adapter.save(project, { variables: { id: 'demo' } });
```
## Supabase And PostgreSQL
Use SQL builders with any PostgreSQL executor, or the Supabase-like adapter with a compatible client shape.
```ts
await executor.query(buildCreateProjectSnapshotTableSql());
await executor.query(buildSaveProjectSnapshotSql(project));
const result = await executor.query(
buildLoadProjectSnapshotSql(project.id),
);
```
```ts
const adapter = new ProjectSnapshotSupabaseAdapter(supabaseLikeClient);
await adapter.save(project);
const restored = await adapter.load(project.id);
```
## Print And Save As PDF
The print recipe prepares a Gantt container for browser printing. Users can choose Print or Save as PDF in the native dialog.
```ts
const result = printGanttContainer(host, {
document,
window,
metadata: {
title: project.name,
subtitle: `${project.tasks.length} tasks`,
range: { startDate: '2026-05-01', endDate: '2026-05-31' },
},
});
```
For a reusable command:
```ts
const printRecipe = createPrintPdfExportRecipe({
target: host,
document,
window,
});
printRecipe.exportProject(project, { label: 'May reporting window' });
```
## Framework Examples
Use the complete public-package workflow for your primary wrapper:
- [Vanilla TypeScript quick start](/guides/gantt/introduction/vanilla-typescript/)
- [React quick start](/guides/gantt/introduction/react/)
- [Vue 3 quick start](/guides/gantt/introduction/vue/)
- [Angular quick start](/guides/gantt/introduction/angular/)
Svelte remains a secondary integration recipe. Use the custom-element approach from the Vanilla guide until its packaged wrapper and support contract match the four primary framework paths.
The [Resource Planning demo](https://demo.rv-grid.com/gantt-resource-planning/ts) shows a host-owned resource filter UI. Filtering updates `grid.gantt.resourceFilterIds` without reading rendered cells.
Resource filtering updates `grid.gantt.resourceFilterIds`:
```ts
grid.gantt = {
...project,
resourceFilterIds: selectedResourceIds.length === allResourceIds.length
? undefined
: selectedResourceIds,
};
```
---
# JSON Project Import And Export
URL: https://pro.rv-grid.com/guides/gantt/features/json-project-import-export/
Source: src/content/docs/guides/gantt/features/json-project-import-export.mdx
Description: Serialize, clone, parse, and lightly validate complete Gantt project snapshots.
Surface: gantt
JSON project import/export provides a framework-neutral way to move complete Gantt project snapshots between files, APIs, tests, undo buffers, and server-side workflows. The helpers operate on `ProjectSnapshot` data only. They do not depend on the grid, DOM, or framework wrappers.
Import the helpers from `@revolist/gantt`:
```ts
import {
cloneProjectSnapshot,
createJsonSnapshotRecipe,
exportProjectSnapshot,
parseProjectSnapshotJson,
} from '@revolist/gantt';
```
## Export A Snapshot
Use `exportProjectSnapshot()` to serialize the current project snapshot.
```ts
const json = exportProjectSnapshot(project, { space: 2 });
```
The optional `space` value is passed to `JSON.stringify`, so you can use it for readable file exports.
## Import A Snapshot
Use `parseProjectSnapshotJson()` to parse JSON into a typed result instead of throwing for common invalid payloads.
```ts
const parsed = parseProjectSnapshotJson(json);
if (!parsed.ok) {
showImportError(parsed.error);
return;
}
applyProject(parsed.project);
```
The parser checks the top-level project shape:
- Required string fields: `id`, `name`, `version`, `currency`, `timeZone`, `primaryCalendarId`, and `updatedAt`.
- Required array fields: `tasks`, `dependencies`, `resources`, `assignments`, `calendars`, and `baselines`.
It intentionally performs lightweight validation. Run scheduler validation or your application-specific validation after import when you need deeper checks for dates, task references, dependency cycles, resource assignments, or business rules.
## Clone Before Editing
Use `cloneProjectSnapshot()` when you need a JSON-safe deep copy before staging edits.
```ts
const draft = cloneProjectSnapshot(project);
draft.name = 'Scenario copy';
```
The clone is detached from nested task, dependency, resource, assignment, calendar, and baseline arrays.
## File Picker Recipe
For host applications that own file pickers and save dialogs, use `createJsonSnapshotRecipe()` with injected text readers and writers.
```ts
const recipe = createJsonSnapshotRecipe({
readText: () => selectedFile.text(),
writeText: (fileName, contents) => downloadTextFile(fileName, contents),
fileName: (project) => `${project.id}-snapshot.json`,
});
const imported = await recipe.load();
await recipe.save(project, 2);
```
The recipe throws an `Error` when load/save hooks are missing or the imported JSON fails parsing. Use the lower-level parser directly when you prefer result objects over exceptions.
## What The JSON Contains
The exported JSON is the project snapshot, not rendered grid state. It includes project metadata and entity arrays:
- Tasks
- Dependencies
- Resources
- Assignments
- Calendars
- Baselines
It does not include projected Gantt row fields, rendered timeline geometry, current scroll position, expanded row state, active selection, filters, or toolbar state.
## Persistence Adapters
The REST, GraphQL, Supabase, and PostgreSQL examples build on the same JSON snapshot helpers. Use those adapters when the snapshot should be loaded from or saved to an application backend.
See [Integrations And Frameworks](../integrations-and-frameworks/) for adapter examples.
---
# Locked Tasks
URL: https://pro.rv-grid.com/guides/gantt/features/locked-tasks/
Source: src/content/docs/guides/gantt/features/locked-tasks.mdx
Description: Use task-level locking to block packaged Gantt task mutations for protected tasks.
Surface: gantt
Locked tasks let an application protect individual tasks after approval, baseline capture, phase closure, or any other workflow decision. A locked task remains visible in the task table and timeline, but packaged Gantt task mutations are rejected for that task.
Set `locked: true` on the flat task source row:
```ts
const tasks = [
{
id: 'design-signoff',
parentId: null,
name: 'Design sign-off',
type: 'milestone',
workflowStatus: 'done',
startDate: '2026-04-15',
endDate: '2026-04-15',
duration: 1,
percentDone: 100,
calendarId: 'standard',
tags: ['approved'],
locked: true,
},
];
```
## What It Blocks
`locked: true` blocks packaged task mutations for the locked task:
- Timeline task movement and resizing.
- Split creation, movement, and removal.
- Progress updates.
- Inline task-field edits routed through the Gantt task mutation service.
- Indent and outdent actions.
- Child task creation under a locked parent.
- Delete when the selected task subtree contains a locked task.
Blocked task mutations return `readonly-task` and leave task data unchanged.
## What Still Works
Locking a task does not remove it from scheduling, rendering, or inspection flows:
- The task still renders in the table and timeline.
- Scheduler recalculation can still read the task and include it in dependencies, critical path, baselines, and diagnostics.
- Search, filter, sort, expand/collapse, scroll, and zoom remain available.
- Non-locked tasks continue to edit normally.
## Boundaries
The source-row `locked` field is task-level protection, not a complete permission or checkout system.
Dependency changes are not globally disabled by locking a task. Assignment/resource edits are also handled by their own mutation service. Use `gantt.readOnly` when the whole project should reject packaged task, dependency, and assignment mutations.
For application-specific policy, pair locked tasks with cancelable Gantt events or validation recipes.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (lockedTaskIds.has(event.detail.taskId)) {
event.preventDefault();
}
});
```
## Visual Indicators
Use the status indicator helpers when you want to show a lock label, class name, icon, or tooltip in a custom renderer.
```ts
const status = createTaskStatusIndicators({
taskId: row.id,
taskLocked: row.locked === true,
});
renderIndicatorCell({
className: status.classNames.join(' '),
title: status.label,
indicators: status.indicators,
});
```
The helper returns metadata only, so the host application can choose the exact icon and placement.
## Context Menu
The built-in Gantt row context menu hides hierarchy actions when the selected task is locked. Mutating row actions are also guarded by the task mutation service, so direct calls still return `readonly-task`.
---
# Microsoft Project Import And Export
URL: https://pro.rv-grid.com/guides/gantt/features/microsoft-project/
Source: src/content/docs/guides/gantt/features/microsoft-project.mdx
Description: Import Microsoft Project XML or native MPP14 schedules, and export Microsoft Project XML with the optional Gantt converter.
Surface: gantt
Microsoft Project conversion is shipped as an optional package so applications that do not exchange Project files do not include the XML or native MPP parser.
```sh
pnpm add @revolist/revogrid-gantt-msp
```
## Install the plugin
Register `GanttMspImportPlugin` beside `GanttPlugin` from `@revolist/gantt`:
```ts
import { GanttPlugin } from '@revolist/gantt';
import { GanttMspImportPlugin } from '@revolist/revogrid-gantt-msp';
grid.plugins = [GanttPlugin, GanttMspImportPlugin];
```
Get the installed converter and import a file selected by the user:
```ts
const plugins = await grid.getPlugins();
const converter = plugins.find(
(plugin): plugin is GanttMspImportPlugin =>
plugin instanceof GanttMspImportPlugin,
);
const result = await converter?.importFile(file, {
projectId: 'customer-rollout',
currency: 'EUR',
timeZone: 'Europe/Lisbon',
});
console.table(result?.warnings);
```
The plugin applies imported tasks and project collections through the same reactive properties used by `GanttPlugin`. It returns the complete `ProjectSnapshot` as well, allowing the application to persist or inspect the imported data.
## Convert without a grid
The framework-neutral conversion functions can be used in upload handlers, persistence services, tests, and export actions:
```ts
import {
exportMicrosoftProjectXml,
importMicrosoftProjectMpp,
importMicrosoftProjectXml,
} from '@revolist/revogrid-gantt-msp';
const { project, warnings } = importMicrosoftProjectXml(xml, {
projectId: 'customer-rollout',
});
const exported = exportMicrosoftProjectXml(project);
downloadXml(exported.xml);
const native = await importMicrosoftProjectMpp(await mppFile.arrayBuffer(), {
projectId: 'native-customer-rollout',
});
```
## File support
| Format | Import | Export | Notes |
| --- | --- | --- | --- |
| Microsoft Project XML (`.xml`) | Yes | Yes | Full initial XML mapping. |
| MPP14 (`.mpp`) | Yes | No | Native format produced by Project 2010 and later. |
| MPP8, MPP9, or MPP12 | No | No | Returns `unsupported-mpp-version`. |
| Encrypted MPP | No | No | Returns `encrypted-mpp-not-supported`. |
`importFile()` detects the OLE compound-document signature rather than trusting the extension. Browser `File` objects already provide the required `arrayBuffer()` method. A custom `.mpp` file-like object without binary access returns `binary-reader-unavailable`, and invalid native content returns `invalid-mpp`. Custom XML-only file-like objects can continue implementing only `name` and `text()`.
## Supported XML data
The adapter maps:
- task hierarchy, names, dates, durations, and notes;
- tasks, summary tasks, and milestones;
- completion progress;
- finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies;
- the first task baseline exposed by the MSP XML converter.
The conversion result includes warnings when a safe default is introduced or a field cannot be represented. The initial adapter creates a standard Monday-Friday calendar because the underlying converter does not expose Microsoft Project calendar definitions.
## Native MPP14 data
The native importer maps task names, hierarchy, dates, durations, summary tasks, milestones, finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies, plus first-baseline dates. It generates WBS codes and a standard Monday-Friday calendar.
The first native release intentionally defaults progress and dependency lag to zero, and omits notes, resources, assignments, Microsoft Project calendars, and additional baselines. The returned `unsupported-fields` warning makes this loss explicit. Native MPP export is not supported; export XML when the resulting schedule must be opened in Microsoft Project.
The converter package ships a genuine legacy `sample.mpp` under `examples/third-party/mpxj/` for signature and version testing. The fixture comes from MPXJ's LGPL-2.1 test corpus and includes its upstream license and exact provenance. Because it is MPP9, importing it returns `unsupported-mpp-version`.
---
# Read-Only Mode
URL: https://pro.rv-grid.com/guides/gantt/features/read-only-mode/
Source: src/content/docs/guides/gantt/features/read-only-mode.mdx
Description: Use Gantt read-only mode to block packaged task, dependency, and assignment mutations while keeping the schedule inspectable.
Surface: gantt
Read-only mode turns the packaged Gantt editing surface into an inspection surface. Users can still view the task table and timeline, navigate, search, filter, sort, zoom, and use non-mutating UI, but packaged project mutations are rejected.
Enable it on the Gantt config:
```ts
grid.gantt = {
id: 'project-1',
name: 'Project Roadmap',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: new Date().toISOString(),
readOnly: true,
};
```
## What It Blocks
`readOnly: true` blocks mutations routed through the packaged Gantt services:
- Task changes: move, resize, split, progress edits, inline task-field edits, task creation, indent, outdent, and delete.
- Dependency changes: create, update, delete, and predecessor/successor field replacement.
- Assignment changes: assignee/resource field updates and assignment replacement.
Blocked mutations return a failure result instead of updating the stores. Task changes return `readonly-task`; dependency and assignment changes return `readonly-project`.
## What Still Works
Read-only mode does not turn the grid into a static screenshot. Non-mutating workflows remain available:
- Timeline rendering, task bars, dependencies, baselines, critical-path visuals, and diagnostics.
- Tree expand/collapse, search, filtering, sorting, scrolling, and zoom.
- External application-controlled state replacement, such as loading a different project snapshot or changing the Gantt config.
The built-in Gantt context menu also hides mutating row actions when the project is read-only.
## Difference From Grid Readonly
`gantt.readOnly` is a Gantt project mutation guard. It protects the packaged task, dependency, and assignment mutation paths.
The RevoGrid `readonly` property is a grid-level editing setting. Use it when you also want to prevent generic cell editing behavior outside Gantt's mutation services.
```ts
grid.readonly = true;
grid.gantt = {
...projectConfig,
readOnly: true,
};
```
---
# Toolbar And Navigation
URL: https://pro.rv-grid.com/guides/gantt/features/toolbar-and-navigation/
Source: src/content/docs/guides/gantt/features/toolbar-and-navigation.mdx
Description: Build custom Gantt toolbar buttons for export, baselines, critical path, and timeline navigation.
Surface: gantt
These helpers let your application render its own toolbar while reusing Gantt command behavior.
The built-in `defineGanttToolbar()` command bar includes an Excel export icon button by default. The button dispatches the same Pro `export-excel` event as `exportGanttExcel()`, so add `ExportExcelPlugin` to the grid when you want the packaged plugin to create the workbook. Hide it with `controls: { exportExcel: false }` or hide the export group with `controls: { export: false }`.
## Built-In Toolbar
Use `defineGanttToolbar()` when you want the packaged Gantt command bar instead of rendering your own buttons. Mount it after assigning `grid.gantt` so it can read the initial visual state.
```ts
import { defineGanttToolbar } from '@revolist/gantt';
const toolbar = document.querySelector('#gantt-toolbar');
const grid = document.querySelector('revo-grid');
if (!toolbar || !grid) {
throw new Error('Gantt toolbar host or grid was not found');
}
grid.gantt = projectConfig;
defineGanttToolbar(toolbar, {
grid,
columns: [
{ prop: 'wbs', label: 'WBS' },
{ prop: 'name', label: 'Name' },
{ prop: 'startDate', label: 'Start Date' },
{ prop: 'duration', label: 'Duration' },
],
visuals: {
showDependencies: true,
showBaseline: false,
showCriticalPath: false,
},
});
```
### Control Visibility
Every built-in toolbar option can be hidden with `controls`. Omitted control flags are visible by default, except `baseline`, which is opt-in.
```ts
defineGanttToolbar(toolbar, {
grid,
controls: {
search: false,
history: false,
tree: false,
zoom: true,
zoomOut: false,
tasks: true,
indent: false,
outdent: false,
export: false,
columns: false,
dependencies: true,
baseline: false,
criticalPath: true,
},
});
```
Parent group flags hide their child buttons:
| Parent flag | Child buttons |
| --- | --- |
| `history` | `undo`, `redo` |
| `tree` | `expandAll`, `collapseAll` |
| `zoom` | `zoomIn`, `zoomOut` |
| `tasks` | `addTask`, `indent`, `outdent` |
| `export` | `exportExcel` |
Leaf flags hide individual controls: `search`, `columns`, `dependencies`, `baseline`, and `criticalPath`.
Hidden visual controls do not write their local defaults back into `grid.gantt.visuals`. You can still set hidden visual behavior directly in `grid.gantt.visuals`.
The dependency toggle is display-only. Turning it off updates `grid.gantt.visuals.showDependencies = false`, which hides dependency arrows without clearing `grid.ganttDependencies` or changing scheduler dependency inputs.
```ts
import {
captureGanttBaseline,
createGanttToolbarQuickWinActions,
exportGanttCsv,
exportGanttExcel,
fitGanttToProject,
listGanttToolbarQuickWinActions,
scrollGanttToToday,
toggleGanttCriticalPath,
} from '@revolist/gantt';
```
## Direct Commands
Use direct helpers when you already know which button was clicked.
```ts
const grid = document.querySelector('revo-grid');
if (!grid) {
throw new Error('Gantt grid was not found');
}
await exportGanttCsv(grid, { filename: 'launch-plan' });
exportGanttExcel(grid, { workbookName: 'launch-plan.xlsx' });
await captureGanttBaseline(grid, { name: 'Sprint baseline' });
const criticalPathVisible = toggleGanttCriticalPath(grid);
await scrollGanttToToday(grid, { align: 0.5 });
await fitGanttToProject(grid, { viewportWidth: grid.clientWidth });
```
## Command Descriptors
Use `createGanttToolbarQuickWinActions()` when a framework toolbar should render generic command objects. Creating descriptors does not export, scroll, or mutate the grid. Work happens only when `run()` is called.
```ts
const actions = createGanttToolbarQuickWinActions(grid, {
csv: { filename: 'launch-plan' },
excel: { workbookName: 'launch-plan.xlsx' },
baseline: { name: 'Sprint baseline' },
today: { align: 0.5 },
fitToProject: { viewportWidth: grid.clientWidth },
});
for (const action of listGanttToolbarQuickWinActions(actions)) {
renderButton({
id: action.id,
label: action.label,
title: action.title,
onClick: () => void action.run(),
});
}
declare function renderButton(command: {
id: string;
label: string;
title: string;
onClick: () => void;
}): void;
```
Disable individual commands by passing `false`.
```ts
const readonlyActions = createGanttToolbarQuickWinActions(grid, {
csv: false,
baseline: false,
});
```
## Behavior Notes
- CSV export uses the RevoGrid export plugin when a plugin with `exportFile()` is installed.
- Excel export dispatches the Pro Excel export event on the grid.
- Baseline capture looks for the Gantt runtime and appends the captured snapshot to `grid.ganttBaselines`.
- Critical path toggling updates `grid.gantt.visuals.showCriticalPath`.
- Today and fit-to-project navigation delegate to runtime methods when available, otherwise they compute timeline coordinates and call `grid.scrollToCoordinate()`.
---
# Add Task Row
URL: https://pro.rv-grid.com/guides/gantt/interaction/add-task-row/
Source: src/content/docs/guides/gantt/interaction/add-task-row.mdx
Description: Use the pinned Gantt add-task row and identify it with isGanttAddTaskRow.
Surface: gantt
`GanttPlugin` can show a pinned bottom row for quickly creating tasks by typing a name into the task table.
Enable it with `taskCreateRow: true` and `allowTaskCreate: true`:
```ts
import { GanttPlugin } from '@revolist/gantt';
grid.plugins = [GanttPlugin];
grid.gantt = {
...project,
allowTaskCreate: true,
taskCreateRow: true,
};
```
The row is hidden when Gantt is read-only or when resource planning mode is active.
## How It Works
`GanttPlugin` installs `GanttAddTaskRowPlugin` internally. Do not add `GanttAddTaskRowPlugin` to `grid.plugins` yourself.
The internal plugin owns a draft row in the `rowPinEnd` source. That draft row is not a task entity and is not written into `grid.source`. When the user edits the `name` cell with a non-empty value, Gantt creates a new root task at the end of the project and resets the draft row.
Only the `name` cell is editable. Other cells are rendered as inert cells, row drag is disabled for the draft row, and the generated Gantt context menu is suppressed on it.
## Identifying The Draft Row
Use `isGanttAddTaskRow()` when custom columns or actions need to skip the draft row:
```ts
import {
GanttPlugin,
isGanttAddTaskRow,
} from '@revolist/gantt';
import { RowSelectPlugin } from '@revolist/revogrid-pro';
grid.plugins = [GanttPlugin, RowSelectPlugin];
grid.columns = [
{
prop: 'selected',
name: '',
rowSelect: ({ model }) => !isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
// Task table columns...
];
```
This is useful for checkbox selection, custom cell renderers, and application actions that should apply only to real task rows.
## Custom Context Menus
When Gantt context-menu integration is enabled, the generated row menu does not open on the add-task draft row. If your application fully disables Gantt context-menu integration and owns `rowContextMenu` itself, keep the same rule in your resolver:
```ts
grid.rowContextMenu = {
items: [
{ name: 'Open task details', action: openTaskDetailsFromMenu },
],
resolve(context) {
if (context.triggerElement.closest('[data-gantt-add-task-row-cell="true"]')) {
return null;
}
return undefined;
},
};
grid.gantt = {
...project,
contextMenu: false,
};
```
For model-based checks inside item actions, read the focused row model from the action context and pass it to `isGanttAddTaskRow()`.
---
# Dependency Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/dependency-editing/
Source: src/content/docs/guides/gantt/interaction/dependency-editing.mdx
Description: Create, select, and delete dependency links in timeline.
Surface: gantt
Dependency editing supports:
- Drag-to-create links from dependency handles.
- Allowed-type filtering via `gantt.allowedDependencyTypes`.
- Hover/select styling and inline delete UI.
- Keyboard delete on selected dependency.
## Mobile And Touch Devices
On touch devices, dependency handles follow the same select-then-edit model as task-bar editing:
- Tap a task bar to select it.
- Gantt reveals larger dependency handles on the selected bar.
- Drag from a visible start or end dependency handle to another task bar.
- If the pointer lands on a target handle, Gantt uses that explicit endpoint.
- If the pointer lands on the target bar but not a handle, Gantt infers start or end from the pointer position relative to the bar midpoint.
Hidden dependency handles are not hit-testable on coarse pointers, so a swipe near the edge of an unselected task does not start dependency creation. This keeps timeline scrolling usable on phones and tablets while preserving precise dependency editing after task selection.
Implementation:
- `features/dependencies/drag-controller.ts`
- `features/dependencies/dependency-tool.ts`
- `features/dependencies/dependency-layer.ts`
---
# Drag, Resize, And Progress Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/drag-resize-progress/
Source: src/content/docs/guides/gantt/interaction/drag-resize-progress.mdx
Description: Timeline gesture interactions for task bars.
Surface: gantt
Implemented interactions:
- Drag task bars to move.
- Drag start/end handles to resize.
- Drag progress handle to update completion.
Each interaction has preview state and commit state and can trigger warning/diagnostic updates.
## Mobile And Touch Devices
Gantt supports phones, tablets, and hybrid devices through adaptive pointer handling. Desktop users can drag task bars and handles directly. Touch users get a safer select-then-edit flow so scrolling a dense timeline does not accidentally change the schedule.
On coarse-pointer devices:
- Tap a task bar to select it.
- Selection reveals larger resize, dependency, progress, and split handles.
- Drag the selected task bar to move the task.
- Drag a visible start/end handle to resize.
- Drag the visible progress handle to update completion.
- Start swiping from an unselected task bar when you want to pan the task table or timeline; hidden handles are not hit-testable until selection.
The mobile path keeps native panning available for unselected task bars and disables browser touch panning only on selected/active bars or visible edit handles. This preserves scroll-first behavior while still allowing precise edits after the user has selected the task.
The packaged task editor dialog also adapts to phone-width screens with a full-viewport, single-column layout and larger touch targets.
Implementation:
- `features/tasks/controllers/task-drag-controller.ts`
- `features/tasks/controllers/task-resize-controller.ts`
- `features/tasks/controllers/task-progress-controller.ts`
- `features/tasks/controllers/pointer-gesture.ts`
- `features/tasks/timeline-preview-layer.ts`
---
# Row Selection
URL: https://pro.rv-grid.com/guides/gantt/interaction/row-selection/
Source: src/content/docs/guides/gantt/interaction/row-selection.mdx
Description: Use RowSelectPlugin with Gantt task operations and row reordering.
Surface: gantt
`GanttPlugin` can work with the RevoGrid Pro `RowSelectPlugin` when you want checkbox-based task selection in the task table.
Add a row-select column, register `RowSelectPlugin`, and enable the row-order bridge when selected rows should move together during Gantt row reordering.
```ts
import { GanttPlugin, isGanttAddTaskRow } from '@revolist/gantt';
import { RowSelectPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [GanttPlugin, RowSelectPlugin];
grid.rowSelect = {
rowOrder: true,
};
grid.columns = [
{
prop: 'selected',
name: '',
rowSelect: ({ model }) => !isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
// Task table columns...
];
```
The checkbox column uses physical task rows from `grid.source`. Gantt ignores resource rows and non-task rows when resolving checkbox selection for task operations.
Use `isGanttAddTaskRow()` when `taskCreateRow` is enabled so the pinned “Add new task” row does not render a checkbox. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for the draft-row contract.
## Bulk Gantt Operations
When no explicit task ids are passed, Gantt resolves task ids in this order:
1. Checkbox-selected rows from `RowSelectPlugin`.
2. Gantt task range selection.
3. The currently focused or selected Gantt task.
This means toolbar buttons, context-menu actions, keyboard handlers, or application code can call the existing Gantt methods without manually collecting checkbox state:
```ts
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin);
gantt?.deleteTasks();
gantt?.indentTasks();
gantt?.outdentTasks();
gantt?.convertTasksToMilestones();
```
After a successful delete, Gantt clears the row-select checkbox state for task rows so removed tasks do not remain selected in `RowSelectPlugin`.
## Row Reordering
Gantt installs `RowOrderPlugin` internally. `RowSelectPlugin` does not automatically move all checked rows unless the row-select row-order integration is enabled:
```ts
grid.rowSelect = {
rowOrder: true,
};
```
With `rowOrder: true`, dragging a checked task row moves the visible checked task rows as one compact block. Dragging an unchecked row keeps the normal Gantt row-order behavior.
## Framework Bindings
React:
```tsx
!isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
...taskColumns,
]}
/>
```
Vue:
```vue
```
Angular:
```html
```
For Vue Gantt examples, use `.prop` for `row-select` because it is a direct grid plugin property.
---
# Task Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/task-editing/
Source: src/content/docs/guides/gantt/interaction/task-editing.mdx
Description: Inline editing, the default task editor dialog, and scheduler-backed mutation flow.
Surface: gantt
Task edits route through mutation services and scheduler recomputation. Before applying task changes, Gantt emits cancelable `gantt-before-task-change`; dependency field edits emit `gantt-before-dependency-change`; assignee edits emit `gantt-before-assignment-change`.
Command/event surface is defined in:
- `packages/gantt/src/gantt/grid/gantt-events.ts`
- `packages/gantt/src/gantt/grid/gantt-plugin.ts`
For persistence and audit, listen to the unified `gridedit` event and inspect its typed `domainChanges`. Successful Gantt mutations are tagged with `gantt-task-change`, `gantt-dependency-change`, or `gantt-assignment-change`; rejected changes do not add Gantt domain entries. The Gantt before-change events remain the cancellation boundary.
## Inline Editing
Inline Gantt edits update the task through the Gantt mutation services, recompute the project, and emit history snapshots for user changes. Initial project hydration is not recorded as a history step.
Call `event.preventDefault()` in a before-change listener to reject a field edit before the task, dependency, or assignment store is mutated. Rejected predecessor/successor edits leave the old dependency rows in place; rejected assignee edits leave the old assignment rows in place.
### Edit validation boundary
`resolveTaskEdit()` is the stable validation entry point for inline task fields. It converts a raw editor value into a validated `TaskUpdate` patch or returns an `invalid-field`, `invalid-value`, or `readonly-derived-field` failure. It does not mutate task data itself. The task mutation service applies the accepted patch, and the scheduling engine then recomputes derived dates and project state.
Task-table text-entry editors follow spreadsheet-style keyboard entry, including text, numeric, percentage, date-text, Duration, Remaining Duration, and Work fields. With a cell selected, typing a printable character starts a new value and replaces the displayed value; for example, pressing `4` opens the editor with `4`. Press Enter or double-click instead when you want to open and modify the existing value. Choice, checkbox, and calendar controls keep their domain-specific input behavior.
The task table and Task Information dialog select controls from the same framework-neutral `TaskEditorFieldSchema.kind` contract. Duration, Remaining Duration, and Work use the explicit `duration` kind, so both surfaces share text input semantics, duration-field identification, formatting, and parsing. Grid adapters still own RevoGrid `EditorBase` lifecycle and keyboard seeds, while dialog adapters own controlled form state; this keeps behavior reusable without coupling modal controls to grid DOM lifecycle.
A zero Duration schedules the row as a milestone, but its Duration remains editable. Entering a positive Duration in either the grid or Task Information dialog converts it back to a regular task automatically. Summary Duration remains read-only because the scheduler derives it from the child-task span.
Dialog control rendering is decomposed by responsibility under `packages/gantt/src/gantt-task-editor-dialog/field-controls/`: input, date, select, switch, tags, textarea, and labels/resource controls each own one control category. The top-level `FieldControl` only dispatches by the shared field kind.
The implementation is split by value category under `packages/gantt/src/gantt/engine/task-edit-service/internal/`: date, duration, numeric, boolean, progress, name, status, task-mode, effort-mode, and constraint handlers each own their parsing, validation messages, and patch creation. Consumers link directly to `engine/task-edit-service/index`, whose only responsibility is dispatching to the appropriate handler. This keeps custom label handling and schedule-aware validation consistent without collecting every edit rule into one service file or exposing handler registries as public API.
The split is internal and does not change imports, event names, field behavior, or `TaskEditResult`. Existing integrations should continue importing Gantt APIs from `@revolist/gantt`.
## Task Editor Dialog
`GanttPlugin` installs the packaged task editor dialog by default, so every Gantt grid gets the row context-menu editor without registering an extra plugin. The same editor contract is also available as presentation-free helpers: render controls from `TASK_EDITOR_FIELD_SCHEMA`, seed them with `createTaskEditorFormValues()`, and turn submitted values into a `TaskUpdate` patch with `normalizeTaskEditorSubmit()`.
The packaged dialog is responsive for mobile use. On phone-width viewports it becomes a full-viewport dialog, switches task fields and dependency rows to a single column, keeps the form body scrollable, and uses larger controls for touch input. The same validation and submit flow runs on desktop, tablet, and phone layouts.
## How It Works
The task editor demo opens the editor from the Gantt row context menu and includes the packaged Gantt toolbar:
- The grid renders normal Gantt tasks, dependencies, calendars, resources, and assignments.
- Right-click a task row and choose **Edit...**.
- Use row context-menu **Add** to insert a `New task N` row without opening the editor, or toolbar **Task** to create a task and open the same editor for the new row.
- The auto-installed task editor dialog adds the row-menu edit item and owns the native HTML ``.
- New tasks open in the editor by default after the Gantt plugin emits its post-create lifecycle event. Set `ganttTaskEditorDialog.openOnCreate` to `false` when task creation should stay silent.
- The dialog renders a tabbed task information UI from `TASK_EDITOR_FIELD_SCHEMA`.
- **General** contains task name, status, task kind, dates, duration, and progress.
- **Advanced** contains effort mode, calendar, tags, constraints, manual scheduling, work, actual dates, inactive state, and leveling controls.
- **Predecessors** edits incoming dependency target, type, and lead/lag days. **Successors** remains available through customization but is hidden by default.
- **Resources** renders the resource assignment picker when resources and assignments are available.
- **Notes** provides a dedicated long-form notes editor.
- The patch preview calls `normalizeTaskEditorSubmit(task, values)` on every edit.
- The submit button applies the returned patch through the Gantt runtime/provider edit path, updates `grid.ganttAssignments` and `grid.ganttDependencies`, and refreshes the project.
```ts
grid.plugins = [GanttPlugin];
grid.ganttTaskEditorDialog = {
title: 'Task details',
description: 'Update task fields and save the changes to the project schedule.',
applyLabel: 'Save changes',
localeText: {
tabs: {
assignments: 'Team',
},
fields: {
effortMode: 'Task type',
},
resourcesSearch: {
placeholder: 'Find a resource',
},
},
};
const values = createTaskEditorFormValues(task, resources, assignments);
const result = normalizeTaskEditorSubmit(task, values);
if (result.ok) {
updateTask(task.id, result.patch);
}
```
## Fields
The schema includes editable task fields such as `name`, `startDate`, `endDate`, `duration`, `progressPercent`, constraints, deadlines, actual dates, work, effort mode, effort-driven scheduling, inactive state, leveling controls, manual scheduling, and `notes`. It also includes display fields such as `resourceLabels`.
Date fields open as date-only controls by default. Use the field-level **Time** toggle in the packaged dialog when you need minute-level scheduling; the dialog stores the submitted value as a UTC ISO datetime.
Set `gantt.dateFormats.editor` when the packaged dialog should show custom date text instead of native date inputs. In that mode, the dialog uses a text input, calls your parser as the user types, and still submits ISO dates to `normalizeTaskEditorSubmit()` and the Gantt mutation services:
```ts
grid.gantt = {
...project,
dateFormats: {
locale: 'en-GB',
timeZone: 'UTC',
table: { day: '2-digit', month: '2-digit', year: 'numeric' },
tooltip: { day: '2-digit', month: 'short', year: 'numeric' },
editor: {
options: { day: '2-digit', month: '2-digit', year: 'numeric' },
parser(value) {
const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value.trim());
return match ? `${match[3]}-${match[2]}-${match[1]}` : null;
},
},
},
};
```
Custom date formats are presentation and input helpers only. Keep persisted tasks as `YYYY-MM-DD` dates or UTC datetimes ending in `Z`.
`resourceLabels` is read-only in the schema helper because `TaskUpdate` does not own assignment rows. The packaged dialog upgrades that field into a resource assignment picker and writes the selected resources to `grid.ganttAssignments`. Set `ganttTaskEditorDialog.resources` to `false` when you want the field to stay read-only.
Resource options show `ResourceEntity.avatarUrl` as a circular profile image. When no image is configured—or an image fails to load—the picker renders deterministic initials from the resource name, so every assignment remains visually identifiable without requiring avatar assets.
Entity panels include search controls: resource assignment searches by resource name and role, while predecessor and successor panels search task names. Resource lists, task dropdowns, dependency type dropdowns, and schema-backed select options are sorted A-Z by their displayed labels.
Use `ganttTaskEditorDialog.localeText` to translate or customize packaged dialog text. It covers tabs, field labels, field option labels, dependency type labels, empty states, search placeholders, switch labels, validation headings, save errors, and context-menu text. Existing top-level `title`, `description`, `applyLabel`, `resetLabel`, `closeLabel`, and `menuItemName` still work as direct overrides for those common labels.
## Customizing Tabs, Fields, and Controls
Use `ganttTaskEditorDialog.customization` to change the packaged editor without replacing it. Built-in tab ids are `details`, `predecessors`, `successors`, `assignments`, `advanced`, and `notes`. Set an item to `false` to remove it, use `hidden`, `title`, and `fields` to reconfigure it, and use `order` to place built-in and custom tabs. Nested customization takes precedence over the legacy top-level `fields` allow-list.
```ts
grid.ganttTaskEditorDialog = {
fields: ['name', 'status', 'type', 'startDate', 'endDate', 'calendarId', 'tags', 'notes'],
customization: {
controls: {
description: false,
reset: false,
},
tabs: {
order: ['details', 'successors', 'advanced', 'notes'],
items: {
details: { title: 'Main', fields: ['name', 'status', 'type', 'startDate', 'endDate'] },
successors: { hidden: false },
assignments: false,
},
},
},
};
```
The shell controls are `header`, `title`, `description`, `close`, `tabs`, `validation`, `preview`, `reset`, `apply`, and `footer`. Setting one to `false` hides only the packaged control; custom content can still call the corresponding controller method.
### Adding a Custom Page
Custom pages use a framework-neutral DOM lifecycle. `mount` runs once when the page is shown, `update` receives fresh task/project/draft context, and `destroy` runs when the page is removed. The controller can update built-in values, resources, dependencies, application-owned custom values, validation errors, and the reset/close/submit lifecycle.
```ts
const auditPage = {
mount(container, context) {
let currentContext = context;
const input = document.createElement('input');
input.placeholder = 'External approval reference';
container.appendChild(input);
const onInput = () => {
currentContext.controller.setCustomValue('approvalReference', input.value);
currentContext.controller.setValidationErrors(input.value.trim() ? [] : ['Approval reference is required.']);
};
input.addEventListener('input', onInput);
return {
update(next) {
currentContext = next;
input.value = String(next.state.customValues.approvalReference ?? '');
},
destroy() {
input.removeEventListener('input', onInput);
},
};
},
};
grid.ganttTaskEditorDialog = {
customization: {
tabs: {
order: ['details', 'audit', 'notes'],
items: { audit: { title: 'Audit', renderer: auditPage } },
},
},
async onSubmit({ result, customValues }) {
await saveTaskAndApproval(result.patch, customValues.approvalReference);
},
};
```
Custom values are passed to `onSubmit` but are never persisted implicitly. Custom validation errors block packaged submission until cleared.
### Replacing the Entire Editor
Set `customization.editor` to the same DOM renderer contract to replace the packaged dialog completely. The replacement receives the live project snapshot and shared draft controller, including `reset()`, `close()`, and `submit()`. Its lifecycle is destroyed when the editor closes or the plugin is removed.
### Calendar and Tags
`calendarId` is populated from the live Gantt calendars and validated before mutation. Changing it uses the normal task mutation, rescheduling, and history path. `tags` accepts comma-separated input; values are trimmed, blank entries are removed, and duplicates are removed while preserving their first-seen order.
Use `ganttTaskEditorDialog.openOnCreate = false` to keep toolbar or programmatic task creation from opening the dialog. The Gantt row context menu always creates silently through the default **Add** action when task creation is allowed, and the editor plugin prepends its **Edit...** item while preserving custom `rowContextMenu` or `contextMenu` items and `resolve` behavior. Set `gantt.contextMenu = false` to opt out of generated Gantt menu items, or set `ganttTaskEditorDialog.contextMenu` when only the packaged editor menu item should change.
## Task Colors
The Gantt row context menu and packaged task editor use the same shared color palette. Choosing a color updates the task through the normal Gantt mutation path, so source synchronization, cancelable before-change events, history, scheduling, and rendering stay consistent. **Default** clears the authored task color; `taskBarColorHook` remains authoritative when it returns a color.
Use `options` to replace the shared palette or `extendOptions` to add project colors. Any swatch can be disabled, as can the Default choice or the complete Color command:
```ts
grid.gantt = {
...project,
contextMenu: {
colorPalette: {
extendOptions: [
{ value: '#7c3aed', label: 'Project violet' },
{ value: '#0f766e', label: 'Reserved teal', disabled: true },
],
defaultOption: { label: 'Use project default' },
colorAriaLabel: (option) => `Task color: ${option.label}`,
},
disabled: {
delete: true,
},
},
};
```
Set `contextMenu.colorPalette` to `false`, `contextMenu.hidden.color` to `true`, or `contextMenu.disabled.color` to `true` to remove or disable this functionality. Set `colorPalette.defaultOption` to `false` to remove Default, or `colorPalette.disabled` to `true` to disable the whole submenu. The same `hidden` and `disabled` maps apply to the other built-in Gantt commands. Application commands supplied through RevoGrid's row-context-menu configuration continue to compose with the generated Gantt items.
The task editor inherits `gantt.contextMenu.colorPalette` by default. Use `ganttTaskEditorDialog.colorPalette` to configure its palette independently, or set it to `false` to remove the Color field from the editor. The editor-specific setting supports the same `options`, `extendOptions`, disabled choices, Default configuration, and accessible option-label resolver.
## Validation
`normalizeTaskEditorSubmit()` validates values before returning a patch:
- `name` cannot be empty.
- Date values must be ISO dates like `2026-05-12` or UTC datetimes ending in `Z`.
- `endDate` cannot be earlier than `startDate`.
- Numeric fields must be non-negative.
- `progressPercent` is clamped to `0..100`.
- Unsupported constraint values return field errors.
If both `startDate` and `endDate` are present, the helper recalculates `duration` from the date range. If values are unchanged, the default result omits them from the patch.
## Production Wiring
In production, keep the same flow but replace the demo's local source update with your persistence and project-state update path:
```ts
async function submitTaskEditor(task, values) {
const result = normalizeTaskEditorSubmit(task, values);
if (!result.ok) {
renderFieldErrors(result.errors);
return;
}
await api.updateTask(task.id, result.patch);
grid.source = grid.source.map((row) =>
row.id === task.id ? { ...row, ...result.patch } : row,
);
}
```
The Gantt plugin still owns timeline projection and scheduling when the refreshed task source is applied.
The packaged dialog defaults to applying task field patches through the Gantt runtime/provider edit path and replacing the edited task's assignment rows in `grid.ganttAssignments`. This keeps task editor saves visible to `HistoryPlugin` instead of bypassing grid edit tracking with direct `grid.source` replacement. To persist edits first, provide `ganttTaskEditorDialog.onSubmit`; return `false` from that callback when your application has already applied the task and assignment updates and the plugin should skip the default patch.
Dependency tabs also patch `grid.ganttDependencies` by replacing links that involve the edited task. Existing dependency ids are preserved, new rows get deterministic ids, and lag supports negative lead or positive lag days.
Set `preview: true` only for diagnostics or documentation when you want to show the generated `TaskUpdate` JSON.
---
# Gantt Quick Start — Angular
URL: https://pro.rv-grid.com/guides/gantt/introduction/angular/
Source: src/content/docs/guides/gantt/introduction/angular.mdx
Description: Build a RevoGrid Gantt project in Angular with the standalone wrapper, typed property bindings, and managed task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for Angular.
2. Create a standalone component and bind every Gantt collection as a property.
```ts
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
GanttPlugin,
createDefaultTaskTableColumn,
type CalendarEntity,
type DependencyEntity,
type GanttPluginConfig,
type GanttTaskSourceRow,
} from '@revolist/gantt';
@Component({
selector: 'app-project-gantt',
standalone: true,
imports: [RevoGrid],
encapsulation: ViewEncapsulation.None,
styles: ['.project-gantt { display: block; width: 100%; height: 640px; }'],
template: `
`,
})
export class ProjectGanttComponent {
plugins = [GanttPlugin];
project: GanttPluginConfig = {
id: 'website-launch', name: 'Website launch', version: '1', currency: 'USD',
timeZone: 'UTC', primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z', zoomPreset: 'day-week',
};
calendars: CalendarEntity[] = [{
id: 'standard', name: 'Standard', timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5], holidays: [], hoursPerDay: 8,
}];
tasks: GanttTaskSourceRow[] = [
{ id: 'design', name: 'Design', startDate: '2026-07-14', duration: 3, percentDone: 50, calendarId: 'standard' },
{ id: 'build', name: 'Build', startDate: '2026-07-17', duration: 5, percentDone: 0, calendarId: 'standard' },
];
dependencies: DependencyEntity[] = [{
id: 'design-to-build', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0,
}];
columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
];
}
```
3. Import the Pro and Gantt CSS files in the application-wide stylesheet or build entry, not in an encapsulated child stylesheet.
Open the runnable Angular demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# Install RevoGrid Gantt
URL: https://pro.rv-grid.com/guides/gantt/introduction/installation/
Source: src/content/docs/guides/gantt/introduction/installation.mdx
Description: Install the standalone RevoGrid Gantt package, styles, and framework wrapper required to render a project.
Surface: gantt
Install `@revolist/gantt` alongside the core grid and Pro. Add only the framework wrapper your application uses.
1. Install the packages.
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/react-datagrid
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/vue3-datagrid
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/angular-datagrid
```
2. Import the product styles once in the application entry point.
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
```
3. Register `GanttPlugin` on the grid.
```ts
import { GanttPlugin } from '@revolist/gantt';
grid.plugins = [GanttPlugin];
```
4. Give the grid a visible size. Gantt needs both width and height to calculate its virtual viewport.
```css
.project-gantt {
display: block;
width: 100%;
height: 640px;
}
```
Evaluating Gantt? Follow the trial installation guide and use `@revolist/gantt-trial` with its matching CSS path.
## Choose Your Framework
- [Vanilla TypeScript](/guides/gantt/introduction/vanilla-typescript/)
- [React](/guides/gantt/introduction/react/)
- [Vue 3](/guides/gantt/introduction/vue/)
- [Angular](/guides/gantt/introduction/angular/)
The general [Pro and product installation guide](/guides/installation/) covers private registry, pre-built, GitHub, source-build, and no-build distribution options.
---
# Gantt Quick Start — React
URL: https://pro.rv-grid.com/guides/gantt/introduction/react/
Source: src/content/docs/guides/gantt/introduction/react.mdx
Description: Build a typed RevoGrid Gantt project in React with memoized project collections, calendars, dependencies, and task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for React.
2. Create the component. Keep project collections and columns stable with `useMemo` so ordinary React renders do not replace Gantt state unnecessarily.
```tsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import {
GanttPlugin,
createDefaultTaskTableColumn,
type CalendarEntity,
type DependencyEntity,
type GanttPluginConfig,
type GanttTaskSourceRow,
} from '@revolist/gantt';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
export default function ProjectGantt() {
const plugins = useMemo(() => [GanttPlugin], []);
const project = useMemo(() => ({
id: 'website-launch',
name: 'Website launch',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z',
zoomPreset: 'day-week',
}), []);
const calendars = useMemo(() => [{
id: 'standard', name: 'Standard', timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5], holidays: [], hoursPerDay: 8,
}], []);
const source = useMemo(() => [
{ id: 'design', name: 'Design', startDate: '2026-07-14', duration: 3, percentDone: 50, calendarId: 'standard' },
{ id: 'build', name: 'Build', startDate: '2026-07-17', duration: 5, percentDone: 0, calendarId: 'standard' },
], []);
const dependencies = useMemo(() => [{
id: 'design-to-build', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0,
}], []);
const columns = useMemo(() => [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
], []);
return (
);
}
```
3. Give `.project-gantt` a fixed or flex-derived height.
```css
.project-gantt { display: block; width: 100%; height: 640px; }
```
Open the runnable React demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# Gantt Quick Start — Vanilla TypeScript
URL: https://pro.rv-grid.com/guides/gantt/introduction/vanilla-typescript/
Source: src/content/docs/guides/gantt/introduction/vanilla-typescript.mdx
Description: Build a RevoGrid Gantt project with the custom element, typed project data, task columns, calendars, and dependencies.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) and add a mount element.
```html
```
2. Register the custom element and create the project.
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
GanttPlugin,
createDefaultTaskTableColumn,
type CalendarEntity,
type DependencyEntity,
type GanttPluginConfig,
type GanttTaskSourceRow,
} from '@revolist/gantt';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
defineCustomElements();
const project: GanttPluginConfig = {
id: 'website-launch',
name: 'Website launch',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z',
zoomPreset: 'day-week',
};
const calendars: CalendarEntity[] = [{
id: 'standard',
name: 'Standard',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: [],
hoursPerDay: 8,
}];
const tasks: GanttTaskSourceRow[] = [
{
id: 'design',
name: 'Design',
startDate: '2026-07-14',
duration: 3,
percentDone: 50,
calendarId: 'standard',
},
{
id: 'build',
name: 'Build',
startDate: '2026-07-17',
duration: 5,
percentDone: 0,
calendarId: 'standard',
},
];
const dependencies: DependencyEntity[] = [{
id: 'design-to-build',
predecessorTaskId: 'design',
successorTaskId: 'build',
type: 'FS',
lagDays: 0,
}];
```
3. Configure and mount the grid. Set `source` after the element is connected.
```ts
const host = document.querySelector('#gantt');
if (!host) throw new Error('Missing #gantt');
const grid = document.createElement('revo-grid');
grid.className = 'project-gantt';
grid.plugins = [GanttPlugin];
grid.gantt = project;
grid.ganttCalendars = calendars;
grid.ganttDependencies = dependencies;
grid.columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
];
host.appendChild(grid);
grid.source = tasks;
// Call when the hosting view is destroyed.
const dispose = () => grid.remove();
```
Open the runnable Vanilla TypeScript demo to inspect every source file.
Next, continue with the [Gantt tutorial](/guides/gantt/tutorial/) or read the [data model](/guides/gantt/concepts/data-model/).
---
# Gantt Quick Start — Vue 3
URL: https://pro.rv-grid.com/guides/gantt/introduction/vue/
Source: src/content/docs/guides/gantt/introduction/vue.mdx
Description: Build a reactive RevoGrid Gantt project in Vue 3 with the official wrapper, project collections, and managed task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for Vue 3.
2. Create a Vue component. Bind Gantt objects with Vue property bindings; do not serialize them as HTML attributes.
```vue
```
Open the runnable Vue demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# What Is A Gantt Chart?
URL: https://pro.rv-grid.com/guides/gantt/introduction/what-is-gantt/
Source: src/content/docs/guides/gantt/introduction/what-is-gantt.mdx
Description: Core concepts used by the standalone RevoGrid Gantt package.
Surface: gantt
> **Gantt = structured timeline for tasks with dependencies**
A **Gantt chart** is a **visual representation of a plan over time**. It’s a way to show tasks, their durations, and how they relate to each other on a timeline.
It answers only three things:
* **What** tasks exist
* **When** they happen
* **In what order / relation** they happen
---
## Core features of a pure Gantt
#### 1. Task model
Each task has:
* id
* start date
* end date (or duration)
* optional parent (hierarchy)
👉 This is the **data backbone**
---
#### 2. Timeline rendering
* Horizontal time axis
* Tasks rendered as bars
* Zoom levels (day / week / month)
👉 Without this, it’s not a Gantt
---
#### 3. Dependencies (defining feature)
Tasks can depend on each other:
* Finish → Start
* Start → Start
* Finish → Finish
👉 This is what makes it a **planning tool**, not just visualization
---
#### 4. Hierarchy (WBS — work breakdown structure)
* Parent / child tasks
* Expand / collapse
👉 Lets you represent real projects, not flat lists
---
#### 5. Direct manipulation
* Drag task → change start date
* Resize → change duration
👉 If this is missing → it's just a chart, not a planning tool
---
#### 6. Milestones
* Tasks with zero duration
* Represent key events
👉 Needed for any serious usage
---
#### 7. Progress (basic tracking)
* % completion per task
* Visual overlay on bars
👉 Not scheduling — just status
---
#### 8. Large data handling
* Thousands of tasks
* Smooth scroll
* Virtualization
👉 This is our competitive edge
---
## What is NOT part of Gantt (important boundary)
❌ Assigning people to time slots
❌ Calendar conflicts
❌ Shift planning
❌ Workforce shift-slot scheduling across personnel calendars
👉 That’s **scheduler territory** layered on top of Gantt fundamentals
---
## Mental model **“Grid-first Gantt”**
A Gantt is:
```text
Hierarchy (tree)
+ Timeline (x-axis)
+ Dependencies (graph edges)
```
* Left side = grid (tasks, editable)
* Right side = timeline (bars)
---
# Accessibility And Keyboard Navigation
URL: https://pro.rv-grid.com/guides/gantt/production/accessibility/
Source: src/content/docs/guides/gantt/production/accessibility.mdx
Description: Build accessible Gantt workflows with RevoGrid keyboard navigation, semantic task data, visible focus, and equivalent editing controls.
Surface: gantt
Gantt inherits grid-cell focus, selection, editing, and keyboard navigation from RevoGrid. Timeline gestures supplement those workflows; they must not be the only way to change or inspect a task.
## Application Checklist
- Provide meaningful task, resource, column, and toolbar labels.
- Keep focus outlines visible and meet contrast requirements in custom task-bar colors.
- Expose move, resize, progress, dependency, and hierarchy actions through task-table fields, menus, or application controls in addition to pointer gestures.
- Keep the packaged task editor available when complex fields cannot be edited comfortably inline.
- Preserve expected Tab, Enter, Escape, and arrow-key behavior in custom editors.
- Announce validation errors next to the affected input and keep canceled edits focused.
- Do not place essential information only in color, hover tooltips, or dependency geometry.
## Custom Content
When using `taskBarContentHook` or custom cell templates, return concise visible content and keep the task name available in the task table. Custom toolbar buttons need native button semantics, accessible names, and pressed state where applicable.
Test Gantt with keyboard-only navigation, browser zoom, high contrast, and a screen reader in the frameworks your application supports. See [Task Editing](/guides/gantt/interaction/task-editing/) and [Visual Configuration](/guides/gantt/customization/visuals/).
---
# Localization And Date Formatting
URL: https://pro.rv-grid.com/guides/gantt/production/localization/
Source: src/content/docs/guides/gantt/production/localization.mdx
Description: Localize Gantt labels and consistently format task-table, tooltip, editor, and timeline dates.
Surface: gantt
`gantt.labels` localizes packaged option labels, while `gantt.dateFormats` controls date display and parsing across the task table, tooltips, editor, and timeline.
```ts
grid.gantt = {
...project,
labels: {
taskMode: { auto: 'Automatisch', manual: 'Manuell' },
status: {
complete: 'Abgeschlossen',
'on-schedule': 'Im Plan',
late: 'Verspätet',
'future-task': 'Zukünftig',
},
workflowStatus: {
'not-started': 'Nicht begonnen',
'in-progress': 'In Arbeit',
done: 'Erledigt',
blocked: 'Blockiert',
},
},
dateFormats: {
locale: 'de-DE',
timeZone: 'Europe/Berlin',
table: { day: '2-digit', month: '2-digit', year: 'numeric' },
tooltip: { dateStyle: 'medium' },
},
};
```
Task values remain ISO date or UTC ISO date-time strings. Formatting changes presentation, not persisted data. When an editor uses a non-ISO text format, provide both a formatter and parser so submitted values return to the supported ISO contract.
Use one project time zone, explicit calendar time zones, and UTC date-time values when hour/minute precision matters. See [Configuration Reference](/guides/gantt/reference/configuration-reference/#date-formatting) for callback contexts and parsing rules.
---
# Performance And Large Projects
URL: https://pro.rv-grid.com/guides/gantt/production/performance/
Source: src/content/docs/guides/gantt/production/performance.mdx
Description: Keep large Gantt projects responsive by preserving virtualization, stable inputs, bounded visual layers, and measured scheduling work.
Surface: gantt
RevoGrid virtualizes the task table and timeline projection. Preserve that advantage by updating project data through public collections rather than rendering parallel task DOM or reading state back from cells.
## Recommendations
- Use stable task and linked-entity IDs.
- Replace only collections that changed and keep wrapper inputs memoized or reactive at the correct boundary.
- Batch related task, dependency, calendar, resource, and assignment changes before triggering a new schedule pass.
- Keep custom cell and task-bar renderers pure and inexpensive.
- Limit visible marker and time-range overlays to information users need in the active view.
- Prefer column presets over rendering every analytical field at once.
- Measure scheduling separately from browser rendering with the Gantt scheduler benchmark, and use the browser benchmark for end-to-end interaction evidence.
- Test representative hierarchy depth, dependency density, resource assignments, and timeline span—not only row count.
## Current Boundary
Virtual rendering is implemented. A server-side, date-windowed Gantt project model is not currently part of the public contract, so the complete linked project graph must be available to the client scheduler. See [Limitations](/guides/gantt/reference/limitations/) before designing very large remote projects.
## Two benchmark layers
The Gantt scheduler benchmark isolates scheduling-engine recalculation in Node.js. It does not measure browser rendering, scrolling, pointer interactions, hierarchy projection, memory, or mounted DOM.
Run the scheduler benchmark with:
```bash
pnpm --filter @revolist/gantt bench
```
For the reproducible browser suite, raw JSON/CSV, exact environment, screenshot, video, and live workload, open the [RevoGrid Gantt browser benchmark](https://rv-grid.com/benchmarks/gantt). Its published matrix covers 100, 1,000, 5,000, and 10,000 tasks across sparse, normal, and highly connected dependency graphs, with one warm-up and five measured runs per isolated scenario.
The browser suite leads with the measurable capacity claim “10,000 editable tasks and 19,796 dependencies in a live browser demo.” It does not make a “fastest” claim or compare numbers collected with a different harness.
---
# Responsive And Touch Interaction
URL: https://pro.rv-grid.com/guides/gantt/production/responsive-touch/
Source: src/content/docs/guides/gantt/production/responsive-touch.mdx
Description: Configure layouts that preserve Gantt scrolling, selection, editing, and task handles on phones, tablets, and narrow containers.
Surface: gantt
Gantt uses the same configuration on desktop and coarse-pointer devices. Touch interaction follows a select-then-edit model so ordinary swipes keep scrolling the task table or timeline.
- Tap a task bar to select it and reveal larger move, resize, progress, dependency, and split handles.
- Drag a selected bar or visible handle to edit.
- Keep the task table wide enough for at least the task name and one scheduling field.
- Allow the Gantt container to shrink with `min-width: 0` inside flex and grid layouts.
- Use a fixed, viewport-derived, or parent-derived height; do not rely on content height.
- The packaged task editor becomes single-column and full-viewport on narrow screens.
```css
.gantt-shell {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
height: min(760px, calc(100vh - 8rem));
}
.gantt-shell revo-grid {
display: block;
min-width: 0;
height: 100%;
}
```
Do not add page-level touch handlers that compete with Gantt pointer handling. Place application toolbars outside the scrollable grid container and verify edge swipes on unselected bars do not mutate tasks.
---
# SSR And Client Rendering
URL: https://pro.rv-grid.com/guides/gantt/production/ssr/
Source: src/content/docs/guides/gantt/production/ssr.mdx
Description: Integrate RevoGrid Gantt with server-rendered applications while keeping custom-element registration and browser APIs client-only.
Surface: gantt
The scheduling data model is framework-neutral, but the Gantt grid renders in the browser. Register custom elements, access `document` or `window`, and mount the grid only on the client.
## Integration Rules
- Render a stable-height placeholder during SSR to avoid layout shift.
- Dynamically import the wrapper or Gantt component with SSR disabled.
- Call `defineCustomElements()` only in a browser entry or client lifecycle.
- Keep project data serializable across the server/client boundary.
- Do not create the grid during module evaluation in code that also runs on the server.
- Clean up listeners and element instances when client-side routes unmount.
```ts
if (typeof window !== 'undefined') {
const { defineCustomElements } = await import('@revolist/revogrid/loader');
defineCustomElements();
}
```
For React meta-frameworks, dynamically load the component client-side. For Vue/Nuxt use a client-only boundary. For Angular SSR, guard browser-only setup with the platform check used by your application.
The [Vanilla quick start](/guides/gantt/introduction/vanilla-typescript/) shows explicit mount and cleanup; the wrapper quick starts show property binding after hydration.
---
# Configuration Reference
URL: https://pro.rv-grid.com/guides/gantt/reference/configuration-reference/
Source: src/content/docs/guides/gantt/reference/configuration-reference.mdx
Description: Human-readable map of public Gantt config objects.
Surface: gantt
Public config entry points:
- `GanttPluginConfig`
- `weekStartsOn?: 0 | 1` controls week header/tick alignment. Omit for Sunday (`0`); set `1` for Monday.
- `timelinePrecision?: 'day' | 'hour'` controls the default precision for timeline task create, move, and resize interactions. Use `'hour'` with `zoomPreset: 'hour-day'` or `zoom.defaultLevelId: 'hour-day'` for hour-scale planning.
- `snap?: { unit?: 'day' | 'hour'; workingTime?: boolean }` controls interaction snapping. `unit: 'hour'` keeps interactions on hour boundaries; `workingTime: true` or omitted skips holidays, closed weekdays, and closed `calendar.workingHours`.
- `calendars?: CalendarEntity[]` can include intraday `workingHours` such as `{ start: '09:00', end: '17:00' }` plus `hoursPerDay` for hour-mode snapping and scheduling.
- `durationSettings?: DurationSettings` controls the fixed/automatic display unit, compact display precision through `maximumFractionDigits` (default `1`), and conversions through `hoursPerDay`, `hoursPerWeek`, and `daysPerMonth`. See [Duration Units and Conversion](/guides/gantt/scheduling/durations/).
- `fields?: ViewFieldMap` maps shared view meanings to properties in each authored task row. See [Task source field mapping](#task-source-field-mapping).
- `allowedTaskTypes?: TaskType[]` restricts the choices in packaged Task Type column and dialog editors. Omit it to allow `task`, `milestone`, and `summary`; use `['task']` to prevent users from converting tasks to milestones or summaries.
- `labels?: GanttLabelsConfig` customizes option labels, validation errors, scheduler warnings, and task-editor text from one project-level object. See [Labels, Errors, and Warnings](#labels-errors-and-warnings).
- `dateFormats?: GanttDateFormatConfig` controls task-table, tooltip, editor, and timeline date presentation and parsing. See [Date Formatting](#date-formatting).
- `visuals.percentDoneMode?: 'number' | 'bar' | 'circle'` controls the project default for the managed `% Done` task-table cell. It defaults to `bar`; `{ prop: 'percentDone', mode: 'circle' }` overrides it per column. See [Actuals and Progress](/guides/gantt/scheduling/progress/#percent-done-column-mode).
- `taskCreateRow?: boolean` shows an opt-in pinned bottom “Add new task” row. It requires `allowTaskCreate: true` and is hidden for read-only or resource-planning views. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for `GanttAddTaskRowPlugin` behavior and `isGanttAddTaskRow()` usage.
- `GanttSchedulingConfig`
- `GanttResourcePlanningConfig`
- `GanttVisualConfig`
## Task Source Field Mapping
Gantt uses the shared `ViewFieldMap` names also used by Scheduler and Kanban. The mapping values are property names in `grid.source`:
| Meaning | Gantt default |
| --- | --- |
| `id` | `id` |
| `title` | `name` |
| `status` | `workflowStatus` |
| `start` | `startDate` |
| `end` | `endDate` |
| `color` | `color` |
| `progress` | `percentDone` |
This lets one application record shape feed several views:
```ts
grid.gantt = {
...project,
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
};
```
The mapping changes property names only. Date and duration parsing, progress units, validation, and scheduling behavior stay the same. Gantt writes accepted task changes back with the authored property names and preserves unrelated row fields. `resourceId` is accepted so the mapping object can be shared, and its source value is preserved; Gantt resource scheduling continues to use `ganttAssignments`, where a task can have multiple assignments.
## Duration Settings
`durationSettings.defaultUnit` and `defaultWorkUnit` accept every `DurationUnit` or `'auto'`. Duration, Remaining Duration, and Work are stored as hours. `defaultUnit` controls task-span quantities while `defaultWorkUnit` independently controls Work; task `durationUnit` and `workUnit` win. `auto` displays sub-hour values as minutes, sub-day values as hours, and longer values as days without changing stored task data.
```ts
grid.gantt = {
...project,
durationSettings: {
defaultUnit: 'auto',
defaultWorkUnit: 'hour',
hoursPerDay: 8,
hoursPerWeek: 40,
daysPerMonth: 20,
},
};
```
See [Duration Units and Conversion](/guides/gantt/scheduling/durations/) for mixed task units, flexible input, elapsed durations, defaults, and conversion behavior.
## Labels, Errors, and Warnings
Use `gantt.labels` as the project-level localization and message catalog. Error and warning overrides can be strings or formatter functions. The plugin keeps the existing result contract—such as `{ field, code, message }`—and changes only the resolved `message`.
```ts
grid.gantt = {
...project,
labels: {
errors: {
taskEdit: {
invalidRemainingDuration:
'Remaining duration must be a value such as 5h, 2d, 3w, or 30m.',
},
taskEditor: {
invalidRemainingDuration: 'Enter remaining time in hours or days.',
},
dependency: {
taskReferenceNotFound: ({ value }) => `No task matches "${value}".`,
},
},
warnings: {
scheduler: {
deadlineMissed: ({ taskId, date }) =>
`Task ${taskId} missed its ${date} deadline.`,
},
},
taskEditor: {
applyLabel: 'Apply changes',
status: {
saveFailed: 'The task could not be saved.',
},
},
},
};
```
Message groups identify the runtime surface: `taskEdit` for managed grid-cell edits, `taskMutation` for task operations and timeline interactions, `assignment`, `dependency`, and `taskEditor`. Scheduler issues and conflicts use `warnings.scheduler`. The exported `DEFAULT_GANTT_ERROR_LABELS` and `DEFAULT_GANTT_WARNING_LABELS` constants contain every supported key and the default English text.
The task-editor dialog inherits `gantt.labels.taskEditor`. A dialog-specific `localeText` option remains supported and takes precedence over the inherited project labels, which allows one editor instance to override selected text without duplicating the rest of the project locale.
## Date Formatting
Managed Gantt date columns display a localized medium date by default (for example, `Apr 6, 2026` with the default `en-US` locale). This applies consistently to Start, End, Actual Start/Finish, Early/Late Start/Finish, Constraint Date, and Deadline. ISO date-time task values such as `2026-04-06T09:00:00.000Z` remain unchanged in your data; only their cell display is formatted, and the default table display intentionally omits the time.
Set `gantt.dateFormats.locale`, `timeZone`, and `table` to choose your own project-wide table format. `table` accepts standard `Intl.DateTimeFormatOptions`:
```ts
grid.gantt = {
...project,
dateFormats: {
locale: 'en-GB',
timeZone: 'Europe/London',
table: {
day: '2-digit',
month: '2-digit',
year: 'numeric',
},
},
};
```
For field-specific or application-defined output, provide a formatter callback. The context includes the managed field name, locale, time zone, and surface:
```ts
grid.gantt = {
...project,
dateFormats: {
table: (value, context) => {
const date = new Date(value.length === 10 ? `${value}T00:00:00Z` : value);
const prefix = context.field === 'deadlineDate' ? 'Due ' : '';
return `${prefix}${new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: 'short',
year: 'numeric',
timeZone: context.timeZone,
}).format(date)}`;
},
},
};
```
Use `dateFormats.tooltip` and `dateFormats.timeline` for those surfaces. If editor text must use a non-ISO format, pair `dateFormats.editor.formatter` with `dateFormats.editor.parser` (or the shared `dateFormats.parser`) so edits are converted back to ISO date or UTC ISO date-time values before scheduling.
## Adaptive Mobile Interactions
Gantt automatically adapts pointer interactions for phones, tablets, and other coarse-pointer devices. There is no mobile-only configuration flag; the same `GanttPluginConfig` works across desktop and touch devices.
Mobile behavior is designed around **select-then-edit**:
- Mouse and trackpad users keep direct desktop interactions for moving, resizing, progress updates, dependency drawing, timeline panning, and split-panel resizing.
- Touch users tap a task bar first to select it and reveal larger resize, progress, dependency, and split handles.
- Dragging a selected task bar moves the task. Dragging a visible handle resizes, updates progress, edits splits, or creates dependencies.
- Plain touch scrolling in the task table or timeline remains scroll-first and should not move, resize, or create tasks accidentally.
- Hidden coarse-pointer handles are not hit-testable until the task is selected or interaction-active, so an edge swipe on an unselected task still behaves like navigation.
- Task editor dialogs switch to a single-column, full-viewport layout on narrow screens with larger controls for touch input.
The Gantt E2E suite includes a dedicated mobile project using an iPhone viewport. It verifies that unselected task-bar swipes do not mutate dates, tapping selects a task and exposes handles, and the task editor dialog fits a phone-sized viewport.
Canonical definitions:
- `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`
For generated signatures, see [API: Gantt](/api/gantt/).
---
# Gantt Capability Coverage
URL: https://pro.rv-grid.com/guides/gantt/reference/coverage/
Source: src/content/docs/guides/gantt/reference/coverage.mdx
Description: Coverage matrix connecting supported Gantt capabilities to customer guides, public API areas, and runnable examples.
Surface: gantt
Use this matrix to move from a supported capability to its explanation, public contract, and—where behavior is visual or interactive—a runnable example. A missing example means the capability is configuration or integration guidance that does not benefit from a separate visual demo.
The matrix covers capabilities documented as supported by the current `@revolist/gantt` package. It deliberately does not imply support for capabilities that exist only in other Gantt products.
---
# Events And Methods
URL: https://pro.rv-grid.com/guides/gantt/reference/events-methods/
Source: src/content/docs/guides/gantt/reference/events-methods.mdx
Description: Use Gantt lifecycle events to validate changes and call public plugin methods for tasks, zoom, baselines, hierarchy, and snapshots.
Surface: gantt
['Project lifecycle', 'applyGantt|clearGantt|getProjectSnapshot'],
['Zoom and range', 'zoomIn|zoomOut|setZoomLevel|getZoomLevel|canZoomIn|canZoomOut|getVisibleRange'],
['Tasks', 'createTask|updateTask|finalizeTaskCreate|discardTaskCreate'],
['Hierarchy', 'indentTask(s)|outdentTask(s)|deleteTask(s)|convertTask(s)ToMilestones'],
['Baselines', 'captureBaseline'],
];
['gantt-before-task-change', 'Before create, move, resize, edit, progress, split, indent, outdent, delete, or milestone conversion.'],
['gantt-before-dependency-change', 'Before dependency create, delete, edit, or set replacement.'],
['gantt-before-assignment-change', 'Before task assignments are edited.'],
];
## Get The Plugin
Outside plugin code, locate the runtime through the grid's plugin collection.
```ts
import { GanttPlugin } from '@revolist/gantt';
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin);
```
Do not install reverse callbacks or custom methods directly on the grid element.
## Public Method Groups
Use the [Gantt API](/api/gantt/) for exact signatures and return types.
## Cancelable Before-Change Events
Call `preventDefault()` to reject a proposed packaged mutation.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (!canEditTask(event.detail.taskId, event.detail.action)) {
event.preventDefault();
}
});
```
## Collect Committed Changes
Gantt publishes successful task, dependency, and assignment mutations through EventManager's existing `gridedit` event. This gives persistence and audit integrations one committed-change stream for grid edits and Gantt interactions such as moving, resizing, changing progress, editing hierarchy, and deleting tasks.
```ts
grid.addEventListener('gridedit', (event) => {
for (const change of event.detail.domainChanges ?? []) {
if (change.type === 'gantt-task') {
if (change.detail.task) saveTask(change.detail.task);
}
}
});
```
Use `event.detail.eventTypes` to detect `gantt-task-change`, `gantt-dependency-change`, or `gantt-assignment-change`. `domainChanges` preserves the committed changes in order and includes previous and current domain state. Bulk operations and scheduler cascades include every affected task.
`data`, `previousData`, and `models` keep the normal grid row/field shape. Stable task-row updates appear there exactly like other grid edits; dependencies, assignments, creates, deletes, and reorders that do not have a safe row coordinate stay in `domainChanges`. Replay-only project snapshots are stored separately in `history` and are never mixed into grid fields.
Cancellation belongs to the corresponding `gantt-before-*-change` event. A Gantt change in `gridedit` has already committed, so calling `preventDefault()` there does not roll it back. When EventManager collection is enabled, consecutive Gantt mutations are emitted as one collected transaction and one history step.
## Commands And Notifications
The grid also uses typed events for task creation/edit requests, hierarchy commands, zoom, search, dependency hover/selection, task-created notification, and panel resize. Prefer exported event constants when code must be refactor-safe.
```ts
import {
GANTT_TASK_CREATE_EVENT,
GANTT_ZOOM_IN_EVENT,
} from '@revolist/gantt';
grid.dispatchEvent(new CustomEvent(GANTT_TASK_CREATE_EVENT));
grid.dispatchEvent(new CustomEvent(GANTT_ZOOM_IN_EVENT));
```
Application listeners should update or validate project data, not query rendered task-bar geometry. See [Application State](/guides/gantt/features/application-state/) for persistence ownership.
---
# Limitations And MSP Differences
URL: https://pro.rv-grid.com/guides/gantt/reference/limitations/
Source: src/content/docs/guides/gantt/reference/limitations.mdx
Description: Current Gantt scheduling boundaries and intentional differences.
Surface: gantt
Implemented behavior is MSP-inspired, not full MSP parity.
Current boundaries:
- Auto-leveling is forward-schedule oriented; backward mode stays warning-only.
- Leveling is delay-based and does not auto-insert new split ranges.
- Constraint and validation coverage is deterministic but narrower than full MSP option matrix.
- Docs describe current code behavior in `packages/gantt/src/gantt`, not aspirational features.
---
# Troubleshooting
URL: https://pro.rv-grid.com/guides/gantt/reference/troubleshooting/
Source: src/content/docs/guides/gantt/reference/troubleshooting.mdx
Description: Common Gantt setup and scheduling issues.
Surface: gantt
Common checks:
- Missing `ganttCalendars` or mismatched `calendarId`.
- Duplicate IDs in tasks/dependencies/resources/assignments.
- Dependency points to missing task ID.
- A circular dependency chain such as `Setup load balancer -> Configure firewall -> Setup load balancer`. The scheduler reports this as `dependency-cycle`; inspect the issue's `taskIds` and `dependencyIds`, then remove or change one of the links.
- Invalid task date ranges or malformed constraints.
- No visible timeline because viewport/zoom span does not include task dates.
## Task Jumps While Dragging
If a task appears to jump during drag or resize, first check for dependency validation issues. Automatic tasks normally clamp to the earliest date allowed by their predecessors. A circular dependency cannot produce a valid predecessor order, so RevoGrid reports `dependency-cycle` and leaves dependency clamping disabled until the cycle is fixed.
Use the scheduling result passed to your diagnostics flow to locate the invalid links:
```ts
const cycleIssue = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle');
if (cycleIssue) {
console.table({
tasks: cycleIssue.taskIds.join(', '),
dependencies: cycleIssue.dependencyIds.join(', '),
});
}
```
Validation and diagnostics are produced by:
- `engine/scheduler-engine/validation.ts`
- `engine/scheduler-types.ts`
---
# Gantt Upgrades
URL: https://pro.rv-grid.com/guides/gantt/releases/
Source: src/content/docs/guides/gantt/releases/index.mdx
Description: Upgrade guidance for standalone RevoGrid Gantt integrations.
Surface: gantt
Use the [Gantt migration guide](./migration/) when upgrading `@revolist/gantt`.
It covers package alignment, public imports, scheduling regression fixtures, application events, persistence, task-table columns, and timeline visuals.
The current public contract is documented in the [Gantt API reference](/api/gantt/). This documentation does not maintain release snapshots or generated API comparisons.
---
# Gantt Migration Guide
URL: https://pro.rv-grid.com/guides/gantt/releases/migration/
Source: src/content/docs/guides/gantt/releases/migration.mdx
Description: Upgrade a standalone RevoGrid Gantt integration using stable imports, project data, events, and application-level regression checks.
Surface: gantt
Use this checklist when upgrading `@revolist/gantt`.
1. Upgrade Gantt, Pro, the framework wrapper, and RevoGrid together.
2. Run the application against the documented package version shown on this page.
3. Replace repository-relative imports with the public package barrel.
4. Review the current [Gantt API reference](/api/gantt/) for exported contracts and deprecations.
5. Re-run schedule fixtures containing dependencies, calendars, constraints, resources, baselines, and manual tasks.
6. Verify cancelable `gantt-before-*` policies and persistence callbacks.
7. Check task-table columns and timeline visuals in each supported breakpoint.
```sh
pnpm up @revolist/gantt @revolist/revogrid-pro @revolist/revogrid
```
For a 1.x-to-2.x repository migration outside Gantt, also read the [general v2 migration guide](/guides/migration-v2/).
---
# Resource Planning View
URL: https://pro.rv-grid.com/guides/gantt/resources/resource-planning-view/
Source: src/content/docs/guides/gantt/resources/resource-planning-view.mdx
Description: Visualize resource-centric timelines and daily/weekly load bars.
Surface: gantt
The Resource Planning View switches the Gantt chart from a task-centric WBS to a resource-centric view. This allows managers to visualize daily or weekly load, capacity thresholds, and over-allocation hotspots.
Resource names include a compact avatar for faster scanning. Gantt derives a colored initials avatar from the resource name by default; supply `avatarUrl` on a resource to render its profile image instead.
## Enabling the Planning View
Use `gantt.resourcePlanning` to toggle the view and configure its behavior:
```typescript
grid.gantt = {
resourcePlanning: {
enabled: true,
visibleResourceIds: ['alex', 'nina', 'ravi'],
loadGranularity: 'day',
capacityDisplay: 'line',
overAllocationDisplay: 'highlight',
},
};
```
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex Morgan',
avatarUrl: '/images/team/alex-morgan.png',
role: 'Lead Engineer',
calendarId: 'standard',
allocationCapacity: 100,
hourlyCost: 120,
}];
```
### Configuration Options
| Option | Description |
|---|---|
| `enabled` | Toggle between task rows and resource load rows. |
| `visibleResourceIds` | Array of resource IDs to display in the planning view. |
| `loadGranularity` | `'day'` or `'week'`. The time bucket for load calculation. |
| `capacityDisplay` | `'line'` or `'none'`. Renders a capacity threshold on load bars. |
| `overAllocationDisplay` | `'highlight'` or `'none'`. Marks buckets exceeding capacity. |
## Resource Filtering
When you are in the normal task view, you can use `resourceFilterIds` to filter tasks assigned to specific resources while keeping ancestor summary tasks visible. This provides context-aware filtering within the project structure.
```typescript
grid.gantt = {
resourceFilterIds: ['alex'],
};
```
The Resource Planning View is highly dynamic. Changes to task dates or assignment units on the main grid are immediately reflected in the resource load bars.
---
# Resources and Assignments
URL: https://pro.rv-grid.com/guides/gantt/resources/resources-and-assignments/
Source: src/content/docs/guides/gantt/resources/resources-and-assignments.mdx
Description: Define resource capacity, assignments, and how they interact with task work.
Surface: gantt
Resources define the people, equipment, or facilities that perform work on your tasks. Assignments connect these resources to specific tasks with defined allocation units and work hours.
## Data Model
Resources and assignments are managed through two distinct collections:
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex',
role: 'Engineer',
calendarId: 'cal-standard',
allocationCapacity: 100, // 100% capacity
hourlyCost: 120,
}];
grid.ganttAssignments = [{
id: 'assign-1',
taskId: 'task-1',
resourceId: 'alex',
allocationUnits: 100, // 100% assignment
workHours: 40,
responsibility: 'Owner',
}];
```
### Capacity and Units
`allocationCapacity` and `allocationUnits` accept either a percent scale (`100`) or a decimal scale (`1.0`) for full capacity. The scheduling engine uses these values to calculate resource load and identify over-allocations.
## Resource Over-Allocation
When the `resourceLeveling` policy is set to `'warn'`, the scheduler calculates allocation by resource and working date. If assigned units exceed the resource's capacity on any given day, it reports a `resource-overallocation` conflict.
```typescript
grid.gantt = {
scheduling: { resourceLeveling: 'warn' },
};
```
### Example: Over-Allocation Warning
If one resource is assigned to multiple overlapping tasks at 100% each, a warning will be triggered:
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex',
allocationCapacity: 100,
}];
grid.ganttAssignments = [
{ taskId: 'build', resourceId: 'alex', allocationUnits: 100 },
{ taskId: 'docs', resourceId: 'alex', allocationUnits: 100 },
];
```
**Expected result**: On days where `build` and `docs` overlap, Alex is allocated at 200%. Each affected task receives a `resource-overallocation` warning in the grid and on the timeline.
---
## Next Steps
- **[Resource Planning View](../resource-planning-view)**: Visualize resource load on a dedicated timeline.
- **[Resource Leveling](../scheduling/leveling)**: Automatically resolve over-allocations by delaying tasks.
- **[Effort Modes](../scheduling/effort-modes)**: Control how assignments affect task duration.
---
# Calendars and Duration
URL: https://pro.rv-grid.com/guides/gantt/scheduling/calendars/
Source: src/content/docs/guides/gantt/scheduling/calendars.mdx
Description: Define working days, holidays, and how they affect task duration calculations.
Surface: gantt
`duration` is stored in hours and can be displayed or entered as minutes, hours, days, weeks, months, quarters, or years. Unit conversion uses `durationSettings` and the effective calendar; see [Duration Units and Conversion](/guides/gantt/scheduling/durations/). Enable calendar exclusion when weekends, holidays, and closed hours should be skipped:
```typescript
grid.gantt = {
scheduling: {
excludeHolidaysFromDuration: true,
},
};
```
## Defining Calendars
Calendars use ISO weekday numbers where Monday is `1` and Sunday is `7`:
```typescript
grid.ganttCalendars = [{
id: 'cal-standard',
name: 'Standard',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-05-25'],
hoursPerDay: 8,
}];
```
Each task references a calendar through `calendarId`. Resource capacity checks use the resource calendar when a resource is assigned.
`CalendarEntity.hoursPerDay` is the default conversion for task durations on that calendar. Project `durationSettings.hoursPerDay` can override it globally. Conversion settings do not change `workingDays`, `holidays`, or `workingHours` automatically.
## Working-Day Lag
Use working-day lag when dependency offsets should skip weekends and holidays:
```typescript
grid.gantt = {
scheduling: {
lagCalendar: 'working-days',
},
};
```
**Example**: A one-day lag after a Friday skips the weekend and a Monday holiday, landing on Tuesday.
Each task references a calendar via its `calendarId` field. Tasks on different calendars respect their own working days and holidays independently.
---
# MSP Compatibility
URL: https://pro.rv-grid.com/guides/gantt/scheduling/compatibility/
Source: src/content/docs/guides/gantt/scheduling/compatibility.mdx
Description: Understand how RevoGrid Gantt maps to Microsoft Project scheduling concepts and behavior.
Surface: gantt
The table below describes how RevoGrid Gantt maps to Microsoft Project (MSP) concepts. This ensures a familiar experience for planners transitioning from desktop project management tools.
| MSP concept | RevoGrid Gantt behavior |
|---|---|
| Manual vs automatic task mode | `taskMode: 'manual'` preserves authored dates and warns on dependency violations. Auto tasks are dependency-safe and are clamped to valid dates. |
| Scheduled Start / Finish | `startDate` and `endDate` in the rendered row are the scheduler's effective dates. For manual tasks, authored dates remain unless a warning is reported. |
| ASAP / ALAP | `scheduleFrom: 'project-start'` gives ASAP-style forward scheduling. `scheduleFrom: 'project-finish'` gives ALAP-style backward scheduling. |
| Dependencies | FS, SS, FF, and SF dependencies are supported. Positive `lagDays` delays a successor; negative `lagDays` is lead time. |
| Constraints | Six date constraints are supported: SNET, SNLT, FNET, FNLT, MSO, and MFO. |
| Deadlines | `deadlineDate` produces a warning indicator when a task finishes late. It does not move the task, matching MSP's deadline intent. |
| Resource over-allocation | `resourceLeveling: 'warn'` reports capacity violations. `resourceLeveling: 'auto'` performs deterministic delay-based leveling for eligible automatic tasks. |
| Effort/task type | `effortMode` supports `fixed-duration`, `fixed-work`, and `fixed-units`. Fixed-work and fixed-units can derive duration from work hours and assignment units. |
| Actuals and remaining duration | `actualFinishDate` pins completed work. `actualStartDate` and `remainingDuration` pin in-progress work when `progressRescheduling` is enabled. |
| Split tasks | `splitRanges` pause work inside a task span, render visual gaps, and extend the computed finish. |
| Critical path | Critical tasks, critical dependencies, early/late dates, and total slack are recomputed after all scheduling rules. |
Microsoft Project has more scheduling knobs than this implementation, including status-date rescheduling options, per-resource Can Level, elapsed duration syntax, and automatic split-based leveling. RevoGrid exposes a deterministic subset designed for web-driven scheduling.
## Reference Links
Useful Microsoft Project references for comparison:
- [Task Mode field](https://support.microsoft.com/en-gb/office/task-mode-task-field-3b185518-0e9a-4774-ba51-7b8d191bb84a)
- [Task constraints](https://support.microsoft.com/en-us/office/set-a-task-start-or-finish-date-constraint-for-a-task-3a7544fa-e992-4647-911b-54cdbe508784)
- [Predecessors field and lead/lag](https://support.microsoft.com/en-us/office/predecessors-task-field-5a5ea9c2-14e6-4be7-9a3d-d8b6cba10cab)
- [Deadline field](https://support.microsoft.com/en-us/office/deadline-task-field-8c6c4acd-1324-484a-a92b-9bd2eb313dfa)
- [Resource leveling](https://support.microsoft.com/en-us/office/resource-leveling-dialog-box-0d280b16-2753-4630-8cca-8c50915df9f5)
- [Task types and effort-driven scheduling](https://support.microsoft.com/en-us/office/change-the-effort-driven-setting-for-task-types-18efd7c7-d146-4b06-bdbe-b6a11564bdf3)
- [Actual Start and Actual Finish](https://support.microsoft.com/en-us/office/actual-start-fields-b1b833a7-07f5-4c4d-858d-f9261d1be7f7)
- [Split a task](https://support.microsoft.com/en-au/office/split-a-task-20c8581b-6266-45e3-af54-cc7c3b10deca)
- [Critical path](https://support.microsoft.com/en-us/office/show-the-critical-path-of-your-project-in-project-ad6e3b08-7748-4231-afc4-a2046207fd86)
---
# Task Constraints
URL: https://pro.rv-grid.com/guides/gantt/scheduling/constraints/
Source: src/content/docs/guides/gantt/scheduling/constraints.mdx
Description: Apply explicit date constraints like SNET, MSO, and MFO to control the scheduler's behavior.
Surface: gantt
Constraints are explicit fields on a task that override or refine the automatic forward-pass scheduling:
```typescript
{
id: 'integration',
name: 'External Integration',
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
// other task fields
}
```
## Supported Constraint Types
| Constraint | MSP equivalent | Behavior |
|---|---|---|
| `start-no-earlier-than` | SNET | Start on or after the constraint date. |
| `start-no-later-than` | SNLT | Start on or before the constraint date. |
| `finish-no-earlier-than` | FNET | Finish on or after the constraint date. |
| `finish-no-later-than` | FNLT | Finish on or before the constraint date. |
| `must-start-on` | MSO | Start exactly on the constraint date. |
| `must-finish-on` | MFO | Finish exactly on the constraint date. |
## Soft vs. Hard Constraints
### Soft Constraints (SNET, SNLT, FNET, FNLT)
These constraints define a legal window for the task. If the dependency propagation moves the task outside this window, the scheduler will:
- Adjust the date to the nearest legal bound (if possible).
- Report a `constraint-window-conflict` if the dependency and constraint windows do not overlap.
### Hard Constraints (MSO, MFO)
Hard constraints are authoritative. They pin the task to an exact date, even if it violates dependencies.
- If a dependency requires a different date, the scheduler reports a `hard-constraint-conflict` or `dependency-constraint-conflict`.
- The task date is NOT moved to satisfy the dependency; the constraint wins.
Microsoft Project also exposes flexible ASAP and ALAP as constraint types. RevoGrid models those at project level with `scheduleFrom`, not as per-task `constraintType` values.
---
# Critical Path and Slack
URL: https://pro.rv-grid.com/guides/gantt/scheduling/critical-path/
Source: src/content/docs/guides/gantt/scheduling/critical-path.mdx
Description: Automatically compute and highlight the critical path — the longest chain of tasks that determines the project end date.
Surface: gantt
The **critical path** is the chain of tasks that controls the calculated project finish date.
In Microsoft Project terms, critical tasks usually have zero total slack: if one slips, the project finish slips.
The Gantt plugin computes this path from the final scheduled graph and highlights it visually.
---
## Enabling Critical Path
Set `showCriticalPath: true` in the `visuals` section of your project config:
```typescript
grid.gantt = {
visuals: {
showCriticalPath: true,
},
};
```
Critical tasks and the dependency arrows connecting them are highlighted in a distinct color (red by default).
## How It Works
The scheduler computes critical path after all scheduling rules (dependencies, constraints, actuals, etc.) have been applied.
It runs a backward pass over the graph to compute:
- **Early Start / Early Finish**: The earliest each task can begin and end.
- **Late Start / Late Finish**: The latest each task can begin and end without delaying the project.
- **Total Slack** = Late Start − Early Start.
| Field | Meaning |
|---|---|
| `earlyStartDate` | Effective scheduled start after all rules are applied. |
| `lateStartDate` | Latest date the task can occupy without delaying the project finish. |
| `totalSlackDays` | Working-day float between early and late dates. |
| `isCritical` | `true` when total slack is zero. |
Summary tasks are treated as critical when at least one child task is critical. Milestone tasks with no slack also appear on the critical path.
## Dependency Types
All four dependency types (FS, SS, FF, SF) participate in critical path analysis. Lags (`lagDays`) are also factored in. A positive lag delays the successor, while a negative lag (lead time) can reduce the path length.
## MSP Comparison
RevoGrid follows the Microsoft Project model: dependencies and final scheduled dates drive total slack, and tasks with zero slack become critical.
Deadlines and resource leveling also influence the critical path by shifting dates or changing risk profiles.
## Critical State Is Projected
Do not set `isCritical` in `grid.source`. Gantt source rows describe authored task data, while `isCritical`, slack fields, early/late dates, and critical dependency styling are projected by the scheduler on each render.
If an application needs to persist critical-path state, persist the scheduling inputs instead: tasks, dependencies, constraints, calendars, assignments, and scheduling options. RevoGrid will recompute the critical path from those inputs.
## CSS Customization
Critical path styling uses data attributes on bar and arrow elements:
```css
/* Critical task bar */
.gantt-bar[data-critical] {
--gantt-bar-color: #e53e3e;
}
/* Critical dependency arrow */
.gantt-dependency[data-critical] {
stroke: #e53e3e;
}
```
See the [Critical path analysis demo](https://demo.rv-grid.com/gantt-critical-path-analysis/ts) for a live example.
---
# Deadlines
URL: https://pro.rv-grid.com/guides/gantt/scheduling/deadlines/
Source: src/content/docs/guides/gantt/scheduling/deadlines.mdx
Description: Use deadlineDate to track target dates without constraining the scheduling engine.
Surface: gantt
Use `deadlineDate` for target dates that should warn but not lock the schedule:
```typescript
{
id: 'frontend',
name: 'Development',
deadlineDate: '2026-05-15',
// other task fields
}
```
## Behavior
If a task finishes after its `deadlineDate`, the scheduler reports a `deadline-missed` conflict. This warning appears in:
- The **Scheduling Warning** column.
- The task **tooltip**.
- The row/bar **warning state**.
- A visual **timeline indicator** (usually a down-arrow on the deadline date).
## Deadline vs. Finish Constraint
Unlike a `must-finish-on` or `finish-no-later-than` constraint, a deadline **never moves the task**.
- It is purely an analytical tool to measure performance against a target.
- It does not affect the critical path or slack calculations of other tasks.
This matches Microsoft Project's distinction between a deadline and an inflexible finish constraint: a deadline is an alert, not a scheduling rule.
---
# Scheduling Direction
URL: https://pro.rv-grid.com/guides/gantt/scheduling/direction/
Source: src/content/docs/guides/gantt/scheduling/direction.mdx
Description: Configure forward (ASAP) or backward (ALAP) scheduling for your Gantt project.
Surface: gantt
Forward scheduling is the default and behaves like Microsoft Project's ASAP (As Soon As Possible) direction. Automatic tasks are placed as early as their dependencies and constraints allow.
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-start',
projectStartDate: '2026-04-06',
},
};
```
When `projectStartDate` is set, automatic root tasks start from that anchor unless a dependency or constraint moves them later.
## Backward Scheduling (ALAP)
Backward scheduling behaves like Microsoft Project's ALAP (As Late As Possible) project-finish direction:
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-finish',
projectFinishDate: '2026-06-30',
},
};
```
Automatic tasks without successors are placed as late as possible against `projectFinishDate`; predecessors are pulled earlier to satisfy outgoing dependencies. If `projectFinishDate` is omitted, the scheduler uses the latest task finish in the current data.
## Example: Project Finish Scheduling
Use project-finish scheduling for ALAP-style plans:
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-finish',
projectFinishDate: '2026-04-30',
},
};
grid.source = [
{
id: 'build',
name: 'Build',
duration: 3,
},
{
id: 'release',
name: 'Release',
duration: 1,
},
];
grid.ganttDependencies = [{
id: 'build-release',
predecessorTaskId: 'build',
successorTaskId: 'release',
type: 'finish-to-start',
lagDays: 0,
}];
```
**Expected result**: `release` is placed as late as possible against `2026-04-30`; `build` is pulled earlier so the FS relationship remains valid.
See the [Project-finish ALAP demo](https://demo.rv-grid.com/gantt-alap/ts) for a visible backward-scheduled chain anchored to a project finish date.
---
# Duration Units and Conversion
URL: https://pro.rv-grid.com/guides/gantt/scheduling/durations/
Source: src/content/docs/guides/gantt/scheduling/durations.mdx
Description: Enter, retain, format, and schedule task durations in minutes, hours, days, weeks, months, quarters, or years.
Surface: gantt
Gantt stores `duration`, `remainingDuration`, and task `workHours` in one canonical unit: **hours**. `durationUnit` controls Duration, Remaining Duration, and Duration Variance; `workUnit` independently controls Work. These unit fields affect display and editing, not storage. Different tasks can therefore retain different display units in the same column:
```ts
grid.source = [
{ id: 'review', name: 'Review', startDate: '2026-04-06T09:00:00Z', duration: 5, durationUnit: 'hour', workHours: 16, workUnit: 'day' }, // 5 hours, 2 days Work
{ id: 'build', name: 'Build', startDate: '2026-04-07', duration: 16, durationUnit: 'day', workHours: '40h' }, // 2 days, 40 hours Work
{ id: 'release', name: 'Release train', startDate: '2026-04-09', duration: 120, durationUnit: 'week' }, // renders 3 weeks
{ id: 'handoff', name: 'Handoff', startDate: '2026-04-30T16:00:00Z', duration: 0.5, durationUnit: 'minute' }, // renders 30 minutes
];
```
The managed Duration, Remaining Duration, Duration Variance, Work, tooltip, task editor, and Excel export surfaces include the unit label, with singular/plural output such as `1 hour`, `5 hours`, `1 day`, and `2 days`.
## DurationUnit
```ts
type DurationUnit =
| 'minute'
| 'hour'
| 'day'
| 'week'
| 'month'
| 'quarter'
| 'year';
```
Minutes are supported. Persist new numeric data as canonical hours and include `durationUnit` when a particular display unit must be retained. At source hydration only, legacy unitless numeric values remain supported as day-based values (or the configured fixed default); hydrated task-store data is converted to hours.
When `durationUnit` is omitted and `defaultUnit` is `auto` (the default), the table chooses a readable display unit: values below one working hour use minutes, values below one working day use hours, and longer values use days. Work follows the same rule through `workUnit` and `defaultWorkUnit`. A unit entered by the user is retained, so `5 hrs` and `2 days` can coexist.
## Flexible input
The inline grid editor and Task Information editor accept a number followed by a short or long unit:
| Input | Meaning |
|---|---|
| `30m`, `30 min`, `30 mins` | 30 working minutes |
| `5h`, `5 hr`, `5 hrs` | 5 working hours |
| `2d`, `2 days` | 2 working days |
| `3w`, `3 wk`, `3 wks` | 3 working weeks |
| `2mo`, `2 months` | 2 working months |
| `1q`, `1 quarter` | 1 working quarter |
| `1y`, `1 year` | 1 working year |
A bare Duration or Remaining Duration edit uses the task's retained `durationUnit`, then `defaultUnit`, then days. A bare Work edit uses `workUnit`, then `defaultWorkUnit`, then hours. Every accepted edit is converted to hours before the task store is updated. Invalid or negative text is rejected. Work is person-time, so elapsed forms such as `2ed` are rejected for Work even though they are valid for task Duration.
## Conversion settings
Set project-wide conversion values with `gantt.durationSettings`:
```ts
grid.gantt = {
...project,
durationSettings: {
defaultUnit: 'auto', // or fix: 'minute' | 'hour' | 'day' | ...
defaultWorkUnit: 'auto', // independent fixed/automatic Work unit
hoursPerDay: 8,
hoursPerWeek: 40,
daysPerMonth: 20,
maximumFractionDigits: 1, // default MSP-style compact precision
},
};
```
With these settings:
- `1 day = 8 hours`
- `1 week = 40 hours = 5 working days`
- `1 month = 20 working days`
- `1 quarter = 3 working months`
- `1 year = 12 working months`
`defaultUnit` and `defaultWorkUnit` can be fixed independently, matching the distinction between task span and person-work. Both default to `auto`. `hoursPerDay` defaults to the effective task calendar's `hoursPerDay`, then 8. `hoursPerWeek` defaults to `hoursPerDay × workingDays.length`, commonly 40. `daysPerMonth` defaults to 20.
`maximumFractionDigits` controls rendered precision for Duration, Remaining Duration, Duration Variance, and Work. It defaults to `1`, trims trailing zeroes, and only affects presentation: `56.625 days` renders as `56.6 days`, while canonical stored hours are unchanged. Set it to `0` for whole units or a larger integer when a project needs more visible precision.
Changing `hoursPerDay` or `hoursPerWeek` changes conversion at edit, render, and scheduling boundaries. It does not rewrite stored hours and does not redefine the calendar's `workingDays`, holidays, or `workingHours`; configure those separately on `CalendarEntity`.
## Working time and elapsed time
Normal units consume working time. With an 8-hour day, `2d` is normally 16 working hours. When `scheduling.excludeHolidaysFromDuration` is enabled, the scheduler also skips closed weekdays, holidays, and configured intraday working-hour gaps.
Elapsed input uses an `e` prefix and runs continuously, ignoring the working calendar:
```ts
{ duration: 48, durationUnit: 'day', durationIsElapsed: true } // renders 2 elapsed days
```
The same value can be entered as `2ed` or `2 elapsed days`. `2d` means two working days; `2ed` means 48 continuous hours and can cross nights, weekends, and holidays. Elapsed aliases are supported for every `DurationUnit`, such as `30em`, `4eh`, and `1ew`.
`durationIsElapsed` affects scheduling, not only formatting. Use it only for work that genuinely continues while the project calendar is closed.
## Summary tasks
Summary duration is derived from the earliest scheduled child start to the latest scheduled child finish. It is a span, not the sum of child durations. Summary Duration cells remain read-only even when their children use mixed units.
## Milestones and zero duration
Setting a task's Duration to `0` schedules and renders it as a milestone. Unlike a derived summary Duration, the milestone Duration cell remains editable. Entering any positive value, such as `4h` or `2d`, stores the converted canonical hours and changes the task type back to `task`. The same reversible behavior applies in the Task Information dialog.
## Programmatic helpers
The standalone Gantt package exports shared date-layer helpers used by source hydration, scheduling, editors, and projection. Duration parsing, conversion, and formatting live in one module, so every integration follows the same canonical-hour rules:
```ts
import {
parseDuration,
formatDurationValue,
durationToHours,
hoursToDuration,
durationToElapsedHours,
elapsedHoursToDuration,
resolveDurationSettings,
} from '@revolist/gantt';
```
Use these helpers in custom editors or persistence adapters instead of implementing a second unit conversion table.
---
# Effort Modes
URL: https://pro.rv-grid.com/guides/gantt/scheduling/effort-modes/
Source: src/content/docs/guides/gantt/scheduling/effort-modes.mdx
Description: Model task types (Fixed Duration, Fixed Work, Fixed Units) to control how work and duration interact.
Surface: gantt
['fixed-duration', 'Preserve duration. Work and units do not recalculate duration.'],
['fixed-work', 'Preserve workHours and derive duration from assigned units and calendar.'],
['fixed-units', 'Preserve assignment units and derive duration from workHours and units.'],
];
Use `effortMode` to model Microsoft Project-style task types:
`workHours` is always stored as canonical hours. Set `workUnit` to retain a task-specific display unit, or enter Work as text such as `5h`, `2d`, or `3w`. Project `durationSettings.defaultWorkUnit` can fix the default Work unit independently from Duration.
## Formula
The duration formula for fixed-work/fixed-units is:
```text
duration = ceil(workHours / (hoursPerDay * totalAssignmentUnits))
```
## Example: Task Types
```typescript
grid.source = [
{
id: 'standup',
name: 'Standup',
effortMode: 'fixed-duration',
duration: 1,
workHours: 2,
},
{
id: 'implementation',
name: 'Implementation',
effortMode: 'fixed-work',
workHours: 32,
workUnit: 'hour',
},
];
grid.ganttAssignments = [
{
taskId: 'implementation',
resourceId: 'alex',
allocationUnits: 100,
},
];
```
**Expected result**: `standup` keeps its one-day duration. `implementation` becomes four days (32h / 8h/day) with one 100% assignment.
See the [Progress and work demo](https://demo.rv-grid.com/gantt-progress-work/ts) for more examples.
---
# Engine Logic
URL: https://pro.rv-grid.com/guides/gantt/scheduling/engine/
Source: src/content/docs/guides/gantt/scheduling/engine.mdx
Description: Deep dive into the RevoGrid Gantt scheduling engine pipeline and conflict resolution.
Surface: gantt
['High', 'Actuals', "actualStartDate and actualFinishDate pin the task; the engine won't move them."],
['High', 'Hard Constraints', 'MSO / MFO dates win over dependencies; conflict reported.'],
['Medium', 'Dependencies', 'Auto-tasks are clamped to satisfy predecessors.'],
['Medium', 'Soft Constraints', 'Dates are adjusted to bounds where possible; conflict reported if windows are disjoint.'],
['Low', 'Authored Dates', 'Used as the starting point for root tasks or manual tasks.'],
];
The RevoGrid Gantt scheduling engine follows a deterministic pipeline to compute effective dates for all tasks. It processes data changes in five distinct phases to ensure dependency safety and constraint satisfaction.
## The 5-Step Pipeline
### 1. Topological Sort
The engine first builds a dependency graph and performs a topological sort. This ensures that tasks are ordered such that every predecessor is processed before its successors.
- Summary tasks are handled separately to ensure they can roll up dates from their children.
- Circular dependencies are detected and reported as `dependency-cycle` issues with the involved task and dependency IDs.
When the active dependency graph contains a cycle, the engine treats the project as structurally invalid schedule data. It does not try to calculate a dependency-valid order through the loop, and task drag/resize does not clamp against predecessor links until the cycle is resolved.
### 2. Forward Pass (ASAP)
In the default `scheduleFrom: 'project-start'` mode, the engine performs a forward pass:
- Root tasks (those without predecessors) are anchored to the `projectStartDate`.
- Successors are shifted forward to satisfy their incoming dependencies (FS, SS, FF, SF) plus any `lagDays`.
- The engine uses the successor's calendar to compute working-day offsets if `lagCalendar: 'working-days'` is enabled.
### 3. Constraint Application
Once the dependency-driven dates are known, the engine applies per-task constraints:
- **Soft Constraints**: SNET, SNLT, etc., refine the start date if the dependency date falls outside the constraint bound.
- **Hard Constraints**: `must-start-on` and `must-finish-on` override the forward pass results. If a hard constraint violates a dependency, a conflict is reported, but the constraint date wins.
### 4. Summary Rollup
After all leaf tasks have their dates resolved, the engine rolls up the schedule to summary (parent) tasks:
- A summary task's `startDate` is the minimum of its children's `startDate`.
- A summary task's `endDate` is the maximum of its children's `endDate`.
- Rollup is recursive, traversing the entire WBS (Work Breakdown Structure) tree.
### 5. Critical Path Analysis
The final phase computes the project's early and late dates using a backward pass from the project finish date:
- **Total Slack**: The number of days a task can be delayed without delaying the project finish.
- **Critical Path**: Tasks with zero total slack are marked as `isCritical`.
- This analysis helps planners identify the "chain" of tasks that determines the total project duration.
## Conflict Resolution
The engine prioritizes user intent and data integrity when scheduling rules collide:
The scheduling engine runs synchronously on each render to provide immediate feedback during drag-and-drop or inline editing.
---
# Resource Leveling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/leveling/
Source: src/content/docs/guides/gantt/scheduling/leveling.mdx
Description: Use automatic resource leveling to resolve over-allocations by delaying tasks.
Surface: gantt
Enable deterministic auto-leveling with:
```typescript
grid.gantt = {
scheduling: {
resourceLeveling: 'auto',
resourceLevelingWithinSlack: false,
},
};
```
Auto-leveling delays later overlapping automatic tasks until the resource is available, then reports `resource-leveling-adjustment`.
## Leveling Controls
Per-task leveling controls:
```typescript
const task = {
priority: 900, // 0-1000; higher priority is protected first
canLevel: true, // false keeps the task fixed during auto-leveling
levelingDelayDays: 2, // stored calendar-day delay applied by leveling
};
```
**Deterministic auto-leveling order**:
1. Non-levelable tasks first (fixed blockers).
2. Higher `priority` before lower `priority`.
3. Earlier start date, then task id.
## Constraints and Actuals
The leveling implementation avoids moving:
- Manually scheduled tasks.
- Tasks with `actualStartDate` or `actualFinishDate`.
- Summary tasks.
- Inactive tasks.
- Tasks pinned by `must-start-on` or `must-finish-on`.
Microsoft Project leveling can delay or split tasks. RevoGrid leveling is deterministic and delay-based; it supports priority and slack-limited leveling but does not split tasks automatically.
---
# Manual Scheduling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/manual/
Source: src/content/docs/guides/gantt/scheduling/manual.mdx
Description: Use taskMode to lock dates and override automatic dependency propagation.
Surface: gantt
Set `taskMode` on each task, or use `taskModeDefault` for tasks that omit it:
```typescript
{
id: 'kickoff',
name: 'Fixed Kickoff Event',
taskMode: 'manual',
startDate: '2026-04-20',
endDate: '2026-04-20',
duration: 0,
// other task fields
}
```
Automatic tasks are dependency-safe. Dragging, resizing from the start edge, or editing an automatic successor before its predecessor allows will clamp the effective schedule to the dependency-valid date.
Manual tasks keep authored dates. If the manual start violates an active dependency, the scheduler reports `manual-dependency-violation` and the row/bar renders a scheduling warning indicator.
Microsoft Project's Task Mode field defaults new tasks to manually scheduled in some desktop versions. RevoGrid defaults to automatic scheduling because dependency safety is the default expectation for this Gantt plugin. Set `taskModeDefault: 'manual'` to make omitted task modes behave manually.
## Dependency Violations
When a manual task is placed at a date that violates an incoming dependency (e.g., starts before a FS predecessor finishes), the scheduler does not move the task. Instead, it:
1. Records a `manual-dependency-violation` conflict.
2. Displays a warning icon in the "Scheduling Warning" column.
3. Highlights the task bar with warning styling.
This allows planners to maintain "best guess" or "negotiated" dates while still seeing where the logical project structure is broken.
---
# Overview Scheduling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/overview/
Source: src/content/docs/guides/gantt/scheduling/overview.mdx
Description: Configure MSP-style scheduling with task modes, dependencies, constraints, calendars, deadlines, resources, leveling, actuals, and critical path.
Surface: gantt
['manual-dependency-violation', 'A manual task starts before an active dependency allows.'],
['dependency-constraint-conflict', 'A dependency cannot be satisfied because an explicit constraint wins.'],
['hard-constraint-conflict', 'A must-start/must-finish constraint overrides dependencies.'],
['constraint-window-conflict', 'Soft constraint and dependency windows do not overlap.'],
['deadline-missed', 'A task finishes after deadlineDate.'],
['resource-overallocation', 'A resource exceeds capacity on a scheduled working date.'],
['resource-leveling-adjustment', 'Auto-leveling shifted a task to remove over-allocation.'],
];
The Gantt scheduler computes effective task dates from authored dates, duration, task mode, dependencies, lag/lead, calendars, constraints, deadlines, resources, actuals, split ranges, and optional resource leveling.
Its compatibility target is Microsoft Project scheduling behavior: automatic tasks are dependency-safe, manual tasks preserve authored dates but show warnings, deadlines warn without constraining, and leveling can delay eligible work.
---
## Scheduling Configuration
Configure scheduling policies under `grid.gantt.scheduling`:
```typescript
grid.gantt = {
scheduling: {
taskModeDefault: 'auto',
scheduleFrom: 'project-start',
projectStartDate: '2026-04-06',
excludeHolidaysFromDuration: true,
lagCalendar: 'working-days',
manualDependencyViolationBehavior: 'warn',
resourceLeveling: 'warn',
effortModeDefault: 'fixed-duration',
progressRescheduling: 'remaining-duration',
},
};
```
Definition source: `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`.
## Scheduling Warnings
Warnings are reported as scheduler conflicts and projected into rows:
## Performance
Large-project scheduler performance is covered by the Gantt benchmark suite:
```bash
pnpm --filter @revolist/gantt bench
```
The benchmark exercises 1,000 and 3,000 task snapshots with dense dependencies and auto-leveling.
---
## Next Steps
Dive deeper into specific scheduling topics:
- **[MSP Compatibility](../compatibility)**: How RevoGrid maps to Microsoft Project.
- **[Engine Logic](../engine)**: The 5-step pipeline and conflict resolution.
- **[Scheduling Direction](../direction)**: Forward (ASAP) vs. Backward (ALAP).
- **[Calendars & Duration](../calendars)**: Working days, holidays, and lag calendars.
- **[Dependencies](../concepts/dependencies)**: FS, SS, FF, SF and lag/lead.
- **[Task Modes](../manual)**: Manual vs. Automatic scheduling.
- **[Constraints](../constraints)**: SNET, MSO, MFO and soft/hard rules.
- **[Deadlines](../deadlines)**: Target dates and missed deadline warnings.
- **[Resources & Assignments](../resources/resources-and-assignments)**: Resource capacity and assignments.
- **[Resource Planning View](../resources/resource-planning-view)**: Resource-centric timeline view.
- **[Resource Leveling](../leveling)**: Auto-leveling logic and priorities.
- **[Effort Modes](../effort-modes)**: Fixed work, duration, and units.
- **[Actuals & Progress](../progress)**: Tracking real-world status.
- **[Split Tasks](../split-tasks)**: Managing work gaps.
- **[Critical Path](../critical-path)**: Slack and schedule analysis.
---
# Actuals and Progress
URL: https://pro.rv-grid.com/guides/gantt/scheduling/progress/
Source: src/content/docs/guides/gantt/scheduling/progress.mdx
Description: Track project progress using actual start/finish dates and remaining duration.
Surface: gantt
Use actual fields to reflect progress:
```typescript
{
id: 'qa',
name: 'QA',
actualStartDate: '2026-04-08',
remainingDuration: 2,
percentDone: 50,
}
```
## Calculated Status and Status Date
Set `gantt.statusDate` to the reporting date used by the read-only **Status** and **Status Indicator** columns. It defaults to today's ISO date when omitted and affects reporting only; it does not move tasks or timeline lines.
```ts
grid.gantt = {
...project,
statusDate: '2026-04-08',
};
```
The calculated values are:
- `Complete` when `% Done` is 100 or Actual Finish exists.
- `Future Task` when the scheduled start is after the Status Date.
- `On Schedule` when linearly distributed reported completion covers the effective planned work through the day before the Status Date.
- `Late` when work has started or should have progressed but reported completion does not reach that boundary.
The comparison uses the effective task calendar, holidays, split ranges, and elapsed-duration rules. Incomplete milestones are Late before the Status Date, On Schedule on it, and Future Task after it. Summary tasks use rolled-up dates and progress. Because Gantt stores aggregate `% Done` rather than timephased actual work, completion is distributed linearly over the effective scheduled work span.
Use the editable **Workflow Status** field for `not-started`, `in-progress`, `done`, or `blocked`. Workflow Status never drives calculated Status or scheduling.
The default **Status**, **Status Indicator**, and **Workflow Status** columns use
the advanced selection filter. Their filter options reuse the same managed badge
or indicator template as the task-table cell, including projected row metadata.
## Percent Done column mode
The managed `% Done` column uses the Pro progress renderers and defaults to a horizontal `bar`. Choose the project default with `gantt.visuals.percentDoneMode`:
```ts
grid.gantt = {
...project,
visuals: {
percentDoneMode: 'circle',
},
};
```
Override that default for an individual authored column with its `mode` property:
```ts
grid.columns = [
{ prop: 'name', name: 'Task' },
{ prop: 'percentDone', name: '% Done', mode: 'number' },
];
```
| Mode | Cell display |
|---|---|
| `number` | Percentage text such as `40%`. |
| `bar` | Horizontal progress bar plus percentage. This is the default. |
| `circle` | Circular progress indicator with the percentage centered inside. |
The column `mode` takes precedence over `visuals.percentDoneMode`. Both change presentation only. Values remain numbers from `0` through `100`; sorting, filtering, editing, scheduling, task-bar progress, and Excel export continue to use `percentDone`. A custom `cellTemplate` supplied on the `% Done` grid column overrides either managed mode.
## Progress-Aware Scheduling
Enable progress-aware scheduling:
```typescript
grid.gantt = {
scheduling: {
progressRescheduling: 'remaining-duration',
},
};
```
When enabled:
- `actualStartDate` anchors in-progress work.
- `remainingDuration` replaces the planned duration for the remaining span.
- `actualFinishDate` pins completed work to the recorded finish.
- The schedule origin is shown as `Actual`.
## Example: Actuals and Remaining Duration
```typescript
grid.gantt = {
scheduling: { progressRescheduling: 'remaining-duration' },
};
grid.source = [
{
id: 'in-progress',
startDate: '2026-04-01',
duration: 10,
actualStartDate: '2026-04-03',
remainingDuration: 2,
},
{
id: 'complete',
startDate: '2026-04-01',
duration: 10,
actualStartDate: '2026-04-02',
actualFinishDate: '2026-04-04',
},
];
```
**Expected result**: `in-progress` is anchored on `2026-04-03` and schedules 2 remaining days. `complete` is pinned to its actual dates.
---
# Split Tasks
URL: https://pro.rv-grid.com/guides/gantt/scheduling/split-tasks/
Source: src/content/docs/guides/gantt/scheduling/split-tasks.mdx
Description: Pause work inside a task span using split ranges.
Surface: gantt
Use `splitRanges` to pause work inside a task:
```typescript
{
id: 'qa',
name: 'QA',
startDate: '2026-04-01',
duration: 4,
splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }],
}
```
The scheduler extends the computed finish by the split duration so the task retains its planned working duration. Resource allocation checks skip split dates, and task bars render split ranges as visible gaps.
## Timeline Split Editing
- **Create**: Alt-drag inside a regular task bar.
- **Move**: Drag an existing split gap.
- **Remove**: Double-click a split gap.
## Example: Split Task with Resource Allocation
```typescript
grid.source = [{
id: 'qa',
startDate: '2026-04-01',
duration: 4,
splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }],
}];
```
**Expected result**: the task keeps four working days of duration, but its computed finish extends. Resource allocation checks skip `2026-04-03` and `2026-04-04`.
---
# Build A Production Gantt Project
URL: https://pro.rv-grid.com/guides/gantt/tutorial/
Source: src/content/docs/guides/gantt/tutorial.mdx
Description: Extend the quick-start project with dependencies, editing, resources, critical path, baselines, persistence, and export.
Surface: gantt
This tutorial starts with any [framework quick start](/guides/gantt/) and builds toward the same data flow used by the full showcase.
1. **Add project hierarchy.** Keep task rows flat and connect children with `parentId`.
```ts
grid.source = [
{ id: 'delivery', name: 'Delivery', type: 'summary', startDate: '2026-07-14', duration: 8 },
{ id: 'design', parentId: 'delivery', name: 'Design', startDate: '2026-07-14', duration: 3 },
{ id: 'build', parentId: 'delivery', name: 'Build', startDate: '2026-07-17', duration: 5 },
{ id: 'launch', name: 'Launch', type: 'milestone', startDate: '2026-07-24', duration: 0 },
];
```
2. **Add dependencies.** Use stable task IDs and one of `FS`, `SS`, `FF`, or `SF`.
```ts
grid.ganttDependencies = [
{ id: 'd1', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 'build', successorTaskId: 'launch', type: 'FS', lagDays: 0 },
];
```
3. **Enable supported editing.** The managed task columns route edits through Gantt scheduling and validation.
```ts
grid.gantt = {
...grid.gantt,
allowTaskCreate: true,
taskCreateRow: true,
contextMenu: {},
};
```
Inline cell editing, timeline drag/resize/progress handles, dependency drawing, the task editor, and the context menu all use the same project state. See [Task Editing](/guides/gantt/interaction/task-editing/).
4. **Assign resources.** Resources and assignments are separate collections so multiple resources can work on one task.
```ts
grid.ganttResources = [{
id: 'designer', name: 'Avery', role: 'Designer', calendarId: 'standard',
allocationCapacity: 100, hourlyCost: 90,
}];
grid.ganttAssignments = [{
id: 'a1', taskId: 'design', resourceId: 'designer',
allocationUnits: 100, workHours: 24, responsibility: 'Visual design',
}];
```
5. **Show schedule analysis.** Critical path and baseline overlays are opt-in visual layers.
```ts
grid.gantt = {
...grid.gantt,
visuals: {
...grid.gantt?.visuals,
showCriticalPath: true,
showBaseline: true,
baselineId: 'approved',
},
};
grid.ganttBaselines = [{
id: 'approved',
name: 'Approved plan',
capturedAt: '2026-07-14T00:00:00.000Z',
tasks: [
{ taskId: 'design', startDate: '2026-07-14', endDate: '2026-07-16', duration: 24, progressPercent: 0 },
{ taskId: 'build', startDate: '2026-07-17', endDate: '2026-07-23', duration: 40, progressPercent: 0 },
],
}];
```
6. **Persist complete project snapshots.** Keep application state authoritative and replace grid collections after a successful save or server update.
```ts
import { exportProjectSnapshot, parseProjectSnapshotJson } from '@revolist/gantt';
const json = exportProjectSnapshot(snapshot, { space: 2 });
const restored = parseProjectSnapshotJson(json);
if (restored.ok) applyProject(restored.project);
```
7. **Add reporting actions.** Grid CSV/Excel export covers task-table data; the print recipe prepares a combined task-table/timeline report.
```ts
import { createPrintPdfExportRecipe } from '@revolist/gantt';
const print = createPrintPdfExportRecipe({ target: document.querySelector('#gantt')!, document, window });
print.exportProject(snapshot, { label: 'Delivery plan' });
```
Treat emitted or edited collections as application state. Do not scrape rendered task bars or cells to reconstruct project data.
Next, browse [all Gantt examples](/guides/gantt/examples/), review [configuration](/guides/gantt/reference/configuration-reference/), or open the [API reference](/api/gantt/).
---
# Smart Excel-Compatible Clipboard
URL: https://pro.rv-grid.com/guides/grid-utilities/clipboard-json/
Source: src/content/docs/guides/grid-utilities/clipboard-json.mdx
Description: Rich Excel and RevoGrid copy and paste with extensible clipboard formats.
`ClipboardPlugin` adds rich, Excel-compatible range copy and paste. It negotiates the
best browser-visible representation in this order: the lossless RevoGrid payload,
application handlers, Excel HTML, generic HTML tables, and TSV/plain text.
Use `DataGridFormattingPlugin` for the recommended setup. It installs one clipboard
owner automatically, so copied values, formulas, formats, merged cells, and explicit
row/column sizes share one canonical clipboard document:
```ts
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
The clipboard codec only extracts browser-visible Excel declarations. Their
normalization into `DataGridCellFormat`, storage, rendering, and conversion back to
Excel HTML are owned by `DataGridFormattingPlugin`; clipboard code never applies
cell CSS directly.
For the complete formatting model, cell/range/row/column scopes, precedence,
Format Cells UI, state persistence, and workbook export, see
[Format Cells](/guides/data-grid/format-cells/).
## What is preserved
| Capability | RevoGrid → RevoGrid | Excel → RevoGrid | RevoGrid → Excel |
| ----------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| Values | Typed values through the rich payload, including serializable objects | Explicit numeric/boolean HTML values stay typed. When Excel supplies display-only Accounting, Percentage, or date text, the formatting plugin uses the accompanying number format to recover a canonical number or Excel serial date | Numbers, booleans, percentages, and dates are emitted as typed Excel HTML values; text remains text |
| Formulas | A1 source, plus the evaluated cached value when FormulaPlugin is available | Browser-visible A1 or R1C1 formulas normalize to FormulaPlugin-compatible A1. If Excel exposes only the displayed result, the formula source cannot be recovered | A1 source becomes Excel-relative R1C1 with a typed cached result when FormulaPlugin is available |
| Number and date formats | Exact canonical preset or custom-code format | `mso-number-format` becomes a canonical `{ kind: 'code' }` format | Presets and exact codes become Excel number-format declarations |
| Font and fill | Preserved through `DataGridCellFormat.appearance` | Allowlisted font, emphasis, text color, and fill declarations are converted | Canonical typography and colors become Excel-compatible HTML styles |
| Alignment and text layout | Horizontal/vertical alignment, wrap, indent, rotation, and shrink-to-fit | Supported Excel declarations are normalized into appearance options | Canonical options are serialized back to Excel HTML |
| Borders | Per-edge style, color, and width | Supported shorthand and longhand Excel borders are normalized | Per-edge borders are emitted; legacy uniform border options remain shorthand |
| Multiple ranges | Sparse offsets and gaps are lossless in the rich payload | Multiple browser-visible HTML tables are decoded as ranges | Each range is emitted as an HTML table; rich RevoGrid geometry remains available to RevoGrid |
| Merged cells | Merge spans are carried and applied when every destination is writable | `rowspan` and `colspan` become merge metadata | Merge metadata becomes `rowspan` and `colspan` |
| Row heights and column widths | Carried in the rich document; destination RevoGrid resizing is not applied currently | Parsed and retained, but destination RevoGrid resizing is not applied currently | Explicit sizes are emitted in the Excel HTML table |
| Application metadata | Namespaced serializable contributors are lossless | Available only when exposed by a registered handler | Written by compatible registered handlers |
Formatting is applied only after the existing provider-backed value paste is
accepted. Readonly cells, rejected validation targets, canceled edits, and
synthetic grouping rows do not receive formatting or merges.
Excel Desktop does not expose identical browser HTML on every platform. For
example, Excel for macOS may omit `x:num` and copy `$ 1,250.50`, `18.0%`, or
`8/20/26` as display text accompanied by Accounting, Percentage, or `Short Date`
format declarations. `DataGridFormattingPlugin` decodes Excel's CSS escapes and
named formats, converts those known display-only values to canonical numbers or
Excel serial dates, and stores the imported number format as a
`DataGridCellFormat`. Unknown or ambiguous text is left unchanged.
### Display-only Excel category coverage
Excel's browser HTML varies by platform, but the formatting owner covers every
documented cell number-format category when a usable format declaration is
present:
| Excel category | Display-only value recovery |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| General | Kept as text unless Excel supplies an explicit typed attribute; visual text alone is ambiguous |
| Number | Localized decimals, grouping, negative signs, parentheses, and scaling commas become numbers |
| Currency and Accounting | Locale/currency tokens and aligned currency displays become numbers; the exact format code remains canonical formatting |
| Percentage | The displayed percentage is converted back to its underlying decimal value once per `%` token |
| Fraction | Simple, mixed, fixed-denominator, and negative fraction displays become numbers |
| Scientific | Localized mantissas and signed `E` exponents become numbers |
| Date | Numeric and localized month-name dates become Excel serial dates using the format's token order and locale |
| Time and Date & time | 12/24-hour times, seconds, fractional seconds, and combined date-time displays become serial day values |
| Elapsed time | `[h]`, `[m]`, and `[s]` duration displays—including values beyond 24 hours—become serial day fractions |
| Text | Kept exactly as text, including leading zeros |
| Special | Postal, phone, social-security, and comparable placeholder formats recover their numeric value while retaining the exact format code |
| Custom | Numeric/date/time/text classification comes from the compiled code; four sections, conditions, colors, literals, spacing, fill, percentages, scaling, fractions, and scientific notation use the same engine as rendering |
Explicit `x:num` and boolean attributes always take precedence. Excel error
values and malformed, unsupported, or ambiguous display strings stay unchanged
instead of being guessed. These categories follow Microsoft's
[available number formats](https://support.microsoft.com/en-us/excel/get-started/available-number-formats-in-excel)
and [custom number-format rules](https://support.microsoft.com/en-us/excel/review-guidelines-for-customizing-a-number-format).
### Native Excel interoperability matrix
The following matrix was created as a real `.xlsx` workbook, opened in current
Excel Desktop for macOS, copied through the macOS system clipboard, pasted into
RevoGrid, and then copied from RevoGrid back into Excel. It is not an authored
HTML fixture.

### Quick support checklist
Use this list to compare an Excel range with its RevoGrid result. **Native
tested** means the format was created in an actual `.xlsx` workbook and copied
through Excel Desktop for macOS and the system clipboard—not merely passed to
the HTML parser in a test.
| Cell data or number format | Support | Native tested |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| General and plain numbers | Supported; an explicit Excel numeric value stays numeric. Ambiguous display-only General text stays text | Yes |
| Number | Supported, including decimals, grouping, signs, parentheses, and scaling commas | Yes |
| Currency and Accounting | Supported, including currency/locale tokens, aligned symbols, negative parentheses, and zero sections | Yes |
| Percentage | Supported; the displayed percentage is restored to its decimal value | Yes |
| Scientific | Supported, including signed exponents | Yes |
| Fraction | Supported, including simple, mixed, fixed-denominator, and negative fractions | Yes |
| Date | Supported for short, long, numeric, month-name, and localized dates | Yes |
| Time, DateTime, and elapsed time | Supported for 12/24-hour time, seconds, fractional seconds, combined values, and `[h]`/`[m]`/`[s]` durations | Yes |
| Text | Supported and emitted with Excel's Text format so leading zeros and number-like strings remain text | Yes |
| Special | Supported for numeric placeholder formats such as phone and postal masks | Phone mask tested |
| Custom | Supported for up to four sections, conditions, colors, literals, leading zeros, percentages, scaling, fractions, scientific notation, spacing, and fill | Positive/negative/zero sections, leading zeros, scaling, literals, and localized date tested |
| Boolean | Supported when Excel exposes Boolean typing; otherwise the displayed `TRUE`/`FALSE` is preserved as text | Conditional on Excel payload |
| Formula | Supported when the clipboard exposes A1/R1C1 source; otherwise only Excel's displayed/cached result can be preserved | RevoGrid → Excel tested; Excel for macOS omitted formula source |
| Appearance or structure | Support | Native tested |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| Font | Family, size, bold, italic, underline, strike-through, and text color | Family, size, bold, italic, and color tested |
| Fill | Solid browser-visible cell background color | Yes |
| Alignment and layout | Horizontal/vertical alignment, wrapping, indentation, text rotation, and shrink-to-fit | Alignment and wrapping tested |
| Borders | Per-edge style, color, and width | Double/per-edge border tested |
| Merged cells | Supported through HTML `rowspan`/`colspan` and the rich RevoGrid payload | Automated copy/paste tested |
| Multiple ranges | Lossless between RevoGrid instances; multiple Excel HTML tables are accepted | Automated copy/paste tested |
| Row heights and column widths | Parsed and copied back to Excel HTML; applying imported sizes to the destination RevoGrid is not implemented yet | Metadata emission tested |
| RevoGrid application metadata | Supported through namespaced, serializable contributors in the rich payload | Automated rich-payload tests |
### Unsupported or unavailable through browser clipboard
| Excel feature | Current behavior |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Native BIFF/OLE clipboard records | Browsers do not expose them; RevoGrid uses HTML, plain text, and its versioned web custom format |
| Pictures, charts, shapes, and other floating objects | Not part of the cell-range clipboard contract |
| Comments, notes, threaded discussions, and attachments | Not copied |
| Data validation rules, dropdown definitions, and non-serializable validator callbacks | Not copied |
| Conditional-format rules, icon sets, data bars, and workbook themes | Rules are not copied; only browser-visible effective styles may be present in Excel HTML |
| Named ranges, tables, filters, pivot state, external links, and workbook relationships | Not copied |
| Macros, VBA, ActiveX, and embedded objects | Not copied or executed |
| Formula source omitted by Excel | Cannot be reconstructed safely; RevoGrid preserves the displayed/cached result and does not guess |
| Unsupported or malformed display-only values | Preserved as text instead of being coerced |
| Destination RevoGrid row/column resizing | Size metadata is retained, but the destination grid is not resized in the current version |
| Windows Excel native interoperability | Supported through the same browser-visible contract, but not yet manually verified in this matrix |
The pass deliberately records browser limits as limits. Excel for macOS exposed
`text/html`, `text/plain`, `text/rtf`, and `Files`, but its HTML omitted the raw
formula source and explicit numeric value for some display-only cells. RevoGrid
therefore preserves the displayed formula result in that direction and only
recovers a zero when the accompanying third number-format section identifies
the display literally. RevoGrid does not guess an unavailable formula. Native
Windows Excel remains a separate manual interoperability target because its
browser-visible clipboard payload can differ.
## How the representations work together
One copy operation writes several representations because the destination—not
the source—chooses which one it understands:
1. `application/x-revogrid-clipboard+json` version 2 is the lossless
RevoGrid-to-RevoGrid document.
2. Registered application formats can override built-ins by priority.
3. `text/html` carries the richest browser-visible Excel-compatible table.
4. `text/plain` keeps the selection useful in editors and applications that do
not understand structured clipboard formats.
On paste, RevoGrid negotiates in the same richest-first order. A malformed or
failing handler emits `clipboardformaterror` and falls through to the next
compatible representation instead of partially applying its result.
## Copying from RevoGrid to Excel
Copy writes an Excel-compatible HTML table alongside plain text and the lossless
RevoGrid payload. `DataGridFormattingPlugin` converts its canonical options back to
Excel number-format codes, font, fill, borders, alignment, wrapping, rotation, and
indentation. The clipboard owner also emits formulas with typed cached results,
merged-cell spans, and explicit row and column sizes.
Date and time application values remain unchanged in the grid. For Excel HTML only,
the formatting plugin converts valid date-like values to Excel 1900-system serials
and writes the matching number-format code. This lets Excel receive a real date cell
instead of display text while keeping the application's source model intact.
`ClipboardPlugin` can also be installed directly for values and custom formats. It
emits a one-time dependency warning and omits formatting metadata when no formatting
runtime is present.
## Browser and Excel boundary
Browsers expose `text/html`, `text/plain`, and permitted web custom formats; they do
not expose Excel's native BIFF/OLE clipboard records. RevoGrid therefore writes all
compatible browser representations and adds the versioned
`application/x-revogrid-clipboard+json` format for lossless grid-to-grid transfers.
Pictures, charts, comments, validation callbacks, and other native-only Excel objects
are outside the cell-range contract.
## Custom formats
Handlers have stable IDs, MIME types, priorities, detection, decoding, and optional
encoding. Higher priorities can override built-ins. A failing handler emits
`clipboardformaterror` and negotiation continues with the next compatible format.
```ts
import type { ClipboardFormatHandler } from '@revolist/revogrid-pro';
const handler: ClipboardFormatHandler = {
id: 'acme-cells',
mimeTypes: ['application/x-acme-cells+json'],
priority: 2_000,
canRead: ({ transfer }) =>
transfer.types.includes('application/x-acme-cells+json'),
read: ({ transfer }) =>
JSON.parse(transfer.getData('application/x-acme-cells+json')),
write: ({ document }) => [
{
type: 'application/x-acme-cells+json',
data: JSON.stringify(document),
},
],
};
grid.useClipboard = {
formats: [handler],
maxCells: 100_000,
maxBytes: 10 * 1024 * 1024,
};
```
Runtime registration is disposable:
```ts
const clipboard = grid
.getPlugins()
.find((plugin) => plugin instanceof ClipboardPlugin);
const unregister = clipboard?.registerFormat(handler);
```
Serializable application metadata can be carried in the rich payload through
`registerMetadataContributor`. Non-serializable editors, validators, and callbacks
are intentionally never copied.
## Migrating from ClipboardJsonPlugin
`ClipboardJsonPlugin`, its `legacyJsonText` option, and the `$rv-parse_` plain-text
marker were removed in v2.8. Use `ClipboardPlugin` directly, or install
`DataGridFormattingPlugin` to include formatting support. Object-valued cells are
preserved grid-to-grid through the versioned RevoGrid MIME payload; `text/plain`
remains readable JSON/text for other applications.
```ts
// Before
import { ClipboardJsonPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardJsonPlugin];
// After: values and custom clipboard formats
import { ClipboardPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardPlugin];
// After: recommended when cell formatting is enabled
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
Applications that persisted or manually generated `$rv-parse_` strings must decode
them before upgrading. The smart clipboard intentionally does not interpret marker
text because ordinary user text must remain ordinary text.
## Example
---
# Dimension Animation
URL: https://pro.rv-grid.com/guides/grid-utilities/dimension-animation/
Source: src/content/docs/guides/grid-utilities/dimension-animation.mdx
Description: Animate row and column dimension sizes for custom collapse, expand, and resize flows.
`DimensionAnimationPlugin` is a plugin that animates grid dimension sizes. Use it when a feature needs rows or columns to collapse, expand, or move between explicit sizes without replacing the grid's own virtualization and dimension systems.
The plugin only animates sizes. It does not own row visibility, trimming, grouping, ordering, or business state. Callers decide what should become hidden or visible, then use the plugin to make the size transition smooth.
## When to use it
- Animate custom row or column collapse and expand flows.
- Animate explicit size changes when you already know the start and end sizes.
- Coordinate delayed state changes around animation completion.
- Support integrations such as tree rows, where `TreeDataPlugin` handles trim state and `DimensionAnimationPlugin` handles the visual transition.
## Installation
```typescript
import { DimensionAnimationPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [DimensionAnimationPlugin];
grid.dimensionAnimation = {
duration: 180,
easing: 'easeOutCubic',
types: ['rgRow'],
};
```
Configure the plugin through `grid.dimensionAnimation`. The older `additionalData.dimensionAnimation` path is still accepted for compatibility, but `grid.dimensionAnimation` is the supported API.
| Property | Default | Description |
| --- | --- | --- |
| `duration` | `180` | Animation duration in milliseconds. Use `0` to apply sizes synchronously. |
| `easing` | `'easeOutCubic'` | Easing used while interpolating sizes. Supports `'linear'`, `'easeOutCubic'`, or a custom `(progress) => number` function. |
| `types` | All row and column dimensions | Dimensions affected by requests that do not pass a specific `type`. |
## Controller API
After the plugin is registered, use its controller methods to animate indexes or explicit frames.
```typescript
const plugins = await grid.getPlugins();
const dimensionAnimation = plugins.find(
plugin => plugin instanceof DimensionAnimationPlugin,
);
await dimensionAnimation?.collapse([0, 1, 2], {
type: 'rgRow',
indexKind: 'visual',
});
await dimensionAnimation?.expand([0, 1, 2], {
type: 'rgRow',
indexKind: 'visual',
});
```
Use `indexKind: 'physical'` when indexes refer to source rows or columns. Use `indexKind: 'visual'` when indexes refer to the current rendered order after sorting, filtering, trimming, pinning, or other view changes.
### Explicit frames
Use `animate()` when you already know the visual indexes and target sizes.
```typescript
await dimensionAnimation?.animate([
{
type: 'rgCol',
visualIndex: 0,
from: 220,
to: 80,
},
]);
```
The plugin cancels any in-flight animation that targets the same dimension index before starting the next one. Call `cancel()` directly to stop matching animations, or omit arguments to cancel all dimension animations.
```typescript
dimensionAnimation?.cancel('rgRow', [0, 1]);
dimensionAnimation?.cancel();
```
## Event API
You can also request animations by dispatching the `dimensionanimate` event from the grid element. The plugin attaches `detail.done` synchronously, so event-driven integrations can wait before applying follow-up logic.
```typescript
const detail = {
action: 'collapse',
indexes: [0],
type: 'rgRow',
indexKind: 'visual',
};
grid.dispatchEvent(new CustomEvent('dimensionanimate', { detail }));
await detail.done;
```
Listen to `beforedimensionanimate` to cancel an operation before it starts. Listen to `afterdimensionanimate` to react after the animation completes or is cancelled.
```typescript
grid.addEventListener('beforedimensionanimate', event => {
if (event.detail.action === 'collapse' && shouldBlockCollapse()) {
event.preventDefault();
}
});
grid.addEventListener('afterdimensionanimate', event => {
console.log(event.detail.results);
});
```
## Tree integration
Tree rows can use dimension animation automatically. Enable `tree.animation`; `TreeDataPlugin` registers `DimensionAnimationPlugin` when it is not already present.
```typescript
import { TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin];
grid.tree = {
animation: true,
};
grid.dimensionAnimation = {
duration: 180,
};
```
In this flow, `TreeDataPlugin` owns tree trim state and expansion state. `DimensionAnimationPlugin` only animates the affected row sizes during collapse and expand.
## API reference
- [Dimension Animation API](/api/dimension-animation/)
- [Tree guide](/guides/data-manage/tree/)
---
# Overlay Nodes
URL: https://pro.rv-grid.com/guides/grid-utilities/overlay-nodes/
Source: src/content/docs/guides/grid-utilities/overlay-nodes.mdx
Description: A guide to using the Overlay Plugin to display notes on grid rows.
The **Overlay Plugin** in RevoGrid provides a powerful way to display additional content over the grid cells. This guide demonstrates how to use it to show notes on specific rows.
1. **Dynamic Overlay Positioning**: The Overlay Plugin automatically handles positioning and scrolling of overlay elements, ensuring they stay aligned with their corresponding grid rows.
2. **Performance Optimized**: Despite rendering additional content, the overlay layer remains virtualized, maintaining high performance even with many notes.
3. **Flexible Content**: You can display any type of content in the overlay, from simple notes to complex interactive components.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Adding Notes](#adding-notes)
- [Customizing Notes](#customizing-notes)
- [API Reference](#api-reference)
---
## Features
- **Row-Specific Notes**: Display notes on specific grid rows
- **Automatic Positioning**: Notes automatically position themselves relative to their rows
- **Scroll Synchronization**: Notes stay aligned with their rows during scrolling
- **Custom Styling**: Fully customizable note appearance and behavior
---
## Basic Setup
1. **Import the Overlay Plugin:**
```javascript
import { OverlayPlugin } from '@revolist/revogrid-pro';
```
2. **Initialize the Grid with the Plugin:**
```javascript
const grid = document.querySelector('revo-grid');
grid.plugins = [OverlayPlugin];
```
3. **Add Notes to Rows:**
```javascript
// Dispatch an event to add a note
grid.dispatchEvent(new CustomEvent('overlaynode', {
detail: {
nodeId: `note-${rowIndex}`,
vnode: h('div', { class: 'note' }, 'Your note text here')
}
}));
```
### Adding Notes
To add a note to a specific row, you need to:
1. Create a unique `nodeId` for the note
2. Create a virtual node (vnode) for the note content
3. Dispatch the `overlaynode` event with the note details
```javascript
const addNote = (rowIndex, noteText) => {
const noteNode = h('div', {
class: 'note',
style: {
position: 'absolute',
top: `${rowIndex * rowHeight}px`,
left: '0',
width: '100%',
padding: '8px',
backgroundColor: 'rgba(255, 255, 0, 0.2)',
border: '1px solid #ffd700'
}
}, noteText);
grid.dispatchEvent(new CustomEvent('overlaynode', {
detail: {
nodeId: `note-${rowIndex}`,
vnode: noteNode
}
}));
};
```
### Customizing Notes
You can customize the appearance and behavior of notes by:
1. Modifying the note's CSS styles
2. Adding interactive elements
3. Implementing custom positioning logic
```javascript
const createCustomNote = (rowIndex, noteData) => {
return h('div', {
class: 'custom-note',
style: {
position: 'absolute',
top: `${rowIndex * rowHeight}px`,
left: '0',
width: '100%',
padding: '12px',
backgroundColor: noteData.color || 'rgba(255, 255, 0, 0.2)',
border: `1px solid ${noteData.borderColor || '#ffd700'}`,
borderRadius: '4px'
}
}, [
h('div', { class: 'note-header' }, noteData.title),
h('div', { class: 'note-content' }, noteData.content)
]);
};
```
---
## API Reference
### OverlayPlugin Events
#### `overlaynode`
Dispatched to add or update a note in the overlay layer.
```typescript
interface OverlayNodeEvent {
nodeId: string;
vnode: VNode;
}
```
#### `overlayclearnode`
Dispatched to remove notes from the overlay layer.
```typescript
interface OverlayClearNodeEvent {
nodeIds: string[];
}
```
### Methods
#### `refresh()`
Manually refresh the overlay layer's positioning and content.
---
# Local Pagination
URL: https://pro.rv-grid.com/guides/grid-utilities/pagination/
Source: src/content/docs/guides/grid-utilities/pagination.mdx
Description: Manage large datasets by breaking them into smaller, more manageable pages.
Enable `PaginationPlugin` to efficiently manage large datasets by breaking them into smaller, more manageable pages, enhancing both performance and user experience.
Pagination allows you to divide your dataset into discrete pages, each containing a specified number of rows.
Users can then navigate through these pages rather than scrolling through a long list of data.
This is especially useful when dealing with large datasets where loading all rows at once could affect performance. It gives you:
- **Improved Performance**: By loading only a subset of data at a time, pagination helps maintain grid performance even with large datasets.
- **Enhanced User Experience**: Users can easily navigate through data without being overwhelmed by a large number of rows.
---
# Smart Panel
URL: https://pro.rv-grid.com/guides/grid-utilities/smart-panel/
Source: src/content/docs/guides/grid-utilities/smart-panel.mdx
Description: Add configurable, formula-powered row, selection, and application summaries beside any RevoGrid.
`SmartPanelPlugin` adds a compact status surface that stays synchronized with
RevoGrid. It reads selection, focus, row visibility, tree/group nodes, pinned
viewports, checkbox row selection, formulas, and source changes from grid
providers instead of inspecting rendered DOM cells.
Use the defaults for spreadsheet-style selection statistics, or replace the
complete item list with application-specific formulas and resolvers. The panel
can sit directly below the grid or render into an application-owned host.
The default panel adds row context on the left and selection statistics on
the right. Items appear only when their visibility conditions are met.
## Quick setup
Install the plugin and enable range selection:
```ts
import { SmartPanelPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.range = true;
grid.plugins = [SmartPanelPlugin];
```
With no `smartPanel` configuration, the plugin creates a panel immediately
after ``. It always shows the number of main data rows. When a range
contains more than one cell it can also show:
- A1-style range address
- Selected cell and numeric-value counts
- Sum, average, minimum, and maximum
- Visible versus total rows when filtering, tree collapse, grouping, or other
trimming hides rows
The panel refreshes after edits, source and column updates, filtering, sorting,
trimming, pagination, row order, focus/range changes, multi-range changes,
checkbox row selection, and tree-state changes. Bursts are coalesced before
the provider snapshot is rebuilt.
## Configure panel items
Replace `items` to control exactly what appears:
```ts
grid.smartPanel = {
locale: 'en-US',
numberFormat: {
maximumFractionDigits: 0,
},
items: [
{
id: 'rows',
label: 'Rows',
metric: 'visibleRows',
align: 'left',
},
{
id: 'range',
label: 'Range',
metric: 'rangeAddress',
align: 'left',
visible: ({ snapshot }) => snapshot.selectedCells.length > 1,
},
{
id: 'revenue',
label: 'Selected revenue',
formula: '=SUM(SELECTION)',
align: 'right',
format: value => `$${Number(value).toLocaleString('en-US')}`,
},
],
};
```
Every item needs a stable `id` and one value source: `metric`, `formula`, or
`resolve`. If more than one is supplied, resolution uses `resolve`, then
`formula`, then `metric`.
### Built-in metrics
| Metric | Result |
| --- | --- |
| `selectedCells` | Unique selected cells across the active and multi-ranges |
| `selectedRows` | Checkbox-selected rows when `RowSelectPlugin` is installed; otherwise rows represented by the cell range |
| `visibleSelectedRows` | Selected rows that remain visible after trimming |
| `selectedColumns` | Unique columns represented by the selected cells |
| `visibleRows` / `totalRows` / `hiddenRows` | Main data-row counts; synthetic grouping rows are excluded |
| `visibleNodes` / `totalNodes` | Main row-store nodes, including synthetic tree or grouping nodes |
| `focusedCell` / `focusedColumn` | Current A1 address or column label |
| `rangeAddress` | One or more A1 ranges, with pinned viewport ownership when needed |
| `numericCount`, `sum`, `average`, `min`, `max` | Finite numeric values from selected cells |
| `distinct` | Unique selected values |
### Formula items
Formula items use the same evaluator as RevoGrid formulas. Two dynamic names
are available:
- `SELECTION` — all values in the current item scope
- `VALUES` — an equivalent alias that reads naturally for row/column formulas
```ts
grid.smartPanel = {
items: [
{ id: 'median', label: 'Median', formula: '=MEDIAN(SELECTION)' },
{ id: 'spread', label: 'Spread', formula: '=MAX(VALUES)-MIN(VALUES)' },
{
id: 'visible-arr',
label: 'Visible ARR',
formula: '=SUM(VALUES)',
scope: 'visibleRows',
column: 'arr',
},
{
id: 'all-arr',
label: 'All ARR',
formula: '=SUM(VALUES)',
scope: 'allRows',
column: 'arr',
},
],
};
```
`scope` defaults to `selection`. For `visibleRows` and `allRows`, set `column`
to evaluate one field. Without `column`, the panel flattens enumerable values
from every row in that scope.
Smart Panel formulas do not require `FormulaPlugin`. Add `FormulaPlugin` when
selected grid cells themselves contain formulas and the panel should aggregate
their evaluated results instead of their raw `=...` source strings.
## Application summaries
Custom resolvers receive the complete provider-backed snapshot and may be
synchronous or asynchronous:
```ts
grid.smartPanel = {
className: 'operations-panel',
items: [
{
id: 'visibility',
label: 'Visible accounts',
align: 'left',
resolve: ({ snapshot }) =>
`${snapshot.visibleRowCount} of ${snapshot.totalRowCount}`,
},
{
id: 'regions',
label: 'Regions',
align: 'center',
resolve: ({ snapshot }) =>
new Set(snapshot.visibleRows.map(row => row.region)).size,
},
{
id: 'pipeline',
label: 'Pipeline',
scope: 'visibleRows',
column: 'pipeline',
formula: '=SUM(VALUES)',
align: 'right',
format: value => `$${Number(value).toLocaleString('en-US')}`,
},
{
id: 'health',
label: 'Health',
align: 'right',
resolve: ({ snapshot }) =>
snapshot.visibleRows.every(row => row.health === 'On track')
? 'On track'
: 'Needs attention',
className: 'operations-panel__health',
},
],
};
```
Formula items and application resolvers can share the same left, center,
and right aligned panel.
The resolver context contains:
| Field | Description |
| --- | --- |
| `grid` | Host `HTMLRevoGridElement` |
| `providers` | RevoGrid plugin providers for advanced integrations |
| `snapshot` | One immutable-by-convention snapshot shared by every item in the refresh |
| `item` | Current item configuration |
| `values` | Values resolved from the item's `scope` and optional `column` |
`snapshot` includes ranges, selected cells and raw/evaluated values, checkbox
and range rows, selected columns, visible/all main rows, visible/all rows across
pinned stores, data-row and node counts, focus metadata, and a revision number.
## Item presentation
| Property | Default | Purpose |
| --- | --- | --- |
| `label` | none | Muted text before the value |
| `align` | `right` | Places the item in the `left`, `center`, or `right` group |
| `order` | declaration index | Orders items inside an alignment group |
| `priority` | `0` | Secondary sort and a `data-priority` styling hook |
| `visible` | `true` | Boolean or snapshot predicate controlling whether the item renders |
| `format` | panel formatter | Converts the resolved value into display text |
| `render` | none | Returns a safe `Node` or text string for custom presentation |
| `className` | none | Adds an application class to the item |
| `title` | none | Static string or context callback for the native title |
Strings returned from `render` or a resolver are inserted as text, never as raw
HTML. Return a DOM `Node` when richer content is required:
```ts
{
id: 'status',
render: value => {
const badge = document.createElement('strong');
badge.className = 'status-badge';
badge.textContent = String(value);
return badge;
},
resolve: ({ snapshot }) =>
snapshot.hiddenRowCount ? 'Filtered' : 'Complete',
}
```
## Render into a custom host
Pass an element or a callback when layout belongs to the application:
```html
```
```ts
grid.smartPanel = {
host: () => document.querySelector('#grid-insights'),
items,
};
grid.plugins = [SmartPanelPlugin];
```
The plugin never removes a user-owned host. Assign a new `smartPanel` object or
call `plugin.setHost(nextHost)` to move the existing component. Passing `null`
returns it to a plugin-owned host immediately after the grid.
## Panel configuration
| Property | Default | Description |
| --- | --- | --- |
| `host` | panel after grid | Element or callback providing the panel container |
| `items` | spreadsheet defaults | Complete ordered item configuration |
| `className` | none | Additional class on the panel host |
| `ariaLabel` | `Grid summary` | Accessible name for the status region |
| `hiddenWhenEmpty` | `false` | Hides the host when no configured items resolve visibly |
| `locale` | browser locale | Locale passed to `Intl.NumberFormat` |
| `numberFormat` | `{ maximumFractionDigits: 2 }` | Default numeric formatting options |
| `formatValue` | built-in formatter | Panel-wide value formatter used before item-level `format` |
| `onError` | none | Receives an isolated item/formula error |
| `schedule` | `microtask` | Use `animationFrame` to align refreshes with visual frame updates |
Assign `false` to hide the installed panel without removing the plugin:
```ts
grid.smartPanel = false;
```
Direct property assignments are observable. The legacy
`additionalData.smartPanel` configuration remains available for compatibility,
but new integrations should use `grid.smartPanel`.
## Runtime API and events
Retrieve the plugin instance through RevoGrid's plugin service:
```ts
const smartPanel = (await grid.getPlugins())
.find(plugin => plugin instanceof SmartPanelPlugin);
const snapshot = smartPanel?.getSnapshot();
const items = smartPanel?.getItems();
await smartPanel?.refresh();
smartPanel?.setHost(document.querySelector('#grid-insights'));
```
| Method | Purpose |
| --- | --- |
| `getSnapshot()` | Returns the last committed provider snapshot |
| `getItems()` | Returns a copy of the last resolved item list |
| `getComponent()` | Returns the presentation-only `SmartPanelComponent` |
| `refresh()` | Immediately rebuilds and renders the snapshot; resolves when committed |
| `setHost(host)` | Moves the panel to a user host or back to an owned host with `null` |
| Event | Detail | Purpose |
| --- | --- | --- |
| `smartpanelchange` | `{ snapshot, items }` | Fires after the latest asynchronous refresh commits |
| `smartpanelerror` | `{ error, item }` | Reports one failed item while healthy items continue rendering |
When async item resolvers overlap, only the newest refresh commits. Use
`onError` or `smartpanelerror` for telemetry without taking down the rest of
the panel.
## Styling
The Pro stylesheet includes responsive wrapping at `720px`. Customize the
panel through its public variables and stable classes:
```scss
.operations-panel {
--revo-smart-panel-background: #101615;
--revo-smart-panel-color: #d7e2de;
--revo-smart-panel-border: #2a3834;
--revo-smart-panel-gap: 24px;
border-radius: 12px;
&__health .revo-smart-panel__value {
color: #32d89b;
}
}
```
| Selector | Role |
| --- | --- |
| `.revo-smart-panel` | Host status region |
| `.revo-smart-panel__group--left` | Left item group |
| `.revo-smart-panel__group--center` | Center item group |
| `.revo-smart-panel__group--right` | Right item group |
| `.revo-smart-panel__item` | One resolved item |
| `.revo-smart-panel__label` | Item label |
| `.revo-smart-panel__value` | Text or custom rendered value |
## Composition patterns
Keep the defaults for range address, count, sum, average, min, and max.
Combine visible/total row metrics with formulas scoped to `visibleRows`.
Use node metrics beside data-row metrics to distinguish structural rows.
Mount into a dashboard shell and add async resolvers or custom Nodes.
The component uses `role="status"`, `aria-live="polite"`, and the configured
accessible label. Keep item labels concise so selection gestures do not create
overly verbose screen-reader announcements.
---
# Tooltip
URL: https://pro.rv-grid.com/guides/grid-utilities/tooltip/
Source: src/content/docs/guides/grid-utilities/tooltip.mdx
Description: A comprehensive guide to using the RevoGrid Tooltip Plugin
The Tooltip Plugin is a Pro plugin that displays additional information when users hover over grid cells or other elements inside the grid. It creates a single managed tooltip element, reads content from cell attributes, and shows it after a short hover delay.
## Features
- Dynamic tooltip creation and positioning on `mouseover`, `mouseout`, and `mousemove`
- Automatic tooltip content from `data-tooltip` or `title` attributes
- Customizable tooltip appearance with `tooltip-type` and `tooltip-position` attributes
- Configurable show delay, animation, and cursor-following behavior
- Automatic mounting inside the active dialog/fullscreen container, with the document body as fallback
- Body fallback so grid and ancestor overflow containers cannot crop the tooltip
- Preserves the grid's effective LTR or RTL writing direction across the portal boundary
- Smart positioning that follows grid UI beyond the host while keeping tooltips within viewport boundaries
- Smooth show/hide transitions
- Support for different tooltip types (info, warning, error, etc.)
## Installation
The Tooltip Plugin is included in RevoGrid Pro. To use it, import and initialize it with your grid:
```typescript
import { TooltipPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [TooltipPlugin];
```
## Usage
### Basic Usage
The simplest way to use tooltips is by adding a `title` or `data-tooltip` attribute to your grid cells:
```typescript
grid.columns = [
{
prop: 'name',
name: 'Name',
cellTemplate: (createElement, props) => {
return createElement('div', {
title: 'User name',
'data-tooltip': 'Full user name',
}, props.model.name);
},
},
];
```
### Showing Behavior
Tooltips are shown only when the hovered element has a non-empty `data-tooltip` or `title` attribute. If both are present, `data-tooltip` is used. The plugin waits `500ms` before showing the tooltip by default, then hides it when the cursor leaves the tooltip target.
Use the `tooltip` grid property to control when and how the tooltip is shown:
```typescript
grid.tooltip = {
enabled: true,
showDelayMs: 250,
animation: true,
followCursor: true,
themeVariables: ['--app-tooltip-accent'],
appendTo: document.getElementById('tooltip-root') ?? document.body,
};
```
| Property | Default | Description |
| --- | --- | --- |
| `enabled` | `true` | Enables or disables tooltip hover behavior. Disabling it hides any open tooltip immediately. |
| `showDelayMs` | `500` | Delay before showing the tooltip. Negative values are normalized to `0`. |
| `animation` | `true` | Enables the tooltip entrance and exit transition. |
| `followCursor` | `true` | Keeps the tooltip attached to the cursor while it is open. Set to `false` to keep the tooltip at its opening position. |
| `themeVariables` | `[]` | Additional CSS custom properties copied from the grid to the portal-mounted tooltip. |
| `appendTo` | Automatic | Element that receives the managed tooltip node. Runtime changes move the existing tooltip to the new target. |
Without `appendTo`, the plugin uses the closest open native dialog or active fullscreen element containing the grid, then falls back to `document.body`. This keeps the tooltip in the browser's active top-layer subtree while the body fallback prevents `overflow: hidden` on the grid or its ancestors from cropping it. For a custom target, prefer a top-level overlay root without clipping or CSS transforms. A target inside an overflow-clipping container can crop the tooltip.
Plugins that own tooltip-specific theme variables can register them without mutating application configuration:
```typescript
const unregister = tooltipPlugin.registerThemeVariables([
'--my-plugin-tooltip-accent',
]);
// Call during plugin teardown.
unregister();
```
### Customizing Tooltip
You can control the tooltip position using the `tooltip-position` attribute and the `tooltip-type` attribute to change the tooltip type:
```typescript
createElement('div', {
'data-tooltip': 'Custom positioned tooltip',
'tooltip-position': 'right', // 'top', 'bottom', 'left', 'right'
'tooltip-type': 'info', // 'info', 'warning', 'error', etc.
}, content);
```
## Best Practices
1. Keep tooltip content concise and relevant
2. Use appropriate tooltip positions based on available space
3. Choose tooltip types that match the information context
4. Consider mobile users when designing tooltip interactions
5. Use tooltips to provide supplementary information, not critical data
---
# Removing Attribution
URL: https://pro.rv-grid.com/guides/hide-attribution/
Source: src/content/docs/guides/hide-attribution.md
Description: Removing Attribution from RevoGrid
As a Pro version user of RevoGrid, you can easily remove attribution from your projects. To do this, simply apply one of the following attributes:
### Method 1: Using `hideAttribution`
Add the `hideAttribution` attribute to your RevoGrid component:
```html
```
### Method 2: Using `hide-attribution`
Alternatively, you can use the `hide-attribution` attribute:
```html
```
### Conclusion
By applying either of these attributes, the attribution will be hidden from your RevoGrid component.
If you have any questions or need further assistance, feel free to reach out to the RevoGrid support team.
---
# Infinity Scroll
URL: https://pro.rv-grid.com/guides/infinity-scroll/
Source: src/content/docs/guides/infinity-scroll.mdx
Description: Efficiently handle large datasets with dynamic loading as users scroll through the grid.
The Infinity Scroll feature enables dynamic data loading as users scroll through the grid, efficiently managing large datasets by loading data in chunks and cleaning up unused data to optimize memory usage.
## Key Features
- Dynamic data loading based on scroll position
- Pre-loads data ahead of scroll direction
- Efficient memory management by cleaning up off-screen data
- Configurable chunk size and buffer sizes
- Support for both known and unknown total data sizes
- Compatible with pinned top and bottom rows
## Basic Setup
To enable infinity scroll in your grid:
```typescript
import { InfinityScrollPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [InfinityScrollPlugin];
grid.pinnedTopSource = [{ id: 'status', name: 'Pinned status row' }];
grid.pinnedBottomSource = [{ id: 'summary', name: 'Pinned summary row' }];
grid.infinityScroll = {
chunkSize: 50, // Number of rows per chunk
bufferSize: 100, // How many rows to keep in buffer
preloadThreshold: 0.75, // When to start loading more data (0-1)
total: 1000, // Optional: total number of rows
loadData: async (skip, limit, order, singleConditionFilters) => {
// Fetch data from your API
const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(singleConditionFilters)}`);
const data = await response.json();
return data; // You can return an array or { data, hasMore, total }
}
};
```
The configuration is an observable grid property, so assigning a new object to
`grid.infinityScroll` updates the plugin. In Vue templates, use the kebab-case
alias with Vue's `.prop` modifier so the object is assigned as an element
property:
```vue
```
`additionalData.infinityScroll` remains available as a deprecated compatibility
fallback for existing applications.
Pinned rows stay outside the remote loading lifecycle. Use the core
`pinnedTopSource` and `pinnedBottomSource` grid properties for static status,
summary, or action rows while `InfinityScrollPlugin` manages only the main
scrollable source.
## Configuration Options
Option
Type
Default
Description
chunkSize
number
undefined
Number of rows to load in each chunk. Can be set dynamically by the grid.
bufferSize
number
undefined
Number of rows to keep in the memory buffer. Can be set dynamically by the grid.
preloadThreshold
number
0.75
When to trigger loading the next chunk (0–1).
total
number
undefined
Total number of rows. If omitted, the plugin grows the source as you scroll.
loadData
function
Required
Function that loads data chunks. Return row[] or{' '}
{ data: row[], hasMore?: boolean, total?: number }.
### Remote advanced filtering
For a new backend integration, use the canonical `filterAst` and
`executionContext` arguments. The tree is JSON-safe and preserves nested
groups, cross-column logic, structured operators, and quick-filter composition.
The context captures the single reference instant, timezone, and serializable
blank-value policy used by relative-date rules, so the server can evaluate the
same request as the grid.
```typescript
loadData: async (
skip,
limit,
order,
_singleConditionFilters,
_multiConditionFilters,
_quickFilter,
_temporalContext,
filterAst,
executionContext,
) => {
const response = await fetch('/api/data', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(
{
page: { skip, limit },
order,
filter: filterAst ? { filterAst, executionContext } : undefined,
},
),
});
return response.json();
}
```
Register `AdvanceFilterPlugin` alongside `InfinityScrollPlugin` before using
this contract. Its `filterastchange` event and the `beforefilterapply` detail
expose the same canonical pair; see [Canonical Filter AST](/guides/data-filter/filter-ast/)
for the tree schema and [Filter Events](/guides/data-filter/filter-events/)
for a custom remote adapter.
#### Compatibility arguments
`loadData` keeps the established positional arguments for existing callers.
The fourth `singleConditionFilters` argument contains at most one condition per
column. The optional fifth `multiConditionFilters` argument contains every
advanced column condition and its relation; use it when an existing backend
supports multiple conditions for one column. The sixth argument is the
normalized global quick-filter payload, and the seventh is temporal transport
data for older date/datetime integrations. The eighth and ninth arguments are
the canonical `filterAst` and `executionContext` shown above.
`multiConditionFilters` can contain runtime-only values such as a selection
filter `Set`, so do not serialize it unchanged in new integrations. If an
existing backend requires it, normalize those values at that adapter boundary.
For example, if the Name column must contain `Remote` and must not contain
`Archived`, the fifth argument keeps both conditions and their relation:
```typescript
{
name: [
{ id: 0, type: 'contains', value: 'Remote', relation: 'and' },
{ id: 1, type: 'notContains', value: 'Archived', relation: 'and' },
],
}
```
The fourth argument remains compatible with single-condition backends and, for
this example, contains only `{ name: { type: 'contains', value: 'Remote' } }`.
Use `multiConditionFilters` whenever the backend needs the full expression.
### Using `hasMore` for unknown total
When your backend does not provide a fixed `total`, return `hasMore` from
`loadData`. This is the most reliable way to tell the plugin when to stop
requesting additional chunks.
```typescript
grid.infinityScroll = {
chunkSize: 100,
loadData: async (skip, limit, order, singleConditionFilters) => {
const response = await fetch('/api/data', {
method: 'POST',
body: JSON.stringify({ skip, limit, order, filter: singleConditionFilters }),
});
const result = await response.json();
return {
data: result.items,
hasMore: result.hasMore,
};
},
};
```
### Dynamic `total` from backend
You can also return `total` from `loadData`. When provided, the plugin updates
its internal total size and uses it for source sizing and end detection.
## Memory Management
The plugin automatically manages memory by:
- Loading new chunks of data as the user scrolls
- Maintaining a buffer of rows before and after the visible area
- Cleaning up data that's far from the current viewport
## Exporting to Excel
`ExportExcelPlugin` exports grid source rows, not rendered DOM rows. Ordinary
client-side virtualization therefore exports the entire source, including rows
currently outside the viewport.
Infinity scroll is different: it keeps only a moving client-side window and may
represent remote, unloaded positions with placeholders. Export does not call
`loadData` repeatedly and cannot discover records that have never been loaded.
To prevent a misleading partial workbook, direct export now fails with an
actionable error while `InfinityScrollPlugin` is active.
If you need a complete workbook, fetch the export dataset from the same backend
API and pass those rows to `ExportExcelPlugin` instead of relying on the visible
infinite-scroll grid. The demo export button uses a temporary hidden grid with
the same columns so the visible grid is not disturbed while the workbook is
generated.
## Best Practices
1. **Choose Appropriate Chunk Size**
- Smaller chunks mean more frequent loading but less memory usage
- Larger chunks mean fewer API calls but more memory usage
2. **Configure Buffer Size**
- Larger buffers provide smoother scrolling but use more memory
- Smaller buffers save memory but might cause more loading events
3. **Optimize Data Loading**
- Implement server-side pagination in your API
- Return only necessary data fields
- Consider data compression for large datasets
4. **Handle Loading States**
- Show loading indicators during data fetching
- Handle errors gracefully
- Consider implementing retry logic for failed requests
## Example with Loading State
```typescript
grid.infinityScroll = {
chunkSize: 50,
bufferSize: 100,
loadData: async (skip, limit, order, singleConditionFilters) => {
try {
// Show loading state
const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(singleConditionFilters)}`);
const data = await response.json();
// Hide loading state
return data;
} catch (error) {
console.error('Failed to load data:', error);
return []; // Return empty array on error
} finally {
// Hide loading state
}
},
};
```
This implementation provides efficient handling of large datasets while maintaining optimal performance and user experience.
---
# Installing RevoGrid Pro Products
URL: https://pro.rv-grid.com/guides/installation/
Source: src/content/docs/guides/installation.mdx
Description: Learn how to install RevoGrid Pro and standalone product packages using either a pre-built release or our private npm registry.
### Which Package Do I Need?
- Install `@revolist/revogrid-pro` when you need advanced Pro features (filters, editors, grouping, tree, export, etc.) without Pivot.
- Install `@revolist/pivot` for Pivot tables, Pivot Charts, and the Pivot configurator. Keep Pro installed alongside it.
- Install `@revolist/gantt` for project timelines and dependencies, `@revolist/scheduler` for event/resource scheduling, or `@revolist/kanban` for workflow boards. Each is independently installable alongside Pro.
- If you are evaluating the trial version, use the matching trial package name, such as `@revolist/pivot-trial`.
```bash
npm install @revolist/revogrid-pro
npm install @revolist/pivot
```
Trial install:
```bash
npm config set "@revolist:registry=https://trial.rv-grid.com"
npm install @revolist/revogrid @revolist/rv-pro-trial @revolist/pivot-trial
```
If you are unsure, start with Pro and add only the standalone product package you need. For trial projects, import CSS from `@revolist/rv-pro-trial/dist/rv-pro-trial.css`.
#### Next Steps
After installation, you can start using RevoGrid Pro and the standalone products in your project. Check out our other guides for:
- [Basic Usage](/guides)
- [Component API](/api/aggregations)
- [Simple Example](/guides/rows/row-odd/)
- [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git)
Each framework has its own set of components and examples that demonstrate how to use RevoGrid Pro products effectively in your chosen framework environment.
---
# Github Installation Guide
URL: https://pro.rv-grid.com/guides/installation-github/
Source: src/content/docs/guides/installation-github.mdx
Description: Learn how to install RevoGrid Pro products using GitHub Packages
[Private GitHub Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)
1. Ensure you have access to the GitHub repository.
2. Authenticate your npm client with GitHub by creating a .npmrc file in your project directory or user home directory with the following content:
```json
@revolist:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN
```
3. Replace `YOUR_PERSONAL_ACCESS_TOKEN` with a GitHub token that has `read:packages` and repo permissions.
4. **Install the packages**. Run the following commands to install the latest versions from the private package registry:
```bash
npm install @revolist/revogrid-pro@latest
npm install @revolist/pivot@latest
```
For additional guidance on installing from GitHub’s private registry, please refer to the [official GitHub Packages documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry).
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
## Supply Chain Metadata
RevoGrid Pro product releases include SBOM files and Sigstore release signatures.
- SBOM files are generated in SPDX JSON and CycloneDX JSON formats.
- Package tarballs are signed with Sigstore Cosign keyless signing in the release workflow.
- The signed tarball is the same tarball published to GitHub Packages.
For verification commands and related policy links, see [Security and Supply Chain](/legal/security).
---
# Local Installation Guide
URL: https://pro.rv-grid.com/guides/installation-local/
Source: src/content/docs/guides/installation-local.mdx
Description: Learn how to install RevoGrid Pro products using local files
---
### Option 1: Directly Include in Builds
- Copy the into your project’s `assets` or `libs` folder.
- Link the necessary JavaScript and CSS files in your HTML or import them into your JavaScript/TypeScript files.
```html
```
Or in your module-based project:
```javascript
import 'path/to/revogrid-pro.js';
import 'path/to/revogrid-pro.css';
// If using build tools like Webpack or Vite:
// Add the ./release folder path to your resolve or alias configuration to reference the files.
resolve: {
alias: {
'@revogrid-pro': path.resolve(__dirname, 'path/to/release'),
},
},
```
This option is particularly suitable for quick testing or projects that don’t rely on npm for dependency management.
---
### Option 2: package.json Using Local Files
For projects where you want to use local versions of RevoGrid Pro products during development, you can reference the package files directly in your package.json.
This method is especially useful if you’re working with a cloned version of the repository or testing custom modifications.
Steps to Import Using Local Files:
1. Download files from the link above and extract them to a local folder.
2. Set Up Local References in package.json. In your project’s package.json file, reference the local folders containing Pro and the standalone products. For example:
```json
{
"dependencies": {
"@revolist/revogrid-pro": "file:./release/pro",
"@revolist/pivot": "file:./release/pivot"
}
}
```
3. Install Dependencies. Run the following command to install the local plugin:
```bash
npm install
```
4. NPM will create a symlink to the specified folders, allowing you to use the local files as if they were npm packages. Import each plugin from its owning package:
```js
import { ColumnStretchPlugin } from '@revolist/revogrid-pro';
import { PivotPlugin } from '@revolist/pivot';
```
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# No-Build JavaScript Usage
URL: https://pro.rv-grid.com/guides/installation-no-build/
Source: src/content/docs/guides/installation-no-build.mdx
Description: Use a delivered RevoGrid Pro JavaScript build directly from the browser without a bundler.
Use this setup when you want to load a delivered RevoGrid Pro JavaScript build from a plain HTML file, a static page, or a server-rendered page that does not use a JavaScript build pipeline.
RevoGrid Pro is not published as a public browser CDN package. Use the JavaScript/CSS build delivered from the portal, or install the private npm package in projects that have a build step. The no-build example below assumes the delivered Pro `dist` files are copied into your app.
## Live Preview
This preview uses the same grid configuration as the no-build HTML example below.
1. Copy the delivered Pro build into your static assets.
Keep the whole Pro `dist` folder together because the ESM entry imports sibling chunk files.
```txt
public/
vendor/
revogrid-pro/
revogrid-pro.js
revogrid-pro.css
revogrid-pro*.js
```
2. Add the Pro stylesheet from your hosted asset path.
```html
```
3. Load the public RevoGrid Core web component from jsDelivr.
```html
```
4. Add an import map for Core package imports and the delivered Pro bundle.
The standalone Core script defines ``. The `@revolist/revogrid` import map entry is still required because the Pro ESM bundle imports Core helpers by package name.
```html
```
5. Import the Pro plugins from a module script.
```html
```
## Complete HTML Example
Save this as an HTML file and open it through any static web server. Do not use a `file://` URL; browser module imports should be served over HTTP.
The Pro ESM bundle imports `@revolist/revogrid` as a package name. Browsers do not resolve npm package names by themselves, so the import map tells the browser where each package entry points in your hosted assets.
---
# NPM Installation Guide
URL: https://pro.rv-grid.com/guides/installation-npm/
Source: src/content/docs/guides/installation-npm.mdx
Description: Learn how to install RevoGrid Pro and standalone product packages using npm
**1. Configure NPM Registry**
First, configure npm to use the RevoGrid registry:
```bash
npm config set "@revolist:registry=https://npm.rv-grid.com"
```
The command npm config set `@revolist:registry=https://npm.rv-grid.com` is used to configure npm
(Node Package Manager) to use a specific registry for packages that are scoped under the `@revolist`
namespace.
---
**2. Install RevoGrid Pro and the Products You Need**
Install the core grid and Pro, then add only the standalone product you need. For example, to add Gantt:
```bash
npm install @revolist/revogrid
npm install @revolist/revogrid-pro
npm install @revolist/gantt
```
Use `@revolist/scheduler`, `@revolist/kanban`, or `@revolist/pivot` instead of Gantt when that is the product you need.
---
**3. Framework Integration Packages**
For better integration with specific frameworks, you can install the corresponding framework package:
**React**
```bash
npm install @revolist/react-datagrid
```
**Angular**
```bash
npm install @revolist/angular-datagrid
```
**Vue**
```bash
npm install @revolist/vue3-datagrid
```
**Svelte**
```bash
npm install @revolist/svelte-datagrid
```
Read more about [RevoGrid Pro Tokens](/guides/installation-token).
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# NPM Installation Guide
URL: https://pro.rv-grid.com/guides/installation-npm-trial/
Source: src/content/docs/guides/installation-npm-trial.mdx
Description: Learn how to install RevoGrid Pro and standalone product trial packages using npm
No account, token, npm login, licence key, form submission, or approval email is
required. A new developer can follow this page immediately from a clean shell.
Trial access uses separate package names and asset paths from the full paid packages. After purchasing, switch each product trial to `@revolist/` and switch Pro trial to `@revolist/revogrid-pro`.
Clone or inspect the working trial example:
[github.com/revolist/revogrid-pro-trial](https://github.com/revolist/revogrid-pro-trial.git).
Trial packages use names such as `@revolist/rv-pro-trial` and
`@revolist/gantt-trial`, `@revolist/scheduler-trial`,
`@revolist/kanban-trial`, and `@revolist/pivot-trial`, with matching CSS filenames.
**1. Configure NPM Registry**
First, configure npm to use the public RevoGrid trial registry. No token or login is required:
```bash
npm config set "@revolist:registry=https://trial.rv-grid.com"
```
This command routes the `@revolist` scope through the public trial registry.
It does not change the default registry used for unscoped packages.
---
**2. Install RevoGrid Pro Trial**
Install the core grid and RevoGrid Pro trial package:
```bash
npm install @revolist/revogrid
npm install @revolist/rv-pro-trial
```
Use Pro exports from the trial package and import the trial CSS file:
```typescript
import '@revolist/rv-pro-trial/dist/rv-pro-trial.css';
import { RowOddPlugin } from '@revolist/rv-pro-trial';
```
Do not use the full package CSS path with the trial package:
```typescript
// Incorrect for trial packages
import '@revolist/rv-pro-trial/dist/revogrid-pro.css';
```
---
**3. Install a Standalone Product Trial**
Install only the product trial you need:
```bash
npm install @revolist/gantt-trial
# or: @revolist/scheduler-trial, @revolist/kanban-trial, @revolist/pivot-trial
```
Product trials build on Pro trial, so keep `@revolist/rv-pro-trial` installed.
```typescript
import '@revolist/rv-pro-trial/dist/rv-pro-trial.css';
import '@revolist/pivot-trial/dist/pivot-trial.css';
import { PivotPlugin, type PivotConfig } from '@revolist/pivot-trial';
import { commonAggregators } from '@revolist/rv-pro-trial';
```
Minimal Pivot setup:
```typescript
import { defineCustomElements } from '@revolist/revogrid/loader';
import '@revolist/rv-pro-trial/dist/rv-pro-trial.css';
import '@revolist/pivot-trial/dist/pivot-trial.css';
import { PivotPlugin, type PivotConfig } from '@revolist/pivot-trial';
import { commonAggregators } from '@revolist/rv-pro-trial';
defineCustomElements();
const grid = document.createElement('revo-grid');
const pivot: PivotConfig = {
dimensions: [
{ prop: 'age' },
{
name: 'Date of Birth',
prop: 'dateOfBirth',
aggregators: {
count: commonAggregators.count,
},
},
],
rows: ['age'],
values: [{ prop: 'dateOfBirth', aggregator: 'count' }],
hasConfigurator: true,
};
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = [
{ name: 'John Doe', age: 25, dateOfBirth: '1998-01-15' },
{ name: 'Jane Smith', age: 30, dateOfBirth: '1993-03-22' },
];
document.getElementById('app')?.appendChild(grid);
```
Collaborative Editing is separately installed so applications that do not need
realtime editing do not load Yjs:
```bash
npm install @revolist/revogrid-collaborative-editing-trial
```
Import its API from the trial package and keep the main Pro trial installed for
`EventManagerPlugin` and other grid integrations:
```typescript
import { EventManagerPlugin } from '@revolist/rv-pro-trial';
import { CollaborativeEditingPlugin } from '@revolist/revogrid-collaborative-editing-trial';
```
---
**4. Framework Integration Packages**
For better integration with specific frameworks, you can install the corresponding framework package:
**React**
```bash
npm install @revolist/react-datagrid
```
**Angular**
```bash
npm install @revolist/angular-datagrid
```
**Vue**
```bash
npm install @revolist/vue3-datagrid
```
**Svelte**
```bash
npm install @revolist/svelte-datagrid
```
---
**5. Verify a Complete Starter**
The maintained starter declares its supported Node and pnpm versions. From a
clean clone:
```bash
git clone https://github.com/revolist/revogrid-pro-trial.git
cd revogrid-pro-trial
corepack enable
corepack install
pnpm install --frozen-lockfile
pnpm build
pnpm dev
```
To run its Chromium smoke tests on a machine that has never used Playwright:
```bash
pnpm test:e2e:install
pnpm test:e2e
```
The starter includes Tree, Pivot, Gantt, and Scheduler examples. Kanban is
installable as `@revolist/kanban-trial` but is not mounted in this starter.
Collaborative Editing is separately installable as
`@revolist/revogrid-collaborative-editing-trial` but is not mounted in this
starter. See the complete
[trial feature inventory](/legal/trial/#trial-feature-inventory).
---
# Build from Source
URL: https://pro.rv-grid.com/guides/installation-source/
Source: src/content/docs/guides/installation-source.mdx
Description: Learn how to build RevoGrid Pro and standalone products from source
For maximum control, you can build RevoGrid Pro and the standalone product packages from source.
This allows you to customize the build process if needed.
1. Clone the repository and navigate to the project directory.
```bash
git clone https://github.com/revolist/revogrid-pro.git
cd revogrid-pro
```
2. Run the build commands for Pro and the products you need.
```bash
pnpm build:pro
pnpm build:pivot
pnpm build:gantt
pnpm build:scheduler
pnpm build:kanban
```
3. After the build completes, the generated files are available in each package's `dist` folder, for example `/packages/pro/dist`, `/packages/pivot/dist`, and `/packages/gantt/dist`.
Choose the method that best suits your workflow, and install only the RevoGrid Pro products your application needs.
---
# Access Token Installation Guide
URL: https://pro.rv-grid.com/guides/installation-token/
Source: src/content/docs/guides/installation-token.mdx
Description: Learn how to use access tokens with RevoGrid Pro NPM Registry
Access tokens provide a secure way to authenticate with the RevoGrid Pro repository, especially useful for CI/CD environments or multi-user repository access. Instead of using password authentication, you can create and manage tokens for secure authorization.
### Prerequisites
Before creating access tokens, you must first configure and login to the npm server. Please follow the [NPM Installation Guide](/guides/installation-npm) instructions first.
### Creating an Access Token
To create a new token using the command line, run:
```bash
npm token create --registry=https://npm.rv-grid.com
```
You will be prompted for your password. After entering it, a new token will be displayed in the console:
```bash
┌──────────┬─────────────────────────┐
│ token │ Ihj... │
├──────────┼─────────────────────────┤
│ user │ your-username │
├──────────┼─────────────────────────┤
│ cidr │ │
├──────────┼─────────────────────────┤
│ readonly │ false │
├──────────┼─────────────────────────┤
│ created │ 2025-03-21T01:02:03.00Z │
└──────────┴─────────────────────────┘
```
Make sure to copy and securely store the token value.
### Viewing Access Tokens
To view all your available tokens:
```bash
npm token list --registry=https://npm.rv-grid.com
```
This will display a list of all your active tokens:
```bash
┌────────┬─────────┬────────────┬──────────┬────────────────┐
│ id │ token │ created │ readonly │ CIDR whitelist │
├────────┼─────────┼────────────┼──────────┼────────────────┤
│ b54f12 │ eyJhb.… │ 2024-03-21 │ no │ │
└────────┴─────────┴────────────┴──────────┴────────────────┘
```
### Removing an Access Token
To remove a token, use its ID from the token list:
```bash
npm token delete tokenId --registry=https://npm.rv-grid.com
```
### Using Tokens in Your Project
You can use access tokens by creating a `.npmrc` file in your project directory with the following content:
```ini
@revolist:registry=https://npm.rv-grid.com
//npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN
```
Replace `YOUR-AUTH-TOKEN` with the token value you received when creating the token.
### Token File Locations
The `.npmrc` file can be stored in several locations, which npm checks in the following order:
1. Project-specific: `/path/to/your/project/.npmrc`
2. User-specific: `~/.npmrc`
3. Global: `$PREFIX/etc/npmrc`
4. npm built-in: `/path/to/npm/npmrc`
### CI/CD Integration
For Continuous Integration/Continuous Delivery (CI/CD) systems, you can use access tokens stored in `.npmrc`. Add the following to your CI/CD environment's `.npmrc` file:
```ini
@revolist:registry=https://npm.rv-grid.com
//npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN
```
### Multi-user Access
For teams working on the same project, you have two options for repository access:
1. **Individual Logins**: Each developer can use their own credentials to access the repository
2. **Shared Tokens**: Create and share access tokens among team members
### Troubleshooting
If you encounter issues with token authentication:
1. Verify your token is valid by running `npm token list`
2. Ensure your `.npmrc` file is properly formatted
3. Check that you're using the correct registry URL
4. Verify your network can access the npm registry
For additional support or questions, please contact our support team.
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# RevoGrid Kanban
URL: https://pro.rv-grid.com/guides/kanban/
Source: src/content/docs/guides/kanban/index.mdx
Description: Render the same RevoGrid source as an accessible, virtualized Kanban board with workflow columns, swimlanes, WIP rules, and customizable cards.
Surface: kanban
`KanbanPlugin` turns canonical `grid.source` rows into workflow cards without creating a second data model. Configured workflow columns become RevoGrid columns and optional swimlanes expand into native card rows. Every workflow cell contains at most one card, so RevoGrid owns both horizontal and vertical virtualization.
## Install
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/kanban
```
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/kanban/styles.css';
```
Keep cards in `grid.source`. Set `grid.kanban` to show the board and set it to `false` to restore the latest cards and table columns.
RevoGrid providers own projected data, columns, dimensions, focus, viewport state, and plugin dependencies.
Reorder cards, move across columns or lanes, drag a multi-selection, validate transitions, and enforce WIP limits.
Replace card, header, lane, empty-state, and drag-preview content while the managed shell preserves interaction and accessibility.
## Minimal setup
```ts
import { KanbanPlugin, type KanbanConfig } from '@revolist/kanban';
const kanban: KanbanConfig = {
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress', wipLimit: 3 },
{ prop: 'done', name: 'Done' },
],
card: { titleField: 'title', descriptionField: 'description' },
};
grid.plugins = [KanbanPlugin];
grid.kanban = kanban;
grid.kanbanCardEditorDialog = {};
grid.source = [
{ id: 'card-1', title: 'First card', status: 'todo', order: 1000 },
];
```
The default fields are `id`, `status`, and `order`. Configure `idField`, `columnField`, or `orderField` when your application uses other names.
`columns` is optional so Kanban can be enabled before workflow metadata or data
loads. An empty configuration and empty source render a stable empty board. If
cards arrive before columns, they remain visible in the managed **Unmapped**
column until the workflow configuration is supplied.
## How the projection works
```txt
canonical grid.source cards
+ KanbanConfig
│
▼
validated card and bucket indexes
│
▼
RevoGrid columns + expandable swimlane card rows
```
Your application owns the card records and persistence. Kanban owns projection, interaction, validation, and local provider updates. Move events describe the exact changes to persist.
## Recommended path
1. Define stable card IDs and explicit [workflow columns](/guides/kanban/data-model/).
2. Add [swimlanes, WIP limits, and card rules](/guides/kanban/swimlanes-wip-rules/) if the workflow needs capacity or policy controls.
3. Configure the [packaged card editor or a replacement UI](/guides/kanban/card-editor/).
4. Connect [events and public methods](/guides/kanban/events-methods/) to application persistence.
5. Add [custom card content and translations](/guides/kanban/customization-i18n/).
6. Verify [accessibility and performance](/guides/kanban/accessibility-performance/) with production-sized data.
Kanban is a presentation plugin rather than an application toolbar. Your application controls the table/Kanban switch by setting `grid.kanban`.
---
# Accessibility And Performance
URL: https://pro.rv-grid.com/guides/kanban/accessibility-performance/
Source: src/content/docs/guides/kanban/accessibility-performance.mdx
Description: Operate Kanban without a pointer and keep large boards responsive through native row virtualization and stable card identity.
Surface: kanban
## Accessibility contract
- The managed card shell exposes selection, read-only, invalid, and drag state without depending on custom card content.
- Each selectable card shell is keyboard focusable. Space picks up or drops, arrows move the proposed target, and Escape cancels. Enter requests the card-edit workflow and Shift+F10 opens the card context menu.
- Ctrl+Space or Cmd+Space toggles the focused card in the managed multi-selection. Shift+Space extends the selection from the current anchor. Keyboard pickup first synchronizes selection when the focused card was not already selected.
- A live region announces selection, pickup, target position, validation failures, completion, and cancellation using `KanbanLabels`.
- Cards expose checkbox selection state, read-only and invalid identity state, plus their supported keyboard shortcuts. Focus uses a persistent high-contrast outline that is distinct from the selected-card treatment.
- Column and swimlane controls have localized expand/collapse names.
- Motion used for card hover and disclosure controls is removed when the operating system requests reduced motion.
- Custom content should use meaningful headings and text, mark nested interactive controls so they do not initiate card movement, and maintain sufficient contrast for rule states.
Automated Kanban browser coverage verifies focus, selection modifiers, keyboard movement, live-region semantics, and reduced-motion CSS. Before release, also test custom cards at 200% and 400% zoom, in forced-colors/high-contrast mode, with keyboard-only navigation, and with the screen reader/browser combinations your application supports.
## Native row virtualization
RevoGrid virtualizes workflow columns and the projected card rows. A cell contains at most one card, avoiding nested virtual scrollers and keeping card focus, drag targets, and row ownership aligned with the grid provider model.
Use `cardRowHeight` to reserve equal space for every card row. Choose a value that fits the number of content bands in your card template; individual swimlane definitions can override it with `height`. RevoGrid virtualizes those rows directly, while card identity and canonical rank remain independent of the rendered row index.
```ts
grid.kanban = {
columns,
cardRowHeight: 176,
customization: { cardContent },
};
```
Card content is contained within its reserved row, so an undersized custom template cannot overlap the next card. Increase `cardRowHeight` when adding metadata rows rather than relying on content overflow.
## Large-board guidance
- Use stable primitive IDs and finite numeric ranks.
- Keep render hooks pure and avoid creating network requests, timers, or global listeners inside templates.
- Put expensive derived values on the card record or memoize them outside the template.
- Configure `cardRowHeight` to fit the complete managed or custom card shell while keeping every projected row uniform.
- For known large server datasets, configure `kanban.remote.total` and `placeholder`; unloaded card rows use the same indexed skeleton pattern as Infinity Scroll.
- In placeholder mode, known positions participate in unfiltered visible counts and total/WIP counts, while placeholders remain non-interactive. Local filters still evaluate the records currently available to the board.
- Indexed page replacement and unfiltered card moves patch affected projection buckets, keeping interaction work independent from a full 100K projection rebuild.
- Persist committed move batches rather than rewriting the entire dataset for every pointer update.
The Kanban acceptance fixture covers 5,000 visible cards and verifies that rendered card DOM stays bounded while drag targets remain correct after virtual scrolling.
---
# Kanban Card Editor
URL: https://pro.rv-grid.com/guides/kanban/card-editor/
Source: src/content/docs/guides/kanban/card-editor.mdx
Description: Add the packaged Kanban CRUD dialog, map default or arbitrary fields, and replace the editor with application UI.
Surface: kanban
`KanbanCardEditorDialogPlugin` is a separate companion plugin that `KanbanPlugin` installs by default. It listens to the existing cancelable create, edit, and delete request events, then uses `KanbanPlugin`'s canonical mutation methods and history integration. The separate Open request remains application-owned for products that provide a distinct read-only detail view.
```ts
import { KanbanPlugin } from '@revolist/kanban';
grid.plugins = [KanbanPlugin];
grid.kanban = kanban;
grid.kanbanCardEditorDialog = {
fields: [{
id: 'points', label: 'Story points', kind: 'number', field: 'points',
validate: (value) => Number(value) < 0 ? 'Story points cannot be negative.' : undefined,
}],
};
```
The packaged form includes title, description, workflow status, swimlane, priority, tags, assignees, progress, start/end schedule, due date, and color. Matching schema IDs override those fields, `hiddenFields` removes them, and `fieldOrder` sets layout order. Additional schemas can map any `ColumnProp`, or use `read` and `write` for composite application records.
Field schemas support text, textarea, number, select, multiselect, tags, checkbox, date, datetime, color, assignees, and custom DOM controls. They can also provide icons, options, placeholders, defaults, parsing, formatting, readonly/required state, synchronous or asynchronous validation, and renderer cleanup lifecycles.
Assignee fields use the same resource picker as Scheduler and Gantt. Supply
schema `options` when your application owns a people directory. If the packaged
field has no explicit options, the editor derives a unique picker catalog from
the canonical Kanban cards through `card.assignees`, or `assigneeField` plus
`assigneeAvatarField`. Explicit options remain authoritative, and existing
mapped selections stay visible even before a directory is loaded.
## Validation
Each visible field runs its required check and synchronous or asynchronous
`validate` callback. Use the editor-level `validate` callback for cross-field or
remote rules; return a form message or a record keyed by field ID.
```ts
grid.kanbanCardEditorDialog = {
fields: [{
id: 'points', field: 'points', kind: 'number', required: true,
validate: async (value) => await api.pointsError(value),
}],
validate: ({ draft }) => draft.startDate > draft.dueDate
? { dueDate: 'Due date must follow the start date.' }
: undefined,
};
```
The validation lifecycle is observable through
`beforekanbancardeditorvalidate`, `beforekanbancardeditorfieldvalidate`,
`kanbancardeditorfieldvalidate`, `kanbancardeditorvalidate`, and
`kanbancardeditorinvalid`. The two `before...` events are cancelable. Their
details expose `setError`, allowing application validators to add field or form
errors without replacing editor persistence. Cancelled or invalid validation
always keeps the editor open and blocks the Kanban mutation.
Replacement editors call `context.controller.validate()` to run the same rules
and events as the packaged dialog. The plugin instance also exposes `validate()`
for an open editor.
## Persistence and identifiers
By default, Save calls `createCard` or `updateCard`, Delete calls `deleteCards`, and every successful operation is undoable. The fallback create ID is a collision-checked string. Use `createCardId` for numeric or domain IDs.
Use `onSubmit` and `onDelete` for remote persistence. Returning `false` tells the plugin that the application handled the mutation. Errors thrown by async hooks remain visible without closing the dialog.
## Replace or disable the editor
Set `grid.kanbanCardEditorDialog = false` or cancel the request event to retain an application-owned workflow. Explicitly registering `KanbanCardEditorDialogPlugin` remains supported and the Kanban dependency lifecycle reuses that instance. A full `customization.editor` DOM renderer receives the draft state and controller, so React, Vue, Angular, and other application UI can mount in place of the packaged modal.
```ts
grid.kanbanCardEditorDialog = {
customization: {
editor: {
mount(container, context) {
return mountApplicationEditor(container, context.controller);
},
},
},
};
```
---
# Customization And i18n
URL: https://pro.rv-grid.com/guides/kanban/customization-i18n/
Source: src/content/docs/guides/kanban/customization-i18n.mdx
Description: Customize Kanban cards, headers, drag previews, empty states, CSS variables, labels, and locale-aware output.
Surface: kanban
## Managed card shell
Kanban owns the outer card shell, focus target, selection state, whole-card drag behavior, rule classes, and accessibility attributes. `customization.cardContent` replaces everything visible inside it.
```ts
customization: {
cardContent: (h, { card, selected, badges }) =>
h('div', { class: 'issue-card' }, [
h('strong', null, card.summary),
h('small', null, `${card.owner} · ${card.points} points`),
selected ? h('span', null, 'Selected') : null,
...badges.map((badge) => h('span', { class: badge.className }, badge.label)),
]),
cardProps: ({ card }) => ({
class: card.blocked ? 'issue-card-shell--blocked' : '',
title: card.summary,
}),
}
```
The same customization object supports `dragPreview`, `swimlaneHeader`,
`swimlaneColumnHeader`, and `emptyState` templates. The swimlane-header context
reports `swimlaneColumnCollapsed`; compact mode keeps a managed vertical title
badge so labels remain readable. Template contexts use canonical cards and
projected metadata; do not query card DOM to recover product state.
## Swimlane header templates
Set `swimlaneTemplate` on an explicit swimlane when that lane needs to own its
complete header layout. This works in both `column` and `top` swimlane layouts:
```ts
grid.kanban = {
...config,
swimlaneField: 'workstream',
swimlaneLayout: 'top',
swimlanes: [
{
id: 'product',
title: 'Product experience',
collapsible: true,
swimlaneTemplate: (h, context) => h('div', { class: 'workstream-header' }, [
h('span', { class: 'workstream-header__icon', 'aria-hidden': 'true' }, '◈'),
h('span', { class: 'workstream-header__kicker' }, 'WORKSTREAM'),
h('strong', null, context.swimlane.title),
h('span', null, `${context.visibleCount} / ${context.totalCount}`),
h('button', {
type: 'button',
onClick: context.toggleCollapsed,
}, context.swimlane.collapsed ? 'Expand' : 'Collapse'),
]),
},
],
};
```
`swimlaneTemplate` fully replaces the managed title, count, WIP badges, and
collapse button for that lane. Render the pieces your product needs and call
`context.toggleCollapsed()` when the custom header exposes collapse. The action
is guarded by the lane's `collapsible` setting. Kanban continues to own the
outer group row, lane identity, configured style, and accessibility label.
A definition-owned template takes precedence over
`customization.swimlaneHeader`. Lanes without one keep the board-wide custom
content or the default header.
## Workflow header templates
Set `columnTemplate` and `columnProperties` directly on a workflow definition,
using the same shape as a RevoGrid column:
```ts
grid.kanban = {
...config,
columns: [
{
prop: 'active',
name: 'Active',
size: 280,
wipLimit: 4,
columnProperties: () => ({
class: 'workflow-header-cell',
style: { backgroundColor: 'var(--active-header)' },
}),
columnTemplate: (h, props, additionalData) => {
const { column, visibleCount, totalCount, overWipLimit, toggleCollapsed } = props.kanban;
const name = column.name ?? String(column.prop);
return h('div', { class: overWipLimit ? 'workflow-header workflow-header--over' : 'workflow-header' }, [
h('strong', null, name),
h('span', null, `${visibleCount} / ${totalCount}`),
h('button', { type: 'button', onClick: toggleCollapsed }, 'Toggle'),
additionalData?.workspaceName
? h('small', null, additionalData.workspaceName)
: null,
]);
},
},
],
};
```
`columnTemplate` fully replaces the inner managed workflow header. Kanban does
not append its default name, count, WIP badge, or collapse button, so render the
pieces your product needs and call `props.kanban.toggleCollapsed()` when the
custom header exposes collapse. Application `additionalData` is passed through
unchanged. RevoGrid's outer resizable header remains managed.
`columnProperties` is composed with Kanban's required header properties through
the shared Pro merging behavior: classes and styles combine, while required
workflow identity and interaction attributes win conflicts. Collapsed body
rails deliberately render `name` (or `String(prop)`) instead of invoking the
header template.
## Default content
Configure `card.titleField` and `card.descriptionField`, or provide formatter callbacks. The title defaults to the `title` field and falls back to the card ID.
Default cards can also render shared progress and assignee content without a
custom template:
```ts
grid.kanban = {
...config,
card: {
titleField: 'name',
startDateField: 'startDate',
endDateField: 'endDate',
dateTimeZone: 'UTC',
progressField: 'percentDone',
assigneeField: 'owners',
assigneeAvatarField: 'ownerAvatars',
},
};
```
Assignee and avatar fields accept either scalar values or parallel arrays. For
The default date range appears directly after the card title and uses the
configured Kanban locale and optional `dateTimeZone`. For fully custom cards,
the managed shell continues to own focus, selection, accessibility, and drag
behavior.
## Labels and locale
English labels are bundled. Pass a partial `labels` object to replace every visible or assistive string used by your product:
```ts
grid.kanban = {
...config,
locale: 'pt-PT',
labels: {
emptyColumn: 'Sem cartões',
dropCardHere: 'Soltar um cartão aqui',
editCard: 'Editar cartão',
addCardHere: 'Adicionar cartão aqui',
deleteCard: 'Eliminar cartão',
cardContextMenu: 'Ações do cartão Kanban',
cardCount: (visible, total) => `${visible} de ${total} cartões`,
moveBlocked: (reason) => `Movimento bloqueado: ${reason}`,
},
};
```
`locale` controls locale-aware query normalization for built-in card search and
the default date-range component. Other number and product-specific display
formatting remains template-owned. Kanban does not automatically download a
translation pack.
## CSS
Import the standalone Kanban stylesheet once:
```ts
import '@revolist/kanban/styles.css';
```
Prefer Kanban CSS variables and classes returned by card rules or customization hooks. Avoid changing managed-shell focus, selection, and drag-state elements in ways that hide their state.
Kanban follows the grid's `material`, `compact`, `darkMaterial`, and
`darkCompact` themes automatically. Its defaults derive from RevoGrid's public
`--revo-grid-*` tokens, while the semantic `--kanban-board-background`,
`--kanban-surface`,
`--kanban-card-background`, `--kanban-border`, `--kanban-text`,
`--kanban-muted`, `--kanban-accent`, and `--kanban-accent-strong` variables let
the host tune either theme without replacing component styles. Drag previews
copy the resolved Kanban tokens, so a body-mounted preview retains the active
dark theme.
---
# Cards, Columns, And Ordering
URL: https://pro.rv-grid.com/guides/kanban/data-model/
Source: src/content/docs/guides/kanban/data-model.mdx
Description: Define canonical Kanban cards, workflow columns, stable ranks, diagnostics, and board projection behavior.
Surface: kanban
## Canonical cards
Kanban reads and updates ordinary RevoGrid source rows. A card needs a stable ID, workflow-column value, and preferably a numeric order:
```ts
grid.source = [
{
workId: 'PAY-104',
summary: 'Retry declined payments',
stage: 'active',
rank: 1000,
team: 'billing',
},
];
grid.kanban = {
idField: 'workId',
columnField: 'stage',
orderField: 'rank',
swimlaneField: 'team',
columns: [
{ prop: 'queued', name: 'Queued' },
{ prop: 'active', name: 'Active' },
{ prop: 'done', name: 'Done' },
],
};
```
When a card has no rank, source order is used. The first move assigns fractional ranks between neighboring cards. If the available numeric gap becomes unsafe, Kanban rebalances only the destination bucket and reports the affected IDs in `kanbancardmove`.
## Shared view fields
Use `kanban.fields` when the same application records also feed Scheduler or
Gantt. The keys describe field meaning and the values name properties in your
source rows:
```ts
grid.kanban = {
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress' },
{ prop: 'done', name: 'Done' },
],
};
```
Kanban defaults these meanings to `id`, `title`, `status`, `startDate`,
`endDate`, `color`, `progress`, and `assignees`. An explicitly mapped shared
key takes precedence over the corresponding `idField`, `columnField`, or
`card.*Field` option. The older options remain available for existing boards.
The mapping only selects properties. It does not convert dates or progress
units. `resourceId` represents one resource identifier; Kanban also continues
to accept the established array form through `card.assigneeField`. Fields that
Kanban does not change, including application metadata, remain on the original
card. Moves, editor changes, remote updates, and undo/redo preserve authored
property names and do not add internal Kanban aliases to source rows.
## Remote card pages
Use `kanban.remote` when the canonical collection should arrive from one
whole-board offset/limit stream:
```ts
const loadCards = async (skip: number, limit: number) => {
const response = await fetch(`/api/cards?skip=${skip}&limit=${limit}`);
const result = await response.json();
return {
data: result.cards,
total: result.total,
};
};
grid.kanban = {
columns,
remote: {
loadData: loadCards,
total: 100_000,
chunkSize: 100,
preloadThreshold: 0.75,
placeholder: (index) => ({
id: `remote-${index}`,
status: statuses[index % statuses.length],
order: Math.floor(index / statuses.length),
}),
},
};
```
With `total` and `placeholder`, Kanban works like Infinity Scroll: it creates the
known number of lightweight positions, renders an animated skeleton for each
unloaded card row, and replaces the requested 100-card slice in place. The
placeholder only needs a unique temporary `id`, workflow column, swimlane when
configured, and rank fields. Its ID may differ from the returned server card ID;
the source offset owns replacement identity. The workflow, swimlane, and rank
values must match the corresponding server card. Scrolling directly to an
unloaded area requests that area's chunk; approaching a loaded chunk boundary
preloads the next one.
Without `placeholder`, remote mode retains append paging: page zero replaces
the current cards and later pages append as the board nears its scroll boundary.
Both modes preserve loaded cards on errors and expose the same inline retry
action.
The result can be a card array or `{ data, total?, hasMore? }`. In append mode,
a returned `total` takes precedence over `hasMore`; otherwise an explicit
`hasMore` wins, then the configured or previously returned total is consulted,
and only then does a short page end the stream. Indexed mode treats the
configured `total` as its fixed layout: every request must return its complete
requested slice, and response `total` or `hasMore` values must agree with that
layout. A mismatch keeps the slice retryable and emits the remote error event.
Local update and move operations preserve indexed server positions. Create and
delete are rejected in indexed mode because they would shift every later
offset; perform those operations on the server and call `refreshRemote()`.
Replacing `grid.source` externally restarts indexed loading from page zero. In
append mode, local CRUD does not rewind the cursor and an external source
replacement adopts its length as the next offset. Both behaviors invalidate a
late response.
The loader type and indexed chunk behavior are shared with
`InfinityScrollPlugin`, so the same application adapter can power a table or
Kanban view. Kanban needs the additional placeholder factory because an
unloaded card's workflow column and optional swimlane cannot be inferred from a
global total. The two plugins should not own one grid simultaneously because
Kanban projects cards into its own virtual rows.
## Workflow columns
`kanban.columns` is the workflow schema; `grid.columns` remains the canonical
table/card-field schema. A card belongs to a workflow column when
`card[columnField] === column.prop`.
Workflow definitions reuse the safe RevoGrid header and sizing subset and add
Kanban behavior such as WIP limits, collapse state, transition rules, metadata,
and card filtering.
The array is optional and defaults to `[]`. This supports loading workflow
metadata and cards independently: no columns plus no cards is an empty board,
while cards without configured columns use the managed **Unmapped** column by
default. Supplying workflow columns later rebuilds the projection from the same
canonical `grid.source` records.
```ts
columns: [
{ prop: 'queued', name: 'Queued', size: 260 },
{
prop: 'active',
name: 'Active',
minSize: 240,
maxSize: 360,
wipLimit: 4,
allowedFrom: ['queued', 'active'],
collapsible: true,
},
]
```
Projected workflow columns keep the exact authored `prop`; no synthetic grid
property is introduced. If `name` is omitted, Kanban displays `String(prop)`.
The default `size` is 288 and `minSize` is 220. The core column-move plugin
handles header dragging; `kanbancolumnorderchange` exposes the resulting prop
order for persistence.
Every workflow column is collapsible by default. Set `collapsible: false` to lock a column open, or use `collapsed: true` for its initial state. At runtime, `setColumnCollapsed(prop, collapsed)` changes one column and `getCollapsedColumnProps()` returns the active collapsed set. A collapsed column remains visible as a narrow rail with a vertical resolved-name badge, so users can restore it without losing board context.
## Unknown and invalid data
- Unknown statuses render in a localized **Unmapped** column by default. Set `unmappedColumn: false` only when the host deliberately hides them.
- Missing or duplicate card IDs produce blocking diagnostics. Those cards remain visible but cannot be moved.
- Duplicate column props produce a blocking configuration diagnostic.
- Invalid numeric ranks fall back to source order and produce a diagnostic.
Listen to `kanbandiagnostics` or call `getDiagnostics()` on the plugin during development and after remote data refreshes.
## Counts
Column and swimlane headers distinguish:
- **visible count**: cards passing RevoGrid filters, Kanban search, and the optional predicate;
- **total count**: all canonical cards in that bucket.
WIP limits always use total counts so a filter cannot accidentally permit over-capacity work.
In append mode, both counts describe the cards loaded so far. In indexed mode,
known placeholder positions participate in total and visible counts, so loading
a page does not change an unfiltered header count. Placeholders remain
non-interactive, and active filters can exclude positions that cannot match.
Enforce server-side rules in the application whenever Kanban does not have the
authoritative records needed for the decision.
---
# Events And Methods
URL: https://pro.rv-grid.com/guides/kanban/events-methods/
Source: src/content/docs/guides/kanban/events-methods.mdx
Description: Validate and persist Kanban moves, observe selection and diagnostics, request CRUD flows, and call public plugin methods.
Surface: kanban
['gridedit', 'Unified committed card changes from EventManager; inspect kanban-card-change and typed domainChanges.'],
['beforekanbancardmove', 'Cancelable proposal with cards, previous locations, target, reason, and proposed records.'],
['kanbancardmove', 'Committed moved cards, every changed canonical record, locations, and destination-bucket rebalance IDs.'],
['kanbanselectionchange', 'Selected card IDs/records and replace, toggle, range, clear, or API reason.'],
['kanbancolumnorderchange', 'Runtime workflow column order after native header drag or API movement.'],
['kanbandropstatechange', 'Current valid, invalid, or order-preserving no-op target state and localized reason.'],
['kanbandiagnostics', 'Current source/config diagnostics.'],
['kanbancardcreaterequest', 'Host-owned create workflow request.'],
['kanbancardopenrequest', 'Host-owned card detail request; the packaged editor does not consume it.'],
['kanbancardeditrequest', 'Host-owned card editor request.'],
['kanbancarddeleterequest', 'Host-owned delete workflow request.'],
['beforekanbancardcreate / kanbancardcreate', 'Cancelable create proposal and committed canonical card.'],
['beforekanbancardupdate / kanbancardupdate', 'Cancelable update proposal and committed card with previous record.'],
['beforekanbancarddelete / kanbancarddelete', 'Cancelable atomic delete proposal and committed card batch.'],
['beforekanbancardeditorvalidate', 'Cancelable start of packaged or replacement-editor validation; listeners can add errors.'],
['beforekanbancardeditorfieldvalidate', 'Cancelable per-field validation proposal with value and setError.'],
['kanbancardeditorfieldvalidate', 'Committed valid, invalid, skipped, or cancelled result for one editor field.'],
['kanbancardeditorvalidate', 'Completed editor validation result with valid, cancelled, and errors.'],
['kanbancardeditorinvalid', 'Validation completion that blocked submission because errors or cancellation remain.'],
['kanbanremoteloadstart', 'A whole-board page request started with skip, limit, reason, and current remote state.'],
['kanbanremoteloadsuccess', 'A page was committed to canonical cards; detail includes the returned cards and updated state.'],
['kanbanremoteloaderror', 'A page failed without removing loaded cards; detail includes the error and retryable state.'],
];
## Get the plugin
```ts
import { KanbanPlugin } from '@revolist/kanban';
const plugins = await grid.getPlugins();
const kanban = plugins.find((plugin) => plugin instanceof KanbanPlugin);
```
Public methods are `getCards`, `getConfig`, `getProjection`, `createCard`, `updateCard`, `deleteCards`, `moveCards`, `getSelectedCardIds`, `setSelectedCardIds`, `revealCard`, `getColumnOrder`, `setColumnOrder`, `getCollapsedColumnProps`, `setColumnCollapsed`, `getSwimlaneColumnCollapsed`, `setSwimlaneColumnCollapsed`, `refresh`, `getDiagnostics`, `getRemoteState`, `loadMoreCards`, `refreshRemote`, `requestCreateCard`, `requestOpenCard`, `requestEditCard`, and `requestDeleteCards`. Obtain the plugin from RevoGrid rather than installing reverse callbacks on the grid element.
`await kanban.revealCard(cardId)` scrolls a currently rendered card's native
grid row and workflow column into view. It resolves to `false` when the card is
missing, filtered, or hidden by collapsed presentation state.
## Event reference
Event constants such as `BEFORE_KANBAN_CARD_MOVE_EVENT` and `KANBAN_CARD_MOVE_EVENT` are exported for refactor-safe listeners.
Every successful create, update, delete, or move also reaches the shared
`gridedit` stream with `kanban-card-change` in `eventTypes`. Its ordered
`domainChanges` contains one `kanban-card` entry per changed canonical card,
including cards re-ranked during a move. Each entry provides the action, card
ID, previous card, and committed card. Create uses a null previous card and
delete uses a null committed card.
`data`, `previousData`, and `models` contain only ordinary row/field patches for
cards that retain a stable canonical source index. Creates, deletes, and
position changes without a safe grid coordinate remain in `domainChanges`;
compact undo/redo state lives separately in `history`.
The Kanban `beforekanban*` events remain the cancellation boundary. A Kanban
`gridedit` has `sourceMutation: 'producer'`, is observational, and calling
`preventDefault()` does not roll back the already committed mutation.
Passing `skipHistory: true` to a mutation still publishes this event with
`recordHistory: false`, but History deliberately creates no undo entry.
Remote loading exports `KANBAN_REMOTE_LOAD_START_EVENT`,
`KANBAN_REMOTE_LOAD_SUCCESS_EVENT`, and `KANBAN_REMOTE_LOAD_ERROR_EVENT`.
Call `getRemoteState()` for `loading`, `nextOffset`, `hasMore`, `total`, and
`error`; `loadMoreCards()` requests the next page and `refreshRemote()` clears
cards and history, or reinitializes configured placeholders, before loading
page zero. Both loading methods return a
promise and deduplicate concurrent requests.
Editor validation constants are exported with
`KanbanCardEditorDialogPlugin`. `beforekanbancardeditorvalidate` and
`beforekanbancardeditorfieldvalidate` are cancelable; all other validation
events report committed results. Cancelling validation always blocks submit.
## Cancel and persist a move
```ts
grid.addEventListener(BEFORE_KANBAN_CARD_MOVE_EVENT, (event) => {
if (!permissions.canMove(event.detail.cardIds, event.detail.target)) {
event.preventDefault();
}
});
grid.addEventListener(KANBAN_CARD_MOVE_EVENT, async (event) => {
await api.saveCards(event.detail.changedCards);
});
```
Before-move listeners must make their cancel decision synchronously. Resolve remote authorization into local policy state before dragging begins.
CRUD request events remain the stable integration point. With `KanbanCardEditorDialogPlugin` installed they open the packaged workflow; without it, when it is disabled, or when a listener cancels the request, the application remains responsible. Committed mutation events are suitable for synchronizing remote state.
---
# Kanban Examples
URL: https://pro.rv-grid.com/guides/kanban/examples/
Source: src/content/docs/guides/kanban/examples.mdx
Description: Live RevoGrid Kanban examples for a basic board, swimlanes and WIP, custom cards and i18n, and persistence events.
Surface: kanban
## Basic workflow
The source rows remain ordinary application cards. Drag between columns or reorder within a workflow column.
## Swimlanes, rules, and blocking WIP
This example uses explicit team lanes, per-lane WIP limits, allowed origins, and an urgent-card rule.
## Custom cards and labels
The managed shell retains selection and drag behavior while the content, properties, and Portuguese labels are application-defined.
## Cancelable moves and persistence payloads
The locked card is rejected before mutation. Successful moves show the payload a persistence adapter receives.
These focused portal examples complement the full `kanban-board` demo, which is available in the examples catalog for TypeScript, React, Vue 3, and Angular. All variants use the same grid properties and `@revolist/kanban` imports.
---
# Filtering, History, And Application State
URL: https://pro.rv-grid.com/guides/kanban/filtering-history-state/
Source: src/content/docs/guides/kanban/filtering-history-state.mdx
Description: Combine RevoGrid filters with Kanban search, undo card moves, persist board state, and restore the table view.
Surface: kanban
## Filtering and search
Core RevoGrid filters are evaluated against canonical cards and the host's captured table columns. Kanban then composes its lightweight search and predicate:
```ts
grid.kanban = {
...config,
filter: {
query: searchInput.value,
fields: ['title', 'owner', 'labels'],
predicate: (card) => !card.archived,
},
};
```
A card must pass all three layers: RevoGrid filters, query matching, and the predicate. Search defaults to the configured card title field.
Header counts expose visible and total cards when filters differ. WIP limits always count all canonical cards.
## Undo and redo
Kanban coordinates with `EventManagerPlugin` and `HistoryPlugin`. Each successful move or editor CRUD action contributes normal stable row patches plus typed card changes to the shared `gridedit` stream. Replay-only card state is kept in separate keyed `history` metadata. With EventManager collection enabled, consecutive operations merge by card ID into one transaction and one history step while preserving each card's earliest prior state and latest committed state. Undo/redo is replayed through Kanban rather than against projected lane rows, including source-length changes.
History is not cleared merely because filtering, sorting, or board projection refreshes. Replacing `grid.source` with an externally authored dataset clears Kanban history so an old move cannot overwrite newer host data.
## Persistence ownership
Listen to `kanbancardmove` and persist `event.detail.changedCards` in one transaction. This atomic list includes the moved cards and any destination-bucket cards whose ranks changed during a rebalance. `event.detail.cards` contains only the cards explicitly moved, while `rebalancedCardIds` identifies the subset re-ranked as part of the commit.
Use `beforekanbancardmove` for asynchronous-policy caches or synchronous authorization checks. Call `preventDefault()` to cancel before local state changes.
## Switching views
```ts
// Table to Kanban
grid.kanban = kanbanConfig;
// Kanban to table
grid.kanban = false;
```
Disabling Kanban restores the latest canonical source, latest host column definitions, and relevant table state. Sorting is not used for card order while the board is active; `orderField` remains authoritative. Re-enabling projects the current source again.
---
# Drag, Drop, And Selection
URL: https://pro.rv-grid.com/guides/kanban/interaction/
Source: src/content/docs/guides/kanban/interaction.mdx
Description: Move and select Kanban cards with pointer, touch, keyboard, auto-scroll, and atomic multi-card operations.
Surface: kanban
## Card movement
Cards can be reordered within a workflow column or moved across columns and swimlanes. The complete non-interactive card surface starts a pointer drag and the card itself is the keyboard pickup target. Pointer capture keeps the drag active outside a card, and drop indicators plus localized announcements expose the proposed target.
During a drag, Kanban auto-scrolls the board horizontally and RevoGrid vertically. Filtered cards remain in the canonical bucket; visible cards act as insertion anchors so hidden records keep deterministic ranks.
### Touch and mobile behavior
Kanban uses Pointer Events for mouse, pen, and touch rather than browser-native HTML drag-and-drop. A tap selects the card without creating a drag preview. Once touch movement crosses the drag threshold, pointer capture keeps the operation active and horizontal edge auto-scroll exposes off-screen workflow columns. Browser `pointercancel` events remove the preview and drop feedback without committing a move.
Draggable cards use `touch-action: pan-y`, so a vertical swipe can continue to scroll a long board from the card surface. A primarily horizontal gesture starts cross-column movement. Because mobile browsers arbitrate vertical gestures before application code, use the context menu, keyboard, or `moveCards()` API when an exact vertical reorder gesture is claimed by native scrolling.
Keep the grid sized to its container on phones; workflow columns retain their configured widths and scroll inside RevoGrid rather than compressing card content. Validate production integrations on physical iOS Safari and Android Chrome in addition to automated mobile emulation.
Empty alignment cells are not drop zones. A column exposes targets on its existing cards and once in the first empty cell immediately after its final visible card, preventing repeated empty placeholders across the swimlane.
## Multiple selection
- Click replaces the selection.
- Cmd/Ctrl-click toggles one card.
- Shift-click selects a contiguous range in board reading order.
- Dragging a selected card moves the entire selection.
- Dragging an unselected card first replaces the selection with that card.
A selection can span several columns and swimlanes. The move is atomic and preserves the selected cards' prior board reading order.
When filters change, selection is reduced to visible cards. Removed source rows are always pruned.
## Keyboard movement
Focus the card, then:
| Key | Action |
| --- | --- |
| Space | Pick up or drop the selected card group. |
| Arrow keys | Move the proposed target through positions, columns, and lanes. |
| Escape | Cancel the active keyboard drag. |
The live region announces pickup, target changes, validation failures, cancelation, and completion. Application card content remains inside the managed shell so custom templates cannot remove these interactions.
## Card context menu
Right-click a card or focus it and press the Context Menu key/Shift+F10. Kanban uses the same shared context-menu surface as Scheduler and Gantt, including Font Awesome SVG icons, keyboard navigation, nested menus, dividers, viewport-aware positioning, and theme colors.
The built-in menu provides:
- A single Edit request action.
- Move destinations grouped by workflow column and swimlane. If the invoked card belongs to the current multi-selection, the complete selection moves atomically.
- Add card here, scoped to the current column and swimlane.
- A visually separated destructive Delete request.
Move commands use the same rules, transitions, WIP checks, history entry, and persistence events as drag-and-drop. CRUD entries always emit the matching cancelable request event. Register `KanbanCardEditorDialogPlugin` to handle those requests with the packaged schema-driven editor, or omit/disable it when the host owns dialogs and persistence. The public Open request is still available for an application-owned detail view, but it is not duplicated in the packaged menu or editor. Hide any request action your application does not handle so the menu advertises only working capabilities.
Disable the generated entries or hide individual commands without replacing an application row menu:
```ts
grid.kanban = {
...config,
contextMenu: {
includeHostItems: true,
hidden: {
edit: ({ card }) => !permissions.canEdit(card),
create: ({ column, swimlane }) => !permissions.canCreate(column.prop, swimlane.id),
delete: ({ cardIds }) => !permissions.canDelete(cardIds),
},
},
};
```
Each `hidden` entry accepts a boolean or a predicate evaluated when the menu opens. The predicate receives the primary `card`, invoked `cardIds`, `column`, and `swimlane`. Set `contextMenu: false` to remove Kanban actions entirely. Visible strings remain configurable through `labels`, while `includeHostItems` composes application `rowContextMenu` entries. Host-owned `ContextMenuPlugin` instances are preserved across activation and teardown.
## Programmatic movement
Locate the plugin through RevoGrid's plugin collection and call `moveCards`:
```ts
const plugins = await grid.getPlugins();
const kanban = plugins.find((plugin) => plugin instanceof KanbanPlugin);
await kanban?.moveCards(
['CARD-12', 'CARD-19'],
{ columnProp: 'review', swimlaneId: 'platform', index: 0 },
{ reason: 'api' },
);
```
Programmatic moves use the same validation, events, provider update, ranking, and history path as pointer and keyboard moves.
---
# Kanban Limitations
URL: https://pro.rv-grid.com/guides/kanban/limitations/
Source: src/content/docs/guides/kanban/limitations.mdx
Description: Understand the intentional v1 boundaries for editing, remote data, dragging, view switching, sorting, and localization.
Surface: kanban
Kanban v1 intentionally keeps application policy and remote persistence outside the rendering plugin.
| Area | v1 behavior |
| --- | --- |
| View switcher | The host sets `grid.kanban`; no built-in table/Kanban toolbar is rendered. |
| Card forms | A separate opt-in editor plugin supplies local CRUD. Hosts can disable, intercept, schema-customize, or completely replace it and remain responsible for remote persistence. |
| Remote data | `kanban.remote` loads one whole-board offset/limit stream. A known `total` plus placement `placeholder` enables fixed Infinity-style indexed skeleton chunks; otherwise pages append. Indexed create/delete must happen on the server followed by `refreshRemote()` so offsets stay stable. Filtering stays local, and the application owns persistence and server-wide validation. |
| External drag | Cross-board, cross-window, file, and third-party drag targets are not supported. |
| Swimlane order | Configuration-owned; swimlanes cannot be drag-reordered. |
| Column order | Uses RevoGrid's core column move behavior. Pointer/touch capabilities follow that plugin; programmatic ordering remains available. |
| Sorting | Table sorting does not reorder board cards. `orderField` is authoritative while Kanban is active. |
| Translation packs | English is bundled; applications provide partial or complete translated labels. |
| Asynchronous validation | `beforekanbancardmove` cancelation is synchronous. Cache remote permissions before interaction. |
Card pointer interaction supports mouse, pen, and touch. Keyboard card movement uses the focusable managed card shell and does not depend on browser-native HTML drag-and-drop.
Unknown workflow values use the **Unmapped** column by default, while invalid or duplicate card identities remain visible but immovable. Treat diagnostics as data-quality errors rather than hiding affected records.
Kanban remote loading and `InfinityScrollPlugin` share the same generic loader
contract and fixed indexed chunks. Kanban additionally needs a lightweight
placement placeholder because a global total cannot identify an unloaded
card's workflow column or swimlane. Do not enable both loaders on the same
Kanban grid: Kanban must retain ownership of the projected row provider while
its remote controller owns canonical cards. If multiple row-source owners are
configured, the first active owner remains enabled and the later plugin
disables itself with a console warning.
---
# Swimlanes, WIP, And Rules
URL: https://pro.rv-grid.com/guides/kanban/swimlanes-wip-rules/
Source: src/content/docs/guides/kanban/swimlanes-wip-rules.mdx
Description: Group cards into lanes, define capacity policies, validate transitions, and apply ordered visual and permission rules.
Surface: kanban
Use swimlanes when each workflow column must be split by team, class of service, release, product area, or another card field.
## Swimlane placement
Swimlane labels render in a dedicated row-header column by default. Use
`swimlaneLayout: 'top'` to render a compact header across the workflow columns
before each lane instead. Both layouts keep counts, custom header content, and
per-lane collapse controls.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlaneLayout: 'top', // use 'column' for the dedicated label column
swimlanes: [
{
id: 'product',
title: 'Product team',
collapsible: true,
swimlaneTemplate: (h, context) => h('div', { class: 'product-lane' }, [
h('strong', null, context.swimlane.title),
h('span', null, `${context.visibleCount} / ${context.totalCount}`),
h('button', { type: 'button', onClick: context.toggleCollapsed }, 'Toggle'),
]),
},
{ id: 'platform', title: 'Platform team', collapsible: true },
],
};
```
`column` is the backward-compatible default. `swimlaneColumn` width and collapse
settings apply only to that dedicated-column layout. A lane's
`swimlaneTemplate` fully controls its header content in either layout while
Kanban keeps the outer lane row and identity semantics.
## Derived or explicit lanes
Setting only `swimlaneField` derives lanes in first-appearance order. Provide definitions when labels, order, heights, collapse controls, styles, or lane-specific WIP limits matter.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlanes: [
{ id: 'web', title: 'Web team', wipLimits: { active: 2 } },
{ id: 'api', title: 'API team', collapsed: false },
],
};
```
Swimlane order is configuration-owned and cannot be dragged in v1.
For a board that does not use swimlanes, disable the row-header presentation
directly in Kanban configuration:
```ts
grid.kanban = {
columns,
swimlaneColumn: false,
};
```
Cards still use the internal implicit lane for ordering and virtualization, but
no swimlane label or lane-collapse control is rendered.
## Collapsible swimlane labels
The swimlane row-header column can collapse independently from the lanes. In
its compact state it shrinks to a narrow rail and renders lane titles as
vertical badges; cards and lane heights are unchanged.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlaneColumn: {
title: 'Teams',
width: 176,
collapsedWidth: 52,
collapsible: true,
collapsed: false,
},
};
```
Use `getSwimlaneColumnCollapsed()` and `setSwimlaneColumnCollapsed(...)` on the
plugin instance when an application toolbar also needs to control the state.
This is separate from each swimlane definition's `collapsed` state, which
reduces that lane's projected row height.
## WIP behavior
Set a column `wipLimit`, or a lane `wipLimits` entry for a more specific bucket limit. `wipBehavior` defaults to `warn`:
- `warn` permits the move and renders/announces the over-limit state.
- `block` rejects the entire proposed move.
Multi-card moves validate the final batch count, not each card independently. A failed card or capacity check cancels the full move.
## Movement permissions
Use `allowedFrom` for simple workflow transitions and `rules` for application policy:
```ts
rules: {
canDrag: (card) => card.locked ? 'Locked by another user' : true,
canSelect: (card) => !card.archived,
canEdit: (card) => card.permissions.includes('edit'),
canDrop: ({ cards, targetColumn }) =>
cards.some((card) => card.requiresApproval) && targetColumn.prop === 'done'
? 'Approval is required'
: true,
}
```
Return `false` or a localized reason string to reject an action.
## Visual card rules
`cardRules` is an ordered declarative layer for badges, classes, styles, and permissions. Matching classes and badges accumulate, later styles override earlier properties, and any explicit permission denial wins.
```ts
cardRules: [
{
id: 'urgent',
when: ({ card }) => card.priority === 'urgent',
result: {
className: 'card--urgent',
badges: [{ label: 'Urgent' }],
style: { '--kanban-card-accent': '#dc2626' },
},
},
]
```
---
# Migrating to 2+
URL: https://pro.rv-grid.com/guides/migration-v2/
Source: src/content/docs/guides/migration-v2.mdx
Description: Step-by-step guide for upgrading to RevoGrid Pro 2+
This guide covers every breaking change introduced in **v2.0** of `@revolist/revogrid-pro` and
the standalone product packages, and explains what to update in your codebase.
v2.0 is a **major release** with breaking changes in imports, plugin configuration, plugin
authoring, CSS setup, and product package structure. Read through the checklist below before
upgrading.
---
## Quick migration checklist
1. Upgrade `@revolist/revogrid` core to `^4.21.4`
2. Replace all sub-path imports with flat imports from `@revolist/revogrid-pro`
3. Replace hardcoded event strings with named constants from `@revolist/revogrid-pro`
4. Import `revogrid-pro.css` in your application entry point
5. Move plugin configuration from `grid.additionalData` to direct grid properties
6. Update custom plugins to extend `CorePlugin` instead of `BasePlugin`
7. Add `@revolist/revogrid-pro` alongside each standalone product
8. Move Pivot and Gantt imports to `@revolist/pivot` and `@revolist/gantt`
9. Update Excel export integrations that relied on bundled SheetJS or bundled workbook import parsing when upgrading from `2.0.8`
10. Rename authored Gantt task `status` fields to `workflowStatus` and treat the Status column as calculated/read-only
11. When upgrading from 2.7 to 2.8, replace model-backed formatting selections and version 1 formatting state with physical coordinates and state version 2
12. Replace `ClipboardJsonPlugin` and `$rv-parse_` clipboard text with `ClipboardPlugin` and the smart clipboard document
13. Move Event Scheduler event arrays from `eventSchedulerEvents` to `source` when convenient; the legacy property remains a deprecated compatibility alias
---
## 2.7 → 2.8 breaking changes
### Event Scheduler uses `source` for canonical events
Put the event entity array in the standard RevoGrid `source` property. The
deprecated `eventSchedulerEvents` property is still accepted and copies its
value into `source`, so existing integrations can migrate incrementally:
```ts
// Legacy compatibility alias
grid.eventSchedulerEvents = events;
// Preferred canonical API
grid.source = events;
```
Generated calendar slots and resource rows are internal projection data and no
longer replace the public property. After an accepted create, edit, delete,
undo/redo, or remote result, `grid.source` contains the latest canonical event
list. Existing scheduler mutation events still provide `detail.events`; use
those events to update React, Vue, or Angular application state and to persist
changes.
### DataGridFormattingPlugin is coordinate-only
v2.8 removes the formatting compatibility layer completely. The following 2.7
contracts are no longer accepted as formatting operation or state inputs:
- model-backed `{ scope, rows, columns, cells }` selections
- `getFormat(model, prop, rowType)`
- `getRowKey` and `rowKeyProp`
- formatting runtime state version 1 and row-key preset state
Cell operations now use inclusive, zero-based physical coordinates in the
selected row and column `source` stores:
```ts
plugin.apply({
scope: 'cells',
rows: [invoice],
columns: [amountColumn],
cells: [{ model: invoice, column: amountColumn, rowType: 'rgRow' }],
}, moneyFormat);
const format = plugin.getFormat(invoice, 'amount', 'rgRow');
```
```ts
plugin.apply({
start: { row: 4, column: 2 },
rowType: 'rgRow',
colType: 'rgCol',
}, moneyFormat);
const format = plugin.getFormat({ row: 4, column: 2 });
```
Complete-column operations use a separate coordinate target so their format
also applies to future rows:
```ts
plugin.apply({
scope: 'columns',
ranges: { start: 2, end: 4, colType: 'rgCol' },
}, moneyFormat);
const format = plugin.getColumnFormat({ column: 2, colType: 'rgCol' });
```
Complete-row operations are symmetrical and also apply to columns added later:
```ts
plugin.apply({
scope: 'rows',
ranges: { start: 4, end: 6, rowType: 'rgRow' },
}, moneyFormat);
const format = plugin.getRowFormat({ row: 4, rowType: 'rgRow' });
```
`DataGridFormattingRuntimeState` now accepts only `version: 3`. v2.8 does not
read or infer formatting from version 1 or version 2 state. Applications must migrate
persisted state before assigning it to `grid.dataGridFormatting` or calling
`plugin.setState()`. The v3 format stores `row`, `column`, `rowType`, and
`colType` directly; `getRowKey` and `rowKeyProp` are unnecessary because
formatting remains attached to the physical coordinate when source objects are
replaced.
Value formats are now a strict discriminated union. Built-in formats use
`{ kind: 'preset', preset: 'currency', ... }`; exact Excel-compatible codes use
`{ kind: 'code', formatCode: '#,##0.00' }`. The ambiguous legacy
`{ preset, formatCode }` object is rejected. The RevoGrid rich clipboard payload
is correspondingly version 2; version 1 payloads are not inferred as v2.
`DataGridFormattingPlugin` no longer auto-installs `EventManagerPlugin`. Add it
explicitly when formatting mutations, managed clipboard paste, or accepted
autofill formatting must flow through the cancelable `gridedit` event. Add
`AutoFillPlugin` alongside Event Manager when fill-handle gestures should copy
formatting as well as values. `HistoryPlugin` continues to install Event Manager
automatically for undo and redo.
Bold cell formatting now uses the standard CSS `700` weight. This also makes
bold formatting imported from Excel visually match the source workbook.
### ClipboardJsonPlugin was replaced by ClipboardPlugin
v2.8 removes `ClipboardJsonPlugin`, `legacyJsonText`, and automatic decoding of
the `$rv-parse_` text marker. The marker overloaded `text/plain`, exposed an
internal serialization convention to external applications, and duplicated the
versioned smart clipboard document.
```ts
import { ClipboardJsonPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardJsonPlugin];
```
```ts
import {
ClipboardPlugin,
DataGridFormattingPlugin,
} from '@revolist/revogrid-pro';
// Values and application-defined clipboard formats:
grid.plugins = [ClipboardPlugin];
// Or the recommended rich-formatting setup:
grid.plugins = [DataGridFormattingPlugin];
```
The smart plugin preserves objects, formulas, formatting, structures, and sparse
multi-range geometry in `application/x-revogrid-clipboard+json`, with HTML and
plain-text fallbacks for Excel and other applications. Decode any stored
`$rv-parse_` values in application code before assigning or pasting them; v2.8
treats marker text literally.
v2.8 also removes the clipboard implementation markers
`SMART_CLIPBOARD_PLUGIN`, `SmartClipboardRuntime`, and
`isSmartClipboardRuntime`. Application code that needs the installed plugin
instance should use the plugin class instead:
```ts
const clipboard = (await grid.getPlugins())
.find(plugin => plugin instanceof ClipboardPlugin);
```
Custom Pro plugins can use the equivalent provider-backed lookup:
`providers.plugins.getByClass(ClipboardPlugin)`.
---
## 1. Core dependency version
v2.0 requires `@revolist/revogrid` **4.21.4 or later**. Earlier core versions are not supported.
```bash
npm install @revolist/revogrid@^4.21.4
# or
pnpm add @revolist/revogrid@^4.21.4
```
---
## 2. Import paths — flat surface only
v1.x supported sub-path imports that pointed to internal modules. These paths are **removed** in v2.0.
All exports are now available from the single package entry point.
```ts
import { commonAggregators } from '@revolist/revogrid-pro/aggregations';
import { CorePlugin } from '@revolist/revogrid-pro/core';
import { FOCUS_APPLY_EVENT } from '@revolist/revogrid-pro/events';
import { createMergedData } from '@revolist/revogrid-pro/utils';
```
```ts
import {
commonAggregators,
CorePlugin,
FOCUS_APPLY_EVENT,
createMergedData,
} from '@revolist/revogrid-pro';
```
---
## 3. Event strings → named constants
v2.0 exports more than 135 named event constants. Replace every hardcoded event string with the
corresponding export to benefit from type checking and rename refactoring.
```ts
grid.addEventListener('beforecellfocus', handler);
grid.addEventListener('beforefocuslost', handler);
grid.addEventListener('beforekeydown', handler);
grid.addEventListener('aftersourceset', handler);
```
```ts
import {
EVENT_BEFORE_CELL_FOCUS,
EVENT_BEFORE_FOCUS_LOST,
BEFORE_KEYDOWN_EVENT,
AFTER_SOURCE_SET_EVENT,
} from '@revolist/revogrid-pro';
grid.addEventListener(EVENT_BEFORE_CELL_FOCUS, handler);
grid.addEventListener(EVENT_BEFORE_FOCUS_LOST, handler);
grid.addEventListener(BEFORE_KEYDOWN_EVENT, handler);
grid.addEventListener(AFTER_SOURCE_SET_EVENT, handler);
```
The full list of available constants is exported from `@revolist/revogrid-pro`.
---
## 4. CSS — required import
Starting in v2.0, the Pro plugin styles are **not** injected automatically. You must import the
stylesheet once in your application entry point.
```ts
// main.ts (or your app entry)
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
```
```html
```
Import the stylesheet for each standalone product you use:
```ts
import '@revolist/pivot/styles.css';
import '@revolist/gantt/styles.css';
```
---
## 5. Plugin configuration — direct grid properties
v2.0 introduces a direct property system for Pro and standalone product plugin configuration. Plugin options
should now be assigned to their own grid property instead of being grouped under
`grid.additionalData`.
`additionalData` is deprecated for plugin configuration. It is still read as a legacy fallback during
the v2 migration window, but new code should use direct properties.
This is a breaking change because plugins no longer treat one large shared object as the primary
configuration surface. The old pattern made `additionalData` grow too quickly: unrelated plugins
shared the same object, and changing one key could notify every plugin observing `additionalData`.
That could cause unrelated state updates and unnecessary redraws. Direct properties make each plugin
observe only the configuration it owns.
```ts
grid.additionalData = {
pivot,
pagination,
tree: {
expandedRowIds,
},
rowExpand: {
expandedRows,
},
rowAutoSize: {
mode: 'auto',
},
eventManager: {
applyEventsToSource: true,
},
};
```
```ts
grid.pivot = pivot;
grid.pagination = pagination;
grid.tree = {
expandedRowIds,
};
grid.rowExpand = {
expandedRows,
};
grid.rowAutoSize = {
mode: 'auto',
};
grid.eventManager = {
applyEventsToSource: true,
};
```
For framework wrappers, bind the plugin property directly:
```tsx
```
```vue
```
```ts
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
@Component({
selector: 'my-grid',
standalone: true,
imports: [RevoGrid],
// Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
schemas: [NO_ERRORS_SCHEMA],
template: `
`,
})
export class MyGridComponent {}
```
Common replacements:
| v1.x `additionalData` key | v2.0 property |
|---|---|
| `additionalData.pivot` | `grid.pivot` |
| `additionalData.pagination` | `grid.pagination` |
| `additionalData.tree` | `grid.tree` |
| `additionalData.rowExpand` | `grid.rowExpand` |
| `additionalData.rowAutoSize` | `grid.rowAutoSize` |
| `additionalData.masterRow` | `grid.masterRow` |
| `additionalData.transpose` | `grid.transpose` |
| `additionalData.rowContextMenu` | `grid.rowContextMenu` |
| `additionalData.columnContextMenu` | `grid.columnContextMenu` |
| `additionalData.eventManager` | `grid.eventManager` |
| `additionalData.infinityScroll` | `grid.infinityScroll` |
| `additionalData.cellMerge` | `grid.cellMerge` |
| `additionalData.excel` | `grid.excel` |
| `additionalData.hiddenColumns` | `grid.hideColumns` |
Core grid inputs such as `source`, `columns`, `plugins`, `theme`, `stretch`, `rowDefinitions`, and
`rowHeaders` are already direct RevoGrid properties. Keep binding those directly as before.
---
## 6. Custom plugins — extend `CorePlugin`
In v2.0 the recommended base class for all Pro plugins changed from `BasePlugin` (re-exported from
`@revolist/revogrid`) to `CorePlugin` (exported from `@revolist/revogrid-pro`). `CorePlugin` adds:
- `observeAttribute(name, callback)` — reacts to DOM attribute mutations
- `observeProperty(name, callback)` — reacts to direct JS property assignments
```ts
import { BasePlugin } from '@revolist/revogrid';
export class MyPlugin extends BasePlugin {
constructor(grid, providers) {
super(grid, providers);
// plugin setup
}
}
```
```ts
import { CorePlugin } from '@revolist/revogrid-pro';
export class MyPlugin extends CorePlugin {
constructor(grid, providers) {
super(grid, providers); // license banner injected here automatically
// plugin setup
this.observeProperty('myProp', (value) => {
// react to grid.myProp = value
});
}
}
```
---
## 7. Standalone products — install Pro as a dependency
Pivot, Gantt, Scheduler, and Kanban require `@revolist/revogrid-pro` as a runtime dependency.
Install Pro explicitly alongside the products you use.
```bash
npm install @revolist/revogrid-pro @revolist/pivot @revolist/gantt
# or
pnpm add @revolist/revogrid-pro @revolist/pivot @revolist/gantt
```
Keep Pro and the standalone products on the **same version** (they are released together).
---
## 8. Pivot and Gantt use standalone packages
In v1.x, Pivot and Gantt plugins were part of `@revolist/revogrid-pro`. They now live in their
own `@revolist/pivot` and `@revolist/gantt` packages.
```ts
import { PivotPlugin, PivotConfig } from '@revolist/revogrid-pro';
import { GanttPlugin } from '@revolist/revogrid-pro';
```
```ts
import { PivotPlugin, type PivotConfig } from '@revolist/pivot';
import { GanttPlugin } from '@revolist/gantt';
```
---
## 9. Excel export provider changes since 2.0.8
This is a breaking change for applications upgrading from `@revolist/revogrid-pro` `2.0.8` or earlier if they relied on bundled SheetJS, non-`.xlsx` default export output, or bundled `.xlsx` / `.xls` import parsing.
`ExportExcelPlugin`, `grid.exportExcel`, `export-excel`, `excel-before-import`, and `excel-before-set` keep their public names. The implementation contract changed so workbook-library choices stay app-owned and the default package is lighter.
| Area | In `2.0.8` and earlier | After this change |
| --- | --- | --- |
| Bundled export library | SheetJS `xlsx` | `write-excel-file` |
| Default export format | SheetJS-supported formats | `.xlsx` only |
| Default import | Workbook parsing through bundled dependency | CSV-only import |
| XLSX import | Plugin-owned | App-owned parser |
| Provider integration | Mostly implicit | Explicit provider objects or factories |
| Formatted workbook output | Reconstructed by provider | Prepared in `context.cellRows` |
### Default export
Use the bundled `.xlsx` provider for normal grid exports:
```ts
grid.plugins = [ExportExcelPlugin];
await excel.export({
workbookName: 'report.xlsx',
sheetName: 'Report',
});
```
The bundled provider now rejects non-`.xlsx` output:
```ts
await excel.export({
workbookName: 'report.xlsb',
});
await excel.export({
writingOptions: { bookType: 'xlsb' },
});
```
Use SheetJS, ExcelJS, workbook-module, or a custom provider when another workbook format is required.
### App-owned SheetJS
If an app depended on SheetJS output formats, install `xlsx` in the app and pass the module object into the SheetJS adapter:
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createSheetJsExcelExportProvider(XLSX),
};
```
The SheetJS adapter writes plain `context.rows` data. It is the compatibility path for apps that want SheetJS-owned workbook writing.
### App-owned ExcelJS
Use ExcelJS when migration requires formatted `cellRows`, merge spans, row heights, freeze panes, or richer workbook style mapping:
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The ExcelJS adapter does not add ExcelJS to RevoGrid Pro dependencies. Your application owns the dependency and bundle cost.
### Custom providers
Custom providers should consume the prepared provider context instead of reading rendered grid rows:
```ts
const provider = {
id: 'custom',
async export(context) {
await writeWorkbook(context.cellRows, {
fileName: context.workbookName,
sheetName: context.sheetName,
metadata: context.exportMetadata,
});
},
};
```
Use `context.cellRows` when the provider needs formatted cells, grouped headers, formulas, or merge spans. Use `context.rows` only for plain row-object output.
### CSV-only default import
Default import now accepts CSV files. It reads files with `FileReader.readAsText`, dispatches `excel-before-import`, maps rows, dispatches `excel-before-set`, then updates the grid source unless either event is prevented.
`.xlsx` and `.xls` parsing is no longer bundled. For workbook uploads:
1. Install a parser such as SheetJS, ExcelJS, or read-excel-file.
2. Parse the file in app code.
3. Normalize workbook data into `columns` and `rows`.
4. Assign `grid.columns`, then `grid.source`.
See [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) for parser tradeoffs and examples.
### Feature-owned helpers
Feature-owned export helpers are still exported from Pro:
- Formula helper for exporting workbook formulas.
- Cell merge and grouped column transforms run automatically.
- Date, numeral, and select helpers are explicit utilities for optional column packages.
- `exportTransformers` are available on both `grid.exportExcel` and individual `plugin.export()` calls.
Provider contexts now include `cellRows`, `sourceRows`, `headerRowsCount`, `gridDerivedOptions`, and `exportMetadata`. Custom providers that need richer export output should consume those fields instead of reconstructing workbook state from `rows`.
When migrating custom providers, keep these rules in mind:
- Export data is prepared from data stores and grid source fallbacks, not rendered DOM rows.
- `sourceRows` preserves pinned segment semantics through `rowType` (`rowPinStart`, `rgRow`, `rowPinEnd`).
- `cellRows` includes generated headers and formatted provider cells; its data rows stay in pinned-top, body, pinned-bottom order.
- Missing pinned sources are empty. If a body data store is not initialized, export falls back to `grid.source`.
---
## Gantt Status is now calculated
The Gantt `Status` field now follows Microsoft Project-style reporting semantics and is read-only. Existing workflow values move to `workflowStatus`:
```ts
// Before
{ id: 'task-1', status: 'in-progress' }
// After
{ id: 'task-1', workflowStatus: 'in-progress' }
```
Legacy source and project JSON ingestion maps `status` to `workflowStatus` only when `workflowStatus` is absent. Serialization writes only `workflowStatus`. Remove code that edits the Status column and use the editable Workflow Status column or task-form field instead.
Set a project reporting date when deterministic results matter:
```ts
grid.gantt = {
...project,
statusDate: '2026-04-08',
};
```
When omitted, `statusDate` defaults to today. Excel export now contains separate `Status` and `Workflow Status` columns, and label overrides are split between `labels.status` and `labels.workflowStatus`.
---
## Versioning strategy
Pro and the standalone product packages are released with the **same version number** (lockstep semver).
| Change type | Version bump |
|---|---|
| Breaking API/behavior change | **Major** (`2.0.0 → 3.0.0`) |
| New plugin or feature (backward-compatible) | **Minor** (`2.0.0 → 2.1.0`) |
| Bug fix | **Patch** (`2.0.0 → 2.0.1`) |
The RevoGrid core package (`@revolist/revogrid`) versions independently — always check its
changelog when upgrading core alongside the Pro product packages.
---
# Pagination Remote
URL: https://pro.rv-grid.com/guides/pagination-remote/
Source: src/content/docs/guides/pagination-remote.mdx
Description: Manage large datasets by breaking them into smaller, more manageable pages.
This example shows how to use pagination with remote data in the grid. To enable pagination you'll need to use the `PaginationPlugin`.
This plugin provides the necessary functionality to paginate data within the grid.
## Complete remote filter state
`loadData` keeps the original fourth argument for compatibility and passes the
complete advanced filter model as an optional fifth argument:
```ts
grid.pagination = {
itemsPerPage: 50,
initialPage: 0,
total: 0,
loadData: async (
skip,
take,
order,
singleConditionFilters,
multiConditionFilters,
) => {
return fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({
skip,
take,
order,
filters: multiConditionFilters,
}),
}).then(response => response.json());
},
};
```
Use `multiConditionFilters` for new integrations because it preserves every
condition and same-column relation. `singleConditionFilters` contains only the
first condition for each column.
```javascript
// fixing render for multiframework
```
---
# Pivot Overview
URL: https://pro.rv-grid.com/guides/pivot/
Source: src/content/docs/guides/pivot/index.mdx
Description: Learn the Pivot mental model, when to use it, and how to build your first RevoGrid Pivot.
Pivot turns a flat fact table into an analytical view. You choose which fields define the row hierarchy, which fields define generated columns, and which fields become aggregated measures. RevoGrid stays responsible for rendering and interaction, while Pivot builds the grouped rows, generated headers, totals, and summary cells.
For raw type signatures, see [Pivot API](/api/pivot/) and [Pivot Config API](/api/pivot-config/). Use this guide set for behavior, defaults, and how the parts work together.
## When To Use Pivot
Use Pivot when your source rows represent detailed facts such as orders, transactions, tickets, or events, and users need to answer questions like:
- "Sales by region and quarter"
- "Count of incidents by team and priority"
- "Average margin by category and month"
- "Grand totals and subtotals across multiple hierarchies"
Use a normal grid when users mainly inspect or edit row-level records. Use Pivot when users need grouped analytical summaries.
## Core Vocabulary
- `dimension`: A field that can be placed on the row axis, column axis, or used as a value source.
- `row field`: A dimension listed in `rows`. It creates the vertical hierarchy.
- `column field`: A dimension listed in `columns`. It creates generated headers.
- `value`: A measure listed in `values`. It is aggregated into Pivot cells.
- `generated row`: A synthetic row produced by Pivot, not a raw input row.
- `generated column`: A synthetic column or column group produced by Pivot.
- `subtotal`: A summary for one intermediate branch of a hierarchy.
- `grand total`: A summary across the full filtered result.
## Mental Model
Client-side Pivot follows this shape:
```txt
source rows
-> group by rows / columns
-> aggregate values
-> generate row hierarchy
-> generate column hierarchy
-> render with RevoGrid
```
Server-side Pivot keeps the same public config, but the aggregation step moves behind a remote engine boundary:
```txt
source rows
-> analytical engine
-> visible analytical window
-> RevoGrid
```
That means the same concepts stay in place even when the data becomes too large for full client-side materialization.
## Conceptual Foundations
If you are new to Pivot tables or Business Intelligence (BI) concepts, we recommend starting with these conceptual guides:
- [What Is Pivot?](/guides/pivot/what-is-pivot/): The basic mental model of transforming flat data.
- [What is an OLAP Cube?](/guides/pivot/olap-cube-concepts/): Understanding multidimensional analysis and core operations.
- [Data Modeling for Pivot](/guides/pivot/data-modeling/): How to structure your facts and dimensions for the best results.
## First Working Pivot
1. Install Pro and the standalone Pivot package.
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/pivot
```
2. Import the plugin and any aggregators you want to expose.
```ts
import {
PivotPlugin,
commonAggregators,
type PivotConfig,
} from '@revolist/pivot';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/pivot/styles.css';
```
3. Register `PivotPlugin` on the grid.
```ts
grid.plugins = [PivotPlugin];
```
4. Define a `PivotConfig`.
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: {
...commonAggregators,
},
},
],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
```
5. Apply it through the direct `pivot` binding.
```ts
grid.pivot = pivot;
```
`additionalData.pivot` is still observed for legacy integrations, but new code should bind `pivot` directly.
## How To Read A PivotConfig
```ts
const pivot: PivotConfig = {
dimensions: [...],
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
};
```
- `dimensions` describes the reusable field metadata.
- `rows` defines the row hierarchy from left to right.
- `columns` defines the generated header hierarchy from left to right.
- `values` defines which measures are aggregated in each visible cell.
- `totals` enables grand totals and subtotals.
- `collapsed` enables grouped row drill-down when the row hierarchy has multiple levels.
- `groupAggregations` makes grouped Pivot rows show aggregate values across generated metric columns.
## What RevoGrid Pivot Owns
When Pivot is active, `PivotPlugin` temporarily owns:
- the rendered `source`
- the rendered `columns`
- optional `pinnedBottomSource` for grand totals
- grouped-row metadata used for drill-down
When Pivot is cleared, the plugin restores the original source, columns, grouping, and pinned totals.
## Feature Map
- Client-side Pivot rows, columns, values, totals, and drill-down are implemented.
- Client-side grouped row aggregates are implemented through `pivot.groupAggregations`.
- `valuesOnRows`, flat headers, and the drag-and-drop configurator are implemented.
- Server-side Pivot exposes a first-class remote engine/store contract and plugin integration.
- Pivot Charts expose a renderer-neutral linked chart lifecycle, the first-party Pivot Charts renderer, persistence, external containers, and an optional modeless UI.
- The docs below describe the current implementation in `packages/pivot/src`, including Pivot core, Pivot Charts, and Pivot Config.
## Recommended Reading Order
1. [What Is Pivot?](/guides/pivot/what-is-pivot/)
2. [What is an OLAP Cube?](/guides/pivot/olap-cube-concepts/)
3. [Data Modeling for Pivot](/guides/pivot/data-modeling/)
4. [Dimensions](/guides/pivot/dimensions/)
5. [Aggregations](/guides/pivot/aggregations/)
6. [Configuration Reference](/guides/pivot/configuration-reference/)
7. [Totals](/guides/pivot/totals/)
8. [Values On Rows](/guides/pivot/values-on-rows/)
9. [Drill-Down](/guides/pivot/drilldown/)
10. [Sorting And Filtering](/guides/pivot/sorting-filtering/)
11. [Pivot Charts](/guides/pivot/concepts/charts/)
12. [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/)
13. [Configurator](/guides/pivot/configurator/)
14. [Field Panel](/guides/pivot/field-panel/)
15. [Server-Side Pivot](/guides/pivot/server-side/)
16. [Caching And Cache Keys](/guides/pivot/caching/)
17. [OLAP Best Practices](/guides/pivot/olap-best-practices/)
---
# Aggregations
URL: https://pro.rv-grid.com/guides/pivot/concepts/aggregations/
Source: src/content/docs/guides/pivot/concepts/aggregations.mdx
Description: Learn how Pivot measures work, how aggregators are resolved, and how to design reliable summaries.
Aggregations turn raw field values into Pivot measures. A value field points to a source field, and an aggregator describes how that field should be summarized inside each Pivot cell.
## Dimension vs Measure
- A `dimension` groups facts.
- A `measure` summarizes facts.
In RevoGrid Pivot, both are built from the same field model. The difference is how you place them:
- `rows` and `columns` use a field as a grouping dimension.
- `values` uses a field as a measure source.
## Basic Value Configuration
```ts
values: [
{ prop: 'sales', aggregator: 'sum' },
{ prop: 'sales', aggregator: 'avg' },
{ prop: 'orders', aggregator: 'count' },
]
```
Each value becomes a separate measure in the generated Pivot output.
## How Aggregator Resolution Works
`values[].aggregator` is a string id. Pivot resolves that id against the matching dimension's `aggregators` map. If you use the configurator, those same registered aggregator ids populate the measure selector.
```ts
dimensions: [
{
prop: 'sales',
name: 'Sales',
aggregators: {
sum: commonAggregators.sum,
avg: commonAggregators.avg,
},
},
],
values: [{ prop: 'sales', aggregator: 'avg' }]
```
If the dimension does not expose the aggregator id, the measure cannot be resolved correctly.
## Built-In Summary Functions
The public remote/server-side summary model supports these stable ids:
- `sum`
- `min`
- `max`
- `avg`
- `count`
Client-side Pivot can also use custom aggregator functions through the dimension model.
## Multiple Measures
When you define multiple values, Pivot generates one measure per configured entry. That is useful for layouts like:
- Sales sum by quarter
- Margin avg by quarter
- Order count by quarter
On the client side, each generated value column uses the measure's `prop` and `aggregator`. On the remote side, result ids are stable summary ids such as `SalesAmount_sum` and `SalesAmount_avg`.
## Formatting Measures
Formatting belongs in the dimension metadata used by the generated columns. For example, attach a numeric column type or a custom template to the measure dimension so generated cells inherit the right rendering behavior.
## Custom Aggregators
Custom aggregators are useful when:
- you need a domain-specific summary
- the display value is not one of the common numeric summaries
- you are staying on the client-side path
Keep them lightweight. Client-side aggregators are run while materializing Pivot output, so very expensive functions will slow down large pivots.
## Average And Derived Measures
`avg` deserves special attention in OLAP-style systems:
- It is not additive.
- Correct remote planning often requires `sum + count` internally.
- Totals must be computed from underlying facts, not from already-averaged child cells.
The remote planner layer in this repository already models `avg` as a derived summary when planning server-side requests.
## Best Practices
- Prefer additive measures like `sum` and `count` when possible.
- Only expose aggregators that make business sense for the field.
- Keep stable aggregator ids across saved states and remote APIs.
- Name measures clearly in examples and UI text so users know whether they are looking at a sum, avg, or count.
## Next
Continue with [Configuration Reference](/guides/pivot/configuration-reference/) for the full `PivotConfig` surface.
---
# Pivot Charts
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/
Source: src/content/docs/guides/pivot/concepts/charts/index.mdx
Description: Turn a committed Pivot result into interactive, linked analytical charts with a built-in renderer or a custom headless integration.
Pivot Charts turn the current Pivot result into an interactive analytical
view. Pivot includes the renderer-neutral chart lifecycle, a first-party
SVG renderer, a searchable chart gallery, semantic drill-down, a current-frame
data table, persistence, export, localization, and server-side contracts. No
separate charting dependency is required.
`PivotChartsPlugin` owns chart data, lifecycle, persistence, drill,
synchronization, and rendering. The optional `PivotChartsUiPlugin` adds the
built-in controls for embedded workspaces, the modeless dialog, and the
**Pivot Chart…** context-menu action. Applications that only need a chart or
their own controls can install only the headless plugin.
A linked chart keeps the visualization synchronized with the committed
Pivot while the modeless dialog remains available for exploration.
## Choose a guide
| Guide | What it covers |
| --- | --- |
| [Chart types and compatibility](./chart-types/) | All 63 built-in types, required data channels, recommendations, and compatibility rules. |
| [Data and bindings](./data-and-bindings/) | Analytical frames, series and channel selection, totals, linking, data view, and limits. |
| [Drill-down and interactions](./drilldown-and-interactions/) | Cell-aware creation, breadcrumbs, synchronization modes, tooltips, legends, keyboard behavior, and events. |
| [Customization and export](./customization-and-export/) | Presentation, chart options, renderer styling, localization, PNG/PDF, and image encoders. |
| [Headless API and persistence](./headless-api-and-persistence/) | Plugin methods, `PivotChartRef`, model restore, custom renderers, and lifecycle events. |
| [Server data and performance](./server-data-and-performance/) | Complete-data providers, semantic scopes, cancellation, validation, dense data, and geographic capabilities. |
## Register Pivot Charts
For a chart-only integration, register the Pivot and headless chart plugins:
```ts
import {
PivotChartsPlugin,
PivotPlugin,
createPivotChartsRenderer,
} from '@revolist/pivot';
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
};
grid.plugins = [PivotPlugin, PivotChartsPlugin];
```
`grid.pivotCharts` is observable. Replacing the configuration updates locale
text and defaults, invalidates cached chart data, and refreshes linked charts.
This is where renderer options, a remote data provider, defaults, limits,
localization, and drill synchronization belong. The application container is
supplied later, when it exists.
## Render a chart outside the Pivot grid
A Pivot chart does not have to live in a popup or inside ``.
`mountPivotChart()` renders into any application-owned `HTMLElement`.
| Experience | Plugins | Mount option |
| --- | --- | --- |
| Chart only | `PivotPlugin` + `PivotChartsPlugin` | Omit `controls` |
| Chart with your own controls | `PivotPlugin` + `PivotChartsPlugin` | Omit `controls`, then call `chart.update()` |
| Chart with built-in controls | Add `PivotChartsUiPlugin` | `controls: 'standard'` |
All three render in your container. None creates a popup.
### Chart only
```html
```
```ts
import {
PivotChartsPlugin,
PivotPlugin,
createPivotChartsRenderer,
mountPivotChart,
} from '@revolist/pivot';
const grid = document.querySelector('#pivot')!;
grid.pivotCharts = { renderer: createPivotChartsRenderer() };
grid.plugins = [PivotPlugin, PivotChartsPlugin];
const chart = await mountPivotChart(grid, {
container: document.querySelector('#sales-chart')!,
presentation: { title: 'Sales by region' },
});
```
That is the complete integration. The chart defaults to grouped columns and
stays linked to the committed Pivot result.
### Chart with built-in controls
Add `PivotChartsUiPlugin`, then request the standard controls at mount time:
```ts
import {
PivotChartsPlugin,
PivotChartsUiPlugin,
PivotPlugin,
mountPivotChart,
} from '@revolist/pivot';
grid.plugins = [PivotPlugin, PivotChartsPlugin, PivotChartsUiPlugin];
const chart = await mountPivotChart(grid, {
container: document.querySelector('#chart-workspace')!,
controls: 'standard',
});
```
This embeds the same chart gallery, Data and Customize tabs, drill
breadcrumbs, current-frame table, and export actions used by the standard
dialog. The application still owns the outer container.
### Chart with your own controls
Use the chart-only mount and connect only the controls your product needs:
```ts
const chart = await mountPivotChart(grid, { container });
typeSelect.addEventListener('change', () => {
void chart.update({
chartType: typeSelect.value as PivotChartType,
});
});
```
Use `chart.update()` for selected series, bindings, totals, link state,
presentation, theme, or chart-specific options. You do not need the UI plugin
for application-built controls.
## Mount after the DOM exists
The container must exist when `mountPivotChart()` runs. In Vue, React, and
other component frameworks, mount the chart in the framework's mounted effect
and destroy it during cleanup.
```vue
```
Use the equivalent mounted effect for React. In an Astro portal, call
`mountPivotChart()` immediately after the portal target has been appended.
An existing container may start hidden and become visible later: the built-in
renderer uses `ResizeObserver` and redraws when its size changes. Give it a
non-zero height when visible. Do not place a container, selector, or ref in
`grid.pivotCharts` or `grid.pivotChartsUi`.
## Open the modeless dialog
`PivotChartsUiPlugin.openChart()` remains available for an application toolbar
or the **Pivot Chart…** context-menu action. A grid owns at most one open
dialog, but it may also own any number of embedded charts.
Right-clicking a Pivot analytical cell creates a cell-aware initial scope:
- a group value opens its immediate children;
- a leaf value opens its siblings under the same parent;
- subtotal and grand-total cells choose the nearest valid breakdown;
- the clicked column path and measure become the initial series;
- values-on-rows preserve the clicked row measure.
The scope comes from committed Pivot row and column metadata, not DOM text or
a stale grid selection.
## What stays synchronized
Linked charts refresh after committed Pivot changes, filtering, sorting,
group expansion, source replacement, and theme changes. Unlinking stores a
bounded normalized dataset in the chart model; the snapshot then remains
stable until the chart is relinked.
Use the **Data** tab to choose totals, relationship bindings, drill
synchronization, and link state. Use **Customize** for presentation and
chart-specific options. **View data** switches the plot to the accessible
table for the current drill frame without mutating the Pivot or chart model.
## Demo and API
- Open the [live Pivot demo](/demo/pivot/) for the complete analytical
workspace. The screenshot states in this guide come from the registered
**Pivot Fields Panel** example, which also runs in all four framework
wrappers.
- Use the [Pivot Charts API reference](/api/pivot-charts/) for exact generated
interfaces and signatures.
- Continue with [Chart types and compatibility](./chart-types/).
---
# Chart Types and Compatibility
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/chart-types/
Source: src/content/docs/guides/pivot/concepts/charts/chart-types.mdx
Description: Choose among all 63 built-in Pivot chart types and understand their data-channel and compatibility requirements.
The built-in catalog is defined by `PIVOT_CHART_DEFINITIONS`. Each definition
owns its stable type ID, family, data view, required semantic channels,
constraints, interactions, presentation capabilities, preview dataset, option
schema, renderer, localized labels, and documentation slug.
The standard dialog evaluates every definition against the current dataset
before selection. Compatible recommendations appear first. Incompatible cards
stay visible and explain the missing channel or semantic requirement instead
of failing after selection.
Every gallery card uses the real renderer. Disabled cards explain the
missing binding, measure, hierarchy, or derived view.
## Gallery behavior
- Search matches localized type names and descriptions.
- Categories follow the catalog family and order.
- Arrow keys move among cards, while Enter and Space
select a compatible type.
- Preview charts are decorative, tooltip-free renderings of catalog fixtures.
- Only near-viewport previews mount; previews are cached by type, renderer,
theme, and locale.
- Catalogs larger than 60 entries retain bounded visible-window behavior.
- A chart type is validated again by the headless plugin, so a custom picker
cannot bypass compatibility rules.
## Comparison charts
| Type | Best for | Required data and constraints |
| --- | --- | --- |
| `groupedColumn` | Comparing multiple series across categories. | Category, value, and one or more series; signed values are supported. |
| `stackedColumn` | Comparing totals and their series contribution. | Category, value, and series; stacks signed values. |
| `normalizedColumn` | Comparing percentage composition across categories. | Category, value, and series; normalizes each category to 100%. |
| `rangeColumn` | Showing vertical low-to-high intervals. | Exactly one `low` and one `high` measure per category. |
| `lollipop` | A lighter single-series category ranking. | Category, value, and exactly one selected series. |
| `groupedBar` | Category comparison when labels are long. | Category, value, and series; signed values are supported. |
| `stackedBar` | Horizontal totals with composition. | Category, value, and series. |
| `normalizedBar` | Horizontal percentage composition. | Category, value, and series; normalizes to 100%. |
| `rangeBar` | Horizontal low-to-high intervals. | Exactly one `low` and one `high` measure. |
## Trend and area charts
| Type | Best for | Required data and constraints |
| --- | --- | --- |
| `line` | Trends across ordered categories. | Category, value, and series. |
| `smoothLine` | Visually continuous trends. | Same channels as `line`; smoothing changes geometry, not data. |
| `steppedLine` | State or price changes that hold until the next category. | Category, value, and series. |
| `smallMultiples` | Comparing many trends without overlapping lines. | Category and value with 1–12 series. |
| `annotatedTimeSeries` | Trends with emphasized analytical points. | Category, value, and series; date-like categories are recommended. |
| `area` | Trend magnitude with filled areas. | Category, value, and series. |
| `steppedArea` | Stepwise magnitude changes. | Category, value, and series. |
| `stackedArea` | Total trend plus series contribution. | Category, value, and series. |
| `normalizedArea` | Percentage composition over an ordered axis. | Category, value, and series; normalizes to 100%. |
| `rangeArea` | A low/high band over categories. | Exactly one `low` and one `high` measure. |
| `confidenceBand` | An uncertainty envelope around a trend. | Exactly one lower and one upper bound through `low` and `high`. |
## Part-to-whole charts
All part-to-whole types require non-negative values and select the first
available series when more than one series exists.
| Type | Best for | Required data |
| --- | --- | --- |
| `pie` | A small set of category shares. | Category, value, and exactly one series. |
| `donut` | Shares with a central visual summary area. | Category, value, and exactly one series. |
| `semiDonut` | A compact half-ring composition. | Category, value, and exactly one series. |
| `waffle` | Discrete-looking proportions in a 100-cell grid. | Category, value, and exactly one series. |
## Relationship charts
| Type | Best for | Required data |
| --- | --- | --- |
| `scatter` | Correlation between two measures. | One `x` and one `y` measure with complete paired observations. |
| `bubble` | Correlation with a third magnitude. | One `x`, one `y`, and one `size` measure. |
| `densityScatter` | Dense relationships where individual points overlap. | One `x` and one `y`; dense geometry is aggregated for rendering and accessibility. |
| `connectedScatter` | Showing the path or sequence between X/Y observations. | One `x` and one `y`; category order defines the connection order. |
The Data panel infers distinct X/Y bindings when possible. A measure already
used by one required relationship channel is disabled for the other.
## Polar charts
Polar charts require non-negative values.
| Type | Best for | Required data |
| --- | --- | --- |
| `rose` | Comparing magnitudes by angle and radius. | Category, value, and series. |
| `radarLine` | Comparing multivariate profiles. | Category, value, and series. |
| `radarArea` | Comparing filled multivariate profiles. | Category, value, and series. |
| `polarColumn` | Radial category comparison. | Category, value, and series. |
| `stackedPolarColumn` | Radial totals and composition. | Category, value, and series. |
| `normalizedPolarColumn` | Radial percentage composition. | Category, value, and series. |
| `radialBar` | A single-series circular progress comparison. | Category, value, and exactly one series. |
| `multiRingRadialBar` | Comparing several series as concentric rings. | Category, value, and series. |
## Distribution and statistical charts
| Type | Best for | Required data |
| --- | --- | --- |
| `histogram` | Frequency distribution across numeric bins. | Category/value input and one series; the analytical frame supplies or derives bins. |
| `stripDot` | Showing individual observations along a value axis. | Category/value input and one series. |
| `boxPlot` | Median, quartiles, spread, and outliers. | A derived distribution or quantile view for one series. |
| `violin` | Distribution shape and density. | A derived distribution or quantile view for one series. |
| `errorInterval` | Actual values with lower and upper error bounds. | One `actual`, one `low`, and one `high` measure. |
| `rangeInterval` | Comparing two or three interval-related series. | Category and value with 2–3 series. |
## Hierarchy charts
Hierarchy charts require row-hierarchy metadata and select one weight series.
Treemap and sunburst additionally require non-negative weights.
| Type | Best for | Required data |
| --- | --- | --- |
| `treemap` | Space-efficient nested proportions. | `hierarchy` plus one `weight` measure. |
| `sunburst` | Concentric hierarchy levels. | `hierarchy` plus one `weight` measure. |
| `icicle` | Depth-oriented rectangular hierarchy. | `hierarchy` plus one `weight` measure. |
| `circlePacking` | Nested hierarchy represented by packed circles. | `hierarchy` plus one `weight` measure. |
## Matrix and specialized charts
| Type | Best for | Required data |
| --- | --- | --- |
| `heatmap` | Comparing magnitude across category-by-series cells. | Category, value, and series. |
| `calendarHeatmap` | Activity intensity by date. | A date-semantic category, value, and exactly one series. |
| `waterfall` | Sequential positive and negative contribution to a result. | Category, value, and exactly one series. |
| `timelineRange` | Start/end intervals by category. | Exactly one `start` and one `end` measure. |
## Process, flow, and financial charts
| Type | Best for | Required data |
| --- | --- | --- |
| `funnel` | Stage-by-stage conversion or attrition. | Non-negative category/value data and one series. |
| `pyramid` | Hierarchical-looking stage magnitude. | Non-negative category/value data and one series. |
| `sankey` | Weighted directional flows. | `source`, `target`, and non-negative `weight` channels. |
| `chord` | Weighted relationships in a circular flow matrix. | `source`, `target`, and non-negative `weight` channels. |
| `candlestick` | Open/high/low/close price movement. | Exactly four series bound to `open`, `high`, `low`, and `close`. |
| `ohlc` | Compact open/high/low/close price ticks. | Exactly four OHLC series. |
| `volumePrice` | OHLC price movement plus traded volume. | Exactly five series: OHLC plus `volume`. |
## Combination and KPI charts
| Type | Best for | Required data |
| --- | --- | --- |
| `columnLine` | Comparing measures that benefit from column and line geometry. | Category/value data with at least two series. |
| `areaLine` | Comparing a filled magnitude with a trend. | Category/value data with at least two series. |
| `bullet` | Actual versus target in a compact linear view. | One `actual` and one `target` measure. |
| `gauge` | Actual versus target on a dial. | One `actual` and one `target` measure. |
| `kpi` | A headline value with target and optional variance. | One `actual`, one `target`, and optionally one `variance` measure. |
| `progressGauge` | Progress toward a target. | One `actual` and one `target` measure. |
## Compatibility rules
`evaluatePivotChartCatalog` and `evaluatePivotChartCompatibility` return
`compatible`, `inferred`, or `incompatible` results with structured reasons.
Compatibility checks cover:
- renderer support for the requested type;
- minimum and maximum channel counts;
- distinct required bindings such as X and Y or low and high;
- non-negative value constraints;
- hierarchy and semantic date requirements;
- derived distribution, range, flow, financial, and geographic views;
- safe category, series, and point limits.
When a compatible relationship chart is missing explicit bindings, the
catalog may return an inferred binding patch. Persisted models still retain
the resolved explicit bindings.
## Optional geographic pack
`createPivotChartGeographicCapabilityPack(provider)` creates a standalone host
for provider-owned `choropleth`, `proportionalSymbolMap`, and
`geographicHeatmap` views. Render these explicitly with `pack.render(...)`.
They are intentionally outside `PIVOT_CHART_TYPES` and the standard gallery.
The provider owns map assets, projected geometry, and licensing; Pivot
does not bundle a map library or geographic data.
[Previous: Pivot Charts overview](../) ·
[Next: Data and bindings](../data-and-bindings/) ·
[API reference](/api/pivot-charts/)
---
# Customization and Export
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/customization-and-export/
Source: src/content/docs/guides/pivot/concepts/charts/customization-and-export.mdx
Description: Customize chart presentation, semantic labels and tooltips, themes, localization, and PNG/PDF export.
Pivot Charts separate portable model presentation from renderer-wide styling.
Use `presentation` and `chartOptions` when the choice must persist with one
chart. Configure `createPivotChartsRenderer` when an application needs a
shared palette, typography, tooltip, or export pipeline.
The Customize tab combines portable presentation fields with the
catalog-defined options supported by the selected chart.
## Portable presentation
```ts
await chart.update({
presentation: {
title: 'Quarterly sales',
subtitle: 'Committed orders only',
legendPosition: 'bottom',
dataLabels: true,
xAxisTitle: 'Region',
yAxisTitle: 'Revenue',
},
theme: 'material',
});
```
`legendPosition` accepts `top`, `right`, `bottom`, `left`, or `none`.
`theme` accepts `auto` or a renderer-supported theme string. The standard UI
offers follow-grid, default, material, compact, dark material, and dark compact
choices.
## Chart-specific options
The catalog generates a typed option schema for every chart:
| Option | Availability | Valid values |
| --- | --- | --- |
| `showGridLines` | Charts with axes. | Boolean. |
| `markOpacity` | Every built-in type. | `0.2`–`1` in `0.05` steps. |
| `strokeWidth` | Line, area, combination, radar line, and radar area. | `1`–`6` in `0.5` steps. |
| `fillOpacity` | Area-family, area-line, radar area, confidence band, and range area. | `0.1`–`0.9` in `0.05` steps. |
```ts
await chart.update({
chartOptions: {
showGridLines: false,
markOpacity: 0.9,
strokeWidth: 2.5,
},
});
```
Unsupported keys, out-of-range values, and values that do not align with the
declared step are rejected during update and restore.
## Renderer styling
```ts
const renderer = createPivotChartsRenderer({
className: 'sales-chart',
fontFamily: '"IBM Plex Sans", sans-serif',
categorySeparator: ' / ',
palette: ['#2563eb', '#f97316', '#16a34a'],
theme: ({ theme }) => ({
...theme,
background: '#fffdf8',
foreground: '#292524',
grid: '#e7e5e4',
}),
});
```
The theme callback receives the resolved light/dark base theme and current
render input. Renderer classes, theme colors, palette, fonts, labels, and
visible chart options are retained in image and document exports.
## Semantic label pipeline
Every visible label passes through one formatter. Kinds are `title`,
`subtitle`, `axisTitle`, `axisTick`, `category`, `legend`, `data`,
`annotation`, `summary`, and `specialized`.
```ts
const renderer = createPivotChartsRenderer({
labels: {
styles: {
title: { fontSize: 20, fontWeight: 700 },
category: { fill: '#475569', fontSize: 11 },
data: { fontWeight: 600 },
},
formatter(context) {
if (context.kind === 'legend') {
return `Measure: ${context.defaultText}`;
}
if (context.kind === 'axisTick' && context.axis === 'y') {
return null;
}
if (context.kind === 'data' && context.formattedValue) {
return {
text: context.formattedValue,
ariaLabel:
`${context.category?.label}: ${context.formattedValue}`,
className: 'sales-chart-value',
};
}
return undefined;
},
},
});
```
Return a string, a structured result, `null` to suppress one label, or
`undefined` to retain the default. Structured results can override text,
accessible text, classes, safe SVG typography, opacity, and visibility.
## Tooltip customization
```ts
const renderer = createPivotChartsRenderer({
tooltip: {
showDelayMs: 100,
hideDelayMs: 60,
followPointer: true,
className: 'sales-chart-tooltip',
formatter(context, defaultContent) {
return {
...defaultContent,
footer: context.drillable
? 'Open the next sales level'
: 'Leaf value',
};
},
},
});
```
The formatter receives raw and formatted values, category and series metadata,
relationship channels, mark color, and drillability. Return structured
content, plain text, an `HTMLElement`, or `null` to suppress one tooltip. Set
`tooltip: false` to disable tooltips.
## Full localization
`pivotCharts.localeText` supports deep partial overrides for dialog actions,
status and errors, breadcrumbs, gallery tabs/families, all chart names and
descriptions, compatibility reasons, chart options, data controls and table,
legend overflow, tooltips, accessibility text, context actions, themes,
totals, and number formatting.
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
localeText: {
actions: {
exportChart: 'Exportar',
downloadPdf: 'Transferir PDF',
viewData: 'Ver dados',
viewChart: 'Ver gráfico',
},
chartTypes: {
groupedColumn: 'Colunas agrupadas',
boxPlot: 'Diagrama de caixa',
},
renderer: {
formatNumber: (value) =>
new Intl.NumberFormat('pt-PT').format(value),
},
},
};
```
## PNG, PDF, and image data
The built-in renderer advertises PNG and PDF through
`capabilities.downloadFormats`. The dialog's **Export** menu downloads the
current rendered chart. Programmatic downloads default to PNG:
```ts
await charts.downloadChart(chart.id, {
fileName: 'quarterly-sales',
fileFormat: 'pdf',
width: 1_600,
height: 900,
});
const dataUrl = await charts.getChartImageDataURL(chart.id, {
width: 1_200,
height: 675,
});
```
Data view and export operate on the current drill frame. Exported graphics
keep the resolved theme and semantic formatting.
Custom renderers should advertise only the formats they implement. The
first-party renderer also accepts `imageEncoder` and `pdfEncoder` callbacks
for applications that need a different rasterization or PDF pipeline.
[Previous: Drill-down and interactions](../drilldown-and-interactions/) ·
[Next: Headless API and persistence](../headless-api-and-persistence/) ·
[API reference](/api/pivot-charts/)
---
# Data and Bindings
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/data-and-bindings/
Source: src/content/docs/guides/pivot/concepts/charts/data-and-bindings.mdx
Description: Control the Pivot data projected into a chart, semantic channel bindings, totals, link state, and safety limits.
Pivot Charts project the committed Pivot result into a renderer-neutral
`PivotChartDataset`. The standard matrix is efficient for category/series
rendering, while the normalized analytical frame supplies stable fields and
observations for relationship, hierarchy, range, flow, financial, KPI, and
other specialized chart types.
Relationship charts expose required semantic channels directly. The same
panel controls totals, drill synchronization, and whether the data remains
linked to the Pivot.
## Dataset shape
```ts
interface PivotChartDataset {
revision: number;
source: 'client' | 'server';
categories: PivotChartCategory[];
series: PivotChartSeries[];
values: Array>;
displayValues?: Array>;
hierarchies?: {
row?: PivotChartHierarchyDefinition;
column?: PivotChartHierarchyDefinition;
};
analyticalFrame?: PivotChartAnalyticalFrame;
}
```
`values` is row-major: `values[categoryIndex][seriesIndex]`. Optional
`displayValues` preserve the Pivot's formatted values for chart labels,
tooltips, data tables, and accessibility text. Categories and series retain
stable IDs, member paths, labels, member kinds, measures, and drill hierarchy
metadata.
The dataset revision identifies the committed Pivot snapshot that produced the
frame. Linked refreshes and drill requests use it to reject stale work.
## Normalized analytical frame
```ts
interface PivotChartAnalyticalFrame {
fields: PivotChartFieldDescriptor[];
observations: PivotChartObservation[];
hierarchies?: PivotChartDataset['hierarchies'];
derived?: {
bins?: PivotChartJsonValue[];
quantiles?: PivotChartJsonValue[];
ranges?: PivotChartJsonValue[];
flowEdges?: PivotChartJsonValue[];
financial?: PivotChartJsonValue[];
geographicFeatures?: PivotChartJsonValue[];
};
}
```
Fields declare a stable ID, label, dimension/measure role, semantic type, source
field, aggregator, and optional format. Each observation stores dimension
values separately from numeric measures and optional display measures.
Specialized renderers consume the normalized fields and derived views instead
of guessing meaning from a series label.
## Semantic bindings
`PivotChartBindings` assigns stable field IDs to renderer channels. Empty and
omitted channels are equivalent.
| Channel group | Channels | Used by |
| --- | --- | --- |
| Standard matrix | `category`, `series`, `value` | Column, bar, line, area, part-to-whole, matrix, and combination types. |
| Relationship | `x`, `y`, `size`, `color`, `shape` | Scatter, bubble, density, and connected-scatter views. |
| Polar | `angle`, `radius` | Polar and radial layouts. |
| Hierarchy | `hierarchy`, `parent`, `child`, `weight` | Treemap, sunburst, icicle, and circle packing. |
| Flow | `source`, `target`, `weight` | Sankey and chord. |
| Range | `start`, `end`, `low`, `high` | Timeline, range, confidence, and error views. |
| Financial | `open`, `high`, `low`, `close`, `volume` | Candlestick, OHLC, and volume-price. |
| KPI | `actual`, `target`, `variance` | Bullet, gauge, KPI, and progress gauge. |
| Faceting and text | `facetRow`, `facetColumn`, `label`, `tooltip` | Small multiples and supplemental labels/tooltips. |
The chart gallery checks required channel counts before selection. For
relationship charts, the Data panel infers distinct X/Y measures when possible
and prevents the same field from satisfying both required channels.
```ts
await chart.update({
chartType: 'bubble',
bindings: {
x: ['measure:cost:sum'],
y: ['measure:revenue:sum'],
size: ['measure:units:sum'],
},
});
```
Use field IDs from `dataset.analyticalFrame.fields`, not visible labels.
## Series and values-on-rows
`seriesIds` selects a subset of dataset series. Types whose catalog definition
uses `firstAvailable` select one series even when the Pivot exposes several.
Types marked `all` retain the compatible series selection.
When the Pivot places values on rows, the category carries its measure.
`categoryMeasure` selects the required `{ prop, aggregator }` pair without
rewriting row paths or pretending the measure is a column series.
```ts
const dataset = await charts.getChartData({
chartType: 'line',
seriesIds: ['series:2026'],
categoryMeasure: {
prop: 'revenue',
aggregator: 'sum',
},
});
```
## Totals
`totals` accepts four modes:
| Mode | Included members |
| --- | --- |
| `none` | Leaf and group members only. |
| `grand` | Include grand totals. |
| `subtotals` | Include subtotals. |
| `all` | Include grand totals and subtotals. |
The default is `none`. Totals keep their semantic member kind, so charts,
tooltips, data tables, drill logic, and persisted models can distinguish them
from ordinary categories.
## Linked and unlinked data
A linked chart follows the current committed Pivot. Data, filters, sorting,
group expansion, source, Pivot model, and relevant theme changes schedule
coalesced refreshes.
Turning off **Keep linked to Pivot** stores the current bounded normalized
dataset in `model.dataset`. Later Pivot changes do not affect that chart.
Relinking removes the stored snapshot and projects the latest committed Pivot
state. Restore validates both linked models and embedded unlinked datasets.
## Current-frame data table
The header's **View data** action replaces the plot with a sortable accessible
table for the active drill frame. It:
- uses the same category order, series, and formatted display values as the
chart;
- represents missing observations consistently;
- announces sort changes and exposes semantic table headers;
- does not alter the chart model, link state, Pivot, or drill state;
- switches back through **View chart**.
## Scope and limits
`getChartData` can request the immediate children of a semantic parent:
```ts
const dataset = await charts.getChartData({
chartType: 'groupedColumn',
totals: 'none',
scope: {
rowPath: ['Europe'],
columnPath: [2026],
},
});
```
Default safety limits are 1,000 categories, 250 series, and 50,000 data points.
The plugin validates limits, actual counts, matrix dimensions, observations,
bindings, and chart compatibility. Unsafe projections fail with a
`PivotChartError` rather than silently changing persisted data.
[Previous: Chart types and compatibility](../chart-types/) ·
[Next: Drill-down and interactions](../drilldown-and-interactions/) ·
[API reference](/api/pivot-charts/)
---
# Drill-down and Interactions
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/drilldown-and-interactions/
Source: src/content/docs/guides/pivot/concepts/charts/drilldown-and-interactions.mdx
Description: Navigate Pivot hierarchies from chart marks and synchronize chart drill state with the Pivot or application.
Pivot Charts carry row and column hierarchy metadata into every compatible
dataset. A mark can therefore identify the exact semantic category, series,
paths, measure, aggregator, and child state that produced it.
The breadcrumb is the navigation surface for the current row and column
scope. Tooltips expose the full path, formatted value, and drill hint.
## Cell-aware creation
With `pivotChartsUi.contextMenu: true` and `ContextMenuPlugin` installed,
right-clicking a Pivot analytical body cell contributes **Pivot Chart…**.
Creation resolves scope from committed Pivot metadata:
- group values open their immediate children;
- leaf values open siblings under the same parent;
- subtotal and grand-total cells use the nearest valid breakdown;
- the clicked column member and measure seed the series selection;
- values-on-rows retain the clicked category measure.
The context action composes with other menu contributions. It does not replace
application items or depend on visible DOM text.
## Mark activation
Pointer click and keyboard activation first look for an automatic semantic
drill target. When the category or series can drill, activation starts the
appropriate row, column, or two-axis drill action and does not emit
`pivot-chart-node-click`. The event is emitted only when no automatic drill
target exists.
The chart ref supports row, column, and two-axis targets:
```ts
if (chart.canDrillDown({ axis: 'row', category })) {
await chart.drillDown({ axis: 'row', category });
}
await chart.drillDown({ axis: 'column', series });
await chart.drillDown({ axis: 'both', category, series });
```
Use `canDrillDown` before offering an application-owned drill control. It
checks hierarchy identity, level, child state, and the current active scope.
## Breadcrumb navigation
Each successful frame is stored in `PivotChartDrillState.frames`. The compact
breadcrumb shows the active row and column labels:
```ts
await chart.drillUp();
await chart.drillTo(1);
await chart.resetDrill();
```
Selecting **All data** resets to the root. Selecting an ancestor calls
`drillTo` directly; it does not replay every intermediate click. After a
successful UI drill, focus moves to the current breadcrumb item.
## Loading, cancellation, and retry
Drill loads are abortable and revision-checked:
- the last successful frame stays visible while the next frame loads;
- a newer action aborts or supersedes older work;
- stale provider completions are ignored;
- rendering failures roll back model and dataset state;
- successful scopes are cached;
- failed drill metadata is retained for `retryDrill()`.
The dialog displays the pending target, failure message, and retry action
without discarding the previous chart.
## Drill synchronization
`drillSyncMode` controls the relationship between chart navigation and the
Pivot frontier:
| Mode | Behavior |
| --- | --- |
| `chart` | Navigate only in the chart. |
| `pivot` | Synchronize the Pivot frontier and let the linked Pivot refresh drive the chart. |
| `both` | Commit the chart frame and synchronize the Pivot. |
| `event` | Emit `pivot-chart-drill-sync`; the application owns synchronization. |
For `pivot` and `both`, the active `PivotPlugin` applies the semantic frontier
unless `pivotCharts.synchronizeDrill` is provided. The custom adapter receives
the event detail and an `AbortSignal`.
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
defaultDrillSyncMode: 'event',
async synchronizeDrill(detail, { signal }) {
await saveAnalyticsRoute(detail.to, { signal });
},
};
```
## Legend focus and selection
Series legends are keyboard-accessible guides, not drill controls. Activating
a legend item focuses that series, emphasizes matching marks, and mutes the
others. Activating it again or selecting the chart background clears the
focus. When a dense legend cannot fit, it shows a deterministic subset and a
localized hidden-series count.
## Semantic tooltips
Interactive marks use an application-owned tooltip card rather than native SVG
`title` text. The default tooltip includes:
- the complete category path;
- series identity and formatted value;
- the resolved mark color;
- X, Y, and size rows for relationship charts;
- a drill hint when the datum has children.
Pointer tooltips stay inside the viewport. Keyboard focus opens the same card
immediately and connects it with `aria-describedby`. Escape closes
the tooltip without closing the chart dialog. Gallery previews intentionally
do not create tooltips.
## Keyboard and accessibility
- The modeless dialog has a stable accessible name and restores focus when it
closes.
- Gallery tabs, cards, breadcrumbs, legend items, chart marks, export actions,
and the data table are keyboard operable.
- Marks have localized accessible names built from category, series, formatted
value, relationship channels, and drillability.
- The data table uses headers, a caption, row headers, and sort announcements.
- Status regions announce loading, drill success, failure, and data-table sort
changes without replacing visible content.
## Drill and interaction events
| Event | Purpose |
| --- | --- |
| `pivot-chart-node-click` | Reports the activated semantic datum and its paths/measure when no automatic drill target exists. |
| `pivot-chart-before-drill` | Cancelable event before a drill is committed. |
| `pivot-chart-drill-start` | Announces a pending drill frame. |
| `pivot-chart-drilled` | Supplies the committed model and dataset. |
| `pivot-chart-drill-error` | Reports the failed transition and error. |
| `pivot-chart-drill-reset` | Reports a successful reset to root. |
| `pivot-chart-drill-sync` | Delegates `pivot`, `both`, or `event` synchronization. |
Event payloads clone JSON-safe semantic state. See
[Headless API and persistence](../headless-api-and-persistence/) for the full
lifecycle event list.
[Previous: Data and bindings](../data-and-bindings/) ·
[Next: Customization and export](../customization-and-export/) ·
[API reference](/api/pivot-charts/)
---
# Headless API and Persistence
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/headless-api-and-persistence/
Source: src/content/docs/guides/pivot/concepts/charts/headless-api-and-persistence.mdx
Description: Create, update, drill, export, save, restore, and destroy Pivot charts through the plugin API.
`PivotChartsPlugin` is the source of truth for chart data and lifecycle.
`PivotChartsUiPlugin` is a client of that API; applications can build their
own gallery, settings, containers, or workspace without replacing the data
model.
For normal application-owned charts, use `mountPivotChart()`:
```ts
import { mountPivotChart } from '@revolist/pivot';
const chart = await mountPivotChart(grid, {
container: document.querySelector('#dashboard-chart')!,
});
```
## Application-owned containers
The container does not need to be inside the grid. The default
`controls: 'none'` path renders only the chart and requires no UI plugin.
Request `controls: 'standard'` when the same container should include the
built-in configuration experience:
```ts
const chart = await mountPivotChart(grid, {
container: document.querySelector('#chart-workspace')!,
controls: 'standard',
});
```
`controls: 'standard'` requires `PivotChartsUiPlugin`; chart-only mounting does
not. Both return the same `PivotChartRef`. Give the mount container an explicit
height. The built-in renderer fills the available size and observes later
layout changes, including a mounted container that becomes visible later.
Treat the container as an empty chart mount point: mounting replaces its
children, and `chart.destroy()` leaves it empty.
Configuration controls are optional application UI. Each control can call the
same chart ref without recreating the renderer:
```ts
chartTypeSelect.addEventListener('change', async () => {
await chart.update({
chartType: chartTypeSelect.value as PivotChartType,
});
});
dataLabelsCheckbox.addEventListener('change', async () => {
await chart.update({
presentation: { dataLabels: dataLabelsCheckbox.checked },
});
});
```
See the [Pivot Charts overview](../) for complete minimal markup and setup for
chart-only, standard-control, and component-lifecycle integrations.
## Direct plugin access
Use the plugin instance when an advanced workflow needs collection-wide
methods such as restore, `getChartModels()`, or `destroyAllCharts()`:
```ts
const charts = (await grid.getPlugins()).find(
(plugin) => plugin instanceof PivotChartsPlugin,
) as PivotChartsPlugin;
```
## Plugin methods
| Method | Purpose |
| --- | --- |
| `getChartData(options?)` | Project and validate a cached immutable dataset for the current Pivot revision and optional semantic scope. |
| `createChart(params)` | Create a model, resolve data, mount a renderer instance, and return `PivotChartRef`. |
| `updateChart(chartId, patch)` | Validate and queue a model/data/presentation update. |
| `refreshChart(chartId)` | Reproject a linked chart from the current committed Pivot. |
| `canDrillDown(chartId, target)` | Test a semantic row, column, or two-axis drill target. |
| `drillDownChart(chartId, target)` | Load and commit a child frame. |
| `drillUpChart(chartId)` | Navigate to the parent frame. |
| `drillToChart(chartId, frameIndex)` | Jump to a stored breadcrumb frame. |
| `resetChartDrill(chartId)` | Return to the chart's root frame. |
| `retryChartDrill(chartId)` | Retry the last failed drill transition. |
| `getChartModels()` | Return cloned serializable models for every active chart. |
| `restoreChart(model, options)` | Validate a saved model and recreate it in a supplied container. |
| `downloadChart(chartId, options?)` | Delegate an advertised PNG/PDF download to the renderer. |
| `getChartImageDataURL(chartId, options?)` | Request an image data URL from a capable renderer. |
| `destroyChart(chartId)` | Destroy one renderer and release its state. |
| `destroyAllCharts()` | Destroy every chart owned by the plugin. |
All updates for a chart are serialized. A later linked refresh cannot
partially overwrite an earlier options update.
## Create and update
```ts
const chart = await charts.createChart({
container: document.querySelector('#sales-chart')!,
chartType: 'groupedColumn',
linked: true,
totals: 'none',
bindings: {},
initialScope: {
rowPath: ['Europe'],
rowLabels: ['Europe'],
columnPath: [2026],
columnLabels: ['2026'],
},
presentation: {
title: 'European sales',
legendPosition: 'bottom',
},
theme: 'auto',
chartOptions: {
showGridLines: true,
markOpacity: 0.9,
},
drillSyncMode: 'chart',
});
await chart.update({
chartType: 'stackedColumn',
totals: 'grand',
presentation: { subtitle: 'Including grand total' },
});
```
Data-affecting patches reproject or validate a snapshot. Presentation, theme,
and chart-option changes reuse the current dataset. Failed renderer updates
roll back to the previous model and rendered input.
## `PivotChartRef`
The ref scopes common operations to one chart:
```ts
chart.id;
chart.container;
chart.model;
await chart.update({ linked: false });
chart.canDrillDown({ axis: 'row', category });
await chart.drillDown({ axis: 'row', category });
await chart.drillUp();
await chart.drillTo(0);
await chart.resetDrill();
await chart.retryDrill();
chart.getDrillState();
chart.focus();
chart.destroy();
```
`model` and `getDrillState()` return clones. Mutating them does not update the
active chart; call `update` or a drill method.
## Model and persistence
`PivotChartModel` version 2 contains:
- stable `chartId` and `chartType`;
- link state and an optional unlinked `dataset` snapshot;
- totals, series selection, values-on-rows measure, and semantic bindings;
- presentation, renderer theme, and validated chart options;
- breadcrumb/drill scope and drill synchronization mode.
```ts
const saved = charts.getChartModels();
localStorage.setItem('pivot-charts', JSON.stringify(saved));
const restoredModels = JSON.parse(
localStorage.getItem('pivot-charts') ?? '[]',
);
for (const model of restoredModels) {
await charts.restoreChart(model, {
container: createWorkspacePanel(model.chartId),
});
}
```
Restore parses the current model version, checks chart identity, renderer
support, options, bindings, selected series, hierarchy paths, dataset shape,
limits, and linked/unlinked rules. Older model versions are rejected rather
than implicitly migrated. Supply `chartId` in `PivotChartRestoreOptions` only
when the application intentionally replaces the persisted identity.
## Custom renderer contract
```ts
const renderer: PivotChartRenderer = {
capabilities: {
supportedTypes: ['groupedColumn', 'line'],
download: true,
downloadFormats: ['png'],
imageDataUrl: true,
},
create(input) {
const instance = mountApplicationChart(input);
return {
update(nextInput) {
instance.update(nextInput);
},
focus() {
instance.focus();
},
async download(options) {
await instance.downloadPng(options);
},
async getImageDataURL(options) {
return instance.getPngDataUrl(options);
},
destroy() {
instance.destroy();
},
};
},
};
```
`PivotChartRenderInput` contains the chart ID, container, chart type, validated
dataset, bindings, presentation, chart options, theme, locale text,
`chart`/`preview` render mode, and the semantic node-click callback. A custom
renderer must not mutate the supplied dataset or model state.
The UI gallery exposes only `capabilities.supportedTypes`. Download and image
actions are available only when the renderer advertises and implements them.
## Standard UI API
`PivotChartsUiPlugin.openChart(params)` accepts every
`PivotChartCreateParams` field except `container`; the UI owns its chart
container. `pivotChartsUi.dialogParent` can move the modeless host into an
application overlay root. Closing the dialog destroys its chart.
`PivotChartsUiPlugin.mountChart(params)` is the lower-level equivalent of
`mountPivotChart(..., { controls: 'standard' })`. It embeds the existing
gallery, Data, Customize, current-frame table, export, and drill controls in
the supplied application container. Prefer `mountPivotChart()` unless the UI
plugin instance is already part of a larger plugin-management workflow.
## Lifecycle events
| Event | Detail |
| --- | --- |
| `pivot-chart-created` | Chart ID and `PivotChartRef`. |
| `pivot-chart-updated` | Chart ID, update reason, and cloned model. |
| `pivot-chart-destroyed` | Chart ID. |
| `pivot-chart-node-click` | Semantic category/series, paths, measure, and aggregator. |
| `pivot-chart-error` | Optional chart ID and normalized `Error`. |
| `pivot-chart-before-drill` | Cancelable transition with from/to drill state. |
| `pivot-chart-drill-start` | Pending drill transition. |
| `pivot-chart-drilled` | Successful transition with model and dataset. |
| `pivot-chart-drill-error` | Failed transition and error. |
| `pivot-chart-drill-reset` | Successful reset transition. |
| `pivot-chart-drill-sync` | Synchronization request and selected mode. |
Update reasons are `data`, `options`, `theme`, `relink`, and `drill`.
[Previous: Customization and export](../customization-and-export/) ·
[Next: Server data and performance](../server-data-and-performance/) ·
[API reference](/api/pivot-charts/)
---
# Server Data and Performance
URL: https://pro.rv-grid.com/guides/pivot/concepts/charts/server-data-and-performance/
Source: src/content/docs/guides/pivot/concepts/charts/server-data-and-performance.mdx
Description: Supply complete semantic chart frames for remote Pivots and understand validation, cancellation, caching, limits, and dense rendering.
A remote Pivot viewport is only a window into the analytical result. Pivot
Charts never treat the visible rows or columns as the complete dataset.
Configure `PivotChartDataProvider` to return the complete immediate-child frame
for every requested semantic scope.
## Configure a provider
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
dataProvider: {
async load(request, { signal }) {
const response = await fetch('/api/pivot/chart-data', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
signal,
});
if (!response.ok) {
throw new Error(`Chart data failed: ${response.status}`);
}
return response.json();
},
},
};
```
The provider is used for root projection, explicit scoped data, linked
refreshes, and drill frames. Local and server Pivots produce the same
`PivotChartDataset` contract after projection and validation.
## Request contract
`PivotChartDataRequest` includes:
| Field | Purpose |
| --- | --- |
| `requestId` | Unique request identity that the response must echo. |
| `viewId` and `fieldsVersion` | Identify the remote analytical view and field registry. |
| `loadOptions` | Committed Pivot filters, sorting, grouping, and engine options. |
| `uiState` | Optional Pivot UI hints. |
| `axisState` | Expanded row and column paths. |
| `chart.seriesIds` | Requested series selection. |
| `chart.categoryMeasure` | Values-on-rows measure selection. |
| `chart.totals` | Requested totals mode. |
| `chart.limits` | Effective category, series, and point limits. |
| `chart.bindings` | Stable semantic field-to-channel bindings. |
| `chart.scope` | Exact committed revision, row/column paths, and immediate-child depths. |
The semantic scope is absolute:
```ts
interface PivotChartSemanticScope {
revision: number;
rowPath: PivotPath;
columnPath: PivotPath;
rowDepth: number;
columnDepth: number;
}
```
The response must contain every immediate child for that scope, not a
viewport-sized subset.
## Response contract
```ts
interface PivotChartDataResponse {
requestId: string;
scope: PivotChartSemanticScope;
categories: PivotChartCategory[];
series: PivotChartSeries[];
values: Array>;
displayValues?: Array>;
hierarchies: {
row?: PivotChartHierarchyDefinition;
column?: PivotChartHierarchyDefinition;
};
totalCategoryCount: number;
totalSeriesCount: number;
}
```
`categories.length` and `series.length` describe the returned complete frame.
The total counts let the client prove that the response is complete and within
limits. Hierarchy definitions and member hierarchy metadata are required when
the response contains drillable members.
## Strict validation
The client rejects:
- a response whose `requestId` differs from the request;
- any change to revision, row/column paths, or requested depths;
- partial frames whose returned and total counts disagree;
- value/display matrices with wrong dimensions;
- duplicate or unstable category/series IDs;
- malformed hierarchy levels or member paths;
- incompatible bindings, selected series, or values-on-rows measures;
- categories, series, or point counts above the effective limits;
- data that violates the selected chart's semantic constraints.
There is no visible-window fallback. A partial response would create a
misleading chart and is treated as an error.
## Cancellation and stale results
Every provider call receives an `AbortSignal`. Abort underlying HTTP, query,
and decoding work when possible. Root loads, linked refreshes, and drill
transitions carry monotonically increasing revisions. The plugin ignores a
completion that is no longer current even if the provider did not stop after
abort.
During drill, the last successful frame remains rendered. During a linked
refresh, the UI exposes a loading state and commits the replacement only after
data validation and renderer update succeed.
## Caching
The plugin caches immutable datasets by committed Pivot revision, effective
data options, bindings, and semantic scope. Concurrent equivalent requests
share the same promise. Successful drill scopes are cached per chart so
breadcrumb navigation does not repeatedly load unchanged data.
Replacing `grid.pivotCharts`, changing the Pivot model/source/filter/sort/group
state, or advancing the committed revision invalidates the relevant cache.
Application/server caches should use the same request identity inputs and must
still return the exact requested scope.
## Dense-data behavior
Defaults are:
```ts
const DEFAULT_PIVOT_CHART_LIMITS = {
maxCategories: 1_000,
maxSeries: 250,
maxDataPoints: 50_000,
};
```
Within those limits, the renderer degrades deliberately:
- every legend position reserves a valid plot rectangle;
- long legends show a deterministic subset and localized hidden count;
- category ticks and data labels are thinned without removing data;
- Cartesian marks retain exact values;
- density scatter, connected trends, small multiples, and timeline geometry
aggregate or sample above their accessibility thresholds;
- the renderer avoids creating one focus target for every dense source point
when that would make keyboard navigation unusable.
Sampling affects rendered geometry, not the persisted normalized dataset.
Exceeding configured projection limits fails rather than silently truncating
the model.
## Renderer preview performance
The gallery uses the same chart definitions and renderer functions as full
charts, but with `renderMode: 'preview'`. Preview mode is decorative and omits
tooltips and interactive focus targets. Intersection-based mounting and cache
keys for type, renderer, theme, and locale prevent a 63-card gallery from
creating 63 active charts at once.
## Geographic capability
Geographic views are opt-in because map geometry, projections, tiles, and
licenses belong to the provider:
```ts
const pack = createPivotChartGeographicCapabilityPack(provider);
const host = await pack.render({
container,
chartType: 'choropleth',
dataset,
bindings: { geographic: ['dimension:country'], value: ['measure:sales'] },
});
```
The pack exposes `choropleth`, `proportionalSymbolMap`, and
`geographicHeatmap`. These views are not built-in gallery types and do not
change `PivotChartModel`. Importing Pivot Charts alone loads no geographic
assets or map dependency.
## Production checklist
- Return complete immediate-child frames from the analytical engine.
- Preserve stable field, category, series, hierarchy, and path identities.
- Echo the request ID and semantic scope exactly.
- Forward abort signals to network and query work.
- Set limits that match renderer and server budgets.
- Include formatted display values when chart and Pivot formatting must match.
- Log provider errors with request/view IDs without exposing raw sensitive
source rows.
- Exercise root, linked refresh, drill, retry, breadcrumb, and stale-response
paths before release.
[Previous: Headless API and persistence](../headless-api-and-persistence/) ·
[Pivot Charts overview](../) ·
[Remote Pivot guide](/guides/pivot/server-side/) ·
[API reference](/api/pivot-charts/)
---
# Configuration Reference
URL: https://pro.rv-grid.com/guides/pivot/concepts/configuration-reference/
Source: src/content/docs/guides/pivot/concepts/configuration-reference.mdx
Description: Complete reference for every public PivotConfig option in the current implementation.
`PivotConfig` is the public configuration model for both client-side and server-side Pivot. This page explains every option in the current implementation and how the options interact.
For raw signatures, see [Pivot Config API](/api/pivot-config/). Use this page for defaults, behavior, and practical guidance.
## Minimal Example
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{ prop: 'sales', name: 'Sales', aggregators: commonAggregators },
],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
```
## Core Data Model
### `dimensions`
Reusable field definitions. This is where you configure field labels, column behavior, and available aggregators.
- Purpose: Define the fields Pivot can reuse.
- Accepted value: `PivotConfigDimension[]`
- Default: `[]`
- Important interactions: `values[].aggregator` resolves against the matching dimension.
### `rows`
Ordered row hierarchy.
- Purpose: Define the vertical analytical hierarchy.
- Accepted value: `ColumnProp[]`
- Default: required
- Important interactions: row drill-down only becomes meaningful when the effective row hierarchy has more than one level.
### `columns`
Ordered generated column hierarchy.
- Purpose: Define the horizontal analytical hierarchy.
- Accepted value: `ColumnProp[]`
- Default: `[]`
- Important interactions: `flatHeaders`, `columnCollapse`, and column totals only matter when column fields exist.
### `values`
Measures to aggregate.
- Purpose: Define which fields are summarized inside each Pivot cell.
- Accepted value: `PivotConfigValue[]`
- Default: required
- Important interactions: values can render as columns or as synthetic rows through `valuesOnRows`.
## Layout Options
### `flatHeaders`
Flattens grouped column headers into single labels.
- Purpose: Reduce visible nesting in generated columns.
- Accepted value: `boolean`
- Default: `false`
- Common pitfall: flat headers still preserve analytical grouping, they only change header presentation. With `flatHeaders: false`, generated Pivot columns use RevoGrid grouped headers so repeated parent labels are visually grouped.
### `mergeValueHeaders`
Merges generated value headers into the terminal column header labels.
- Purpose: Remove repeated measure headers such as `Revenue` under every generated column member.
- Accepted value: `boolean`
- Default: `false`
- Behavior: with one value, terminal columns use the column member label, for example `Q1`; with multiple values, the value label is appended, for example `Q1 Revenue`.
- Important interactions: can be combined with `flatHeaders`; `valuesOnRows` already moves measures out of generated value columns.
### `valuesOnRows`
Moves values into the row hierarchy.
- Purpose: Render measures as synthetic row members.
- Accepted value: `boolean`
- Default: `false`
- Important interactions: affects row drill-down behavior because values become part of the effective row structure.
### `rowTree`
Controls the effective row hierarchy, including where measures appear.
- Purpose: Place the `$values` pseudo-field at the beginning, middle, or end of the row tree.
- Accepted value: `(ColumnProp | '$values')[]`
- Default: derived from `rows`, with `$values` appended only when `valuesOnRows: true`
- Important interactions: when provided, `rowTree` overrides the value placement implied by `valuesOnRows`.
### `rowAxisLayout`
Controls the visible layout of an active multi-level row hierarchy.
- Purpose: Choose between one compact hierarchy column and one column per row field.
- Accepted value: `'compact' | 'tabular'`
- Default: `'compact'` while row drill-down is active; otherwise `'tabular'`
- Behavior:
- `'compact'` renders group labels and terminal row values in one localized Rows column.
- `'tabular'` preserves separate visible columns for every configured row field.
- Important note: this changes only the grid presentation. Configured row fields remain separate in aggregation, grouping, configurator state, and remote requests.
## Configurator Options
### `hasConfigurator`
Enables the built-in Pivot configurator panel.
- Purpose: Let users rearrange fields visually.
- Accepted value: `boolean`
- Default: `false`
### `mountTo`
Custom configurator mount target.
- Purpose: Render the configurator outside the default grid wrapper.
- Accepted value: `HTMLElement`
- Default: internal grid wrapper mount
### `showColumns`
Hide or show the configurator columns zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `showRows`
Hide or show the configurator rows zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `showValues`
Hide or show the configurator values zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `i18n`
Configurator strings.
- Purpose: Override built-in English labels.
- Accepted value: `typeof PIVOT_CONFIG_EN`
- Default: built-in English copy
## Totals
### `totals`
Controls grand totals and subtotals.
- Purpose: Add analytical summary rows and columns.
- Accepted value: `PivotConfigTotals`
- Default: all disabled
Nested options:
- `grandTotal`
- `subtotals`
- `grandTotalLabel`
- `subtotalLabel`
- `suppressSingleChildSubtotals`
- `suppressGrandTotalWhenSingleLeaf`
See [Totals](/guides/pivot/totals/) for behavior examples.
## Drill-Down State
### `collapsed`
Initial row drill-down state.
- Purpose: Enable grouped row expansion behavior.
- Accepted value: `boolean`
- Default: disabled unless `expanded` is provided or `rowAxisLayout: 'compact'` is set
- Important note: only matters when the effective row hierarchy has more than one level
### `expanded`
Persisted grouped row expansion state.
- Purpose: Restore row drill-down state.
- Accepted value: `Record`
- Default: `undefined`
### `groupAggregations`
Render aggregate values inside grouped Pivot drill-down rows.
- Purpose: Show grouped branch aggregates under generated Pivot metric columns, not only the group label.
- Accepted value: `boolean`
- Default: `false`
- Important note: currently applies to the client-side Pivot path. Server-side Pivot still needs backend-precomputed grouped aggregates for exact support.
### `groupLabelColumn`
Controls which visible row cell renders grouped row labels.
- Purpose: Keep grouped labels visible when grouped fields are hidden with column-hide.
- Accepted value: `'grouped' | 'firstVisible'`
- Default: `'grouped'`
- Behavior:
- `'grouped'` keeps labels in the grouped field column.
- `'firstVisible'` renders labels in the first currently visible row cell.
### `columnCollapse`
Generated column collapse config.
- Purpose: Enable collapsible Pivot column groups.
- Accepted value: `boolean | PivotColumnCollapseConfig`
- Default: enabled and collapsed when `columns` is non-empty
- Disable with: `false` or `{ enabled: false }`
Nested options when using the object form:
- `enabled`
- `collapsed` (defaults to `true`; set `false` to start expanded)
- `filterable`
- `aggregator`
- `placeholder`
### `columnLevels`
Per-level generated column behavior.
- Purpose: Override collapse, subtotal, filtering, and sorting behavior for a
specific zero-based level in `columns`.
- Accepted value: `Record`
- Default: omitted; global Pivot and value-dimension settings apply
Level options:
- `collapsible`
- `collapsed`
- `subtotal` (applies while `totals.subtotals` is enabled)
- `subtotalLabel`
- `subtotalPosition`: `'before' | 'after'` (defaults to `'before'`)
- `subtotalValues`: ordered value-field subset (defaults to all Pivot values)
- `filterable`
- `sortable`
```ts
columns: ['year', 'quarter', 'month'],
totals: { subtotals: true },
columnLevels: {
0: {
subtotal: true,
subtotalLabel: 'Year Total',
subtotalPosition: 'before',
subtotalValues: ['sales'],
filterable: true,
sortable: true,
},
1: {
subtotal: false,
filterable: false,
sortable: false,
},
}
```
An explicit `subtotal` value in `columnLevels` takes precedence over
`totals.disabledSubtotals.columns` for that level. `totals.subtotals` remains
the global on/off switch.
See [Column Totals With Drill-Down](/guides/pivot/totals/#column-totals-with-drill-down)
for the complete configuration matrix, resolution order, interaction
guarantees, and screenshots.
### `collapsedColumns`
Persisted generated column collapse state.
- Purpose: Restore collapsed column groups.
- Accepted value: `boolean | Record`
- Default: `undefined`
## Engine Options
### `engine`
Switches between local and remote analytical execution.
- Purpose: Keep the same public Pivot config while changing where computation happens.
- Accepted value: object
- Default: omitted, which keeps current client-side behavior
Nested options:
### `engine.mode`
- Accepted value: `'client' | 'server'`
- Default: omitted
- Behavior: `'server'` makes `PivotPlugin` load through an adapter or remote store
### `engine.adapter`
- Accepted value: `PivotEngineAdapter`
- Behavior: custom execution boundary used directly by the plugin
### `engine.remoteStore`
- Accepted value: `PivotRemoteStore`
- Behavior: framework-agnostic HTTP or transport-backed store used by the server adapter
### `engine.viewId`
- Accepted value: `string`
- Default: `'default'`
- Behavior: backend semantic view or dataset identifier
### `engine.fieldsVersion`
- Accepted value: `string`
- Default: `'local'`
- Behavior: field registry version or checksum
### `engine.rowAxis`
- Accepted value: `Partial`
- Default: `{ offset: 0, limit: 100 }`
- Behavior: initial row-axis analytical window hint
### `engine.columnAxis`
- Accepted value: `Partial`
- Default: `{ offset: 0, limit: 24 }`
- Behavior: initial column-axis analytical window and hierarchy frontier.
`expandedPaths` and `collapsedPaths` use typed analytical paths and are
updated automatically by Pivot column expand/collapse controls in server
mode.
## Realistic Example
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region', sortable: true },
{ prop: 'rep', name: 'Rep' },
{ prop: 'year', name: 'Year' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: commonAggregators,
},
],
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
groupAggregations: true,
hasConfigurator: true,
};
```
## Common Mistakes
- Treating `dimensions` as optional metadata only. They are the field model Pivot depends on.
- Expecting `columns` to be required. A row-only Pivot is valid.
- Using `columnCollapse` without generated column groups.
- Assuming `engine` changes the public config shape. It only changes the execution path.
## Related Guides
- [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/)
- [Configurator API](/guides/pivot/configurator-api/)
- [Field Panel](/guides/pivot/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
- [OLAP Best Practices](/guides/pivot/olap-best-practices/)
---
# Dimensions
URL: https://pro.rv-grid.com/guides/pivot/concepts/dimensions/
Source: src/content/docs/guides/pivot/concepts/dimensions.mdx
Description: Understand Pivot dimensions, how they map to fields, and which column options carry over into Pivot.
A Pivot dimension is the reusable definition of a field. It tells Pivot what the field is called, how it should behave in generated headers, and which aggregators are valid when that field is used as a measure source.
## What A Dimension Is
Each dimension is a `PivotConfigDimension`. In practice it is a partial RevoGrid column definition plus Pivot-specific metadata.
```ts
const dimensions = [
{ prop: 'region', name: 'Region', sortable: true },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: {
sum: commonAggregators.sum,
avg: commonAggregators.avg,
},
},
];
```
`prop` is the source-of-truth field id. `rows`, `columns`, and `values` all refer back to these same `prop` values.
## How Dimensions Are Reused
- In `rows`, a dimension becomes part of the leading row hierarchy.
- In `columns`, a dimension becomes part of the generated header hierarchy.
- In `values`, the same field acts as the carrier for the aggregated measure.
This shared model is what keeps Pivot configuration consistent. You define a field once, then reuse it on whichever analytical axis makes sense.
## Column Options That Matter In Pivot
`PivotConfigDimension` extends `Partial`, so the most useful inherited options are:
- `prop`: The field id Pivot uses everywhere.
- `name`: Display label for generated headers and configurator entries.
- `sortable`: Enables sorting of generated members for that dimension.
- `order`: Initial sort direction for generated members when sorting is enabled.
- `filter`: Filter metadata carried into generated columns.
- `columnType`: Lets generated value columns reuse an existing column type.
- `template`: Custom rendering for generated cells or headers.
- `cellProperties`: Per-cell visual or behavioral overrides.
- `size`, `minSize`, `maxSize`: Width behavior for generated columns.
The most important rule is that Pivot does not invent a second metadata model. Generated columns are still RevoGrid columns, and the dimension is where their base behavior comes from.
## Aggregators Live On The Dimension
Dimensions also declare which aggregators are available when the field is used in `values`.
```ts
{
prop: 'margin',
name: 'Margin',
aggregators: {
sum: commonAggregators.sum,
avg: commonAggregators.avg,
max: commonAggregators.max,
},
}
```
This matters in two places:
- `values[].aggregator` resolves against the dimension.
- The configurator uses the dimension's registered aggregators to populate its value selector.
## Good Dimension Design
- Keep `prop` stable. Changing it breaks saved Pivot layouts.
- Use business-friendly `name` values because generated headers reuse them.
- Define aggregators only where they make semantic sense.
- Avoid using display labels as identifiers. `prop` should be the identifier, `name` should be presentation.
## Common Mistakes
- Omitting a dimension for a field that later appears in `values`.
- Registering an aggregator on a text field just because the function exists.
- Expecting dimension order to control row or column placement. Placement is controlled by `rows` and `columns`, not by the `dimensions` array order.
## Next
Continue with [Aggregations](/guides/pivot/aggregations/) to see how measures are resolved from these dimensions.
---
# Drill-Down
URL: https://pro.rv-grid.com/guides/pivot/concepts/drilldown/
Source: src/content/docs/guides/pivot/concepts/drilldown.mdx
Description: Understand row expansion, column collapse, and the state that controls Pivot exploration.
Drill-down lets users move between summary and detail inside the Pivot structure. In the current implementation there are two separate drill-down concepts: grouped row expansion and generated column collapse.
## Row Drill-Down
Row drill-down is enabled when:
- the effective row hierarchy has more than one level
- `collapsed` is a boolean, `expanded` state exists, or `rowAxisLayout` is `'compact'`
```ts
const pivot: PivotConfig = {
rows: ['region', 'rep'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
};
```
Behavior:
- `collapsed: true` starts grouped rows collapsed
- `collapsed: false` starts grouped rows expanded
- `expanded` stores per-group overrides using grouped path keys
- active row drill-down uses one compact Rows column by default
- `rowAxisLayout: 'tabular'` keeps one visible column per configured row field
Compact mode changes only the generated grid columns. The Pivot model, field
panel, persisted configuration, and remote requests continue to use separate
`region` and `rep` dimensions.
```ts
const pivot: PivotConfig = {
rows: ['region', 'rep'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
rowAxisLayout: 'tabular', // opt back into separate Region and Rep columns
};
```
`PivotPlugin` translates the Pivot row structure into RevoGrid grouping options, then emits `pivot-config-update` when users expand or collapse groups.
## Grouped Row Aggregates
Set `groupAggregations: true` to render aggregate values inside grouped Pivot rows.
```ts
const pivot: PivotConfig = {
rows: ['region', 'rep'],
columns: ['year'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
groupAggregations: true,
};
```
Behavior:
- the grouped row still shows the expand/collapse UI and group label
- generated Pivot value columns show the aggregate for the current grouped branch
- nested groups aggregate only their own descendants
- collapsed groups still show their aggregate values
If you hide the grouped row field column (for example with `hide-columns="region"`), you can move the grouped label into the first visible row cell:
```ts
const pivot: PivotConfig = {
rows: ['region', 'product'],
columns: ['quarter'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
collapsed: true,
rowAxisLayout: 'tabular',
groupAggregations: true,
groupLabelColumn: 'firstVisible',
};
```
`groupLabelColumn` modes:
- `'grouped'` (default): keep label in the grouped field column
- `'firstVisible'`: render label in the first currently visible row cell
Current limitation:
- `groupAggregations` currently applies to the client-side Pivot path
- server-side Pivot still renders grouped labels only unless backend-precomputed grouped aggregates are added
## Column Drill-Down
Column drill-down is built into Pivot whenever `columns` is non-empty. Generated
groups start collapsed without additional configuration.
```ts
columns: ['year', 'quarter', 'month']
```
Behavior:
- generated Pivot column groups become collapsible
- generated groups start collapsed at the highest level and expand one level at a time
- set `columnCollapse: false` or `{ enabled: false }` to disable column drill-down
- set `columnCollapse: { collapsed: false }` to start with all generated groups expanded
- use `columnLevels` to override collapse, subtotal, filter, and sort behavior
independently at each zero-based column level
- collapsed groups render a placeholder header/cell
- placeholder cells use a collapsed-bucket aggregator, not a naive sum of visible child cells
- expanded subtotal columns appear before descendant columns by default, keeping
the parent aggregate visible and actionable
- `columnLevels[level].subtotalValues` can keep that aggregate to one value
field—or any ordered subset—when the Pivot has multiple measures
- expanding reveals one level only; descendants and sibling branches keep
their current state
- aggregate filters and sorting remain attached to the same analytical group
when its compact and expanded presentations switch
Persisted state for generated groups lives in `collapsedColumns`.
See [Column Totals With Drill-Down](/guides/pivot/totals/#column-totals-with-drill-down)
for the complete subtotal/collapse behavior matrix, precedence rules, and
screenshots.
## Related Options
- `collapsed`: row drill-down default
- `expanded`: persisted row group expansion state
- `groupAggregations`: render aggregate values inside grouped client-side Pivot rows
- `groupLabelColumn`: choose where grouped row labels render (`'grouped'` or `'firstVisible'`)
- `columnCollapse`: enable and configure generated column collapse
- `columnLevels`: configure each generated column hierarchy level
- `collapsedColumns`: persisted generated column collapse state
## Placeholder Behavior
When a column group is collapsed, Pivot can render:
- a default placeholder header based on the group name
- a custom placeholder header or cell template
- a custom collapsed aggregator, globally or per value field
This is useful when a collapsed analytical bucket should still show a meaningful aggregate instead of just hiding all child columns.
## State Persistence
`PivotPlugin` emits `pivot-config-update` as users interact with grouped rows or collapsible columns. That event is the right place to persist the current Pivot state if you want drill-down state to survive reloads.
## Common Mistakes
- Expecting row drill-down with only one row field.
- Expecting grouped rows to show metric values without enabling `groupAggregations`.
- Expecting `columnCollapse` to work without generated column groups.
- Treating `expanded` as UI row indexes. It is persisted grouped state, not viewport position.
## Next
Continue with [Sorting And Filtering](../pivot/sorting-filtering/) or [Plugin Lifecycle](../pivot/plugin-lifecycle/).
---
# Export And State
URL: https://pro.rv-grid.com/guides/pivot/concepts/export-state/
Source: src/content/docs/guides/pivot/concepts/export-state.mdx
Description: Export visible Pivot data to CSV, copy Pivot data as TSV, and save Pivot layout as JSON.
Pivot export helpers turn the current visible Pivot grid into plain text that users can download, paste, or save. Pivot state JSON saves the Pivot configuration so the same report layout can be restored later.
## What Each Format Is For
CSV means comma-separated values. Use CSV when users need a file they can open in Excel, Numbers, Google Sheets, or another reporting tool. The Pivot CSV helper flattens grouped column headers into readable header paths and appends pinned bottom rows, such as grand totals, after the body rows.
TSV means tab-separated values. Use TSV for copy and paste because spreadsheets understand tabs as cell boundaries. The Pivot TSV helper keeps row-axis headers and column-axis headers separated, which makes pasted Pivot output easier to read.
State JSON is not table data. It is a saved Pivot configuration, including fields such as rows, columns, values, filters, totals, collapsed state, and expanded groups. Use state JSON when users need to save a report layout, share a layout, or restore a layout after reloading the page.
## Basic Setup
Import the helpers from `@revolist/pivot` with the Pivot plugin:
```ts
import {
buildPivotCopyTsv,
exportVisiblePivotToCsv,
parsePivotStateJson,
PivotPlugin,
serializePivotStateJson,
type PivotConfig,
} from '@revolist/pivot';
```
Render Pivot as usual:
```ts
const pivot: PivotConfig = {
rows: ['region', 'channel'],
columns: ['quarter'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'orders', aggregator: 'sum', label: 'Orders' },
],
totals: {
grandTotal: true,
subtotals: true,
},
};
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = rows;
```
## Read The Visible Grid Model
CSV and TSV helpers work from the current grid model, not from the original raw dataset. Pass the visible `columns`, visible `source`, and any `pinnedBottomSource`.
```ts
function readGridModel(grid: HTMLRevoGridElement & { pinnedBottomSource?: any[] }) {
return {
columns: grid.columns ?? [],
source: grid.source ?? [],
pinnedBottomSource: grid.pinnedBottomSource ?? [],
};
}
```
This matters because Pivot generates columns and rows from the source data. Exporting the original array would miss generated value columns, grouped headers, subtotal rows, and grand-total rows.
## Export CSV
Use `exportVisiblePivotToCsv` when the user wants a file-oriented export:
```ts
const csv = exportVisiblePivotToCsv(readGridModel(grid));
```
For browser download:
```ts
const csv = exportVisiblePivotToCsv(readGridModel(grid));
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'pivot-report.csv';
link.click();
URL.revokeObjectURL(url);
```
CSV is best for file export, email attachments, imports into other systems, and audit snapshots.
## Copy TSV
Use `buildPivotCopyTsv` when the user wants clipboard text:
```ts
const tsv = buildPivotCopyTsv(readGridModel(grid));
await navigator.clipboard.writeText(tsv);
```
TSV is best for copy actions because pasting into a spreadsheet preserves cells without requiring the user to import a file.
You can also limit copy output when you know the selected props or selected rows:
```ts
const tsv = buildPivotCopyTsv({
...readGridModel(grid),
selectedProps: ['region', '__pivot_grand_total__|revenue'],
selectedRowIndexes: [0, 1, 2],
});
```
Only pass selected props that exist in the current visible columns. Missing props are ignored.
## Save State JSON
Use `serializePivotStateJson` to turn the current Pivot configuration into deterministic JSON:
```ts
const json = serializePivotStateJson(pivot);
localStorage.setItem('sales:pivot-state', json);
```
The JSON is wrapped with a kind and version so the parser can reject unsupported state:
```json
{
"config": {
"columns": ["quarter"],
"rows": ["region", "channel"],
"values": [{ "aggregator": "sum", "prop": "revenue" }]
},
"kind": "revogrid:pivot.state",
"version": 1
}
```
Use `parsePivotStateJson` to validate and restore it:
```ts
try {
const saved = localStorage.getItem('sales:pivot-state');
if (saved) {
grid.pivot = parsePivotStateJson(saved) as PivotConfig;
}
} catch (error) {
console.error('Could not restore Pivot state:', error);
}
```
State JSON is best for saved views, shared report layouts, user preferences, and restoring drill-down state after refresh.
## Practical Example
This example exposes four buttons:
- `CSV`: reads the visible Pivot grid and writes CSV text into the output box.
- `TSV`: reads the visible Pivot grid and writes clipboard-ready TSV into the output box.
- `State JSON`: serializes the current Pivot configuration.
- `Apply JSON`: parses the edited JSON and applies it back to the grid.
The important part is keeping the data export path separate from the state path:
```ts
let pivotConfig: PivotConfig = {
rows: ['region', 'channel'],
columns: ['quarter'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'orders', aggregator: 'sum', label: 'Orders' },
],
};
function showCsv() {
output.value = exportVisiblePivotToCsv(readGridModel(grid));
}
function showTsv() {
output.value = buildPivotCopyTsv(readGridModel(grid));
}
function showState() {
output.value = serializePivotStateJson(pivotConfig);
}
function applyState() {
pivotConfig = parsePivotStateJson(output.value) as PivotConfig;
grid.pivot = pivotConfig;
}
```
## Common Mistakes
- Exporting the original source rows instead of the visible grid model. Use `grid.columns`, `grid.source`, and `grid.pinnedBottomSource`.
- Using CSV for copy and paste. TSV usually pastes into spreadsheet cells more reliably.
- Expecting state JSON to contain row data. It stores Pivot configuration, not the source dataset or exported result.
- Applying edited JSON without `try` / `catch`. Invalid JSON, unsupported versions, and invalid field shapes throw errors.
- Forgetting pinned bottom rows. If grand totals are enabled, include `pinnedBottomSource` so totals appear in CSV and TSV output.
- Saving functions in state JSON. State JSON can only contain JSON-safe configuration values; custom runtime callbacks must be restored by application code.
## Next
Continue with [Sorting And Filtering](../sorting-filtering/) or [Field Panel](../field-panel/).
---
# Field Metadata
URL: https://pro.rv-grid.com/guides/pivot/concepts/field-metadata/
Source: src/content/docs/guides/pivot/concepts/field-metadata.mdx
Description: Configure Pivot field labels, hidden dimensions, empty group labels, measure aliases, and selective subtotals.
Field metadata is the information Pivot uses to describe fields before they become row groups, column groups, filters, or measures. Use it to give users clearer labels, keep internal fields out of normal field lists, and make generated totals easier to read.
## Hidden Dimensions
Mark a dimension as `hidden` when it should still be available to the Pivot config but should not appear in normal field lists.
```ts
const dimensions = [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'internalSegment',
name: 'Internal Segment',
description: 'Internal reporting segment.',
hidden: true,
},
];
const pivot = {
dimensions,
rows: ['region'],
columns: ['quarter', 'internalSegment'],
filters: ['internalSegment'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
};
```
Hidden does not remove the field from the Pivot engine. In this example, `internalSegment` can still be used in `columns` and `filters`; it is only hidden from helper-driven field lists unless you explicitly show hidden fields.
## Show Hidden Fields In Search
Use `filterPivotDimensions` for searchable field lists. Hidden dimensions are omitted by default. Pass `{ showHidden: true }` when you want an admin or advanced-user mode that reveals them.
```ts
import { filterPivotDimensions } from '@revolist/pivot';
const visibleFields = filterPivotDimensions(dimensions, searchText);
const allFields = filterPivotDimensions(dimensions, searchText, {
showHidden: true,
});
```
The helper searches `prop`, `name`, and `description`, so descriptions are useful for discovery even when the visible label is short.
## Empty And Null Group Labels
Real data often contains empty strings, `null`, or `undefined`. Pivot can replace those values with user-facing labels in row and column groups.
```ts
const pivot = {
rows: ['region', 'channel'],
columns: ['quarter'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
groupLabels: {
empty: '(Empty)',
null: '(No value)',
},
};
```
`groupLabels.empty` is used for empty strings (`''`). `groupLabels.null` is used for `null` and `undefined`. Without these options, the current implementation displays empty labels for those group members.
## Measure Aliases
Each value can have its own display label. Use `label` for the preferred generated measure name. Use `name` as a fallback when `label` is not set.
```ts
const pivot = {
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue $' },
{ prop: 'units', aggregator: 'sum', name: 'Units Sold' },
],
};
```
Aliases are useful when the source field name is technical, abbreviated, or shared by multiple reports.
## Disabled Subtotals
Global subtotals can stay enabled while specific subtotal rows or subtotal columns are skipped. Target subtotals by axis field or by zero-based axis level.
```ts
const pivot = {
rows: ['region', 'channel', 'product'],
columns: ['quarter', 'internalSegment'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
totals: {
grandTotal: true,
subtotals: true,
disabledSubtotals: {
rows: {
fields: ['channel'],
},
columns: {
levels: [0],
},
},
},
};
```
In this example, Pivot keeps subtotal generation enabled overall, skips row subtotals for the `channel` field, and skips column subtotals at level `0`.
## Complete Config
```ts
const pivot = {
dimensions: [
{ prop: 'region', name: 'Region', description: 'Sales territory.' },
{ prop: 'channel', name: 'Channel', description: 'Go-to-market channel.' },
{ prop: 'product', name: 'Product' },
{ prop: 'quarter', name: 'Quarter' },
{ prop: 'internalSegment', name: 'Internal Segment', hidden: true },
{ prop: 'revenue', name: 'Revenue' },
{ prop: 'units', name: 'Units' },
],
rows: ['region', 'channel', 'product'],
columns: ['quarter', 'internalSegment'],
filters: ['internalSegment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue $' },
{ prop: 'units', aggregator: 'sum', name: 'Units Sold' },
],
groupLabels: {
empty: '(Empty)',
null: '(No value)',
},
totals: {
grandTotal: true,
subtotals: true,
disabledSubtotals: {
rows: { fields: ['channel'] },
columns: { levels: [0] },
},
},
};
```
## Common Mistakes
- Expecting `hidden: true` to prevent a field from being used in `rows`, `columns`, `filters`, or `values`. It only affects field-list helpers.
- Passing `{ showHidden: true }` to the Pivot config. It belongs to `filterPivotDimensions`, not `pivot`.
- Using only `groupLabels.empty` when the source data contains `null` or `undefined`.
- Disabling `totals.subtotals` and then expecting `disabledSubtotals` to do anything. Selective disabling only matters when `subtotals: true`.
---
# Field Panel
URL: https://pro.rv-grid.com/guides/pivot/concepts/field-panel/
Source: src/content/docs/guides/pivot/concepts/field-panel.mdx
Description: Show Pivot row, column, data, and filter fields above the grid.
The Pivot field panel renders the active field layout above the grid. Use it when users need to see or rearrange row, column, data, and filter fields without opening a separate configurator panel.
## Enable It
```ts
const pivot = {
dimensions: [...],
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
filters: ['year'],
fieldPanel: {
visible: true,
showResetLayout: false,
allowFieldDragging: true,
allowFieldRemoving: false,
showDataFields: true,
showRowFields: true,
showColumnFields: true,
showFilterFields: true,
texts: {
dropFilterFieldsHere: 'Drop Filter Fields Here',
},
},
};
```
## Options
- `fieldPanel.visible`: mounts the compact in-grid field panel.
- `fieldPanel.showResetLayout`: shows the Reset layout action in Pivot configuration UI. Default is `false`.
- `allowFieldDragging`: enables drag-and-drop between panel areas.
- `allowFieldRemoving`: enables explicit removal from areas. Default is `false`.
- `showDataFields`, `showRowFields`, `showColumnFields`, `showFilterFields`: toggle individual areas.
- `fieldPanel.texts`: overrides field panel labels and placeholders.
- `filters`: stores the ordered filter fields shown in the filter area.
`fieldPanel.visible` is separate from `hasConfigurator`. Use `fieldPanel.visible` for the compact in-grid field strip, and `hasConfigurator` for the full side configurator.
## Reuse Dropdown Cell Templates
Filter fields with `filterOptions` can reuse the owning dimension's dropdown
presentation. Enable `dropdown.syncCellTemplate` and provide the same dropdown
source metadata used by the cell template:
```ts
const statusBadge = (h, { model, value }) =>
h('span', { class: `status-badge ${model.tone}` }, value);
const pivot = {
dimensions: [{
prop: 'status',
filterOptions: ['Open', 'Closed'],
cellTemplate: statusBadge,
dropdown: {
syncCellTemplate: true,
source: [
{ value: 'Open', label: 'Open', tone: 'green' },
{ value: 'Closed', label: 'Closed', tone: 'gray' },
],
},
}],
filters: ['status'],
};
```
The synchronized content is used by both the compact field-panel selector and
the Pivot member-filter dialog. Explicit dropdown renderers remain
authoritative. When the field panel is mounted by `PivotPlugin`, grid
`columnTypes` and `additionalData` are forwarded to the template resolver.
---
# Formatting Presets
URL: https://pro.rv-grid.com/guides/pivot/concepts/formatting-presets/
Source: src/content/docs/guides/pivot/concepts/formatting-presets.mdx
Description: Format Pivot values with locale-aware number, currency, percent, and date presets, then highlight analytical cells with conditional formatting.
Formatting presets help Pivot reports show numbers, money, percentages, and dates in a readable way. Conditional formatting presets then help users spot high revenue, low margin, or other important analytical values.
## Basic Setup
Import the helpers from `@revolist/pivot` and create reusable formatters for the value types in your report:
```ts
import {
createPivotValueFormatter,
formatPivotValue,
pivotConditionalFormattingPresets,
createPivotConditionalCellProperties,
PivotPlugin,
type PivotConfig,
} from '@revolist/pivot';
const revenueFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'USD',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const marginFormatter = createPivotValueFormatter({
preset: 'percent',
locale: 'en-US',
options: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
});
```
`createPivotValueFormatter()` returns a function. Use it when the same formatting rule is reused by many cells. Use `formatPivotValue()` when you only need to format one value:
```ts
const formattedLabel = formatPivotValue(null, {
preset: 'date',
nullDisplay: 'No close date',
});
```
## Value Formatters
Pivot formatters are display helpers. They do not change the original source data or the aggregated number that Pivot calculates. Keep your source values as real numbers and dates, then format them at render time.
```ts
const columnTypes = {
pivotCurrency: {
cellTemplate: (_h, props) => revenueFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
pivotPercent: {
cellTemplate: (_h, props) => marginFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
};
```
Attach those column types to the Pivot dimensions that produce analytical value cells:
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{ prop: 'segment', name: 'Segment' },
{ prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency' },
{ prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent' },
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
};
```
## Locale And Presets
The formatter presets use the browser `Intl` formatters. Choose the preset that matches the value:
- `number`: plain numeric values such as counts, units, or scores.
- `currency`: money values. Always provide `currency`, for example `"USD"` or `"EUR"`.
- `percent`: ratio values such as `0.315`, displayed as `31.5%`.
- `date`: date-only values.
- `datetime`: date and time values.
```ts
const integerFormatter = createPivotValueFormatter({
preset: 'number',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const eurFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'EUR',
locale: 'de-DE',
});
const dateFormatter = createPivotValueFormatter({
preset: 'date',
locale: 'en-US',
options: { month: 'short', day: '2-digit', year: 'numeric' },
});
```
## Conditional Formatting Presets
Use `pivotConditionalFormattingPresets` to describe matching rules, and `createPivotConditionalCellProperties()` to turn those rules into a RevoGrid `cellProperties` function.
```ts
const conditionalCellProperties = createPivotConditionalCellProperties(
[
pivotConditionalFormattingPresets.gt(300000, {
field: 'revenue',
style: {
backgroundColor: 'rgba(22, 163, 74, 0.16)',
color: '#166534',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.lt(0.28, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(220, 38, 38, 0.14)',
color: '#991b1b',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.between(0.28, 0.34, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(234, 179, 8, 0.14)',
color: '#854d0e',
},
}),
],
{ match: 'all' },
);
```
Available presets:
- `gt(value, props)`: greater than a number.
- `lt(value, props)`: less than a number.
- `between(min, max, props)`: between two numbers, including both bounds.
- `equal(value, props)`: equal to a value.
- `textContains(value, props)`: contains text.
The optional `field` narrows a rule to a generated value field. For example, `field: 'revenue'` applies to generated revenue cells even when Pivot creates column paths such as `Software|revenue`.
## Analytical Cells
By default, `createPivotConditionalCellProperties()` only formats Pivot analytical cells. These are generated cells from the `values` area, not row header fields such as `region` or `closeDate`.
This default prevents a rule like `gt(300000)` from accidentally styling row dimensions that happen to contain comparable values. If you intentionally want rules to apply outside analytical value cells, enable `includeNonAnalytical`:
```ts
const allCellProperties = createPivotConditionalCellProperties(rules, {
includeNonAnalytical: true,
});
```
Most Pivot reports should keep the default and attach conditional formatting to value dimensions:
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum' },
{ prop: 'marginRate', aggregator: 'avg' },
],
};
```
## Practical Example
This report groups opportunities by region and close date, splits generated value columns by segment, formats revenue as currency, formats margin as a percent, and highlights high revenue or weak margin.
```ts
const rows = [
{ region: 'North America', closeDate: '2026-01-16', segment: 'Software', revenue: 184000, marginRate: 0.37 },
{ region: 'North America', closeDate: '2026-01-22', segment: 'Services', revenue: 126000, marginRate: 0.31 },
{ region: 'EMEA', closeDate: '2026-02-06', segment: 'Services', revenue: 118000, marginRate: 0.28 },
];
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
totals: {
subtotals: true,
grandTotal: true,
},
groupLabels: {
null: formatPivotValue(null, { preset: 'date', nullDisplay: 'No close date' }),
},
};
const grid = document.querySelector('revo-grid');
grid.columnTypes = columnTypes;
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = rows;
```
## Common Mistakes
- Formatting the source data before Pivot aggregates it. Keep values numeric so `sum`, `avg`, and comparisons work correctly.
- Using `preset: 'percent'` with whole percentages. `31.5%` should usually be stored as `0.315`, not `31.5`.
- Forgetting `currency` when using the `currency` preset.
- Expecting conditional formatting to affect row fields by default. It targets analytical value cells unless `includeNonAnalytical` is enabled.
- Omitting `field` on a conditional rule when the report has several value fields. Without `field`, the rule can match every analytical value cell.
- Expecting `cellTemplate` formatting to change conditional comparisons. Conditional rules evaluate the raw cell value, not the formatted display string.
## Next
Continue with [Sorting And Filtering](../sorting-filtering/) or [Configuration Reference](/guides/pivot/configuration-reference/).
---
# Plugin Lifecycle
URL: https://pro.rv-grid.com/guides/pivot/concepts/plugin-lifecycle/
Source: src/content/docs/guides/pivot/concepts/plugin-lifecycle.mdx
Description: Understand how PivotPlugin applies, updates, and clears Pivot state on a RevoGrid instance.
`PivotPlugin` is the runtime layer that turns the direct `grid.pivot` config into rendered rows, generated columns, and grouped state. Understanding its lifecycle helps you persist user choices, avoid conflicting updates, and reason about when Pivot owns the grid payload.
## Activation
Pivot activates when:
- `PivotPlugin` is present in `grid.plugins`
- `grid.pivot` contains a config
The plugin observes the direct `pivot` property and reapplies Pivot whenever the config changes. Legacy `additionalData.pivot` updates are still supported for compatibility.
## What Happens On Apply
When `applyPivot` runs:
1. the plugin captures the original source, columns, grouping, and pinned-bottom rows on first activation
2. it decides whether to use the client or server execution path
3. it generates or loads Pivot rows and columns
4. it writes those generated structures back to the grid
This means Pivot is temporarily the owner of the rendered grid payload.
## Client Path
In client mode:
- raw grid source is read from the captured original data
- `createPivotData` materializes Pivot rows
- `pivotColumns` generates the column tree
- grand total rows are moved to `pinnedBottomSource`
## Server Path
In server mode:
- the plugin resolves a `PivotEngineAdapter`
- it builds a `PivotLoadRequest`
- it aborts any stale in-flight remote request
- only the latest response is allowed to update the grid
This is the last-write-wins behavior that protects the UI when filters or layouts change quickly.
## Configurator Integration
If `hasConfigurator` is enabled, the plugin renders the configurator and wires its updates back into the Pivot state. Each configurator update:
- emits `pivot-config-update`
- applies the updated config unless the event was prevented
## `pivot-config-update`
This event is the main integration point for external state ownership.
Use it to:
- persist layout changes
- sync Pivot state into a framework store
- restore `expanded` or `collapsedColumns` later
## Reapplication Triggers
Pivot recalculates when:
- `grid.pivot` changes
- legacy `additionalData.pivot` changes
- the underlying non-Pivot source changes
- the underlying non-Pivot columns change
- row drill-down or column collapse state changes through plugin-managed interactions
The plugin intentionally avoids treating already-generated Pivot rows or columns as new base inputs.
## Clearing Pivot
`clearPivot()`:
- aborts any remote load
- removes managed Pivot state
- restores the original source, columns, grouping, and pinned-bottom rows
Removing `grid.pivot` has the same practical effect. Removing legacy `additionalData.pivot` also clears Pivot when that legacy path is the active source.
## Best Practices
- Treat direct `grid.pivot` / framework `pivot` binding as the durable state you own.
- Listen to `pivot-config-update` if user changes should persist.
- Do not manually overwrite `grid.source` or `grid.columns` with Pivot active unless you mean to replace the base data and trigger a reapply.
## Related Guides
- [Configuration Reference](/guides/pivot/configuration-reference/)
- [Pivot Charts](/guides/pivot/concepts/charts/)
- [Configurator API](/guides/pivot/configurator-api/)
- [Field Panel](/guides/pivot/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
---
# Sorting And Filtering
URL: https://pro.rv-grid.com/guides/pivot/concepts/sorting-filtering/
Source: src/content/docs/guides/pivot/concepts/sorting-filtering.mdx
Description: Learn what sorting and filtering affect in Pivot output and how client-side and remote/server-side behavior differ.
Sorting and filtering in Pivot operate on analytical members and generated measures, not on the final rendered cells alone. The same ideas exist in both client-side and server-side Pivot, but the execution layer is different.
## Client-Side Sorting
Client-side Pivot uses dimension metadata when generating row and column members.
Relevant dimension options:
- `sortable`
- `order`
If a Pivot column dimension is marked sortable, generated members for that dimension are ordered using the configured comparer and sort direction.
## Client-Side Filtering
Generated value columns inherit configured filter metadata from their dimensions, so the resulting grid can expose filter-ready columns. This is still based on the generated Pivot output, not on the raw fact table semantics of a remote analytical engine.
## What Actually Gets Sorted
In client mode:
- row hierarchy ordering comes from the grouped source data
- column hierarchy ordering comes from the configured dimension metadata
- measure cells are results of aggregation, not independently sorted records
## Server-Side Sorting And Filtering
In server mode:
- filter expressions are part of `PivotLoadRequest.loadOptions.filter`
- sort directives are part of `PivotLoadRequest.loadOptions.sort`
- the analytical engine is responsible for applying them before returning the visible window
That distinction matters because server-side Pivot should sort and filter the cube window, not just the already-rendered grid cells.
## Sort By Summary
The remote request contract also supports sort-by-summary semantics through `bySummary` on sort descriptors. Use that when your backend can rank groups by a measure rather than only by a dimension label.
## Best Practices
- Keep client-side sorting expectations simple: it orders generated members, not facts.
- Use remote/server-side filtering for large datasets or security-sensitive selectors.
- Do not rely on client-generated columns as the trust boundary for secure filtering. The field registry belongs on the server side.
## Next
Continue with [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) to see how Pivot owns and restores grid state.
---
# Totals
URL: https://pro.rv-grid.com/guides/pivot/concepts/totals/
Source: src/content/docs/guides/pivot/concepts/totals.mdx
Description: Learn how Pivot grand totals and subtotals work across rows and generated columns.
Totals help users move from detailed branch-level summaries to whole-table summaries. RevoGrid Pivot supports grand totals and subtotals through `pivot.totals`.
## The Totals Config
```ts
totals: {
grandTotal: true,
subtotals: true,
grandTotalLabel: 'All Sales',
subtotalLabel: 'Subtotal',
}
```
## What Each Option Does
- `grandTotal`: Adds a grand-total row. When column dimensions exist, it also adds grand-total value columns.
- `subtotals`: Adds subtotal rows for intermediate row branches and subtotal value columns for intermediate column branches.
- `grandTotalLabel`: Overrides the default `"Grand Total"` label.
- `subtotalLabel`: Overrides the default `"Subtotal"` label.
- `suppressSingleChildSubtotals`: Hides subtotal rows for single-source branches where the subtotal duplicates the only leaf.
- `suppressGrandTotalWhenSingleLeaf`: Hides the grand-total row when the whole result is one source-backed leaf.
## Smart Suppression
Use suppression when totals are enabled for broad reports, but small filtered results should avoid duplicate summary rows:
```ts
totals: {
subtotals: true,
grandTotal: true,
suppressSingleChildSubtotals: true,
suppressGrandTotalWhenSingleLeaf: true,
}
```
Suppression is conservative. Multiple source rows, multiple meaningful leaves, or disabled `subtotals` / `grandTotal` keep the existing total behavior.
## Row Totals
When row fields exist:
- subtotal rows are inserted after the descendants of each branch
- the grand-total row is rendered separately and pinned at the bottom
This keeps the detailed body rows readable while still making the full-table total visible.
## Column Totals
When column fields exist:
- intermediate column paths can expose subtotal value columns
- the full column hierarchy can expose grand-total value columns
Pivot does not create a separate total table. Totals become part of the generated analytical structure.
## Column Totals With Drill-Down
Column totals and column collapse are designed to work together. A collapsed
group shows one aggregate carrier. Expanding that group keeps the parent
aggregate visible as a subtotal column and reveals only the next configured
level.
Collapsed years use one aggregate carrier per year. Grand-total columns
remain independent from the branch collapse state.
Expanding 2023 reveals its configured Year Total first, followed by the
next-level month groups. The subtotal spans the otherwise unused lower
header depth.
Drill-down is progressive: expanding January reveals only January's
measures. Sibling months stay collapsed and Year Total remains one Sales
subtotal column.
### Behavior Matrix
| Configuration | Collapsed group | Expanded group |
| --- | --- | --- |
| `totals.subtotals: false` | Uses the regular collapsed aggregate bucket. | Shows descendants without a parent subtotal column. |
| `totals.subtotals: true` and the level subtotal is enabled | Uses the level subtotal as the aggregate carrier. | Shows the same subtotal plus the next visible descendant level. |
| `columnLevels[level].subtotal: false` | Uses the regular collapsed aggregate bucket for that level. | Omits the subtotal for that level, even when other levels show subtotals. |
| `columnLevels[level].collapsible: false` | The level cannot enter a collapsed state. | The level stays expanded; its subtotal can still be shown. |
| `subtotalPosition: 'before'` | Position is not visible while the branch is compact. | Places the parent subtotal before descendant columns. This is the default. |
| `subtotalPosition: 'after'` | Position is not visible while the branch is compact. | Places the parent subtotal after descendant columns. |
| `subtotalValues: ['sales']` | The compact carrier exposes only Sales for that subtotal. | The subtotal contains only Sales, while descendant groups can still expose every configured measure. |
| `totals.grandTotal: true` | Grand-total columns and the pinned grand-total row remain visible. | Grand totals remain independent from individual branch expansion. |
### Recommended Per-Level Configuration
This configuration matches the screenshots:
```ts
const pivot: PivotConfig = {
rows: ['country', 'segment'],
columns: ['year', 'month'],
values: [
{ prop: 'sales', aggregator: 'sum' },
{ prop: 'profit', aggregator: 'sum' },
{ prop: 'unitsSold', aggregator: 'sum' },
],
columnCollapse: {
enabled: true,
collapsed: true,
},
columnLevels: {
0: {
collapsible: true,
collapsed: true,
subtotal: true,
subtotalLabel: 'Year Total',
subtotalPosition: 'before',
subtotalValues: ['sales'],
filterable: true,
sortable: true,
},
1: {
collapsible: true,
collapsed: true,
subtotal: false,
filterable: false,
sortable: false,
},
},
totals: {
subtotals: true,
grandTotal: true,
},
};
```
`columnLevels` uses the zero-based order of `columns`: level `0` configures
`year`, level `1` configures `month`, and so on.
### Resolution And Precedence
The following rules make mixed configurations predictable:
1. `totals.subtotals` is the global subtotal gate. When it is `false`, no
`columnLevels[level].subtotal` setting can enable a subtotal.
2. With global subtotals enabled, an explicit
`columnLevels[level].subtotal` overrides
`totals.disabledSubtotals.columns` for that level.
3. An explicit `columnLevels[level].collapsible` overrides the global
`columnCollapse` setting for that level. When omitted, the level inherits
`columnCollapse.enabled`.
4. `columnLevels[level].collapsed` controls the initial state for that level
and falls back to `columnCollapse.collapsed`.
5. Persisted `collapsedColumns` state wins over both initial-state defaults for
the matching generated group.
6. `subtotalLabel`, `subtotalPosition`, `subtotalValues`, `filterable`, and
`sortable` apply only to their configured level.
7. Grand-total columns do not belong to a generated hierarchy level, so
`columnLevels` does not change them.
### Filtering And Sorting Through Collapse
The subtotal or collapsed aggregate represents one analytical group across
both visual states. A filter or sort applied to that aggregate therefore stays
active when the group expands or collapses:
- expanding does not broaden the filter to every newly visible child column
- collapsing does not discard the active filter or sort direction
- the active control moves between the compact carrier, parent group proxy,
and visible subtotal without changing its analytical property
- `filterable: false` removes aggregate filter controls for that level
- `sortable: false` removes aggregate sorting for that level
The expand/collapse control appears before the group title. Expanding a group
reveals one hierarchy level only; it does not expand its descendants or sibling
branches.
### Header Spanning
A direct subtotal such as `Year Total` fills the unused descendant header rows
vertically. It remains one value column; it does not span horizontally across
the descendant columns. This keeps the subtotal aligned with deeper headers
without covering the parent expand/collapse control.
## Example
For:
```ts
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
```
You get:
- leaf rows for each `region -> rep`
- subtotal rows for each `region`
- grand total at the bottom
- leaf value columns for each `year -> quarter`
- subtotal value columns for each `year`
- grand-total value columns across all years and quarters
## Important Notes
- Subtotals only appear when a hierarchy has more than one level to summarize.
- Grand totals use the configured label only for display. The internal total keys stay synthetic.
- A column subtotal remains the same analytical aggregate through collapse and
expansion; only its header presentation changes.
- Per-level subtotal, collapse, filter, and sort settings are independent.
- In remote/server mode, totals stay part of the analytical contract and should be computed by the engine, not recomputed from visible UI cells.
## Common Mistakes
- Expecting `subtotals: true` to add subtotal rows when there is only one row field.
- Expecting grand totals to stay inside the body rows. In the current implementation, grand total rows are pinned bottom rows.
- Assuming `subtotal: true` can bypass `totals.subtotals: false`.
- Assuming expanding a year should expand every month below it.
- Confusing a regular collapsed bucket with a subtotal carrier. They look
similarly compact, but only a level with an enabled subtotal exposes that
same subtotal as a visible column after expansion.
## Next
Continue with [Values On Rows](/guides/pivot/values-on-rows/) or [Drill-Down](/guides/pivot/drilldown/).
---
# Values On Rows
URL: https://pro.rv-grid.com/guides/pivot/concepts/values-on-rows/
Source: src/content/docs/guides/pivot/concepts/values-on-rows.mdx
Description: Move Pivot measures into the row hierarchy and understand when that layout works best.
By default, Pivot renders measures as generated value columns. `valuesOnRows` flips that layout so measures become synthetic row members instead.
## Default Layout vs Values On Rows
Default layout:
```txt
rows: Region
columns: Quarter
values: Sales, Margin
```
This usually produces:
- one row branch per region
- one generated value column per quarter and measure
With `valuesOnRows: true`, the same measures become row members:
- Region
- Sales
- Margin
That is useful when:
- you have many measures and few row dimensions
- you want a more compact column area
- users compare measures vertically rather than horizontally
## Configuration
```ts
const pivot: PivotConfig = {
rows: ['region'],
columns: ['quarter'],
values: [
{ prop: 'sales', aggregator: 'sum' },
{ prop: 'margin', aggregator: 'avg' },
],
valuesOnRows: true,
};
```
## Explicit Values Position
Use `rowTree` with the `$values` pseudo-field when measures need to appear at a specific hierarchy level:
```ts
rowTree: ['carModel', 'cellId', '$values']
rowTree: ['carModel', '$values', 'cellId']
rowTree: ['$values', 'carModel', 'cellId']
```
`rowTree` is the explicit advanced API. If it is provided, it controls value placement even when `valuesOnRows` is also set.
## What Changes Internally
- Pivot adds a synthetic row field called `Values`.
- Generated value columns become value labels in the row hierarchy.
- Grouping and row drill-down now treat the value label as part of the row structure.
This is why row drill-down helpers account for `valuesOnRows` when deriving row props.
## Tradeoffs
Benefits:
- fewer generated columns
- easier comparison of multiple measures
- clearer layouts on narrow screens
Costs:
- more synthetic rows
- row drill-down can feel deeper because the value label becomes part of the hierarchy
- users expecting a spreadsheet-style “measures across columns” layout may find it less familiar
## When Not To Use It
Avoid `valuesOnRows` when:
- you only have one measure
- your users think in terms of wide analytical columns
- you already have a deep row hierarchy and want to keep rows compact
## Next
Continue with [Drill-Down](/guides/pivot/drilldown/) to see how row and column expansion behave in both layouts.
---
# Configurator
URL: https://pro.rv-grid.com/guides/pivot/configurator/
Source: src/content/docs/guides/pivot/configurator/index.mdx
---
# Configurator
URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator/
Source: src/content/docs/guides/pivot/configurator/configurator.mdx
Description: Learn what the Pivot configurator does and how users interact with rows, columns, and values.
The configurator is the drag-and-drop field panel for Pivot. It gives users a visual way to rearrange dimensions between rows, columns, and values without writing a new `PivotConfig` by hand.
## What It Does
The configurator renders:
- a dimension list
- a rows drop zone
- a columns drop zone
- a values drop zone
When users move a field:
- the configurator emits updated rows, columns, or values
- `PivotPlugin` re-emits `pivot-config-update`
- the Pivot result is recomputed and the grid updates
## Enable It
```ts
const pivot: PivotConfig = {
dimensions: [...],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
hasConfigurator: true,
};
```
## Visibility Controls
Use these options to hide zones:
- `showRows`
- `showColumns`
- `showValues`
These only affect the configurator UI. They do not remove support for the corresponding Pivot feature.
The Reset layout action is hidden by default. Set `fieldPanel.showResetLayout: true` to expose it in either the full configurator or compact field panel.
## Mounting
By default, `PivotPlugin` creates the configurator inside the grid wrapper. If you set `mountTo`, it mounts into the supplied element instead.
## Aggregator Selection
The configurator’s value zone reads available aggregators from the matching dimension. That means aggregator choices stay consistent with the field definition, rather than being hard-coded in the panel.
## i18n
Configurator labels come from `i18n`. If omitted, Pivot uses the built-in English defaults from `PIVOT_CONFIG_EN`.
## Common Mistakes
- Enabling `hasConfigurator` without providing useful `dimensions`.
- Expecting hidden zones to disable Pivot features globally.
- Treating the configurator as the state owner. It is a UI for editing Pivot state, not the persistence layer.
## Next
Continue with [Field Panel](/guides/pivot/field-panel/) for the compact in-grid layout, [Configurator API](/guides/pivot/configurator-api/) for the standalone embedding surface, or [Server-Side Pivot](/guides/pivot/server-side/) for remote analytical usage.
---
# Configurator API
URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator-api/
Source: src/content/docs/guides/pivot/configurator/configurator-api.mdx
Description: Use the standalone Pivot configurator and understand its callbacks, mounting model, and integration points.
The built-in configurator can be used through `PivotPlugin`, but it is also available as a standalone API. Use this when you want the field panel in a custom shell, sidebar, or framework-managed layout.
## Main Entry Point
```ts
import { definePivotConfigurator } from '@revolist/pivot';
```
`definePivotConfigurator(el, config, actions)` mounts the configurator into any element.
## Example
```ts
definePivotConfigurator(container, pivotConfig, {
onUpdateConfiguration: (patch) => {
pivotConfig = { ...pivotConfig, ...patch };
},
onUpdateColumns: (columns) => {
pivotConfig = { ...pivotConfig, columns };
},
onUpdateRows: (rows) => {
pivotConfig = { ...pivotConfig, rows };
},
onUpdateValues: (values) => {
pivotConfig = { ...pivotConfig, values };
},
});
```
## `PivotConfigurationActions`
The current callback surface is:
- `onUpdateConfiguration`
- `onUpdateColumns`
- `onUpdateRows`
- `onUpdateValues`
- `onUpdateFilters`
The zone callbacks receive the new ordered configuration for one edited zone.
`onUpdateConfiguration` receives a partial `PivotConfig` containing every zone
changed by one action. Prefer it when an integration must keep cross-zone drag,
group drag, or Rows/Columns transpose updates atomic and avoid multiple Pivot
regenerations. The individual callbacks remain available for compatibility.
## What The Configurator Does Not Own
The configurator does not:
- execute Pivot by itself
- persist state for you
- own the grid instance
It is purely a configuration UI. You decide where the updated state goes next.
## Embedded Via `PivotPlugin`
When `hasConfigurator` is enabled on `PivotConfig`, `PivotPlugin` internally uses the same standalone configurator surface and wires it to `pivot-config-update`.
That means:
- standalone usage and plugin-managed usage stay conceptually aligned
- examples built around the standalone API still apply to plugin-managed usage
## Visibility And i18n
The configurator reads:
- `showRows`
- `showColumns`
- `showValues`
- `i18n`
These options let you tailor the panel for a simpler or more controlled layout.
## Best Practices
- Keep the configurator state source-of-truth in one place, usually the same state object you pass to the grid.
- Provide stable `dimensions` metadata so labels and aggregators stay consistent.
- Persist the resulting `PivotConfig` rather than trying to persist the configurator UI separately.
## Related Guides
- [Configurator](/guides/pivot/configurator/)
- [Field Panel](/guides/pivot/field-panel/)
- [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/)
- [Configuration Reference](/guides/pivot/configuration-reference/)
---
# Pivot Features
URL: https://pro.rv-grid.com/guides/pivot/features/
Source: src/content/docs/guides/pivot/features.mdx
Description: Current Pivot feature status sourced from the standalone Pivot package.
---
# Introduction
URL: https://pro.rv-grid.com/guides/pivot/introduction/
Source: src/content/docs/guides/pivot/introduction/index.mdx
---
# Data Modeling for Pivot
URL: https://pro.rv-grid.com/guides/pivot/introduction/data-modeling/
Source: src/content/docs/guides/pivot/introduction/data-modeling.mdx
Description: Learn how to structure your data for optimal performance and analytical flexibility in RevoGrid Pivot.
The quality of your Pivot analysis depends entirely on how your data is structured. To get the most out of RevoGrid Pivot, you should understand the difference between **Facts** and **Dimensions**.
## Facts vs. Dimensions
In the world of data modeling (specifically the **Star Schema**), data is divided into two categories:
### 1. Fact Tables (The "Events")
A Fact Table contains the quantitative data you want to measure. Each row represents a specific occurrence or transaction.
- **Characteristics**: Usually has many rows, contains numeric "measures," and "foreign keys" to dimension tables.
- **Example Fields**: `Price`, `Quantity`, `Duration`, `DiscountAmount`.
### 2. Dimension Tables (The "Context")
A Dimension Table contains descriptive attributes that provide context to your facts.
- **Characteristics**: Usually has fewer rows, contains categorical text or dates.
- **Example Fields**: `Product Name`, `Color`, `Region`, `Customer Segment`, `Date (Year/Month/Day)`.
## The "Perfect" Pivot Source
For RevoGrid's client-side Pivot, the "perfect" input is a **Denormalized Flat Table**. This means you have "joined" your facts and dimensions into a single list of objects before passing them to the grid.
### ❌ Bad Data Shape (Normalized)
The grid has to look up values in multiple places. Hard to pivot.
```json
// Transactions
[{ "id": 1, "product_id": 101, "amount": 50 }]
// Products
[{ "id": 101, "name": "Apple" }]
```
### ✅ Good Data Shape (Denormalized)
Everything the pivot needs is in one object.
```json
[
{
"id": 1,
"product": "Apple",
"category": "Fruit",
"region": "North",
"amount": 50,
"date": "2024-05-01"
}
]
```
## Modeling Best Practices
### 1. Numeric vs. Categorical
- **Numbers** should be raw, unformatted values (e.g., `1200.50`, not `"$1,200.50"`). This allows aggregators like `sum` and `avg` to work correctly.
- **Categories** should be stable strings. Avoid using ID numbers (like `1`, `2`, `3`) as labels; use human-readable names (like `"Active"`, `"Pending"`, `"Closed"`) so the generated headers make sense.
### 2. Date Hierarchies
Dates are the most common pivot dimension. Instead of just a raw ISO string, it is often helpful to provide pre-calculated date parts if you are doing client-side pivoting:
```json
{
"orderDate": "2024-05-21",
"year": 2024,
"quarter": "Q2",
"month": "May"
}
```
In your `PivotConfig`, you can then set `rows: ['year', 'quarter', 'month']` to create an instant time-drilldown experience.
### 3. Stability of Fields
The `prop` in your [Dimensions](/guides/pivot/dimensions/) should be stable. If you change a field name in your data, you must update the `prop` in your configuration, or the Pivot will fail to find the data.
## Large Scale Modeling (Server-Side)
If your "Fact Table" has millions of rows, you shouldn't denormalize it into a single JSON array for the browser. Instead, you should keep it in an **OLAP-optimized database** (like ClickHouse, BigQuery, or Snowflake) and use the [Server-Side Pivot](/guides/pivot/server-side/) mode.
In this mode, RevoGrid sends the "Analytical Request" (the rows, columns, and filters) to your backend, which performs the SQL `JOIN` and `GROUP BY` efficiently and returns only the visible summary.
## Next Steps
Now that your data is modeled correctly, learn how to define your [Dimensions](/guides/pivot/dimensions/) to tell RevoGrid how to use these fields.
---
# What is an OLAP Cube?
URL: https://pro.rv-grid.com/guides/pivot/introduction/olap-cube-concepts/
Source: src/content/docs/guides/pivot/introduction/olap-cube-concepts.mdx
Description: Understand the multidimensional "Cube" metaphor and how Online Analytical Processing (OLAP) powers advanced data discovery.
When working with RevoGrid Pivot, you will often hear the term **OLAP** (Online Analytical Processing) or **OLAP Cube**. While the name sounds complex, the concept is fundamental to modern business intelligence.
## The "Cube" Metaphor
In a traditional spreadsheet, data is 2-dimensional (Rows and Columns). In an **OLAP Cube**, data is **Multidimensional**.
Think of a "Cube" where each side represents a different category (a **Dimension**):
1. **Product** (what we sold)
2. **Time** (when we sold it)
3. **Geography** (where we sold it)
Inside the cube, at the intersection of these three dimensions, lies the **Measure** (e.g., Sales Amount).
By "rotating" this cube, you can view the data from any angle. In RevoGrid Pivot, this "rotation" happens when you move fields between the Rows and Columns axes.
## Core OLAP Operations
OLAP cubes are designed to support four primary analytical operations, all of which are supported by RevoGrid:
### 1. Slicing
Slicing is like taking a single "slice" of the cube.
- **Concept**: Picking one value for a dimension.
- **Example**: "Show me sales only for the **Year 2024**."
- **In RevoGrid**: This is achieved through [Filtering](/guides/pivot/sorting-filtering/).
### 2. Dicing
Dicing is like cutting a smaller "sub-cube" out of the big one.
- **Concept**: Picking specific values across multiple dimensions.
- **Example**: "Show me sales for **Electronics** in the **North Region** during **Q1**."
- **In RevoGrid**: This is achieved by combining filters and row/column grouping.
### 3. Drill-Down / Drill-Up
Moving through the levels of a hierarchy.
- **Concept**: Going from summarized data to detailed data (Drill-Down) or back (Drill-Up).
- **Example**: Looking at sales by **Year**, then clicking to see **Quarters**, then **Months**.
- **In RevoGrid**: Enabled via the `collapsed` property in [Drill-Down](/guides/pivot/drilldown/).
### 4. Pivoting (Rotating)
Changing the orientation of the axes.
- **Concept**: Swapping Rows and Columns to see the data differently.
- **Example**: Moving "Region" from the Row axis to the Column axis to compare regions side-by-side.
- **In RevoGrid**: Performed via the [Configurator](/guides/pivot/configurator/) or by updating the `rows` and `columns` arrays in your code.
## Why "Cube" vs. "Flat Table"?
| Feature | Flat Table (SQL) | OLAP Cube |
| :--- | :--- | :--- |
| **Speed** | Slow for large aggregations (calculates on the fly) | Fast (aggregations are often pre-calculated) |
| **Complexity** | Requires complex `JOIN` and `GROUP BY` logic | Logical, business-friendly dimensions |
| **Scale** | Hard to scale to billions of rows for real-time UI | Designed for massive analytical scale |
## How RevoGrid Fits In
RevoGrid acts as the **Viewer** for your OLAP data.
- **Client-Side**: RevoGrid builds a "virtual cube" in memory from your flat JSON data.
- **Server-Side**: RevoGrid communicates with a real OLAP engine (like Snowflake, ClickHouse, or Cube.js) using the [Remote API](/guides/pivot/remote-api-reference/).
## Next Steps
Now that you understand the "Cube" metaphor, learn how to prepare your data for these operations in [Data Modeling for Pivot](/guides/pivot/data-modeling/).
---
# What Is Pivot?
URL: https://pro.rv-grid.com/guides/pivot/introduction/what-is-pivot/
Source: src/content/docs/guides/pivot/introduction/what-is-pivot.mdx
Description: A conceptual introduction to Pivot tables, the mental model, and how they transform flat data into analytical insights.
A **Pivot Table** is a data summarization tool that automatically sorts, counts, totals, or averages data stored in one table and displays the results in a second, organized table.
In RevoGrid, the Pivot plugin takes a "flat" list of records and transforms it into a multidimensional view where you can see relationships between different data points at a glance.
## The Transformation: From Facts to Insights
Imagine you have a list of sales transactions. Each row represents a single event:
Date
Region
Category
Sales
2024-01-01 North Electronics $500
2024-01-02 South Electronics $300
2024-01-03 North Furniture $1,200
2024-01-04 North Electronics $450
This "flat" view is great for data entry, but hard to read for analysis. A **Pivot** transforms it by choosing axes:
1. **Row Axis**: Grouping data vertically (e.g., by **Region**).
2. **Column Axis**: Grouping data horizontally (e.g., by **Category**).
3. **Values**: The numbers you want to calculate (e.g., **Sum of Sales**).
### The Resulting Pivot View:
Region
Electronics
Furniture
Grand Total
North $950 $1,200 $2,150
South $300 $0 $300
Grand Total $1,250 $1,200 $2,450
## Core Axes of a Pivot
To work with Pivot, you need to think in terms of three primary axes:
### 1. Rows (The Hierarchy)
Rows define the vertical structure. If you add multiple fields to rows (e.g., `['Region', 'Manager']`), Pivot creates a nested hierarchy. Users can "drill down" from Region to see the individual Managers within that region.
### 2. Columns (The Breakdown)
Columns define the horizontal structure. Unlike normal grid columns, Pivot columns are often **generated** based on the data. If you put `Quarter` in columns, Pivot will automatically create a column for every unique quarter found in your dataset.
### 3. Values (The Measures)
Values are the "meat" of the table. They are the numeric fields that get crunched using **Aggregators** (like `sum`, `avg`, `min`, `max`, or `count`). Every cell in a Pivot table is the result of an aggregation of all raw records that match that specific row and column intersection.
## Why Use RevoGrid Pivot?
- **Massive Data**: RevoGrid's virtualization allows you to pivot millions of rows without crashing the browser.
- **Dynamic Discovery**: Users can drag and drop fields to explore data from different angles.
- **Advanced analysis**: Subtotals, Grand Totals, and Drill-down are built in and optimized.
- **Server-Side Scaling**: For datasets too large for the browser, RevoGrid can connect to an [OLAP Backend](/guides/pivot/connect-olap-backend/) to perform the heavy lifting on the server while maintaining a smooth UI.
## Next Steps
To understand how these concepts map to actual code, read about [Dimensions](/guides/pivot/dimensions/) or explore the [Mental Model](/guides/pivot/#mental-model) in the Overview.
---
# Server-Side
URL: https://pro.rv-grid.com/guides/pivot/server-side/
Source: src/content/docs/guides/pivot/server-side/index.mdx
---
# Caching And Cache Keys
URL: https://pro.rv-grid.com/guides/pivot/server-side/caching/
Source: src/content/docs/guides/pivot/server-side/caching.mdx
Description: Understand how RevoGrid Pivot generates deterministic cache keys for safe and efficient remote analytical execution.
In server-side Pivot, caching is critical for performance and cost management. Analytical queries can be expensive and slow; deduplicating identical requests ensures that the backend only performs the work once.
RevoGrid Pivot uses **deterministic cache keys** to identify identical analytical windows.
## What Is A Cache Key?
A cache key is a unique string that represents the exact state of an analytical request. If any part of the request changes (filters, grouping, viewport, or data version), a new key is generated.
This allows both the client-side `HttpPivotRemoteStore` and your backend analytical engine to:
1. **Deduplicate**: Prevent multiple identical requests from hitting the database simultaneously.
2. **Memoize**: Return previously computed results for the same analytical window.
3. **Invalidate Safely**: Ensure that if the underlying data or field definitions change, old cached results are never shown.
## Key Components
A stable Pivot cache key is composed of several semantic layers:
| Component | Description | Why It Matters |
| :--- | :--- | :--- |
| `tenantId` | Identifies the client or organization scope. | Prevents data leakage between different users/tenants. |
| `viewId` | Identifies the specific analytical view or dashboard. | Keeps caches separate for different reports using the same fields. |
| `fieldsVersion` | A checksum or version of the field registry. | If you change a field's calculation or data type, old summaries must be invalidated. |
| `datasetWatermark` | A timestamp or batch ID representing data freshness. | Automatically invalidates the cache when new data is loaded into the warehouse. |
| `request` | A stable stringification of filters, rows, columns, and windows. | Ensures that changing a single filter or scrolling the viewport yields a specific key. |
## How Keys Are Generated
RevoGrid provides a standard `createPivotCacheKey` utility that builds these keys. It uses a **stable stringification** algorithm, meaning that the order of keys in a JSON object does not change the resulting string.
```ts
// Internal logic simplified
const cacheKey = `pivot:${tenantId}:${viewId}:${fieldsVersion}:${datasetWatermark}:${stableStringify(request)}`;
```
### Stable Stringification Example
Even if the UI sends filters in a different order, the cache key remains the same:
```ts
// Request A
{ filter: { region: 'US', category: 'Tech' } }
// Request B
{ filter: { category: 'Tech', region: 'US' } }
// Both result in the same stable key:
// "...:{"category":"Tech","region":"US"}"
```
## Observing Cache Status
When using `HttpPivotRemoteStore` or the recommended backend response format, the grid receives cache metadata in the `meta` object:
```ts
{
"data": [...],
"meta": {
"cacheKey": "pivot:tenant-1:sales:v1:20240424:...",
"cacheStatus": "hit", // or 'miss', 'warm', 'bypass'
"elapsedMs": 12
}
}
```
This is useful for UI diagnostics and understanding whether your analytical engine is performing efficiently.
## Best Practices
- **Keep `viewId` Stable**: Do not use random IDs for views if you want users to benefit from shared caching across sessions.
- **Update `datasetWatermark`**: Your backend should provide a watermark (like a `max(updated_at)` or a batch ID) to the client so the cache key updates automatically when data changes.
- **Normalize Requests**: If you are implementing a custom `PivotRemoteStore`, ensure you normalize filters and descriptors before generating keys to maximize cache hits.
## Next
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Remote API Reference](/guides/pivot/remote-api-reference/)
- [OLAP Best Practices](/guides/pivot/olap-best-practices/)
---
# Connect An OLAP Backend
URL: https://pro.rv-grid.com/guides/pivot/server-side/connect-olap-backend/
Source: src/content/docs/guides/pivot/server-side/connect-olap-backend.mdx
Description: Wire RevoGrid Pivot to an OLAP-capable backend through HttpPivotRemoteStore and an application API.
This guide shows the practical wiring pattern for connecting RevoGrid Pivot to an OLAP-capable backend. The important rule is simple: the browser talks to your application API, and your application API talks to the cube, warehouse, or semantic layer.
For the conceptual background, read [Server-Side Pivot](/guides/pivot/server-side/) first. For the transport contract, see [Remote API Reference](/guides/pivot/remote-api-reference/).
## Live Simulated Server Example
The example below uses the real `HttpPivotRemoteStore` API with a simulated in-memory `/api/pivot/*` server. The Request Log panel on the right shows the actual client and server events as they happen — `load:started`, `load:received`, `load:responded`, and `load:succeeded`.
## Architecture
The recommended production boundary looks like this:
```txt
RevoGrid + PivotPlugin
-> HttpPivotRemoteStore
-> your /api/pivot/* endpoints
-> field registry + validation + planner/adapter
-> OLAP engine / warehouse / cube API
```
Do not connect the browser directly to the OLAP database. Your backend layer must own:
- authentication
- tenant isolation
- selector whitelisting
- request normalization
- query translation
- caching and observability
## Minimum Pieces You Need
1. A client `PivotConfig` with `engine.mode = 'server'`
2. An `HttpPivotRemoteStore`
3. Backend `POST /api/pivot/load`
4. Backend `POST /api/pivot/drilldown`
5. A field registry for safe public field ids
6. A planner or adapter that translates Pivot requests into your backend’s OLAP query shape
## Client Setup
The client stays small. It only needs to describe the Pivot layout and point the plugin at your application API.
```ts
import {
HttpPivotRemoteStore,
PivotPlugin,
commonAggregators,
type PivotConfig,
} from '@revolist/pivot';
import { PaginationPlugin } from '@revolist/revogrid-pro';
const remoteStore = new HttpPivotRemoteStore({
baseUrl: '/api',
tenantId: 'tenant-a',
datasetWatermark: 'sales-cube-v42',
authProvider: async () => ({
Authorization: `Bearer ${await getAccessToken()}`,
}),
});
const pivot: PivotConfig = {
dimensions: [
{ prop: 'Region', name: 'Region' },
{ prop: 'Category', name: 'Category' },
{ prop: 'Date', name: 'Date' },
{
prop: 'SalesAmount',
name: 'Sales',
aggregators: commonAggregators,
},
],
rows: ['Region', 'Category'],
columns: ['Date'],
values: [{ prop: 'SalesAmount', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
engine: {
mode: 'server',
remoteStore,
viewId: 'sales-by-region',
fieldsVersion: 'sha256:fields-v1',
rowAxis: { offset: 0 }, // limit driven by pagination.itemsPerPage
columnAxis: { offset: 0, limit: 24 },
},
};
// Add PaginationPlugin to enable server-side row paging.
// PivotPlugin will read itemsPerPage as the rowAxis limit and keep
// pagination.total in sync with response.rowAxis.totalCount.
grid.plugins = [PivotPlugin, PaginationPlugin];
grid.pivot = pivot;
grid.additionalData = {
pagination: {
itemsPerPage: 50,
initialPage: 0,
total: 0,
},
};
```
## What The Client Sends
At runtime, `PivotPlugin` converts the visible Pivot state into a `PivotLoadRequest` and sends it through `HttpPivotRemoteStore`.
That request includes:
- `rows`, `columns`, and `values`
- filters and sort directives
- `rowAxis` and `columnAxis` windows
- expansion state represented as analytical paths
The client does not build SQL and it does not know how your cube works internally.
## Backend Load Endpoint
This example uses a lightweight Node/Express style handler for readability. The contract itself is framework-agnostic.
```ts
import type { Request, Response } from 'express';
import {
createFieldRegistry,
normalizePivotLoadRequest,
type PivotLoadRequest,
type PivotLoadResponse,
} from '@revolist/pivot';
const registry = createFieldRegistry([
{
id: 'Region',
label: 'Region',
dataType: 'string',
expression: { kind: 'column', value: 'sales.region' },
allowedOperations: ['=', '<>', 'in'],
},
{
id: 'Category',
label: 'Category',
dataType: 'string',
expression: { kind: 'column', value: 'sales.category' },
allowedOperations: ['=', '<>', 'in'],
},
{
id: 'Date',
label: 'Date',
dataType: 'date',
expression: { kind: 'column', value: 'sales.order_date' },
allowedOperations: ['=', '>=', '<='],
allowedGroupIntervals: ['year', 'quarter', 'month', 'day'],
},
{
id: 'SalesAmount',
label: 'Sales Amount',
dataType: 'number',
expression: { kind: 'column', value: 'sales.amount' },
allowedOperations: ['=', '>', '>=', '<', '<='],
allowedSummaries: ['sum', 'avg', 'min', 'max', 'count'],
drilldownVisible: true,
},
]);
export async function postPivotLoad(req: Request, res: Response) {
const tenantId = await requireTenant(req);
const request = req.body as PivotLoadRequest;
const normalized = normalizePivotLoadRequest(request, registry);
const cubeResult = await runCubeLoad({
tenantId,
viewId: normalized.viewId,
fieldsVersion: normalized.fieldsVersion,
request: normalized,
});
const response: PivotLoadResponse = {
requestId: request.requestId,
version: 1,
data: cubeResult.data,
summary: cubeResult.summary,
rowAxis: cubeResult.rowAxis,
columnAxis: cubeResult.columnAxis,
meta: cubeResult.meta,
};
res.json(response);
}
```
## Translating The Pivot Request Into An OLAP Query
Your adapter layer is where Pivot concepts become backend-native OLAP concepts.
Recommended mapping:
- Pivot `rows` -> cube row dimensions
- Pivot `columns` -> cube column dimensions
- Pivot `totalSummary` and `groupSummary` -> cube measures
- Pivot `filter` -> cube filter tree
- `expandedPaths` / `collapsedPaths` -> hierarchy frontier exceptions
- `uiState.columnGroupsCollapsedByDefault` -> column hierarchy default state
- `rowAxis` / `columnAxis` -> returned analytical windows
Example adapter:
```ts
type RunCubeLoadArgs = {
tenantId: string;
viewId: string;
fieldsVersion: string;
request: ReturnType;
};
async function runCubeLoad(args: RunCubeLoadArgs) {
const { request } = args;
const cubeQuery = {
dimensions: {
rows: (request.loadOptions.rows ?? []).map((item) => ({
id: item.selector,
interval: item.groupInterval,
desc: item.desc,
})),
columns: (request.loadOptions.columns ?? []).map((item) => ({
id: item.selector,
interval: item.groupInterval,
desc: item.desc,
})),
},
measures: (request.loadOptions.totalSummary ?? []).map((summary) => ({
id: summary.selector,
aggregate: summary.summaryType,
})),
filters: request.loadOptions.filter,
sort: request.loadOptions.sort ?? [],
window: request.viewport,
uiState: request.uiState,
};
const result = await cubeClient.loadPivotWindow(cubeQuery);
return {
data: result.cells,
summary: result.summaryRows,
rowAxis: {
totalCount: result.rowAxis.totalCount,
returned: result.rowAxis.returned,
offset: request.viewport.rowAxis.offset,
limit: request.viewport.rowAxis.limit,
expandedPaths: request.viewport.rowAxis.expandedPaths,
paths: result.rowAxis.paths,
nodes: result.rowAxis.nodes,
},
columnAxis: {
totalCount: result.columnAxis.totalCount,
returned: result.columnAxis.returned,
offset: request.viewport.columnAxis.offset,
limit: request.viewport.columnAxis.limit,
expandedPaths: request.viewport.columnAxis.expandedPaths,
collapsedPaths: request.viewport.columnAxis.collapsedPaths,
paths: result.columnAxis.paths,
nodes: result.columnAxis.nodes,
},
meta: {
cacheKey: result.cacheKey,
generatedAt: result.generatedAt,
elapsedMs: result.elapsedMs,
cacheStatus: result.cacheStatus,
warnings: result.warnings ?? [],
},
};
}
```
## Drilldown Endpoint
Drilldown should return the underlying facts for a visible summary cell, not a second aggregated result.
```ts
import type { Request, Response } from 'express';
import {
createFieldRegistry,
normalizePivotDrilldownRequest,
type PivotDrilldownRequest,
} from '@revolist/pivot';
export async function postPivotDrilldown(req: Request, res: Response) {
await requireTenant(req);
const request = req.body as PivotDrilldownRequest;
const normalized = normalizePivotDrilldownRequest(request, registry);
const facts = await cubeClient.drilldown({
rowPath: normalized.cell.rowPath,
columnPath: normalized.cell.columnPath,
dataIndex: normalized.cell.dataIndex,
fields: normalized.customColumns ?? ['Region', 'Category', 'Date', 'SalesAmount'],
offset: normalized.offset,
limit: normalized.limit,
});
res.json({
requestId: request.requestId,
data: facts.rows,
totalCount: facts.totalCount,
meta: {
cacheStatus: facts.cacheStatus ?? 'bypass',
},
});
}
```
## Request Lifecycle
1. The grid shows a Pivot layout with row and column windows.
2. `PivotPlugin` builds a `PivotLoadRequest`.
3. `HttpPivotRemoteStore` sends the request to your application API.
4. Your backend authenticates, validates selectors, and normalizes the request.
5. Your planner or cube adapter translates the normalized request into backend-native OLAP calls.
6. The cube or warehouse returns only the visible analytical block.
7. Your backend maps that result into `PivotLoadResponse`.
8. RevoGrid materializes the returned window into visible columns and rows.
## What Changes For A True OLAP Cube
If your backend already exposes dimensions, measures, and drilldown concepts, the frontend setup stays the same. Only the backend adapter changes.
Typical differences:
- date grouping may map directly to cube hierarchy levels
- drilldown may map to cube “drill through” APIs
- row and column windows may map to axis slicing instead of SQL paging
- `avg` and derived measures may already be modeled by the cube
That is why the recommended contract stays generic HTTP. The transport is stable even when the backend engine changes.
## Do Not Do This
- Do not connect the browser directly to the OLAP database.
- Do not trust selectors from the client.
- Do not return the full cube by default.
- Do not compute totals from rendered UI values.
## Related Guides
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Remote API Reference](/guides/pivot/remote-api-reference/)
- [OLAP Best Practices](/guides/pivot/olap-best-practices/)
---
# OLAP Best Practices
URL: https://pro.rv-grid.com/guides/pivot/server-side/olap-best-practices/
Source: src/content/docs/guides/pivot/server-side/olap-best-practices.mdx
Description: Design Pivot-friendly analytical models with stable dimensions, secure field registries, and viewport-aware execution.
RevoGrid Pivot is not itself an OLAP database, but the server-side foundation is designed to work well with OLAP-style engines, semantic layers, and warehouse-backed pivot services. This page explains the practices that keep that integration correct and maintainable.
## Start With Facts And Dimensions
The best Pivot source model is:
- a fact table with additive events or transactions
- dimensions that describe those facts
Examples:
- facts: orders, sales lines, support incidents, page views
- dimensions: region, product, customer segment, time
This model keeps grouping and drilldown semantics predictable.
## Treat The Field Registry As A Semantic Layer
Your client should use public field ids. Your backend should map them through a field registry.
Good registry properties:
- stable field ids
- business-friendly labels
- safe backend expressions
- explicit allowed operations
- explicit allowed summaries
- explicit allowed grouping intervals
This prevents raw selector leakage and becomes the trust boundary for every analytical request.
## Design Hierarchies Intentionally
Rows and columns should represent real analytical hierarchies, not arbitrary field lists.
Good examples:
- `['Region', 'Rep']`
- `['Year', 'Quarter', 'Month']`
- `['Category', 'Subcategory']`
Poor examples:
- mixing unrelated dimensions only because the UI allows it
- saving unstable intermediate fields that are likely to change name or meaning
## Prefer Additive Measures
`sum` and `count` are easiest to reason about across subtotals and grand totals.
Be careful with:
- `avg`
- ratios
- percentages
- any metric derived from already-aggregated cells
For those, totals should come from base facts or planner-managed derived summaries, not by averaging visible children.
## Separate UI State From Analytical State
Keep these concerns separate:
- analytical definition: fields, filters, summaries, sort
- UI state: expanded paths, collapsed columns, viewport windows
This separation makes saved state more stable and keeps backends from mixing presentation-only state with semantic query meaning.
## Return Windows, Not The Whole Cube
For large datasets, the backend should return:
- only the visible row-axis window
- only the visible column-axis window
- only the summaries needed for the visible analytical block
Returning the full cube defeats the whole purpose of server-side Pivot.
## Drilldown Should Mean Facts
When users drill into a summary cell, the backend should return the underlying facts scoped by:
- the current filters
- the row path
- the column path
- the selected measure if needed
That keeps drilldown trustworthy and consistent with visible summaries.
## Keep Cache Keys Semantic
Stable, semantic cache keys are the difference between a useful analytical cache and an unsafe one. Because Pivot requests are complex, your backend must identify identical windows deterministically.
Good Pivot cache keys should include:
- tenant and view identifiers
- field registry versions
- dataset watermarks (freshness)
- normalized filters, groupings, and summaries
- viewport windows
See [Caching and Cache Keys](/guides/pivot/caching/) for the full breakdown of how these keys are constructed and why they matter for safety and performance.
## Recommended Implementation Pattern
The most reliable production wiring is:
```txt
browser Pivot UI
-> HttpPivotRemoteStore
-> application API
-> field registry + normalization + planner/adapter
-> OLAP cube / warehouse
```
This keeps the public Pivot model stable while allowing your backend to translate the same request into SQL, a semantic-layer API, or a cube-native query format.
Use [Connect An OLAP Backend](/guides/pivot/connect-olap-backend/) for a practical client + backend example.
## What To Avoid
- exposing backend column names to the client
- concatenating raw selectors into SQL
- computing totals from already-rendered UI cells
- using row indexes as expansion identifiers
- coupling the grid to a specific backend framework
## Current Product Position
Today, RevoGrid Pivot already provides:
- a shared client/server config model
- a field registry and normalization layer
- a planner abstraction
- a transport-agnostic remote store
- visible-window materialization into RevoGrid
What it does not claim to be:
- a full OLAP engine
- a warehouse execution service
- a finished large-scale remote virtualization system on its own
That distinction is important. RevoGrid should remain the UI and viewport layer, while the analytical engine remains replaceable.
## Related Guides
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Connect An OLAP Backend](/guides/pivot/connect-olap-backend/)
- [Remote API Reference](/guides/pivot/remote-api-reference/)
- [Configuration Reference](/guides/pivot/configuration-reference/)
---
# Remote API Reference
URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-api-reference/
Source: src/content/docs/guides/pivot/server-side/remote-api-reference.mdx
Description: Technical specification for the RevoGrid Pivot remote analytical contract.
The Remote Pivot API is a transport-agnostic JSON contract that allows RevoGrid to communicate with analytical backends, OLAP cubes, or custom SQL planners.
## Load Request
The `PivotLoadRequest` is sent whenever the grid needs to materialize a visible window of analytical data.
### Root Properties
- `viewId`: Semantic identifier for the dataset (e.g., `sales_cube`).
- `fieldsVersion`: Version of the field registry used to resolve selectors.
- `loadOptions`: The analytical query definition.
- `viewport`: Request windows for row and column axes.
- `uiState`: Optional hints for engine-side formatting.
### Load Options
The `loadOptions` object defines the shape of the analytical query:
- `rows`: Array of group descriptors for the row axis.
- `columns`: Array of group descriptors for the column axis.
- `values`: Measures to aggregate.
- `filter`: Complex filter expression tree.
- `sort`: Sorting directives (can include sort-by-summary).
- `groupSummary`: Measures to calculate at every group level.
### Viewport
The `viewport` defines independent analytical windows:
- `rowAxis`: Contains `offset`, `limit`, and `expandedPaths`.
- `columnAxis`: Contains `offset`, `limit`, `expandedPaths`, and
`collapsedPaths`.
Use typed analytical paths for expansion state, not visible column indexes.
`expandedPaths` and `collapsedPaths` describe exceptions to
`uiState.columnGroupsCollapsedByDefault`. This lets the backend calculate and
return only the visible column frontier in either default mode.
### Normalization Limits
Before planning/execution, requests are typically normalized against backend limits (`PivotLimits`) to keep payloads and query complexity bounded.
Common limits include:
- `maxRowWindowSize`: Maximum allowed `rowAxis.limit`.
- `maxColumnWindowSize`: Maximum allowed `columnAxis.limit`.
- `maxExpandedPaths`: Maximum combined number of expanded and collapsed
frontier paths per axis.
- `maxExpansionDepth`: Maximum hierarchy depth of a single expansion path.
- `maxDrilldownLimit`: Maximum page size for drilldown requests.
For `maxExpansionDepth`, depth is the number of path segments. Example: `['Region', 'Country', 'City']` has depth `3`.
## Load Response
`PivotLoadResponse` returns only the data needed for the current visible window.
### Root Properties
- `data`: Array of row records (expected to have `__rowPath` and `__columnPath`).
- `summary`: Optional grand-total or pinned rows.
- `rowAxis`: Window metadata for the row axis.
- `columnAxis`: Window metadata for the column axis.
- `groupAggregates`: Optional grouped-row measures.
- `meta`: Diagnostic metadata.
Important response metadata:
- `cacheStatus`: 'hit', 'miss', 'warm', or 'bypass'.
## Axis Metadata
Both `rowAxis` and `columnAxis` use the same metadata structure.
| Field | Role | Description |
| :--- | :---: | :--- |
| `paths` | Required (Columns) | Array of analytical paths used to generate headers. |
| `totalCount` | Required (Rows) | Total members on server. Required for row-axis scrollbars and pagination. |
| `expandedPaths` | Optional | Expanded analytical paths echoed by the engine. |
| `collapsedPaths` | Optional | Collapsed analytical paths echoed by the engine. |
## Drilldown
Drilldown uses `PivotDrilldownRequest` and `PivotDrilldownResponse`.
The request identifies a visible summary cell by:
- `rowPath`
- `columnPath`
- optional `dataIndex`
The backend should reuse the same analytical filter context as the Pivot request, then return matching fact rows with pagination.
## UI State Hints
`uiState` hints allow the backend to optimize its output format:
- `rowHeaderLayout`: 'tree' or 'flat'.
- `showTotalsPrior`: Placement of subtotals.
- `collapsedByDefault`: Initial hierarchy state.
- `columnGroupsCollapsedByDefault`: Default state for generated column groups.
---
# Remote Tools
URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-tools/
Source: src/content/docs/guides/pivot/server-side/remote-tools.mdx
Description: Add diagnostics, source-row drilldown, and saved views to a server-side Pivot screen.
Remote tools are small UI and application-layer helpers for server-side Pivot. They help you show request diagnostics, load source rows for a summarized cell, track remote activity, and manage saved Pivot layouts that can be restored into the grid.
These helpers are not a required backend. They sit around a `PivotRemoteStore` or `PivotEngineAdapter` that your app already uses for remote Pivot loading. In the demo, the remote tools panel is outside the dedicated RevoGrid wrapper so Pivot-owned UI such as the field panel can mount directly beside the grid.
## What This Page Covers
- Diagnostics model: converts remote response metadata into rows and chips your UI can render.
- Drilldown controller: turns a summarized Pivot cell into table-ready source rows.
- Field panel: lets users reorder and move row, column, value, and filter fields before saving a layout. The demo keeps unused fields in the Filters area so they can be moved into the active layout.
- Saved-view helpers: serialize, load, rename, duplicate, and delete user-owned Pivot configs that can be written back to `grid.pivot`.
- Remote store hooks: expose request activity such as `load`, `drilldown`, `saveState`, and `loadState` for support panels or audit trails.
The grid loads aggregated data through the remote store. Your surrounding app UI can reuse the same store to ask follow-up questions: "How fast was that request?", "Which source rows produced this number?", "Which saved layout should this user load?", or "What remote operation just ran?".
## Import The Helpers
All helpers below are exported from `@revolist/pivot`.
```ts
import {
PivotDrilldownController,
createPivotDiagnosticsModel,
deletePivotSavedView,
duplicatePivotSavedView,
loadPivotView,
renamePivotSavedView,
savePivotView,
type PivotConfig,
type PivotLoadRequest,
type PivotRemoteStoreHooks,
type PivotSavedViewRecord,
} from '@revolist/pivot';
```
## Diagnostics Model
Remote Pivot responses can include `meta` data such as elapsed time, cache status, generated timestamp, and warnings. `createPivotDiagnosticsModel` converts that raw response into a simple display model.
Use it when you want a side panel, footer, debug drawer, or support view that explains what the last remote request did.
```ts
const response = await remoteStore.load({
requestId: `pivot-diagnostics-${Date.now()}`,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
loadOptions: {
rows: [{ selector: 'region' }],
columns: [{ selector: 'quarter' }],
totalSummary: [{ selector: 'sales', summaryType: 'sum' }],
groupSummary: [{ selector: 'sales', summaryType: 'sum' }],
},
viewport: {
rowAxis: { offset: 0, limit: 50 },
columnAxis: { offset: 0, limit: 12 },
},
uiState: {
collapsedByDefault: true,
rowHeaderLayout: 'tree',
},
} satisfies PivotLoadRequest);
const diagnostics = createPivotDiagnosticsModel(response, {
formatGeneratedAt: (value) => new Date(value).toLocaleString(),
});
for (const row of diagnostics.rows) {
console.log(row.label, row.value);
}
for (const chip of diagnostics.chips) {
console.log(chip.label, chip.tone);
}
```
The returned model has:
- `rows`: stable rows such as elapsed time, generated time, visible rows, and visible columns.
- `chips`: compact status items such as cache status or warnings.
- `warnings`: warning strings from the response metadata.
- `hasWarnings`: a boolean for showing warning states in your UI.
The helper does not make network requests. It only formats a `PivotLoadResponse` that your remote store already returned. In the live demo, the diagnostics panel refreshes against the currently selected layout so the visible rows and columns reflect that saved view.
## Drilldown Controller And Table State
Aggregated Pivot cells often need a "show source rows" action. `PivotDrilldownController` wraps the remote drilldown contract and keeps table state for you.
The controller owns:
- current status: `idle`, `loading`, `success`, or `error`
- table columns inferred from returned rows or `customColumns`
- source rows for the selected summary cell
- total matching row count
- request cancellation when a newer drilldown request starts
```ts
const drilldown = new PivotDrilldownController(remoteStore, {
defaultLimit: 100,
});
const unsubscribe = drilldown.subscribe((state) => {
renderDrilldownTable({
loading: state.loading,
columns: state.columns,
rows: state.rows,
totalCount: state.totalCount,
error: state.error,
});
});
await drilldown.load({
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
cell: {
rowPath: ['North', 'Jane'],
columnPath: [2024, 'Q1'],
},
customColumns: ['region', 'rep', 'year', 'quarter', 'sales'],
offset: 0,
limit: 25,
});
const state = drilldown.getState();
console.log(state.status, state.rows.length, state.totalCount);
unsubscribe();
drilldown.abort();
```
The `cell` value should identify the analytical cell, not the visible DOM cell. Use Pivot paths like `rowPath` and `columnPath` so your backend can apply the same grouping context and return matching fact rows. The controller state is table-ready: render `state.columns`, `state.rows`, `state.totalCount`, and `state.status` directly in your surrounding UI.
## Saved-View Helpers
Saved views are user-owned Pivot configurations. They are useful when users build several layouts, such as "Revenue by Region" and "Quarterly Margin".
The helpers use `store.saveState()` and `store.loadState()` for persistence. Rename, duplicate, and delete operate on your app's list of saved-view records, so you can combine them with your own sync logic, menus, permissions, or undo UI. In an interactive screen, listen for `pivot-config-update` from the field panel and save that latest config. Loading a view should apply the returned config back to the direct Pivot binding (`grid.pivot`, `pivot`, or framework equivalent) so the report changes immediately.
```ts
const USER_ID = 'user-123';
let views: PivotSavedViewRecord[] = [];
const config: Partial = {
dimensions: [
{ prop: 'region' },
{ prop: 'rep' },
{ prop: 'quarter' },
{ prop: 'sales' },
],
rows: ['region', 'rep'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
};
const saved = await savePivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales',
config,
});
views = [...views.filter((view) => view.viewId !== saved.viewId), saved];
const loaded = await loadPivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
});
grid.pivot = {
...loaded.config,
engine: {
mode: 'server',
remoteStore,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
},
};
views = renamePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales by Quarter',
});
views = duplicatePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
newViewId: 'north-sales-copy',
name: 'North Sales Copy',
});
views = deletePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
});
```
`savePivotView` stores a JSON-serializable Pivot config. It validates common Pivot fields before sending state to the store, which helps catch accidental functions, class instances, or invalid axis values before they become a saved view.
## Remote Store Activity
`HttpPivotRemoteStore` accepts lifecycle hooks for telemetry. They are useful for request logs, support drawers, cache badges, and lightweight audit trails near the grid.
```ts
const hooks: PivotRemoteStoreHooks = {
requestStarted: ({ type }) => {
activity.unshift(`${type}: started`);
},
requestSucceeded: ({ type, cacheStatus }) => {
activity.unshift(`${type}: succeeded${cacheStatus ? ` (${cacheStatus})` : ''}`);
},
requestFailed: ({ type }) => {
activity.unshift(`${type}: failed`);
},
cacheStatusChanged: ({ cacheStatus }) => {
activity.unshift(`cache: ${cacheStatus}`);
},
};
```
The live example uses those hooks with the same mocked `HttpPivotRemoteStore` that powers the grid. Running diagnostics, loading source rows, saving a view, and loading a view all update the activity log.
## Backend Expectations
These helpers assume the remote store supports the operation you call:
- Diagnostics needs `load()` responses with useful `meta`, `rowAxis`, and `columnAxis` data.
- Drilldown needs an adapter or store with a `drilldown()` method.
- Saved views need `saveState()` and `loadState()`.
- Activity logs need store hooks or equivalent instrumentation around your request layer.
You can implement those methods against any backend: REST, GraphQL, local storage for a demo, or an internal state service. RevoGrid does not require a specific saved-view backend.
## Recommended Reading
- [Server-Side Pivot](/guides/pivot/server-side/) - How remote Pivot loading works.
- [Remote API Reference](/guides/pivot/remote-api-reference/) - Request, response, and drilldown contracts.
- [Caching And Cache Keys](/guides/pivot/caching/) - Cache metadata used by diagnostics.
---
# Server-Side Pivot
URL: https://pro.rv-grid.com/guides/pivot/server-side/server-side/
Source: src/content/docs/guides/pivot/server-side/server-side.mdx
Description: Connect RevoGrid Pivot to a remote analytical engine without changing the public Pivot configuration model.
Server-side Pivot is for datasets that are too large, too sensitive, or too analytically expensive to fully materialize on the client. RevoGrid still owns the viewport and interaction model, but the analytical engine owns aggregation, windowing, drilldown facts, and cache-aware execution.
For raw type signatures, see [Pivot API](/api/pivot/). For the full request and store contract, continue with [Remote API Reference](/guides/pivot/remote-api-reference/).
## Architecture
The key boundary is:
```txt
Pivot UI / RevoGrid Plugin
->
Pivot engine adapter
->
remote store or analytical backend
```
In practical terms:
- `PivotPlugin` receives the direct `pivot` config
- `pivot.engine.mode = 'server'` switches the plugin to remote loading
- `PivotEngineAdapter` or `PivotRemoteStore` handles the analytical request
- the grid only receives the visible block to render
## Why The Public Config Stays The Same
The core Pivot model does not change:
- `rows`
- `columns`
- `values`
- `totals`
- drill-down related UI state
That is deliberate. The UI should not need one config model for client Pivot and another for server Pivot.
## Basic Setup
```ts
import {
PivotPlugin,
HttpPivotRemoteStore,
type PivotConfig,
} from '@revolist/pivot';
import { PaginationPlugin } from '@revolist/revogrid-pro';
const remoteStore = new HttpPivotRemoteStore({
baseUrl: 'https://example.internal',
tenantId: 'tenant-a',
datasetWatermark: 'sales-v42',
authProvider: async () => ({
Authorization: `Bearer ${await getToken()}`,
}),
});
const pivot: PivotConfig = {
dimensions: [...],
rows: ['Category', 'Subcategory'],
columns: ['Date'],
values: [{ prop: 'SalesAmount', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
engine: {
mode: 'server',
remoteStore,
viewId: 'sales-by-region',
fieldsVersion: 'sha256:fields-v1',
rowAxis: { },
columnAxis: { },
},
};
// PaginationPlugin drives row-axis paging automatically.
// PivotPlugin reads itemsPerPage as rowAxis.limit and writes
// response.rowAxis.totalCount back to pagination.total after each load.
grid.plugins = [PivotPlugin, PaginationPlugin];
grid.pivot = pivot;
grid.additionalData = {
pagination: {
itemsPerPage: 50,
initialPage: 0,
total: 0, // auto-populated from the first server response
},
};
```
## Pagination With Remote Pivot
`PivotPlugin` integrates directly with `PaginationPlugin`. When both plugins are active:
- `pagination.itemsPerPage` becomes the row-axis page size (`rowAxis.limit`).
- When the user changes page, `PivotPlugin` reloads the server window at the new `rowAxis.offset`.
- After each successful load, `response.rowAxis.totalCount` is written back to `pagination.total` so the pagination panel shows the correct count.
- Changing the pivot structure (rows, columns, values) resets the offset to 0 automatically.
You do not need to handle page-change events yourself. Drop both plugins into `grid.plugins`, bind the Pivot config to `grid.pivot`, and add `pagination` to `additionalData`.
## What RevoGrid Owns vs What The Engine Owns
RevoGrid and `PivotPlugin` own:
- visible grid rendering
- generated RevoGrid columns
- visible row block
- grouped-row metadata used by current drill-down UI
- cancellation and last-response-wins behavior in the plugin lifecycle
The remote engine or backend owns:
- selector validation
- filter execution
- analytical grouping
- summary execution
- row-axis and column-axis windows
- drilldown fact pages
- cache policy and invalidation
## Request Flow
1. `PivotPlugin` sees `engine.mode = 'server'`.
2. It builds a `PivotLoadRequest` from the public Pivot config and current window hints.
3. The adapter or remote store executes the request.
4. The response is materialized into generated columns, visible rows, and pinned totals.
5. RevoGrid renders only the visible analytical window.
## Field Registry And Planner
- Public selectors must be resolved through a field registry.
- Backend expressions must come from the registry, never from client strings.
- Query planning belongs behind a planner abstraction so SQL, OLAP, or warehouse backends can share the same request model.
This repository already includes:
- a field registry model
- request normalization and limit enforcement
- a typed error model
- an SQL-oriented logical planner shape
- a framework-agnostic `HttpPivotRemoteStore`
## Complete Backend Implementation
A production Pivot backend needs to translate the analytical request into your engine's native query format. This example demonstrates a TypeScript handler using the core request and response types.
```ts
import {
type PivotLoadRequest,
type PivotLoadResponse,
} from '@revolist/pivot';
/**
* Handle POST /api/pivot/load
*/
export async function handlePivotLoad(req: Request): Promise {
const request = req.body as PivotLoadRequest;
const { loadOptions, viewport, viewId, fieldsVersion, uiState } = request;
// 1. Authenticate and resolve tenant
const tenantId = await getTenant(req);
// 2. Extract analytical parameters
const {
rows = [], // Group descriptors for rows
columns = [], // Group descriptors for columns
filter, // Complex filter expression tree
sort = [], // Sorting directives
totalSummary = [], // Grand-total measures
groupSummary = [], // Group-level measures (for groupAggregations)
} = loadOptions;
// 3. Extract viewport windows
const { rowAxis, columnAxis } = viewport;
// 4. Execute analytical query (e.g., against SQL or OLAP)
const result = await myAnalyticalEngine.execute({
tenantId,
viewId,
fieldsVersion,
rows,
columns,
filter,
sort,
measures: [...totalSummary, ...groupSummary],
rowWindow: rowAxis,
columnWindow: columnAxis,
// uiState.rowHeaderLayout, uiState.showTotalsPrior, etc.
});
// 5. Build the response
return {
data: result.cells, // REQUIRED: Visible analytical payload
summary: result.totals, // OPTIONAL: Grand-total or pinned rows
rowAxis: {
totalCount: result.rowCount, // REQUIRED: Drives scrollbars/pagination
},
columnAxis: {
paths: result.colPaths, // REQUIRED: Defines the generated headers
},
// Optional: Grouped-row aggregate payloads (if groupSummary requested)
groupAggregates: result.groupSummaries,
meta: {
cacheStatus: result.fromCache ? 'hit' : 'miss', // Used for telemetry
},
};
}
```
## Current Scope
Server-side Pivot is a powerful but complex feature. The standard `PivotPlugin` implements the core windowing and materialization logic, but some advanced scenarios require custom engine adapters:
- **Implemented**: Row-axis windowing, generated columns from `paths`, tree-mode row headers, grouped aggregations, and grand totals.
- **Custom Adapters**: Complex sorting-by-summary or calculated measures typically require a custom `PivotEngineAdapter` to map your specific backend capabilities to the Pivot contract.
- **Not implemented here**: Infinite horizontal scrolling (past the requested column window) or full server-side column grouping collapse (currently managed on client).
## Recommended Reading
- [Remote API Reference](/guides/pivot/remote-api-reference/) — Detailed JSON schema for requests and responses.
- [Caching And Cache Keys](/guides/pivot/caching/) — How to ensure deterministic and efficient analytical execution.
- [OLAP Best Practices](/guides/pivot/olap-best-practices/) — Performance and security tips for backend implementations.
---
# Financial Pivot Showcase
URL: https://pro.rv-grid.com/guides/pivot/showcase/
Source: src/content/docs/guides/pivot/showcase.mdx
Description: Explore sales, profit, and units by country, segment, year, and month using realistic synthetic financial data.
This showcase uses 3,600 original synthetic records shaped like a familiar financial sales model. The schema is inspired by Microsoft's [Financial Sample workbook](https://learn.microsoft.com/en-gb/power-bi/create-reports/sample-financial-download), but the bundled values and product names are generated specifically for this demo.
## What This Example Demonstrates
- multiple row levels
- generated column groups
- sales, profit, and units-sold measures
- product and discount-band filters
- totals and subtotals
- interactive configurator updates
- `pivot-config-update` for external state sync
- instant Sales Overview, Profitability, and Product Performance presets
- responsive configuration, reset, and expanded-grid controls
## How To Read It
Watch how the layout changes as you:
- move fields between rows, columns, and values
- change aggregators
- expand or collapse grouped rows
- enable or disable totals
The initial layout uses `Country → Segment` for rows and `Year → Month` for columns, so you can compare markets and segments while following monthly performance.
The example is useful because it shows that Pivot is not a separate reporting component. It is still RevoGrid, with generated analytical rows and columns layered on top.
## Related Guides
- [Configuration Reference](/guides/pivot/configuration-reference/)
- [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/)
- [Configurator API](/guides/pivot/configurator-api/)
- [Field Panel](/guides/pivot/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
---
# Comprehensive Guide to Using RevoGrid Plugins
URL: https://pro.rv-grid.com/guides/plugin/
Source: src/content/docs/guides/plugin.mdx
RevoGrid is a highly flexible and extensible grid component that supports a powerful plugin system. This guide will walk you through everything you need to know about using RevoGrid plugins, from understanding the basics to creating and integrating your custom plugins.
#### 1. **Understanding RevoGrid Plugins**
RevoGrid plugins are modular pieces of code that extend the functionality of the RevoGrid component. These plugins can:
- Add new features.
- Modify existing behavior.
- Integrate with other libraries or frameworks.
All RevoGrid plugins extend from the `BasePlugin` class, which provides a minimal core and utility methods for interacting with the RevoGrid component.
#### 2. **Setting Up Your Development Environment**
Before you can start working with RevoGrid plugins, ensure you have a development environment set up with the following:
- **Node.js** and **npm** (Node Package Manager) installed.
- A code editor (such as **Visual Studio Code**).
- A basic understanding of TypeScript, as RevoGrid and its plugins are TypeScript-based.
To get started, you need to install RevoGrid in your project:
```bash
npm install @revolist/revogrid
```
#### 3. **Creating a Simple Plugin**
Let's start by creating a simple plugin that add class to odd rows.
1. **Create a New Plugin Class**: Your plugin should extend the `BasePlugin` class provided by RevoGrid.
```typescript
import { BasePlugin, type PluginProviders } from '@revolist/revogrid';
export class RowOddPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener(
'beforerowrender',
({
detail,
}: CustomEvent) => {
detail.node.$attrs$ = {
...detail.node.$attrs$,
odd: detail.item.itemIndex % 2 !== 0,
};
}
);
}
}
```
2. **Integrate the Plugin**: Once your plugin is created, integrate it with your RevoGrid instance.
```typescript
const gridElement = document.querySelector('revo-grid');
gridElement.plugins.push(new RowOddPlugin(gridElement, providers));
```
#### 4. **Exploring Core Plugin Methods**
When creating more complex plugins, you'll likely use several core methods provided by the `BasePlugin` class:
- **`addEventListener(eventName: string, callback: (e: CustomEvent) => void): void`**: Subscribes to a RevoGrid event.
Example:
```typescript
this.addEventListener('beforecellrender', this.handleCellRender);
```
- **`watch(prop: string, callback: (arg: T) => boolean | void, config?: Partial): void`**: Watches a property on the RevoGrid component for changes.
Example:
```typescript
this.watch('rowSelection', (newValue) => {
console.log('Row selection changed:', newValue);
});
```
- **`emit(eventName: string, detail?: any): CustomEvent`**: Emits a custom event from the RevoGrid component.
Example:
```typescript
this.emit('customEvent', { data: 'someData' });
```
- **`clearSubscriptions(): void`**: Clears all event listeners subscribed by the plugin.
- **`destroy(): void`**: Cleans up the plugin by removing all subscriptions and performing any necessary teardown.
#### 5. **Advanced Plugin Development**
RevoGrid provides a set of providers that you can use to interact with its internal state. These providers include:
- **Data Provider**: Manipulate the grid's data.
- **Dimension Provider**: Manage row and column dimensions.
- **Selection Provider**: Handle cell and row selections.
- **Column Provider**: Manage column data and properties.
- **Viewport Provider**: Handle scrolling and viewport rendering.
Here's how you might use these providers in a more advanced plugin:
```typescript
class HighlightSelectionPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener('afterselectionchange', this.highlightSelectedCells);
}
private highlightSelectedCells = () => {
const selectedCells = this.providers.selection.getSelectedCells();
selectedCells.forEach(cell => {
const model = this.providers.data.getModel(cell.rowIndex, cell.type);
model.cellProperties = {
style: {
backgroundColor: 'yellow'
}
};
});
};
}
```
#### 6. **Styling with Plugins**
Plugins can also be used to dynamically style the grid. For example, using a plugin to flash cells after they are edited:
```typescript
import './cell-flash.style.css';
class CellFlashPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener('onEdit', this.handleEdit);
}
private handleEdit = (e: CustomEvent) => {
const { rowIndex, prop } = e.detail;
const cell = this.revogrid.getCell(rowIndex, prop);
cell.classList.add('flash');
setTimeout(() => cell.classList.remove('flash'), 1000);
};
}
// cell-flash.style.css
.flash {
animation: flash 1s ease;
}
@keyframes flash {
from { background-color: yellow; }
to { background-color: transparent; }
}
```
#### 7. **Best Practices for Plugin Development**
- **Keep Plugins Modular**: Each plugin should have a single responsibility and be as modular as possible.
- **Manage Subscriptions**: Always clear subscriptions when they are no longer needed to avoid memory leaks.
- **Utilize Providers Wisely**: Only use the necessary providers to keep your plugin efficient.
- **Ensure Compatibility**: Test your plugins thoroughly to ensure they work well with other plugins and configurations.
#### 8. **Deploying and Sharing Plugins**
Once you've created a plugin, you can package it as an npm module and share it with the community. Here's a basic setup for packaging your plugin:
1. **Create a `package.json` file**:
```json
{
"name": "revogrid-cell-logger",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc"
},
"dependencies": {
"@revolist/revogrid": "^4.0.0"
}
}
```
2. **Build your plugin**:
```bash
npm run build
```
3. **Publish to npm**:
```bash
npm publish
```
#### 9. **Conclusion**
RevoGrid's plugin system is a powerful way to extend its functionality and adapt it to your needs. Whether you're creating simple event listeners or complex data manipulation tools, the flexibility provided by the `BasePlugin` class and the various providers make it easy to build, deploy, and share custom plugins.
By following this guide, you should now be equipped to start creating your own plugins, enhancing the capabilities of RevoGrid, and contributing to a richer ecosystem for all users.
---
# Drag and Drop (Advanced)
URL: https://pro.rv-grid.com/guides/rows/row-advanced-drag-drop/
Source: src/content/docs/guides/rows/row-advanced-drag-drop.mdx
Description: Custom drag-and-drop behavior and interactions.
Implement sophisticated drag-and-drop functionality within your grid
Reorder plugin in RevoGrid may appear simple at first glance, but it’s quite a complex and powerful feature.
It supports **multi-item reordering**, allowing users to move multiple rows or columns at once while preserving the integrity of the dataset.
Leveraging **`proxyItems`** for efficient handling of the full dataset, the plugin ensures that **filter positioning remains correct** even after reordering, maintaining a seamless user experience.
Beyond its core functionality, plugin plays a **crucial role in supporting other plugins** within RevoGrid.
It facilitates **data transfer** across different operations and plugins, extending the overall capabilities of the grid.
This makes it an essential component for managing dynamic datasets in RevoGrid Pro.
- **Custom Drag-and-Drop Behavior**:
Tailor the drag-and-drop interactions to fit your application’s specific needs. You can configure how rows are moved, allowing for both simple and complex drag-and-drop operations.
- **Enhanced Interactivity**:
Improve the overall usability of your grid. With responsive drag-and-drop functionality, users can quickly reorganize data, making it easier to manage and visualize information.
- **Multi-Row Dragging**:
Users can drag and drop multiple rows at once, simplifying bulk operations. This is particularly useful for scenarios where users need to rearrange large datasets quickly.
- **Flexible Dimensions**:
Allow dragging in any direction—vertically or horizontally—supporting both pinned and classic rows. This flexibility enables users to interact with the grid in a way that feels natural to them.
- **Custom Classes During Dragging**:
Apply custom styles during drag operations to provide visual feedback. This feature helps users understand what they are dragging and where they can drop it, enhancing the overall experience.
- **Drag Handle Customization**:
Implement your own drag handles and apply them to custom cells without limitations. This means you can design specific elements within your rows to serve as drag handles, making it clear to users which parts of the UI are draggable.
- **Extendable Event Source**:
Enhance functionality by extending the drag event source. You can create custom events and interactions that suit your application’s specific requirements, allowing for a more tailored user experience.
### How to use
To enable drag and drop functionality for specific columns, first, import the `RowOrderPlugin` from `@revolist/revogrid-pro` and then simply add the `rowDrag` property to the column configuration:
```ts
import { RowOrderPlugin } from '@revolist/revogrid-pro';
grid.columns = [
{ name: '🆔 ID', prop: 'id', rowDrag: true },
];
// or in row headers
grid.rowHeaders = { rowDrag: true };
// don't forget to add the plugin
grid.plugins = [RowOrderPlugin];
```
### Row Order Preview
Use `grid.rowOrder` to customize what appears in the drag preview. `prop` keeps the existing compact label behavior, and `previewProp` can be a column prop or callback when the preview label needs application-specific text:
```ts
grid.rowOrder = {
prop: 'name',
preview: 'compact',
previewProp: ({ firstItem, itemCount }) => {
const label = firstItem?.name ?? 'Rows';
return itemCount > 1 ? `${label} and ${itemCount - 1} more` : label;
},
};
```
`additionalData.rowOrder` is still supported for compatibility, but direct `grid.rowOrder` configuration is preferred.
### Flash Moved Rows
If `CellFlashPlugin` is installed with `RowOrderPlugin`, successfully moved rows flash automatically after the reorder is committed. The flash applies only to the dragged row or dragged selection block at its new visible position:
```ts
import { CellFlashPlugin, RowOrderPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOrderPlugin, CellFlashPlugin];
grid.cellFlash = {
rowDuration: 1200,
};
```
Invalid, canceled, or no-op drops do not flash. Existing `cellFlash` options control the row flash duration, CSS class, reduced-motion behavior, and whether flashing is enabled.
To keep `CellFlashPlugin` active for edits or manual flash calls but disable row-order drop flashes, set `rowOrder.flashOnDrop` to `false`:
```ts
grid.rowOrder = {
prop: 'name',
flashOnDrop: false,
};
```
### Live Drop Validation
Use `validateDrop` when some positions should be blocked while the user is dragging. Invalid targets are highlighted immediately and are not committed on mouseup:
```ts
grid.rowOrder = {
prop: 'name',
validateDrop: ({ targetIndex }) => {
if (targetIndex === 0) {
return {
valid: false,
reason: 'The first row is locked.',
};
}
return { valid: true };
},
};
```
You can also validate with events when a plugin or application controller owns the rule:
```ts
grid.addEventListener('beforerowdropvalidate', (event) => {
if (event.detail.targetIndex === 0) {
event.detail.result = {
valid: false,
reason: 'The first row is locked.',
};
}
});
grid.addEventListener('rowdropstatechange', (event) => {
if (!event.detail.result.valid) {
console.info(event.detail.result.reason);
};
});
```
### Grouped Row Drops
When row grouping is active, row order keeps data rows inside their current group by default. This also applies when rows are selected through `RowSelectPlugin`: synthetic group rows are ignored, and only real data rows are validated. For multi-level grouping, the full configured `grouping.props` path must match before the drop is committed.
If your workflow should let users move rows between groups, enable `applyTargetGroupOnDrop`. The row-order plugin will apply the target group values to every moved row and emit normal `beforeedit` and `afteredit` events for each changed group field. Calling `event.preventDefault()` in `beforeedit` cancels the drop.
```ts
grid.grouping = {
props: ['team', 'status'],
expandedAll: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
applyTargetGroupOnDrop: true,
};
grid.addEventListener('beforeedit', (event) => {
if (event.detail.prop === 'team' && event.detail.val === 'Archived') {
event.preventDefault();
}
});
```
### User-Centric Benefits
By leveraging the advanced drag-and-drop capabilities, users will experience:
- **Increased Efficiency**: Streamline workflows by allowing users to quickly rearrange rows, making data management less cumbersome.
- **Improved Data Visualization**: Users can reorganize data in a way that makes the most sense to them, leading to better insights and understanding.
- **Intuitive Interface**: A well-implemented drag-and-drop feature provides an intuitive interface, reducing the learning curve for new users.
- **Customization Flexibility**: Allowing for custom drag handles and styles gives users the ability to tailor their experience according to their preferences.
Advanced drag-and-drop functionality empowers users to interact with their data in a more dynamic and intuitive way. By customizing the behavior and appearance of drag-and-drop actions, you can significantly enhance the user experience within your grid application. Embrace these capabilities to create a more engaging and efficient interface that meets your users' needs.
---
# Header (Advanced)
URL: https://pro.rv-grid.com/guides/rows/row-advanced-header/
Source: src/content/docs/guides/rows/row-advanced-header.mdx
Description: Custom Row Header
Sophisticated Row Header example based on `RowHeaderPlugin`. The demo uses a task pipeline layout so the row header has a clear job: it anchors each row, opens row-level actions, and keeps row focus visible while the cells provide richer operational context.
- Use `rowHeaders({ showHeaderFocusBtn: true })` for a dedicated row action rail
- Open a row `ContextMenuPlugin` menu directly from the row-header click
- Combine row headers with custom column templates for status, priority, owner, due date, and progress
- Focus the whole row from the row header while preserving readable business data in the grid body
- Style the row-header rail and content cells together so the row action area feels intentional
---
```javascript
// fixing render for multiframework
```
---
# Auto Size Row
URL: https://pro.rv-grid.com/guides/rows/row-autosize/
Source: src/content/docs/guides/rows/row-autosize.mdx
Description: Automatically adjust row heights based on content
The **Row Auto Size Plugin** enhances RevoGrid by automatically calculating and adjusting row heights based on their content. This ensures optimal display of data, particularly useful for cells containing multi-line text, rich content, or varying amounts of information.
When the plugin is enabled, normal grid cells wrap text so the rendered content matches the height calculation. Row heights are recalculated from the wrapped content after source changes, edits, scrolling into new virtual rows, configuration changes, and manual column resizing.
It is a **sophisticated feature** that significantly improves data presentation and user experience:
1. **Dynamic Content Adaptation**: Automatically adjusts row heights to accommodate varying content lengths, ensuring all data is visible without truncation.
2. **Performance Optimization**: Uses efficient caching and virtualization to maintain smooth scrolling even with large datasets and dynamic heights.
3. **Precise Calculations**: Offers both approximate and precise height calculations, allowing you to balance between performance and accuracy.
4. **Event-Driven Updates**: Intelligently recalculates heights only when necessary, such as after edits or viewport changes.
5. **Memory Management**: Implements cleanup strategies to prevent memory leaks and maintain optimal performance during long sessions.
- **Automatic Height Calculation**: Dynamically adjusts row heights based on content.
- **Content-Aware**: Considers line breaks, text wrapping, and column widths.
- **Resize-Aware**: Recalculates visible row heights after manual column resizing.
- **Configurable Limits**: Set minimum and maximum heights to maintain consistency.
- **Custom Calculator Support**: Implement your own height calculation logic.
- **Efficient Caching**: Caches calculated heights to optimize performance.
- **Precise Mode**: Optional precise calculations using DOM measurements.
## Basic Setup
To enable the Row Auto Size Plugin in your RevoGrid setup:
```typescript
import { RowAutoSizePlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [RowAutoSizePlugin];
grid.rowAutoSize = {
minHeight: 24,
maxHeight: 200,
preciseSize: true, // Use DOM-based precise calculations
precalculate: true, // Warm approximate row sizes ahead of scroll
precalculateBatchSize: 500, // Rows processed per frame during warmup
};
grid.resize = true; // Optional: row heights adapt after user column resizing
```
## Configuration Options
The plugin supports several configuration options to customize its behavior:
```typescript
type RowAutoSizeConfig = {
// Minimum row height in pixels (default: 24)
minHeight?: number;
// Maximum row height in pixels (default: 200)
maxHeight?: number;
// Use precise DOM-based calculations (slower but more accurate)
preciseSize?: boolean;
// Warm all approximate row heights in chunks before they are scrolled into view (default: true)
precalculate?: boolean;
// Rows processed per animation frame during non-precise warmup (default: 500)
precalculateBatchSize?: number;
// Custom height calculator function
calculateHeight?: (rowData: DataType, columns: ColumnRegular[]) => number | Promise;
};
```
### Custom Height Calculator
You can provide your own height calculation logic through the `calculateHeight` function:
```typescript
grid.rowAutoSize = {
calculateHeight: (rowData, columns) => {
// Custom logic to determine row height
const maxLines = columns.reduce((max, col) => {
const content = rowData[col.prop]?.toString() || '';
const lines = content.split('\n').length;
return Math.max(max, lines);
}, 1);
return maxLines * 24; // 24px per line
}
};
```
## Events and Updates
The plugin automatically responds to various grid events to maintain accurate row heights:
- **Data Changes**: Recalculates heights when the source data is updated
- **Cell Edits**: Updates heights for edited rows
- **Viewport Changes**: Calculates heights for newly visible rows
- **Configuration Changes**: Recomputes all heights when plugin settings change
- **Column Resizing**: Recomputes visible row heights after resized widths are rendered
## Performance Considerations
The plugin is designed with performance in mind, but there are some considerations:
1. **Precise Mode**: Using `preciseSize: true` provides more accurate calculations but is slower as it requires DOM operations.
2. **Precalculation**: In non-precise mode, `precalculate` is enabled by default. The plugin warms row height cache in chunks so rows already have a height when they are scrolled into view.
3. **Batch Size**: `precalculateBatchSize` controls how many rows are processed per animation frame. Larger values warm the cache faster but make each frame heavier.
4. **Large Datasets**: For very large datasets, set `precalculate: false` to calculate only visible rows and preserve demand-driven behavior.
5. **Custom Calculators**: When implementing a custom calculator, ensure it's efficient as it will be called frequently during scrolling.
To disable background warmup:
```typescript
grid.rowAutoSize = {
minHeight: 24,
maxHeight: 200,
precalculate: false,
};
```
## Best Practices
1. **Default Mode for Large Datasets**: Use the default approximate calculation mode for large datasets to maintain performance.
2. **Custom Height Logic**: Use `calculateHeight` when your data needs application-specific sizing.
3. **Reasonable Limits**: Set appropriate `minHeight` and `maxHeight` to prevent extreme row sizes.
4. **Cache Management**: The plugin automatically manages its cache, but consider clearing it when making major data changes.
## Example Use Cases
1. **Multi-line Text**:
```typescript
grid.columns = [{
prop: 'description',
name: 'Description',
size: 200,
}];
grid.source = [{
description: 'This is a long description\nthat spans multiple lines\nand needs appropriate height'
}];
```
2. **Rich Content**:
```typescript
grid.rowAutoSize = {
calculateHeight: (rowData) => {
if (rowData.hasImage) {
return 100; // Taller rows for rows with images
}
return 24; // Default height for other rows
}
};
```
## Conclusion
The Row Auto Size Plugin provides a powerful solution for handling varying content heights in RevoGrid. By automatically adjusting row heights while maintaining performance, it significantly improves the presentation and usability of your grid, especially when dealing with dynamic or rich content.
---
# Rows and Columns Context Menu
URL: https://pro.rv-grid.com/guides/rows/row-context-menu/
Source: src/content/docs/guides/rows/row-context-menu.mdx
Description: Custom context menu for rows and columns
This example demonstrates how to create separate context menus with interactive actions for grid rows and columns. Use the same `ContextMenuPlugin`, but pass row actions through `rowContextMenu` and column-header actions through `columnContextMenu`.
**Key Features**:
- Separate row and column context menu configurations
- Actions with visual indicators
- Styled menu buttons with hover effects
---
---
# Auto-Focus on Next Row WCAG
URL: https://pro.rv-grid.com/guides/rows/row-next-line/
Source: src/content/docs/guides/rows/row-next-line.mdx
Description: Enhances user navigation within the grid by automatically moving the focus to the next row
The plugin enhances user navigation within the grid by automatically moving the focus to the next row when the user reaches the last cell of the current row. This feature streamlines data entry and editing processes by eliminating the need for manual navigation between rows.
**Key Features**:
- **Automatic Navigation**: Focus shifts seamlessly to the next row once the user navigates to the last cell in the current row.
- **Enhanced Efficiency**: Improves data entry and editing efficiency by reducing the need for manual row transitions.
- **Configurable Behavior**: Allows customization of focus behavior to fit specific user needs and grid configurations.
**Benefits:**
- **Improved User Experience**: Simplifies navigation within the grid, making data handling more intuitive.
- **Increased Productivity**: Accelerates data entry tasks by automating row transitions.
**Usage:**
Integrate this plugin into your setup to enable automatic focus shifting, enhancing navigation and overall grid interaction.
```javascript
// fixing render for multiframework
```
---
# Row Odd
URL: https://pro.rv-grid.com/guides/rows/row-odd/
Source: src/content/docs/guides/rows/row-odd.mdx
Description: Configurable row striping
Apply configurable row striping for better readability and data distinction with the help of the `RowOddPlugin`.
```ts
import { RowOddPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOddPlugin];
grid.rowOdd = {
mode: 'custom',
interval: 3,
offset: 1,
className: 'priority-band',
};
```
`RowOddPlugin` is still a small rendering plugin, but it now covers production theming needs: odd/even striping, custom intervals, runtime updates, and predicate-based row bands.
---
# Row Status
URL: https://pro.rv-grid.com/guides/rows/row-status/
Source: src/content/docs/guides/rows/row-status.mdx
Description: Source-backed todo checkboxes, row attributes, strike-through styling, and hierarchy propagation.
`RowStatusPlugin` turns boolean columns into status checkboxes. The boolean remains in your source model, while the plugin adds semantic attributes to rendered rows and provides a default strike-through treatment.
- Status values are ordinary source booleans, so they can be saved, filtered, exported, and sorted.
- Every status column defines its own row attribute.
- Multiple status columns work independently.
- Tree parents cascade changes to descendants and reflect partial completion with an indeterminate checkbox.
- Completed-row sorting is application policy and stays outside the plugin.
## Basic setup
Register the plugin and add `rowStatus` to a boolean column. Only the strict value `true` activates the status.
```ts
import { RowStatusPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowStatusPlugin];
grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task' },
];
grid.source = [
{ task: 'Write release notes', done: false },
{ task: 'Publish the build', done: true },
];
```
An active row receives both `[data-done]` and the common `[row-status]` attribute. The common attribute drives the built-in strike-through style.
Use safe semantic names such as `data-done`, `data-archived`, or an appropriate `aria-*` attribute. Structural and event attributes such as `class`, `style`, and `onclick` are rejected so status configuration cannot overwrite renderer-owned row properties. Invalid names are ignored and reported once in development output.
## Styling
The checkbox cell stays interactive while normal row cells receive the status treatment. Override the CSS variables on the grid when a different presentation is needed:
```css
revo-grid.my-todos {
--revo-row-status-color: #64748b;
--revo-row-status-opacity: 0.7;
--revo-row-status-text-decoration: line-through;
--revo-row-status-checkbox-size: 20px;
--revo-row-status-checkbox-radius: 50%;
}
revo-grid.my-todos .rgRow[data-archived] .rgCell:not(.row-status-cell) {
--revo-row-status-text-decoration: line-through double;
}
```
Row-status controls are circular by default so they remain visually distinct from regular checkbox editors and row-selection checkboxes. Set `--revo-row-status-checkbox-radius` to another value if a different treatment is preferred.
## Multiple status columns
Each column uses its own source prop and attribute. A row keeps `[row-status]` while any configured status is active.
```ts
grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{
prop: 'archived',
name: 'Archived',
rowStatus: { attribute: 'data-archived' },
},
{ prop: 'task', name: 'Task' },
];
```
## Tree todo lists
Add `TreeDataPlugin` and normal tree configuration. Propagation is isolated per status column.
```ts
import { RowStatusPlugin, TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin, RowStatusPlugin];
grid.tree = {
idField: 'id',
parentIdField: 'parentId',
rootParentId: null,
expandAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'task', name: 'Task', tree: true },
];
```
| Action | Result |
| --- | --- |
| Check a parent | The parent and every descendant become complete, including collapsed or filtered descendants. |
| Clear a parent | The parent and every descendant become incomplete. |
| Complete every child | Ancestors become complete from the deepest parent to the root. |
| Complete only some children | The parent value remains false and its checkbox renders indeterminate. |
| Load inconsistent initial values | The source is not rewritten; subtree state is displayed and affected ancestors reconcile on the next edit. |
## Classic row grouping
Classic grouping works automatically when `grid.grouping` is configured. Every synthetic group label renders one checkbox for each row-status column; no extra plugin option is required.
```ts
grid.plugins = [RowStatusPlugin];
grid.grouping = {
props: ['department', 'team'],
expandedAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'archived', name: 'Archived', rowStatus: { attribute: 'data-archived' } },
{ prop: 'task', name: 'Task' },
{ prop: 'department', name: 'Department' },
{ prop: 'team', name: 'Team' },
];
```
| Grouping action | Result |
| --- | --- |
| Check a group status | Every real descendant receives that boolean value, including descendants hidden by collapsed groups or filters. |
| Edit one child | Ancestor group checkboxes recompute as checked, unchecked, or indeterminate. |
| Use multiple statuses | Each group checkbox and source prop remains independent. |
| Sort, filter, or regroup | Status remains attached to the real source model through its original physical index. |
| Listen for `rowstatuschange` | Only real models and application-source indexes are included; synthetic group rows are excluded. |
Normal data rows receive the configured attribute directly. Because core grouping headers are synthetic rows rendered through the group-label template, their derived attributes are placed on `.row-status-group-label` instead. The same `[row-status]` styling is applied when every descendant is complete for at least one status.
## Complete batch event
The directly clicked checkbox uses the regular `beforeedit` and `afteredit` events. Listen for `rowstatuschange` when you need the complete hierarchy batch:
```ts
grid.addEventListener('rowstatuschange', event => {
const {
prop,
value,
physicalIndexes,
models,
} = event.detail;
persistStatusChanges({ prop, value, physicalIndexes, models });
});
```
## Move completed items down
Sorting is intentionally configured by the application. Set ascending boolean sorting (`false` before `true`) and reapply it after a completed-status batch:
```ts
grid.columns = [
{
prop: 'done',
name: 'Done',
sortable: true,
order: 'asc',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task', tree: true },
];
grid.addEventListener('rowstatuschange', event => {
if (event.detail.prop === 'done') {
void grid.updateColumnSorting({ prop: 'done' }, 'asc', false);
}
});
```
See the generated [`RowStatusPlugin` API reference](/api/row-status/) for exported types, constants, and the full JSDoc example.
---
# Row Transpose
URL: https://pro.rv-grid.com/guides/rows/row-transpose/
Source: src/content/docs/guides/rows/row-transpose.mdx
Description: Learn how to use the Row Transpose Plugin in RevoGrid Pro to switch rows and columns dynamically.
The Row Transpose Plugin modifies the grid by transposing its data, meaning that the original rows are converted into columns and vice versa.
This can be especially useful when dealing with datasets that require pivoting or when users need to explore data from different perspectives.
- **Toggle Transpose**: Switch between the original view and the transposed view.
- **Dynamic Headers**: Customizable transposed headers for better data representation.
- **Custom Transpose Configuration**: Define how the columns and rows should be transformed, including whether to transpose column headers into rows.
## How Transposing Works
The plugin offers two primary functionalities:
- **Original View**: Displays rows and columns as defined in your data model.
- **Transposed View**: When activated, this feature converts rows into columns and columns into rows.
The `RowTransposePlugin` listens for the `row-transpose`.
When triggered, it switches between the original view and the transposed view while preserving the original structure, allowing you to revert to the standard view at any time.
Alternatively, you can access the plugin directly using the following JavaScript code:
```javascript
document.querySelector('revo-grid').getPlugins().then(plugins => {
const plugin = plugins.find(p => p instanceof RowTransposePlugin);
plugin?.transpose();
});
```
When the rows are transposed, RevoGrid enters a virtual transposed mode. This means that events related to row and column edits will return instances of the `TransposedRow` class, which represent the transposed data model. If you wish to retrieve the original model, you can use the `TransposedRow.getOriginalModels` method. This method takes transposed column properties as input and converts the corresponding transposed cell back to the original model.
For example:
```javascript
document.querySelector('revo-grid').addEventListener('beforeedit', async (e) => {
const model = e.detail.model;
console.log('beforeedit', model.getOriginalModels([e.detail.prop])); // returns original row model
});
```
### Customizing the Transpose
The plugin configuration allows you to fine-tune the transposing process. For example, you can choose to transpose the column headers into rows, or leave them unchanged.
```javascript
document.querySelector('revo-grid').additionalData = {
transpose: {
transposedRowHeader: { cellTemplate: () => '', columnTemplate: () => '' }, // defined row/header template
transposedColumnHeader: { columnTemplate: () => '' }, // template for transposed columns
}
};
```
## Conclusion
The **Row Transpose Plugin** is a powerful tool for displaying data in different formats within RevoGrid.
Whether you're working with complex datasets that require pivoting or simply need to view your data from a different perspective, this plugin provides the flexibility to switch between views seamlessly. With customizable options for how rows and columns are transposed, you can easily tailor the grid to your needs.
---
# Selection & Focus
URL: https://pro.rv-grid.com/guides/selection/
Source: src/content/docs/guides/selection/index.mdx
Description: Install the complete RevoGrid Pro selection suite with one plugin.
`SelectionPlugin` is the master entry point for spreadsheet-style selection in
RevoGrid Pro. It composes the compatible cell, row, and column selection
runtimes while keeping their existing events, configuration, and public methods
available.
## Quick setup
```ts
import {
SelectionPlugin,
rowHeaders,
} from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.rowHeaders = rowHeaders({ showHeaderFocusBtn: false });
grid.columns = [
{ prop: 'selected', name: '', size: 52, rowSelect: true },
{ prop: 'name', name: 'Name' },
{ prop: 'team', name: 'Team' },
];
grid.plugins = [SelectionPlugin];
```
One registration enables Ctrl/Cmd multi-range selection, whole-row selection
from row headers, whole-column selection from column headers, checkbox row
selection for `rowSelect` columns, and optional range constraints.
## Included plugins
| Runtime | Responsibility | Activation/configuration |
| --- | --- | --- |
| `MultiRangeSelectionPlugin` | Disjoint cell ranges, shared range rendering, clipboard operations, and range edits | Set `grid.range = true` for range gestures; configure `grid.multiRangeSelection` when needed |
| `RowHeaderPlugin` | Excel-style whole-row selection and full-width row ranges | Configure `grid.rowHeaders` |
| `ColumnSelectionPlugin` | Whole-column selection from headers and keyboard shortcuts | Active when the suite is installed |
| `RowSelectPlugin` | Independent checkbox/programmatic row selection | Add `rowSelect: true` to a column; configure `grid.rowSelect` when needed |
| `RangeSelectionLimitPlugin` | Optional row-only or column-only interactive ranges | Disabled until `grid.rangeSelectionLimit` is set |
The suite installs these runtimes in dependency order and reuses any instance
that is already present. This is safe, although redundant:
```ts
grid.plugins = [SelectionPlugin, RowSelectPlugin];
// There is still only one RowSelectPlugin runtime.
```
Use individual plugins instead of `SelectionPlugin` when the application needs
only a smaller subset.
## Interaction ownership
The master plugin does not introduce another selection store. Each included
plugin remains the sole owner of its existing state:
- A plain cell click drops interactive row-header or column-header selection
and focuses the clicked cell.
- Row-header and column-header selection hand off to each other, so both do not
compete for the active full-width/full-height range.
- Ctrl/Cmd and Shift gestures continue to use the multi-range engine.
- Checkbox row selection stays independent from row-header selection. It keeps
its own selected rows and `rowselected` events.
- Existing plugin methods are retrieved from the child runtime as before:
```ts
const plugins = await grid.getPlugins();
const ranges = plugins
.find(plugin => plugin instanceof MultiRangeSelectionPlugin)
?.getSelectedRanges();
```
`AutoFillPlugin`, `AutoFillPreviewPlugin`, and `RangeCopyPreviewPlugin` are not
installed by `SelectionPlugin`. They own fill/copy editing previews rather than
selection state, and smart AutoFill preview versus exact-copy preview is an
intentional one-of choice. Add the preview behavior that matches the grid.
## Selection guides
Select, edit, clear, and copy disjoint cell ranges.
[Read the guide](./multi-range-selection/)
Select, extend, and toggle complete columns.
[Read the guide](./column-selection/)
Configure checkbox rows and Excel-style row-header gestures.
[Read the guide](./row-select/)
Restrict interactive ranges to one row or one column.
[Read the guide](./range-selection-limit/)
Add the optional exact-copy drag preview.
[Read the guide](./range-copy-preview/)
See the [`SelectionPlugin` API reference](/api/selection/) for the exported
class and dependency metadata.
---
# Column Selection
URL: https://pro.rv-grid.com/guides/selection/column-selection/
Source: src/content/docs/guides/selection/column-selection.mdx
Description: Column selection functionality.
Enable spreadsheet-style selection for complete visible columns. Header labels
select columns, while the dedicated sort and filter affordances keep their
normal behavior.
## User behavior
| Gesture | Result |
| --- | --- |
| Click a header | Replace the selection with that column |
| Shift-click | Select the contiguous span from the anchor column |
| Ctrl/Cmd-click | Toggle a disjoint column |
| Ctrl/Cmd+Shift-click | Add an anchored column span |
| Drag across headers | Select the dragged contiguous span |
| Ctrl/Cmd-drag | Add the dragged span |
| Ctrl+Space | Select the focused cell's entire column |
| Ctrl+Shift+Space | Extend from the column anchor to the focused column |
| Click a sort icon | Sort without changing the column selection |
The plugin reuses RevoGrid's multi-range selection engine, so copying, clearing,
and editing disjoint selected columns follow the same rules as ordinary
multi-range selections. Selections follow column `prop` values through sorting
and reordering and update to the current visible row extent after source or
filter changes.
## Events and programmatic access
`beforecolumnselection` is cancelable. Its detail describes the gesture source,
selection mode, target column, resulting column props, anchor, and original
pointer or keyboard event. Canceling it leaves the normal header action, such as
sorting a sortable column, available.
Use the plugin instance to read selected column props in visual order:
```ts
const plugin = (await grid.getPlugins())
.find(plugin => plugin instanceof ColumnSelectionPlugin);
const selectedColumns = plugin?.getSelectedColumns();
```
```javascript
// fixing render for multiframework
```
---
# Multi-Range Selection
URL: https://pro.rv-grid.com/guides/selection/multi-range-selection/
Source: src/content/docs/guides/selection/multi-range-selection.mdx
Description: Select, clear, and copy multiple disjoint RevoGrid ranges.
`MultiRangeSelectionPlugin` adds spreadsheet-style disjoint range selection while
leaving the native RevoGrid active range in charge of focus, Shift extension, and
editing.
Use it when users need to compare, clear, or copy multiple non-contiguous regions from the
same grid.
Select a range, Ctrl/Cmd+click another cell, Shift+click to extend the new
range, then copy. Plain-text clipboard output includes selected cells only, so
paste does not clear gap cells between disjoint ranges.
## Usage
```ts
import { MultiRangeSelectionPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [MultiRangeSelectionPlugin];
```
Cross-viewport autofill is enabled by default when this plugin is installed.
Users can drag the autofill handle between pinned and main row or column
viewports, and the committed result is stored as multiple selected ranges.
Disable only that drag integration with:
```ts
grid.multiRangeSelection = { crossViewportAutofill: false };
```
## User Behavior
1. Select a cell or range normally.
2. Ctrl/Cmd+click another cell to keep the previous range and start a new active range.
3. Shift+click or Shift+Arrow extends only the active range.
4. Tab and Enter move the focused cell inside the active range.
5. Delete and Backspace clear inactive ranges plus the active range.
6. Copy includes inactive ranges plus the active range.
7. Dragging the autofill handle across pinned/main viewport boundaries fills the visible target segments.
8. Plain click clears inactive ranges and starts a normal selection.
## Clipboard Shape
Multi-range copy preserves relative shape for nearby ranges in `text/plain`.
For example, diagonal selected cells are copied with blank cells between them so
external spreadsheets can keep the diagonal layout instead of stacking values
one after another. RevoGrid also writes a custom multi-range clipboard payload;
when the payload is pasted back into RevoGrid, only the selected cells are
edited, so the blank gaps do not clear existing destination values. Very large
sparse selections fall back to selected rectangular blocks to avoid huge
clipboard payloads. Rich `text/html` output mirrors the selected clipboard
shape.
Pinned and regular viewport coordinates are normalized into whole-grid visual
order before copy, so a pinned-left cell followed by the first regular cell is
copied as adjacent clipboard columns.
If only one range is selected, RevoGrid keeps its default copy behavior.
## Programmatic API
Call the public methods on the plugin instance:
```ts
const plugin = (await grid.getPlugins())
.find(plugin => plugin instanceof MultiRangeSelectionPlugin);
plugin?.setSelectedRanges([
{ rowType: 'rgRow', colType: 'rgCol', range: { x: 0, y: 0, x1: 1, y1: 1 } },
{ rowType: 'rgRow', colType: 'rgCol', range: { x: 3, y: 0, x1: 3, y1: 1 } },
]);
const ranges = plugin?.getSelectedRanges();
plugin?.clearSelectedRanges();
```
The grid also dispatches `multirangeselectionchange` whenever the selected ranges
change.
## Rendering
Inactive cells receive:
- attribute: `multi-range-selection`
- class: `multi-range-selection-cell`
- edge classes for the range perimeter
The active range remains the native RevoGrid selection overlay.
---
# Range Copy Preview
URL: https://pro.rv-grid.com/guides/selection/range-copy-preview/
Source: src/content/docs/guides/selection/range-copy-preview.mdx
Description: Shows ghost values for the exact range copy that will be applied on drop.
`RangeCopyPreviewPlugin` shows the copied values that will be applied when a user
drags a selected range with the fill handle. It is a view-only companion plugin:
the data store is not changed until the existing drop-time range copy flow runs.
Use it when users need confidence that a drag will repeat the selected source
range exactly, without AutoFill sequence inference.
Select the first two or three populated rows in the demo, drag the fill handle
downward, and the empty target cells show ghost copied values before mouseup.
## Usage
```ts
import { RangeCopyPreviewPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [RangeCopyPreviewPlugin];
```
The plugin uses the same core drag signal as range copy. It does not need
configuration for the default behavior.
## User Behavior
1. Select a source cell or range.
2. Drag the fill handle to a larger target range.
3. Target cells show the exact copied values in a ghost style.
4. Source cells remain visually unchanged.
5. Release the mouse to apply the core range copy.
If the temporary range is cleared, the source changes, or the plugin is destroyed,
the preview is removed immediately.
## Difference From AutoFill Preview
`AutoFillPreviewPlugin` previews smart sequence output from `AutoFillPlugin`, such
as `1, 2` becoming `3, 4` or `Mon, Tue` becoming `Wed`.
`RangeCopyPreviewPlugin` previews exact range copy output:
```txt
Draft, Approved -> Draft, Approved, Draft, Approved
North, West -> North, West, North, West
```
Use one preview plugin at a time for this drag surface:
| Plugin | Preview intent |
| --- | --- |
| `AutoFillPreviewPlugin` | Show predicted smart AutoFill sequence values. |
| `RangeCopyPreviewPlugin` | Show exact copied source values. |
## Rendering
Preview cells receive:
- attribute: `range-copy-preview`
- class: `range-copy-preview-cell`
Default styling uses reduced opacity and italic text so users can distinguish
preview values from committed data.
```css
.range-copy-preview-cell {
opacity: 0.45;
font-style: italic;
}
```
## Notes
The plugin does not mutate `source`, `newData`, or provider stores. It only
overrides the render model during `beforecellrender`, then refreshes affected row
types in a frame batch.
---
# Range Selection Limit
URL: https://pro.rv-grid.com/guides/selection/range-selection-limit/
Source: src/content/docs/guides/selection/range-selection-limit.mdx
Description: Restrict interactive range selection to a single row or column.
`RangeSelectionLimitPlugin` keeps user-created range selections in one dimension.
Use it when a workflow accepts row-only or column-only bulk actions and should not
allow rectangular selections.
The plugin does not replace RevoGrid selection. It intercepts the pending range
at the `beforeapplyrange` lifecycle point, constrains the shape, and then lets the
normal `setrange` flow continue.
## Usage
```ts
import { RangeSelectionLimitPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [RangeSelectionLimitPlugin];
grid.rangeSelectionLimit = { mode: 'column' };
```
`grid.range = true` is still required because this plugin only constrains range
selection after core range selection is enabled.
## Modes
| Mode | Behavior | Typical use |
| --- | --- | --- |
| `column` | Keeps the focused column fixed and expands only across rows. | Select multiple rows for one field. |
| `row` | Keeps the focused row fixed and expands only across columns. | Select multiple fields for one row. |
### Column Mode
`column` keeps the focused column fixed and allows the selection to expand
vertically:
```ts
grid.rangeSelectionLimit = { mode: 'column' };
```
If the user starts on column `status` and drags diagonally, the selected range
stays inside `status` while the row bounds change.
### Row Mode
`row` keeps the focused row fixed and allows the selection to expand
horizontally:
```ts
grid.rangeSelectionLimit = { mode: 'row' };
```
If the user starts on row `12` and drags diagonally, the selected range stays on
row `12` while the column bounds change.
## Configuration Forms
Boolean shorthand is supported:
```ts
grid.rangeSelectionLimit = true; // same as { mode: 'column' }
grid.rangeSelectionLimit = false; // disabled
```
String shorthand is also supported:
```ts
grid.rangeSelectionLimit = 'column';
grid.rangeSelectionLimit = 'row';
```
The object form is preferred for new code because it leaves room for future
options:
```ts
grid.rangeSelectionLimit = {
mode: 'column',
};
```
For legacy integrations, the plugin also reads `additionalData.rangeSelectionLimit`,
but direct `grid.rangeSelectionLimit` configuration is preferred.
```ts
grid.additionalData = {
rangeSelectionLimit: {
mode: 'row',
},
};
```
## Lifecycle
The plugin uses the existing RevoGrid range lifecycle:
1. RevoGrid computes the pending range from focus and drag/keyboard input.
2. `beforeapplyrange` is emitted by the overlay selection component.
3. `RangeSelectionLimitPlugin` replaces `event.detail.range` with the constrained range.
4. RevoGrid transforms that range to row/column data and emits `beforesetrange`.
5. RevoGrid emits `setrange`, and the selection store receives the constrained range.
The plugin does not add render layers, change data, or override single-cell
focus. If there is no focused cell, it uses the pending range start as the anchor.
## API
```ts
export type RangeSelectionLimitMode = 'column' | 'row';
export type RangeSelectionLimitOptions = {
mode: RangeSelectionLimitMode;
};
export type RangeSelectionLimitConfig =
| boolean
| RangeSelectionLimitMode
| RangeSelectionLimitOptions;
```
## Future Shape Validation
Arbitrary shape validation should be added as a separate mode or validator API so
the existing `row` and `column` modes keep their current behavior.
---
# Row Selection
URL: https://pro.rv-grid.com/guides/selection/row-select/
Source: src/content/docs/guides/selection/row-select.mdx
Description: Row selection in grid
Apply selection to rows in grid.
This is an advanced feature that allows users to select multiple rows in grid and explains how to implement this with the help of the `RowSelectPlugin`.
It is an extremely **powerful and flexible feature**, despite its straightforward functionality.
1. **Handles Complex Edge Cases**: The selection plugin is built to support **extreme edge case scenarios**, ensuring it remains reliable and performant in even the most challenging use cases.
2. **Efficient Redraw Management**: Plugin provides **better control** over the selection updates, boosting **performance** and responsiveness for large datasets. Also it doesn't modify original data set.
3. **Visual Feedback with Highlighted Rows**: The plugin highlights **selected rows** and applies relevant CSS classes, providing **instant visual feedback** to users, making the grid more interactive and intuitive.
4. **Bulk Selection Support**: The plugin simplifies bulk operations by enabling **select all** functionality, allowing users to select and manipulate **multiple rows** at once.
5. **Event-Driven Row Selection**: The plugin emits a `rowselected` event whenever a row is selected, enabling easy collection of **selected rows** and providing flexibility for further actions.
6. **Interaction with Other Features**: The plugin works smoothly with **filtering, sorting, grouping, tree data, and drag-and-drop**, allowing users to **select rows** while still interacting with other grid features without interference.
7. **Advanced Plugin Example**: The `RowSelectPlugin` serves as an **advanced example** of how to create sophisticated, flexible grid plugins, demonstrating how to implement custom solutions for specific needs.
8. **Foundation for Future Plugins**: The plugin sets a strong **foundation** for building more advanced system features, providing a **reusable and extensible pattern** for other interactive grid-based plugins.
- **Row Selection**: Allows users to select multiple rows in the grid.
- **Column Selection**: Enables users to select entire columns.
- **Grouped Row Selection**: Can render optional group-level checkboxes that select all descendant data rows.
- **Checkbox-Based Row Reorder**: Can hand checkbox-selected rows to `RowOrderPlugin` so checked rows drag as one compact block.
- **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings.
- **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further.
To enable the `RowSelectPlugin` in your RevoGrid setup, you have two options:
1. **Using `rowSelect` Prop in Columns**
2. **Using `columnType` with `RowSelectColumnType`**
## Option 1: Using `rowSelect` Prop in Columns
In this approach, the rowSelect property in the column definition enables checkbox-based row selection for the specific column.
```typescript
grid.columns = [
{
prop: '_check',
rowSelect: true,
},
...
];
// Define plugin
grid.plugins = [RowSelectPlugin];
```
## Option 2: Using columnType with RowSelectColumnType
Alternatively, you can use a custom columnType and the RowSelectColumnType to enable row selection.
```typescript
import { RowSelectColumnType, RowSelectPlugin, DEFAULT_SEL_PROP } from '@revolist/revogrid-pro';
grid.source = [];
// Define columns
grid.columns = [{
prop: DEFAULT_SEL_PROP,
columnType: 'select' // Use columnType 'select'
}, ...];
// Define plugin
grid.plugins = [RowSelectPlugin];
// Define the custom column type for selection
grid.columnTypes = { select: new RowSelectColumnType()};
```
## Pointer And Keyboard Gestures
Checkboxes and row headers are independent selection modes. Checkbox state,
anchors, programmatic selection, and `rowselected` events remain owned by
`RowSelectPlugin`. Spreadsheet-style row-header state and anchors remain owned
by `RowHeaderPlugin`, so using one mode never replaces or toggles the other.
Row-header selections are also exposed as full-width multi-ranges, allowing
copy, Delete, and other range operations to act on every selected row. Delete
and Backspace clear editable cells across pinned and regular column sections;
readonly cells and values rejected by strict column validation remain unchanged:
| Gesture | Result |
| --- | --- |
| Click a row checkbox | Toggle that row without clearing other checked rows |
| Click the row-header number, surrounding area, or drag handle | Replace selection and focus the whole row |
| Click an ordinary cell after row-header or row-header drag-handle selection | Clear the row selection and focus the cell |
| Ctrl-click / Cmd-click an ordinary cell after row-header selection | Keep the row selection and add the cell selection |
| Shift-click an ordinary cell after row-header selection | Keep the row selection while extending the active cell range |
| Ctrl-click / Cmd-click a row header | Toggle one row and preserve the other selected rows |
| Shift-click a row header | Select the visible range from the selection anchor |
| Ctrl/Cmd+Shift-click a row header | Add the anchored visible range |
| Space on a focused row-selection checkbox | Toggle that row |
Selections are stored against source rows. Sorting, filtering, and virtual
scrolling do not convert them into rendered viewport positions. A selected row
therefore remains selected while it is outside the viewport or temporarily
hidden by a filter.
## Grouped Row Selection
Grouped row checkboxes are opt-in so existing grouped grids keep their current rendering.
```typescript
grid.columns = [
{ prop: 'selected', rowSelect: true },
{ prop: 'name', name: 'Name' },
{ prop: 'department', name: 'Department' },
];
grid.grouping = { props: ['department'], expandedAll: true };
grid.rowSelect = {
grouping: true,
};
grid.plugins = [RowSelectPlugin];
```
You can also use the future-compatible object form:
```typescript
grid.rowSelect = {
grouping: {
enabled: true,
},
};
```
When enabled, selecting a group checkbox selects all descendant data rows. Selecting only part of a group reflects an indeterminate state on the group checkbox. Synthetic grouping rows are not included in selected row counts.
## Checkbox-Based Row Reorder
`RowSelectPlugin` can opt into `RowOrderPlugin` integration. When enabled, dragging a checked row moves all visible checked data rows in the current row viewport as a compact block.
This feature is disabled by default. Existing row-order behavior is unchanged unless `rowSelect.rowOrder` is enabled.
```typescript
import { RowOrderPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
grid.columns = [
{ prop: 'drag', name: '', rowDrag: true, size: 42 },
{ prop: 'selected', name: '', rowSelect: true, size: 56 },
{ prop: 'name', name: 'Name' },
{ prop: 'team', name: 'Team' },
];
grid.rowSelect = {
rowOrder: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
};
grid.plugins = [RowSelectPlugin, RowOrderPlugin];
```
You can also use the object form:
```typescript
grid.rowSelect = {
rowOrder: {
enabled: true,
},
};
```
Checkbox selection takes priority over range selection only when the dragged row is checked. If the dragged row is not checked, `RowOrderPlugin` falls back to its current range-selection or single-row behavior.
Only visible checked data rows move during the drag. Rows hidden by filtering, collapsed grouping, or collapsed tree branches remain selected but are not part of that drag operation. Synthetic grouping rows are never moved as selected data rows.
## Grouping And Row Reorder Together
Grouped row checkboxes and checkbox-based row reorder can be enabled together:
```typescript
grid.grouping = { props: ['team'], expandedAll: true };
grid.rowSelect = {
grouping: true,
rowOrder: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
};
grid.plugins = [RowSelectPlugin, RowOrderPlugin];
```
With this setup:
- Group checkboxes select or clear all descendant data rows.
- Dragging a checked child row moves visible checked child rows as a compact block.
- Parent and ancestor group checkboxes reflect child selection with checked or indeterminate state.
- Group headers are excluded from select-all counts and row-order moved items.
- Row-order grouping validation still applies, so invalid cross-group drops remain blocked unless your row-order configuration explicitly allows applying target group values.
## Filtering, Sorting, Row Drag, And Tree Data
Row selection is stored against real data row indexes, not the rendered row position. This keeps checkbox state stable when the visible row topology changes.
| Case | Behavior |
| --- | --- |
| Filtering | Selected hidden rows remain selected. The `rowselected` event is re-emitted so `visibleCount` and `visibleRowsCount` reflect the filtered view. |
| Sorting | Checkbox state follows the same data rows after sort order changes. |
| Row drag | Checkbox state follows moved data rows after row order changes. |
| Checkbox row-order | When `rowSelect.rowOrder` is enabled, visible checked rows are moved as a compact block before range selection is considered. |
| Grouping | Group checkboxes are opt-in through `rowSelect.grouping`; synthetic group rows are not counted as selected rows. |
| Tree data | When `TreeDataPlugin` is registered, selecting a tree parent toggles the parent and descendants. Dragging a checkbox-selected tree parent with descendants preserves the subtree relationship. |
## API Quick Reference
| API | Default | Meaning |
| --- | --- | --- |
| `ColumnRegular.rowSelect` | `false` | Renders row-selection checkboxes in normal data cells for that column. A predicate can hide checkboxes for specific cells. |
| `grid.rowSelect.grouping` | `false` | Enables group-level row-selection checkboxes. Accepts `true` or `{ enabled: true }`. |
| `grid.rowSelect.rowOrder` | `false` | Enables checkbox-selected row drag integration with `RowOrderPlugin`. Accepts `true` or `{ enabled: true }`. |
| `additionalData.rowSelect` | none | Legacy alias for `grid.rowSelect`. Prefer direct `grid.rowSelect`. |
The `rowselected` event exposes totals for both all selectable rows and the currently visible selectable rows:
| Event field | Meaning |
| --- | --- |
| `count` | Selected real data rows across the full dataset. |
| `allRowsCount` | Selectable real data rows across the full dataset. |
| `visibleCount` | Selected real data rows currently visible. |
| `visibleRowsCount` | Selectable real data rows currently visible. |
## Bulk Row Selection Demo
The live demo on this page is the common **Bulk Row Selection** example. It includes flat, grouped, and tree modes, a row checkbox column, row drag handles, group checkbox selection, filtering, sorting, selected/visible totals, and opt-in checkbox-based row reorder.
---
# Server-side Row Grouping
URL: https://pro.rv-grid.com/guides/server-side-grouping/
Source: src/content/docs/guides/server-side-grouping.mdx
Description: Group, expand, and lazy-load millions of rows without loading the full dataset into the browser.
Server-side row grouping for large datasets lets RevoGrid render grouped data from a backend route API. The browser keeps only visible grouped blocks and cached route blocks, while your server owns grouping, sorting, filtering, quick filtering, counts, and aggregates.
Use it for ERP, financial analytics, logs, operations, and reporting tools where a complete client-side dataset is too large.
- Group by one or multiple columns.
- Lazy-load group children on expand.
- Works with server-side Infinity Scroll semantics.
- Supports unknown row counts.
- Keeps browser memory low with block caching.
- Sends sort, filter, quick-filter, and group state to your backend.
## Setup
```ts
import { ServerSideGroupingPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
loadRows: async (request, signal) => {
const response = await fetch('/api/grid/grouping', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
},
};
grid.plugins = [ServerSideGroupingPlugin];
```
`groupBy` and `loadRows` are the only required options. The full configuration can add request tuning, a quick-filter payload, an imperative plugin controller, and an optimized expand-all datasource:
```ts
let groupingPluginControllerApi;
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
blockSize: 100,
purgeClosedGroups: false,
maxConcurrentRequests: 4,
blockLoadDebounceMs: 25,
quickFilter: { text: '' },
api: api => {
groupingPluginControllerApi = api;
},
loadRows,
loadExpandedGroups: async (request, signal) => {
const response = await fetch('/api/grid/grouping/expand-all', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
},
};
```
`api` is not a server endpoint. It is a callback that gives the application an imperative controller after the plugin is initialized. Use it from toolbar buttons, route changes, or refresh actions:
```ts
groupingPluginControllerApi?.refreshServerSide();
groupingPluginControllerApi?.expandGroup(['Germany']);
groupingPluginControllerApi?.purgeServerSideCache();
```
`loadRows` is the required server datasource. It loads one route block at a time: root groups, child groups, or final leaf rows. `loadExpandedGroups` is optional and only optimizes `expandAllGroups()` by letting the backend return a flattened group tree in one request.
When `AdvanceFilterPlugin` is active, assigning `grid.quickFilter` refreshes the grouped datasource and forwards normalized `{ text, columns? }` data in every request. An explicitly assigned grid-level quick filter takes precedence over `serverSideGrouping.quickFilter`, including an explicit empty value used to clear search. The config value remains the fallback until the grid-level property is assigned.
The plugin owns the main row source while active. `InfinityScrollPlugin` is complementary in behavior and request shape, but should not also own the same `rgRow` source. If both plugins are registered and `serverSideGrouping` is configured, Infinity Scroll no-ops with a warning.
When combining server-side grouping with selection filters, provide `filter.selection.getItems` from the same backing datasource used by your server loader. The grid source contains generated group rows and only the currently loaded route blocks, so deriving selection options from the visible grid source can produce incomplete values such as only `Empty`.
## Request Contract
`loadRows(request, signal)` receives:
```ts
{
route: ['Germany'],
level: 1,
groupBy: ['country', 'sport'],
start: 0,
limit: 100,
sort: { revenue: 'desc' },
filter: { country: { type: 'contains', value: 'Ger' } },
quickFilter: { text: 'premium' },
parent: { key: 'Germany', count: 12450 },
levelParams: { tenantId: 'acme' }
}
```
Return group rows before the final grouping level:
```ts
{
groups: [
{ key: 'Swimming', count: 2100, aggregates: { Revenue: 'EUR 9.2M' } },
{ key: 'Cycling', count: 1540 }
],
rowCount: 2,
grandTotals: { Revenue: 'EUR 18.7M' }
}
```
Return leaf rows at the final level:
```ts
{
rows: [
{ id: 1, country: 'Germany', sport: 'Swimming', city: 'Berlin', revenue: 1200 }
],
rowCount: 2100,
hasMore: true
}
```
When `rowCount` is omitted, RevoGrid treats the count as unknown and keeps a loading tail while `hasMore` is true.
## Runtime API
The API is exposed through the config callback, not by mutating the grid instance.
```ts
grid.serverSideGrouping = {
groupBy: ['country'],
loadRows,
api: api => {
groupingPluginControllerApi = api;
groupingPluginControllerApi?.expandGroup(['Germany']);
groupingPluginControllerApi?.expandAllGroups();
groupingPluginControllerApi?.refreshRoute(['Germany']);
groupingPluginControllerApi?.purgeServerSideCache();
console.log(groupingPluginControllerApi?.getServerSideCacheState());
},
};
```
Available methods:
| Method | Description |
|---|---|
| `expandGroup(id)` | Expands and lazy-loads a group route. |
| `expandAllGroups()` | Loads group blocks recursively and expands every server group route. Leaf-row blocks still load from the viewport. |
| `collapseGroup(id)` | Collapses a group route. |
| `collapseAllGroups()` | Collapses all group routes currently known by the cache. |
| `refreshServerSide()` | Invalidates all route blocks and reloads root. |
| `refreshRoute(route)` | Invalidates only one route branch when possible. |
| `purgeServerSideCache(route?)` | Clears all cache or one route branch. |
| `getServerSideCacheState()` | Returns debug route, block, loading, and queue state. |
`expandAllGroups()` opens every group route. It does not load every leaf row, so large final groups stay virtualized and fetch visible row blocks through the normal server-side scroll path.
When `loadExpandedGroups` is configured, `expandAllGroups()` uses that single endpoint instead of recursively loading route blocks:
```ts
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
loadRows,
loadExpandedGroups: async request => ({
groups: [
{ route: ['Germany'], key: 'Germany', count: 12450 },
{ route: ['Germany', 'Swimming'], key: 'Swimming', count: 2100 },
{ route: ['Germany', 'Cycling'], key: 'Cycling', count: 1540 },
],
}),
};
```
Each returned group row must include its full `route`. The plugin rebuilds group route blocks from this flattened tree and keeps leaf-row blocks unloaded until they enter the viewport.
## Configuration
| Option | Default | Description |
|---|---:|---|
| `groupBy` | required | Grouping column props in server route order. |
| `blockSize` | `100` | Direct children requested per block. |
| `purgeClosedGroups` | `false` | Purge child route cache on collapse. |
| `maxConcurrentRequests` | `4` | Limits simultaneous route block requests. |
| `blockLoadDebounceMs` | `0` | Delays block load dispatch after scroll/materialization. |
| `quickFilter` | `undefined` | Global filter payload forwarded to the server. |
| `api` | `undefined` | Receives the client-side plugin controller. This is not a server request callback. |
| `loadRows` | required | Server datasource for root groups, nested group routes, and final leaf rows. |
| `loadExpandedGroups` | `undefined` | Optional one-request expand-all endpoint that returns flattened group rows with routes. |
---
# Cell Validation with Renderers
URL: https://pro.rv-grid.com/guides/validate-basic/
Source: src/content/docs/guides/validate-basic.mdx
Description: Explore different methods for validating cell data in RevoGrid and ensuring data integrity.
One straightforward way to validate cell data is by combining `validate`, `validationTooltip`, and `validationRenderer`.
This marks invalid cells during rendering without changing edit behavior, which is useful for audits, imported data review, and soft warnings.
```ts
import { validationRenderer } from '@revolist/revogrid-pro';
const columns = [
{
prop: 'price',
name: 'Price',
validate: (value, row) => value >= row.floor && value <= row.ceiling,
validationTooltip: (value, row) =>
`Price ${value} must stay between ${row.floor} and ${row.ceiling}`,
...validationRenderer({
severity: 'error',
indicatorPlacement: 'corner',
messagePlacement: 'tooltip',
}),
},
];
```
`validationRenderer` returns `cellProperties` and `cellTemplate`, so it can wrap your existing cell renderer while keeping validation styling in one place.
The helper supports:
- `severity`: `error`, `warning`, or `info`.
- `indicatorPlacement`: `corner`, `start`, `end`, or `none`.
- `messagePlacement`: `tooltip`, `inline`, `both`, or `none`.
- `message`: a custom message resolver when `validationTooltip` is not enough.
- `invalidProperties`: extra cell properties for invalid cells.
`messagePlacement` controls where the validation message is exposed:
- `tooltip` adds `title` and tooltip data attributes.
- `inline` renders the message inside the cell without tooltip attributes.
- `both` renders inline text and tooltip attributes.
- `none` keeps the visual indicator but hides the message text.
:::note
This renderer-only approach shows invalid data already present in the grid. To prevent invalid edits from being committed, use `CellValidatePlugin` from the input validation guide.
:::
---
# Validate user data input
URL: https://pro.rv-grid.com/guides/validate-input/
Source: src/content/docs/guides/validate-input.mdx
Description: Explore strict and soft cell validation for spreadsheet-style data entry.
This demo exposes the rules as controls. Try strict mode to reject invalid edits, soft mode to save invalid data with markers, and the seed/reset actions to simulate cleanup workflows.
The **CellValidatePlugin** validates edits before they are written to the grid source. Use it when the grid should enforce spreadsheet-style rules such as required fields, numeric ranges, enum membership, cross-field limits, and approval gates.
Install it together with **EventManagerPlugin** so edit events are collected through the Pro event pipeline:
```ts
import {
CellValidatePlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
EventManagerPlugin,
CellValidatePlugin,
];
grid.eventManager = {
applyEventsToSource: true,
};
```
:::note
`EventManagerPlugin` should be registered before `CellValidatePlugin`. Validation resolves inside the event manager's pre-commit phase, so normal `gridedit` consumers such as history, audit, flash, and collaboration observe only the accepted patch.
:::
## Clipboard validation resolution
Clipboard paste is atomic by default: one strict validation failure rejects the complete paste. Opt into `valid-cells` when valid destinations should be applied while invalid destinations keep their previous values and validation feedback:
```ts
grid.useClipboard = {
validationResolution: 'valid-cells',
};
```
The policy applies to standard keyboard paste, multi-range/cross-viewport paste, and context-menu paste. It does not change ordinary edits, autofill, readonly filtering, or soft validation. The accepted cells remain one edit transaction, so Undo restores them together without touching rejected destinations.
## Strict and soft validation
By default validation is strict. If `validate(value, model)` returns `false`, the edit is rejected and the source keeps the previous value.
```ts
const columns: ColumnRegular[] = [
{
name: 'Price',
prop: 'price',
validate: (value, model) => {
const price = Number(value);
return Number.isFinite(price) && price >= 50 && price <= 1200;
},
validationTooltip: (value) =>
`Price ${value} must be between 50 and 1200`,
},
];
```
Set `softValidation: true` when invalid values should still be saved and marked visually. This is useful for imported data cleanup flows where users need to see and fix invalid rows in place. Pair it with `validationRenderer` when you want saved invalid values to stay visible after the edit.
```ts
{
prop: 'discount',
softValidation: true,
...validationRenderer(),
validate: (value, model) => Number(value) <= Number(model.price) * 0.3,
validationTooltip: (value, model) =>
`Discount cannot exceed 30% of price (${Math.round(Number(model.price) * 0.3)})`,
}
```
## Candidate row model
Both `validate(value, model)` and `validationTooltip(value, model)` receive the row model. During strict edit validation, the model includes the pending row patch, so cross-field validation works for single edits and pasted batches. If the edit is rejected, the tooltip renderer receives the rejected value and candidate model, which lets the message explain what failed even though the source was not changed.
```ts
{
name: 'Approved',
prop: 'approved',
validate: (value, model) => {
const netPrice = Number(model.price) - Number(model.discount ?? 0);
return netPrice <= 900 || value === true;
},
validationTooltip: () => 'High-value rows require approval',
}
```
`validationTooltip` also receives the column as a third argument: `validationTooltip(value, model, column)`. Use that when one shared message resolver needs column metadata.
## Cross-column example
The validation function can also block a cell based on another column in the same row. This example prevents fees on expired services.
---
# Aggregations
URL: https://pro.rv-grid.com/api/aggregations/
Source: src/content/docs/api/aggregations.md
### `commonAggregators`
Defines a collection of common aggregation functions used for table calculations.
These aggregators transform an array of numerical values into meaningful statistical summaries.
```ts
commonAggregators: {
sum: (values: any[]) => any;
count: (values: any[]) => number;
avg: (values: any[]) => number;
min: (values: any[]) => number;
max: (values: any[]) => number;
median: (values: any[]) => number;
mode: (values: any[]) => string | 0;
range: (values: any[]) => number;
variance: (values: any[]) => number;
stdDev: (values: any[]) => number;
first: (values: any[]) => any;
last: (values: any[]) => any;
distinct: (values: any[]) => number;
};
```
---
### `advancedAggregators`
```ts
advancedAggregators: {
'%oftotal': (values: any[]) => number[];
accSum: (values: any[]) => any;
};
```
---
### `Aggregations`
```ts
export type Aggregations = Record any>;
```
---
# Array Renderer
URL: https://pro.rv-grid.com/api/array-renderer/
Source: src/content/docs/api/array-renderer.md
### `arrayRenderer`
The `arrayRenderer` is a custom cell renderer for RevoGrid that can handle both single values
and arrays of values. It applies different renderers based on the value type.
**Features**:
- Detects if the value is an array or a single value
- For arrays, renders multiple elements using the provided renderer
- For single values, renders a single element using the provided renderer
- Supports custom separator between array items
- Supports custom wrapper for array items
**Usage**:
- Import `arrayRenderer` and use it as a wrapper around your existing renderer
- Configure the renderer with options like separator and wrapper
### Example
```typescript
import { arrayRenderer } from '@revolist/revogrid-pro';
import { linkRenderer } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'links',
name: 'Links',
cellTemplate: arrayRenderer(linkRenderer, {
separator: ', ',
wrapper: 'div',
}),
},
];
```
Example of using multiRenderer to handle different types of values
prop: 'data',
name: 'Data',
cellTemplate: multiRenderer({
default: progressLineRenderer,
renderers: [
{
// Use linkRenderer for URLs
condition: (value) => typeof value === 'string' &&
(value.startsWith('http://') || value.startsWith('https://')),
renderer: linkRenderer,
},
{
// Use progressLineRenderer for numbers
condition: (value) => typeof value === 'number',
renderer: progressLineRenderer,
},
],
separator: ' | ',
wrapper: 'div',
wrapperClass: 'multi-data-container',
}),
};
```ts
export function arrayRenderer(
renderer: ColumnRegular['cellTemplate'],
options: ArrayRendererOptions = {}): Required['cellTemplate'];
```
---
### `multiRenderer`
The `multiRenderer` is a custom cell renderer for RevoGrid that can apply different renderers
based on the value type. It's similar to `arrayRenderer` but allows for more complex rendering logic.
**Features**:
- Detects if the value is an array or a single value
- For arrays, applies a different renderer for each item based on a condition
- For single values, applies a default renderer
- Supports custom separator between array items
- Supports custom wrapper for array items
**Usage**:
- Import `multiRenderer` and use it to define different renderers for different value types
- Configure the renderer with options like separator and wrapper
### Example
```typescript
import { multiRenderer } from '@revolist/revogrid-pro';
import { linkRenderer } from '@revolist/revogrid-pro';
import { progressLineRenderer } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'data',
name: 'Data',
cellTemplate: multiRenderer({
default: progressLineRenderer,
renderers: [
{
condition: (value) => typeof value === 'string' && value.startsWith('http'),
renderer: linkRenderer,
},
],
}),
},
];
```
```ts
export function multiRenderer(config: {
/**
* Default renderer to use if no condition matches
*/
default: ColumnRegular['cellTemplate'];
/**
* Array of renderers with conditions
*/
renderers: Array<{
/**
* Condition function that returns true if the renderer should be used
*/
condition: (value: any) => boolean;
/**
* Renderer to use if the condition is true
*/
renderer: ColumnRegular['cellTemplate'];
}>;
/**
* Separator between array items
* @default ', '
*/
separator?: string;
/**
* Wrapper element for array items
* @default 'div'
*/
wrapper?: string;
/**
* CSS class for the wrapper element
*/
wrapperClass?: string;
/**
* CSS style for the wrapper element
*/
wrapperStyle?: Record;
}): ColumnRegular['cellTemplate'];
```
---
### `ArrayRendererOptions`
```ts
export type ArrayRendererOptions = {
/**
* Separator between array items
* @default ', '
*/
separator?: string;
/**
* Wrapper element for array items
* @default 'div'
*/
wrapper?: string;
/**
* CSS class for the wrapper element
*/
wrapperClass?: string;
/**
* CSS style for the wrapper element
*/
wrapperStyle?: Record;
};
```
---
# Audit History
URL: https://pro.rv-grid.com/api/audit-history/
Source: src/content/docs/api/audit-history.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
auditHistory?: AuditHistoryConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* @deprecated Use the direct `grid.auditHistory` property instead.
*/
auditHistory?: AuditHistoryConfig
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
auditrecord: AuditRecord;
auditrecordsloaded: AuditRecord[];
auditerror: AuditHistoryError;
beforeauditrestore: AuditRestoreEvent;
auditrestore: AuditRestoreEvent
}
```
## Plugin API
### `createLocalStorageAuditHistoryAdapter`
```ts
export function createLocalStorageAuditHistoryAdapter(storageKey = DEFAULT_STORAGE_KEY): AuditHistoryStorageAdapter;
```
---
### `AuditHistoryPlugin`
#### Dependencies
- **Required** `EventManagerPlugin`: Requires EventManagerPlugin to receive normalized edit and paste events.
- **Auto-installed** `HistoryPlugin`: Auto-installs or reuses HistoryPlugin for undo/redo shortcuts and replay integration.
```ts
class AuditHistoryPlugin {
getRecords(filter?: AuditHistoryFilter): AuditRecord[];
getCellHistory(rowId: string | number, column: ColumnProp): AuditRecord[];
getRowHistory(rowId: string | number): AuditRecord[];
getRecord(id: string): AuditRecord | undefined;
getTransaction(transactionId: string): AuditRecord[];
getLastChange(rowId: string | number, column?: ColumnProp): AuditChange | undefined;
getStats(filter?: AuditHistoryFilter): AuditHistoryStats;
getRowIdentity(row: DataType | undefined, rowIndex: number, rowType: DimensionRows): string | undefined;
clear(): boolean;
replaceRecords(records: AuditRecord[], options: { merge?: boolean; persist?: boolean } = {}): AuditRecord[];
recordEvent(input: AuditRecordEventInput): AuditRecord | undefined;
createSnapshot(label: string, options: AuditSnapshotOptions = {}): AuditRecord | undefined;
restoreSnapshot(recordOrId: AuditRecord | string): boolean;
async refreshRecords(options: { merge?: boolean } = { merge: true }): Promise;
exportRecords(options: AuditHistoryExportOptions = {}): string;
canRestoreRecord(context: AuditHistoryRestoreContext): boolean;
restoreCell(change: AuditChange): boolean;
restoreRow(recordOrRowId: AuditRecord | string | number): boolean;
restoreTransaction(transactionId: string): boolean;
}
```
---
### `AuditUser`
```ts
export type AuditUser = {
id: string;
name?: string;
email?: string;
};
```
---
### `BuiltInAuditActionType`
```ts
export type BuiltInAuditActionType =
| 'cell-change'
| 'bulk-paste'
| 'row-added'
| 'row-deleted'
| 'row-restored'
| 'undo'
| 'redo'
| 'restore-cell'
| 'restore-row'
| 'restore-transaction'
| 'api-sync'
| 'formula-update'
| 'snapshot-created'
| 'snapshot-restored';
```
---
### `AuditActionType`
```ts
export type AuditActionType = BuiltInAuditActionType | (string & {});
```
---
### `AuditHistorySource`
```ts
export type AuditHistorySource =
| 'manual'
| 'paste'
| 'api'
| 'formula'
| 'restore'
| 'snapshot'
| 'system';
```
---
### `AuditRecordPresentation`
```ts
export type AuditRecordPresentation = {
source?: AuditHistorySource;
verb?: string;
targetLabel?: string;
detailLabel?: string;
rangeLabel?: string;
avatarLabel?: string;
avatarIndex?: number;
avatarColor?: string;
accent?: boolean;
};
```
---
### `AuditChangePresentation`
```ts
export type AuditChangePresentation = {
cellLabel?: string;
columnLabel?: string;
};
```
---
### `AuditChange`
```ts
export type AuditChange = {
id: string;
rowId?: string;
rowIndex?: number;
rowType: DimensionRows;
column?: ColumnProp;
oldValue?: unknown;
newValue?: unknown;
oldRow?: DataType;
newRow?: DataType;
metadata?: Record;
presentation?: AuditChangePresentation;
};
```
---
### `AuditRecord`
```ts
export type AuditRecord = {
id: string;
transactionId: string;
type: AuditActionType;
changedAt: string;
changedBy: AuditUser;
changes: AuditChange[];
metadata?: Record;
presentation?: AuditRecordPresentation;
};
```
---
### `AuditHistoryFilter`
```ts
export type AuditHistoryFilter = {
rowId?: string | number;
column?: ColumnProp;
userId?: string;
userEmail?: string;
actionType?: AuditActionType;
transactionId?: string;
dateFrom?: string | Date;
dateTo?: string | Date;
};
```
---
### `AuditHistoryStorageAdapter`
```ts
export type AuditHistoryStorageAdapter = {
load?: () => AuditRecord[] | Promise;
save?: (records: AuditRecord[]) => void | Promise;
append?: (record: AuditRecord, records: AuditRecord[]) => void | Promise;
clear?: () => void | Promise;
};
```
---
### `AuditHistoryError`
```ts
export type AuditHistoryError = {
phase:
| 'get-current-user'
| 'metadata'
| 'sanitize'
| 'storage-load'
| 'storage-save'
| 'storage-append'
| 'storage-clear'
| 'on-record'
| 'restore'
| 'export'
| 'replace-records'
| 'record-event'
| 'track-record'
| 'snapshot';
error: unknown;
record?: AuditRecord;
};
```
---
### `AuditHistoryRestoreContext`
```ts
export type AuditHistoryRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryStats`
```ts
export type AuditHistoryStats = {
totalRecords: number;
totalChanges: number;
firstChangedAt?: string;
lastChangedAt?: string;
byType: Partial>;
byUser: Record;
};
```
---
### `AuditHistoryExportFormat`
```ts
export type AuditHistoryExportFormat = 'json' | 'csv';
```
---
### `AuditHistoryExportOptions`
```ts
export type AuditHistoryExportOptions = {
format?: AuditHistoryExportFormat;
filter?: AuditHistoryFilter;
includeMetadata?: boolean;
};
```
---
### `AuditRecordTrackContext`
```ts
export type AuditRecordTrackContext = {
type: AuditActionType;
changes: AuditChange[];
source?: AuditHistorySource;
metadata?: Record;
record: AuditRecord;
event?: unknown;
};
```
---
### `AuditHistoryConfig`
```ts
export type AuditHistoryConfig = {
getCurrentUser?: () => AuditUser;
getMetadata?: (context: { type: AuditActionType; changes: AuditChange[] }) => Record | undefined;
rowIdProp?: string;
getRowId?: (
row: DataType,
context: { rowIndex: number; rowType: DimensionRows },
) => string | number | undefined;
sanitizeValue?: (
value: unknown,
context: {
rowId?: string;
rowIndex?: number;
rowType: DimensionRows;
column?: ColumnProp;
direction: 'old' | 'new';
},
) => unknown;
shouldTrackChange?: (
change: Omit,
context: { type: AuditActionType },
) => boolean;
shouldTrackRecord?: (context: AuditRecordTrackContext) => boolean;
trackedActionTypes?: AuditActionType[];
ignoredActionTypes?: AuditActionType[];
ignoredColumns?: ColumnProp[] | ((context: { column?: ColumnProp; rowType: DimensionRows; rowId?: string }) => boolean);
captureSourceUpdates?: boolean;
immutable?: boolean;
canRestore?: (context: AuditHistoryRestoreContext) => boolean;
storage?: 'memory' | 'localStorage' | 'custom';
storageKey?: string;
storageAdapter?: AuditHistoryStorageAdapter;
storageProviders?: AuditHistoryStorageAdapter[];
mergeStorageProviders?: boolean;
records?: AuditRecord[];
maxRecords?: number;
disabled?: boolean;
onAuditRecord?: (record: AuditRecord) => void | Promise;
onAuditError?: (error: AuditHistoryError) => void;
onRecordsLoaded?: (records: AuditRecord[]) => void;
};
```
---
### `AuditEventChangeInput` (Extended from `index.ts`)
```ts
export type AuditEventChangeInput = Omit & { id?: string };
```
---
### `AuditRecordEventInput`
```ts
export type AuditRecordEventInput = {
type: AuditActionType;
changes?: AuditEventChangeInput[];
changedAt?: string;
changedBy?: AuditUser;
transactionId?: string;
metadata?: Record;
presentation?: AuditRecordPresentation;
allowEmpty?: boolean;
};
```
---
### `AuditSnapshotOptions`
```ts
export type AuditSnapshotOptions = {
rowType?: DimensionRows;
rows?: DataType[];
metadata?: Record;
presentation?: AuditRecordPresentation;
};
```
---
### `AuditRestoreType`
```ts
export type AuditRestoreType = 'cell' | 'row' | 'transaction' | 'snapshot';
```
---
### `AuditRestoreEvent`
```ts
export type AuditRestoreEvent = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `defineAuditHistoryPanel`
Renders the Audit History panel into `el` for the given RevoGrid element.
The grid must have `AuditHistoryPlugin` installed.
```ts
export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions = {}): AuditHistoryPanelHandle;
```
---
### `AuditHistoryPanelDateFormatContext`
```ts
export type AuditHistoryPanelDateFormatContext = {
mode: 'time' | 'day' | 'filter';
record?: AuditRecord;
};
```
---
### `AuditHistoryPanelEvents`
```ts
export type AuditHistoryPanelEvents = {
beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void;
beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void;
beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void;
};
```
---
### `AuditHistoryPanelExportContext`
```ts
export type AuditHistoryPanelExportContext = {
format: AuditHistoryExportFormat;
};
```
---
### `AuditHistoryPanelJumpContext`
```ts
export type AuditHistoryPanelJumpContext = {
record: AuditRecord;
change: AuditChange;
};
```
---
### `AuditHistoryPanelLabels`
```ts
export type AuditHistoryPanelLabels = {
title: string;
liveStatus: string;
recordsSummary: (count: number) => string;
changesSummary: (count: number) => string;
allTab: string;
rowTab: string;
cellTab: string;
tableTab: string;
selectedCell: (rowId: string, column: ColumnProp) => string;
selectedRow: (rowId: string) => string;
export: string;
exportCsv: string;
exportJson: string;
exportCsvTitle: string;
exportJsonTitle: string;
close: string;
openPanel: string;
searchPlaceholder: string;
searchAriaLabel: string;
searchShortcut: string;
userPlaceholder: string;
userAriaLabel: string;
columnAriaLabel: string;
actionAriaLabel: string;
dateFromAriaLabel: string;
dateToAriaLabel: string;
allColumns: string;
allActions: string;
clearFilters: string;
addFilter: string;
removeFilter: (label: string) => string;
noRecords: string;
noComparableRecord: string;
scopedRecordCount: (visibleChanges: number, totalChanges: number) => string;
changedBy: (changedAt: string, user: string) => string;
rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string;
cellValues: (oldValue: string, newValue: string) => string;
rowSnapshotAvailable: string;
rowRemoved: string;
restoreCell: string;
restoreRow: string;
restoreTransaction: string;
restoreSnapshot: string;
jumpToCell: string;
revertAll: string;
moreActions: string;
compare: string;
compareTitle: string;
compareBefore: string;
compareAfter: string;
closeCompare: string;
compareEmpty: string;
viewChanges: (count: number) => string;
previousPage: string;
nextPage: string;
pageStatus: (currentPage: number, pageCount: number) => string;
dayToday: string;
dayYesterday: string;
sourceLabels: Record;
actions: Partial>;
actionVerbs: Partial>;
};
```
---
### `AuditHistoryPanelLabelsOptions` (Extended from `index.ts`)
```ts
export type AuditHistoryPanelLabelsOptions = Partial> & {
actions?: Partial>;
actionVerbs?: Partial>;
sourceLabels?: Partial>;
};
```
---
### `AuditHistoryPanelHandle`
```ts
export type AuditHistoryPanelHandle = {
destroy: () => void;
setPlacement: (placement: AuditHistoryPanelPlacement) => void;
refresh: () => Promise;
};
```
---
### `AuditHistoryPanelOptions`
```ts
export type AuditHistoryPanelOptions = {
pageSize?: number;
allowExport?: boolean;
allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean);
allowClose?: boolean;
miniOnClose?: boolean;
restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean);
formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string;
events?: AuditHistoryPanelEvents;
placement?: AuditHistoryPanelPlacement;
tooltips?: false | Partial>;
labels?: AuditHistoryPanelLabelsOptions;
};
```
---
### `AuditHistoryPanelRecordContext`
```ts
export type AuditHistoryPanelRecordContext = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `AuditHistoryPanelRestoreContext`
```ts
export type AuditHistoryPanelRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryPanelPlacement`
```ts
export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none';
```
---
### `AuditHistoryPanelTooltipKey`
```ts
export type AuditHistoryPanelTooltipKey =
| 'totalRecords'
| 'totalChanges'
| 'selectedScope'
| 'searchFilter'
| 'userFilter'
| 'columnFilter'
| 'actionFilter'
| 'dateFromFilter'
| 'dateToFilter'
| 'clearFilters';
```
---
### `AUDIT_HISTORY_STORAGE_KEY`
```ts
AUDIT_HISTORY_STORAGE_KEY: string;
```
---
### `cloneValue`
```ts
export function cloneValue(value: T): T;
```
---
### `cloneChange`
```ts
export function cloneChange(change: AuditChange): AuditChange;
```
---
### `cloneRecord`
```ts
export function cloneRecord(record: AuditRecord): AuditRecord;
```
---
### `normalizeRowId`
```ts
export function normalizeRowId(rowId: string | number | undefined): string | undefined;
```
---
### `toTime`
```ts
export function toTime(value: string | Date | undefined, fallback: number);
```
---
### `shouldIncludeRecord`
```ts
export function shouldIncludeRecord(record: AuditRecord, filter?: AuditHistoryFilter);
```
---
### `trimRecords`
```ts
export function trimRecords(records: AuditRecord[], maxRecords: number): AuditRecord[];
```
---
### `mergeRecords`
```ts
export function mergeRecords(current: AuditRecord[], next: AuditRecord[]): AuditRecord[];
```
---
### `createAuditStats`
```ts
export function createAuditStats(records: AuditRecord[], filter?: AuditHistoryFilter): AuditHistoryStats;
```
---
### `stringifyExportValue`
```ts
export function stringifyExportValue(value: unknown): string;
```
---
### `exportAuditRecords`
```ts
export function exportAuditRecords(records: AuditRecord[], options: AuditHistoryExportOptions = {}): string;
```
---
# Audit History Panel
URL: https://pro.rv-grid.com/api/audit-history-panel/
Source: src/content/docs/api/audit-history-panel.md
### `createAuditHistoryPanelDock`
```ts
export function createAuditHistoryPanelDock(
panel: HTMLElement,
grid: HTMLRevoGridElement,
placement: AuditHistoryPanelPlacement,
);
```
---
### `defineAuditHistoryPanel`
Renders the Audit History panel into `el` for the given RevoGrid element.
The grid must have `AuditHistoryPlugin` installed.
```ts
export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions = {}): AuditHistoryPanelHandle;
```
---
### `AuditHistoryPanelDateFormatContext`
```ts
export type AuditHistoryPanelDateFormatContext = {
mode: 'time' | 'day' | 'filter';
record?: AuditRecord;
};
```
---
### `AuditHistoryPanelEvents`
```ts
export type AuditHistoryPanelEvents = {
beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void;
beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void;
beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void;
};
```
---
### `AuditHistoryPanelExportContext`
```ts
export type AuditHistoryPanelExportContext = {
format: AuditHistoryExportFormat;
};
```
---
### `AuditHistoryPanelJumpContext`
```ts
export type AuditHistoryPanelJumpContext = {
record: AuditRecord;
change: AuditChange;
};
```
---
### `AuditHistoryPanelLabels`
```ts
export type AuditHistoryPanelLabels = {
title: string;
liveStatus: string;
recordsSummary: (count: number) => string;
changesSummary: (count: number) => string;
allTab: string;
rowTab: string;
cellTab: string;
tableTab: string;
selectedCell: (rowId: string, column: ColumnProp) => string;
selectedRow: (rowId: string) => string;
export: string;
exportCsv: string;
exportJson: string;
exportCsvTitle: string;
exportJsonTitle: string;
close: string;
openPanel: string;
searchPlaceholder: string;
searchAriaLabel: string;
searchShortcut: string;
userPlaceholder: string;
userAriaLabel: string;
columnAriaLabel: string;
actionAriaLabel: string;
dateFromAriaLabel: string;
dateToAriaLabel: string;
allColumns: string;
allActions: string;
clearFilters: string;
addFilter: string;
removeFilter: (label: string) => string;
noRecords: string;
noComparableRecord: string;
scopedRecordCount: (visibleChanges: number, totalChanges: number) => string;
changedBy: (changedAt: string, user: string) => string;
rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string;
cellValues: (oldValue: string, newValue: string) => string;
rowSnapshotAvailable: string;
rowRemoved: string;
restoreCell: string;
restoreRow: string;
restoreTransaction: string;
restoreSnapshot: string;
jumpToCell: string;
revertAll: string;
moreActions: string;
compare: string;
compareTitle: string;
compareBefore: string;
compareAfter: string;
closeCompare: string;
compareEmpty: string;
viewChanges: (count: number) => string;
previousPage: string;
nextPage: string;
pageStatus: (currentPage: number, pageCount: number) => string;
dayToday: string;
dayYesterday: string;
sourceLabels: Record;
actions: Partial>;
actionVerbs: Partial>;
};
```
---
### `AuditHistoryPanelLabelsOptions` (Extended from `index.tsx`)
```ts
export type AuditHistoryPanelLabelsOptions = Partial> & {
actions?: Partial>;
actionVerbs?: Partial>;
sourceLabels?: Partial>;
};
```
---
### `AuditHistoryPanelHandle`
```ts
export type AuditHistoryPanelHandle = {
destroy: () => void;
setPlacement: (placement: AuditHistoryPanelPlacement) => void;
refresh: () => Promise;
};
```
---
### `AuditHistoryPanelOptions`
```ts
export type AuditHistoryPanelOptions = {
pageSize?: number;
allowExport?: boolean;
allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean);
allowClose?: boolean;
miniOnClose?: boolean;
restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean);
formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string;
events?: AuditHistoryPanelEvents;
placement?: AuditHistoryPanelPlacement;
tooltips?: false | Partial>;
labels?: AuditHistoryPanelLabelsOptions;
};
```
---
### `AuditHistoryPanelRecordContext`
```ts
export type AuditHistoryPanelRecordContext = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `AuditHistoryPanelRestoreContext`
```ts
export type AuditHistoryPanelRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryPanelPlacement`
```ts
export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none';
```
---
### `AuditHistoryPanelTooltipKey`
```ts
export type AuditHistoryPanelTooltipKey =
| 'totalRecords'
| 'totalChanges'
| 'selectedScope'
| 'searchFilter'
| 'userFilter'
| 'columnFilter'
| 'actionFilter'
| 'dateFromFilter'
| 'dateToFilter'
| 'clearFilters';
```
---
### `resolveLabels`
```ts
export function resolveLabels(labels?: AuditHistoryPanelLabelsOptions): AuditHistoryPanelLabels;
```
---
### `getAuditActionLabel`
```ts
export function getAuditActionLabel(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string;
```
---
### `getAuditActionVerb`
```ts
export function getAuditActionVerb(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string;
```
---
### `actionTypes`
```ts
actionTypes: AuditActionType[];
```
---
### `titleizeActionType`
```ts
titleizeActionType: (value: string) => string;
```
---
### `createInitialFilters`
```ts
export function createInitialFilters(): AuditPanelFilters;
```
---
### `stringifyValue`
```ts
export function stringifyValue(value: unknown): string;
```
---
### `formatTime`
```ts
export function formatTime(value: string, mode: 'compact' | 'day' = 'compact');
```
---
### `formatPanelDate`
```ts
export function formatPanelDate(
value: string,
options: AuditHistoryPanelOptions,
context: { mode: 'time' | 'day' | 'filter'; record?: AuditRecord },
);
```
---
### `getUserLabel`
```ts
export function getUserLabel(record: AuditRecord);
```
---
### `getAvatarLabel`
```ts
export function getAvatarLabel(record: AuditRecord);
```
---
### `matchesFilters`
```ts
export function matchesFilters(record: AuditRecord, filters: AuditPanelFilters);
```
---
### `getScopedChanges`
```ts
export function getScopedChanges(record: AuditRecord, tab: AuditPanelTab, selected: SelectedAuditTarget): AuditChange[];
```
---
### `getRecordSource`
```ts
export function getRecordSource(type: AuditActionType, record?: AuditRecord): AuditHistorySource;
```
---
### `getTargetLabel`
```ts
export function getTargetLabel(record: AuditRecord, changes: AuditChange[], labels: AuditHistoryPanelLabels);
```
---
### `getDetailLabel`
```ts
export function getDetailLabel(record: AuditRecord, changes: AuditChange[]);
```
---
### `isComparable`
```ts
export function isComparable(record: AuditRecord, changes: AuditChange[]);
```
---
### `toTimelineRecord`
```ts
export function toTimelineRecord(
record: AuditRecord,
changes: AuditChange[],
labels: AuditHistoryPanelLabels,
options: AuditHistoryPanelOptions = {},
): TimelineRecord;
```
---
### `groupTimelineRecords`
```ts
export function groupTimelineRecords(records: TimelineRecord[]): TimelineGroup[];
```
---
### `getPlugin`
```ts
export async function getPlugin(grid: HTMLRevoGridElement): Promise;
```
---
### `panelRefreshEvents`
```ts
panelRefreshEvents: readonly ["auditrecord", "auditrecordsloaded", "auditrestore"];
```
---
### `canSelectRecord`
```ts
export function canSelectRecord(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext);
```
---
### `canOpenCompare`
```ts
export function canOpenCompare(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext);
```
---
### `canJumpToCell`
```ts
export function canJumpToCell(options: AuditHistoryPanelOptions, context: AuditHistoryPanelJumpContext);
```
---
### `canUseRestoreAction`
```ts
export function canUseRestoreAction(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRestoreContext);
```
---
### `canExportAuditHistory`
```ts
export function canExportAuditHistory(options: AuditHistoryPanelOptions, format: AuditHistoryExportFormat);
```
---
### `AuditHistoryTooltip`
```ts
export function AuditHistoryTooltip({ options, tooltipKey }: { options: AuditHistoryPanelOptions; tooltipKey: AuditHistoryPanelTooltipKey });
```
---
### `AuditPanelTab`
```ts
export type AuditPanelTab = 'cell' | 'row' | 'table';
```
---
### `SelectedAuditTarget`
```ts
export type SelectedAuditTarget = {
rowId?: string;
column?: ColumnProp;
columnLabel?: ColumnProp;
rowIndex?: number;
rowType?: DimensionRows;
};
```
---
### `AuditPanelFilters`
```ts
export type AuditPanelFilters = {
search: string;
user: string;
column: string;
actionType: '' | AuditActionType;
dateFrom: string;
dateTo: string;
};
```
---
### `AuditHistoryPluginRuntime`
```ts
export type AuditHistoryPluginRuntime = {
getRecords: () => AuditRecord[];
getCellHistory: (rowId: string, column: ColumnProp) => AuditRecord[];
getRowHistory: (rowId: string) => AuditRecord[];
getStats: () => AuditHistoryStats;
exportRecords: (options?: AuditHistoryExportOptions) => string;
getRowIdentity: (row: DataType | undefined, rowIndex: number, rowType: DimensionRows) => string | undefined;
canRestoreRecord: (context: { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string }) => boolean;
restoreCell: (change: AuditChange) => boolean;
restoreRow: (record: AuditRecord) => boolean;
restoreTransaction: (transactionId: string) => boolean;
restoreSnapshot?: (recordOrId: AuditRecord | string) => boolean;
recordEvent?: (input: AuditRecordEventInput) => AuditRecord | undefined;
createSnapshot?: (label: string, options?: AuditSnapshotOptions) => AuditRecord | undefined;
};
```
---
### `VisibleAuditRecord`
```ts
export type VisibleAuditRecord = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `TimelineRecord` (Extended from `types.ts`)
```ts
export type TimelineRecord = VisibleAuditRecord & {
id: string;
source: AuditHistorySource;
verb: string;
userLabel: string;
avatarLabel: string;
avatarIndex?: number;
avatarColor?: string;
targetLabel: string;
detailLabel?: string;
rangeLabel?: string;
changedAtLabel: string;
dayKey: string;
dayLabel: string;
daySubLabel?: string;
accent: boolean;
comparable: boolean;
};
```
---
### `TimelineGroup`
```ts
export type TimelineGroup = {
key: string;
label: string;
subLabel?: string;
records: TimelineRecord[];
};
```
---
### `AuditHistoryPanel`
```ts
export function AuditHistoryPanel({
grid,
options = {},
refreshSignal = 0,
onClose,
}: {
grid: HTMLRevoGridElement;
options?: AuditHistoryPanelOptions;
refreshSignal?: number;
onClose?: () => void;
});
```
---
### `CompareDrawer`
```ts
export function CompareDrawer({ record, labels, onClose }: CompareDrawerProps);
```
---
### `Filters`
```ts
export function Filters({ filters, columns, actionTypes, labels, options, searchInputRef, onFiltersChange, onClearFilters }: FiltersProps);
```
---
### `MiniAuditHistoryPanel`
```ts
export function MiniAuditHistoryPanel({
grid,
options,
onOpen,
}: {
grid: HTMLRevoGridElement;
options: AuditHistoryPanelOptions;
onOpen: () => void;
});
```
---
### `Pagination`
```ts
export function Pagination({
visibleRecordCount,
pageSize,
currentPage,
pageCount,
labels,
onPageChange,
}: PaginationProps);
```
---
### `RecordsList`
```ts
export function RecordsList({
groups,
labels,
plugin,
selectedRecordId,
collapsingRecordId,
onSelectRecord,
onRestoreCell,
onRestoreRow,
onRestoreTransaction,
onRestoreSnapshot,
onJumpToChange,
canUseRestoreAction,
canJumpToChange,
}: RecordsListProps);
```
---
### `Toolbar`
```ts
export function Toolbar({
records,
stats,
selected,
tab,
tabCounts,
labels,
options,
onTabChange,
onExport,
onClose,
}: ToolbarProps);
```
---
# Autofill
URL: https://pro.rv-grid.com/api/autofill/
Source: src/content/docs/api/autofill.md
### `createDefaultAutoFillStrategies`
Creates the default AutoFill strategy list in evaluation order.
Strategies are ordered from specific to general, with copy fallback last. The
same list is used by AutoFillPlugin and AutoFillPreviewPlugin when no active
AutoFillPlugin instance can be resolved.
**@returns** Default AutoFill strategies.
```ts
export function createDefaultAutoFillStrategies(): AutoFillStrategy[];
```
---
### `AutoFillPlugin`
The `AutoFillPlugin` enhances RevoGrid with advanced autofill capabilities,
allowing users to automatically fill cell ranges based on various sequence
strategies. This plugin supports numeric, date, and text sequences, making it
versatile for different data types and user needs.
**Features**:
- Utilizes multiple pre-defined strategies for numbers, dates, time values,
text-number labels, weekday/month names, boolean cycles, and copy fallback.
- Listens for the `beforeautofill` to trigger autofill actions based on user
interactions.
- Allows registration of additional custom autofill strategies.
- Applies autofilled data directly to the grid, updating the data store efficiently.
**Usage**:
- Import `AutoFillPlugin` and add it to the RevoGrid's plugin list.
- The grid will automatically handle range selection and autofilling based on the
registered strategies.
### Example
```typescript
import { AutoFillPlugin } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin]; // Add autofill functionality to the grid
```
This plugin is ideal for enhancing user productivity in data entry tasks by
providing intelligent filling of repetitive or sequential data patterns, commonly
used in spreadsheets and data management applications.
```ts
class AutoFillPlugin {
/**
* Registers a custom autofill strategy.
*
* Custom strategies are inserted before the copy fallback strategy so they can
* override generic copy behavior.
*
* @param strategy Strategy function to register.
*/
registerStrategy(strategy: AutoFillStrategy);
/**
* Returns the active strategy list.
*
* The preview plugin reads this list from the active AutoFillPlugin instance so
* preview and final commit use identical strategy order.
*
* @returns Registered strategy list in evaluation order.
*/
getStrategies();
/**
* Register a post-processor shared by autofill preview and commit.
*
* The returned disposer removes only this registration, allowing integration
* plugins to detach without affecting host strategies or other transformers.
*/
registerResultTransformer(transformer: AutoFillResultTransformer): () => void;
/** Return active result transformers in registration order. */
getResultTransformers(): readonly AutoFillResultTransformer[];
}
```
---
### `computeAutoFillData`
```ts
export function computeAutoFillData({
oldRange,
newRange,
type,
colType,
providers,
strategies,
transformers = [],
}: AutoFillComputeOptions): AutoFillComputedData | null;
```
---
### `mapAutoFillMatrixToData`
```ts
export function mapAutoFillMatrixToData(
matrix: any[][],
target: RangeArea,
providers: Pick,
colType: DimensionCols,
): Record;
```
---
### `AutoFillComputedData`
```ts
export type AutoFillComputedData = {
matrix: any[][];
data: Record;
};
```
---
### `AutoFillComputeOptions` (Extended from `index.ts`)
```ts
export type AutoFillComputeOptions = Pick & {
type: DimensionRows;
colType: DimensionCols;
providers: PluginProviders;
strategies: AutoFillStrategy[];
transformers?: readonly AutoFillResultTransformer[];
};
```
---
### `createAutoFillCellLocationMatrix`
Build provider-backed locations for a normalized single-viewport range.
```ts
export function createAutoFillCellLocationMatrix(
range: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
): AutoFillCellLocation[][];
```
---
### `AutoFillCellLocation`
Provider-backed location of one cell participating in an autofill operation.
```ts
/** Provider-backed location of one cell participating in an autofill operation. */
export type AutoFillCellLocation = {
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
readonly rowIndex: number;
readonly colIndex: number;
};
```
---
### `AutoFillResultTransformerContext` (Extended from `index.ts`)
Context supplied after a strategy has produced its base autofill matrix.
```ts
/** Context supplied after a strategy has produced its base autofill matrix. */
export type AutoFillResultTransformerContext = AutoFillStrategyContext & {
selectedData: any[][];
sourceCells: readonly (readonly AutoFillCellLocation[])[];
targetCells: readonly (readonly (AutoFillCellLocation | undefined)[])[];
};
```
---
### `AutoFillResultTransformer`
Post-processes a strategy result without replacing strategy selection.
```ts
/** Post-processes a strategy result without replacing strategy selection. */
export type AutoFillResultTransformer = (
matrix: any[][],
context: AutoFillResultTransformerContext,
) => any[][];
```
---
### `AutoFillPreviewPlugin`
```ts
class AutoFillPreviewPlugin {
destroy();
}
```
---
### `AutoFillDirection`
Direction in which the autofill drag expands from the selected source range.
`both` is used when the source and target ranges cannot be reduced to a single
horizontal or vertical direction.
```ts
/**
* Direction in which the autofill drag expands from the selected source range.
*
* `both` is used when the source and target ranges cannot be reduced to a single
* horizontal or vertical direction.
*/
export type AutoFillDirection = 'up' | 'down' | 'left' | 'right' | 'both';
```
---
### `AutoFillStrategyContext`
Rich context passed to autofill strategies.
Existing custom strategies can keep using the legacy three-argument signature.
New strategies can read this object when they need selection metadata, drag
direction, dimension types, or plugin providers.
```ts
/**
* Rich context passed to autofill strategies.
*
* Existing custom strategies can keep using the legacy three-argument signature.
* New strategies can read this object when they need selection metadata, drag
* direction, dimension types, or plugin providers.
*/
export type AutoFillStrategyContext = {
/** Original selected/source range used as the autofill seed. */
oldRange: RangeArea;
/** Target range that will be previewed or committed. */
newRange: RangeArea;
/** Direction in which the target range expands from the source range. */
direction: AutoFillDirection;
/** Row dimension type for the active autofill operation. */
rowType: DimensionRows;
/** Column dimension type for the active autofill operation. */
colType: DimensionCols;
/** Plugin providers available to advanced strategy implementations. */
providers: PluginProviders;
/**
* Cross-viewport autofill metadata when a plugin maps one visual gesture to
* several viewport-scoped target ranges.
*/
crossViewport?: {
source: {
rowType: DimensionRows;
colType: DimensionCols;
range: RangeArea;
};
targets: {
rowType: DimensionRows;
colType: DimensionCols;
range: RangeArea;
}[];
};
};
```
---
### `AutoFillStrategy`
Autofill strategy function.
**@param** selectedData Matrix of selected source values, ordered row-major.
**@param** direction Direction in which the autofill drag expands.
**@param** newRange Target range that should be generated.
**@param** context Optional rich strategy context for advanced strategies.
**@returns** Generated target matrix, or `null` when the strategy does not support
the selected values.
**Example:**
```typescript
const copyStrategy: AutoFillStrategy = selectedData => selectedData;
AutoFillPlugin.registerStrategy(copyStrategy);
```
```ts
/**
* Autofill strategy function.
*
* @param selectedData Matrix of selected source values, ordered row-major.
* @param direction Direction in which the autofill drag expands.
* @param newRange Target range that should be generated.
* @param context Optional rich strategy context for advanced strategies.
* @returns Generated target matrix, or `null` when the strategy does not support
* the selected values.
*
* @example
* ```typescript
* const copyStrategy: AutoFillStrategy = selectedData => selectedData;
* AutoFillPlugin.registerStrategy(copyStrategy);
* ```
*/
export type AutoFillStrategy = (
selectedData: any[][],
direction: AutoFillDirection,
newRange: RangeArea,
context?: AutoFillStrategyContext,
) => any[][] | null;
```
---
### `getMatrixSize`
Calculates the row and column count for a RevoGrid range.
**@param** range Range to measure.
**@returns** Matrix size matching the inclusive range coordinates.
```ts
export function getMatrixSize(range: RangeArea): MatrixSize;
```
---
### `flattenMatrix`
Flattens a row-major matrix into a one-dimensional sequence.
**@param** data Matrix to flatten.
**@returns** Row-major sequence of values.
```ts
export function flattenMatrix(data: T[][]): T[];
```
---
### `createMatrix`
Creates a matrix for a target range.
**@param** range Target range that defines output dimensions.
**@param** getValue Callback invoked for each cell position.
**@returns** Generated matrix in row-major order.
```ts
export function createMatrix(
range: RangeArea,
getValue: (position: number, rowIndex: number, colIndex: number) => T,
): T[][];
```
---
### `repeatMatrix`
Tiles source values across the target range while preserving source rows and
columns.
**@param** selectedData Source matrix to repeat.
**@param** targetRange Target range that defines output dimensions.
**@returns** Generated matrix containing repeated source values.
```ts
export function repeatMatrix(selectedData: T[][], targetRange: RangeArea): T[][];
```
---
### `isNil`
Checks whether a value is `null` or `undefined`.
**@param** value Value to check.
**@returns** `true` when the value is missing.
```ts
export function isNil(value: unknown): value is null | undefined;
```
---
### `isNumericValue`
Checks whether a value can be treated as a finite number.
**@param** value Value to check.
**@returns** `true` for finite numbers and numeric strings.
```ts
export function isNumericValue(value: unknown): value is number | string;
```
---
### `parseTextNumber`
Parses the first integer segment inside a text value.
**@param** value Value to parse.
**@returns** Prefix, numeric value, suffix, and zero-padding width, or `null`
when the value does not contain an integer segment.
```ts
export function parseTextNumber(value: unknown);
```
---
### `formatTextNumber`
Formats a text value with a numeric segment and preserved padding.
**@param** prefix Text before the number.
**@param** value Number to format.
**@param** suffix Text after the number.
**@param** width Minimum digit width for zero padding.
**@returns** Formatted text-number value.
```ts
export function formatTextNumber(prefix: string, value: number, suffix: string, width: number);
```
---
### `parseDateValue`
Parses a Date object or supported date string.
**@param** value Value to parse.
**@returns** Parsed date and original format, or `null` for unsupported values.
```ts
export function parseDateValue(value: unknown): ParsedDateValue | null;
```
---
### `formatDateValue`
Formats a date using the preserved input format.
**@param** date Date to format.
**@param** format Output format captured from the selected source value.
**@returns** Date object copy or formatted date string.
```ts
export function formatDateValue(date: Date, format: ParsedDateValue['format']);
```
---
### `addUtcDays`
Adds a number of days using UTC date math.
**@param** date Source date.
**@param** days Number of days to add.
**@returns** New Date instance.
```ts
export function addUtcDays(date: Date, days: number);
```
---
### `addUtcMonths`
Adds a number of months using UTC date math.
**@param** date Source date.
**@param** months Number of months to add.
**@returns** New Date instance.
```ts
export function addUtcMonths(date: Date, months: number);
```
---
### `monthDiff`
Calculates whole-month distance between two UTC dates.
**@param** a Start date.
**@param** b End date.
**@returns** Number of calendar months from `a` to `b`.
```ts
export function monthDiff(a: Date, b: Date);
```
---
### `parseTimeValue`
Parses a supported clock-time string.
**@param** value Value to parse.
**@returns** Parsed seconds and original format, or `null` for unsupported values.
```ts
export function parseTimeValue(value: unknown): ParsedTimeValue | null;
```
---
### `formatTimeValue`
Formats seconds since midnight as a clock-time string.
**@param** totalSeconds Seconds since midnight. Values wrap around 24 hours.
**@param** format Output format captured from the selected source value.
**@returns** Formatted clock-time string.
```ts
export function formatTimeValue(totalSeconds: number, format: ParsedTimeValue['format']);
```
---
### `MatrixSize`
Size of a two-dimensional autofill matrix.
```ts
/**
* Size of a two-dimensional autofill matrix.
*/
export type MatrixSize = {
/** Number of rows in the target matrix. */
rows: number;
/** Number of columns in the target matrix. */
cols: number;
};
```
---
### `ParsedDateValue`
Parsed date value with the visible input format needed for output.
```ts
/**
* Parsed date value with the visible input format needed for output.
*/
export type ParsedDateValue = {
/** Parsed date normalized to a Date object. */
date: Date;
/** Original visible input format. */
format: 'date-object' | 'yyyy-mm-dd' | 'yyyy/mm/dd' | 'mm/dd/yyyy';
};
```
---
### `ParsedTimeValue`
Parsed clock-time value with the visible input format needed for output.
```ts
/**
* Parsed clock-time value with the visible input format needed for output.
*/
export type ParsedTimeValue = {
/** Seconds elapsed since midnight. */
seconds: number;
/** Original visible input format. */
format: 'hh:mm' | 'hh:mm:ss';
};
```
---
### `linearNumericSequenceStrategy`
The `linearNumericSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin designed to generate numeric sequences with a constant interval.
This strategy is ideal for filling grid cells with linear numeric progressions.
**Features**:
- Validates that all selected data elements are numeric, filtering out non-numeric
sequences to prevent errors.
- Computes the common difference between numbers in the sequence to establish a
consistent interval.
- Extends the numeric sequence across the specified range, maintaining the calculated
interval throughout the grid.
- Supports dynamic autofill in both horizontal and vertical directions.
**Usage**:
- Import `linearNumericSequenceStrategy` and register it with the AutoFillPlugin
for use in RevoGrid to enable linear numeric autofill functionality.
### Example
```typescript
import { linearNumericSequenceStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(linearNumericSequenceStrategy); // Register strategy
```
This strategy is particularly suited for applications requiring predictable numeric
data entry, such as financial models, inventory lists, or any scenario where
arithmetic progression is needed.
```ts
linearNumericSequenceStrategy: AutoFillStrategy;
```
---
### `dateSequenceStrategy`
The `dateSequenceStrategy` is an autofill strategy designed for RevoGrid's
AutoFillPlugin, which facilitates the automatic generation of consistent date
sequences within grid cells. This strategy is used to extend a selected range
of date values either as Date objects or ISO date strings.
**Features**:
- Validates that all selected data elements are either Date objects or valid
ISO date strings, ensuring consistency in input types.
- Computes the common difference between consecutive dates to determine the
sequence's interval.
- Generates a new date sequence extending the original, maintaining the identified
interval across the target range specified.
- Supports output as either Date objects or 'YYYY-MM-DD' formatted strings,
depending on the input type.
**Usage**:
- Import `dateSequenceStrategy` and register it with the AutoFillPlugin for use
in RevoGrid to enable date autofill functionality.
### Example
```typescript
import { dateSequenceStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(dateSequenceStrategy); // Register date sequence strategy
```
This strategy is ideal for applications dealing with chronological data entry,
where users benefit from automatic date progression, such as scheduling tools
or timeline management systems.
```ts
dateSequenceStrategy: AutoFillStrategy;
```
---
### `dateSequenceWithIntervalStrategy`
The `dateSequenceWithIntervalStrategy` is an advanced autofill strategy for
RevoGrid's AutoFillPlugin that facilitates filling grids with date sequences
that maintain a consistent interval. It extends upon basic date sequence
strategies by using a defined interval between consecutive dates.
**Features**:
- Validates that input data consists of either Date objects or ISO date strings.
- Ensures consistency in date intervals across the sequence, filtering out
inconsistent sequences.
- Generates a date sequence extending beyond the initial selection, maintaining
the calculated interval throughout the target range.
- Supports output in both Date objects and 'YYYY-MM-DD' strings, based on input type.
**Usage**:
- Import `dateSequenceWithIntervalStrategy` and register it with the AutoFillPlugin
to enable interval-based date autofill in RevoGrid.
### Example
```typescript
import { dateSequenceWithIntervalStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(dateSequenceWithIntervalStrategy); // Register strategy
```
This strategy is particularly useful for applications that require consistent
scheduling or timeline management, where maintaining a regular interval between
dates is essential for data accuracy and usability.
```ts
dateSequenceWithIntervalStrategy: AutoFillStrategy;
```
---
### `textSequenceStrategy`
```ts
textSequenceStrategy: AutoFillStrategy;
```
---
### `copyFallbackStrategy`
The `copyFallbackStrategy` is the final default autofill strategy for
RevoGrid's AutoFillPlugin. It repeats the selected source values when no
sequence-specific strategy can safely infer a smarter progression.
**Features**:
- Repeats the selected matrix in row-major order across the target range.
- Supports plain text, numbers, dates, booleans, objects, and mixed values.
- Avoids surprising inferred sequences for ambiguous input.
- Provides deterministic preview and final apply data for every non-empty
selection.
**Usage**:
- Included by default as the last built-in strategy.
- Custom strategies registered through AutoFillPlugin are inserted before this
fallback so they can override copy behavior.
### Example
```typescript
import { copyFallbackStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(copyFallbackStrategy);
```
```ts
copyFallbackStrategy: AutoFillStrategy;
```
---
### `singleNumericStepStrategy`
The `singleNumericStepStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that expands a single numeric source value into a simple
incrementing numeric sequence.
**Features**:
- Activates only when exactly one selected value is numeric.
- Uses a default step of `+1`.
- Supports numeric strings and number values.
- Falls through for mixed, empty, or multi-value selections so more specific
strategies or the copy fallback can handle them.
**Usage**:
- Included by default before the copy fallback.
- Useful for spreadsheet-like fills such as `1` -> `1, 2, 3`.
### Example
```typescript
import { singleNumericStepStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(singleNumericStepStrategy);
```
```ts
singleNumericStepStrategy: AutoFillStrategy;
```
---
### `textNumberSequenceStrategy`
The `textNumberSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues strings containing an embedded number.
**Features**:
- Supports numeric suffixes, prefixes, and middle-number patterns.
- Preserves leading zero width, for example `INV-099` -> `INV-100`.
- Supports ordinal text such as `1st Floor`, `2nd Floor`, `3rd Floor`.
- Requires a consistent prefix/suffix shape and numeric interval.
- Falls through for ambiguous mixed text patterns.
**Usage**:
- Included by default before the legacy text suffix strategy.
- Use it for identifiers, labels, invoice numbers, and numbered locations.
### Example
```typescript
import { textNumberSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(textNumberSequenceStrategy);
```
```ts
textNumberSequenceStrategy: AutoFillStrategy;
```
---
### `weekdayMonthNameStrategy`
The `weekdayMonthNameStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues English weekday and month name sequences.
**Features**:
- Supports full names such as `Monday` and `January`.
- Supports three-letter names such as `Mon` and `Jan`.
- Preserves the selected full-name or short-name format.
- Wraps around calendar boundaries, for example `Sat, Sun` -> `Mon`.
- Falls through when weekday and month labels are mixed or ambiguous.
**Usage**:
- Included by default before generic text and copy strategies.
- Use it for spreadsheet-like calendar labels in planning grids.
### Example
```typescript
import { weekdayMonthNameStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(weekdayMonthNameStrategy);
```
```ts
weekdayMonthNameStrategy: AutoFillStrategy;
```
---
### `dateUnitSequenceStrategy`
The `dateUnitSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues date sequences using calendar-aware day and
month units.
**Features**:
- Supports Date objects and common date strings: `YYYY-MM-DD`, `YYYY/MM/DD`,
and `MM/DD/YYYY`.
- Preserves the selected input format in generated values.
- Infers daily, weekly, monthly, quarterly, and yearly progressions through
consistent day or month steps.
- Uses a one-day step for a single selected date.
- Falls through for invalid, mixed-format, or irregular date selections.
**Usage**:
- Included by default before legacy date strategies.
- Use it for scheduling, planning, and timeline grids where visible date
formatting should remain stable.
### Example
```typescript
import { dateUnitSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(dateUnitSequenceStrategy);
```
```ts
dateUnitSequenceStrategy: AutoFillStrategy;
```
---
### `timeSequenceStrategy`
The `timeSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues clock-time strings.
**Features**:
- Supports `HH:mm` and `HH:mm:ss` formats.
- Preserves the selected time format in generated values.
- Infers a consistent interval from multiple selected times.
- Uses a one-hour step for a single selected time.
- Wraps around 24-hour boundaries.
**Usage**:
- Included by default before generic text and copy strategies.
- Use it for scheduling grids, time slots, and recurring interval data.
### Example
```typescript
import { timeSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(timeSequenceStrategy);
```
```ts
timeSequenceStrategy: AutoFillStrategy;
```
---
### `booleanCycleStrategy`
The `booleanCycleStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that repeats boolean-like toggle values.
**Features**:
- Supports boolean values: `true` and `false`.
- Supports common string pairs: `true/false`, `yes/no`, and `on/off`.
- Preserves the original selected values while repeating the selected cycle.
- Requires both string pair values to be present before string cycling.
- Falls through for single string values so plain copy fallback can handle them.
**Usage**:
- Included by default before the generic copy fallback.
- Use it for toggles, status columns, and simple alternating flags.
### Example
```typescript
import { booleanCycleStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(booleanCycleStrategy);
```
```ts
booleanCycleStrategy: AutoFillStrategy;
```
---
# Avatar
URL: https://pro.rv-grid.com/api/avatar/
Source: src/content/docs/api/avatar.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
avatarIndexProp?: ColumnProp;
avatarLabelProp?: ColumnProp;
avatarProp?: ColumnProp;
avatarSize?: number;
rectangular?: boolean
}
```
## Plugin API
### `createAvatarElement`
Creates the shared avatar presentation for DOM-based overlays and plugins.
```ts
export function createAvatarElement(
document: Document,
options: AvatarTemplateOptions = {},
): HTMLSpanElement;
```
---
### `getAvatarCellBackground`
```ts
export function getAvatarCellBackground(index = 0);
```
---
### `getAvatarInitials`
```ts
export function getAvatarInitials(value: unknown);
```
---
### `getAvatarIndex`
```ts
export function getAvatarIndex(value: unknown);
```
---
### `avatarTemplate`
```ts
export function avatarTemplate(h: any, options: AvatarTemplateOptions = {});
```
---
### `AvatarTemplateOptions`
```ts
export type AvatarTemplateOptions = {
ariaLabel?: string;
className?: string;
index?: number;
initials?: string;
key?: string | number;
label?: string;
rectangular?: boolean;
size?: number;
style?: Record;
value?: unknown;
};
```
---
### `AVATAR_CELL_BACKGROUNDS`
```ts
AVATAR_CELL_BACKGROUNDS: readonly ["linear-gradient(135deg, #7dd3fc, #2563eb)", "linear-gradient(135deg, #facc15, #b45309)", "linear-gradient(135deg, #f9a8d4, #be185d)", "linear-gradient(135deg, #a5b4fc, #4f46e5)", "linear-gradient(135deg, #86efac, #15803d)", "linear-gradient(135deg, #fda4af, #be123c)", "linear-gradient(135deg, #c4b5fd, #7c3aed)", "linear-gradient(135deg, #fdba74, #c2410c)"];
```
---
### `avatarRenderer`
```ts
avatarRenderer: CellTemplate> | undefined;
```
---
### `avatarWithTextRenderer`
```ts
avatarWithTextRenderer: CellTemplate> | undefined;
```
---
# Buttons
URL: https://pro.rv-grid.com/api/buttons/
Source: src/content/docs/api/buttons.md
### `DeleteButton`
```ts
DeleteButton: ({ onClick, locale, }: { onClick: (e: MouseEvent) => void; locale?: { title?: string | undefined; ariaLabel?: string | undefined; } | undefined; }) => any;
```
---
# Calendar
URL: https://pro.rv-grid.com/api/calendar/
Source: src/content/docs/api/calendar.md
### `CalendarWeekStart`
```ts
export type CalendarWeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
```
---
### `CalendarView`
```ts
export type CalendarView = 'month' | 'year';
```
---
### `CalendarDateRange`
```ts
interface CalendarDateRange {
readonly from: ISODateString;
readonly to: ISODateString
}
```
---
### `CalendarTranslations`
All non-date text rendered by the shared calendar.
```ts
interface CalendarTranslations {
readonly previousMonth: string;
readonly nextMonth: string;
readonly previousYear: string;
readonly nextYear: string;
readonly chooseMonth: string;
readonly calendar: string;
readonly today: string;
readonly invalidSelection: string
}
```
---
### `CalendarDayContext`
```ts
interface CalendarDayContext {
readonly outside: boolean;
readonly disabled: boolean;
readonly selected: boolean;
readonly previewed: boolean;
readonly today: boolean
}
```
---
### `CalendarSelectionValidationContext`
```ts
interface CalendarSelectionValidationContext {
readonly month: ISODateString;
readonly view: CalendarView;
readonly selection?: CalendarDateRange;
readonly preview?: CalendarDateRange;
readonly referenceDate?: ISODateString
}
```
---
### `CalendarSelectionValidationResult`
```ts
export type CalendarSelectionValidationResult = boolean | string;
```
---
### `CalendarProps`
```ts
interface CalendarProps {
readonly month: ISODateString;
readonly focusedDate: ISODateString;
readonly referenceDate?: ISODateString;
readonly selection?: CalendarDateRange;
readonly preview?: CalendarDateRange;
readonly locale?: string;
readonly weekStartsOn?: CalendarWeekStart;
readonly minDate?: ISODateString;
readonly maxDate?: ISODateString;
readonly isDateDisabled?: (date: ISODateString) => boolean;
readonly showOutsideDays?: boolean;
readonly allowOutsideMonthSelection?: boolean;
readonly fixedWeeks?: boolean;
readonly showTodayButton?: boolean;
/** Hides the complete month/year heading and navigation row. */
readonly showHeader?: boolean;
/** Convenience switch for both previous and next actions. */
readonly showNavigation?: boolean;
readonly showPreviousButton?: boolean;
readonly showNextButton?: boolean;
readonly showMonthLabel?: boolean;
readonly showWeekdays?: boolean;
/** Allows the month/year heading to open a twelve-month overview. Defaults to true. */
readonly enableYearView?: boolean;
/** Available views. Defaults to month and year; supersedes `enableYearView`. */
readonly views?: readonly CalendarView[];
readonly defaultView?: CalendarView;
readonly isMonthDisabled?: (month: ISODateString) => boolean;
/** Overrides any subset of the exported English calendar translations. */
readonly translations?: Partial;
readonly className?: string;
readonly dayClassName?: string | ((date: ISODateString, context: CalendarDayContext) => string | undefined);
readonly renderDay?: (date: ISODateString, context: CalendarDayContext) => ComponentChildren;
readonly formatMonth?: (month: ISODateString, locale: string) => string;
readonly formatWeekday?: (date: ISODateString, locale: string) => string;
readonly formatDayLabel?: (date: ISODateString, locale: string) => string;
readonly onMonthChange: (month: ISODateString) => void;
readonly onFocusedDateChange: (date: ISODateString) => void;
readonly onSelect: (date: ISODateString) => void;
readonly onPreviewDateChange?: (date?: ISODateString) => void;
readonly validateSelection?: (
date: ISODateString,
context: CalendarSelectionValidationContext,
) => CalendarSelectionValidationResult;
readonly onInvalidSelection?: (reason: string, date: ISODateString) => void;
readonly onViewChange?: (view: CalendarView) => void
}
```
---
### `MoveCalendarFocusOptions`
```ts
interface MoveCalendarFocusOptions {
readonly date: ISODateString;
readonly key: string;
readonly shiftKey?: boolean;
readonly weekStartsOn: CalendarWeekStart;
readonly minDate?: ISODateString;
readonly maxDate?: ISODateString;
readonly isDisabled?: (date: ISODateString) => boolean
}
```
---
### `DEFAULT_CALENDAR_TRANSLATIONS`
Complete English fallback used when a consumer does not override independent calendar text.
```ts
DEFAULT_CALENDAR_TRANSLATIONS: Readonly;
```
---
### `createCalendarMonth`
```ts
export function createCalendarMonth(
month: ISODateString,
weekStartsOn: CalendarWeekStart,
fixedWeeks = true,
): readonly ISODateString[];
```
---
### `orderCalendarRange`
```ts
export function orderCalendarRange(from: ISODateString, to: ISODateString): CalendarDateRange;
```
---
### `isDateInCalendarRange`
```ts
export function isDateInCalendarRange(date: ISODateString, range?: CalendarDateRange): boolean;
```
---
### `isCalendarDateDisabled`
```ts
export function isCalendarDateDisabled(
date: ISODateString,
options: Pick,
): boolean;
```
---
### `isCalendarMonthDisabled`
```ts
export function isCalendarMonthDisabled(
month: ISODateString,
options: Pick & {
readonly isMonthDisabled?: (month: ISODateString) => boolean;
},
): boolean;
```
---
### `moveCalendarFocus`
```ts
export function moveCalendarFocus(options: MoveCalendarFocusOptions): ISODateString | undefined;
```
---
### `Calendar`
```ts
export function Calendar({
locale = 'en-US',
weekStartsOn = 1,
showOutsideDays = false,
allowOutsideMonthSelection = false,
fixedWeeks = true,
showTodayButton = false,
showHeader = true,
showNavigation = true,
showPreviousButton = showNavigation,
showNextButton = showNavigation,
showMonthLabel = true,
showWeekdays = true,
enableYearView = true,
translations = {},
formatMonth = defaultMonthFormat,
formatWeekday = defaultWeekdayFormat,
formatDayLabel = defaultDayLabel,
...props
}: CalendarProps);
```
---
### `CalendarHeader`
```ts
export function CalendarHeader({
label,
previousLabel,
nextLabel,
labelAction,
showLabel = true,
showPrevious = true,
showNext = true,
previousDisabled = false,
nextDisabled = false,
onPrevious,
onNext,
}: {
label: string;
previousLabel: string;
nextLabel: string;
labelAction?: { ariaLabel: string; onClick: () => void };
showLabel?: boolean;
showPrevious?: boolean;
showNext?: boolean;
previousDisabled?: boolean;
nextDisabled?: boolean;
onPrevious: () => void;
onNext: () => void;
});
```
---
### `CalendarWeekdays`
```ts
export function CalendarWeekdays({
dates,
locale,
format,
}: {
dates: readonly ISODateString[];
locale: string;
format: (date: ISODateString, locale: string) => string;
});
```
---
### `CalendarDay`
```ts
export function CalendarDay(props: CalendarDayProps);
```
---
### `CalendarDayProps`
```ts
interface CalendarDayProps {
readonly date: ISODateString;
readonly label: string;
readonly className?: string;
readonly outside: boolean;
readonly hidden: boolean;
readonly disabled: boolean;
readonly focused: boolean;
readonly selected: boolean;
readonly previewed: boolean;
readonly rangeStart: boolean;
readonly rangeEnd: boolean;
readonly previewStart: boolean;
readonly previewEnd: boolean;
readonly today: boolean;
readonly content?: ComponentChildren;
readonly setRef: (date: ISODateString, element: HTMLButtonElement | null) => void;
readonly onFocus: (date: ISODateString) => void;
readonly onPreview: (date?: ISODateString) => void;
readonly onKeyDown: (event: h.JSX.TargetedKeyboardEvent, date: ISODateString) => void;
readonly onSelect: (date: ISODateString) => void
}
```
---
### `CalendarMonthGrid`
```ts
export function CalendarMonthGrid({
dates,
month,
focusedDate,
referenceDate,
selection,
preview,
showOutsideDays,
allowOutsideMonthSelection,
dayClassName,
renderDay,
isDisabled,
formatDayLabel,
setDayRef,
onFocus,
onPreview,
onKeyDown,
onSelect,
}: {
dates: readonly ISODateString[];
month: ISODateString;
focusedDate: ISODateString;
referenceDate?: ISODateString;
selection?: CalendarDateRange;
preview?: CalendarDateRange;
showOutsideDays: boolean;
allowOutsideMonthSelection: boolean;
dayClassName?: string | ((date: ISODateString, context: CalendarDayContext) => string | undefined);
renderDay?: (date: ISODateString, context: CalendarDayContext) => ComponentChildren;
isDisabled: (date: ISODateString) => boolean;
formatDayLabel: (date: ISODateString) => string;
setDayRef: (date: ISODateString, element: HTMLButtonElement | null) => void;
onFocus: (date: ISODateString) => void;
onPreview: (date?: ISODateString) => void;
onKeyDown: (event: h.JSX.TargetedKeyboardEvent, date: ISODateString) => void;
onSelect: (date: ISODateString) => void;
});
```
---
### `CalendarYearGrid`
```ts
export function CalendarYearGrid({
month,
referenceDate,
minDate,
maxDate,
isMonthDisabled,
locale,
ariaLabel,
onSelect,
onEscape,
}: {
month: ISODateString;
referenceDate?: ISODateString;
minDate?: ISODateString;
maxDate?: ISODateString;
isMonthDisabled?: (month: ISODateString) => boolean;
locale: string;
ariaLabel: string;
onSelect: (month: ISODateString) => void;
onEscape: () => void;
});
```
---
# Calendars
URL: https://pro.rv-grid.com/api/calendars/
Source: src/content/docs/api/calendars.md
### `calendarKey`
```ts
export function calendarKey(calendarId: CalendarId): string;
```
---
### `normalizeIsoWorkingDays`
```ts
export function normalizeIsoWorkingDays(
days: readonly number[] | undefined,
fallback: readonly number[] = DEFAULT_CALENDAR.workingDays,
): readonly number[];
```
---
### `jsWeekdayFromIsoWeekday`
```ts
export function jsWeekdayFromIsoWeekday(day: number): number;
```
---
### `jsWeekdaysFromIsoWeekdays`
```ts
export function jsWeekdaysFromIsoWeekdays(days: readonly number[]): readonly number[];
```
---
### `normalizeCalendarHolidaySet`
```ts
export function normalizeCalendarHolidaySet(holidays: readonly ISODateString[] | undefined): ReadonlySet;
```
---
### `normalizeCalendarWorkingHours`
```ts
export function normalizeCalendarWorkingHours(
workingHours: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[] | undefined,
options: { readonly errorPrefix?: string } = {},
): readonly CalendarResolvedWorkingTimeRange[];
```
---
### `normalizeCalendar`
```ts
export function normalizeCalendar(
calendar: CalendarEntity,
options: { readonly workingHoursErrorPrefix?: string } = {},
): ResolvedCalendarEntity;
```
---
### `createResolvedCalendarMap`
```ts
export function createResolvedCalendarMap(
calendars: readonly CalendarEntity[] | undefined,
options: { readonly workingHoursErrorPrefix?: string } = {},
): Map;
```
---
### `createCalendarMap`
```ts
export function createCalendarMap(calendars: readonly CalendarEntity[] | undefined): Map;
```
---
### `resolveCalendarFromMap`
```ts
export function resolveCalendarFromMap(
target: CalendarLookupTarget,
calendarsById: ReadonlyMap,
primaryCalendar: CalendarEntity | null,
): CalendarEntity | null;
```
---
### `resolveCalendarFromList`
```ts
export function resolveCalendarFromList(
context: CalendarListLookupContext,
target?: CalendarLookupTarget,
): CalendarEntity | null;
```
---
### `resolveCalendarItemFromMap`
```ts
export function resolveCalendarItemFromMap(
target: CalendarLookupTarget,
items: ReadonlyMap,
): T | null;
```
---
### `isCalendarHoliday`
```ts
export function isCalendarHoliday(
date: ISODateString | string,
calendar: Pick | Pick | null | undefined,
): boolean;
```
---
### `isCalendarWorkingWeekday`
```ts
export function isCalendarWorkingWeekday(
weekday: number,
calendar: Pick | Pick | null | undefined,
): boolean;
```
---
### `isWorkingCalendarDate`
```ts
export function isWorkingCalendarDate(
date: ISODateString | string,
weekday: number,
calendar: (
Pick
| Pick
| null
| undefined
),
): boolean;
```
---
### `isCalendarWorkingHour`
```ts
export function isCalendarWorkingHour(
date: ISODateString | string,
calendar: CalendarEntity | ResolvedCalendarEntity | null | undefined,
): boolean;
```
---
### `CalendarId`
Unique identifier for a working calendar.
```ts
/** Unique identifier for a working calendar. */
export type CalendarId = string | number;
```
---
### `CalendarWorkingTimeRange`
Time-of-day working window for calendar-aware schedulers.
```ts
interface CalendarWorkingTimeRange {
/** Start time in `HH:mm` or AM/PM format. */
readonly start: string;
/** End time in `HH:mm` or AM/PM format. */
readonly end: string
}
```
---
### `CalendarResolvedWorkingTimeRange`
Parsed working time window expressed as minutes from midnight.
```ts
interface CalendarResolvedWorkingTimeRange {
readonly startMinutes: number;
readonly endMinutes: number
}
```
---
### `CalendarEntity`
Defines the working schedule for tasks, events, and resources.
Working days use ISO weekday numbers: 1 = Monday ... 7 = Sunday.
```ts
interface CalendarEntity {
/** Unique calendar identifier. */
readonly id: CalendarId;
/** Display name, for example `"Standard"` or `"Night Shift"`. */
readonly name: string;
/** IANA time zone, for example `"America/New_York"`. */
readonly timeZone: string;
/** ISO weekday numbers that are working days. */
readonly workingDays: readonly number[];
/** Specific dates that are non-working regardless of weekday. */
readonly holidays: readonly ISODateString[];
/** Optional working time windows inside a working day. */
readonly workingHours?: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[];
/** Number of working hours per day, used for effort/capacity calculations. */
readonly hoursPerDay: number
}
```
---
### `DEFAULT_CALENDAR`
Default Monday-Friday calendar shared by Gantt and Event Scheduler.
```ts
DEFAULT_CALENDAR: {
id: string;
name: string;
timeZone: string;
workingDays: number[];
holidays: never[];
workingHours: { start: string; end: string; };
hoursPerDay: number;
};
```
---
### `ResolvedCalendarEntity`
Calendar with derived lookup-friendly fields used by schedulers.
```ts
interface ResolvedCalendarEntity {
readonly calendar: CalendarEntity;
readonly workingDays: readonly number[];
readonly workingHours: readonly CalendarResolvedWorkingTimeRange[];
readonly holidays: ReadonlySet
}
```
---
### `CalendarListLookupContext`
```ts
interface CalendarListLookupContext {
readonly calendars: readonly CalendarEntity[];
readonly primaryCalendarId?: CalendarId
}
```
---
### `CalendarLookupTarget`
```ts
export type CalendarLookupTarget = CalendarId | { readonly calendarId?: CalendarId } | undefined;
```
---
# Cell Flash
URL: https://pro.rv-grid.com/api/cell-flash/
Source: src/content/docs/api/cell-flash.md
## Module Extensions
### `CellTemplateProp` (Extended from `@revolist/revogrid`)
```ts
interface CellTemplateProp {
/**
* Previous value captured when the flash was requested.
*/
previousValue?: any;
/**
* Direction resolved by the flash plugin.
*/
flashDirection?: CellFlashDirection;
/**
* Active flash state for the rendered cell.
*/
flashState?: any
}
```
---
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* Enables flash feedback for this column.
*
* Legacy `(value) => boolean` predicates are supported. Context-aware
* predicates can inspect the previous value, row model, row type, and edit
* event metadata, and can return a decision object to customize a flash.
*
* @example
* ```ts
* const columns = [
* { prop: 'name', name: 'Name', flash: value => Boolean(value) },
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: value !== context.previousValue,
* rowFlash: true,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
flash?: CellFlashPredicate
}
```
---
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
/**
* Cell flash plugin configuration.
*
* @example
* ```ts
* const grid = document.querySelector('revo-grid');
* grid.plugins = [CellFlashPlugin, EventManagerPlugin];
* grid.cellFlash = {
* duration: 1200,
* mode: 'cell-and-row',
* queue: 'merge',
* };
* ```
*/
cellFlash?: CellFlashConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* Additional data property for cell flash configuration.
*
* Useful for framework wrappers where all plugin options are passed through
* one memoized or computed `additionalData` object.
*
* @example
* ```tsx
* const additionalData = useMemo(() => ({
* cellFlash: {
* duration: 900,
* mode: 'cell-and-row',
* },
* }), []);
* ```
*/
cellFlash?: CellFlashConfig
}
```
## Plugin API
### `CellFlashPlugin`
Highlights recently changed cells and rows.
Existing usage with `flash: (value) => true`, `FLASH_CELL_EVENT`, and
`cellFlashArrowTemplate()` remains supported. Configure richer behavior
through `grid.cellFlash` or call the instance methods for manual dashboard
updates.
**Example:**
```typescript
import { CellFlashPlugin, EventManagerPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [CellFlashPlugin, EventManagerPlugin];
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'price', name: 'Price', flash: value => value !== undefined },
];
```
**Example:**
```typescript
grid.cellFlash = {
mode: 'cell-and-row',
queue: 'merge',
duration: 900,
rowDuration: 1400,
aria: { enabled: true },
};
```
**Example:**
```typescript
grid.columns = [
{
prop: 'inventory',
name: 'Inventory',
flash: (value, context) => ({
flash: value !== context.previousValue,
rowFlash: Number(value) === 0,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
ariaLabel: `Inventory changed to ${value}`,
}),
},
];
```
#### Dependencies
- **Event integration** `EventManagerPlugin`: Integrates with EventManagerPlugin edit events for automatic edit flashes.
- **Event integration** `HistoryPlugin`: Integrates with HistoryPlugin undo and redo replay edit events when present.
- **Config integration** `additionalData.cellFlash`: Reads cell flash configuration from additionalData.cellFlash.
```ts
class CellFlashPlugin {
/**
* Flashes one cell manually.
*
* Use this for live-feed updates or external actions that do not come through
* RevoGrid edit events. The method keeps the old event-driven behavior intact
* and only applies an additional manual flash state.
*
* @param target Cell coordinates and optional flash metadata.
*
* @example
* ```ts
* const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
* plugin?.flashCell({
* rowIndex: 3,
* prop: 'price',
* previousValue: 42,
* value: 45,
* direction: 'up',
* duration: 800,
* });
* ```
*/
flashCell(target: CellFlashCellTarget);
/**
* Flashes a batch of changed cells using the same detail shape as the
* `flashcell` event.
*
* Column `flash` predicates are evaluated for each changed prop. Queue and
* mode options override the configured defaults for this call only.
*
* @param detail Batch detail containing changed values, row type, and optional previous values.
* @param options Optional queue, mode, and duration overrides for this batch.
*
* @example
* ```ts
* plugin.flashCells(
* {
* type: 'rgRow',
* data: { 0: { price: 45, status: 'Updated' } },
* previousData: { 0: { price: 42, status: 'Draft' } },
* eventTypes: ['live-feed'],
* },
* { queue: 'merge', mode: 'cell-and-row' },
* );
* ```
*
* @example
* ```ts
* grid.dispatchEvent(new CustomEvent('flashcell', {
* detail: {
* type: 'rgRow',
* data: { 1: { price: 50 } },
* previousData: { 1: { price: 48 } },
* },
* }));
* ```
*/
flashCells(detail: FlashBatchDetail, options?: CellFlashApplyOptions);
/**
* Flashes one or more rows manually.
*
* Row flashes are rendered through `beforerowrender`, so virtualized rows keep
* the correct visual state when they re-enter the viewport before expiry.
*
* @param targets One row target or an array of row targets.
* @param options Optional queue, mode, and row duration overrides.
*
* @example
* ```ts
* plugin.flashRows(
* [
* { rowIndex: 0, direction: 'changed' },
* { rowIndex: 4, direction: 'down', rowClassName: 'risk-row-flash' },
* ],
* { queue: 'merge', rowDuration: 1200 },
* );
* ```
*/
flashRows(targets: CellFlashRowTarget | CellFlashRowTarget[], options?: CellFlashApplyOptions);
/**
* Clears active flash state.
*
* Without a target, all active cell and row flashes are cleared. Passing a
* row target clears the row plus all active cell flashes in that row. Passing
* both `rowIndex` and `prop` clears one cell.
*
* @param target Optional cell or row target to clear.
*
* @example
* ```ts
* plugin.clearFlash(); // clear all flashes
* plugin.clearFlash({ rowIndex: 2 }); // clear row 2 and its cell flashes
* plugin.clearFlash({ rowIndex: 2, prop: 'price' }); // clear one cell
* ```
*/
clearFlash(target?: Partial);
destroy();
}
```
---
### `cellFlashArrowTemplate`
```ts
cellFlashArrowTemplate: (templateOrOptions?: CellTemplate> | CellFlashArrowTemplateOptions | undefined, templateOptions?: CellFlashArrowTemplateOptions | undefined) => CellTemplate> | undefined;
```
---
### `resolveCellFlashConfig`
Resolves partial public configuration into the plugin's internal defaults.
**Example:**
```typescript
const config = resolveCellFlashConfig({ duration: 1500, queue: 'merge' });
console.log(config.mode); // "cell"
```
```ts
export function resolveCellFlashConfig(
config?: CellFlashConfig,
): ResolvedCellFlashConfig;
```
---
### `CellFlashDirection`
Direction metadata attached to an active cell or row flash.
The default resolver returns `up` or `down` for numeric changes,
`changed` for non-numeric value changes, and `none` when no previous value
is available.
**Example:**
```typescript
const columns = [
{
prop: 'price',
name: 'Price',
flash: (value, context) => ({
flash: true,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
}),
},
];
```
```ts
/**
* Direction metadata attached to an active cell or row flash.
*
* The default resolver returns `up` or `down` for numeric changes,
* `changed` for non-numeric value changes, and `none` when no previous value
* is available.
*
* @example
* ```ts
* const columns = [
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: true,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
export type CellFlashDirection = 'up' | 'down' | 'changed' | 'none';
```
---
### `CellFlashMode`
Defines whether flash requests affect cells, rows, or both.
**Example:**
```typescript
grid.cellFlash = {
mode: 'cell-and-row',
rowDuration: 1400,
};
```
```ts
/**
* Defines whether flash requests affect cells, rows, or both.
*
* @example
* ```ts
* grid.cellFlash = {
* mode: 'cell-and-row',
* rowDuration: 1400,
* };
* ```
*/
export type CellFlashMode = 'cell' | 'row' | 'cell-and-row';
```
---
### `CellFlashQueueMode`
Controls how new flash requests interact with active flashes.
`replace` preserves the legacy behavior by clearing existing flashes before
applying a new batch. `merge` keeps existing flashes active until their own
timers expire.
**Example:**
```typescript
grid.cellFlash = {
queue: 'merge',
maxActive: 300,
};
```
```ts
/**
* Controls how new flash requests interact with active flashes.
*
* `replace` preserves the legacy behavior by clearing existing flashes before
* applying a new batch. `merge` keeps existing flashes active until their own
* timers expire.
*
* @example
* ```ts
* grid.cellFlash = {
* queue: 'merge',
* maxActive: 300,
* };
* ```
*/
export type CellFlashQueueMode = 'replace' | 'merge';
```
---
### `CellFlashContext`
Runtime information passed to context-aware column flash predicates and
direction resolvers.
**Example:**
```typescript
const columns = [
{
prop: 'status',
name: 'Status',
flash: (value, context) => {
return context.previousValue !== value && context.eventTypes?.includes('edit');
},
},
];
```
```ts
interface CellFlashContext {
/** New value from the edit/manual flash batch. */
value: unknown;
/** Previous value captured from the event detail or current row model. */
previousValue: unknown;
/** Row index in the target row type. */
rowIndex: number;
/** Column property for the changed cell. */
prop: ColumnProp;
/** Column definition for the changed cell. */
column: ColumnRegular;
/** Current row model, when available from the data provider. */
model?: DataType;
/** RevoGrid row bucket, for example `rgRow`, `rowPinStart`, or `rowPinEnd`. */
type: DimensionRows;
/** Edit/history event type metadata when emitted by EventManagerPlugin. */
eventTypes?: string[];
/** Original flash/edit event detail for advanced consumers. */
detail?: Partial
}
```
---
### `CellFlashDecision`
Per-change decision returned from a context-aware column `flash` predicate.
Returning `true` is equivalent to `{ flash: true }`. Return a decision object
to customize duration, direction, classes, row flashing, or accessibility
text for a specific value change.
**Example:**
```typescript
const columns = [
{
prop: 'inventory',
name: 'Inventory',
flash: (value, context) => ({
flash: true,
rowFlash: Number(value) === 0,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
className: Number(value) === 0 ? 'cell-flash-danger' : undefined,
ariaLabel: `Inventory changed to ${value}`,
}),
},
];
```
```ts
interface CellFlashDecision {
/** Whether the changed cell should flash. Defaults to true for decision objects. */
flash?: boolean;
/** Whether the containing row should flash for this change. */
rowFlash?: boolean;
/** Cell flash duration in milliseconds for this change. */
duration?: number;
/** Row flash duration in milliseconds for this change. */
rowDuration?: number;
/** Direction metadata for templates, attributes, and CSS selectors. */
direction?: CellFlashDirection;
/** Additional class applied to the flashing cell. */
className?: string;
/** Additional class applied to the flashing row. */
rowClassName?: string;
/** Message announced through the plugin aria-live region when enabled. */
ariaLabel?: string
}
```
---
### `CellFlashPredicate`
Column flash configuration.
Legacy predicates that only accept `(value) => boolean` still work. New
predicates may accept the second `CellFlashContext` argument and return a
`CellFlashDecision`.
**Example:**
```typescript
const columns = [
{ prop: 'name', name: 'Name', flash: true },
{
prop: 'price',
name: 'Price',
flash: (value, context) => ({
flash: value !== context.previousValue,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
}),
},
];
```
```ts
/**
* Column flash configuration.
*
* Legacy predicates that only accept `(value) = > boolean` still work. New
* predicates may accept the second `CellFlashContext` argument and return a
* `CellFlashDecision`.
*
* @example
* ```ts
* const columns = [
* { prop: 'name', name: 'Name', flash: true },
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: value !== context.previousValue,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
export type CellFlashPredicate =
| boolean
| ((value: unknown, context: CellFlashContext) => boolean | CellFlashDecision);
```
---
### `CellFlashAriaConfig`
Accessibility announcement configuration for cell and row flashes.
**Example:**
```typescript
grid.cellFlash = {
aria: {
enabled: true,
label: state => `Row ${state.rowIndex + 1} changed`,
},
};
```
```ts
interface CellFlashAriaConfig {
/** Enables aria-live announcements for flash requests. */
enabled?: boolean;
/** Live-region politeness. Defaults to `polite`. */
live?: 'off' | 'polite' | 'assertive';
/** Controls the `aria-atomic` attribute on the live region. Defaults to true. */
atomic?: boolean;
/** CSS class applied to the generated live region. */
liveRegionClassName?: string;
/** Static label or callback that returns the announcement text. */
label?: string | ((state: CellFlashState) => string)
}
```
---
### `CellFlashLabels`
User-facing labels used by the plugin.
These labels are used for default accessibility announcements. A decision or
manual target `ariaLabel` still has highest priority, followed by
`aria.label`, then these per-kind labels.
**Example:**
```typescript
grid.cellFlash = {
aria: true,
labels: {
cellUpdated: state => `Updated ${String(state.prop)} on row ${state.rowIndex + 1}`,
rowUpdated: state => `Updated row ${state.rowIndex + 1}`,
},
};
```
```ts
interface CellFlashLabels {
/** Default announcement for cell flashes. */
cellUpdated?: string | ((state: CellFlashState) => string);
/** Default announcement for row flashes. */
rowUpdated?: string | ((state: CellFlashState) => string)
}
```
---
### `CellFlashConfig`
Public plugin configuration accepted by `grid.cellFlash` and
`additionalData.cellFlash`.
Defaults preserve the original plugin behavior: flashing is enabled,
cell-only, lasts 1000ms, replaces active flashes on each event, and clears on
source reset.
**Example:**
```typescript
grid.plugins = [CellFlashPlugin, EventManagerPlugin];
grid.cellFlash = {
duration: 1200,
mode: 'cell-and-row',
queue: 'merge',
rowClassName: 'price-row-flash',
aria: { enabled: true },
labels: {
cellUpdated: state => `Price cell changed on row ${state.rowIndex + 1}`,
rowUpdated: state => `Market row ${state.rowIndex + 1} changed`,
},
directionResolver: ({ value, previousValue }) => {
if (Number(value) > Number(previousValue)) return 'up';
if (Number(value) < Number(previousValue)) return 'down';
return 'changed';
},
};
```
```ts
interface CellFlashConfig {
/** Enables or disables new flash requests. */
enabled?: boolean;
/** Default cell flash duration in milliseconds. */
duration?: number;
/** Default row flash duration in milliseconds. */
rowDuration?: number;
/** Default flash target mode. */
mode?: CellFlashMode;
/** Default active flash queue behavior. */
queue?: CellFlashQueueMode;
/** Maximum number of active cell and row flashes retained at once. */
maxActive?: number;
/** Clears active flash state when the grid source changes. */
clearOnSourceChange?: boolean;
/** Disables animation when the user prefers reduced motion. */
respectReducedMotion?: boolean;
/** Enables or configures aria-live announcements. */
aria?: boolean | CellFlashAriaConfig;
/** User-facing labels used by default plugin announcements. */
labels?: CellFlashLabels;
/** Base class applied to flashing cells. */
className?: string;
/** Base class applied to flashing rows. */
rowClassName?: string;
/** Optional custom direction resolver used before the default resolver. */
directionResolver?: (context: CellFlashContext) => CellFlashDirection
}
```
---
### `ResolvedCellFlashConfig`
```ts
interface ResolvedCellFlashConfig {
enabled: boolean;
duration: number;
rowDuration: number;
mode: CellFlashMode;
queue: CellFlashQueueMode;
maxActive: number;
clearOnSourceChange: boolean;
respectReducedMotion: boolean;
aria: Required>
& Pick;
labels: Required;
className: string;
rowClassName: string;
directionResolver?: (context: CellFlashContext) => CellFlashDirection
}
```
---
### `CellFlashState`
```ts
interface CellFlashState {
rowIndex: number;
prop?: ColumnProp;
type: DimensionRows;
value?: unknown;
previousValue?: unknown;
direction: CellFlashDirection;
className?: string;
rowClassName?: string;
ariaLabel?: string;
startedAt: number;
duration: number
}
```
---
### `CellFlashCellTarget`
Target accepted by `CellFlashPlugin.flashCell()`.
**Example:**
```typescript
const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
plugin?.flashCell({
rowIndex: 0,
prop: 'price',
previousValue: 42,
value: 47,
direction: 'up',
});
```
```ts
interface CellFlashCellTarget {
rowIndex: number;
prop: ColumnProp;
type?: DimensionRows;
value?: unknown;
previousValue?: unknown;
duration?: number;
direction?: CellFlashDirection;
className?: string;
ariaLabel?: string
}
```
---
### `CellFlashRowTarget`
Target accepted by `CellFlashPlugin.flashRows()`.
**Example:**
```typescript
const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
plugin?.flashRows([
{ rowIndex: 2, direction: 'changed', rowClassName: 'row-flash-warning' },
{ rowIndex: 5, direction: 'down' },
]);
```
```ts
interface CellFlashRowTarget {
rowIndex: number;
type?: DimensionRows;
duration?: number;
direction?: CellFlashDirection;
rowClassName?: string;
ariaLabel?: string
}
```
---
### `CellFlashApplyOptions`
Per-call options for manual and event-driven flash batches.
**Example:**
```typescript
plugin.flashCells(
{ type: 'rgRow', data: { 0: { price: 12 } }, previousData: { 0: { price: 10 } } },
{ queue: 'merge', mode: 'cell-and-row', duration: 800 },
);
```
```ts
interface CellFlashApplyOptions {
queue?: CellFlashQueueMode;
mode?: CellFlashMode;
duration?: number;
rowDuration?: number
}
```
---
### `CellFlashTemplatePropExtension`
```ts
interface CellFlashTemplatePropExtension {
previousValue?: any;
flashDirection?: CellFlashDirection;
flashState?: any
}
```
---
### `CellFlashArrowTemplateOptions`
Options for `cellFlashArrowTemplate()`.
Defaults preserve the original helper output: wrapper class `cell-flash`,
arrow class `cell-flash-arrow`, `up`/`down`/`changed` direction classes,
and `↑` / `↓` symbols.
**Example:**
```typescript
cellTemplate: cellFlashArrowTemplate({
symbols: { up: '+', down: '-', changed: '~' },
className: 'market-flash',
arrowClassName: 'market-flash-icon',
directionClassNames: { up: 'positive', down: 'negative' },
})
```
```ts
interface CellFlashArrowTemplateOptions {
/** Direction symbols rendered before the cell content. */
symbols?: Partial>;
/** Wrapper class applied around the arrow and cell content. */
className?: string;
/** Class applied to the symbol element. */
arrowClassName?: string;
/** Direction-specific wrapper classes. */
directionClassNames?: Partial>
}
```
---
### `CellFlashTemplateParams` (Extended from `index.ts`)
```ts
export type CellFlashTemplateParams = CellTemplateProp & CellFlashTemplatePropExtension;
```
---
# Cell Focus
URL: https://pro.rv-grid.com/api/cell-focus/
Source: src/content/docs/api/cell-focus.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* The focus function for the column.
*/
focus?: (detail: BeforeSaveDataDetails) => boolean
}
```
## Plugin API
### `CellColumnFocusVerifyPlugin`
The CellColumnFocusVerifyPlugin is a RevoGrid plugin that enables custom focus
verification logic for cells based on their column configurations. This plugin
intercepts the cell focus initialization event to apply additional validation rules
defined at the column level, ensuring that only cells meeting specific criteria
can receive focus.
**Key Features**:
- Listens to the `beforecellfocusinit` event, allowing for pre-focus validation.
- Utilizes column-level focus functions to determine whether a cell can be focused.
- Prevents default focus behavior if the column's focus condition is not satisfied,
enabling dynamic user interaction control based on custom logic.
**Usage**:
- Integrate this plugin into RevoGrid by adding it to the grid's plugin array.
- Define custom focus validation logic within the column definition using the `focus`
property, which should be a function that returns a boolean based on the event detail.
### Example
```ts
import { CellColumnFocusVerifyPlugin } from './cell-column-focus-verify';
const grid = document.createElement('revo-grid');
grid.plugins = [CellColumnFocusVerifyPlugin];
grid.columns = [
{
prop: 'num',
name: 'Numeric Column',
focus: (detail) => detail.value > 10, // Custom focus validation logic
},
];
```
```ts
class CellColumnFocusVerifyPlugin {}
```
---
# Cell Link
URL: https://pro.rv-grid.com/api/cell-link/
Source: src/content/docs/api/cell-link.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
link?: LinkConfig | any
}
```
## Plugin API
### `linkRenderer`
The `linkRenderer` is a custom cell editor for RevoGrid that provides a link input
to edit URL values directly within the grid cells.
**Features**:
- Renders a link element for cells, allowing users to click and navigate to URLs
- Supports different link configurations (target, styling, etc.)
- Extracts protocols from displayed text while preserving the full URL for navigation
- Automatically dispatches a `celledit` event upon change, updating the grid's data model
**Usage**:
- Import `linkRenderer` and assign it to the `cellTemplate` property of a column in the RevoGrid.
- Configure link behavior using the `linkConfig` property.
- Ideal for columns that contain URLs or links to external resources.
### Example
```typescript
import { linkRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'website',
name: 'Website',
cellTemplate: linkRenderer,
link: {
target: '_blank',
showProtocol: false,
className: 'custom-link',
},
},
];
```
**Event Handling**:
- The `linkRenderer` dispatches a `celledit` event whenever the link value changes.
- The event detail contains:
- `rgCol`: The column index of the edited cell.
- `rgRow`: The row index of the edited cell.
- `type`: The type of the cell.
- `prop`: The property of the cell being edited.
- `val`: The new URL value.
- `preventFocus`: A flag to control grid focus behavior (default: `false`).
This editor is particularly useful in scenarios where users need to view and interact with
URLs directly from the grid, providing a quick and user-friendly interface for link data.
```ts
linkRenderer: CellTemplate> | undefined;
```
---
# Cell Merge
URL: https://pro.rv-grid.com/api/cell-merge/
Source: src/content/docs/api/cell-merge.md
## Module Extensions
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* Additional data property for cell merge configuration.
*
* @deprecated Use `grid.cellMerge` instead.
* @example
* grid.additionalData = {
* cellMerge: [
* {
* row: 0,
* column: 0,
* rowSpan: 2,
* colSpan: 2,
* rowType: 'rgRow',
* colType: 'rgCol',
* }
* ]
* }
*/
cellMerge?: MergeData[]
}
```
---
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
cellMerge?: MergeData[];
'cell-merge'?: MergeData[]
}
```
## Plugin API
### `CellMergePlugin`
The `CellMergePlugin` is a RevoGrid plugin designed to manage and render merged cells within a grid.
It allows users to visually and functionally combine multiple adjacent cells into a single larger cell,
offering a more cohesive data presentation within the grid interface.
**Key Features**:
- Merging Logic: Extends the size of specified cells and hides cells covered by the merged area.
- Event Management: Overrides grid events and focus behavior to accommodate hidden cells due to merging.
- Dynamic Updates: Listens for data changes and adjusts merged configurations accordingly.
- UI Rendering: Adjusts cell sizes and parameters for merged cells during rendering.
**Usage**:
- This plugin should be included in the grid's plugin array to enable cell merging functionality.
- Configure the merge data through the grid's `cellMerge` property to define which cells should be merged.
### Example
```typescript
import { CellMergePlugin } from './cell-merge-plugin';
const grid = document.createElement('revo-grid');
grid.plugins = [CellMergePlugin]; // Add merge plugin
grid.cellMerge = [
{ row: 1, column: 1, rowSpan: 2, colSpan: 2 },
];
```
Events:
- Listens to a variety of events like `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, etc., to handle merge configurations and rendering.
#### Dependencies
- **Event integration** `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, `EDIT_RENDER_EVENT`: Integrates with range, focus, and edit overlay events to preserve merged-cell behavior.
- **Config integration** `grid.cellMerge`: Reads merge configuration from the grid cellMerge DOM property.
```ts
class CellMergePlugin {
destroy(): void;
}
```
---
### `MergeData`
Represents a range of cells that should be merged.
The coordinates are relative to the top-left cell of the range.
```ts
/**
* Represents a range of cells that should be merged.
* The coordinates are relative to the top-left cell of the range.
*/
export type MergeData = {
/**
* The row index of the top-left cell of the range.
*/
row: number;
/**
* The column index of the top-left cell of the range.
*/
column: number;
/**
* The number of rows to span. If not specified, the range will span only one row.
*/
rowSpan?: number;
/**
* The type of the row dimension. If not specified, it will default to 'rgRow'.
*/
rowType?: DimensionRows;
/**
* The number of columns to span. If not specified, the range will span only one column.
*/
colSpan?: number;
/**
* The type of the column dimension. If not specified, it will default to 'rgCol'.
*/
colType?: DimensionCols;
};
```
---
### `MergeRowCache`
```ts
export type MergeRowCache = Partial>;
```
---
### `MergeCache`
```ts
export type MergeCache = Partial>;
```
---
### `getCellMergeExcelRanges`
```ts
export function getCellMergeExcelRanges(
context: CellMergeExcelExportContext = {},
): CellMergeExcelRange[];
```
---
### `getCellMergeExcelProviderOptions`
```ts
export function getCellMergeExcelProviderOptions(
context: CellMergeExcelExportContext = {},
): CellMergeExcelProviderOptions;
```
---
### `applyCellMergeWriteExcelFileSpans`
```ts
export function applyCellMergeWriteExcelFileSpans({
cellRows,
inPlace,
...context
}: ApplyCellMergeWriteExcelFileSpansOptions): TCell[][];
```
---
### `CellMergeExcelSegment`
```ts
export type CellMergeExcelSegment = {
/**
* Segment start in the export matrix before the generated header offset is
* applied. Omit when segment lengths are supplied in grid order.
*/
start?: number;
/** Number of rows or columns in the segment. */
length?: number;
};
```
---
### `CellMergeExcelSegments`
```ts
export type CellMergeExcelSegments = Partial>;
```
---
### `CellMergeExcelExportContext`
```ts
export type CellMergeExcelExportContext = {
/** Explicit merge data. Wins over merge data read from `revogrid`. */
cellMerge?: MergeData[];
/** Optional grid element source for `cellMerge` or legacy `cell-merge`. */
revogrid?: Pick & Partial>;
/**
* Generated header rows before data rows. ExportExcel currently generates one
* header row, so this defaults to 1.
*/
headerRows?: number;
/**
* Row segment lengths or starts in export data-row order. Starts are counted
* after generated header rows; the helper adds `headerRows`.
*/
rowSegments?: CellMergeExcelSegments;
/**
* Column segment lengths or starts in export column order.
*/
columnSegments?: CellMergeExcelSegments;
};
```
---
### `CellMergeExcelRange`
```ts
export type CellMergeExcelRange = {
/** Zero-based start row in the provider matrix, including generated headers. */
startRow: number;
/** Zero-based start column in the provider matrix. */
startColumn: number;
/** Zero-based end row in the provider matrix, including generated headers. */
endRow: number;
/** Zero-based end column in the provider matrix. */
endColumn: number;
rowSpan: number;
columnSpan: number;
rowType: DimensionRows;
colType: DimensionCols;
source: MergeData;
};
```
---
### `CellMergeExcelProviderOptions`
```ts
export type CellMergeExcelProviderOptions = {
/** Generic provider merge ranges in zero-based export matrix coordinates. */
mergeRanges: CellMergeExcelRange[];
};
```
---
### `CellMergeWriteExcelFileCell`
```ts
export type CellMergeWriteExcelFileCell = Record | null | undefined;
```
---
### `ApplyCellMergeWriteExcelFileSpansOptions` (Extended from `index.ts`)
```ts
export type ApplyCellMergeWriteExcelFileSpansOptions =
CellMergeExcelExportContext & {
cellRows: TCell[][];
/** Mutate the supplied `cellRows` matrix instead of returning a patched copy. */
inPlace?: boolean;
};
```
---
### `MergeCacheService`
Service responsible for managing the cell merge cache.
This service handles all operations related to storing, retrieving, and manipulating
the cache of merged cells, separating these concerns from the main plugin logic.
```ts
class MergeCacheService {
/**
* Clears the entire merge cache
*/
public clearCache(): void;
/**
* Gets the current merge cache
*/
public getCache(): MergeCache;
/**
* Check cache for merged cells and return spans
*/
public getSpans(types: AllDimensionType, startX: number, startY: number);
/**
* High level mapping start
*/
public setMerge(data: MergeData[] | undefined);
}
```
---
# Cell Validate
URL: https://pro.rv-grid.com/api/cell-validate/
Source: src/content/docs/api/cell-validate.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
Adds validation properties to ColumnRegular.
```ts
interface ColumnRegular {
/**
* Optional property to enable soft validation, when set to true, the cell will be saved if it is invalid.
*/
softValidation?: boolean;
/**
* Optional function to provide validation message text for invalid cells.
* @param cellValue - The value of the cell.
* @param model - The full row model. During CellValidatePlugin edit validation,
* this includes the pending row patch for the edit being validated.
* @param column - The column definition that owns the validator.
* @returns The validation message for the cell.
*/
validationTooltip?(cellValue?: any, model?: any, column?: ColumnRegular): any;
/**
* Optional severity used by validation renderers for invalid cell styling.
* @param cellValue - The value of the cell.
* @param model - The full row model for the cell.
* @param column - The column definition that owns the validator.
* Defaults to `error`.
*/
validationSeverity?(cellValue?: any, model?: any, column?: ColumnRegular): ValidationSeverity;
/**
* Optional properties applied by validationRenderer when a cell is invalid.
*/
invalidProperties?: ColumnRegular['cellProperties'];
/**
* Optional function to validate the cell value.
* @param cellValue - The value of the cell.
* @param model - The full row model containing all column values. During
* CellValidatePlugin edit validation, this includes the pending row patch.
* @returns `true` if the value is valid, otherwise `false`.
*/
validate?(cellValue?: any, model?: any): boolean
}
```
## Plugin API
### `CellValidatePlugin`
The `CellValidatePlugin` is a RevoGrid plugin designed to enforce cell validation within
the grid, ensuring data integrity by preventing invalid data entries from being saved.
It integrates seamlessly with RevoGrid's event system and requires the `EventManagerPlugin`
for full functionality.
**Features**:
- Resolves validation before unified grid edits are published to normal edit consumers.
For row-header-owned batches and clipboard `valid-cells` mode, invalid cells are
removed and highlighted while valid cells continue. Other edit origins retain their
existing validation behavior.
The validator receives a candidate row model that includes the pending row patch, so
cross-column validation works for single-cell edits and pasted row batches.
- Adds visual indicators to invalid cells by tagging them during the `BEFORE_CELL_RENDER_EVENT`,
providing immediate feedback to users about validation errors.
- Integrates custom validation logic through column definitions, supporting both strict
and soft validation modes.
- Utilizes `validationRenderer` for rendering cells with validation feedback, allowing
customization of how invalid cells are displayed using tooltips or other UI elements.
**Usage**:
- Implement this plugin in a RevoGrid instance to enable cell validation logic and improve
data quality by preventing erroneous entries.
### Example
```ts
import { CellValidatePlugin } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [CellValidatePlugin];
```
#### Dependencies
- **Required** `EventManagerPlugin`: Requires EventManagerPlugin to receive edit events for validation.
```ts
class CellValidatePlugin {}
```
---
### `isValidationRendererFunction`
```ts
export function isValidationRendererFunction(
fn: T | undefined,
): fn is ValidationRendererFunction;
```
---
### `invalidCellProps`
```ts
export function invalidCellProps(invalid = false);
```
---
### `ValidationSeverity`
Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type.
```ts
/** Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type. */
export type ValidationSeverity = 'error' | 'warning' | 'info';
```
---
### `ValidationIndicatorPlacement`
Position of the invalid-cell indicator rendered by `validationRenderer`.
```ts
/** Position of the invalid-cell indicator rendered by `validationRenderer`. */
export type ValidationIndicatorPlacement = 'corner' | 'start' | 'end' | 'none';
```
---
### `ValidationMessagePlacement`
Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden.
```ts
/** Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden. */
export type ValidationMessagePlacement = 'tooltip' | 'inline' | 'both' | 'none';
```
---
### `ValidationRendererContext`
Runtime context passed to advanced validation renderer callbacks.
```ts
interface ValidationRendererContext {
/** Cell value passed by RevoGrid into the cell renderer. */
value: any;
/** Full row model for the rendered cell. */
model?: any;
/** Column definition being rendered. */
column: ColumnRegular;
/** Whether `column.validate(value, model)` failed for this cell. */
invalid: boolean;
/** Resolved severity for the invalid cell. */
severity: ValidationSeverity
}
```
---
### `ValidationRendererOptions`
Options for `validationRenderer`.
Existing `template` and `invalidProperties` usage remains supported. New options let
applications choose severity-aware styling, move the validation indicator, and render
messages as tooltips, inline text, both, or neither.
```ts
interface ValidationRendererOptions {
/** Existing cell template to wrap with validation feedback. */
template?: ColumnRegular['cellTemplate'];
/** Extra cell properties applied only when the cell is invalid. */
invalidProperties?: ColumnRegular['cellProperties'];
/** Static or callback-driven severity for invalid cell styling. */
severity?: ValidationSeverity | ((context: Omit) => ValidationSeverity);
/** Where to render the validation indicator. Defaults to `corner`. */
indicatorPlacement?: ValidationIndicatorPlacement;
/** Where to expose the validation message. Defaults to `tooltip`. */
messagePlacement?: ValidationMessagePlacement;
/** Optional message resolver. Falls back to `column.validationTooltip`. */
message?: (context: ValidationRendererContext) => any
}
```
---
### `validationRenderer`
Extends the `ColumnRegular` interface from the RevoGrid framework to include
properties and functions for validating cell data and rendering validation feedback.
It facilitates cell validation and provides visual indicators for invalid cells.
**Features**:
- Extends `ColumnRegular` with:
- `softValidation`: An optional boolean indicating if a cell should be saved even when invalid.
- `validationTooltip`: A function to provide validation message text for invalid cells.
- `validationSeverity`: A function to choose `error`, `warning`, or `info` styling per invalid cell.
- `validate`: A function to determine if a cell's value is valid.
- The `invalidCellProps` function returns cell properties indicating whether a cell is invalid.
- The `validationRenderer` function returns a renderer with:
- `cellProperties`: Adds validation styles and properties to cells, highlighting them when invalid.
- `cellTemplate`: Renders invalid-cell indicators and optional tooltip or inline messages.
**Usage**:
- Integrate these extensions into the RevoGrid column configuration to enable cell validation.
- Customize cell templates and properties using `validationRenderer` to enhance the grid's UI.
### Example
```typescript
import { validationRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'num',
name: 'Linear',
validate: (value) => value >= 0 && value <= 40,
validationTooltip: (value) => `Value ${value} is out of range`,
...validationRenderer({
severity: 'error',
indicatorPlacement: 'corner',
messagePlacement: 'tooltip',
}),
},
];
```
This module is crucial for applications requiring data validation, improving user experience
by providing immediate feedback for data entry errors within the RevoGrid framework.
```ts
validationRenderer: ({ template, invalidProperties, severity, indicatorPlacement, messagePlacement, message, }?: ValidationRendererOptions) => Pick>>, "cellTemplate" | "cellProperties">;
```
---
### `validateNumber`
Validates if the value is a number.
**@param** value - The value to validate.
**@returns** `true` if the value is a number, otherwise `false`.
```ts
export function validateNumber(value: any): boolean;
```
---
### `validateBoolean`
Validates if the value is a boolean.
**@param** value - The value to validate.
**@returns** `true` if the value is a boolean, otherwise `false`.
```ts
export function validateBoolean(value: any): boolean;
```
---
### `validateString`
Validates if the value is a string.
**@param** value - The value to validate.
**@returns** `true` if the value is a string, otherwise `false`.
```ts
export function validateString(value: any): boolean;
```
---
### `validateInteger`
Validates if the value is an integer.
**@param** value - The value to validate.
**@returns** `true` if the value is an integer, otherwise `false`.
```ts
export function validateInteger(value: any): boolean;
```
---
### `validateDecimal`
Validates if the value is a decimal.
**@param** value - The value to validate.
**@returns** `true` if the value is a decimal, otherwise `false`.
```ts
export function validateDecimal(value: any): boolean;
```
---
### `validateRange`
Validates if the value is within a specific range.
**@param** value - The value to validate.
**@param** min - The minimum value.
**@param** max - The maximum value.
**@returns** `true` if the value is within the range, otherwise `false`.
```ts
export function validateRange(value: any, min: number, max: number): boolean;
```
---
### `validateArray`
Validates if the value is an array.
**@param** value - The value to validate.
**@returns** `true` if the value is an array, otherwise `false`.
```ts
export function validateArray(value: any): boolean;
```
---
### `validateObject`
Validates if the value is an object.
**@param** value - The value to validate.
**@returns** `true` if the value is an object, otherwise `false`.
```ts
export function validateObject(value: any): boolean;
```
---
### `validateNull`
Validates if the value is null.
**@param** value - The value to validate.
**@returns** `true` if the value is null, otherwise `false`.
```ts
export function validateNull(value: any): boolean;
```
---
### `validateUndefined`
Validates if the value is undefined.
**@param** value - The value to validate.
**@returns** `true` if the value is undefined, otherwise `false`.
```ts
export function validateUndefined(value: any): boolean;
```
---
### `validateDate`
Validates if the value is a date.
**@param** value - The value to validate.
**@returns** `true` if the value is a date, otherwise `false`.
```ts
export function validateDate(value: any): boolean;
```
---
### `validateRegex`
Validates if the value matches the provided regular expression.
**@param** value - The value to validate.
**@param** regex - The regular expression to test against.
**@returns** `true` if the value matches the regular expression, otherwise `false`.
```ts
export function validateRegex(value: any, regex: RegExp): boolean;
```
---
### `validateEnum`
Validates if the value is one of the allowed values.
**@param** value - The value to validate.
**@param** allowedValues - The array of allowed values.
**@returns** `true` if the value is one of the allowed values, otherwise `false`.
```ts
export function validateEnum(value: any, allowedValues: any[]): boolean;
```
---
### `validatePositive`
Validates if the value is a positive number.
**@param** value - The value to validate.
**@returns** `true` if the value is a positive number, otherwise `false`.
```ts
export function validatePositive(value: any): boolean;
```
---
### `validateNegative`
Validates if the value is a negative number.
**@param** value - The value to validate.
**@returns** `true` if the value is a negative number, otherwise `false`.
```ts
export function validateNegative(value: any): boolean;
```
---
### `validateFinite`
Validates if the value is a finite number.
**@param** value - The value to validate.
**@returns** `true` if the value is a finite number, otherwise `false`.
```ts
export function validateFinite(value: any): boolean;
```
---
### `validateEmptyString`
Validates if the value is an empty string.
**@param** value - The value to validate.
**@returns** `true` if the value is an empty string, otherwise `false`.
```ts
export function validateEmptyString(value: any): boolean;
```
---
### `validateNonEmptyString`
Validates if the value is a non-empty string.
**@param** value - The value to validate.
**@returns** `true` if the value is a non-empty string, otherwise `false`.
```ts
export function validateNonEmptyString(value: any): boolean;
```
---
### `validateInstance`
Validates if the value is an instance of a class.
**@param** value - The value to validate.
**@param** cls - The class to test against.
**@returns** `true` if the value is an instance of the class, otherwise `false`.
```ts
export function validateInstance(value: any, cls: any): boolean;
```
---
### `parseBoolean`
Utilities used in the RevoGrid project to assist in converting
various data types to a boolean value. This function is essential for data processing
tasks where boolean values may be represented in different formats.
**Features**:
- Accepts inputs of type boolean, number, or string, and attempts to convert them to
a boolean value.
- For numbers, returns `true` if the value is `1`, otherwise `false`.
- For strings, it normalizes the input to lowercase and trims whitespace, returning
`true` for common affirmative representations ('true', 'yes', '1') and `false` for
negative representations ('false', 'no', '0').
- Returns `undefined` if the input cannot be reliably converted to a boolean, allowing
for error handling or default value assignment in calling code.
**Usage**:
- Call `parseBoolean` when you need a flexible utility to standardize various input
representations into a boolean format, particularly useful in grid configuration
or data validation scenarios.
### Example
```typescript
const isEnabled = parseBoolean('yes'); // Returns true
const isDisabled = parseBoolean(0); // Returns false
```
```ts
export function parseBoolean(value: any): boolean | undefined;
```
---
# Charts
URL: https://pro.rv-grid.com/api/charts/
Source: src/content/docs/api/charts.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* The position of the bars in the chart.
*/
barPosition?: 'top' | 'bottom';
/**
* Define badge styles for the cell.
*/
badgeStyles?: Record;
/**
* Whether to render the shape as a rectangular.
*/
rectangular?: boolean;
/**
* Maximum number of stars in the rating. Default is 5.
*/
maxStars?: number;
/**
* Minimum value for the progress line. Default is 0.
*/
minValue?: number;
/**
* Maximum value for the progress line. Default is 100.
*/
maxValue?: number;
/** Midpoint threshold used by custom three-stop heatmaps. */
midValue?: number;
/** Custom color at the heatmap minimum threshold. */
lowColor?: string;
/** Custom color at the heatmap midpoint threshold. */
midColor?: string;
/** Custom color at the heatmap maximum threshold. */
highColor?: string;
/** Legacy built-in heatmap palette used when custom colors are absent. */
colorMap?: 'heatmap' | 'coldmap';
/**
* Define thresholds for the cell.
*/
thresholds?: ThresholdConfig[];
/**
* Format the value of the cell.
*/
formatValue?: (value: number, column: ColumnRegular) => string;
/**
* Function to calculate progress based on row data.
* This allows connecting multiple fields to determine progress.
* @param config Configuration object containing model, data, and prop
* @returns A number between 0 and 100 representing progress
*/
progress?: (config: { model: DataType; data: DataType[]; prop: ColumnProp }) => number;
/** Visible timeline range. Invalid or inverted ranges fall back to 0–100. */
timelineRange?: { start: number; end: number };
/** Localized accessible name for a rendered timeline. */
timelineAriaLabel?: string | ((events: readonly TimelineEvent[]) => string);
/** Localized accessible name for a sparkline. */
sparklineAriaLabel?: string | ((values: readonly number[]) => string);
/** Localized accessible name for a rating visualization. */
ratingAriaLabel?: string | ((value: number, maximum: number) => string)
}
```
## Plugin API
### `progressLineRenderer`
The `progressLineRenderer` is a custom cell renderer for RevoGrid that provides a visual
representation of progress as a horizontal line within grid cells. This renderer effectively
communicates the completion level of a numeric value relative to a defined range.
**Features**:
- Displays a progress line that visually indicates the percentage completion within specified
minimum and maximum values.
- Shows a percentage label beside the progress bar, formatted to one decimal place for precision.
- Automatically normalizes values to ensure accurate representation within the defined range.
- Configurable through column properties such as `minValue` and `maxValue`.
- Supports a custom progress function to calculate progress based on row data.
**Usage**:
- Import `progressLineRenderer` and use it as the `cellTemplate` in your RevoGrid's column configuration.
- Specify `minValue` and `maxValue` for each column to define the range for progress calculation.
- Optionally provide a `progress` function to calculate progress based on row data.
### Example
```typescript
import { progressLineRenderer } from './charts/progress-line.renderer';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'score',
name: 'Completion Rate',
minValue: 0,
maxValue: 100,
cellTemplate: progressLineRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
},
];
```
This renderer is suitable for applications where tracking progression or percentage completion
is necessary, such as performance dashboards, goal tracking systems, or any scenario where a
visual progress indicator is beneficial.
```ts
progressLineRenderer: CellTemplate>;
```
---
### `renderProgressBar`
Shared framework-neutral progress primitive used by chart cell renderers.
```ts
export function renderProgressBar(h: ProgressHyperFunc, {
value,
min = 0,
max = 100,
ariaLabel,
className,
fillClass,
}: ProgressBarProps);
```
---
### `ProgressBarProps`
```ts
interface ProgressBarProps {
value: number;
min?: number;
max?: number;
ariaLabel?: string;
className?: string;
fillClass?: string | Record
}
```
---
### `progressBarRenderer`
Compact progress-only RevoGrid cell renderer.
```ts
progressBarRenderer: CellTemplate>;
```
---
### `progressLineWithValueRenderer`
```ts
progressLineWithValueRenderer: CellTemplate>;
```
---
### `sparklineRenderer`
```ts
sparklineRenderer: CellTemplate> | undefined;
```
---
### `barChartRenderer`
RevoGrid Renderer
Generates a bar chart visualization based on the provided data
values and column configuration.
```ts
barChartRenderer: (h: HyperFunc, { value, column, }: { value?: any; column: Partial>>; }) => VNode;
```
---
### `heatmapRenderer`
```ts
heatmapRenderer: CellTemplate> | undefined;
```
---
### `badgeRenderer`
RevoGrid Renderer
Renderers a badge cell with custom background color, text
Supports both badgeStyles and thresholds for styling
```ts
badgeRenderer: CellTemplate> | undefined;
```
---
### `ratingStarRenderer`
```ts
ratingStarRenderer: CellTemplate>;
```
---
### `formatTimelineEvents`
Converts valid Timeline events to the same readable text used by tooltips
and accessibility labels.
**@param** value Timeline event array; invalid entries are ignored.
**@param** separator Text inserted between valid event descriptions.
**@returns** A human-readable schedule, or an empty string when no event is valid.
**Example:**
```typescript
formatTimelineEvents([
{ start: 8, end: 12, label: 'Build' },
{ start: 14, end: 18, label: 'Review' },
], '; ');
// "Build: 8–12; Review: 14–18"
```
```ts
export function formatTimelineEvents(value: unknown, separator = ', '): string;
```
---
### `TimelineEvent`
```ts
interface TimelineEvent {
readonly start: number;
readonly end: number;
readonly label?: string
}
```
---
### `timelineRenderer`
```ts
timelineRenderer: CellTemplate> | undefined;
```
---
### `changeRenderer`
RevoGrid Renderer
Formats a numeric cell value with corresponding styles and icons based on whether the value
is positive, negative, or zero,
and exports a cell template renderer for a grid that utilizes this formatting.
```ts
changeRenderer: CellTemplate>;
```
---
### `thumbsRenderer`
```ts
thumbsRenderer: CellTemplate> | undefined;
```
---
### `renderColumnTypeIcon`
Helper to render icons based on the columnType.
**@param** h Hyperscript function.
**@param** columnType The column type to determine the icon.
**@returns** A VNode containing the corresponding SVG icon.
```ts
export function renderColumnTypeIcon(
h: HyperFunc,
columnType: string
);
```
---
### `columnTypeRenderer`
```ts
columnTypeRenderer: ColumnTemplateFunc | undefined;
```
---
### `PieData`
```ts
interface PieData {
value: number;
color?: string;
name?: string
}
```
---
### `pieChartRenderer`
```ts
pieChartRenderer: (h: HyperFunc, { value, column, }: { value?: number[] | PieData[] | undefined; column?: Partial, ColumnRegular>, ColumnProp>> | undefined; }) => VNode;
```
---
### `summaryHeaderRenderer`
```ts
summaryHeaderRenderer: (h: HyperFunc, summary: Record, column?: { maxItems?: number | undefined; }) => VNode;
```
---
### `summaryAggregateRenderer`
```ts
summaryAggregateRenderer: (h: HyperFunc, summary: Record, column?: { showSum?: boolean | undefined; showAvg?: boolean | undefined; }) => VNode;
```
---
### `avatarRenderer`
```ts
avatarRenderer: CellTemplate> | undefined;
```
---
### `avatarTemplate`
```ts
export function avatarTemplate(h: any, options: AvatarTemplateOptions = {});
```
---
### `avatarWithTextRenderer`
```ts
avatarWithTextRenderer: CellTemplate> | undefined;
```
---
### `thresholdRenderer`
The `thresholdRenderer` is a custom cell renderer for RevoGrid that applies CSS classes based on defined thresholds.
It uses the existing threshold system to apply classes to cells based on their values.
**Features**:
- Supports multiple thresholds with custom CSS classes
- Simple and straightforward threshold-based styling
- Works with the existing threshold system
**Usage**:
```typescript
import { thresholdRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'score',
name: 'Score',
thresholds: [
{ value: 0, className: 'high' }, // Red for negative values
{ value: 50, className: 'medium' }, // Yellow for values 0-50
{ value: 100, className: 'low' }, // Green for values 50-100
],
cellProperties: thresholdRenderer,
},
];
```
```ts
thresholdRenderer: PropertiesFunc>;
```
---
### `circularProgressRenderer`
```ts
circularProgressRenderer: CellTemplate>;
```
---
### `normalizeProgressRange`
Normalize a numeric value into a finite, clamped 0–100 progress range.
```ts
export function normalizeProgressRange(
value: unknown,
configuredMin: unknown = 0,
configuredMax: unknown = 100,
): NormalizedProgressRange;
```
---
### `NormalizedProgressRange`
```ts
interface NormalizedProgressRange {
readonly min: number;
readonly max: number;
readonly percent: number
}
```
---
### `getThresholdClasses`
Get the threshold classes for a given value and column.
### Example
```typescript
const columns: ColumnRegular[] = [
{
name: 'Avg Rating',
prop: 'Average Rating',
filter: ['number', 'slider'],
sortable: true,
maxStars: 5,
maxValue: 5,
thresholds: [
{ value: 4, className: 'high' },
{ value: 3, className: 'medium' },
{ value: 0, className: 'low' },
],
},
];
```
```ts
export function getThresholdClasses(
value: number,
column: Partial,
);
```
---
### `ThresholdConfig`
The `ThresholdConfig` interface defines the structure of a threshold configuration object.
It specifies the value and the class name to be applied when the value exceeds or equals the threshold.
```ts
interface ThresholdConfig {
/**
* The value of the threshold.
*/
value: number;
/**
* The class name to be applied when the value exceeds or equals the threshold.
*/
className: string
}
```
---
# Checkbox
URL: https://pro.rv-grid.com/api/checkbox/
Source: src/content/docs/api/checkbox.md
### `renderCheckbox`
Render the shared native checkbox while keeping the caller's renderer and
event semantics. This works with both RevoGrid/Stencil templates and Preact.
```ts
export function renderCheckbox(h: PreactH, attributes?: SharedCheckboxAttributes): PreactVNode;;
```
---
### `Checkbox`
Preact adapter for the shared checkbox presentation. The native input stays
the interaction owner; the adapter only normalizes indeterminate state and
optional label/description composition.
```ts
export function Checkbox({
checked,
indeterminate = false,
label,
description,
ariaLabel,
controlClassName = '',
inputRef,
onChange,
...attributes
}: CheckboxProps): any;
```
---
### `SHARED_CHECKBOX_CLASS`
```ts
SHARED_CHECKBOX_CLASS: string;
```
---
### `SharedCheckboxAttributes` (Extended from `index.ts`)
```ts
export type SharedCheckboxAttributes = Record & {
class?: string;
className?: string;
};
```
---
### `CheckboxProps` (Extended from `index.ts`)
```ts
export type CheckboxProps = Omit & {
checked?: boolean;
indeterminate?: boolean;
label?: any;
description?: any;
ariaLabel?: string;
controlClassName?: string;
inputRef?: (input: HTMLInputElement | null) => void;
onChange?: (checked: boolean, event: Event & { currentTarget: HTMLInputElement }) => void;
};
```
---
# Choice Chips
URL: https://pro.rv-grid.com/api/choice-chips/
Source: src/content/docs/api/choice-chips.md
### `ChoiceChips`
Shared wrapping choice control for compact presets and granularities.
```ts
export function ChoiceChips({
value,
options,
onChange,
ariaLabel,
appearance = 'soft',
size = 'medium',
className = '',
itemClassName = '',
disabled = false,
testId,
}: ChoiceChipsProps): any;
```
---
### `ChoiceChipValue`
```ts
export type ChoiceChipValue = string | number;
```
---
### `ChoiceChipOption`
```ts
export type ChoiceChipOption = {
value: T;
label: any;
ariaLabel?: string;
title?: string;
disabled?: boolean;
className?: string;
style?: any;
testId?: string;
};
```
---
### `ChoiceChipsProps`
```ts
export type ChoiceChipsProps = {
value?: T | readonly T[];
options: readonly ChoiceChipOption[];
onChange: (value: T, option: ChoiceChipOption) => void;
ariaLabel: string;
appearance?: 'soft' | 'outlined';
size?: 'small' | 'medium';
className?: string;
itemClassName?: string;
disabled?: boolean;
testId?: string;
};
```
---
# Clipboard
URL: https://pro.rv-grid.com/api/clipboard/
Source: src/content/docs/api/clipboard.md
## Module Extensions
### `ClipboardConfig` (Extended from `@revolist/revogrid`)
```ts
interface ClipboardConfig {
/** Additional rich clipboard formats used by ClipboardPlugin. */
formats?: readonly ClipboardFormatHandler[];
/** Maximum decoded cells. Defaults to 1,000,000. */
maxCells?: number;
/** Maximum bytes inspected in one text representation. Defaults to 16 MiB. */
maxBytes?: number
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
clipboardformaterror: ClipboardFormatDiagnostic
}
```
## Plugin API
### `ClipboardPlugin`
Browser-rich, Excel-compatible clipboard orchestration for RevoGrid Pro.
#### Dependencies
- **Optional** `DataGridFormattingPlugin`: Uses DataGridFormattingPlugin when available to import and apply rich formatting.
```ts
class ClipboardPlugin {
registerFormat(handler: ClipboardFormatHandler): () => void;
registerMetadataContributor(contributor: ClipboardMetadataContributor): () => void;
}
```
---
### `matrixToClipboardDocument`
Converts a value matrix into one dense clipboard-document range.
```ts
export function matrixToClipboardDocument(
matrix: readonly (readonly DataFormat[])[],
source = 'revogrid',
): ClipboardDocument;
```
---
### `clipboardDocumentToMatrix`
Materializes positioned clipboard ranges as one dense value matrix.
```ts
export function clipboardDocumentToMatrix(document: ClipboardDocument): DataFormat[][];
```
---
### `countClipboardCells`
Counts the rectangular cell capacity declared by a clipboard document.
```ts
export function countClipboardCells(document: ClipboardDocument);
```
---
### `isClipboardDocument`
Validates the supported clipboard document wire shape.
```ts
export function isClipboardDocument(value: unknown): value is ClipboardDocument;
```
---
### `createExcelHtmlClipboardFormat`
```ts
export function createExcelHtmlClipboardFormat(
formatting?: ClipboardExcelFormattingAdapter,
): ClipboardFormatHandler;
```
---
### `createGenericHtmlClipboardFormat`
```ts
export function createGenericHtmlClipboardFormat(
formatting?: ClipboardExcelFormattingAdapter,
): ClipboardFormatHandler;
```
---
### `extractCfHtml`
```ts
export function extractCfHtml(value: string): string;
```
---
### `parseHtmlClipboard`
```ts
export function parseHtmlClipboard(
raw: string,
options: {
source: string;
maxBytes: number;
maxCells: number;
formatting?: ClipboardExcelFormattingAdapter;
},
): ClipboardDocument | null;
```
---
### `serializeExcelHtml`
```ts
export function serializeExcelHtml(
document: ClipboardDocument,
formatting?: ClipboardExcelFormattingAdapter,
): string;
```
---
### `ClipboardExcelFormattingAdapter`
```ts
interface ClipboardExcelFormattingAdapter {
importFormat(declarations: readonly string[]): ClipboardCell['format'];
importValue(displayText: string, format?: ClipboardCell['format']): unknown;
exportFormat(format: NonNullable): string | undefined;
exportValue(
value: unknown,
format: NonNullable,
): { readonly value: unknown; readonly displayText: string }
}
```
---
### `excelHtmlClipboardFormat`
Value-only default. ClipboardPlugin supplies the formatting runtime adapter.
```ts
excelHtmlClipboardFormat: ClipboardFormatHandler;
```
---
### `genericHtmlClipboardFormat`
Value-only default. ClipboardPlugin supplies the formatting runtime adapter.
```ts
genericHtmlClipboardFormat: ClipboardFormatHandler;
```
---
### `parseDelimitedText`
```ts
export function parseDelimitedText(value: string, delimiter: string): string[][];
```
---
### `serializeDelimitedText`
```ts
export function serializeDelimitedText(
matrix: readonly (readonly unknown[])[],
delimiter: string,
): string;
```
---
### `plainTextClipboardFormat`
```ts
plainTextClipboardFormat: {
id: string;
mimeTypes: string[];
priority: number;
read: ({ transfer, maxBytes, maxCells }: ClipboardFormatReadContext) => ClipboardDocument;
write: ({ document }: ClipboardFormatWriteContext) => { type: string; data: string; }[];
};
```
---
### `ClipboardFormatRegistry`
```ts
class ClipboardFormatRegistry {
register(handler: ClipboardFormatHandler): () => void;
read(transfer: ClipboardDataReader, limits: { maxCells: number; maxBytes: number });
write(document: ClipboardDocument, transfer: ClipboardDataWriter): void;
}
```
---
### `resolveClipboardPluginConfig`
Resolves clipboard limits and custom formats from the grid configuration.
```ts
export function resolveClipboardPluginConfig(
useClipboard: boolean | ClipboardConfig | undefined,
): ResolvedClipboardPluginConfig;
```
---
### `REVOGRID_CLIPBOARD_MIME`
```ts
REVOGRID_CLIPBOARD_MIME: string;
```
---
### `REVOGRID_CLIPBOARD_VERSION`
```ts
REVOGRID_CLIPBOARD_VERSION: 2;
```
---
### `ClipboardCell`
```ts
interface ClipboardCell {
readonly value: T;
readonly displayText?: string;
readonly formula?: string;
/** `null` explicitly copies an ordinary RevoGrid cell with no effective format. */
readonly format?: DataGridCellFormat | null;
readonly metadata?: Readonly>
}
```
---
### `ClipboardRange`
```ts
interface ClipboardRange {
readonly row: number;
readonly col: number;
readonly width: number;
readonly height: number;
readonly cells: readonly ClipboardCell[];
readonly merges?: readonly MergeData[];
readonly columnWidths?: readonly (number | undefined)[];
readonly rowHeights?: readonly (number | undefined)[];
readonly rowType?: DimensionRows;
readonly colType?: DimensionCols;
readonly metadata?: Readonly>
}
```
---
### `ClipboardDocument`
```ts
interface ClipboardDocument {
readonly version: typeof REVOGRID_CLIPBOARD_VERSION;
readonly source: string;
readonly ranges: readonly ClipboardRange[];
readonly metadata?: Readonly>
}
```
---
### `ClipboardDataReader`
```ts
interface ClipboardDataReader {
readonly types: readonly string[];
getData(type: string): string
}
```
---
### `ClipboardDataWriter`
```ts
interface ClipboardDataWriter {
setData(type: string, value: string): void
}
```
---
### `ClipboardFormatReadContext`
```ts
interface ClipboardFormatReadContext {
readonly transfer: ClipboardDataReader;
readonly maxCells: number;
readonly maxBytes: number
}
```
---
### `ClipboardFormatWriteContext`
```ts
interface ClipboardFormatWriteContext {
readonly document: ClipboardDocument
}
```
---
### `ClipboardRepresentation`
```ts
interface ClipboardRepresentation {
readonly type: string;
readonly data: string
}
```
---
### `ClipboardFormatHandler`
```ts
interface ClipboardFormatHandler {
readonly id: string;
readonly mimeTypes: readonly string[];
readonly priority?: number;
canRead?(context: ClipboardFormatReadContext): boolean;
read(context: ClipboardFormatReadContext): ClipboardDocument | null;
write?(context: ClipboardFormatWriteContext): readonly ClipboardRepresentation[]
}
```
---
### `ClipboardMetadataCaptureContext`
```ts
interface ClipboardMetadataCaptureContext {
readonly document: ClipboardDocument;
readonly providers: PluginProviders
}
```
---
### `ClipboardMetadataApplyContext`
```ts
interface ClipboardMetadataApplyContext {
readonly document: ClipboardDocument;
readonly providers: PluginProviders
}
```
---
### `ClipboardMetadataContributor`
```ts
interface ClipboardMetadataContributor {
readonly id: string;
capture?(context: ClipboardMetadataCaptureContext): unknown;
apply?(metadata: unknown, context: ClipboardMetadataApplyContext): void
}
```
---
### `ClipboardFormatDiagnostic`
```ts
interface ClipboardFormatDiagnostic {
readonly phase: 'read' | 'write' | 'metadata';
readonly handlerId: string;
readonly error: unknown
}
```
---
### `ClipboardPluginConfig`
```ts
interface ClipboardPluginConfig {
readonly formats?: readonly ClipboardFormatHandler[];
readonly maxCells?: number;
readonly maxBytes?: number
}
```
---
### `ResolvedClipboardPluginConfig`
```ts
interface ResolvedClipboardPluginConfig {
readonly formats: readonly ClipboardFormatHandler[];
readonly maxCells: number;
readonly maxBytes: number
}
```
---
### `captureClipboardSelection`
Captures current grid selections as a rich clipboard document.
```ts
export function captureClipboardSelection(
context: ClipboardCaptureContext,
matrix?: DataFormat[][],
): ClipboardDocument | null;
```
---
### `getActiveClipboardSelection`
Reads the active provider-backed selection as normalized clipboard coordinates.
```ts
export function getActiveClipboardSelection(
providers: PluginProviders,
): ClipboardSelection[] | null;
```
---
### `getClipboardPasteStart`
Returns the focused viewport cell used as the paste origin.
```ts
export function getClipboardPasteStart(
providers: PluginProviders,
): ClipboardPasteStart | null;
```
---
### `getClipboardSelectionOffsets`
Converts viewport selections into offsets from their shared whole-grid origin.
```ts
export function getClipboardSelectionOffsets(
selections: readonly ClipboardSelection[],
providers: PluginProviders,
);
```
---
### `createClipboardCoordinateResolver`
Creates a cached coordinate resolver for one copy or paste operation.
```ts
export function createClipboardCoordinateResolver(
providers: PluginProviders,
start: ClipboardPasteStart,
): ClipboardCoordinateResolver;
```
---
### `forEachClipboardDocumentCell`
Visits every valid cell stored in a clipboard document without an intermediate array.
```ts
export function forEachClipboardDocumentCell(
document: ClipboardDocument,
visit: (entry: ClipboardDocumentCell) => boolean | void,
): boolean;
```
---
### `getSingleClipboardDocumentCell`
Returns the only stored document cell, or null when the count is not one.
```ts
export function getSingleClipboardDocumentCell(document: ClipboardDocument);
```
---
### `getClipboardRangeCellOffset`
Resolves one row-major range cell into its document-relative offset.
```ts
export function getClipboardRangeCellOffset(
range: ClipboardRange,
index: number,
): ClipboardCellOffset | null;
```
---
### `getClipboardDocumentBounds`
Computes clipboard-document bounds in one pass.
```ts
export function getClipboardDocumentBounds(document: ClipboardDocument);
```
---
### `getClipboardCellCount`
Counts the rectangular capacity declared by all clipboard ranges.
```ts
export function getClipboardCellCount(document: ClipboardDocument);
```
---
### `getClipboardMatrixData`
Flattens a matrix into a dense row-major clipboard range.
```ts
export function getClipboardMatrixData(
matrix: readonly (readonly DataFormat[])[],
);
```
---
### `createClipboardMatrix`
Builds a dense value matrix from positioned clipboard ranges.
```ts
export function createClipboardMatrix(document: ClipboardDocument): DataFormat[][];
```
---
### `getClipboardFormulaDestination`
Resolves the absolute destination used to restore an Excel formula.
```ts
export function getClipboardFormulaDestination(
resolver: ClipboardCoordinateResolver,
range: ClipboardRange,
cellIndex: number,
);
```
---
### `forEachClipboardRangeFillCell`
Visits every projected destination for a one-cell range-fill paste.
```ts
export function forEachClipboardRangeFillCell(
selection: ClipboardSelection,
start: ClipboardPasteStart,
cell: ClipboardCell,
visit: (entry: ClipboardDocumentCell) => boolean | void,
): boolean;
```
---
### `clipboardOutcomeIncludesTarget`
Checks whether a managed paste outcome includes one resolved target.
```ts
export function clipboardOutcomeIncludesTarget(
detail: {
type?: DimensionRows;
data?: Record>;
} | undefined,
target: ClipboardResolvedPasteTarget,
);
```
---
### `createClipboardTargetMerge`
Repositions clipboard merge metadata at a resolved viewport target.
```ts
export function createClipboardTargetMerge(
merge: MergeData,
target: ClipboardPasteStart,
): MergeData;
```
---
### `captureClipboardMerge`
Converts an in-selection merge into clipboard-relative coordinates.
```ts
export function captureClipboardMerge(
merge: MergeData,
area: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
): MergeData | null;
```
---
### `getClipboardMergeOffsets`
Returns the inclusive paste bounds of a clipboard merge.
```ts
export function getClipboardMergeOffsets(
range: ClipboardRange,
merge: MergeData,
);
```
---
### `forEachClipboardMergeCell`
Visits every document-relative cell covered by a clipboard merge.
```ts
export function forEachClipboardMergeCell(
range: ClipboardRange,
merge: MergeData,
visit: (offset: ClipboardCellOffset) => boolean | void,
): boolean;
```
---
### `clipboardMergesOverlap`
Checks whether two same-viewport merge rectangles overlap.
```ts
export function clipboardMergesOverlap(left: MergeData, right: MergeData);
```
---
### `ClipboardPasteStart`
```ts
export type ClipboardPasteStart = ViewportCellAddress;
```
---
### `ClipboardResolvedPasteTarget`
```ts
export type ClipboardResolvedPasteTarget = ResolvedViewportCell;
```
---
### `ClipboardMetadataTargetFilter`
```ts
export type ClipboardMetadataTargetFilter = (target: ClipboardResolvedPasteTarget) => boolean;
```
---
### `ClipboardSelection`
```ts
interface ClipboardSelection {
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
readonly range: RangeArea
}
```
---
### `ClipboardCellOffset`
```ts
interface ClipboardCellOffset {
readonly rowOffset: number;
readonly colOffset: number
}
```
---
### `ClipboardDocumentCell` (Extended from `coordinates.ts`)
```ts
interface ClipboardDocumentCell {
readonly cell: ClipboardCell
}
```
---
### `ClipboardCoordinateResolver`
Resolves clipboard offsets through one cached provider-coordinate snapshot.
```ts
interface ClipboardCoordinateResolver {
/** Converts a document offset into a whole-grid row and column. */
getAbsoluteCell(offset: ClipboardCellOffset): { readonly row: number; readonly column: number };
/** Reads cached source and item mappings for one viewport pair. */
getViewportSources(rowType: DimensionRows, colType: DimensionCols): ClipboardViewportSources | null;
/** Resolves a document offset to its model-backed grid cell. */
resolveTarget(offset: ClipboardCellOffset): ClipboardResolvedPasteTarget | null;
/** Resolves a document offset to its viewport-local address. */
resolveTargetAddress(offset: ClipboardCellOffset): ClipboardPasteStart | null;
/** Converts a resolved cell from visible indexes to physical source indexes. */
getPhysicalCellAddress(target: ClipboardResolvedPasteTarget): {
readonly row: number;
readonly column: number;
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
};
/** Converts a resolved column from its visible index to its physical source index. */
getPhysicalColumnAddress(target: ClipboardResolvedPasteTarget): {
readonly column: number;
readonly colType: DimensionCols;
};
/** Returns the cached physical row source for readonly evaluation. */
getTargetRowSource(target: ClipboardResolvedPasteTarget): DataType[]
}
```
---
### `resolveClipboardFormattingRuntime`
Finds the installed data-grid formatting runtime, when available.
```ts
export function resolveClipboardFormattingRuntime(
providers: PluginProviders,
): DataGridFormattingRuntime | undefined;
```
---
### `applyClipboardFormatting`
Applies rich clipboard formats to every accepted destination cell.
```ts
export function applyClipboardFormatting(
context: ClipboardFormattingContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
targetFilter?: ClipboardMetadataTargetFilter,
): void;
```
---
### `isClipboardPasteValueCompatible`
Checks that pasted values match every effective destination editor domain.
```ts
export function isClipboardPasteValueCompatible(
context: ClipboardFormattingContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
): boolean;
```
---
### `resolveClipboardPasteFormulas`
Rebases stored Excel R1C1 formulas against the current paste destination.
```ts
export function resolveClipboardPasteFormulas(
providers: PluginProviders,
document: ClipboardDocument,
start: ClipboardPasteStart,
): ClipboardDocument;
```
---
### `isWritableClipboardTarget`
Checks provider readonly policy for one resolved clipboard destination.
```ts
export function isWritableClipboardTarget(
resolver: ClipboardCoordinateResolver,
target: ClipboardResolvedPasteTarget,
): boolean;
```
---
### `clipboardDocumentToSparsePayload`
Converts multi-range rich data into the legacy sparse clipboard payload.
```ts
export function clipboardDocumentToSparsePayload(
document: ClipboardDocument,
): MultiRangeClipboardPayload | null;
```
---
### `captureClipboardMerges`
Captures merges fully contained by one copied viewport range.
```ts
export function captureClipboardMerges(
revogrid: HTMLRevoGridElement,
area: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
);
```
---
### `applyClipboardStructure`
Applies valid, writable clipboard merges at the paste destination.
```ts
export function applyClipboardStructure(
context: ClipboardStructureContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
targetFilter?: ClipboardMetadataTargetFilter,
): void;
```
---
### `normalizeExcelClipboardFormula`
Convert browser-visible Excel R1C1 references into FormulaPlugin-compatible A1 references.
```ts
export function normalizeExcelClipboardFormula(
value: string,
rowIndex: number,
columnIndex: number,
): string;
```
---
### `toExcelClipboardFormula`
Convert FormulaPlugin-compatible A1 references into Excel HTML R1C1 references.
```ts
export function toExcelClipboardFormula(
value: string,
rowIndex: number,
columnIndex: number,
): string;
```
---
### `cloneClipboardDocument`
```ts
export function cloneClipboardDocument(document: ClipboardDocument): ClipboardDocument;
```
---
### `revogridJsonClipboardFormat`
```ts
revogridJsonClipboardFormat: {
id: string;
mimeTypes: string[];
priority: number;
read: ({ transfer, maxBytes, maxCells }: ClipboardFormatReadContext) => ClipboardDocument | null;
write: ({ document }: ClipboardFormatWriteContext) => { type: string; data: string; }[];
};
```
---
# Clipboard Validation
URL: https://pro.rv-grid.com/api/clipboard-validation/
Source: src/content/docs/api/clipboard-validation.md
## Module Extensions
### `ClipboardConfig` (Extended from `@revolist/revogrid`)
```ts
interface ClipboardConfig {
/**
* Resolves strict validation failures during paste.
*
* `atomic` rejects the complete paste when any destination is invalid.
* `valid-cells` applies accepted destinations and preserves rejected cells.
*
* @default 'atomic'
*/
validationResolution?: ClipboardValidationResolution
}
```
## Plugin API
### `ClipboardValidationResolution`
Validation policy used when clipboard data contains strict validation failures.
```ts
/** Validation policy used when clipboard data contains strict validation failures. */
export type ClipboardValidationResolution = 'atomic' | 'valid-cells';
```
---
# Collaborative Presence
URL: https://pro.rv-grid.com/api/collaborative-presence/
Source: src/content/docs/api/collaborative-presence.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
collaborativePresence?: CollaborativePresenceConfig;
'collaborative-presence'?: CollaborativePresenceConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
collaborativePresence?: CollaborativePresenceConfig
}
```
## Plugin API
### `CollaborativePresencePlugin`
Renders remote collaborator focus and range presence without mutating local
grid focus, selection, data, or history.
#### Dependencies
- **Config integration** `additionalData.collaborativePresence`: Reads remote collaborator presence from grid.collaborativePresence or additionalData.collaborativePresence.
- **Event integration** `afterrender`: Refreshes visible remote focus and range markers after grid render updates.
```ts
class CollaborativePresencePlugin {
destroy();
}
```
---
### `CollaborativePresenceActivity`
```ts
export type CollaborativePresenceActivity = 'viewing' | 'editing' | 'idle';
```
---
### `CollaborativePresenceCellRef`
```ts
interface CollaborativePresenceCellRef {
x: number;
y: number;
colType?: DimensionCols;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceRangeRef` (Extended from `index.ts`)
```ts
interface CollaborativePresenceRangeRef {
colType?: DimensionCols;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceUser`
```ts
interface CollaborativePresenceUser {
id: string;
name: string;
initials?: string;
color?: string;
activity?: CollaborativePresenceActivity;
focus?: CollaborativePresenceCellRef | null;
range?: CollaborativePresenceRangeRef | null;
lastActiveAt?: number | string | Date
}
```
---
### `CollaborativePresenceRowPatch`
```ts
interface CollaborativePresenceRowPatch {
rowIndex: number;
data: Record;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceConfig`
```ts
interface CollaborativePresenceConfig {
enabled?: boolean;
showLabels?: boolean;
staleAfterMs?: number;
users?: CollaborativePresenceUser[];
remoteEdits?: CollaborativePresenceRowPatch[]
}
```
---
### `ResolvedCollaborativePresenceConfig`
```ts
interface ResolvedCollaborativePresenceConfig {
enabled: boolean;
showLabels: boolean;
staleAfterMs?: number;
users: CollaborativePresenceUser[];
remoteEdits: CollaborativePresenceRowPatch[]
}
```
---
### `CollaborativePresenceRect`
```ts
interface CollaborativePresenceRect {
left: number;
top: number;
width: number;
height: number
}
```
---
### `CollaborativePresenceMarker`
```ts
interface CollaborativePresenceMarker {
kind: 'focus' | 'range';
user: CollaborativePresenceUser;
rect: CollaborativePresenceRect;
colType: DimensionCols;
rowType: DimensionRows
}
```
---
### `normalizeCollaborativePresenceCell`
```ts
export function normalizeCollaborativePresenceCell(
cell: CollaborativePresenceCellRef,
): Required;
```
---
### `normalizeCollaborativePresenceRange`
```ts
export function normalizeCollaborativePresenceRange(
range: CollaborativePresenceRangeRef,
): Required;
```
---
### `resolveCollaborativePresenceConfig`
```ts
export function resolveCollaborativePresenceConfig(
config?: CollaborativePresenceConfig,
): ResolvedCollaborativePresenceConfig;
```
---
### `getVisibleCollaborativePresenceUsers`
```ts
export function getVisibleCollaborativePresenceUsers(
config: ResolvedCollaborativePresenceConfig,
now = Date.now(),
): CollaborativePresenceUser[];
```
---
### `resolveCollaborativePresenceMarkers`
```ts
export function resolveCollaborativePresenceMarkers(
_grid: HTMLElement,
users: CollaborativePresenceUser[],
providers?: PluginProviders,
): CollaborativePresenceMarker[];
```
---
### `applyCollaborativePresenceRowPatches`
Applies row patches directly into the RevoGrid data store without triggering
a full source reset or plugin re-initialization.
`rowIndex` is treated as a **source (physical) index** — the position in the
original data array — so patches always target the correct row regardless of
the current sort or reorder state.
Per rowType:
1. Mutate source[rowIndex] directly for each patch
2. store.set('source', [...]) once — triggers a single targeted reactive update
Mirrors the low-level write pattern in spreadsheet.simulation.ts.
```ts
export function applyCollaborativePresenceRowPatches(
providers: PluginProviders,
patches: CollaborativePresenceRowPatch[],
): void;
```
---
### `CollaborativePresenceLayer`
Manages per-viewport slot overlay elements and draws presence markers into them.
Each viewport type combination (colType × rowType) gets its own slot div so markers
clip and scroll correctly within their viewport.
```ts
class CollaborativePresenceLayer {
render(markers: CollaborativePresenceMarker[], showLabels: boolean): void;
destroy(): void;
}
```
---
### `getCollaborativePresenceViewportOffset`
```ts
export function getCollaborativePresenceViewportOffset(
rect: CollaborativePresenceRect,
): { left: number; top: number } | undefined;
```
---
### `resolveCollaborativePresenceCellRect`
```ts
export function resolveCollaborativePresenceCellRect(
providers: PluginProviders | undefined,
cell: PresenceCell,
): CollaborativePresenceRect | undefined;
```
---
### `resolveCollaborativePresenceRangeRect`
```ts
export function resolveCollaborativePresenceRangeRect(
providers: PluginProviders | undefined,
range: PresenceRange,
): CollaborativePresenceRect | undefined;
```
---
# Color Palette
URL: https://pro.rv-grid.com/api/color-palette/
Source: src/content/docs/api/color-palette.md
### `ColorPalette`
```ts
export function ColorPalette({
value,
options = DEFAULT_COLOR_PALETTE_OPTIONS,
ariaLabel,
defaultLabel,
colorAriaLabel,
disabled = false,
includeDefault = true,
defaultDisabled = false,
onChange,
}: ColorPaletteProps);
```
---
### `createColorPaletteMenuItems`
```ts
export function createColorPaletteMenuItems(options: {
/** `undefined` represents a mixed multi-selection with no checked choice. */
readonly value: ColorPaletteValue | undefined;
readonly options?: readonly ColorPaletteOption[];
readonly defaultLabel: string;
readonly colorAriaLabel: (option: ColorPaletteOption) => string;
readonly disabled?: boolean;
readonly includeDefault?: boolean;
readonly defaultDisabled?: boolean;
readonly onChange: (value: ColorPaletteValue) => void;
}): ContextMenuItem[];
```
---
### `ColorPaletteProps`
```ts
interface ColorPaletteProps {
readonly value: ColorPaletteValue;
readonly options?: readonly ColorPaletteOption[];
readonly ariaLabel: string;
readonly defaultLabel: string;
readonly colorAriaLabel: (option: ColorPaletteOption) => string;
readonly disabled?: boolean;
readonly includeDefault?: boolean;
readonly defaultDisabled?: boolean;
readonly onChange: (value: ColorPaletteValue) => void
}
```
---
### `ColorPaletteOption`
```ts
interface ColorPaletteOption {
readonly value: string;
readonly label: string;
readonly disabled?: boolean
}
```
---
### `ColorPaletteValue`
```ts
export type ColorPaletteValue = string | null;
```
---
### `DEFAULT_COLOR_PALETTE_OPTIONS`
```ts
DEFAULT_COLOR_PALETTE_OPTIONS: readonly ColorPaletteOption[];
```
---
# Column Add Popup
URL: https://pro.rv-grid.com/api/column-add-popup/
Source: src/content/docs/api/column-add-popup.md
## Module Extensions
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* Column add popup plugin configuration.
*
* @deprecated Prefer the direct `grid.columnAddPopup` property.
*/
columnAddPopup?: ColumnAddPopupConfig
}
```
---
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
/**
* Column add popup plugin configuration.
*/
columnAddPopup?: ColumnAddPopupConfig
}
```
## Plugin API
### `ColumnAddPopupPlugin`
```ts
class ColumnAddPopupPlugin {
open(trigger: HTMLElement, originalEvent: MouseEvent);
destroy();
}
```
---
### `ColumnAddPopupTone`
```ts
export type ColumnAddPopupTone =
| 'green'
| 'blue'
| 'cyan'
| 'red'
| 'orange'
| 'yellow'
| 'violet'
| 'pink'
| 'gray';
```
---
### `ColumnAddPopupItem`
```ts
interface ColumnAddPopupItem {
id: string;
label: string;
description?: string;
icon?: string;
tone?: ColumnAddPopupTone;
disabled?: boolean;
selected?: boolean
}
```
---
### `ColumnAddPopupSection`
```ts
interface ColumnAddPopupSection {
title: string;
items: ColumnAddPopupItem[]
}
```
---
### `ColumnAddPopupSelectContext`
```ts
interface ColumnAddPopupSelectContext {
item: ColumnAddPopupItem;
selected: boolean;
grid: HTMLRevoGridElement;
originalEvent: MouseEvent;
close: () => void
}
```
---
### `ColumnAddPopupItemStateContext`
```ts
interface ColumnAddPopupItemStateContext {
item: ColumnAddPopupItem;
grid: HTMLRevoGridElement
}
```
---
### `ColumnAddPopupFooterContext`
```ts
interface ColumnAddPopupFooterContext {
grid: HTMLRevoGridElement;
originalEvent: MouseEvent;
close: () => void
}
```
---
### `ColumnAddPopupConfig`
```ts
interface ColumnAddPopupConfig {
sections?: ColumnAddPopupSection[];
title?: string;
footerLabel?: string;
className?: string;
isSelected?: (context: ColumnAddPopupItemStateContext) => boolean;
onSelect?: (context: ColumnAddPopupSelectContext) => void;
onFooterClick?: (context: ColumnAddPopupFooterContext) => void
}
```
---
# Column Autosize
URL: https://pro.rv-grid.com/api/column-autosize/
Source: src/content/docs/api/column-autosize.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
/**
* Kebab-case alias for framework bindings.
*/
'auto-size-column'?: AutoSizeColumnProp
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
Add autoSizeColumnConfig property to AdditionalData interface
```ts
interface AdditionalData {
/**
* @deprecated Use `grid.autoSizeColumn` instead.
*/
autoSizeColumnConfig?: AutoSizeColumnConfig
}
```
---
### `ColumnRegular` (Extended from `@revolist/revogrid`)
Add autoSize property to ColumnRegular interface
```ts
interface ColumnRegular {
autoSize?: boolean
}
```
## Plugin API
### `AutoSizeColumnConfig`
```ts
export type AutoSizeColumnConfig = {
// ui behavior mode
mode?: ColumnAutoSizeMode;
/**
* autoSize for all columns
* if allColumnes true all columns treated as autoSize, worse for performance
* false by default
*/
allColumns?: boolean;
/**
* assumption per characted size
* by default defined as 8, can be changed in this config
*/
letterBlockSize?: number;
/** make size calculation exact
* by default it based on assumption each character takes some space defined in letterBlockSize */
preciseSize?: boolean;
};
```
---
### `ColumnAutoSizeMode`
```ts
enum ColumnAutoSizeMode {
// increases column width on header click according the largest text value
headerClickAutosize = 'headerClickAutoSize',
// increases column width on data set and text edit, decreases performance
autoSizeOnTextOverlap = 'autoSizeOnTextOverlap',
// increases and decreases column width based on all items sizes, worst for performance
autoSizeAll = 'autoSizeAll'
}
```
---
### `AutoSizeColumnPlugin`
The `AutoSizeColumnPlugin` is a plugin for the RevoGrid framework that provides automatic column resizing
functionality. It allows users to resize columns based on the content of the cells, providing a flexible
and user-friendly interface for data presentation.
**Features**:
- Automatically resizes columns based on the content of the cells.
- Supports different modes of automatic resizing:
- Header click autosize: Resizes columns when the header is clicked.
- Text overlap autosize: Resizes columns when text is edited.
- All columns autosize: Resizes all columns based on the content of the cells.
**Usage**:
- Add the `AutoSizeColumnPlugin` to the grid's plugins array.
- Configure the plugin using the `additionalData` property with the `autoSizeColumnConfig` key.
### Example
```typescript
import { AutoSizeColumnPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [AutoSizeColumnPlugin]; // Add the plugin to the grid
grid.additionalData = {
autoSizeColumnConfig: {
mode: ColumnAutoSizeMode.autoSizeOnTextOverlap,
},
};
```
#### Dependencies
- **Event integration** `aftersourceset`, `afteredit`, `headerdblclick`: Integrates with grid lifecycle events to recalculate column widths.
- **Config integration** `additionalData.autoSizeColumnConfig`: Reads legacy auto-size configuration from additionalData.autoSizeColumnConfig.
```ts
class AutoSizeColumnPlugin {
async setSource(source: DataType[]): Promise;
getLength(len?: any): number;
afteredit(e: EditEvent);
afterEditAll(e: EditEvent);
getColumnSize(index: number, type: DimensionCols): number;
/** Measure a column on demand without enabling continuous auto-size. */
getColumnContentSize(column: ColumnRegular): number;
columnSet(columns: Record);
clearPromise();
isRangeEdit(e: EditEvent): e is BeforeRangeSaveDataDetails;
initiatePresizeElement(): HTMLElement;
destroy();
}
```
---
# Column Collapse
URL: https://pro.rv-grid.com/api/column-collapse/
Source: src/content/docs/api/column-collapse.md
## Module Extensions
### `Group` (Extended from `@revolist/revogrid`)
Add collapsible property to Group interface
```ts
interface Group {
/**
* Whether the group is collapsible
*/
collapsible?: boolean;
/**
* Whether the group is collapsed
*/
collapsed?: boolean;
/**
* Custom placeholder configuration used when a collapsed group has no sealed columns.
*/
placeholder?: string | ColumnTemplateFunc | ColumnCollapsePlaceholder;
/**
* Render collapsed top-level groups through their carrier leaf header when
* every marked sibling is collapsed.
*/
compactCollapsedHeader?: boolean
}
```
---
### `ColumnGrouping` (Extended from `@revolist/revogrid`)
```ts
interface ColumnGrouping {
/**
* Opt this group into compact single-row collapsed header projection.
*/
compactCollapsedHeader?: boolean
}
```
---
### `ColumnRegular` (Extended from `@revolist/revogrid`)
Add sealed property to ColumnRegular interface
```ts
interface ColumnRegular {
/**
* Whether the column is sealed, if true the column will not be trimmed/collapsed
*/
sealed?: boolean
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
Add column collapse event to HTMLRevoGridElementEventMap
```ts
interface HTMLRevoGridElementEventMap {
/**
* Column collapse event
*/
[COLUMN_COLLAPSE_EVENT]: { group: Group };
/**
* Column expand event
*/
[COLUMN_EXPAND_EVENT]: { group: Group }
}
```
## Plugin API
### `ColumnCollapsePlaceholder`
```ts
interface ColumnCollapsePlaceholder {
header?: string | ColumnTemplateFunc;
value?: (schema: CellTemplateProp, state: CollapsedGroupState) => unknown;
cellTemplate?: NonNullable;
cellProperties?: PropertiesFunc;
/**
* Preserve the carrier column's filter configuration and filter-header UI.
* Disabled by default because a generic placeholder may not represent the
* carrier field's value.
*/
preserveFilter?: boolean;
/**
* Preserve the carrier column's sortable state and active sort indicator.
* Disabled by default because a generic placeholder may not represent the
* carrier field's value.
*/
preserveSorting?: boolean;
/**
* Optional filter proxy rendered on the group header while the group is
* expanded. Its prop must identify the same aggregate represented by the
* collapsed carrier.
*/
filterColumn?: ColumnRegular
}
```
---
### `ColumnCollapsePlugin`
The `ColumnCollapsePlugin` is a plugin for the RevoGrid framework that enables collapsible columns
with content trimming functionality. It allows users to collapse and expand columns, reducing the
visual clutter and improving the overall user experience when dealing with large datasets.
**Features**:
- Enables collapsible columns with content trimming
- Provides a visual indicator (▼) in the header to collapse and expand columns
**Usage**:
- Add the `ColumnCollapsePlugin` to the grid's plugins array.
- Add the `collapsible` property to the column configuration to enable collapsible columns.
- Add the `collapsed` property to the column configuration to set the initial collapsed state.
- Add the `children` property to the column configuration to define the columns to be collapsed.
- Add the `sealed` property to the column configuration to prevent the column from being trimmed.
### Example
```typescript
import { ColumnCollapsePlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.columns = [
{
collapsible: true,
collapsed: true,
children: [
{
prop: 'age',
sealed: true,
},
],
},
];
grid.plugins = [ColumnCollapsePlugin];
```
```ts
class ColumnCollapsePlugin {
/** Returns whether a collapsible authored column group is currently collapsed. */
isGroupCollapsed(group: Group, type: DimensionCols): boolean;
/**
* Applies an explicit collapsed state to an authored column group.
* No-op when the group cannot be collapsed or already has the requested state.
*/
setGroupCollapsed(group: Group, type: DimensionCols, collapsed: boolean): void;
/** Toggles an authored collapsible column group. */
toggleGroupCollapsed(group: Group, type: DimensionCols): void;
destroy();
}
```
---
### `CollapsedGroupMode`
```ts
export type CollapsedGroupMode = 'sealed' | 'placeholder';
```
---
### `CollapsedGroupState`
```ts
interface CollapsedGroupState {
key: string;
mode: CollapsedGroupMode;
group: Group;
indexes: number[];
visibleIndexes: number[];
carrierIndex?: number;
hiddenCount: number
}
```
---
# Column Dialog
URL: https://pro.rv-grid.com/api/column-dialog/
Source: src/content/docs/api/column-dialog.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
columnDialog?: ColumnDialogConfig
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
[COLUMN_DIALOG_OPEN_EVENT]: undefined;
[COLUMN_DIALOG_CLOSE_EVENT]: undefined;
[COLUMN_DIALOG_TOGGLE_EVENT]: undefined;
[COLUMN_DIALOG_CHANGE_EVENT]: ColumnDialogChangeDetail
}
```
## Plugin API
### `buildColumnDialogRows`
```ts
export function buildColumnDialogRows({
columnOrderByType,
columnsByType,
hiddenColumns,
groupsByType,
}: BuildTreeOptions): ColumnDialogRow[];
```
---
### `buildColumnDialogRowsFromProviders`
```ts
export function buildColumnDialogRowsFromProviders(
providers: PluginProviders,
hiddenColumns: ReadonlySet,
): ColumnDialogRow[];
```
---
### `filterColumnDialogRows`
```ts
export function filterColumnDialogRows(
rows: readonly ColumnDialogRow[],
query: string,
): ColumnDialogRow[];
```
---
### `getDialogRowDescendantProps`
```ts
export function getDialogRowDescendantProps(
rows: readonly ColumnDialogRow[],
rowId: string,
): ColumnProp[];
```
---
### `resolveNextHiddenColumns`
```ts
export function resolveNextHiddenColumns(
rows: readonly ColumnDialogRow[],
hiddenColumns: ReadonlySet,
rowId: string,
visible: boolean,
): ColumnProp[];
```
---
### `resolveColumnDialogReorder`
```ts
export function resolveColumnDialogReorder({
draggedRowId,
position,
rows,
targetRowId,
}: ColumnDialogReorderRequest): ColumnDialogReorderResult;
```
---
### `normalizeHiddenColumns`
```ts
export function normalizeHiddenColumns(value: unknown): ColumnProp[];
```
---
### `ColumnCollection`
Column collection definition.
Used to access indexed data for columns.
Can be accessed via different events.
```ts
/**
* Column collection definition.
* Used to access indexed data for columns.
* Can be accessed via different events.
*/
export type ColumnCollection = {
/**
* Columns as they are in stores
*/
columns: Record;
/**
* Columns indexed by prop for quick access, it's possible to have multiple columns with same prop but not recommended
*/
columnByProp: Record;
/**
* Grouped columns
*/
columnGrouping: ColumnGroupingCollection;
/**
* Max level of grouping
*/
maxLevel: number;
/**
* Sorting
*/
sort: Record;
};
```
---
### `resolveColumnDialogGridTheme`
```ts
export function resolveColumnDialogGridTheme(theme: ColumnDialogTheme | undefined): 'compact' | 'darkCompact';
```
---
### `createDialogColumns`
```ts
export function createDialogColumns(
onToggle: (rowId: string, visible: boolean) => void,
disableDrag = false,
): ColumnRegular[];
```
---
### `ColumnDialog`
```ts
export function ColumnDialog({
config,
hiddenColumns,
open,
rows,
onClose,
onHiddenColumnsChange,
onReorder,
}: ColumnDialogProps);
```
---
### `COLUMN_DIALOG_OPEN_EVENT`
```ts
COLUMN_DIALOG_OPEN_EVENT: string;
```
---
### `COLUMN_DIALOG_CLOSE_EVENT`
```ts
COLUMN_DIALOG_CLOSE_EVENT: string;
```
---
### `COLUMN_DIALOG_TOGGLE_EVENT`
```ts
COLUMN_DIALOG_TOGGLE_EVENT: string;
```
---
### `COLUMN_DIALOG_CHANGE_EVENT`
```ts
COLUMN_DIALOG_CHANGE_EVENT: string;
```
---
### `ColumnDialogApplyMode`
```ts
export type ColumnDialogApplyMode = 'immediate';
```
---
### `ColumnDialogTheme`
```ts
export type ColumnDialogTheme = 'light' | 'dark';
```
---
### `ColumnDialogConfig`
```ts
interface ColumnDialogConfig {
title?: string;
searchPlaceholder?: string;
closeLabel?: string;
width?: number | string;
height?: number | string;
/** Omits drag handles and disables column reordering. Defaults to `false`. */
disableDrag?: boolean;
/** Color mode for the compact dialog grid and surface. Defaults to `light`. */
theme?: ColumnDialogTheme;
/**
* @deprecated The Pro dialog always hides its internal grid attribution.
* This option is retained as a no-op for source compatibility.
*/
hideAttribution?: boolean
}
```
---
### `ColumnDialogRowKind`
```ts
export type ColumnDialogRowKind = 'group' | 'column';
```
---
### `ColumnDialogVisibilityState`
```ts
export type ColumnDialogVisibilityState = 'checked' | 'unchecked' | 'indeterminate';
```
---
### `ColumnDialogRow`
```ts
interface ColumnDialogRow {
id: string;
parentId: string | null;
name: string;
prop: ColumnProp | null;
kind: ColumnDialogRowKind;
type: DimensionCols;
physicalIndex: number | null;
sourceIndexes: number[];
order: number;
depth: number;
visible: boolean;
visibilityState: ColumnDialogVisibilityState;
rowDrag: boolean
}
```
---
### `ColumnDialogChangeDetail`
```ts
interface ColumnDialogChangeDetail {
hiddenColumns: ColumnProp[]
}
```
---
### `ColumnDialogReorderRequest`
```ts
interface ColumnDialogReorderRequest {
rows: readonly ColumnDialogRow[];
draggedRowId: string;
targetRowId: string;
position: 'before' | 'after'
}
```
---
### `ColumnDialogReorderResult`
```ts
interface ColumnDialogReorderResult {
valid: boolean;
reason?: string;
type?: DimensionCols;
proxyItems?: number[]
}
```
---
### `ColumnDialogPlugin`
Column manager dialog for RevoGrid Pro.
`ColumnDialogPlugin` renders a modal column-management dialog backed by an
internal RevoGrid tree. It lists all host grid columns, including columns
currently hidden by `ColumnHidePlugin`, and lets users search, toggle
visibility, and reorder columns or column groups inside their current
parent/pin section. Set `columnDialog.disableDrag` to use visibility-only
mode without drag handles or row-order behavior. The internal grid always
uses compact density, hides its implementation-detail attribution, and
`columnDialog.theme` selects light or dark mode.
Visibility is intentionally delegated to `ColumnHidePlugin`; this plugin
auto-installs it when it is not already registered and writes changes through
`grid.hideColumns`.
### Usage
```ts
import { ColumnDialogPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.plugins = [ColumnDialogPlugin];
grid.columnDialog = {
title: 'Columns',
searchPlaceholder: 'Search columns...',
};
// Open with a grid event.
grid.dispatchEvent(new CustomEvent('revocolumndialogopen'));
// Or open through the plugin instance.
const plugins = await grid.getPlugins();
plugins.getByClass(ColumnDialogPlugin)?.open();
grid.addEventListener('revocolumndialogchange', (event) => {
console.log(event.detail.hiddenColumns);
});
```
Public events:
- `revocolumndialogopen` opens the dialog.
- `revocolumndialogclose` closes the dialog.
- `revocolumndialogtoggle` toggles open state.
- `revocolumndialogchange` emits after visibility or ordering changes.
#### Dependencies
- **Auto-installed** `ColumnHidePlugin`: Uses ColumnHidePlugin as the only host visibility engine.
- **Event integration** `COLUMN_UPDATED_EVENT`, `HIDDEN_COLUMNS_UPDATED_EVENT`, `COLUMN_DRAG_END_EVENT`: Tracks host column definitions, hidden-column state, and host column reorder.
```ts
class ColumnDialogPlugin {
open(): void;
close(): void;
toggle(): void;
destroy(): void;
}
```
---
# Column Group Panel
URL: https://pro.rv-grid.com/api/column-group-panel/
Source: src/content/docs/api/column-group-panel.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
/** Configuration for the column group panel. */
columnGroupPanel?: ColumnGroupPanelConfig;
/** Kebab-case alias for framework bindings. */
'column-group-panel'?: ColumnGroupPanelConfig
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
columngroupingchange: ColumnGroupPanelChangeDetail
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/** @deprecated Use `grid.columnGroupPanel` instead. */
columnGroupPanel?: ColumnGroupPanelConfig;
/** @deprecated Use `grid.columnGroupPanel` instead. */
columnGroupingPanel?: ColumnGroupPanelConfig
}
```
## Plugin API
### `ColumnGroupPanelChangeDetail`
```ts
interface ColumnGroupPanelChangeDetail {
props: ColumnProp[];
grouping: GroupingOptions
}
```
---
### `ColumnGroupPanelConfig`
```ts
interface ColumnGroupPanelConfig {
/**
* Text to display in the group panel when no columns are grouped.
* @default 'Drag columns here to group by row.'
*/
emptyPanelText?: string;
/** Show the grouping position before each key. @default true */
showOrder?: boolean
}
```
---
### `ColumnGroupPanelPlugin`
Adds a header panel for creating, removing, and reordering row groupings.
**Example:**
```typescript
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnGroupPanelPlugin];
grid.columnGroupPanel = { emptyPanelText: 'Drop columns here' };
```
#### Dependencies
- **Config integration** `core-grouping`: Writes grid grouping props through the core grouping API.
- **Event integration** `columndragstart`, `columndragmousemove`, `beforecolumndragend`, `columndragend`: Shares the column-move gesture lifecycle for header-to-panel grouping.
```ts
class ColumnGroupPanelPlugin {
addGrouping(columnProp: ColumnProp);
removeGrouping(indexOrProp: number | ColumnProp);
reorderGrouping(draggedProp: ColumnProp, targetIndex: number);
setGroupings(props: ColumnProp[]);
getGroupings(): ColumnProp[];
destroy();
}
```
---
### `ColumnGroupPanelDragController`
```ts
class ColumnGroupPanelDragController {
destroy();
}
```
---
### `normalizeColumnGroupings`
```ts
export function normalizeColumnGroupings(props?: ColumnProp[]): ColumnProp[];
```
---
### `moveColumnGrouping`
```ts
export function moveColumnGrouping(
groupings: ColumnProp[],
draggedProp: ColumnProp,
rawTargetIndex: number,
): ColumnProp[];
```
---
### `resolveColumnGroupingDropIndex`
```ts
export function resolveColumnGroupingDropIndex(
currentIndex: number,
measuredTargetIndex: number,
): number;
```
---
### `withColumnGroupings`
```ts
export function withColumnGroupings(
grouping: GroupingOptions | undefined,
props: ColumnProp[],
): GroupingOptions;
```
---
### `ColumnGroupPanelView`
```ts
class ColumnGroupPanelView {
setConfig(config: ColumnGroupPanelConfig);
render(groupings: ColumnProp[]);
showDropPreview(index: number, label: string);
clearDropPreview();
getDropIndex(clientX: number): number;
getPanelDropIndex(clientX: number, clientY: number): number | null;
destroy();
}
```
---
# Column Group Render Sync
URL: https://pro.rv-grid.com/api/column-group-render-sync/
Source: src/content/docs/api/column-group-render-sync.md
### `ensureColumnGroupRenderSyncPlugin`
```ts
export function ensureColumnGroupRenderSyncPlugin(
revogrid: HTMLRevoGridElement,
providers: PluginProviders,
): ColumnGroupRenderSyncPlugin;
```
---
### `ColumnGroupRenderSyncPlugin`
#### Dependencies
- **Event integration** `BEFORE_GROUP_HEADER_RENDER_EVENT`: Projects source-indexed column groups to visible render indexes during grouped header rendering.
- **Event integration** `COLUMN_UPDATED_EVENT`: Refreshes source-indexed group snapshots when columns are replaced.
```ts
class ColumnGroupRenderSyncPlugin {
refreshSourceGroups(type?: DimensionCols): void;
}
```
---
# Column Hide
URL: https://pro.rv-grid.com/api/column-hide/
Source: src/content/docs/api/column-hide.md
## Module Extensions
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
/**
* Event triggered when hidden columns are updated
*/
hiddencolumnsupdated: { hiddenColumns: Set }
}
```
---
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
/**
* Columns to hide by prop.
*/
hideColumns?: ColumnHideConfig;
/**
* Kebab-case alias for framework bindings.
*/
'hide-columns'?: ColumnHideConfig;
/**
* Columns to hide by prop.
* @deprecated Use `grid.hideColumns` instead.
*/
columnHide?: ColumnHideConfig;
/**
* Kebab-case alias for the legacy `columnHide` property.
* @deprecated Use `grid.hideColumns` or `hide-columns` instead.
*/
'column-hide'?: ColumnHideConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
Additional data property for column hide configuration
```ts
interface AdditionalData {
/**
* Additional data property for column hide configuration
* @deprecated Use `grid.hideColumns` instead.
* @example
* grid.additionalData = {
* // Hide columns with indices 1, 2, and 3
* hiddenColumns: [1, 2, 3],
* }
*/
hiddenColumns?: HiddenColumnsConfig
}
```
## Plugin API
### `ColumnHidePlugin`
The `ColumnHidePlugin` is a plugin for the RevoGrid framework that enables column hiding functionality
based on the `hide-columns` attribute, `hideColumns` property, or
`additionalData.hiddenColumns`. It allows users to hide specific columns
by providing their column `prop` values.
**Features**:
- Enables column hiding through the `hide-columns` attribute
- Supports runtime updates through the `hideColumns` property
- Supports legacy runtime updates through the `columnHide` property
- Supports hiding columns through `additionalData.hiddenColumns`
- Automatically updates when the attribute, property, or additionalData changes
- Preserves column state when columns are updated
- Supports multiple framework attribute formats:
- Web component: `hide-columns="name,age"`
- React: `hideColumns={['name', 'age']}`
- Angular/Vue/Svelte: bind `hideColumns` as an array
**Usage**:
```html
grid.hideColumns = ['name', 'age'];
grid.columnHide = ['name', 'age'];
```
#### Dependencies
- **Event integration** `COLUMN_UPDATED_EVENT`: Integrates with column update events to preserve hidden-column state.
- **Event integration** `COLUMN_DRAG_END_EVENT`: Keeps hidden-column trim order aligned after visible columns are moved.
- **Config integration** `grid.hideColumns`, `additionalData.hiddenColumns`: Reads direct hidden-column configuration from grid.hideColumns or legacy additionalData.hiddenColumns.
```ts
class ColumnHidePlugin {}
```
---
### `HiddenColumnsConfig`
The `HiddenColumnsConfig` type
specifies which columns should be hidden in the grid.
**Usage**:
- Import the `HiddenColumnsConfig` type to define hidden columns in RevoGrid components.
- Assign the desired hidden columns configuration to `grid.hideColumns` to control which columns
are hidden.
**Example:**
```typescript
:
```typescript
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnHidePlugin];
grid.hideColumns = [1, 2, 3]; // Hide columns with indices 1, 2, and 3
```
This configuration type allows developers to programmatically control which columns
are hidden in the grid, enhancing the flexibility of the grid's display options.
```
```ts
/**
* The `HiddenColumnsConfig` type
* specifies which columns should be hidden in the grid.
*
* **Usage**:
* - Import the `HiddenColumnsConfig` type to define hidden columns in RevoGrid components.
* - Assign the desired hidden columns configuration to `grid.hideColumns` to control which columns
* are hidden.
*
* @example:
* ```typescript
* import { ColumnHidePlugin } from '@revolist/revogrid-pro';
*
* const grid = document.createElement('revo-grid');
* grid.plugins = [ColumnHidePlugin];
*
* grid.hideColumns = [1, 2, 3]; // Hide columns with indices 1, 2, and 3
* ```
*
* This configuration type allows developers to programmatically control which columns
* are hidden in the grid, enhancing the flexibility of the grid's display options.
*/
export type HiddenColumnsConfig = ColumnProp[];
```
---
### `ColumnHideConfig`
```ts
export type ColumnHideConfig = HiddenColumnsConfig | string;
```
---
# Column Move With Groups
URL: https://pro.rv-grid.com/api/column-move-with-groups/
Source: src/content/docs/api/column-move-with-groups.md
### `ColumnMoveAdvancedPlugin`
```ts
class ColumnMoveAdvancedPlugin {
dragStart({ event, data }: DragStartEventDetails);
doMove(e: MouseEvent);
clearOrder();
clearSubscriptions();
onMouseUp(e: MouseEvent);
}
```
---
# Column Selection
URL: https://pro.rv-grid.com/api/column-selection/
Source: src/content/docs/api/column-selection.md
## Module Extensions
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
/** Fires before a pointer or keyboard column-selection intent is applied. */
beforecolumnselection: ColumnSelectionIntent
}
```
## Plugin API
### `ColumnSelectionPlugin`
Adds spreadsheet-style whole-column selection to RevoGrid.
Header clicks select columns, Shift extends an anchored span, Ctrl/Cmd
toggles disjoint columns, pointer drag selects a span, and Ctrl+Space selects
the focused cell's column. Sort/filter/resize affordances keep their native
behavior and do not change the column selection.
#### Dependencies
- **Auto-installed** `MultiRangeSelectionPlugin`: Uses MultiRangeSelectionPlugin for disjoint range rendering and clipboard behavior.
- **Event integration** `column-selection-handoff`: Clears mutually exclusive interactive row-header selection before selecting columns.
- **Event integration** `COLUMN_DRAG_START_EVENT`, `COLUMN_DRAG_MOVE_EVENT`, `COLUMN_DRAG_END_EVENT`: Keeps whole-column selection isolated from and aligned after column-move gestures.
```ts
class ColumnSelectionPlugin {
/** Returns selected column props in current visual order. */
getSelectedColumns(): ColumnProp[];
/** Clears private column state before row-header selection takes ownership. */
clearFromRowHeader();
/** Clears private column state before select-all takes ownership. */
clearFromSelectAll();
destroy(): void;
}
```
---
### `resolveColumnSelection`
```ts
export function resolveColumnSelection({
ordered,
selected,
anchor,
target,
shift,
additive,
}: ResolveColumnSelectionOptions): ResolvedColumnSelection;
```
---
### `reconcileSelectedColumns`
```ts
export function reconcileSelectedColumns(
ordered: ColumnSelectionAddress[],
selected: Iterable,
);
```
---
### `buildColumnSelectionRanges`
```ts
export function buildColumnSelectionRanges(
ordered: ColumnSelectionAddress[],
selected: Iterable,
rowLengths: Partial>,
): MultiRangeSelectionRange[];
```
---
### `findActiveRangeIndex`
```ts
export function findActiveRangeIndex(
ranges: MultiRangeSelectionRange[],
ordered: ColumnSelectionAddress[],
activeId: string | null,
);
```
---
### `ColumnSelectionAddress`
```ts
export type ColumnSelectionAddress = {
id: string;
prop: ColumnProp;
index: number;
type: DimensionCols;
};
```
---
### `ColumnSelectionMode`
```ts
export type ColumnSelectionMode = 'replace' | 'extend' | 'toggle' | 'add';
```
---
### `ResolveColumnSelectionOptions`
```ts
export type ResolveColumnSelectionOptions = {
ordered: ColumnSelectionAddress[];
selected: Iterable;
anchor: string | null;
target: string;
shift: boolean;
additive: boolean;
};
```
---
### `ResolvedColumnSelection`
```ts
export type ResolvedColumnSelection = {
selected: Set;
anchor: string;
mode: ColumnSelectionMode;
};
```
---
### `ColumnSelectionSource`
```ts
export type ColumnSelectionSource = 'header' | 'drag' | 'keyboard';
```
---
### `ColumnSelectionIntent`
```ts
export type ColumnSelectionIntent = {
source: ColumnSelectionSource;
mode: ColumnSelectionMode;
column: ColumnRegular;
selectedColumns: ColumnProp[];
anchor: ColumnProp;
originalEvent: MouseEvent | PointerEvent | KeyboardEvent;
};
```
---
### `ColumnSelectionHandoffProvider`
Runtime contract used to clear mutually exclusive row-header state.
```ts
interface ColumnSelectionHandoffProvider {
clearFromColumnHeader(): void
}
```
---
# Column Stretch
URL: https://pro.rv-grid.com/api/column-stretch/
Source: src/content/docs/api/column-stretch.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
/**
* Direct column stretch configuration.
*/
stretch?: StretchConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
Additional data property for column stretch configuration
```ts
interface AdditionalData {
/**
*
* Additional data property for column stretch configuration
* @deprecated Use `grid.stretch` instead.
* @example
* grid.additionalData = {
* // Stretch all columns to utilize available space
* stretch: 'all',
* // Stretch the first column to utilize available space
* stretch: 1,
* // Stretch the last column to utilize available space
* stretch: 'last',
* // Stretch the last column and preserve column sizes when columns are replaced
* stretch: {
* mode: 'last',
* preserveSizesOnColumnChange: true,
* },
* }
*/
stretch?: StretchConfig
}
```
## Plugin API
### `ColumnStretchPlugin`
The ColumnStretchPlugin is a RevoGrid plugin designed to dynamically adjust column
widths based on available space within the grid. It ensures that extra space is
efficiently utilized by increasing the width of specified columns, enhancing the
presentation of data without manual resizing.
**Key Features**:
- Automatically stretches the width of columns to fill any remaining space in the grid.
- Supports configurations for stretching all columns, a specific column by index,
or only the last column.
- Observes direct `grid.stretch` and legacy `additionalData.stretch` config
updates, then adjusts column sizes based on grid dimensions or configuration changes.
- Utilizes `ResizeObserver` and a debounced `applyStretch` function to efficiently
manage size recalculations during viewport size changes.
**Usage**:
- Integrate the plugin into a RevoGrid instance by adding it to the grid's plugin array.
- Configure the stretching behavior through direct `grid.stretch` or legacy
`additionalData.stretch`, specifying modes like 'none', 'all', or a specific index.
### Example
```typescript
import { ColumnStretchPlugin } from './column-stretch';
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnStretchPlugin];
grid.stretch = 'last'; // Stretch the last column to fit available space
```
By using this plugin, developers can enhance the adaptability and visual layout
of their RevoGrid, ensuring optimal use of grid space and improving data display
without manual intervention.
#### Dependencies
- **Config integration** `auto-size-column`: Skips columns marked with autoSize when applying stretch sizing.
- **Config integration** `additionalData.stretch`: Reads legacy stretch configuration from additionalData.stretch.
```ts
class ColumnStretchPlugin {
destroy(): void;
}
```
---
### `StretchMode`
The `StretchConfig` type
specifies how extra space in the grid should be utilized by adjusting the width
of columns.
**Usage**:
- Import the `StretchConfig` type to define stretching behavior in RevoGrid components.
- Assign the desired stretching configuration to the grid's `additionalData.stretch`
property to control how columns adjust to available space.
**Example:**
```typescript
:
```typescript
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnStretchPlugin];
grid.additionalData = {
stretch: 'all', // Stretch all columns to utilize available space
};
```
This configuration type allows developers to customize how columns adapt to
changes in grid size, ensuring optimal data presentation and grid layout.
```
```ts
// column-stretch/stretch.types.ts
/**
* The `StretchConfig` type
* specifies how extra space in the grid should be utilized by adjusting the width
* of columns.
*
* **Usage**:
* - Import the `StretchConfig` type to define stretching behavior in RevoGrid components.
* - Assign the desired stretching configuration to the grid's `additionalData.stretch`
* property to control how columns adjust to available space.
*
* @example:
* ```typescript
* import { ColumnStretchPlugin } from '@revolist/revogrid-pro';
*
* const grid = document.createElement('revo-grid');
* grid.plugins = [ColumnStretchPlugin];
*
* grid.additionalData = {
* stretch: 'all', // Stretch all columns to utilize available space
* };
* ```
*
* This configuration type allows developers to customize how columns adapt to
* changes in grid size, ensuring optimal data presentation and grid layout.
*/
export type StretchMode = 'none' | 'last' | 'all' | number;
```
---
### `StretchOptions`
```ts
export type StretchOptions = {
/**
* Stretch mode.
*/
mode: StretchMode;
/**
* Preserve rendered column sizes for matching column props when columns are replaced.
* Matching uses the column dimension type and `prop`, so reordered columns keep their
* previous width while new columns use their configured/default width.
*/
preserveSizesOnColumnChange?: boolean;
};
```
---
### `StretchConfig`
```ts
export type StretchConfig = false | StretchMode | StretchOptions;
```
---
### `ColumnStretchSizePreserve`
```ts
class ColumnStretchSizePreserve {
snapshot();
restore(columns: ColumnCollection['columns']);
}
```
---
# Column Type Boolean
URL: https://pro.rv-grid.com/api/column-type-boolean/
Source: src/content/docs/api/column-type-boolean.md
### `formatBooleanValue`
```ts
export function formatBooleanValue(
value: unknown,
localeText: Pick = BOOLEAN_COLUMN_TYPE_LOCALE_TEXT,
): string;
```
---
### `createBooleanDropdownOptions`
```ts
export function createBooleanDropdownOptions(
localeText: BooleanColumnTypeLocaleText = BOOLEAN_COLUMN_TYPE_LOCALE_TEXT,
): DropdownOption[];
```
---
### `createBooleanColumnType`
```ts
export function createBooleanColumnType(
options: BooleanColumnTypeOptions = {},
): BooleanColumnType;
```
---
### `BooleanColumnValue`
```ts
export type BooleanColumnValue = boolean | null | undefined;
```
---
### `BooleanColumnTypeLocaleText`
```ts
interface BooleanColumnTypeLocaleText {
yes: string;
no: string;
notSet: string;
editorAriaLabel: string
}
```
---
### `BooleanColumnTypeOptions`
```ts
interface BooleanColumnTypeOptions {
localeText?: Partial
}
```
---
### `BOOLEAN_COLUMN_TYPE_LOCALE_TEXT`
```ts
BOOLEAN_COLUMN_TYPE_LOCALE_TEXT: {
yes: string;
no: string;
notSet: string;
editorAriaLabel: string;
};
```
---
### `BooleanDropdownEditor`
Dropdown editor adapter that presents every nullish boolean as Not set.
```ts
class BooleanDropdownEditor {}
```
---
### `BooleanColumnType`
Tri-state boolean column type backed by the Pro dropdown editor.
Values remain `true`, `false`, or `null`; locale text affects display only.
Missing and null values render as empty cells while the editor exposes the
localized Not set option.
```ts
class BooleanColumnType {}
```
---
# Column Type Progress
URL: https://pro.rv-grid.com/api/column-type-progress/
Source: src/content/docs/api/column-type-progress.md
### `ProgressColumnType`
The `ProgressColumnType` class implements a custom column type for the RevoGrid, designed
to visually display numeric values as progress bars within grid cells. It extends the
functionality of the standard `ColumnType` by providing validation and rendering logic
specific to progress indicators.
**Features**:
- Validation: Ensures the value is a number between 0 and 100, representing a valid
percentage for progress visualization.
- Custom cell rendering: Uses a `cellTemplate` function to render the progress bar,
adapting the visual fill based on the cell value.
How to use:
- Integrate `ProgressColumnType` into your grid's column configuration to enable
progress bar visualization.
- Assign the `cellTemplate` of this class to the corresponding column definition to
render the progress bars.
### Example
```typescript
import { ProgressColumnType } from './progress-column-type';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'progress',
name: 'Progress',
cellTemplate: ProgressColumnType.prototype.cellTemplate, // Use progress bar template
},
];
```
This class enhances user experience by providing a clear visual representation of
progress data directly within the grid interface, making data interpretation more
intuitive and visually appealing.
```ts
class ProgressColumnType {}
```
---
# Column Type Rate
URL: https://pro.rv-grid.com/api/column-type-rate/
Source: src/content/docs/api/column-type-rate.md
### `RatingColumnType`
The RatingColumnType class defines a custom column type for the RevoGrid, specifically
designed to handle and render rating values as a series of star icons. This custom type
includes a validation method to ensure rating values are within an acceptable range (0 to 5)
and a `cellTemplate` method for rendering the visual representation of these ratings.
**Key Features**:
- Validation: Ensures rating values are between 0 and 5, inclusive.
- Custom cell rendering: Displays rating as stars, using a `cellTemplate` function to map
numeric values to visual elements.
**Usage**:
- Implement the RatingColumnType in the grid's column configuration to apply this rating
visualization logic.
- Utilize the `cellTemplate` function provided by this class within your column definitions
to render ratings.
### Example
```typescript
import { RatingColumnType } from './rating-column-type';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'rating',
name: 'User Rating',
cellTemplate: RatingColumnType.prototype.cellTemplate, // Apply custom rating cell template
},
];
```
This class enhances the grid's capability to visually represent rating data, facilitating
a more intuitive user experience for reviewing rating values.
```ts
class RatingColumnType {}
```
---
# Conditional Formatting
URL: https://pro.rv-grid.com/api/conditional-formatting/
Source: src/content/docs/api/conditional-formatting.md
### `ConditionalFormattingDialogPlugin`
#### Dependencies
- **Auto-installed** `DialogPlugin`: Uses the shared Pro dialog runtime for conditional-formatting editors.
```ts
class ConditionalFormattingDialogPlugin {
open(options: ConditionalFormattingDialogOpenOptions): void;
close(): void;
}
```
---
### `createConditionalFormattingEditor`
```ts
export function createConditionalFormattingEditor({
document,
localeText,
operators,
initialValue,
}: ConditionalFormattingEditorOptions): ConditionalFormattingEditor;
```
---
### `ConditionalFormattingEditor`
```ts
interface ConditionalFormattingEditor {
readonly element: HTMLElement;
readonly secondValueField: HTMLLabelElement;
readonly preview: HTMLElement;
getDraft(): ConditionalFormattingDialogDraft;
isValid(): boolean
}
```
---
### `ConditionalFormattingOperatorOption`
```ts
interface ConditionalFormattingOperatorOption {
readonly value: string;
readonly label: string;
readonly usesSecondValue?: boolean
}
```
---
### `ConditionalFormattingDialogDraft`
```ts
interface ConditionalFormattingDialogDraft {
readonly operator: string;
readonly value: string;
readonly secondValue: string;
readonly backgroundColor: string;
readonly textColor: string
}
```
---
### `ConditionalFormattingDialogLocaleText`
```ts
interface ConditionalFormattingDialogLocaleText {
readonly description: string;
readonly rule: string;
readonly condition: string;
readonly value: string;
readonly secondValue: string;
readonly valuePlaceholder: string;
readonly secondValuePlaceholder: string;
readonly appearance: string;
readonly background: string;
readonly text: string;
readonly preview: string;
readonly sampleValue: string;
readonly clearRules: string;
readonly cancel: string;
readonly apply: string
}
```
---
### `ConditionalFormattingDialogOpenOptions`
```ts
interface ConditionalFormattingDialogOpenOptions {
readonly title: string;
readonly closeLabel: string;
readonly localeText: ConditionalFormattingDialogLocaleText;
readonly operators: readonly ConditionalFormattingOperatorOption[];
readonly initialValue?: Partial;
readonly onApply: (draft: ConditionalFormattingDialogDraft) => void;
readonly onClear: () => void
}
```
---
# Context Menu
URL: https://pro.rv-grid.com/api/context-menu/
Source: src/content/docs/api/context-menu.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
/**
* Context menu configuration for row and body-cell targets.
*/
rowContextMenu?: ContextMenuConfig;
/**
* Context menu configuration for column-header targets.
*/
columnContextMenu?: ContextMenuConfig;
/**
* Legacy/shared context menu configuration. Used as a fallback when target-specific
* row/column configs are not provided.
*/
contextMenu?: ContextMenuConfig;
/** Kebab-case alias for framework/native custom element bindings. */
'row-context-menu'?: ContextMenuConfig;
/** Kebab-case alias for framework/native custom element bindings. */
'column-context-menu'?: ContextMenuConfig;
/** Kebab-case alias for framework/native custom element bindings. */
'context-menu'?: ContextMenuConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* The context menu configuration for row and body-cell targets.
*
* @example
* ```typescript
* grid.additionalData = {
* rowContextMenu: { items: [{ name: 'Edit', action: () => console.log('Edit action') }] },
* };
* ```
*
* Prefer the direct `grid.rowContextMenu` property when the framework wrapper supports it.
*/
rowContextMenu?: ContextMenuConfig;
/**
* The context menu configuration for column-header targets.
*
* @example
* ```typescript
* grid.additionalData = {
* columnContextMenu: { items: [{ name: 'Autosize', action: () => console.log('Autosize') }] },
* };
* ```
*
* Prefer the direct `grid.columnContextMenu` property when the framework wrapper supports it.
*/
columnContextMenu?: ContextMenuConfig
}
```
## Plugin API
### `getContextMenuPrimaryShortcut`
```ts
export function getContextMenuPrimaryShortcut(
key: string,
platform = globalThis.navigator?.platform,
): string;
```
---
### `getContextMenuFilterPlugin`
Locate the active Core or Pro filter runtime without forcing filter setup.
```ts
export function getContextMenuFilterPlugin(
context: ContextMenuActionContext,
): ContextMenuFilterPlugin | undefined;
```
---
### `openContextMenuColumnFilter`
Open the existing filter popup while preserving the original synchronous API.
```ts
export function openContextMenuColumnFilter(
context: ContextMenuActionContext,
column: ColumnRegular,
): boolean;
```
---
### `openContextMenuColumnFilterAsync`
Open the existing filter popup and await its full async lifecycle.
```ts
export async function openContextMenuColumnFilterAsync(
context: ContextMenuActionContext,
column: ColumnRegular,
): Promise;
```
---
### `createContextMenuCellFilterItems`
Build the standard value-filter submenu used by the Data Grid preset.
Returns no items when the focused column or active filter runtime cannot
apply a filter.
```ts
export function createContextMenuCellFilterItems(
options: ContextMenuCellFilterOptions,
): ContextMenuItem[];
```
---
### `applyContextMenuCellValueFilter`
Apply the standard keep/exclude predicate used by context-menu filtering.
```ts
export async function applyContextMenuCellValueFilter(
filter: ContextMenuCellFilterRuntime,
column: ColumnRegular,
model: DataType,
exclude: boolean,
): Promise;
```
---
### `ContextMenuCellFilterRuntime`
```ts
export type ContextMenuCellFilterRuntime = {
multiFilterItems: Record>;
onFilterChange(detail: unknown): void | Promise;
onFilterReset(prop?: ColumnProp): void | Promise;
};
```
---
### `ContextMenuCellFilterLabels`
```ts
interface ContextMenuCellFilterLabels {
readonly keepOnly?: string;
readonly exclude?: string;
readonly open?: string;
readonly clear?: string
}
```
---
### `ContextMenuCellFilterOptions`
```ts
interface ContextMenuCellFilterOptions {
readonly actionContext: ContextMenuActionContext;
readonly column: ColumnRegular;
readonly model: DataType;
readonly labels?: ContextMenuCellFilterLabels
}
```
---
### `ContextMenuPlugin`
Adds configurable context menus to RevoGrid.
The plugin supports separate `rowContextMenu` and `columnContextMenu`
configurations while keeping `contextMenu` as a legacy fallback. Column menus
receive the current column, virtual column index, and column section, so a
single `columnContextMenu` can resolve different menu items for regular,
left-pinned, right-pinned, or specific columns.
**Example:**
```typescript
import { ContextMenuPlugin } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [ContextMenuPlugin];
grid.rowContextMenu = { items: [{ name: 'Delete row' }] };
grid.columnContextMenu = {
items: [{ name: 'Autosize column' }],
resolve(context) {
if (context.target === 'column' && context.columnType === 'colPinStart') {
return { items: [{ name: 'Unpin left column' }] };
}
},
};
```
#### Dependencies
- **Event integration** `RowHeaderPlugin`: Integrates with RowHeaderPlugin row menu events when present.
```ts
class ContextMenuPlugin {
/**
* Registers additive items that are resolved whenever a context menu opens.
*
* The application config and its `resolve` callback run first. Contributions
* are then appended in ascending `order`, with registration order breaking
* ties. The returned disposer is safe to call more than once.
*/
registerContribution(contribution: ContextMenuContribution): () => void;
/**
* Applies defaults to a partial context menu config.
*
* @param config - User-provided menu config.
* @param targetSelectors - Default selectors for the target-specific config.
*/
getConfig(config: Partial = {}, targetSelectors: string[] = ['.rgCell']);
/**
* Resolves a rendered header target to the same semantic column context used
* by the shared context menu.
*/
resolveColumnContext(
target: HTMLElement,
event?: MouseEvent,
): ColumnContextMenuOpenContext;
/**
* Hides the context menu popup without clearing the active config.
*/
close(restoreFocus = true);
/**
* Opens the context menu for a DOM target.
*
* If `menuTarget` is provided, that target config is used directly. Otherwise
* the plugin inspects the DOM target and chooses the column menu before the row
* menu. The selected config is passed through `resolve` before rendering.
*
* @param target - Element that should anchor or identify the menu target.
* @param event - Pointer event used for positioning and resolver context.
* @param menuTarget - Optional explicit target used by row-header menu events.
*/
async open(
target: EventTarget | null,
event?: MouseEvent,
menuTarget?: ContextMenuTarget,
cell?: ContextMenuCellContext,
);
clearSubscriptions();
destroy();
}
```
---
### `ContextMenuTarget`
Supported high-level targets for the context menu.
`row` covers body cells and row-menu buttons. `column` covers column header
cells, including pinned column sections.
```ts
/**
* Supported high-level targets for the context menu.
*
* `row` covers body cells and row-menu buttons. `column` covers column header
* cells, including pinned column sections.
*/
export type ContextMenuTarget = 'row' | 'column';
```
---
### `ContextMenuOpenContextBase`
Shared context passed to dynamic menu resolvers and menu item actions.
```ts
/**
* Shared context passed to dynamic menu resolvers and menu item actions.
*/
export type ContextMenuOpenContextBase = {
/** The active menu target type. */
target: ContextMenuTarget;
/** Element that caused the menu to open. */
triggerElement: HTMLElement;
/** Mouse event that opened the menu, when opened from pointer interaction. */
triggerEvent?: MouseEvent;
/** Grid element that owns the plugin instance. */
revogrid: HTMLRevoGridElement;
/** Core plugin providers for data, dimensions, selection, columns, viewport, and plugins. */
providers: PluginProviders;
};
```
---
### `ContextMenuColumnGroup` (Extended from `index.ts`)
```ts
export type ContextMenuColumnGroup = Group & {
/** Grouping row depth in the header. */
depth?: number;
};
```
---
### `ColumnContextMenuOpenContext` (Extended from `index.ts`)
Context available when a menu opens from a column header target.
```ts
/**
* Context available when a menu opens from a column header target.
*/
export type ColumnContextMenuOpenContext = ContextMenuOpenContextBase & {
target: 'column';
/** Column model resolved from the header section and virtual column index. */
column?: ColumnRegular;
/** Column group model resolved when opening from a grouped header cell. */
columnGroup?: ContextMenuColumnGroup;
/** Physical column indexes covered by this menu target when it is a grouped header cell. */
columnIndexes?: number[];
/** Grouping row depth when opening from a grouped header cell. */
columnGroupDepth?: number;
/** Virtual column index inside the current column section. */
columnIndex?: number;
/** Column section that opened the menu: regular, left-pinned, or right-pinned. */
columnType?: DimensionCols;
};
```
---
### `RowContextMenuOpenContext` (Extended from `index.ts`)
Context available when a menu opens from a row/body target.
```ts
/**
* Context available when a menu opens from a row/body target.
*/
export type RowContextMenuOpenContext = ContextMenuOpenContextBase & {
target: 'row';
/**
* Exact body cell that opened the menu. This remains distinct from the
* current selection focus when a secondary click intentionally preserves an
* existing range.
*/
cell?: ContextMenuCellContext;
};
```
---
### `ContextMenuCellContext`
```ts
export type ContextMenuCellContext = Readonly<
Omit, 'colType'> & {
/** Data-column section or Core's dedicated row-header section. */
colType: DimensionCols | 'rowHeaders';
}
>;
```
---
### `ContextMenuOpenContext`
Union of all target-specific open contexts.
```ts
/**
* Union of all target-specific open contexts.
*/
export type ContextMenuOpenContext = ColumnContextMenuOpenContext | RowContextMenuOpenContext;
```
---
### `ContextMenuConfigResolver`
Dynamic menu resolver.
Return `undefined` to keep the base config, return a partial config to override
settings/items for this invocation, or return `null` to suppress the menu.
**Example:**
```typescript
const columnContextMenu = {
items: defaultItems,
resolve(context) {
if (context.target === 'column' && context.columnType === 'colPinStart') {
return { items: pinnedColumnItems };
}
},
};
```
```ts
/**
* Dynamic menu resolver.
*
* Return `undefined` to keep the base config, return a partial config to override
* settings/items for this invocation, or return `null` to suppress the menu.
*
* @example
* ```ts
* const columnContextMenu = {
* items: defaultItems,
* resolve(context) {
* if (context.target === 'column' && context.columnType === 'colPinStart') {
* return { items: pinnedColumnItems };
* }
* },
* };
* ```
*/
export type ContextMenuConfigResolver = (
context: ContextMenuOpenContext,
) => Partial | null | undefined;
```
---
### `ContextMenuContribution`
Additive context-menu items owned by an independent plugin or feature.
Contributions are resolved alongside the active application config and are
composed before or after it without mutating that config.
```ts
/**
* Additive context-menu items owned by an independent plugin or feature.
*
* Contributions are resolved alongside the active application config and are
* composed before or after it without mutating that config.
*/
export type ContextMenuContribution = {
/** Identifier unique within one `ContextMenuPlugin` instance. */
readonly id: string;
/** Ascending contribution order. Registration order breaks ties. */
readonly order?: number;
/**
* Extra DOM selectors owned by this contribution. They are merged with the
* active row or column menu config without changing application settings.
*/
readonly targets?: Partial>
| (() => Partial>);
/**
* Places the contribution before or after the resolved application items.
* Defaults to `append` for backward compatibility.
*/
readonly placement?: 'prepend' | 'append';
/**
* Optional application-item filter for contexts owned by this contribution.
* Return `false` to suppress a base item without mutating its source config.
*/
readonly filterBaseItem?: (
item: ContextMenuItem,
context: ContextMenuOpenContext,
) => boolean;
/** Optional policy applied to the complete menu after all contributions compose. */
readonly filterItem?: (
item: ContextMenuItem,
context: ContextMenuOpenContext,
) => boolean;
/**
* Accessible popup name used when the application config does not provide
* one. A later, more specific contribution overrides an earlier label.
*/
readonly ariaLabel?: string | ((context: ContextMenuOpenContext) => string | undefined);
/** Resolves items for the current row or column menu invocation. */
readonly resolve: (context: ContextMenuOpenContext) => ContextMenuItem[];
};
```
---
### `ContextMenuActionContext`
Context passed as the fifth argument to `ContextMenuItem.action`.
```ts
/**
* Context passed as the fifth argument to `ContextMenuItem.action`.
*/
export type ContextMenuActionContext = {
/** Grid element that owns the plugin instance. */
revogrid: HTMLRevoGridElement,
/** Core plugin providers for data, dimensions, selection, columns, viewport, and plugins. */
providers: PluginProviders,
/** Context captured when this menu was opened. */
menu?: ContextMenuOpenContext,
};
```
---
### `ContextMenuItemStateResolver`
```ts
export type ContextMenuItemStateResolver = (
item: ContextMenuItem,
focused?: Cell | null,
range?: RangeArea | null,
) => boolean;
```
---
### `ContextMenuChildrenResolver`
```ts
export type ContextMenuChildrenResolver = (
focused?: Cell | null,
range?: RangeArea | null,
context?: ContextMenuActionContext,
) => ContextMenuItem[];
```
---
### `ContextMenuItem`
Single menu item definition.
```ts
/**
* Single menu item definition.
*/
export type ContextMenuItem = {
/** Stable identifier used by consumers and renderers. */
id?: string;
/**
* Item semantics. `separator` items ignore action, icon, checked, and
* children. `heading` items are non-interactive menu context. `control`
* items host interactive form content supplied by `template` without menu
* activation or automatic closing. Omitted kind renders a standard menu
* item.
*/
kind?: 'item' | 'checkbox' | 'radio' | 'separator' | 'heading' | 'control';
/** Text or dynamic text rendered inside the item. */
name: string | ((focused?: Cell | null, range?: RangeArea | null) => string);
/** Secondary text rendered below a heading label. */
subtitle?: string;
/** Accessible label when visible text is insufficient. */
ariaLabel?: string;
/** Keyboard shortcut hint rendered at the trailing edge. */
shortcut?: string;
/** Inline SVG rendered inside the trailing shortcut keycap. */
shortcutIcon?: string;
/** Native tooltip text, useful for explaining a disabled action. */
title?: string;
/** CSS class added to the `` item. */
class?: string;
/** Icon rendered before the item name. Accepts CSS classes or inline SVG markup. */
icon?: string;
/** Apply the shared destructive-action presentation. */
danger?: boolean;
/** CSS class added to the ` ` item, optionally calculated from selection state. */
rowClass?: string | ((item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null) => string);
/** Hide the item statically or from current selection state. */
hidden?: boolean | ContextMenuItemStateResolver;
/** Disable the item statically or from current selection state while keeping it visible. */
disabled?: boolean | ContextMenuItemStateResolver;
/** Checked state for checkbox and radio items. */
checked?: boolean | ContextMenuItemStateResolver;
/** Logical group for radio items. */
radioGroup?: string;
/** Nested submenu items or a resolver evaluated for the current menu context. */
children?: ContextMenuItem[] | ContextMenuChildrenResolver;
/** Called when the item is selected. */
action?: (event: MouseEvent, focused?: Cell | null, range?: RangeArea | null, close?: () => void, context?: ContextMenuActionContext) => void;
/** Keep the popup open after this item action runs. */
keepOpen?: boolean;
/** Custom item renderer. */
template?: (h: HyperFunc, item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null, close?: () => void) => any;
};
```
---
### `ContextMenuConfig`
Context menu configuration.
Use `rowContextMenu` for row/body targets and `columnContextMenu` for header
targets. `resolve` can refine either menu at open time for column sections,
individual columns, or any other target-specific condition.
```ts
/**
* Context menu configuration.
*
* Use `rowContextMenu` for row/body targets and `columnContextMenu` for header
* targets. `resolve` can refine either menu at open time for column sections,
* individual columns, or any other target-specific condition.
*/
export type ContextMenuConfig = {
/** Items rendered in the popup. */
items: ContextMenuItem[];
/** Accessible name for the root menu. */
ariaLabel?: string;
/** Open the menu on the native `contextmenu` event. Defaults to `true`. */
rightClick?: boolean;
/** Open the menu on left click. Defaults to `false`. */
leftClick?: boolean;
/** CSS selectors that can trigger this menu. */
targetSelectors?: string[];
/** Position the popup relative to the target element instead of pointer coordinates. */
anchorToTarget?: boolean;
/**
* Resolves a menu override for the current target before the menu opens.
* Return `null` to suppress the menu for that target, `undefined` to use the base config,
* or a partial config to override items/settings for this invocation.
*/
resolve?: ContextMenuConfigResolver;
};
```
---
### `resolveColumnGroupContext`
Resolve a rendered grouped-header cell to its source-backed group model.
```ts
export function resolveColumnGroupContext({
columnType,
header,
headerCell,
providers,
}: ResolveColumnGroupContextOptions): ColumnGroupContext;
```
---
### `HeaderWithColumnGroups` (Extended from `context-menu.column-group-target.ts`)
```ts
export type HeaderWithColumnGroups = HTMLElement & {
type?: DimensionCols | 'rowHeaders';
colData?: ReturnType;
groups?: Record;
};
```
---
### `resolveColumnContext`
Resolve ordinary and grouped column-header targets into one public context.
```ts
export function resolveColumnContext({
event,
providers,
revogrid,
target,
}: ResolveColumnContextOptions): ColumnContextMenuOpenContext;
```
---
### `createContextMenuConfig`
Apply stable defaults to application and target-specific menu configs.
```ts
export function createContextMenuConfig(
config: Partial = {},
targetSelectors: string[] = ['.rgCell'],
): ContextMenuConfig;
```
---
### `resolveContextMenuConfig`
Compose application items and contributions into the final visible menu.
```ts
export function resolveContextMenuConfig({
config,
context,
contributions,
providers,
revogrid,
}: {
config: ContextMenuConfig;
context: ContextMenuOpenContext;
contributions: ContextMenuContributionRegistry;
providers: PluginProviders;
revogrid: HTMLRevoGridElement;
}): ContextMenuConfig | undefined;
```
---
### `ContextMenuContributionRegistry`
Instance-owned contribution registration, ordering, and resolution.
```ts
class ContextMenuContributionRegistry {
register(contribution: ContextMenuContribution): () => void;
clear(): void;
getTargetSelectors(target: ContextMenuTarget): string[];
resolve(context: ContextMenuOpenContext): ResolvedContributionItems;
}
```
---
### `ContextMenuPointerIntent`
Keeps each open submenu stable while the pointer crosses its sibling rows.
It does no continuous pointer tracking: decisions happen only on item entry.
```ts
class ContextMenuPointerIntent {
enter(
item: HTMLElement,
event: Pick,
activate: () => void,
): void;
enterSubmenu(item: HTMLElement): void;
reset(): void;
}
```
---
### `showContextMenuPopup`
Opens a context-menu element in the browser top layer when available.
Browsers without the Popover API retain the existing display-based popup.
```ts
export function showContextMenuPopup(element: TopLayerPopupElement): ContextMenuPopupMode;
```
---
### `hideContextMenuPopup`
Hides a popup opened by `showContextMenuPopup`; safe to call repeatedly.
```ts
export function hideContextMenuPopup(
element: TopLayerPopupElement,
mode?: ContextMenuPopupMode,
): void;
```
---
### `calculateContextMenuPosition`
Resolves menu coordinates in the coordinate space of the supplied boundary.
The caller converts the result to grid-local coordinates for the fallback.
```ts
export function calculateContextMenuPosition({
anchorRect,
menuRect,
boundary,
anchorToTarget,
pointer,
inset = 0,
}: ContextMenuPositionOptions): { top: number; left: number };
```
---
### `ContextMenuPopupMode`
```ts
export type ContextMenuPopupMode = TopLayerPopupMode;
```
---
### `ContextMenuBoundary`
```ts
export type ContextMenuBoundary = Pick<
DOMRect,
'top' | 'right' | 'bottom' | 'left' | 'width' | 'height'
>;
```
---
### `ContextMenuPositionOptions`
```ts
export type ContextMenuPositionOptions = {
anchorRect: Pick;
menuRect: Pick;
boundary: ContextMenuBoundary;
anchorToTarget: boolean;
pointer?: Pick;
inset?: number;
};
```
---
### `shouldPreserveRowFocus`
Preserve an existing selected row range for a secondary-click menu.
```ts
export function shouldPreserveRowFocus({
detail,
selectedRange,
}: {
detail?: BeforeSaveDataDetails;
selectedRange?: RangeArea | null;
}): boolean;
```
---
### `shouldPreserveAppliedRange`
Preserve an existing selected range while Core applies pointer selection.
```ts
export function shouldPreserveAppliedRange({
nextRange,
selectedRange,
}: {
nextRange?: RangeArea;
selectedRange?: RangeArea | null;
}): boolean;
```
---
### `isSecondaryMouseEvent`
```ts
export function isSecondaryMouseEvent(event: unknown): event is MouseEvent;
```
---
### `getPendingPointerCell`
```ts
export function getPendingPointerCell(
pending: PendingPointerCell | undefined,
target: HTMLElement,
): ContextMenuCellContext | undefined;
```
---
### `resolveRowContext`
Resolve ordinary, row-header, grouping-row, and focused row contexts.
```ts
export function resolveRowContext({
cell,
event,
pendingCell,
providers,
revogrid,
target,
}: ResolveRowContextOptions): RowContextMenuOpenContext;
```
---
### `isResolvedGroupingRowContext`
```ts
export function isResolvedGroupingRowContext(
context: ContextMenuOpenContext,
): boolean;
```
---
### `toContextMenuCell`
```ts
export function toContextMenuCell(
detail: Pick<
BeforeSaveDataDetails,
| 'prop'
| 'model'
| 'column'
| 'rowIndex'
| 'colIndex'
| 'colType'
| 'type'
| 'value'
>,
): ContextMenuCellContext;
```
---
### `resolveFocusedCell`
Resolve the currently focused body cell when Core emits no pointer payload.
```ts
export function resolveFocusedCell(
providers: PluginProviders,
): ContextMenuCellContext | undefined;
```
---
### `PendingPointerCell`
```ts
export type PendingPointerCell = {
cell: ContextMenuCellContext;
target: EventTarget | null;
};
```
---
### `resolveGroupingRowCellContext`
Resolves Core's full-width synthetic group renderer to a semantic cell-like
context. Group rows are intentionally not focusable and therefore do not
emit the normal `beforecellfocus` payload used by ordinary body cells.
```ts
export function resolveGroupingRowCellContext(
target: HTMLElement,
providers: PluginProviders,
): ContextMenuCellContext | undefined;
```
---
### `resolveRowHeaderCellContext`
Resolves a rendered row-header cell when Core does not emit the ordinary
`beforecellfocus` payload. Row headers share the row data store but live in
their own column viewport, so their context must be reconstructed from the
rendered virtual row index.
```ts
export function resolveRowHeaderCellContext(
target: HTMLElement,
providers: PluginProviders,
): ContextMenuCellContext | undefined;
```
---
### `isRowHeaderCellTarget`
Whether a target belongs to Core's dedicated row-header viewport.
```ts
export function isRowHeaderCellTarget(target: HTMLElement): boolean;
```
---
### `DEFAULT_ROW_CONTEXT_MENU_TARGET_SELECTORS`
Core body targets that have row context-menu semantics.
```ts
DEFAULT_ROW_CONTEXT_MENU_TARGET_SELECTORS: string[];
```
---
### `resetContextMenuPointerIntent`
```ts
export function resetContextMenuPointerIntent(element?: HTMLElement): void;
```
---
### `resetContextMenuInteractionState`
```ts
export function resetContextMenuInteractionState(element?: HTMLElement): void;
```
---
### `renderMenuItemIcon`
```ts
export function renderMenuItemIcon(icon?: string);
```
---
### `normalizeContextMenuItems`
Resolves dynamic children and removes hidden, empty, leading, trailing, and
consecutive separators without mutating application-owned menu items.
```ts
export function normalizeContextMenuItems(
items: ContextMenuItem[],
focused?: Cell | null,
range?: RangeArea | null,
context?: ContextMenuActionContext,
): ContextMenuItem[];
```
---
### `calculateContextSubmenuPosition`
```ts
export function calculateContextSubmenuPosition(input: {
itemRect: Pick;
menuRect: Pick;
viewportWidth: number;
viewportHeight: number;
inset?: number;
});
```
---
### `handleContextMenuKeyDown`
```ts
export function handleContextMenuKeyDown(event: KeyboardEvent, close: () => void): void;
```
---
### `contextPopUp`
Extra VNode renderer registered by `ContextMenuPlugin`.
It reads the plugin instance state (`config`, `activeContext`, selection, and
close handler) and renders the current popup items. Menu item actions receive
the latest `activeContext`, which lets column menu actions know the exact
pinned section and column that opened the menu.
```ts
export function contextPopUp(this: {
contextMenu?: HTMLElement | undefined;
providers: PluginProviders;
revogrid: HTMLRevoGridElement;
config?: ContextMenuConfig;
activeContext?: ContextMenuOpenContext;
close: () => void;
});
```
---
### `POP_EL`
Registered extra VNode name for the context-menu popup.
The plugin uses this value to replace any previous popup renderer when the
plugin is re-created, preventing duplicate popup nodes.
```ts
POP_EL: string;
```
---
# Core
URL: https://pro.rv-grid.com/api/core/
Source: src/content/docs/api/core.md
### `CorePlugin`
```ts
class CorePlugin {
/**
* Observe attribute changes on the grid element
* @param attrName - The attribute name to observe (supports both kebab-case and camelCase)
* @param callback - Callback function when attribute changes
*
* Note:
* This observer tracks DOM attribute mutations only (e.g. setAttribute).
* It does not react to direct property assignments like grid.someProp = value.
*/
observeAttribute(attrName: string, callback: (value: string | null) => void);
/**
* Stop observing an attribute
* @param attrName - The attribute name to stop observing
*/
unobserveAttribute(attrName: string);
/**
* Set the trimmed state for a column
*/
setColumnTrimmed(trimmed: Record>, type: DimensionCols);
/**
* Destroy plugin and clean up all observers
*/
destroy();
}
```
---
### `announcePopupOpen`
Announces a transient popup opening within one grid.
Popup owners subscribed to the same grid close unless they sent the event.
```ts
export function announcePopupOpen(
revogrid: HTMLRevoGridElement,
source: PopupOwner,
);
```
---
### `subscribeToPopupOpen`
Subscribes a transient popup owner to other popup openings in the same grid.
```ts
export function subscribeToPopupOpen(
revogrid: HTMLRevoGridElement,
source: PopupOwner,
close: () => void,
);
```
---
### `PopupOwner`
```ts
export type PopupOwner = object;
```
---
### `findDataGridRowSourceOwner`
Returns the first active row-source owner other than the requesting plugin.
```ts
export function findDataGridRowSourceOwner(
plugins: readonly unknown[],
requester?: unknown,
): string | undefined;
```
---
### `DATA_GRID_ROW_SOURCE_OWNER`
Shared runtime contract for plugins that replace the primary `rgRow` source.
```ts
DATA_GRID_ROW_SOURCE_OWNER: typeof DATA_GRID_ROW_SOURCE_OWNER;
```
---
### `DataGridRowSourceOwner`
```ts
interface DataGridRowSourceOwner {
readonly [DATA_GRID_ROW_SOURCE_OWNER]?: string
}
```
---
### `hasCellTemplateContent`
Returns whether a cell template produced visible content.
```ts
export function hasCellTemplateContent(rendered: unknown): boolean;
```
---
### `renderSyncedCellTemplate`
Render a cell template against a synthetic option row.
Object option metadata is merged into the row model, while the target prop is
always set from `option.value` so metadata cannot shadow the selected value.
```ts
export function renderSyncedCellTemplate