# 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. --- # 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.
Excel-style selection filter with a mini-filter, checked Select All control, collapsed year values, a Blanks option, and Apply and Cancel buttons.
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.
Excel-style selection filter searching for ap, with Select All Search Results, Add current selection to filter, and matching Apple and Grapes options.
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)**.
Excel-style date checklist with 2025 and February expanded to day 03, and mixed-state checkboxes on Select All and the 2025 parent.
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: `..