# 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 [Boilerplates for Pro and Enterprise](https://github.com/revolist/revogrid-pro-boilerplate.git) which include examples for all major frameworks. In these Pro and Enterprise examples, we 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 Pro MCP](/guides/ai-mcp) - [Agent-readable docs index](/llms.txt) ### Repository layout The source repository is a pnpm workspace. Pro and Enterprise 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 - enterprise/ RevoGrid Enterprise package - plugins/ Enterprise plugin source code, including Pivot - src/ Package entrypoints and shared exports - revogrid/ Core grid library (Git submodule) - tests/ - e2e-pro/ Pro browser tests - e2e-enterprise/ Enterprise 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 both Pro and Enterprise packages. 2. Import Pro or Enterprise Packages and CSS After installation, import the packages and CSS based on the features you need. Use Pro for advanced plugin features, and add Enterprise for Pivot and Enterprise-only capabilities. Here is a typical setup: ```ts // Pro import '@revolist/revogrid-pro/dist/revogrid-pro.css'; import { RowOddPlugin } from '@revolist/revogrid-pro'; // Enterprise (includes Enterprise-only plugins such as Pivot) import '@revolist/revogrid-enterprise/dist/revogrid-enterprise.css'; import { PivotPlugin } from '@revolist/revogrid-enterprise'; ``` 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/) - [Column Selection](/guides/columns/column-selection/) - [Multi-Range Selection](/guides/data-manage/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 Excel](/guides/data-manage/formula-excel/) - [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. --- # How to use RevoGrid Pro 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 Pro docs, examples, plugins, migration notes, and typed API context. RevoGrid Pro MCP gives AI coding tools structured access to RevoGrid knowledge through the hosted Pro endpoint: ```text https://mcp.rv-grid.com/pro ``` When your client is configured with the Pro endpoint and a valid bearer token, agents can use Pro-only examples, advanced docs, richer feature resolution, and typed plugin context. RevoGrid Pro has a wide plugin surface, richer examples, and advanced features like pivot, event manager, history, Pro renderers, and specialized editors. MCP helps an AI tool retrieve the right implementation guidance before it writes code. ## Your MCP token If you are signed in to the RevoGrid Pro portal, use the token details below directly in your MCP client: ## What you can use it for Use Pro MCP when you want an AI tool to: - find the best Pro example for a specific framework - determine whether a feature is public or Pro before suggesting code - build around typed plugin APIs instead of guessing event names or option shapes - gather migration notes before upgrading a Pro implementation - work from real examples for pivot, export, filters, history, validation, or custom renderers ## Agent-readable documentation files The portal also exposes Markdown text files for AI tools that do not use MCP directly: - [`/llms.txt`](/llms.txt) — a concise index of the most useful RevoGrid Pro and Enterprise documentation links. - [`/llms-full.txt`](/llms-full.txt) — a full normalized Markdown export of the portal documentation corpus. Use MCP when your tool supports retrieval with authentication and typed context. Use the `llms.txt` files when your agent needs a public, text-only documentation source it can fetch directly. ## How to access Pro MCP RevoGrid Pro users should connect to the dedicated Pro endpoint: ```text https://mcp.rv-grid.com/pro ``` Pro access requires both: - the Pro MCP URL: `https://mcp.rv-grid.com/pro` - an `Authorization` header in the format `Bearer ` ### MCP token flow RevoGrid Pro MCP works best when each Pro user receives their own MCP token. High-level flow: 1. RevoGrid configures the MCP server with `MCP_AUTH_JWT_SECRET`. 2. The portal generates a signed JWT token for the signed-in Pro user. 3. The user connects their MCP client to `https://mcp.rv-grid.com/pro`. 4. The user sends that token in the request header as `Authorization: Bearer `. 5. Valid token requests are treated as Pro and unlock Pro-only MCP results. The user should receive a token, not the raw `MCP_AUTH_JWT_SECRET`. The secret stays on the MCP server and is only used to sign and verify tokens. ### Access checklist 1. Have an active RevoGrid Pro subscription or trial. 2. Sign in to the RevoGrid Pro portal. 3. Add `https://mcp.rv-grid.com/pro` to Codex, Cursor, Claude Code, or VS Code. 4. Copy your token from this page and configure your MCP client to send `Authorization: Bearer `. 5. If the agent only sees public results, contact RevoGrid support and ask them to verify MCP access for your Pro account. If you can connect to the MCP server but only receive public documentation or Core-only matches, your client is most likely connected successfully but not yet provisioned for Pro-aware retrieval. ## What Pro access changes With Pro access enabled, the MCP can help the agent retrieve: - Pro-only docs and examples - advanced plugin guidance - Core vs Pro feature resolution - richer typed context for Pro plugins and workflows - better matches for pivot, event manager, history, export, validation, and advanced filters ## Recommended setup 1. Add the Pro MCP endpoint to your AI coding tool. 2. Add your MCP token to the client as an authorization header in the format `Authorization: Bearer `. 3. Ask the agent to search MCP before writing code. 4. Ask it to validate the final implementation against RevoGrid types and examples. ## Install in AI coding tools ### Claude Code ```bash claude mcp add --transport http revogrid https://mcp.rv-grid.com/pro ``` Then configure the server to send: ```text Authorization: Bearer ``` ### Codex ```bash codex mcp add revogrid --url https://mcp.rv-grid.com/pro ``` Then configure the server to send: ```text Authorization: Bearer ``` ### Cursor ```json { "mcpServers": { "RevoGrid": { "url": "https://mcp.rv-grid.com/pro", "type": "http", "headers": { "Authorization": "Bearer " } } } } ``` ### VS Code ```json { "servers": { "RevoGrid MCP": { "url": "https://mcp.rv-grid.com/pro", "type": "http", "headers": { "Authorization": "Bearer " } } }, "inputs": [] } ``` ## How to prompt effectively The best prompts tell the agent three things: 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 Pro 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 Pro example for advanced filter headers in Angular and adapt it to server-side filtering.` - `Use RevoGrid Pro MCP to implement row master with nested editing. Validate the final API names against types.` - `Check if this feature is available in Core or Pro, then recommend the best matching RevoGrid implementation path.` ## Best practices for Pro usage Prompt the agent to inspect RevoGrid MCP before generating code. This reduces invented APIs and helps it start from real examples. RevoGrid has strong typing. Ask the agent to verify plugin names, event payloads, config fields, and feature usage against RevoGrid types. If your implementation is allowed to use Pro features, say so. If you need a Core-only fallback, ask for that explicitly. Ask the agent to use the best matching Pro demo or guide as the starting point, then adapt it to your app. ## Typical Pro workflows ### Build advanced features faster Use MCP when implementing: - pivot tables - history and undo flows - advanced filters - custom editors and renderers - row master, row transpose, and row order - validation, charts, and export ### Migrate more safely Ask the agent to retrieve migration notes and typed API context before changing: - plugin setup - advanced grid config - event handling - framework wrapper integration ### Review existing code Ask the agent to compare your local code with: - the closest Pro example - the relevant plugin docs - the matching typed API symbols ## Example workflow 1. Add `https://mcp.rv-grid.com/pro` to your MCP client. 2. Open your RevoGrid Pro project. 3. Ask the agent to find the best matching Pro example and docs. 4. Ask it to adapt that result to your framework and data model. 5. Ask it to verify the result against RevoGrid types before finalizing. ## Suggested first prompts - `Show me the best RevoGrid Pro examples for pivot in React.` - `Find the Pro docs and type context for event manager.` - `Use RevoGrid Pro MCP to build advanced filter headers in Vue.` - `Review this implementation and compare it to the nearest RevoGrid Pro example.` ## Related pages - [Welcome to the RevoGrid Pro documentation!](/guides/) - [Installing RevoGrid Pro](/guides/installation) - [Pivot](/guides/data-manage/pivot) - [Event Manager Explanation](/guides/data-manage/event-manager-explanation) - [Features](/guides/faq/features) --- # 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: 600; } ``` 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 colors and text to highlight categorical or status-based data. ### 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: 600; } ``` `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 Selection URL: https://pro.rv-grid.com/guides/columns/column-selection/ Source: src/content/docs/guides/columns/column-selection.mdx Description: Column selection functionality. Enable column selection functionality, allowing users to select entire columns easily. --- # 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, }; ``` ## 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. - Pinned columns can split a group into pinned and unpinned visible bands, matching the behavior used by major data grids. --- # Date Filter URL: https://pro.rv-grid.com/guides/data-filter/date-filter/ Source: src/content/docs/guides/data-filter/date-filter.mdx Description: The Date Filter is a powerful feature in RevoGrid that allows you to filter data based on dates using a variety of criteria. It provides an intuitive interface for filtering dates with both standard comparison operators and relative date ranges. The Date Filter is a powerful feature in RevoGrid that allows you to filter data based on dates using a variety of criteria. It provides an intuitive interface for filtering dates with both standard comparison operators and relative date ranges. ## Features - Standard date comparison operators (equals, before, after, etc.) - Relative date ranges (today, this month, last quarter, etc.) - Date range selection with From/To inputs - Empty value handling ## Filter Types ### Standard Operators - Equals - Matches a specific date - Before - Matches dates before a specific date - After - Matches dates after a specific date - On or Before - Matches dates on or before a specific date - On or After - Matches dates on or after a specific date - Between - Matches dates in a range - Not Equal - Matches dates not equal to a specific date - Is Empty / Is Not Empty - Matches null/undefined/empty cells ### Relative Date Ranges - Today - Yesterday - Last 7 Days - This Month - Last Month - This Quarter - Next Quarter - Previous Quarter - This Year - Next Year - Previous Year - Custom Period (user-defined) ## Usage To enable the date filter for a column: - Add the plugin to your grid's plugins array `import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';` - Add the plugin to the grid's plugins array: ```typescript plugins: [ AdvanceFilterPlugin, ], ``` - Add 'date' to the column's filter array: ```typescript import { AdvanceFilterPlugin } from '@revolist/revogrid-pro'; const columns = [ { name: 'Event Date', prop: 'date', filter: ['date'], // Enable date filter } ]; ``` ## Filter Operators | Operator | Description | |----------|-------------| | equals | Exact date match | | before | Dates before specified date | | after | Dates after specified date | | onOrBefore | Dates on or before specified date | | onOrAfter | Dates on or after specified date | | between | Dates within specified range | | notEqual | Dates not matching specified date | | isEmpty | Empty/null date values | | isNotEmpty | Non-empty date values | | today | Today's dates | | yesterday | Yesterday's dates | | last7Days | Dates within last 7 days | | thisMonth | Dates in current month | | lastMonth | Dates in previous month | | thisQuarter | Dates in current quarter | | nextQuarter | Dates in next quarter | | previousQuarter | Dates in previous quarter | | thisYear | Dates in current year | | nextYear | Dates in next year | | previousYear | Dates in previous year | --- # 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. ## 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" ``` 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") ``` ## 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', }, }, }; ``` --- # Filter Header URL: https://pro.rv-grid.com/guides/data-filter/filter-header/ Source: src/content/docs/guides/data-filter/filter-header.mdx The Filter Header Plugin enhances RevoGrid by embedding interactive filter controls directly within the column headers. This means that users can apply text-based and selection-based filters right at the grid level, without needing separate filter panels. This feature allows for complex multi-criteria filtering that is both intuitive and highly efficient. What makes this so powerful - is its ability to wrap existing header content with dynamic filter inputs, ensuring that the grid remains clean and user-friendly while offering robust filtering capabilities. With optimized performance through input debouncing, the Filter Header Plugin significantly improves data exploration, making it an essential tool for managing and analyzing large datasets. ## Usage To enable this powerful filtering capability, you simply need to include the FilterHeaderPlugin in your grid’s plugins array. ```typescript import { AdvanceFilterPlugin, FilterHeaderPlugin } from '@revolist/revogrid-pro' const plugins = ref([AdvanceFilterPlugin, FilterHeaderPlugin]) ``` This plugin automatically integrates filter input elements into your column headers, so when you mark a column with a filter property, it dynamically adds a text input or selection control right below the header. ## Selection popup headers For selection, slider, and date popup filters, the header renders a popup trigger instead of a free text input. 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. When values are unchecked in the selection popup, the trigger shows the selected values and, by default, includes a count when more than one value is selected. Set `hideFilterHeaderCount` on the column to hide the count. --- # Filter Selection URL: https://pro.rv-grid.com/guides/data-filter/filter-selection/ Source: src/content/docs/guides/data-filter/filter-selection.mdx Description: An advanced filtering plugin for RevoGrid with custom filter types like slider and selection. The **Advanced Filtering Plugin** for RevoGrid extends the grid's capabilities by introducing powerful filtering options, making data management more efficient and flexible. This plugin includes new filter types, such as **slider** and **selection**, which provide intuitive ways for users to interact with and refine data views. Selection filter options are rendered in a virtualized nested `revo-grid`, so large custom option lists stay fast while keeping the same search, select-all-visible, checkbox toggling, sorting, and filter semantics. The live example also enables the optional expression editor. Open a selection filter popup and click **Expression** to type selection expressions such as `in ("Apple 🍎", "Orange 🍊")` or `not in ("Banana 🍌", "Lemon 🍋")`. - **Custom Filter Types**: Enables the use of **slider** and **selection** filters, allowing users to quickly narrow down data based on specific criteria. - **Virtualized Selection List**: Selection popups use a nested grid viewport, so hundreds or thousands of options only render a small visible row window. - **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further. - **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings. - **Custom Filter Types**: Enables the use of **slider** and **selection** filters, allowing users to quickly narrow down data based on specific criteria. - **Virtualized Selection List**: Selection options render through a nested `revo-grid` with row virtualization. - **Custom Option Templates**: Selection options can render custom content next to the checkbox, such as icons, badges, or formatted labels. - **Grouped Selection Options**: Selection options can be grouped by fields returned from `selection.getItems`. - **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further. - **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings. ### Filter Selection Options - **sortDirection**: The default sort direction, can be 'asc' or 'desc' or 'none' - **quickSearchFiltering**: Controls whether typing in the selection popup search input also filters grid rows. Defaults to `true`. Set to `false` to only narrow the popup option list. - **quickSearchFilter**: Custom matcher for selection popup quick search. Use it when option `value` fields are IDs but users should search by rendered `label` or item metadata. It accepts one matcher for every selection-filter column or a record keyed by column `prop`. - **sourceRowTypes**: Row stores used by the default selection option loader. Defaults to all row stores. Set to `['rgRow']` to ignore pinned rows such as `rowPinStart`. - **getItems**: A custom source for selection values. It accepts either one loader function for every selection-filter column or a record keyed by column `prop`. - **itemTemplate**: Custom renderer for option content next to the plugin-owned checkbox. It accepts one renderer for every selection-filter column or a record keyed by column `prop`. - **grouping**: Optional grouping for the nested selection option grid. It accepts one `GroupingOptions` object or a record keyed by column `prop`. - **plugins**: Optional plugins for the nested selection option grid. It accepts one plugin array or a record keyed by column `prop`. - **gridSettings**: Optional nested selection grid settings, such as `theme`, `rowSize`, `frameSize`, `columnTypes`, `additionalData`, `readonly`, `range`, or `useClipboard`. `source`, `columns`, and `grouping` are controlled by the selection filter renderer. - **cascadeOptions.enabled**: Enables context-aware selection options that apply active filters from other columns and ignore the current column filter. - **selectionTitle**: Optional title shown above the selection filter options. Omit it to render no selection title. - **selectionSearchPlaceholder**: Placeholder text for the selection filter search input. Defaults to `Search...`. - **sliderTitle**: The title of the slider filter - **expressions**: Enables the optional expression editor in the popup. Selection expressions such as `is "Apple 🍎"`, `in ("Apple 🍎", "Orange 🍊")`, and `not in ("Banana 🍌")` compile into the same hidden selection filter model when the column has option metadata. Use `defineAdvancedFilterConfig` when you only need to override part of the advanced filter localization. It fills the default filter names so you do not need to pass them manually. ```typescript import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro'; grid.filter = defineAdvancedFilterConfig({ localization: { captions: { popupHeaderColumnName: (column) => column.headerAlias || column.name || String(column.prop), }, }, }); ``` ```typescript grid.filter = { expressions: { enabled: true, placeholder: 'in ("Apple 🍎", "Orange 🍊")', }, localization: { captions: { selectionTitle: 'Selection', selectionSearchPlaceholder: 'Search values...', sliderTitle: 'Slider', expressionButton: 'Expression', expressionTitle: 'Filter expression', expressionInvalid: 'Fix the expression before applying.', }, }, selection: { sortDirection: 'asc', // default sort direction, can be 'asc' or 'desc' or 'none' quickSearchFiltering: true, // default: selection popup search also filters grid rows quickSearchFilter: ({ search, label, item }) => `${label} ${item.family ?? ''}`.toLowerCase().includes(search), getItems: async (prop) => { if (prop !== 'name') { return []; } return [ { value: 'apple', label: 'Apple', family: 'Tree fruit', color: 'Red' }, { value: 'banana', label: 'Banana', family: 'Tropical', color: 'Yellow' }, { value: 'orange', label: 'Orange', family: 'Citrus', color: 'Orange' }, ]; }, grouping: { name: { props: ['family', 'color'], expandedAll: true, }, }, }, }; ``` ### Usage Example ```typescript import { AdvanceFilterPlugin } from '@revolist/revogrid-pro'; // ... grid.columns = [ { // ... filter: ['string', 'selection'] } ]; grid.plugins = [AdvanceFilterPlugin]; grid.filter = { localization: { captions: { selectionTitle: 'Selection', }, }, selection: { sortDirection: 'asc', // default sort direction, can be 'asc' or 'desc' or 'none' quickSearchFiltering: true, // default: search narrows options and filters grid rows }, }; ``` ### Virtualized Selection Popup 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. ### 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. - The feature is opt-in, and default behavior is unchanged when omitted. ```typescript grid.filter = { selection: { cascadeOptions: { enabled: true, }, }, }; ``` ### Custom List Source 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. ### 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. ```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. ### 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 Grid 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`. ### Predefined Selection Values 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 filter-selection demo uses this pattern for the Fruit column, so one or two fruit values are already unchecked when the demo loads. ### Customization and Extensibility This plugin highlights the extensibility of RevoGrid’s plugin system, showcasing how developers can build and integrate advanced features tailored to specific needs. The flexibility of RevoGrid plugins enables comprehensive data manipulation and display control, making it an ideal choice for complex data-driven applications. For a deeper dive into plugin development, refer to the RevoGrid Plugin Documentation. --- # Filter Selection Cascade URL: https://pro.rv-grid.com/guides/data-filter/filter-selection-cascade/ Source: src/content/docs/guides/data-filter/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. 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 feature is opt-in and does not change default behavior. ## 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, showDependencyNumbers: true, }, }, }; ``` ## Compatibility with custom getItems If `selection.getItems` is provided, that custom source is used as-is. Cascade options are only applied to the built-in source loader. --- # Filter Use Case URL: https://pro.rv-grid.com/guides/data-filter/filter-showcase/ Source: src/content/docs/guides/data-filter/filter-showcase.mdx --- # Filter Slider URL: https://pro.rv-grid.com/guides/data-filter/filter-slider/ Source: src/content/docs/guides/data-filter/filter-slider.mdx Description: An advanced filtering plugin for RevoGrid with custom filter types like slider and selection. The **Advanced Filtering Plugin** for RevoGrid extends the grid's capabilities by introducing powerful filtering options, making data management more efficient and flexible. This plugin includes new filter types, such as **slider** and **selection**, which provide intuitive ways for users to interact with and refine data views. ### Key Features - **Custom Filter Types**: Enables the use of **slider** and **selection** filters, allowing users to quickly narrow down data based on specific criteria. - **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further. - **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings. ### Usage Example ```typescript import { AdvanceFilterPlugin } from '@revolist/revogrid-pro'; // Basic setup const grid = document.createElement('revo-grid'); grid.columns = [ { name: 'Numeric Column', prop: 'numericValue', filter: ['number', 'slider'] } ]; grid.plugins = [AdvanceFilterPlugin]; grid.filter = {}; // Advanced setup with slider configuration grid.filter = { slider: { showTooltips: true, showRangeDisplay: true, showRangeInputs: true, formatValue: (value) => `$${value.toFixed(2)}`, formatInputValue: (value) => value.toFixed(2), parseInputValue: (value) => Number(value.replace(',', '.')) } }; ``` ### Slider Configuration Options The slider filter can be customized using the following configuration options: | Option | Type | Default | Description | |--------|------|---------|-------------| | `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; } ``` ### Advanced Features 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 ### Integration 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 } ]; ``` ### Best Practices 1. **Performance**: - The slider filter is optimized for large datasets - Range calculations are performed efficiently - Real-time updates are throttled for smooth performance 2. **User Experience**: - Use `showTooltips` for precise value selection - Implement `formatValue` for meaningful value display - Enable `showRangeDisplay` for clear range visualization 3. **Accessibility**: - Slider handles are keyboard-navigable - ARIA attributes are automatically applied - High contrast visual feedback for active states ### Customization and Extensibility This plugin highlights the extensibility of RevoGrid's plugin system, showcasing how developers can build and integrate advanced features tailored to specific needs. The flexibility of RevoGrid plugins enables comprehensive data manipulation and display control, making it an ideal choice for complex data-driven applications. For a deeper dive into plugin development, refer to the RevoGrid Plugin Documentation. --- # Audit Trail History URL: https://pro.rv-grid.com/guides/data-manage/audit-history/ Source: src/content/docs/guides/data-manage/audit-history.mdx Description: Track, review, persist, restore, and export accountable RevoGrid data changes. `AuditHistoryPlugin` records accountable data-change transactions for spreadsheet-like business workflows. Use it when the grid edits important records and your application needs to answer who changed what, when it changed, and how to recover it. `HistoryPlugin` owns local undo and redo. `AuditHistoryPlugin` owns the reviewable change log, user metadata, persistence hooks, restore actions, and audit panel. Audit History automatically reuses an existing `HistoryPlugin` or installs one when needed; use `grid.history` when you need to configure History behavior. ## What To Try The demo is wired like a production audit surface: edit cells, select a changed cell, filter the history, export records, and restore previous values from the panel. Change a cell and watch the panel add a timestamped record with user and before/after value details. Hover a marked cell to see the latest change summary while keeping the full audit trail in the panel. Click a cell or row to switch between focused cell history, row history, and full-grid history. Use restore actions to roll back a cell, row snapshot, or transaction while recording the restore itself. ## Why Use It When users edit operational data, the final cell value is not enough. Audit history keeps the context around each change so business users, admins, and support teams can review the full lifecycle of a value. Record the current user, timestamp, action type, row identity, changed column, previous value, and new value. Group paste and range-edit operations into one transaction instead of treating every changed cell as an unrelated record. Restore a single cell, a row snapshot, or a complete transaction after accidental edits. Keep records in memory, browser `localStorage`, or a custom backend adapter. ## When To Use It Audit history is useful when RevoGrid edits real business data, not temporary UI state. Track changes to orders, invoices, stock levels, delivery statuses, prices, and approvals. Review manual adjustments to budgets, forecasts, risk values, and reporting tables. Log edits to permissions, limits, subscriptions, account status, and configuration records. Explain why a value changed without guessing from the current dataset. ## Basic Setup ### Cell Change Indicators The Audit History examples also mark edited cells directly in the grid. When a cell has recorded changes, the cell shows a small upper-right corner marker aligned to the cell edge. Hovering the cell uses `TooltipPlugin` to show the change count, latest before/after value, user, and timestamp. This is built on existing plugin APIs: resolve the `AuditHistoryPlugin` instance, call `getCellHistory(rowId, column)` from cell rendering hooks, add tooltip attributes through `cellProperties`, and refresh visible rows when audit events fire. ```ts const records = auditPlugin.getCellHistory(row.id, column.prop); ``` The marker is only a visual affordance. The audit panel remains the complete review and restore surface. Use `AuditHistoryPlugin` with `EventManagerPlugin`. The event manager normalizes cell edits, range edits, paste operations, undo, redo, and audit restore replays into edit events the audit plugin and other edit integrations can observe. Audit History delegates range-shaped restore updates through the existing History replay path. That means cell restore, row replacement restore, and transaction restore emit `beforehistedit`; `EventManagerPlugin` turns that into the normal `gridedit` flow; and `CellFlashPlugin` can flash restored cells from the same edit pipeline. Row insertion and snapshot restore still use direct source/data updates because they are not pure range patches. 1. Import the plugins, panel helper, and config type. ```ts import { AuditHistoryPlugin, CellFlashPlugin, EventManagerPlugin, createLocalStorageAuditHistoryAdapter, defineAuditHistoryPanel, type AuditHistoryConfig, } from '@revolist/revogrid-pro'; ``` 2. Provide the audit config. ```ts const auditHistory: AuditHistoryConfig = { getCurrentUser: () => ({ id: 'avery-stone', name: 'Avery Stone', email: 'avery.stone@company.com', }), rowIdProp: 'id', storage: 'memory', onAuditRecord(record) { // Persist to your API, Supabase, Firebase, PostgreSQL, etc. console.log(record); }, }; ``` 3. Register plugins and bind the config directly on the grid. ```ts grid.plugins = [EventManagerPlugin, AuditHistoryPlugin, CellFlashPlugin]; grid.auditHistory = auditHistory; ``` `HistoryPlugin` does not need to be listed for normal Audit History restore behavior. Add it explicitly only when the page also has direct History UI controls or you want to control plugin order yourself. 4. Mount the optional review panel. ```ts const auditPanel = defineAuditHistoryPanel(panelElement, grid, { pageSize: 25, allowExport: true, placement: 'right', restoreActions: ['cell', 'row'], formatDate: (value, { mode }) => new Intl.DateTimeFormat(undefined, mode === 'day' ? { weekday: 'short', month: 'short', day: 'numeric' } : { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(new Date(value)), events: { beforeExport: ({ format }) => format !== 'json', beforeRestore: ({ type }) => type !== 'transaction', }, }); // Later: auditPanel.setPlacement('bottom'); auditPanel.refresh(); ``` The plugin still observes `additionalData.auditHistory` for older integrations, but new code should use the direct `grid.auditHistory` property. ## Stable Row Identity Audit records should point to business records, not visual row positions. Row indexes can change after sorting, filtering, grouping, or lazy loading, so every row should expose a stable identity. ```ts const rows = [ { id: 'invoice-123', status: 'Draft', amount: 1200 }, { id: 'invoice-124', status: 'Paid', amount: 800 }, ]; const auditHistory: AuditHistoryConfig = { rowIdProp: 'id', }; ``` If your row identity is derived, use `getRowId` instead: ```ts const auditHistory: AuditHistoryConfig = { getRowId: (row) => `${row.tenantId}:${row.invoiceNumber}`, }; ``` ## What Gets Recorded Each top-level audit item is an `AuditRecord`. A record contains one transaction ID and one or more `AuditChange` entries. ```ts { id: 'audit-001', transactionId: 'tx-001', type: 'cell-change', changedAt: '2026-05-08T12:00:00Z', changedBy: { id: 'avery-stone', email: 'avery.stone@company.com', }, changes: [ { id: 'change-001', rowId: 'invoice-123', rowType: 'rgRow', column: 'status', oldValue: 'Draft', newValue: 'Approved', }, ], } ``` For a paste operation, the same record can contain many changes: ```ts { transactionId: 'tx-002', type: 'bulk-paste', changedAt: '2026-05-08T12:05:00Z', changedBy: { id: 'avery-stone' }, changes: [ { id: 'change-002', rowId: 'invoice-123', rowType: 'rgRow', column: 'status', oldValue: 'Draft', newValue: 'Approved', }, { id: 'change-003', rowId: 'invoice-124', rowType: 'rgRow', column: 'status', oldValue: 'Draft', newValue: 'Rejected', }, ], } ``` ## Storage Options Use `storage: 'memory'` for demos, temporary sessions, and test fixtures. Use `storage: 'localStorage'` when browser persistence is enough for the workflow. Use `storage: 'custom'` with `storageAdapter` when your backend should load, append, save, or clear records. Use `storageProviders` to list cache or server providers. Provider loads refresh the panel automatically. Use `maxRecords` to bound retained records. The implementation defaults to `1000`. For production systems, prefer a trusted server provider. If you include several providers, the default is intentionally isolated: the first provider that returns records owns the loaded audit trail, and later providers with records are ignored with a console warning. Set `mergeStorageProviders: true` only when your app deliberately wants to merge records by id across providers. ```ts const auditHistory: AuditHistoryConfig = { getCurrentUser: () => currentUser, rowIdProp: 'id', storageProviders: [ { async load() { const response = await fetch('/api/audit-history'); return response.json(); }, async append(record) { await fetch('/api/audit-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(record), }); }, async save(records) { await fetch('/api/audit-history', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(records), }); }, async clear() { await fetch('/api/audit-history', { method: 'DELETE' }); }, }, ], }; ``` Provider write failures emit `auditerror` but do not block editing. Configuring `storageProviders` together with `storage` or `storageAdapter` shows a console warning because `storageProviders` takes precedence. When you need a deliberate cache-plus-server merge, opt in explicitly: ```ts const auditHistory: AuditHistoryConfig = { rowIdProp: 'id', mergeStorageProviders: true, storageProviders: [ createLocalStorageAuditHistoryAdapter('invoices:audit-history'), serverAuditHistoryAdapter, ], }; ``` ### Provider Load Flow Provider chains are useful when the user should see cached history quickly while a server-backed provider loads the durable records. 1. The panel mounts and reads the current in-memory records. 2. Storage providers load in order. By default, the first provider that returns records owns the loaded audit trail. 3. The plugin emits `auditrecordsloaded` whenever loaded records are applied. 4. The built-in panel refreshes on `auditrecordsloaded`, `auditrecord`, and `auditrestore`, so async loads and external restore controls stay visible without polling. Client-side records are excellent for review panels and recovery UX. Compliance-grade audit trails should be written to a trusted backend. ## Privacy And Policy Controls Production audit logs often need more than raw before/after values. Use `ignoredColumns`, `sanitizeValue`, `getMetadata`, `canRestore`, and `immutable` to align the client behavior with your business rules. ```ts const auditHistory: AuditHistoryConfig = { getCurrentUser: () => currentUser, rowIdProp: 'id', ignoredColumns: ['internalNotes'], sanitizeValue(value, context) { if (context.column === 'amount') { return '[redacted]'; } return value; }, getMetadata() { return { workspaceId: currentWorkspace.id, requestId: crypto.randomUUID(), }; }, canRestore({ type }) { return currentUser.role === 'admin' && type !== 'transaction'; }, }; ``` Use action-type policy when entire classes of audit records should be included or ignored: ```ts const auditHistory: AuditHistoryConfig = { ignoredActionTypes: ['undo', 'redo'], trackedActionTypes: ['cell-change', 'bulk-paste', 'approval-submitted'], shouldTrackRecord({ type, changes, metadata }) { if (type === 'server-validation' && metadata?.severity === 'debug') { return false; } return changes.length > 0 || type === 'approval-submitted'; }, }; ``` For compliance-style screens where the client must not mutate local audit records, set `immutable: true`. This disables `clear`, `replaceRecords`, and restore actions in the plugin. Server-side immutability should still be enforced by your backend. ```ts const auditHistory: AuditHistoryConfig = { storage: 'custom', storageAdapter, immutable: true, }; ``` ## Review And Restore The audit panel lets users inspect grid changes without leaving the page. Common review flows include full-grid history, selected-row history, selected-cell history, user filtering, date filtering, action-type filtering, and old/new value comparison. Switch between full-grid, selected-row, and selected-cell history without changing the underlying audit records. The panel refreshes after new audit records, provider-loaded batches, and restore events from other UI controls. `canRestore`, `beforeauditrestore`, and `immutable` let the app decide which restore actions users can run. Generate filtered CSV or JSON exports for reporting, support workflows, or backend handoff. Use the plugin instance when you need audit data in custom UI: ```ts const plugins = await grid.getPlugins(); const auditHistoryPlugin = plugins.find( (plugin) => plugin instanceof AuditHistoryPlugin, ); const records = auditHistoryPlugin?.getRecords({ userId: 'avery-stone' }); const cellHistory = auditHistoryPlugin?.getCellHistory('invoice-123', 'status'); const rowHistory = auditHistoryPlugin?.getRowHistory('invoice-123'); const lastStatusChange = auditHistoryPlugin?.getLastChange('invoice-123', 'status'); const stats = auditHistoryPlugin?.getStats(); ``` Restore helpers return `true` when a restore was applied: ```ts auditHistoryPlugin?.restoreCell(change); auditHistoryPlugin?.restoreRow('invoice-123'); auditHistoryPlugin?.restoreTransaction(transactionId); ``` Cell, row replacement, and transaction restores are replayed through `HistoryPlugin.replayChange()`. The restore itself is still recorded as `restore-cell`, `restore-row`, or `restore-transaction`, but the replay is guarded so it does not create a second normal edit record. Use `recordEvent` for application-driven records that do not come from direct grid editing: ```ts auditHistoryPlugin?.recordEvent({ type: 'api-sync', changedBy: { id: 'webhook', name: 'Webhook' }, presentation: { source: 'api', targetLabel: 'F1:F8', detailLabel: '8 cells recalculated', }, }); ``` Custom action types are plain strings. When the record carries normal cell or row changes, existing restore helpers can use it for revert workflows: ```ts auditHistoryPlugin?.recordEvent({ type: 'approval-submitted', transactionId: 'approval-42', changedBy: currentUser, changes: [{ rowId: 'invoice-123', rowIndex: 0, rowType: 'rgRow', column: 'status', oldValue: 'Draft', newValue: 'Approved', }], presentation: { source: 'api', verb: 'submitted approval', targetLabel: 'Invoice 123', }, }); auditHistoryPlugin?.restoreTransaction('approval-42'); ``` Summary-only custom records are ignored unless you opt in with `allowEmpty: true`. Use snapshots when users need a named restore point: ```ts const snapshot = auditHistoryPlugin?.createSnapshot('Before Q4 close'); if (snapshot) { auditHistoryPlugin?.restoreSnapshot(snapshot); } ``` Query helpers return defensive copies, so application code cannot accidentally mutate the plugin's internal audit log. ## Loading And Export Use `refreshRecords` when your custom storage adapter can load existing records from a backend. A single `storageAdapter` still supports merge-by-id refreshes by default; pass `{ merge: false }` to replace the local set. Provider chains follow the configured provider policy: isolated by default, merged only with `mergeStorageProviders: true`. ```ts await auditHistoryPlugin?.refreshRecords(); await auditHistoryPlugin?.replaceRecords(serverRecords, { merge: false }); ``` Use `exportRecords` for custom export buttons or reporting workflows: ```ts const csv = auditHistoryPlugin?.exportRecords({ format: 'csv', filter: { actionType: 'bulk-paste' }, includeMetadata: true, }); const json = auditHistoryPlugin?.exportRecords({ format: 'json' }); ``` Export failures are reported through `auditerror` with phase `export` and return an empty string. That keeps export buttons from crashing the page while still giving your app a single error channel for observability. The built-in panel docks to the right of the grid by default and can be pinned with `placement: 'left' | 'right' | 'top' | 'bottom' | 'none'`. It includes timeline day grouping, search, user/column/action/date filter chips, scoped cell and row views, pagination, CSV/JSON export controls, hover restore/jump actions, row-click compare mode, optional mini close state, and i18n-friendly labels. Use `restoreActions`, callback-capable `allowCompare`, `formatDate`, and `events` to control panel actions. Pass `tooltips: false` to hide all help icons, or override/hide individual entries with `tooltips: { searchFilter: 'Find a record', clearFilters: false }`. Pass `labels` to rename visible text, placeholders, aria labels, action names, source labels, and dynamic strings such as pagination or selected row/cell summaries. Listen for `beforeauditrestore` to validate or block a restore, and `auditrestore` after a restore succeeds. New audit records are emitted through `auditrecord`. Storage and hook failures are emitted through `auditerror`; loaded record batches are emitted through `auditrecordsloaded`. ## Production Flow ```txt User edits grid -> EventManagerPlugin normalizes the edit -> AuditHistoryPlugin creates an audit transaction -> the record is stored locally or sent to your backend -> users review changes in the audit panel -> admins restore cells, rows, or transactions when needed -> range-shaped restores replay through HistoryPlugin and EventManagerPlugin ``` ## Summary `AuditHistoryPlugin` adds accountability to editable RevoGrid applications. Pair it with stable row IDs, a current-user provider, and a trusted persistence path when users edit important operational data. --- # Autofill URL: https://pro.rv-grid.com/guides/data-manage/autofill/ Source: src/content/docs/guides/data-manage/autofill.mdx Description: Fill ranges with spreadsheet-like copy and sequence behavior. The `AutoFillPlugin` adds spreadsheet-style drag fill to RevoGrid. Users select one or more cells, drag the fill handle, and RevoGrid fills the target range with the best matching strategy. For a richer spreadsheet experience, pair it with `AutoFillPreviewPlugin`. The preview plugin shows ghost values while the user is dragging, but it does not write anything to the data store until the drop-time autofill is accepted. Select one or more seed cells in the demo below, then drag the fill handle to preview the values before releasing the mouse to commit them. ## Usage ```ts import { AutoFillPlugin, AutoFillPreviewPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.plugins = [AutoFillPlugin, AutoFillPreviewPlugin]; ``` Use only `AutoFillPlugin` when you want values to appear only after mouse release. Add `AutoFillPreviewPlugin` when users should see the predicted values before they drop the mouse. ### Crossing Pinned And Main Viewports Add `MultiRangeSelectionPlugin` when users should be able to drag the autofill handle from a pinned row or column into the main viewport, or from the main viewport into a pinned section. Cross-viewport autofill is enabled by default with multi-range selection. ```ts import { AutoFillPlugin, MultiRangeSelectionPlugin, } from '@revolist/revogrid-pro'; grid.plugins = [AutoFillPlugin, MultiRangeSelectionPlugin]; ``` Cross-viewport autofill is scoped to visible viewport segments. If the main viewport is scrolled, the plugin does not treat the pinned section and main data as one hidden continuous range, and it does not write rows or columns behind the scroll gap. The final selection is represented as multiple ranges so each visible source and target segment remains explicit. Disable the cross-viewport drag integration while keeping multi-range selection enabled with: ```ts grid.multiRangeSelection = { crossViewportAutofill: false }; ``` ## What Users See 1. Select a source cell or range. 2. Drag the small autofill handle from the selection corner. 3. Ghost values appear in the temporary target range when preview is enabled. 4. Release the mouse to commit the same values that were previewed. 5. Pressing escape, clearing the temporary range, or changing the source clears the preview. Existing selected source cells are not visually replaced by preview values. The preview only appears in the autofill target range. ## Built-In Strategies AutoFill strategies are checked from most specific to most general. If no sequence strategy can safely infer a pattern, RevoGrid repeats the selected values with copy fallback. | Strategy | User behavior | Example | | --- | --- | --- | | Linear numeric sequence | Continues a selected numeric interval. | `1, 3` -> `5, 7` | | Single numeric step | Extends one selected number by `+1`. | `10` -> `11, 12` | | Number in text | Continues labels with embedded numbers and preserves padding. | `INV-099` -> `INV-100`; `1st Floor` -> `2nd Floor` | | Weekday names | Continues English weekday labels and wraps the week. | `Mon, Tue` -> `Wed` | | Month names | Continues English month labels and wraps the year. | `Jan, Feb` -> `Mar` | | Date unit sequence | Continues daily, weekly, monthly, quarterly, and yearly date intervals. | `2024-12-04, 2024-12-05` -> `2024-12-06` | | Time sequence | Continues `HH:mm` and `HH:mm:ss` time intervals. | `09:00, 09:30` -> `10:00` | | Boolean cycle | Repeats boolean-like toggle pairs. | `Yes, No` -> `Yes, No` | | Text suffix sequence | Keeps compatibility with existing `Book 1`, `Book 2` fills. | `Book 1, Book 2` -> `Book 3` | | Copy fallback | Repeats selected values when the pattern is plain or ambiguous. | `Draft, Approved` -> `Draft, Approved` | ## Strategy Notes ### Numeric Values Select two or more numeric cells to preserve an interval. For example, selecting `100` and `110` continues with `120`, `130`, and so on. Selecting one numeric cell uses a simple `+1` sequence. ### Text With Numbers AutoFill understands numbers inside labels, not only numbers at the end. It keeps the visible text shape stable when the selected values are consistent. ```txt Item 001 -> Item 002 INV-099 -> INV-100 1st Floor, 2nd Floor -> 3rd Floor ``` If labels use different prefixes, suffixes, or number formats, RevoGrid falls back to copying the selected values instead of guessing. ### Dates And Times Dates preserve the selected visible format when possible: ```txt 2024-12-04 -> 2024-12-05 2024/12/04 -> 2024/12/05 12/04/2024 -> 12/05/2024 ``` Monthly, quarterly, and yearly fills are inferred from consistent month steps. Time fills preserve `HH:mm` or `HH:mm:ss` and wrap across midnight. ### Weekdays And Months Weekday and month strategies support English full names and three-letter labels. The output format follows the source format: ```txt Monday, Tuesday -> Wednesday Mon, Tue -> Wed January, February -> March Jan, Feb -> Mar ``` ### Boolean-Like Values Boolean cycle supports `true/false`, `yes/no`, and `on/off`. String pairs require both values in the selected range, so a single `Yes` cell is copied instead of turning into an inferred toggle. ### Copy Fallback Copy fallback is intentional. It handles plain text, mixed values, and unsupported patterns by repeating the selected values exactly. This keeps AutoFill predictable for status columns and other business data where guessing a sequence would be surprising. ## Custom Strategies Developers can register custom strategies for domain-specific patterns. Custom strategies run before copy fallback, so they can override generic copy behavior. ```ts import type { AutoFillStrategy } from '@revolist/revogrid-pro'; const priorityStrategy: AutoFillStrategy = (selectedData, direction, targetRange, context) => { // Return a matrix when your strategy supports the selection. // Return null to let the next strategy try. return null; }; AutoFillPlugin.registerStrategy(priorityStrategy); ``` Return `null` when a strategy is not confident. Preview and final apply share the same strategy list, so a custom strategy automatically affects both behaviors. --- # Collaborative Editing URL: https://pro.rv-grid.com/guides/data-manage/collaborative-editing/ Source: src/content/docs/guides/data-manage/collaborative-editing.mdx Description: Learn how to add shared cell editing, presence, pending review, permissions, server commits, conflicts, and audit attribution to RevoGrid Pro. `CollaborativeEditingPlugin` lets several users work on the same grid data at the same time. One user edits a cell, the edit is written into a shared Yjs document, and the other connected grids can receive the same change. You do not need to know Yjs before using the plugin. Think of Yjs as the shared notebook where the browser stores collaborative changes. RevoGrid still renders the grid, your app still owns users and business rules, and your backend still decides what is finally saved. Install collaborative editing as a separate package so Yjs is only loaded by applications that need realtime editing: ```sh pnpm add @revolist/revogrid-collaborative-editing @revolist/revogrid-pro ``` ## Live Demo: Split Collaborative Editing The demo catalog includes a complete split-view example with two users editing the same shared document. Open it when you want to see the moving parts together instead of reading isolated snippets. [Open the Collaborative Editing demo](https://demo.rv-grid.com/collaborative-editing/ts) The demo shows: - two grids on the same page using one collaborative document; - Avery and Grace rendered with matching presence colors and avatars; - live remote focus and selection markers through `CollaborativePresencePlugin`; - the first grid with the `Task` column pinned while the second grid keeps it unpinned; - `EventManagerPlugin` with `applyEventsToSource: false`; - an in-memory presence transport that stands in for a realtime provider; - a mock `commitAdapter` that logs server commits so the backend boundary is visible. ## The Mental Model Collaborative editing has a few moving parts. The names are technical, but the ideas are simple. A user changes a cell. `EventManagerPlugin` normalizes that edit so collaborative editing sees cell edits, paste, range edits, undo, and redo through one pipeline. Presence means "where are the other users looking or editing?" Add `CollaborativePresencePlugin` when you want remote cursors, ranges, colors, and labels. In review mode, edits stay pending until your UI commits or discards them. This is useful when a manager, workflow, or server must approve changes. A commit is the point where your app asks the backend to accept changes. The plugin calls your `commitAdapter`; your backend can accept, rewrite, or reject each edit. A conflict happens when users edit the same row and column concurrently. The default policy keeps the change pending so your UI can show a decision. Audit records explain who changed what and when. Add `AuditHistoryPlugin` when committed collaborative edits should be logged. The plugin coordinates grid edits, shared Yjs state, pending changes, conflicts, presence bridging, and audit integration. Your app still owns authentication, user identity, backend persistence, authorization, and the review UI. ## Beginner Setup Start with the smallest local setup, then add the pieces your product needs. ### 1. Minimal Local Grid This example needs no server and no transport. It is useful for learning the plugin and verifying that local cell edits enter the collaborative pipeline. 1. Import the base plugins. ```ts import { EventManagerPlugin, } from '@revolist/revogrid-pro'; import { CollaborativeEditingPlugin, type CollaborativeEditingConfig, } from '@revolist/revogrid-collaborative-editing'; ``` 2. Give every row a stable id. ```ts const rows = [ { id: 'task-1', task: 'Revenue forecast', owner: 'Avery', status: 'Draft' }, { id: 'task-2', task: 'Vendor renewal', owner: 'Mina', status: 'Review' }, { id: 'task-3', task: 'Headcount plan', owner: 'Jon', status: 'Approved' }, ]; const columns = [ { prop: 'task', name: 'Task' }, { prop: 'owner', name: 'Owner' }, { prop: 'status', name: 'Status' }, ]; ``` 3. Register plugins and bind configuration. ```ts const grid = document.querySelector('revo-grid')!; grid.columns = columns; grid.source = rows; grid.plugins = [ EventManagerPlugin, CollaborativeEditingPlugin, ]; grid.eventManager = { applyEventsToSource: false }; const collaborativeEditing: CollaborativeEditingConfig = { documentId: 'local-learning-session', user: { id: 'avery', name: 'Avery Stone', color: '#2563eb', permissionLevel: 'editor', }, rowIdProp: 'id', }; grid.collaborativeEditing = collaborativeEditing; ``` `grid.eventManager = { applyEventsToSource: false }` is important. It lets collaborative editing own how edits are applied, reviewed, committed, or rolled back instead of letting each raw grid edit immediately mutate source data twice. ### 2. Two Grids On The Same Page Use a shared Yjs document when two grids on one page should represent the same collaborative session. This is the easiest way to understand how two users share edits before connecting a real transport. ```ts import * as Y from 'yjs'; import { CollaborativePresencePlugin, EventManagerPlugin, } from '@revolist/revogrid-pro'; import { CollaborativeEditingPlugin, } from '@revolist/revogrid-collaborative-editing'; const doc = new Y.Doc(); const plugins = [ EventManagerPlugin, CollaborativePresencePlugin, CollaborativeEditingPlugin, ]; function configureGrid(grid: HTMLRevoGridElement, user: { id: string; name: string; color: string }) { grid.columns = columns; grid.source = rows.map(row => ({ ...row })); grid.plugins = plugins; grid.eventManager = { applyEventsToSource: false }; grid.collaborativeEditing = { documentId: 'same-page-demo', user, rowIdProp: 'id', doc, presence: { enabled: true, showLabels: true, staleAfterMs: 15_000, }, }; } configureGrid(gridA, { id: 'avery', name: 'Avery Stone', color: '#2563eb' }); configureGrid(gridB, { id: 'grace', name: 'Grace Hopper', color: '#16a34a' }); ``` Both grids use the same `documentId` and the same `doc`, so they are intentionally part of the same collaborative document. `CollaborativeEditingPlugin` maps awareness state into presence users. `CollaborativePresencePlugin` renders those users as colored focus and range markers. For deeper cursor and range behavior, read [Collaborative Presence](/guides/data-manage/collaborative-presence/). ### 3. Review Mode With Pending Changes Use review mode when users should make changes locally, inspect the pending list, then commit or discard those changes from your UI. ```ts grid.collaborativeEditing = { documentId: `budget:${budgetId}`, user: currentUser, rowIdProp: 'id', mode: 'review', conflictPolicy: 'manual', }; ``` Read the plugin instance when your UI needs pending changes or review actions. ```ts const plugins = await grid.getPlugins(); const collaborative = plugins.find( plugin => plugin instanceof CollaborativeEditingPlugin, ) as CollaborativeEditingPlugin | undefined; const pending = collaborative?.getPendingChanges() ?? []; await collaborative?.commitPendingChanges(pending.map(change => change.id)); collaborative?.discardPendingChanges(); ``` Review mode is a good default for finance, procurement, admin, support, planning, and any workflow where users should not silently overwrite each other. ### 4. Commit Changes To A Server Use `commitAdapter` when your backend must validate, persist, approve, reject, version, or rewrite edits. ```ts grid.collaborativeEditing = { documentId: `forecast:${forecastId}`, user: currentUser, rowIdProp: 'id', mode: 'autoCommit', commitAdapter: { async commit(changes, context) { const response = await fetch(`/api/forecasts/${forecastId}/changes`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ documentId: context.documentId, user: context.user, changes, }), }); return response.json(); }, }, }; ``` The response should describe which changes were committed and which were rejected. Rejected changes are rolled back to their previous local value. ```ts [ { id: 'change-1', status: 'committed' }, { id: 'change-2', status: 'rejected', reason: 'Budget is locked' }, ] ``` ### 5. Add Business Permissions Use `permissions.canEditCell` for user, role, row, tenant, or workflow-specific edit rules. ```ts grid.collaborativeEditing = { documentId: `pricing:${priceBookId}`, user: { id: currentUser.id, name: currentUser.name, roles: currentUser.roles, permissionLevel: currentUser.permissionLevel, }, rowIdProp: 'sku', permissions: { canEditCell({ user, row, prop, newValue }) { if (row?.locked) return false; if (prop === 'approvedPrice') return user.roles?.includes('pricing-admin') ?? false; if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager'; return user.permissionLevel === 'editor'; }, }, }; ``` Client permissions improve UX and prevent obvious invalid edits early. They are not a security boundary; repeat important checks in your backend through `commitAdapter`. ## Connect More Than One Browser A shared local `Y.Doc` is enough for demos and same-page tests. For real users in different browsers, add a transport. ```ts import { createYjsWebSocketTransport, } from '@revolist/revogrid-collaborative-editing'; grid.collaborativeEditing = { documentId: `project:${projectId}:tasks`, user: currentUser, rowIdProp: 'id', transport: createYjsWebSocketTransport({ url: 'wss://collaboration.example.com', params: { token: sessionToken }, }), }; ``` Use WebSocket when your product needs a controlled server, authentication, observability, and enterprise networking. Use WebRTC for peer-to-peer collaboration where your deployment can support signaling and peer connectivity. Use a custom transport when your application already wraps Yjs providers. ## Listen For Conflicts The default `conflictPolicy` is `manual`. When two users edit the same row and column concurrently, the plugin emits `collaborativeconflict` and keeps the local change pending. ```ts grid.addEventListener('collaborativeconflict', (event) => { const conflict = event.detail; showConflictDialog({ rowId: conflict.local.rowId, column: conflict.local.prop, localValue: conflict.local.newValue, remoteValue: conflict.remoteValue, user: conflict.remoteUser, }); }); ``` Set `conflictPolicy: 'lastWriteWins'` only for low-risk cells such as notes, draft comments, or transient planning values where silent overwrite is acceptable. ## Configuration Reference ### Business-Critical Properties | Property | Type | Default | First-time guidance | |:--|:--|:--|:--| | `documentId` | `string` | `'revogrid-collaborative-document'` | The shared session name. Use the same value for every user editing the same business object, such as `budget:2026` or `project:alpha:tasks`. | | `user` | `CollaborativeEditingUser` | `undefined` | The current editor. Provide at least `id` and `name`; without a user, local edits are ignored because edits must be attributable. | | `rowIdProp` | `string` | `'id'` | The row field that uniquely identifies the business record. Use your real primary key, such as `invoiceId`, `sku`, `taskId`, or `employeeId`. | | `commitAdapter` | `CollaborativeEditingCommitAdapter` | `undefined` | Your backend boundary. Add it when changes must be saved, validated, approved, rejected, versioned, or audited by a server. | | `permissions` | `CollaborativeEditingPermissions` | `undefined` | Client-side business rules. Use this for role checks, locked rows, approval status, tenant rules, or field-level permissions. | | `audit` | `CollaborativeEditingAuditConfig \| boolean` | enabled | Audit integration. Keep it enabled for operational, financial, admin, support, or regulated workflows. | | `conflictPolicy` | `'manual' \| 'lastWriteWins'` | `'manual'` | How same-cell concurrent edits are handled. Keep `manual` for business data. | ### Runtime And Collaboration Properties | Property | Type | Default | First-time guidance | |:--|:--|:--|:--| | `enabled` | `boolean` | `true` | Turns collaboration on or off without changing the plugin list. Useful for feature flags or read-only screens. | | `doc` | `Y.Doc` | new isolated document | Pass this when your app owns the Yjs document or when several grids should share one local document. | | `mode` | `'autoCommit' \| 'review'` | `'autoCommit'` | Use `autoCommit` for immediate server reconciliation. Use `review` when users need commit and discard buttons. | | `transport` | `CollaborativeEditingTransport` | `undefined` | Connects different browsers. Without transport, collaboration is local to the current page or shared `doc`. | | `presence` | `{ enabled, showLabels, staleAfterMs }` | enabled, labels shown | Controls the bridge into `CollaborativePresencePlugin`. Use it when users should see who is focused or editing. | ### `user` `user` tells the plugin who is editing. It is used for presence labels, permission checks, commit requests, audit records, and conflict metadata. ```ts const user = { id: 'u-123', name: 'Avery Stone', email: 'avery@example.com', initials: 'AS', color: '#2563eb', roles: ['planner', 'budget-editor'], permissionLevel: 'editor', }; ``` Use a stable application user id. Avoid session ids or display names as `id`, because audit history and server reconciliation need durable identity. ### `documentId` `documentId` means "which shared document is this grid editing?" ```ts documentId: `forecast:${forecastId}` documentId: `project:${projectId}:schedule` documentId: `tenant:${tenantId}:price-book:${priceBookId}` ``` Use the same `documentId` for every client editing the same dataset. Use a different `documentId` when two grids should not share edits or presence. ### `rowIdProp` Collaborative editing stores cell changes by stable row identity. If `rowIdProp` is wrong, edits may apply to the wrong row after sorting or filtering. ```ts const rows = [ { invoiceId: 'INV-1001', status: 'Draft', total: 1200 }, ]; grid.collaborativeEditing = { documentId: 'invoices:open', user, rowIdProp: 'invoiceId', }; ``` Use `id` only when every row really has a durable `id`. For generated, aggregated, or temporary rows, provide a stable key before enabling collaboration. ### `commitAdapter` `commitAdapter` means "ask my backend whether these edits are accepted." ```ts commitAdapter: { async commit(changes, context) { const response = await fetch('/api/reconcile-grid-changes', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ documentId: context.documentId, user: context.user, changes, }), }); return response.json() as Promise; }>>; }, } ``` Return `committed` when the server accepts a change. Return `rejected` when validation, permissions, workflow status, version checks, or business rules fail. ### `permissions` `permissions.canEditCell` runs before a local edit is accepted. It complements RevoGrid's `readonly` support: - use column `readonly` for static UI-level rules - use `permissions.canEditCell` for user, row, tenant, or workflow-specific rules ```ts permissions: { canEditCell({ user, row, prop, newValue }) { if (row?.approvalStatus === 'Approved') return false; if (prop === 'budgetLimit') return user.roles?.includes('finance-admin') ?? false; if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager'; return user.permissionLevel === 'editor'; }, } ``` ### `audit` When `AuditHistoryPlugin` is registered, collaboration commits are recorded as audit events by default. ```ts audit: { enabled: true, actionType: 'collaborative-price-change', source: 'api', } ``` Disable audit only for temporary or non-business data: ```ts audit: false ``` ## Commit Modes Use `mode: 'autoCommit'` when the server should accept or reject each edit as soon as it is made. Use `mode: 'review'` when users need an explicit commit/discard workflow. In review mode, edits are optimistic locally and remain in `getPendingChanges()` until the host commits or discards them through the plugin instance. ## Audit Trail When `AuditHistoryPlugin` is registered and `audit.enabled !== false`, committed collaborative changes call `AuditHistoryPlugin.recordEvent()` with: - `type: 'collaborative-edit'` - current collaborative user - transaction id - row id, row index, column, old value, and new value - collaborative metadata and document id Use the Audit History storage hooks to persist those records to your backend. ## Common First-Time Mistakes | Mistake | What happens | Fix | |:--|:--|:--| | Missing `EventManagerPlugin` | Edits do not enter the normalized collaborative edit pipeline. | Register `EventManagerPlugin` before `CollaborativeEditingPlugin`. | | Missing `user` | Local edits are ignored because the plugin cannot attribute them. | Pass a stable `user` with at least `id` and `name`. | | Missing or unstable `rowIdProp` | Edits can target the wrong row after sorting, filtering, or reordering. | Use a durable business id field. | | Different `documentId` values | Users do not share the same collaborative document. | Use one stable `documentId` for every client in the same session. | | Expecting automatic persistence | Local/Yjs state changes, but your backend is not updated. | Add a `commitAdapter`. | | Expecting visible labels without presence plugin | Editing works, but remote cursors and ranges do not render. | Add `CollaborativePresencePlugin` and enable `presence`. | ## What To Read Next - [Collaborative Presence](/guides/data-manage/collaborative-presence/) for remote cursors, selections, labels, stale users, and pinned-area behavior. - [Audit Trail History](/guides/data-manage/audit-history/) for persisted change logs and restore workflows. - [Event Manager](/guides/data-manage/event-manager-explanation/) for the edit event pipeline collaborative editing uses. --- # Collaborative Presence URL: https://pro.rv-grid.com/guides/data-manage/collaborative-presence/ Source: src/content/docs/guides/data-manage/collaborative-presence.mdx Description: Render remote collaborator focus cells, selected ranges, labels, colors, stale-user cleanup, and source-index row patches in RevoGrid Pro. `CollaborativePresencePlugin` shows where other users are looking or editing inside the grid. It renders colored focus rectangles, selected ranges, avatars, and user labels without changing the local user's focus, selection, editor, data history, or keyboard state. Use it by itself when your app already has presence data, or use it with [Collaborative Editing](/guides/data-manage/collaborative-editing/) when Yjs awareness should drive remote cursors and ranges automatically. ## What It Does Show the single cell another user is focused on. Show another user's selected or active range. Render initials, names, and stable colors so users can identify each other quickly. Resolve marker positions from RevoGrid viewport state, including pinned columns, instead of querying physical cell DOM nodes. Presence markers are overlays. They do not move local focus, create local selections, open editors, or persist data by themselves. ## Basic Setup 1. Import and register the plugin. ```ts import { CollaborativePresencePlugin, } from '@revolist/revogrid-pro'; grid.plugins = [ CollaborativePresencePlugin, ]; ``` 2. Provide users through `grid.collaborativePresence`. ```ts grid.collaborativePresence = { users: [ { id: 'avery', name: 'Avery Stone', initials: 'AS', color: '#2563eb', activity: 'editing', focus: { x: 0, y: 1 }, range: { x: 0, y: 1, x1: 2, y1: 3 }, lastActiveAt: Date.now(), }, { id: 'grace', name: 'Grace Hopper', initials: 'GH', color: '#16a34a', activity: 'viewing', focus: { x: 1, y: 0 }, lastActiveAt: Date.now(), }, ], }; ``` The plugin observes `grid.collaborativePresence`, `grid['collaborative-presence']`, and `additionalData.collaborativePresence`. ## Cell And Range Coordinates Presence coordinates use grid source indexes: - `x` is the source column index in the target column area. - `y` is the source row index in the target row area. - `x1` and `y1` are the opposite range corner. - `colType` defaults to `'rgCol'`. - `rowType` defaults to `'rgRow'`. ```ts grid.collaborativePresence = { users: [ { id: 'avery', name: 'Avery Stone', color: '#2563eb', focus: { x: 0, y: 2 }, range: { x: 0, y: 2, x1: 3, y1: 4 }, }, ], }; ``` For pinned columns, pass the matching column type. This keeps the marker in the pinned viewport instead of the main viewport. ```ts grid.collaborativePresence = { users: [ { id: 'grace', name: 'Grace Hopper', color: '#16a34a', focus: { x: 0, y: 1, colType: 'colPinStart', rowType: 'rgRow', }, }, ], }; ``` The plugin resolves source indexes through RevoGrid stores and viewport structures. This is important when sorting, filtering, pinning, or virtualization changes which physical cells are mounted. ## Configuration Reference | Property | Type | Default | What it controls | |:--|:--|:--|:--| | `enabled` | `boolean` | `true` | Turns all presence markers on or off without removing the plugin. | | `showLabels` | `boolean` | `true` | Shows or hides user name labels. Focus and range colors still render when labels are hidden. | | `staleAfterMs` | `number` | `undefined` | Hides users whose `lastActiveAt` is older than the configured timeout. | | `users` | `CollaborativePresenceUser[]` | `[]` | The remote users to render. Each user can provide `focus`, `range`, or both. | | `remoteEdits` | `CollaborativePresenceRowPatch[]` | `[]` | Optional source-row patches for remote data changes that should update grid rows without resetting focus or selection. | ## User Shape ```ts type CollaborativePresenceUser = { /** * Stable user id used to distinguish one collaborator from another. * Use your application user id, not a temporary session id or display name. */ id: string; /** * Human-readable name shown in the presence label when `showLabels` is true. */ name: string; /** * Optional short avatar text. If omitted, the label can derive initials from `name`. */ initials?: string; /** * Optional marker color used for focus borders, range overlays, avatar, and label. * Keep this stable per user so collaborators are easy to recognize. */ color?: string; /** * Optional activity state for your own presence model. * The plugin accepts viewing, editing, and idle states. */ activity?: 'viewing' | 'editing' | 'idle'; /** * Optional focused cell for this user. * `x` and `y` are source indexes in the specified column and row areas. * Set this to `null` when the user has no active focus to render. */ focus?: { /** Source column index inside `colType`. */ x: number; /** Source row index inside `rowType`. */ y: number; /** * Column area that owns the focused cell. * Defaults to `rgCol`; use pinned column types for pinned cells. */ colType?: 'colPinStart' | 'rgCol' | 'colPinEnd'; /** * Row area that owns the focused cell. * Defaults to `rgRow`; use pinned row types for pinned rows. */ rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd'; } | null; /** * Optional selected or active range for this user. * `x`, `y` and `x1`, `y1` are opposite corners in source indexes. * Set this to `null` when the user has no range to render. */ range?: { /** Source column index for one range corner. */ x: number; /** Source row index for one range corner. */ y: number; /** Source column index for the opposite range corner. */ x1: number; /** Source row index for the opposite range corner. */ y1: number; /** * Column area that owns the range. * Defaults to `rgCol`; use pinned column types for pinned ranges. */ colType?: 'colPinStart' | 'rgCol' | 'colPinEnd'; /** * Row area that owns the range. * Defaults to `rgRow`; use pinned row types for pinned ranges. */ rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd'; } | null; /** * Last known activity time for stale-user cleanup. * Accepts a timestamp, date string, or Date object. * Used only when `staleAfterMs` is configured. */ lastActiveAt?: number | string | Date; }; ``` Use stable `id` values and consistent colors so the same person looks the same across grids and sessions. ## Labels And Stale Users Use `showLabels: false` for dense grids where full labels would cover too much content. ```ts grid.collaborativePresence = { showLabels: false, users, }; ``` Use `staleAfterMs` to hide users who disconnect or stop sending presence updates. ```ts grid.collaborativePresence = { staleAfterMs: 15_000, users: remoteUsers.map(user => ({ ...user, lastActiveAt: user.lastSeenAt, })), }; ``` ## Remote Row Patches `remoteEdits` can apply remote row updates directly into the RevoGrid data store without resetting the full source or disturbing the local editor state. ```ts grid.collaborativePresence = { remoteEdits: [ { rowIndex: 2, rowType: 'rgRow', data: { status: 'Approved', owner: 'Grace', }, }, ], users, }; ``` `rowIndex` is a source row index, not the currently visible row position. That keeps patches stable when the grid is sorted or filtered. ## With Collaborative Editing When `CollaborativeEditingPlugin` has presence enabled, it maps Yjs awareness state into `CollaborativePresencePlugin`. ```ts grid.plugins = [ EventManagerPlugin, CollaborativePresencePlugin, CollaborativeEditingPlugin, ]; grid.collaborativeEditing = { documentId: `budget:${budgetId}`, user: currentUser, rowIdProp: 'id', presence: { enabled: true, showLabels: true, staleAfterMs: 15_000, }, }; ``` Use direct `grid.collaborativePresence` updates when your app owns the realtime presence source. Use collaborative editing presence when Yjs awareness owns the realtime presence source. --- # Event Manager URL: https://pro.rv-grid.com/guides/data-manage/event-manager-explanation/ Source: src/content/docs/guides/data-manage/event-manager-explanation.mdx Description: Get comprehensive documentation on the event management. Learn how to handle, customize, and optimize events to create complex interactions and workflows with the Event Manager Plugin for RevoGrid. The main purpose of this plugin is to collect and dispatch edit events in one centralized location, simplifying event management. The Event Manager Plugin aggregates various editing events into a single event, `gridedit`. This approach reduces the complexity of handling multiple event types individually, such as `clipboardrangepaste`, `beforeedit`, and `beforerangeedit`. By using the Event Manager Plugin, you can catch all these events through one unified event, making it a robust starting point for building your custom event management system. ## Key Features - **Centralized Event Handling:** Collects multiple edit events into a single `gridedit` event. - **Simplified Event Management:** Reduces the need to manage numerous event types individually. - **Customizable Workflows:** Provides a foundation to create complex interactions and workflows. ## Implementation Here's a brief overview of how to implement the Event Manager Plugin in your RevoGrid setup. ### Step-by-Step Guide 1. **Import Required Modules:** Import the necessary modules from the RevoGrid library to start setting up the Event Manager Plugin. 2. **Define Edit Details Type:** Create a type for edit details that will help structure the event data. 3. **Create EventManagerPlugin Class:** Develop the `EventManagerPlugin` class to handle and dispatch edit events. This class listens for various edit events and dispatches a unified `gridedit` event. - **Collect Edit Events:** Listens for `clipboardrangepaste`, `beforeedit`, and `beforerangeedit` events. - **Dispatch Unified Event:** Dispatches the `gridedit` event after collecting details from the various edit events. 4. **Add Plugin to RevoGrid:** Attach the Event Manager Plugin to your RevoGrid instance and start listening for the `gridedit` event. ```javascript ... grid.plugins = [EventManagerPlugin]; ... ``` For detailed implementation, please refer to the full code below. The Event Manager Plugin is a powerful tool for managing edit events. By consolidating multiple event types into a single, manageable event, it simplifies event handling and provides a solid foundation for developing custom workflows and interactions. --- # Formula Excel URL: https://pro.rv-grid.com/guides/data-manage/formula-excel/ Source: src/content/docs/guides/data-manage/formula-excel.mdx Description: Implement and use complex formulas similar to Excel, allowing for dynamic calculations and data manipulation within your grid cells. Implement and use complex formulas similar to Excel, allowing for dynamic calculations and data manipulation within your grid cells. The Formula Plugin empowers grid to support Excel-like formulas, enabling users to perform advanced data processing directly within the grid. With the Formula Plugin, users can enter formulas in grid cells using an equal sign (`=`) followed by the formula (e.g., `=SUM(A1:B2)`). The plugin will parse the formula, calculate the result, and display it in the corresponding cell. It also ensures that the results are dynamically updated whenever the referenced cells change, similar to how Excel operates. > This feature makes it easier to handle complex data manipulations and calculations directly within the grid, providing a powerful tool for users who need to process and analyze data on the fly. ## Named ranges and formula names Named ranges are a Pro layer for making formulas readable and reusable. Add `NamedRangesPlugin` next to `FormulaPlugin` whenever a grid uses `grid.formulaNames`, the Formula Name Manager, named constants, named ranges, or named formulas. `FormulaPlugin` can evaluate basic A1 formulas by itself. It also keeps a lightweight fallback for read-only `grid.formulaNames` evaluation, but `NamedRangesPlugin` owns the runtime registry: validation, mutation APIs, lifecycle events, range jump behavior, and deterministic A1 ref updates after row or column changes. For Excel-style named formulas, install both plugins explicitly. Names can be: - **Workbook-global**: available to every sheet context. - **Sheet-local**: tied to a `sheetId` and resolved before a workbook name with the same display name. - **Range names**: A1 references such as `B2:B20`, usable in functions like `SUM(Revenue)`. - **Constant names**: values such as thresholds, rates, or flags. - **Formula names**: reusable formulas such as `=SUM(Revenue) * TaxRate`. ### Quick setup ```ts import { ColumnDropdown, FormulaBarPlugin, FormulaDependencyHighlightPlugin, FormulaPlugin, NamedRangesPlugin, createFormulaConditionalCellProperties, createNamedRangeDropdown, } from '@revolist/revogrid-pro'; const source = [ { product: 'Keyboard', price: 120, category: 'Hardware' }, { product: 'License', price: 900, category: 'Software' }, ]; const columns = [ { prop: 'product', name: 'Product' }, { prop: 'price', name: 'Price' }, { prop: 'category', name: 'Category', columnType: 'categoryDropdown' }, ]; const formulaNames = { activeSheetId: 'Budget', names: [ { name: 'PriceList', scope: 'workbook', kind: 'range', ref: 'B1:B2' }, { name: 'TargetPrice', scope: 'workbook', kind: 'constant', value: 500 }, { name: 'CategoryList', scope: 'workbook', kind: 'range', ref: 'C1:C2' }, { name: 'AboveTargetTotal', scope: 'workbook', kind: 'formula', value: '=SUM(PriceList)' }, ], }; columns[1].cellProperties = createFormulaConditionalCellProperties( '=cellvalue>TargetPrice', { class: 'above-target', style: { fontWeight: '600' } }, { allSources: source, columns, names: formulaNames.names }, ); columns[2].dropdown = createNamedRangeDropdown( 'CategoryList', { allSources: source, columns, names: formulaNames.names }, ); grid.plugins = [FormulaBarPlugin, FormulaDependencyHighlightPlugin, NamedRangesPlugin, FormulaPlugin]; grid.columnTypes = { categoryDropdown: ColumnDropdown }; grid.formulaNames = formulaNames; grid.formulaDependencyHighlight = { includeNamedRanges: true, dependencyColors: ['#2563eb', '#dc2626', '#16a34a'], }; grid.columns = columns; grid.source = source; grid.pinnedBottomSource = [{ product: 'Total', price: '=SUM(PriceList)', category: 'Named range' }]; const formulaBar = document.querySelector('#formula-bar'); if (formulaBar) { grid.formulaBar = { el: formulaBar }; } ``` :::note `additionalData.formulaNames` is still supported for existing integrations, but new code should prefer `grid.formulaNames`. ::: ### Configuration Set names either as a full config object or as a shorthand array. ```ts grid.formulaNames = { activeSheetId: 'Q4', rowIdProp: 'id', autoUpdateRefs: true, names: [ { name: 'Revenue', scope: 'workbook', kind: 'range', ref: 'B2:B50' }, { name: 'TaxRate', scope: 'workbook', kind: 'constant', value: 0.2 }, { name: 'NetRevenue', scope: 'sheet', sheetId: 'Q4', kind: 'formula', value: '=SUM(Revenue) * (1 - TaxRate)' }, ], }; ``` | Field | Description | | --- | --- | | `names` | List of formula-name definitions. | | `activeSheetId` | Current sheet context. Defaults to `default`. | | `rowIdProp` | Stable row identity field used to detect deterministic inserts/deletes across immutable row replacements. | | `autoUpdateRefs` | Enables A1 ref updates after deterministic row/column inserts/deletes. Defaults to `true`. | Each definition supports: | Field | Description | | --- | --- | | `name` | Display name used in formulas. Must not look like a cell address or match a formula function. | | `scope` | `workbook` or `sheet`. Defaults to `workbook` unless `sheetId` is set. | | `sheetId` | Sheet id for sheet-local names. | | `kind` | `range`, `constant`, or `formula`. | | `ref` | A1 reference for range names. | | `value` | Constant value or named formula string. Formula values must start with `=`. | | `comment` | Optional description shown by tooling such as the Name Manager. | ### Scope and resolution Unqualified names resolve with Excel-style precedence: the active sheet-local name wins, then the workbook-global name is used. ```ts grid.formulaNames = { activeSheetId: 'Q4', names: [ { name: 'TaxRate', scope: 'workbook', kind: 'constant', value: 0.18 }, { name: 'TaxRate', scope: 'sheet', sheetId: 'Q4', kind: 'constant', value: 0.22 }, { name: 'TaxRate', scope: 'sheet', sheetId: 'Q1', kind: 'constant', value: 0.2 }, ], }; // Uses Q4 sheet-local value. grid.source = [{ total: '=A1*TaxRate' }]; // Force workbook or a specific sheet. grid.source = [ { total: '=A1*workbook!TaxRate' }, { total: '=A1*Q1!TaxRate' }, ]; ``` Duplicate names are rejected only within the same scope. The same display name may exist once globally and once per sheet. ### Runtime API After the grid is initialized, get the plugin instance through `grid.getPlugins()` and use the runtime methods. ```ts const plugins = await grid.getPlugins(); const namedRanges = plugins.find((plugin) => 'getFormulaNames' in plugin); namedRanges.upsertFormulaName({ name: 'ForecastRange', scope: 'workbook', kind: 'range', ref: 'D2:D20', }); const validation = namedRanges.validateFormulaNameRef({ name: 'ForecastRange', kind: 'range', ref: 'D2:D20', }); await namedRanges.jumpToFormulaName('ForecastRange'); ``` Available methods: | Method | Description | | --- | --- | | `getFormulaNames()` | Returns valid names as a defensive copy. | | `getFormulaNamesConfig()` | Returns the current public config shape. | | `setFormulaNames(config)` | Replaces the full registry. | | `upsertFormulaName(definition)` | Creates or replaces a name in its scope. | | `deleteFormulaName(name, scope?, sheetId?)` | Deletes a name. Pass scope and sheet id to target a sheet-local name. | | `validateFormulaNameRef(definition)` | Validates name syntax, duplicate scope conflicts, formula kind, and range bounds. | | `jumpToFormulaName(name)` | Scrolls and focuses the first cell of a range name. Supports `workbook!Name` and `SheetId!Name`. | The plugin emits: | Event | Detail | | --- | --- | | `formulanameschange` | Current normalized registry. | | `formulanamevalidationerror` | Validation result for invalid definitions. | | `formulanamejump` | Name, definition, row index, and column index for a successful jump. | ### Name Manager UI Use the bundled panel when users need to manage names at runtime. ```ts import { defineFormulaNameManager } from '@revolist/revogrid-pro'; const panel = document.querySelector('#name-manager'); defineFormulaNameManager(panel, grid, { pageSize: 50 }); ``` The manager supports: - Create, edit, and delete. - Workbook and sheet-local scope selection. - Range, constant, and formula names. - Inline validation status. - Jump-to for range names. ### Formula bar input Use `FormulaBarPlugin` when you need an Excel-style input outside the grid. The bar shows the focused cell's raw source value, so formula cells display `=...` even while the grid renders the computed result. ```ts import { FormulaBarPlugin, FormulaPlugin, } from '@revolist/revogrid-pro'; grid.plugins = [FormulaBarPlugin, FormulaPlugin]; const input = document.querySelector('#formula-bar'); if (input) { grid.formulaBar = { el: input }; } ``` The input commits on Enter. Blur only synchronizes the bar with the current grid state and does not write to the grid. While the input is active, the grid marks the cell that will receive the formula bar edit; if grid focus changes, that edit target is cleared so the bar cannot accidentally apply an old value to a newly focused cell. Set `grid.formulaBar = null` when removing the input. The plugin runtime also exposes `getFormulaBarState()` and `setFormulaBarValue(value)` through `grid.getPlugins()` for custom UI controls. If the same grid also uses named formulas, install the runtime registry plugin as well: ```ts grid.plugins = [FormulaBarPlugin, NamedRangesPlugin, FormulaPlugin]; ``` ### Formula dependency highlighting Use `FormulaDependencyHighlightPlugin` to highlight the direct cells and named ranges that a focused formula cell references. The first version highlights direct A1 references, A1 ranges, and named ranges only; it does not expand named formulas or the full transitive dependency chain. ```ts import { FormulaDependencyHighlightPlugin, FormulaPlugin, NamedRangesPlugin, } from '@revolist/revogrid-pro'; grid.plugins = [FormulaDependencyHighlightPlugin, NamedRangesPlugin, FormulaPlugin]; grid.formulaDependencyHighlight = { dependencyClass: 'formula-source-cell', formulaCellClass: 'formula-active-cell', dependencyColors: ['#2563eb', '#dc2626', '#16a34a', '#9333ea'], includeNamedRanges: true, }; ``` Dependency cells cycle through `dependencyColors` in formula-reference order. Set `grid.formulaDependencyHighlight = false` or `{ enabled: false }` to disable highlighting while keeping the plugin installed. ### Dropdown validation lists `createNamedRangeDropdown` converts a named range into a dropdown source. This is a lightweight validation-list helper; it does not replace a full validation subsystem. ```ts columns[2] = { prop: 'status', name: 'Status', columnType: 'statusDropdown', dropdown: createNamedRangeDropdown('AllowedStatuses', { allSources: source, columns, names: [{ name: 'AllowedStatuses', kind: 'range', ref: 'C1:C3' }], label: (value) => String(value).toUpperCase(), }), }; ``` ### Conditional formatting formulas `createFormulaConditionalCellProperties` builds a `cellProperties` callback. Conditional formulas can use names and the built-in tokens `cellvalue`, `row`, and `column`. ```ts columns[1].cellProperties = createFormulaConditionalCellProperties( '=cellvalue >= TargetPrice', { class: 'above-target' }, { allSources: source, columns, names: [{ name: 'TargetPrice', kind: 'constant', value: 500 }], }, ); ``` Supported comparison operators are `>`, `>=`, `<`, `<=`, `=`, and `<>`. Numeric and string comparisons are supported. ### Ref updates after row and column changes When `autoUpdateRefs !== false`, `NamedRangesPlugin` updates A1 refs in: - Named range refs. - Named formula strings. - Formula strings stored in existing source rows. Ref updates happen only when the plugin can infer a deterministic row or column insert/delete. For immutable source replacements, configure `rowIdProp` so existing rows can be matched reliably. ```ts grid.formulaNames = { rowIdProp: 'id', names: [{ name: 'InputRows', kind: 'range', ref: 'A2:A20' }], }; ``` If a replacement is arbitrary and stable identity cannot be inferred, the plugin preserves refs and reports validation state instead of guessing. ### Framework examples The Formula demo includes named ranges across all supported framework examples. Each example includes the same interaction surface: formula names on/off, compact/darkCompact theme switching, odd-row styling on/off, row insertion for ref-update behavior, a formula-name manager panel, named dropdown values, conditional formatting, and highlighted formula cells. - Vanilla TypeScript: `examples/components/src/components/formula/Formula.ts` - React: `examples/components/src/components/formula/Formula.tsx` - Vue 3: `examples/components/src/components/formula/Formula.vue` - Angular: `examples/components/src/components/formula/FormulaAngular.ts` ## Formula List The Formula Plugin is based on the `formulajs` engine, which means many features are only limited by system resources. This flexibility allows for extensive formula functionality, making it possible to use the plugin for a wide range of calculations and data operations. Below is a comprehensive list of supported formulas and their capabilities. The performance and limits of the Formula Plugin are largely dependent on the system resources available. Factors such as available memory and CPU power can influence the plugin’s performance, especially when working with large datasets or complex calculations. Always consider the environment in which the plugin will be used to optimize its performance. ## Saving Resolved (Calculated) Values The Formula Plugin evaluates formulas **at render time only**. The underlying data source always stores the raw formula string (e.g., `=SUM(B1:B6)`). This mirrors the way Excel works internally — the formula itself is the source of truth, and the computed display value is derived from it. This means that reading `grid.source` (or listening to `afteredit`) will return the formula string, **not** the numeric result. If you need to persist the computed value to a database you must resolve it yourself before saving. ### Resolving formulas before saving Use the `evaluateRawValuesFormula` utility (exported from `@revolist/revogrid-pro`) together with `grid.getSource()` and `grid.getColumns()`: ```typescript import { evaluateRawValuesFormula, isFormula } from '@revolist/revogrid-pro'; async function saveToDatabase(grid: HTMLRevoGridElement) { const source = await grid.getSource(); // raw rows — formulas intact const columns = await grid.getColumns(); // ColumnRegular[] const resolvedRows = source.map(row => { const resolved = { ...row }; for (const col of columns) { const val = row[col.prop]; if (isFormula(val)) { resolved[col.prop] = evaluateRawValuesFormula(val, source, columns); } } return resolved; }); // persist resolvedRows to your database } ``` :::note `evaluateRawValuesFormula` expects flat `ColumnRegular[]` (no grouping columns). The column order must match the column array you pass to the grid, as the formula engine resolves cell addresses (e.g., `B3`) by column index within that array. This is the same approach used internally by the Excel export plugin. ::: --- # Grid Preset URL: https://pro.rv-grid.com/guides/data-manage/grid-preset/ Source: src/content/docs/guides/data-manage/grid-preset.mdx Description: Compose common Pro grid behaviors with one aggregate preset plugin. `GridPresetPlugin` is a Pro aggregate plugin. It installs a named set of existing Pro plugins and applies sensible defaults only when the matching direct grid prop is missing. The preset does not duplicate configuration already owned by individual plugins. Customize behavior through the regular plugin props such as `grid.tree`, `grid.rowOrder`, `grid.rowContextMenu`, `grid.columnContextMenu`, `grid.masterRow`, `grid.stretch`, and `grid.columnTypes`. ## Project Pipeline Use `project-pipeline` for task and project grids that need event management, row drag and drop, row selection, master-detail overlays, row headers, row and column context menus, column stretch, animation, advanced filtering, filter headers, column hiding, tooltips, and column add popup. ```ts import { GridPresetPlugin } from '@revolist/revogrid-presets'; const grid = document.createElement('revo-grid'); grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'project-pipeline'; grid.columns = [ { prop: 'name', name: 'Task', tree: true, rowDrag: true }, { prop: 'owner', name: 'Owner' }, { prop: 'status', name: 'Status' }, ]; grid.source = [ { id: 1, parentId: null, name: 'Launch plan', owner: 'PM', status: 'Active' }, { id: 2, parentId: 1, name: 'Design review', owner: 'Design', status: 'Done' }, ]; ``` `project-pipeline` installs tree capability, but hierarchy is opt-in. Set `grid.tree` when the project grid should use parent/child rows: ```ts grid.tree = { idField: 'id', parentIdField: 'parentId', rootParentId: null, animation: true, }; ``` It creates missing `grid.rowOrder`, `grid.stretch`, `grid.rowContextMenu`, and `grid.columnContextMenu` defaults. ## Override Tree Settings Set `grid.tree` before or after `gridPreset`. The direct prop is the source of truth, so the preset does not overwrite it. ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'project-pipeline'; grid.tree = { idField: 'taskId', parentIdField: 'parentTaskId', rootParentId: 'root', expandedRowIds: new Set(['planning']), }; ``` ## Extend Context Menus Use the existing context menu props when you want to replace preset defaults. ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'project-pipeline'; grid.rowContextMenu = { items: [ { name: 'Open task', action: (_event, _focused, _range, close, context) => { console.log('row menu', context?.menu); close?.(); }, }, ], }; grid.columnContextMenu = { items: [ { name: 'Autosize column' }, { name: 'Hide column' }, ], }; ``` For `operations`, use `gridPreset.contextMenus` when you want to keep the default row/column operations and append app-specific actions. ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = { preset: 'operations', contextMenus: { row: { mode: 'extend', items: [ { name: 'Copy invoice', action: (_event, _focused, _range, close) => close?.(), }, ], }, }, }; ``` ## Operations Preset Use `operations` for a basic operational grid with event management, row selection, row headers, context menus, column stretch, odd row styling, column hide support, and tooltips. ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'operations'; grid.stretch = 'last'; ``` The preset supplies a default row menu with `Add new row before`, `Add new row after`, and `Delete row`. It supplies a default column menu for sorting, hiding, pinning/unpinning, grouping by column, clearing sorting, clearing grouping, and opening/clearing filters. Sorting and filtering entries only appear when the target column and grid support those capabilities. Setting `grid.rowContextMenu` or `grid.columnContextMenu` directly replaces those defaults; `contextMenus.*.mode = 'extend'` appends to them. Grouped column headers use the same column menu entry point. The operations preset adds group-wide actions for clearing sorting/filtering, grouping by the group columns, clearing grouping, hiding the whole group, pinning the group left/right, and unpinning pinned groups. ## Common Column Types Use `common-column-types` to register a reusable column type map for common data and Pro editor cells. ```ts import { GridPresetPlugin } from '@revolist/revogrid-presets'; grid.plugins = [GridPresetPlugin]; grid.gridPreset = { presets: ['common-column-types', 'project-pipeline'], conflictPolicy: 'silent', }; grid.columns = [ { prop: 'due', name: 'Due', columnType: 'date' }, { prop: 'budget', name: 'Budget', columnType: 'currency' }, { prop: 'status', name: 'Status', columnType: 'dropdown' }, ]; ``` The built-in keys are `date`, `number`, `integer`, `percent`, `currency`, `select`, `dropdown`, `progress`, `rating`, `rowSelect`, `multi`, `checkbox`, `counter`, `slider`, `timeline`, `textarea`, `link`, `avatar`, `avatarText`, `badge`, `thumbs`, `change`, `progressLine`, `progressLineValue`, `circularProgress`, `timelineRange`, `sparkline`, and `heatmap`. For project and operations grids, those visual keys keep column definitions compact while still using the existing Pro renderers: ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'common-column-types'; grid.columns = [ { prop: 'owner', columnType: 'avatarText' }, { prop: 'status', columnType: 'badge', badgeStyles: { Blocked: { backgroundColor: '#fee2e2', color: '#991b1b' }, }, }, { prop: 'progress', columnType: 'progressLine' }, ]; ``` When you need common types plus custom types, use the helper and assign `grid.columnTypes` directly: ```ts import { createCommonColumnTypes } from '@revolist/revogrid-presets'; grid.columnTypes = { ...createCommonColumnTypes(), statusBadge: { readonly: true }, }; ``` ## Tree Preset Use `tree` when the grid only needs event management, hierarchy, row ordering, selection, row headers, context menus, stretch, and dimension animation. ```ts grid.plugins = [GridPresetPlugin]; grid.gridPreset = 'tree'; grid.tree = { idField: 'id', parentIdField: 'parentId', rootParentId: null, }; ``` ## Plugin Overrides Use the object form to merge presets, change the preset plugin list, or silence warnings. Keep plugin behavior configuration on direct plugin props. ```ts import { GridPresetPlugin } from '@revolist/revogrid-presets'; import { TooltipPlugin } from '@revolist/revogrid-pro'; grid.plugins = [GridPresetPlugin]; grid.gridPreset = { presets: ['tree', 'operations'], plugins: { prepend: [MyBeforePresetPlugin], append: [MyAfterPresetPlugin], remove: [TooltipPlugin], }, }; ``` `GridPresetPlugin` skips duplicate classes and skips plugins that are already registered in the grid plugin provider. You can also merge presets with the array shorthand: ```ts grid.gridPreset = ['tree', 'operations']; ``` If you only want a reusable plugin pack and do not want any built-in preset defaults, omit `preset`/`presets` and provide the plugin list directly: ```ts import { GridPresetPlugin, type GridPresetConfig, } from '@revolist/revogrid-presets'; const customPreset = { plugins: { append: [MyStatusPlugin, MyAuditPlugin], }, } satisfies GridPresetConfig; grid.plugins = [GridPresetPlugin]; grid.gridPreset = customPreset; ``` ## Conflict Warnings Presets do not block conflicting features. By default they warn when they find likely overlap with `RowExpandPlugin`, core grouping, or `ServerSideGroupingPlugin`. ```ts grid.gridPreset = { preset: 'project-pipeline', conflictPolicy: 'silent', }; ``` ## Commands ```sh pnpm docs:api pnpm --filter @revolist/revogrid-presets test:ssr-import pnpm --filter @revolist/revogrid-presets test pnpm --filter @revolist/revogrid-presets lint pnpm --filter @revolist/revogrid-presets build pnpm --filter @revolist/revogrid-portal build ``` See the [Grid Preset API](/api/grid-preset/) for exported types and module extensions. --- # Grouping with Aggregation URL: https://pro.rv-grid.com/guides/data-manage/grouping-aggregation/ Source: src/content/docs/guides/data-manage/grouping-aggregation.mdx Description: Learn how to implement data grouping with custom aggregation functions in RevoGrid ## Overview The Grouping with Aggregation feature allows you to organize your data into hierarchical groups and display custom aggregated values for each group. This is particularly useful for displaying summary statistics, totals, averages, or custom calculations for grouped data. ## Key Features - **Hierarchical Grouping**: Group data by multiple columns in a tree-like structure - **Custom Aggregations**: Define custom aggregation functions for different data types - **Visual Indicators**: Expandable/collapsible groups with aggregation values displayed - **Flexible Templates**: Customize how group headers and aggregations are displayed ## Basic Usage ## Implementation ### 1. Import Required Components ```typescript import { groupingAggregation } from '@revolist/revogrid-pro'; ``` ### 2. Define Aggregation Functions Create custom aggregation functions for different columns. **Important**: Aggregation functions receive an array of complete data objects, not just the column values. You need to extract the specific property you want to aggregate: ```typescript const aggregations = { price: (values: any[]) => { const prices = values.map(item => item.price); const sum = prices.reduce((acc, val) => acc + val, 0); const avg = sum / prices.length; return `Avg: $${avg.toFixed(2)}`; }, quantity: (values: any[]) => { const quantities = values.map(item => item.quantity); const total = quantities.reduce((acc, val) => acc + val, 0); return `Total: ${total}`; }, product: (values: any[]) => { return `${values.length} products`; } }; ``` ### 3. Create Group Template Use the `groupingAggregation` function to create a template that displays aggregations: ```typescript const groupTemplate = (h: any, props: any) => { return groupingAggregation(h, props, aggregations); }; ``` ### 4. Configure Columns Apply the group template to columns that will be used for grouping: ```typescript const columns = [ { name: '🆔 ID', prop: 'id', size: 60 }, { name: '📂 Category', prop: 'category', size: 120, template: groupTemplate }, { name: '📁 Subcategory', prop: 'subcategory', size: 120, template: groupTemplate }, { name: '📦 Product', prop: 'product', size: 150 }, { name: '💰 Price', prop: 'price', size: 100 }, { name: '📊 Quantity', prop: 'quantity', size: 100 }, { name: '📅 Order Date', prop: 'orderDate', size: 120 }, { name: '🚚 Delivery Date', prop: 'deliveryDate', size: 120 }, { name: '👤 Customer', prop: 'customer', size: 150 }, { name: '🌍 Region', prop: 'region', size: 100 }, { name: '📋 Status', prop: 'status', size: 100 }, ]; ``` ### 5. Set Up Grouping Configuration Define which columns should be used for grouping: ```typescript const grouping = { props: ['orderDate', 'category', 'subcategory'] }; ``` ### 6. Initialize the Grid ```typescript const grid = document.createElement('revo-grid'); grid.source = yourData; grid.columns = columns; grid.grouping = grouping; // Grouping is a built-in feature, no plugin needed ``` ## Aggregation Function Examples ### Numeric Aggregations ```typescript // Average price: (values: any[]) => { const prices = values.map(item => item.price); const avg = prices.reduce((acc, val) => acc + val, 0) / prices.length; return `Avg: $${avg.toFixed(2)}`; } // Sum quantity: (values: any[]) => { const quantities = values.map(item => item.quantity); const total = quantities.reduce((acc, val) => acc + val, 0); return `Total: ${total}`; } // Min/Max price: (values: any[]) => { const prices = values.map(item => item.price); const min = Math.min(...prices); const max = Math.max(...prices); return `Range: $${min} - $${max}`; } ``` ### String Aggregations ```typescript // Count product: (values: any[]) => { return `${values.length} products`; } // Concatenation names: (values: any[]) => { const names = values.map(item => item.name); return names.join(', '); } // Unique count categories: (values: any[]) => { const categories = values.map(item => item.category); const unique = new Set(categories); return `${unique.size} unique categories`; } ``` ### Date Aggregations ```typescript // Date range orderDate: (values: any[]) => { const dates = values.map(item => new Date(item.orderDate)); const sorted = dates.sort((a, b) => a.getTime() - b.getTime()); const earliest = sorted[0].toLocaleDateString(); const latest = sorted[sorted.length - 1].toLocaleDateString(); return `${earliest} - ${latest}`; } ``` ## Advanced Configuration ### Custom Group Header Styling You can customize the appearance of group headers by modifying the template function: ```typescript const customGroupTemplate = (h: any, props: any) => { const aggregationValue = getAggregationValue(props); return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '4px 8px', backgroundColor: '#f0f0f0', borderRadius: '4px' } }, [ h('span', { style: { fontWeight: 'bold' } }, props.name), h('span', { style: { fontSize: '12px', color: '#666' } }, `(${aggregationValue})`) ]); }; ``` ### Conditional Aggregations You can apply different aggregation logic based on the column or group: ```typescript const conditionalAggregations = { price: (values: any[]) => { const prices = values.map(item => item.price); const sum = prices.reduce((a, b) => a + b, 0); const avg = sum / prices.length; return `Avg: $${avg.toFixed(2)}`; } }; ``` ## Best Practices 1. **Performance**: Keep aggregation functions lightweight, especially with large datasets 2. **User Experience**: Provide clear, meaningful aggregation labels 3. **Data Types**: Ensure aggregation functions handle the correct data types 4. **Error Handling**: Add validation for edge cases (empty arrays, null values) 5. **Accessibility**: Use descriptive text for screen readers ## Common Use Cases - **Sales Reports**: Group by region/category with revenue totals - **Inventory Management**: Group by department with stock quantities - **Financial Data**: Group by account type with balance summaries - **Project Management**: Group by team with task counts and completion rates --- # History Undo/Redo URL: https://pro.rv-grid.com/guides/data-manage/history/ Source: src/content/docs/guides/data-manage/history.mdx Description: History and undo and redo capabilities in data grid, making it easier to manage user edits The History Plugin brings powerful undo and redo capabilities to your data grid, making it easier to manage user edits and maintain data integrity. Keyboard shortcuts like `Ctrl+Z` (or `Cmd+Z` on macOS) for undo and `Ctrl+Y` (or `Cmd+Y` on macOS) / or `Ctrl+Shift+Z` (or `Cmd+Shift+Z` on macOS) for redo are supported out of the box. Shortcuts work from document keydown events by default, even when the grid does not currently have a focused cell. Set `keyboardShortcutsWithoutFocus: false` to require grid focus. You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks. - Undo/Redo Management: Automatically tracks user changes with the ability to undo/redo actions using keyboard shortcuts. - Configurable Stack Size: Tracks up to 200 changes by default, and accepts partial `history` config when you only need to override one option. - Provider Persistence: Can load and save stack state through local cache and custom server providers for same-dataset sessions. - Custom Event Hooks: Supports custom behavior via beforeundo and beforeredo events, allowing full control over the undo/redo process. - Pro Editing Coverage: Tracks regular cell edits, Pro editor templates, textarea editors, range edits, clipboard paste, autofill, row-edit saves, and audit restore replays when paired with `EventManagerPlugin`. ### Additional details Use `EventManagerPlugin` together with `HistoryPlugin`. `HistoryPlugin` listens to `ON_EDIT_EVENT`, which is emitted by `EventManagerPlugin`. ```ts import { EventManagerPlugin, HistoryPlugin, createLocalStorageHistoryAdapter, } from '@revolist/revogrid-pro'; plugins = [EventManagerPlugin, HistoryPlugin]; ``` Undo, redo, and public replay operations emit `beforehistedit`. When `EventManagerPlugin` is installed, it handles that event and emits the normal `gridedit` flow. When it is not installed, History applies the replay directly with `setRangeData`. `AuditHistoryPlugin` automatically reuses or installs `HistoryPlugin` for restore replay, keyboard shortcuts, and existing History integrations. Keep History configuration on `grid.history`; Audit History does not define a nested History config. `grid.history` is optional. Add it only when you need custom stacks, a smaller or larger stack limit, or an initially disabled plugin. ```ts grid.history = { maxStackSize: 100, keyboardShortcutsWithoutFocus: false, }; ``` ### Persisting History Undo and redo stacks are index-based, so persisted history is safe only when the user returns to the same dataset and row order. For long-term recovery across changing data, use `AuditHistoryPlugin`, which restores by stable row identity. Use `sourceId` to reject stale server states. With multiple providers, the plugin loads the first provider immediately, then applies later providers only when their `updatedAt` state is newer and their `sourceId` is compatible. When storage is configured, `clearOnSourceChange` defaults to `false` so the initial data load does not erase the restored stack. Set it to `true` only when your app wants every source replacement to clear persisted undo/redo state. ```ts grid.history = { sourceId: `invoices:${workspaceId}:${datasetVersion}`, storageProviders: [ createLocalStorageHistoryAdapter('invoices:history'), { async load() { const response = await fetch('/api/grid-history'); return response.json(); }, async save(state) { await fetch('/api/grid-history', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(state), }); }, async clear() { await fetch('/api/grid-history', { method: 'DELETE' }); }, }, ], }; ``` Listen to `historyerror` when you want to surface provider failures or skipped states: ```ts grid.addEventListener('historyerror', (event) => { console.warn(event.detail.phase, event.detail.error); }); ``` To call `undo()`, `redo()`, `clear()`, `disable()`, or state helpers, get plugin instances from the grid and find the `HistoryPlugin` instance. ```ts async undo() { // get plugins from the grid const plugins = await this.gridRefNativeElement.getPlugins(); const history = plugins.find((plugin) => plugin instanceof HistoryPlugin); if (history) { if (history.canUndo()) { history.undo(); } } } ``` For custom controls, listen to `historychanged` instead of reading plugin internals. The event detail includes `undoStackSize`, `redoStackSize`, `canUndo`, `canRedo`, and `disabled`. ### Troubleshooting - Undo/redo does not record changes: ensure plugin order is `[EventManagerPlugin, HistoryPlugin]`. - Restore flashes are missing in Audit History: install `EventManagerPlugin` and `CellFlashPlugin`; Audit restore replay flows through `beforehistedit` and the normal edit event path. - Calling plugin methods does not work: call `getPlugins()` first, then find `HistoryPlugin`. - History clears after sort, filter, row-order, or source replacement: this is intentional because those operations can change visible row indexes. - Persisted history was skipped: check the `historyerror` event for a `storage-source-mismatch` phase and update the configured `sourceId`. - Need a known-good reference: see code in: `revogrid-pro/src/components/history/**.**`. ### Key methods The History Plugin provides four key methods to enhance control over `undo/redo` functionality: - `clear()`: Resets both the undo and redo stacks, clearing all recorded changes. - `disable(disable = true)`: Temporarily disables the plugin, preventing changes from being recorded or undo/redo operations from being triggered. - `undo()`: Reverts the last change recorded in the undo stack and moves it to the redo stack for potential reapplication. - `redo()`: Reapplies the most recent change from the redo stack, moving it back to the undo stack for further management. - `canUndo()` / `canRedo()`: Returns whether undo or redo is currently available. - `getUndoStackSize()` / `getRedoStackSize()`: Returns stack counts for custom controls. - `isDisabled()`: Returns whether history is disabled. These methods give developers flexibility in managing user actions and tailoring the plugin’s behavior to specific application requirements. ### Behind the Scenes The plugin listens to edit events (onEditEvent) and tracks changes in undo and redo stacks. Keyboard shortcuts like Ctrl+Z for undo and Ctrl+Y (or Ctrl+Shift+Z) for redo are supported out of the box. You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks. Here’s a quick snippet showing how the plugin processes undo actions: ```ts undo() { if (this.undoStack.length === 0) return; const lastChange = this.undoStack.pop(); if (lastChange) { const event = this.emit(BEFORE_UNDO_EVENT, { data: lastChange.previousData, type: lastChange.type, lastChange, }); if (!event.defaultPrevented) { const data = event.detail.data || lastChange.previousData; this.replayChange({ data, type: event.detail.type || lastChange.type, models: lastChange.models, previousData: lastChange.data, }); this.redoStack.push(lastChange); } } } ``` `replayChange()` emits `beforehistedit` first. If no integration handles that event, History falls back to `providers.data.setRangeData(...)`. Try it Out! Add the History Plugin to your RevoGrid instance and experience seamless undo/redo functionality. This plugin is especially useful in applications that handle complex data operations, ensuring users can easily revert or reapply changes as needed. --- # Multi-Range Selection URL: https://pro.rv-grid.com/guides/data-manage/multi-range-selection/ Source: src/content/docs/guides/data-manage/multi-range-selection.mdx Description: Select, clear, and copy multiple disjoint RevoGrid ranges. `MultiRangeSelectionPlugin` adds spreadsheet-style disjoint range selection while leaving the native RevoGrid active range in charge of focus, Shift extension, and editing. Use it when users need to compare, clear, or copy multiple non-contiguous regions from the same grid. Select a range, Ctrl/Cmd+click another cell, Shift+click to extend the new range, then copy. Plain-text clipboard output includes selected cells only, so paste does not clear gap cells between disjoint ranges. ## Usage ```ts import { MultiRangeSelectionPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.range = true; grid.plugins = [MultiRangeSelectionPlugin]; ``` Cross-viewport autofill is enabled by default when this plugin is installed. Users can drag the autofill handle between pinned and main row or column viewports, and the committed result is stored as multiple selected ranges. Disable only that drag integration with: ```ts grid.multiRangeSelection = { crossViewportAutofill: false }; ``` ## User Behavior 1. Select a cell or range normally. 2. Ctrl/Cmd+click another cell to keep the previous range and start a new active range. 3. Shift+click or Shift+Arrow extends only the active range. 4. Tab and Enter move the focused cell inside the active range. 5. Delete and Backspace clear inactive ranges plus the active range. 6. Copy includes inactive ranges plus the active range. 7. Dragging the autofill handle across pinned/main viewport boundaries fills the visible target segments. 8. Plain click clears inactive ranges and starts a normal selection. ## Clipboard Shape Multi-range copy preserves relative shape for nearby ranges in `text/plain`. For example, diagonal selected cells are copied with blank cells between them so external spreadsheets can keep the diagonal layout instead of stacking values one after another. RevoGrid also writes a custom multi-range clipboard payload; when the payload is pasted back into RevoGrid, only the selected cells are edited, so the blank gaps do not clear existing destination values. Very large sparse selections fall back to selected rectangular blocks to avoid huge clipboard payloads. Rich `text/html` output mirrors the selected clipboard shape. Pinned and regular viewport coordinates are normalized into whole-grid visual order before copy, so a pinned-left cell followed by the first regular cell is copied as adjacent clipboard columns. If only one range is selected, RevoGrid keeps its default copy behavior. ## Programmatic API Call the public methods on the plugin instance: ```ts const plugin = (await grid.getPlugins()) .find(plugin => plugin instanceof MultiRangeSelectionPlugin); plugin?.setSelectedRanges([ { rowType: 'rgRow', colType: 'rgCol', range: { x: 0, y: 0, x1: 1, y1: 1 } }, { rowType: 'rgRow', colType: 'rgCol', range: { x: 3, y: 0, x1: 3, y1: 1 } }, ]); const ranges = plugin?.getSelectedRanges(); plugin?.clearSelectedRanges(); ``` The grid also dispatches `multirangeselectionchange` whenever the selected ranges change. ## Rendering Inactive cells receive: - attribute: `multi-range-selection` - class: `multi-range-selection-cell` - edge classes for the range perimeter The active range remains the native RevoGrid selection overlay. --- # Nested Grid URL: https://pro.rv-grid.com/guides/data-manage/nested-grid/ Source: src/content/docs/guides/data-manage/nested-grid.mdx Description: Learn how to use the Nested Grid to display hierarchical data within RevoGrid cells. The Nested Grid allows you to display hierarchical data by embedding RevoGrids inside cells. This is particularly useful when you need to show detailed, structured data within a cell, such as project details, task lists, or any other nested data structure. ## Row Expansion The Nested Grid combined with the Row Expansion plugin to provide a more interactive experience. Row expansion allows users to expand a row to reveal additional details or nested grids. ### Enabling Row Expansion To enable row expansion, you can configure the grid as follows: ```typescript import { RowExpandPlugin } from '@revolist/revogrid-pro'; grid.plugins = [RowExpandPlugin]; ``` ### Example Configuration Here's an example of how to set up row expansion with nested grids: ```typescript grid.columns = [ { prop: 'id', name: 'ID', expand: true }, // Enable row expansion for this column { prop: 'name', name: 'Name' }, { prop: 'details', name: 'Details', } ]; // Additional configuration for row expansion grid.additionalData = { rowExpand: { expandedRows: new Set([1]), // Example of expanded rows expandedRowHeight: 200, // Height for the expanded row // Hide expand toggle based on row data hideExpand: (rowIndex, model) => { // Example: Hide expand toggle for rows without tasks return !model.tasks || model.tasks.length === 0; }, template: (h, props) => { return h('revo-grid', { key: 'nested-grid', class: 'mt-1', style: { height: '100%', maxHeight: '90%', maxWidth: '95%', }, theme: isDark() ? 'darkCompact' : 'compact', resize: true, readonly: true, columns: [ { prop: 'id', name: '#', size: 50 }, { prop: 'task', name: 'Task', size: 220 }, { prop: 'status', name: 'Status', size: 150, cellProperties: ({ value }) => ({ style: { color: '#fff', backgroundColor: value === 'Complete' ? '#00c874' : value === 'In Progress' ? '#fdaa3d' : '#e2435c', }, }), }, ], source: props.model.tasks, // Source data for the nested grid hideAttribution: true, }); }, }, }; ``` ## Configuration Options The nested grid configuration supports all standard RevoGrid properties. Here are some commonly used options: - `columns`: Array of column definitions for the nested grid - `source`: Function that returns the data array for the nested grid - `theme`: Theme to apply to nested grids - `readonly`: Whether the nested grid should be read-only - `rowSize`: Height of rows in the nested grid - `resize`: Enable column resizing in nested grid - Any other RevoGrid properties you want to apply to nested grids ### Row Expansion Options The row expansion configuration supports several options to control expansion behavior: - `expandedRows`: Set of row IDs that should be expanded by default - `expandedRowHeight`: Height for expanded rows (in pixels) - `hideExpand`: Controls visibility of expand toggle buttons, can be: - A boolean: `true` to hide all expand toggles - A function: `(rowIndex: number, model: any) => boolean` for conditional control Example of conditional expand toggle visibility: ```typescript grid.additionalData = { rowExpand: { // Hide expand toggle for rows that don't have nested data hideExpand: (rowIndex, model) => { return !model.hasNestedData || model.tasks?.length === 0; }, // ... other configuration options } }; ``` ## Best Practices 1. **Data Structure**: Organize your data hierarchically with clear parent-child relationships. 2. **Performance**: Consider the following for optimal performance: - Limit the number of nested grids visible at once - Keep nested data sets reasonably sized - Use read-only mode for nested grids if editing isn't required 3. **Responsive Design**: Ensure columns with nested grids have sufficient width. 4. **User Experience**: Use row expansion judiciously to enhance user interaction without overwhelming them with too much information at once. ## Advanced Usage ### Custom Cell Templates You can combine nested grids with custom cell templates for rich data visualization: ```typescript { prop: 'progress', name: 'Progress', cellTemplate: (h, props) => { const progress = props.model[props.prop] || 0; return h('div', { style: { width: '100%', height: '100%' } }, [ h('div', { style: { width: `${progress}%`, backgroundColor: progress === 100 ? '#4CAF50' : '#2196F3' } }) ]); } } ``` --- # Range Copy Preview URL: https://pro.rv-grid.com/guides/data-manage/range-copy-preview/ Source: src/content/docs/guides/data-manage/range-copy-preview.mdx Description: Shows ghost values for the exact range copy that will be applied on drop. `RangeCopyPreviewPlugin` shows the copied values that will be applied when a user drags a selected range with the fill handle. It is a view-only companion plugin: the data store is not changed until the existing drop-time range copy flow runs. Use it when users need confidence that a drag will repeat the selected source range exactly, without AutoFill sequence inference. Select the first two or three populated rows in the demo, drag the fill handle downward, and the empty target cells show ghost copied values before mouseup. ## Usage ```ts import { RangeCopyPreviewPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.range = true; grid.plugins = [RangeCopyPreviewPlugin]; ``` The plugin uses the same core drag signal as range copy. It does not need configuration for the default behavior. ## User Behavior 1. Select a source cell or range. 2. Drag the fill handle to a larger target range. 3. Target cells show the exact copied values in a ghost style. 4. Source cells remain visually unchanged. 5. Release the mouse to apply the core range copy. If the temporary range is cleared, the source changes, or the plugin is destroyed, the preview is removed immediately. ## Difference From AutoFill Preview `AutoFillPreviewPlugin` previews smart sequence output from `AutoFillPlugin`, such as `1, 2` becoming `3, 4` or `Mon, Tue` becoming `Wed`. `RangeCopyPreviewPlugin` previews exact range copy output: ```txt Draft, Approved -> Draft, Approved, Draft, Approved North, West -> North, West, North, West ``` Use one preview plugin at a time for this drag surface: | Plugin | Preview intent | | --- | --- | | `AutoFillPreviewPlugin` | Show predicted smart AutoFill sequence values. | | `RangeCopyPreviewPlugin` | Show exact copied source values. | ## Rendering Preview cells receive: - attribute: `range-copy-preview` - class: `range-copy-preview-cell` Default styling uses reduced opacity and italic text so users can distinguish preview values from committed data. ```css .range-copy-preview-cell { opacity: 0.45; font-style: italic; } ``` ## Notes The plugin does not mutate `source`, `newData`, or provider stores. It only overrides the render model during `beforecellrender`, then refreshes affected row types in a frame batch. --- # Range Selection Limit URL: https://pro.rv-grid.com/guides/data-manage/range-selection-limit/ Source: src/content/docs/guides/data-manage/range-selection-limit.mdx Description: Restrict interactive range selection to a single row or column. `RangeSelectionLimitPlugin` keeps user-created range selections in one dimension. Use it when a workflow accepts row-only or column-only bulk actions and should not allow rectangular selections. The plugin does not replace RevoGrid selection. It intercepts the pending range at the `beforeapplyrange` lifecycle point, constrains the shape, and then lets the normal `setrange` flow continue. ## Usage ```ts import { RangeSelectionLimitPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.range = true; grid.plugins = [RangeSelectionLimitPlugin]; grid.rangeSelectionLimit = { mode: 'column' }; ``` `grid.range = true` is still required because this plugin only constrains range selection after core range selection is enabled. ## Modes | Mode | Behavior | Typical use | | --- | --- | --- | | `column` | Keeps the focused column fixed and expands only across rows. | Select multiple rows for one field. | | `row` | Keeps the focused row fixed and expands only across columns. | Select multiple fields for one row. | ### Column Mode `column` keeps the focused column fixed and allows the selection to expand vertically: ```ts grid.rangeSelectionLimit = { mode: 'column' }; ``` If the user starts on column `status` and drags diagonally, the selected range stays inside `status` while the row bounds change. ### Row Mode `row` keeps the focused row fixed and allows the selection to expand horizontally: ```ts grid.rangeSelectionLimit = { mode: 'row' }; ``` If the user starts on row `12` and drags diagonally, the selected range stays on row `12` while the column bounds change. ## Configuration Forms Boolean shorthand is supported: ```ts grid.rangeSelectionLimit = true; // same as { mode: 'column' } grid.rangeSelectionLimit = false; // disabled ``` String shorthand is also supported: ```ts grid.rangeSelectionLimit = 'column'; grid.rangeSelectionLimit = 'row'; ``` The object form is preferred for new code because it leaves room for future options: ```ts grid.rangeSelectionLimit = { mode: 'column', }; ``` For legacy integrations, the plugin also reads `additionalData.rangeSelectionLimit`, but direct `grid.rangeSelectionLimit` configuration is preferred. ```ts grid.additionalData = { rangeSelectionLimit: { mode: 'row', }, }; ``` ## Lifecycle The plugin uses the existing RevoGrid range lifecycle: 1. RevoGrid computes the pending range from focus and drag/keyboard input. 2. `beforeapplyrange` is emitted by the overlay selection component. 3. `RangeSelectionLimitPlugin` replaces `event.detail.range` with the constrained range. 4. RevoGrid transforms that range to row/column data and emits `beforesetrange`. 5. RevoGrid emits `setrange`, and the selection store receives the constrained range. The plugin does not add render layers, change data, or override single-cell focus. If there is no focused cell, it uses the pending range start as the anchor. ## API ```ts export type RangeSelectionLimitMode = 'column' | 'row'; export type RangeSelectionLimitOptions = { mode: RangeSelectionLimitMode; }; export type RangeSelectionLimitConfig = | boolean | RangeSelectionLimitMode | RangeSelectionLimitOptions; ``` ## Future Shape Validation Arbitrary shape validation should be added as a separate mode or validator API so the existing `row` and `column` modes keep their current behavior. --- # Row Expand Plugin URL: https://pro.rv-grid.com/guides/data-manage/row-expand/ Source: src/content/docs/guides/data-manage/row-expand.mdx Description: Learn how to use RowExpandPlugin for inline expandable row details. The `RowExpandPlugin` adds inline expandable detail rows to RevoGrid. It is useful when a row needs a compact secondary area for a form, nested grid, notes, preview content, or any row-local custom template. Unlike the [Master Rows](/guides/data-manage/row-master/) feature, `RowExpandPlugin` does not render a master-detail overlay. It inserts an expanded row into the regular row viewport and renders your `rowExpand.template` inside that row. Use `RowExpandPlugin` for inline detail rows that should stay in the normal grid flow. Use [`MasterRowPlugin`](/guides/data-manage/row-master/) when the detail area must span the grid, stay aligned through overlay rendering, or work as a full master-detail view across pinned areas. ## Basic Setup Import the plugin and add it to the grid: ```ts import { RowExpandPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.plugins = [RowExpandPlugin]; ``` Add `expand: true` to the column that should render the expand toggle: ```ts grid.columns = [ { prop: 'name', name: 'Name', expand: true }, { prop: 'status', name: 'Status' }, ]; ``` Configure expanded row behavior with `grid.rowExpand`: ```ts grid.rowExpand = { expandedRows: new Set([0]), expandedRowHeight: 220, template: (h, props) => h('div', { class: 'row-detail' }, [ h('strong', null, props.model.name), h('p', null, props.model.description), ]), }; ``` ## Configuration | Option | Type | Description | | ------ | ---- | ----------- | | `expandedRows` | `Set` | Physical row indexes that should be expanded by default. | | `expandedRowHeight` | `number` | Height, in pixels, for each expanded detail row. | | `hideExpand` | `boolean \| ((rowIndex: number, model: any) => boolean)` | Hides the expand toggle for all rows or for specific rows. Return `true` to hide the toggle. | | `template` | `(h: any, props: any) => VNode` | Renders the expanded row content. The template receives the row model and render context. | `additionalData.rowExpand` is still supported for compatibility, but prefer the direct `grid.rowExpand` property. ## Programmatic Expansion The plugin listens for row expansion events: ```ts import { ROW_COLLAPSE, ROW_COLLAPSE_ALL, ROW_EXPAND, ROW_EXPAND_ALL, } from '@revolist/revogrid-pro'; grid.dispatchEvent(new CustomEvent(ROW_EXPAND, { detail: { rowIndex: 0 } })); grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE, { detail: { rowIndex: 0 } })); grid.dispatchEvent(new CustomEvent(ROW_EXPAND_ALL)); grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE_ALL)); ``` The `rowIndex` value is the virtual row index at the time the event is dispatched. ## Difference From Master Rows `RowExpandPlugin` and `MasterRowPlugin` both expose expandable row experiences, but their rendering models are different: | Capability | RowExpandPlugin | MasterRowPlugin | | ---------- | --------------- | --------------- | | Rendering model | Inline expanded row in the regular row viewport | Overlay-rendered master-detail row | | Required plugins | `RowExpandPlugin` | `MasterRowPlugin` (`OverlayPlugin` is auto-installed) | | Toggle setup | Set `expand: true` on a column | Use `EXPAND_COLUMN` or dispatch `ROW_MASTER` | | Best for | Nested grids, row forms, compact detail rows | Full-width master-detail content and pinned-area spanning layouts | | Layout scope | Constrained by regular grid row and columns | Can span across the grid through overlay rendering | For the overlay-based master-detail implementation, read the [Master Rows guide](/guides/data-manage/row-master/) or the [Row Master API](/api/row-master/). ## Example Use Cases - Inline edit forms, as shown in the [Form Edit example](/guides/form-edit/). - Nested grids inside expanded rows, as shown in the [Nested Grid guide](/guides/data-manage/nested-grid/). - Compact row detail panels that do not need to span pinned columns or the full grid. --- # Master Rows URL: https://pro.rv-grid.com/guides/data-manage/row-master/ Source: src/content/docs/guides/data-manage/row-master.mdx Description: A guide to using the Master Row Plugin. The **Master Row Plugin** enhances the [RevoGrid](https://rv-grid.com/) grid component by enabling the creation and management of master-detail rows. It is an extremely **complex and advanced feature**, despite its seemingly simple nature. Read more below. 1. Dynamic Overlay Synchronization: The Master Row Plugin creates a dynamic overlay that tracks grid scrolling. This ensures that master-detail information remains perfectly aligned and visible as you navigate through large datasets. 2. Expansive Master Detail Context: Unlike tree view, group view, or merge cell approaches, this plugin uniquely expands the master row context into the pinned area, allowing it to fill the entire grid. This capability provides a richer, more immersive data presentation and is exceptionally challenging to achieve. 3. Virtualized Performance: Despite the complexity of rendering an overlay that spans the grid, the layer remains virtualized. This ensures high performance and responsiveness, even when handling extensive and detailed master row content. ## Table of Contents - [Features](#features) - [Installation](#installation) - [Usage](#usage) - [Basic Setup](#basic-setup) - [Configuration](#configuration) - [Custom Templates](#custom-templates) - [Programmatic Expand and Collapse](#programmatic-expand-and-collapse) - [API Reference](#api-reference) --- ## Features - **Expandable Rows:** Easily expand rows to reveal additional details. - **Custom Templates:** Define how master rows are rendered using customizable templates. --- ## Basic Setup 1. **Import Necessary Plugins and Utilities:** ```javascript import { EXPAND_COLUMN, MasterRowPlugin, CellColumnFocusVerifyPlugin } from '@revolist/revogrid-pro'; import type { RowMasterConfig } from '@revolist/revogrid'; import './row-master.style.css'; ``` 2. **Define Columns:** ```javascript const columns: HTMLRevoGridElement['columns'] = [ { prop: 'expand', ...EXPAND_COLUMN, }, // ... ]; ``` 3. **Initialize the Grid with Plugins:** ```javascript const grid = document.querySelector('revo-grid'); grid.source = makeData(10, 20); grid.columns = columns; // Define plugin grid.plugins = [ MasterRowPlugin, ]; const masterRow: RowMasterConfig = { rowHeight: 100, template: (h, data) => { return data.model.subRows?.map((subRow: Person) => h('span', { class: { 'master-row': true } }, [ h('img', { src: subRow.avatar, width: '20', height: '20' }), `${subRow.firstName} ${subRow.lastName} `, ]), ); }, }; grid.masterRow = masterRow; } ``` ### Configuration The Master Row Plugin can be customized by assigning a `RowMasterConfig` object to `grid.masterRow`. The primary configuration option is the `template` function, which defines how the master row content is rendered. #### RowMasterConfig | Property | Type | Description | | -------- | ---- | ----------- | | `template` | `(h: Function, data: any) => JSX.Element` | A function that returns the JSX structure for the master row. It receives a rendering function `h` and the data context `data`. | | `allowDetailEvents` | `readonly string[]` | Event names from the master detail area that are allowed to bubble to the parent grid. All RevoGrid events inside the detail area are stopped by default. | ### Custom Templates Customize the master row by defining a `template` function that returns the desired JSX structure. This allows for flexible and dynamic rendering based on the row data. ```javascript const masterRow: RowMasterConfig = { template: (h, data) => { return data.model.subRows?.map((subRow: Person) => h('div', { class: { 'custom-master-row': true } }, [ h('img', { src: subRow.avatar, width: '30', height: '30' }), `${subRow.firstName} ${subRow.lastName}`, ]), ); }, }; ``` To let selected events from nested detail content reach the parent grid, add them by name: ```javascript const masterRow: RowMasterConfig = { allowDetailEvents: ['afteredit'], template: (h, data) => h('revo-grid', { source: data.model.details }), }; ``` ### Programmatic Expand and Collapse `EXPAND_COLUMN` toggles a single master row by dispatching the `ROW_MASTER` event with the current cell template payload. You can dispatch the same event directly when a toolbar, external control, or custom cell template needs to control master-row state. For one row, pass the same cell template payload shape used by cell templates. The plugin treats this as a toggle: ```ts import { ROW_MASTER } from '@revolist/revogrid-pro'; grid.dispatchEvent(new CustomEvent(ROW_MASTER, { detail: cellTemplateData, })); ``` For multiple rows, pass a batch payload with a `rows` array. Each row target declares its desired final state with `expanded: true` or `expanded: false`, so one event can open and close different rows at the same time: ```ts import { ROW_MASTER } from '@revolist/revogrid-pro'; import type { RowMasterBatchEvent } from '@revolist/revogrid-pro'; const detail: RowMasterBatchEvent = { rows: [ { rowType: 'rgRow', rowIndex: 1, expanded: true }, { rowType: 'rgRow', rowIndex: 3, expanded: false }, { rowType: 'rowPinStart', rowIndex: 0, expanded: true }, ], }; grid.dispatchEvent(new CustomEvent(ROW_MASTER, { detail })); ``` `rowIndex` is the current virtual row index for the given `rowType`. The plugin converts every target to a stable internal row reference before applying the batch, so one event can safely mix opened and closed rows without later changes shifting earlier targets. If multiple targets resolve to the same row, the last entry wins. ### Automatic Collapse Expanded master rows are cleared when the grid is about to change row visibility or row structure in a way that can invalidate the detail overlay position. The plugin calls its internal `clearAllRows()` cleanup when: - the grid source is replaced - filtering is about to trim rows through `beforefiltertrimmed` - row dragging starts During that cleanup, expanded master rows are collapsed, detail overlay nodes are removed, duplicated detail rows are removed from the row item lists, and custom detail row heights are cleared. This prevents a filtered or replaced row from leaving stale detail height behind after the row itself is no longer visible. To stop this automatic cleanup for one of these moments, listen for `BEFORE_ROW_MASTER_COLLAPSE` and call `preventDefault()`: ```ts import { BEFORE_ROW_MASTER_COLLAPSE } from '@revolist/revogrid-pro'; grid.addEventListener(BEFORE_ROW_MASTER_COLLAPSE, (event) => { if (event.detail.reason === 'filter') { event.preventDefault(); } }); ``` The event detail includes `reason` (`'source'`, `'filter'`, or `'drag'`) and `rows`, the expanded master rows that are about to be cleared. Preventing the default cleanup means your application is responsible for handling any stale detail overlays or custom row heights if the related source row is no longer visible. --- ## API Reference ### MasterRowPlugin A plugin class that manages master-detail rows within the RevoGrid component. #### Methods - **constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders)** Initializes the plugin with the RevoGrid instance and plugin providers. - **expandRow(type: DimensionRows, virtRowIndex: number)** Expands a specific row to show its master details. - **collapseRow(type: DimensionRows, rowVIndexes: number[])** Collapses one or more rows, hiding their master details. - **destroy()** Cleans up event listeners and other resources when the plugin is destroyed. #### Events The plugin listens to various RevoGrid events to manage the state of master rows, including: - `AFTER_GRID_RENDER_EVENT` - `BEFORE_ROW_RENDER_EVENT` - `BEFORE_ROW_MASTER_COLLAPSE` - cancelable event fired before automatic master-row cleanup collapses expanded rows. - `ROW_MASTER` - toggles one row from a cell template payload, or applies a batch of desired row states with `{ rows: [{ rowType, rowIndex, expanded }] }`. - `OVERLAY_NODE` - ...and more. --- # Tree Data Grid URL: https://pro.rv-grid.com/guides/data-manage/tree/ Source: src/content/docs/guides/data-manage/tree.mdx Description: Enhance your RevoGrid with hierarchical data visualization using the TreeData Plugin. The **TreeData Plugin** transforms parent-child relationships data into a hierarchical tree structure and provides the ability to group rows by `parentId` and is optimized for the best performance, making it suitable for large datasets. It enables features such as expandable rows, and level indicators, making it ideal for applications that handle nested data. ## Tree Plugin vs Key Grouping RevoGrid has two different ways to show hierarchical rows, and they solve different problems: - **Key grouping** uses the grid's built-in `grouping` configuration, for example `grid.grouping = { props: ['country', 'city'] }`. The grid reads one or more column values, creates synthetic group header rows for each matching key, and places source rows under those generated groups. Use it when the hierarchy should be calculated from repeated values such as category, region, status, or date. - **TreeDataPlugin** uses explicit row relationships from your data, normally `id` and `parentId`. The plugin keeps your original rows as the tree nodes, computes tree metadata such as level, expanded state, and visibility, and trims collapsed descendants from the viewport. Use it when the hierarchy already exists in the data, such as folders, tasks and subtasks, organization charts, bill of materials, or nested records. In short, key grouping answers "which rows share the same values?", while the tree plugin answers "which row is the parent of this row?". Key grouping is value-driven and creates group rows; tree data is relationship-driven and renders your existing rows as parent and child nodes. It is an extremely **complex and advanced feature**, despite its seemingly simple nature. 1. **Group Rows by Parent ID**: build hierarchical structures without modifying the original data, ensuring smooth performance even with large datasets. 2. **Complicated Drag-and-Drop**: The plugin supports **drag-and-drop between parent nodes** and automatically updates the `parentId` of the moved rows. 3. **Advanced Proxy System**: The plugin uses an advanced **proxy system** to handle sorting and filtering while keeping track of hierarchical relationships. This ensures that **all other plugins** remain unaffected by tree view manipulations, allowing for seamless integration. 4. **Metadata Computation**: The plugin computes tree metadata (e.g., level, expanded state, visibility) without modifying the underlying data. It uses a **recursive approach** to compute tree levels and visibility based on the parent-child relationships, and it leverages **row trimming** to optimize performance without altering the source data. 5. **Crucial for Other Plugins**: Beyond its core functionality, the Tree View plugin plays a **crucial role** in supporting other RevoGrid plugins. It facilitates **data transfer** between different components, ensuring compatibility with features like filtering, sorting, and row selection. - **Hierarchical Data Support**: Automatically organizes flat data into a tree structure based on customizable `id`, `parentId`, and `level` fields. - **Expandable Rows**: Expand and collapse rows dynamically to reveal or hide child rows. - **Customizable Templates**: Supports tree-specific cell templates for better customization. - **Root Parent Support**: Define custom root parent identifiers for flexibility in tree structure. ## Data Structure To build the hierarchical tree structure, each data item must include the following fields: - **`id`**: A unique identifier for each row. - **`parentId`**: Links the item to its parent row. Root-level rows should use the `rootParentId` value (default: `'root'`). Without these fields, the plugin cannot establish the necessary parent-child relationships for the tree structure. ```javascript const data = [ { id: '1', parentId: 'root', name: 'Parent 1' }, { id: '2', parentId: '1', name: 'Child 1.1' }, { id: '3', parentId: '1', name: 'Child 1.2' }, { id: '4', parentId: 'root', name: 'Parent 2' }, ]; ``` ## Additional Configuration Options The plugin supports the following options for customization: - idField: Defines the field representing unique row identifiers. Default: 'id'. - parentIdField: Specifies the field indicating parent row IDs. Default: 'parentId'. - levelField: Sets the field to store hierarchy levels. Default: 'level'. - rootParentId: Defines the identifier for root-level rows. Default: 'root'. - expandedRowIds: A Set of row IDs to predefine expanded rows. ### Example Configuration You can provide these options via revogrid.additionalData?.tree to customize the plugin’s behavior: ```javascript grid.additionalData = { tree: { idField: 'customId', parentIdField: 'customParentId', levelField: 'depth', rootParentId: null, expandedRowIds: new Set(['row1', 'row2']), // Pre-expanded rows }, }; ``` By setting these options, you can adapt the plugin to different data structures and define default states for the tree. ### Animated Collapse and Expand Tree rows can animate while they are trimmed during collapse and restored during expand. Enable `tree.animation`; TreeDataPlugin registers `DimensionAnimationPlugin` automatically when it is not already present. ```javascript import { TreeDataPlugin } from '@revolist/revogrid-pro'; grid.plugins = [TreeDataPlugin]; grid.tree = { animation: true, }; grid.dimensionAnimation = { duration: 180, }; ``` When `tree.animation` is not enabled, tree collapse and expand use the existing immediate trim behavior. --- # Checkbox Editor URL: https://pro.rv-grid.com/guides/editors/editor-checkbox/ Source: src/content/docs/guides/editors/editor-checkbox.mdx The `editorCheckbox` is a custom cell editor for RevoGrid that provides a checkbox input to edit boolean values directly within the grid cells. * **Features**: - Renders a checkbox input element for cells, allowing users to toggle boolean values (`true/false`) with a simple click. - Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value. - Ensures seamless integration with RevoGrid by providing row and column details in the event payload. - The demo combines checkbox editing with `RowOddPlugin` zebra striping and the `material`/`darkMaterial` themes. --- # Counter Editor URL: https://pro.rv-grid.com/guides/editors/editor-counter/ Source: src/content/docs/guides/editors/editor-counter.mdx Description: A plus/minus counter editor for numeric values The `editorCounter` is a custom cell editor for RevoGrid that provides plus and minus buttons to increment/decrement numeric values within a specified range directly within the grid cells. **Features**: - Renders plus and minus buttons for easy value adjustment - Supports customizable minimum and maximum values through column properties - Configurable step size for increments/decrements - Shows current value with optional display toggle - Automatically dispatches a `celledit` event upon change - Seamless integration with RevoGrid's data model - Modern, responsive design with customizable theming ## Usage Import the `editorCounter` and assign it to the `cellTemplate` property of a column in the RevoGrid: ```typescript import { editorCounter } from '@revolist/revogrid-pro' grid.columns = [ { prop: 'quantity', name: 'Quantity', cellTemplate: editorCounter, min: 0, // Minimum value (default: 0) max: 100, // Maximum value (default: 100) step: 5, // Step size for increments/decrements (default: 1) hideValue: false, // Set to true to hide the value display }, ]; ``` ## Configuration Options The counter editor supports several configuration options through column properties: - `min` (number): The minimum allowed value (default: 0) - `max` (number): The maximum allowed value (default: 100) - `step` (number): The increment/decrement step size (default: 1) - `hideValue` (boolean): Whether to hide the numeric value display (default: false) ## Event Handling The counter editor dispatches a `celledit` event whenever the value changes. The event detail contains: - `rgCol`: The column index of the edited cell - `rgRow`: The row index of the edited cell - `type`: The type of the cell - `prop`: The property of the cell being edited - `val`: The new numeric value after the change - `preventFocus`: A flag to control grid focus behavior (default: true) ## Best Practices 1. **Step Size**: Choose a step size that makes sense for your data. For example: - Use 1 for integer values like quantities - Use 0.1 or 0.01 for decimal values like prices - Use 5 or 10 for larger ranges 2. **Value Range**: Set appropriate min and max values to prevent invalid data: - For quantities: min = 0, max = maximum stock level - For percentages: min = 0, max = 100 - For ratings: min = 1, max = 5 (or your rating scale) 3. **Visual Feedback**: The editor provides visual feedback through: - Disabled buttons when at min/max values - Hover effects on buttons - Clear value display (unless hidden) 4. **Accessibility**: The editor supports keyboard interaction: - Tab navigation between buttons - Button states clearly indicated through styling - Numeric value visible by default --- # Dropdown Editor URL: https://pro.rv-grid.com/guides/editors/editor-dropdown/ Source: src/content/docs/guides/editors/editor-dropdown.mdx The `ColumnDropdown` column type renders the Pro dropdown editor inside a grid cell. Use it for single-select values, or enable `config.multiSelect` when the cell value is an array. Register `ColumnDropdown` in `columnTypes`, set `columnType: 'dropdown'` on a column, and provide a `dropdown.source` list with `{ value, label }` options. **Features**: - Supports single-select cells with scalar values - Supports multi-select cells with array values through `config.multiSelect: true` - Can enable search with `config.search: true` - Saves changes through RevoGrid cell edit events, so the grid data model stays in sync - Supports custom option and selected-value templates through the dropdown render callbacks --- # Row Editor URL: https://pro.rv-grid.com/guides/editors/editor-row/ Source: src/content/docs/guides/editors/editor-row.mdx The `RowEditPlugin` is a custom plugin for RevoGrid that provides row-level editing capabilities, allowing users to edit entire rows inline with dedicated action buttons for saving or canceling changes. * **Features**: - **Row Editing Mode:** Enables editing of entire rows by rendering editors for all cells in the selected row. - **Inline Editors:** Supports inline editing of multiple cells in a row simultaneously. - **Action Buttons:** Provides intuitive buttons for `Save` and `Cancel` actions: - `Save`: Dispatches `CELL_EDIT_SAVE_EVENT` to persist changes. - `Cancel`: Dispatches `CELL_EDIT_CANCEL_EVENT` to discard changes. - **Event Handling:** Listens for key events to manage row editing: - `CELL_EDIT_EVENT`: Initiates row editing mode. - `CELL_EDIT_SAVE_EVENT`: Saves edited data and updates the grid model. - `CELL_EDIT_CANCEL_EVENT`: Cancels editing and restores original data. - `BEFORE_CELL_RENDER_EVENT`: Ensures cells in the editing row render with editors. --- # Slider Editor URL: https://pro.rv-grid.com/guides/editors/editor-slider/ Source: src/content/docs/guides/editors/editor-slider.mdx The `editorSlider` is a custom cell editor for RevoGrid that provides a slider input to edit numeric values within a specified range directly within the grid cells. **Features**: - Renders a slider input element for cells, allowing users to select numeric values by dragging a slider handle - Supports customizable minimum and maximum values through column properties - Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value - Ensures seamless integration with RevoGrid by providing row and column details in the event payload --- # Textarea Editor URL: https://pro.rv-grid.com/guides/editors/editor-textarea/ Source: src/content/docs/guides/editors/editor-textarea.mdx This editor is a custom cell editor for RevoGrid that allows users to handle multi-line text input, making it suitable for applications requiring rich text editing capabilities within grid cells. To move to the next line within the `TextAreaEditor`, the user should press `Shift + Enter`. --- # Timeline Editor URL: https://pro.rv-grid.com/guides/editors/editor-timeline/ Source: src/content/docs/guides/editors/editor-timeline.mdx Description: A custom cell editor for managing date ranges with a visual timeline representation The `editorTimeline` is a custom cell editor for RevoGrid that provides a visual timeline interface for managing date ranges directly within grid cells. **Features**: - Visual timeline bar with progress indicator - Date range selection with native date pickers - Progress tracking based on current date - Responsive design that fits within grid cells --- # Customization URL: https://pro.rv-grid.com/guides/event-scheduler/customization/ Source: src/content/docs/guides/event-scheduler/customization/index.mdx Description: Customize scheduler labels, renderers, properties, styles, visual hooks, and states. Event Scheduler can be customized at several levels. Start with labels, formatters, CSS variables, and classes when you only need to change text or visual styling. Use `eventScheduler.customization` when a product needs RevoGrid-style render hooks for generated scheduler headers, cells, event sections, or create previews. The scheduler still owns projection, selection, drag/drop, resize, keyboard behavior, permission checks, conflicts, and mutation events. Custom render hooks should describe what a section looks like, while the scheduler continues to manage how that section behaves. ## Customization layers | Layer | Use it for | | --- | --- | | `labels` | Rename built-in UI text such as resource labels, editor fields, action labels, and validation messages. | | Formatters | Change displayed text without replacing markup, for example resource labels, time labels, day headers, hover labels, create-range labels, and tooltips. | | Direct properties hooks | Add classes, inline styles, attributes, data fields, CSS variables, status colors, or simple renderers to scheduler surfaces. | | `customization` | Replace or annotate scheduler headers, cells, event sub-sections, resize handles, badges, and create previews. | | Conflict and state hooks | Customize conflict badges/tooltips and empty/loading/error overlay states. | | CSS variables/classes | Theme the scheduler and target built-in state classes such as selected, conflict, locked, status, type, weekend, today, or non-working time. | ## Customization areas | Area | Use it for | | --- | --- | | [Labels and formatters](./labels-formatters/) | Text-only labels, date/time display, resource names, hover labels, and tooltips. | | [Event rendering](./events/) | Event button properties, content templates, badges, resize handles, tooltips, and status colors. | | [Layout, headers, and cells](./layout/) | Column sizes, generated headers, time rows, day cells, resource rows, and timeline cells. | | [Slots, hours, and markers](./slots-hours/) | Create previews, empty slots, closed hours, availability styling, and current-time markers. | | [Conflicts and states](./conflicts-states/) | Conflict indicators, warning/error treatment, remote loading, empty, and error overlays. | | [Context menus](./context-menu/) | Event, slot, day-header, and background commands. | | [Styling and themes](./styling/) | CSS variables, built-in state classes, and white-label themes. | ## Choosing the right hook - Use `labels` or formatters when the result is plain text. - Use CSS variables when the built-in structure is correct and the change is visual. - Use `eventProperties` or `statusColorResolver` when styling depends on event data. - Use `customization.*Properties` to add classes, styles, ARIA labels, or data attributes without replacing content. - Use `customization.*Template` only when the visible markup for that section needs to change. Template hooks replace only the section they target. Scheduler-owned interaction handlers, selection state, permission attributes, drag/resize behavior, generated data attributes, conflict classes, and core accessibility attributes remain managed by the plugin. For interaction callbacks, cancelable DOM events, selection, clipboard, keyboard shortcuts, and custom editor workflows, see [Editing and Interaction](../event-editing/). For compact copyable examples of each major customization surface, see [Examples](../examples/). That page includes basic weekly setup, event templates, day headers, slot context menus, current-time markers, closed hours, read-only mode, cancelable selection, and theme snippets. --- # Conflicts and States URL: https://pro.rv-grid.com/guides/event-scheduler/customization/conflicts-states/ Source: src/content/docs/guides/event-scheduler/customization/conflicts-states.mdx Description: Customize conflict warnings, empty overlays, loading overlays, and error states. ## Conflict display Conflict customization is separate from general event rendering so products can apply a consistent warning/error treatment without replacing event content. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', customization: { conflicts: { className: ({ severity }) => `booking-conflict booking-conflict--${severity}`, properties: ({ conflictTypes }) => ({ 'data-conflict-types': conflictTypes.join(' '), }), indicator: (h, { severity }) => h('span', { class: 'booking-conflict__badge' }, severity === 'error' ? 'Blocked' : 'Review'), tooltip: ({ label }) => `Planning issue: ${label}`, }, }, }; ``` The scheduler still adds built-in conflict classes, data attributes, and severity attributes to event blocks. Custom conflict hooks run only for conflicted segments. ## Empty, loading, and error states Remote mode and local empty states can use product-specific wording and markup through `labels`, `emptyStateText`, and `customization.emptyState`. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', emptyStateText: 'No resources match this view.', labels: { remoteLoading: 'Loading bookings...', remoteError: 'Bookings could not be loaded.', remoteEmpty: 'No bookings in this range.', }, customization: { emptyState: { template: ({ kind, label }) => kind === 'error' ? `Action needed: ${label}` : label, properties: ({ kind }) => ({ class: `booking-scheduler-state booking-scheduler-state--${kind}`, }), }, }, }; ``` The template can return a string or a DOM node. Property hooks can add classes, style variables, ARIA attributes, and data attributes to the overlay element. --- # Context Menus URL: https://pro.rv-grid.com/guides/event-scheduler/customization/context-menu/ Source: src/content/docs/guides/event-scheduler/customization/context-menu.mdx Description: Customize scheduler context-menu actions for events, slots, headers, and background surfaces. ## Context menu actions Use scheduler context-menu hooks when product actions depend on the clicked event, empty slot, resource, or scheduler surface. The default event and slot menus are generic conveniences; products can replace them, append shared actions, or keep built-ins and filter individual commands. | Config | Purpose | | --- | --- | | `getSlotContextMenuItems` | Replaces the empty-slot menu for the clicked slot. | | `getEventContextMenuItems` | Replaces the event-bar menu for the clicked event segment. | | `getDayHeaderContextMenuItems` | Defines optional day-header menu commands for products that expose header actions. | | `getBackgroundContextMenuItems` | Defines background/empty-scheduler-space commands. Slot backgrounds use this when no slot factory is supplied. | | `contextMenuTemplate` | Replaces the rendered content for scheduler-specific menu items. | | `onContextMenuAction` | Receives every scheduler-specific item selection with target context. | | `hiddenItems` / `disabledItems` | Hide or disable built-in scheduler actions by id. | | `items` / `itemFilter` | Compatibility hooks for appending raw Pro context-menu items or transforming built-ins. | Scheduler-specific menu items support `id`, `label`, `icon`, `shortcut`, `disabled`, `hidden`, `danger`, `separator`, `group`, and `action`. Use `danger` for destructive actions, `separator` for visual breaks, and `disabled` / `hidden` functions when the command depends on event or slot state. Menu item visibility and disabled state are resolved when the menu opens. If visibility depends on remote permissions or capacity checks, keep that state in application data and return it synchronously from `hidden` or `disabled`. Time-row context menus are optional; products that need them can use the underlying Pro row context-menu configuration until a dedicated scheduler time-row trigger is required. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', contextMenu: { enabled: true, hiddenItems: { confirm: true }, getEventContextMenuItems: ({ segment }) => [ { id: 'open-record', label: 'Open record', icon: 'fa-solid fa-arrow-up-right-from-square', shortcut: 'O', action: () => openPlanningRecord(segment?.event.id), }, { id: 'event-edit-separator', separator: true }, { id: 'duplicate-event', label: 'Duplicate', shortcut: 'Cmd+D', disabled: ({ segment }) => segment?.event.locked === true, action: ({ segment }) => duplicateBooking(segment?.event.id), }, { id: 'delete-event', label: 'Delete', danger: true, disabled: ({ segment }) => segment?.event.readonly === true, action: ({ segment }) => deleteBooking(segment?.event.id), }, ], getSlotContextMenuItems: ({ slot }) => [ { id: 'create-booking', label: 'Create booking here', icon: 'fa-solid fa-plus', shortcut: 'C', action: () => createBookingAt(slot?.startDateTime), }, { id: 'block-slot', label: 'Block this time', group: 'availability', action: ({ slot }) => createHoldAt(slot?.startDateTime), }, ], getBackgroundContextMenuItems: () => [ { id: 'refresh-availability', label: 'Refresh availability' }, ], contextMenuTemplate: (h, item) => item.separator ? null : h('span', { class: 'planner-menu-item' }, [ item.icon ? h('span', { class: item.icon }) : null, h('span', null, item.label), item.shortcut ? h('kbd', null, item.shortcut) : null, ]), onContextMenuAction: ({ itemId, target, segment, slot }) => { trackSchedulerMenuAction({ itemId, target, eventId: segment?.event.id, slot }); }, }, }; ``` Built-in context-menu actions continue to respect scheduler permissions, locked resources, read-only mode, disabled/non-selectable slots, and conflict rules. The default built-in labels can be changed through `labels`, and all built-ins can be removed with `hiddenItems`, transformed with `itemFilter`, or replaced entirely with target-specific factories. Built-in menu labels are generic by default: `createEventHereAction`, `assignOpenEventAction`, `editEventAction`, `confirmEventAction`, and `markPlannedAction`. Integrations should use these generic event labels or replace the menus with target-specific factories. --- # Event Rendering URL: https://pro.rv-grid.com/guides/event-scheduler/customization/events/ Source: src/content/docs/guides/event-scheduler/customization/events.mdx Description: Customize event colors, properties, content, badges, resize handles, tooltips, and accessible labels. ## Event branding Event blocks support a lightweight styling layer before you use the grouped `customization.events` API. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', statusColorResolver: (event) => { if (event.status === 'confirmed') return '#2563eb'; if (event.status === 'planned') return '#d97706'; if (event.status === 'blocked') return '#dc2626'; }, eventProperties: ({ event }) => ({ class: `scheduler-event--${event.type ?? 'default'}`, style: { '--shift-accent': event.status === 'blocked' ? '#dc2626' : '#2563eb', }, 'data-shift-status': event.status, }), eventTemplate: (h, { event }) => h('span', { class: 'shift-event-title' }, event.title), }; ``` `statusColorResolver` resolves the scheduler event accent color when the event does not already provide `event.color` and the status does not map to a built-in status class. `eventProperties` is merged onto the event button and can return `class`, `style`, attributes, data fields, or event handlers. `eventTemplate` replaces the default event content only; it does not replace the outer interactive event shell. ## Event section templates Use direct event hooks when a product needs a simple public API for event bars in week and resource-timeline views. | Config | Purpose | | --- | --- | | `eventTemplate` | Legacy top-level event content renderer. | | `eventContentTemplate` | Replaces the default title, time, badge, and resource content block. | | `eventProperties` | Adds classes, styles, attributes, data fields, or event handlers to the event button. | | `eventTooltipTemplate` | Overrides the event `title` tooltip with custom details. | | `eventBadgeTemplate` | Replaces individual status, type, category, or required-role badges. | | `eventResizeHandleTemplate` | Replaces the visual content inside start/end resize handles. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', eventProperties: (context) => ({ class: [ 'event-card', context.isSelected ? 'event-card--selected' : '', context.isLocked ? 'event-card--locked' : '', context.hasConflict ? 'event-card--conflict' : '', context.isShortEvent ? 'event-card--short' : '', ].filter(Boolean).join(' '), style: { '--event-accent': context.hasConflict ? '#dc2626' : context.event.color ?? '#2563eb', }, 'data-event-duration': context.duration, }), eventContentTemplate: (h, { event, resource, start, end, isFromPreviousDay, isContinuedToNextDay }) => h('span', { class: 'event-card__content' }, [ h('strong', { class: 'event-card__title' }, event.title), h('span', { class: 'event-card__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`), resource ? h('span', { class: 'event-card__resource' }, resource.name) : null, isFromPreviousDay ? h('span', { class: 'event-card__continuation' }, 'From previous day') : null, isContinuedToNextDay ? h('span', { class: 'event-card__continuation' }, 'Continues') : null, ]), eventBadgeTemplate: (h, { kind, label, isLocked }) => h('span', { class: `event-card__badge event-card__badge--${kind}` }, isLocked ? 'Locked' : label), eventResizeHandleTemplate: (h, { edge }) => h('span', { class: `event-card__resize event-card__resize--${edge}` }), eventTooltipTemplate: ({ event, resource, duration, hasConflict }) => `${event.title} | ${resource?.name ?? 'Unassigned'} | ${duration} minutes${hasConflict ? ' | Conflict' : ''}`, }; ``` Event render contexts include `event`, `resource`, `segment`, `start`, `end`, `day`, `duration`, `status`, `type`, `isSelected`, `isDragging`, `isResizing`, `isLocked`, `hasConflict`, `isReadonly`, `isFromPreviousDay`, `isContinuedToNextDay`, and `isShortEvent`. Dragging and resizing state are currently reserved and remain `false` until the scheduler exposes public pointer-state rendering. `customization.events` provides the grouped form of the same customization surface and layers on top of direct event properties where both are supplied. It customizes the event button and its internal sections. It applies to both week and resource timeline render modes. | Hook | What it customizes | | --- | --- | | `className` | Adds one or more classes to the event button. | | `style` | Adds inline styles or CSS custom properties to the event button. | | `properties` | Adds attributes, classes, styles, or data attributes to the event button. | | `content` | Replaces the default title, time, badge, and resource content block. | | `continuationLabels` | Replaces previous/next-day continuation labels. | | `badgeTray` | Replaces the tray that holds badges for multi-slot events. | | `badge` | Replaces individual status, type, category, or required-role badges. | | `resizeHandle` | Replaces the visual content inside start/end resize handles. | | `tooltip` | Overrides the event `title` tooltip. | | `ariaLabel` | Overrides the event button accessible label. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', customization: { events: { className: ({ event }) => `event-card event-card--${event.status ?? 'default'}`, style: ({ event }) => ({ '--event-border-color': event.status === 'blocked' ? '#dc2626' : '#2563eb', }), properties: ({ event, segment }) => ({ 'data-event-id': event.id, 'data-event-start-slot': segment.startSlot, }), content: (h, { event, resource, segment }) => h('span', { class: 'event-card__content' }, [ h('strong', { class: 'event-card__title' }, event.title), h('span', { class: 'event-card__resource' }, resource?.name ?? 'Unassigned'), segment.conflict ? h('span', { class: 'event-card__warning' }, 'Conflict') : null, ]), badge: (h, { kind, label }) => h('span', { class: `event-card__badge event-card__badge--${kind}` }, label), tooltip: ({ event, resource }) => `${event.title} - ${resource?.name ?? 'Unassigned'}`, ariaLabel: ({ event }) => `Scheduled event ${event.title}`, }, }, }; ``` The outer event element remains a scheduler-managed button. It keeps click, double-click, pointer, selection, disabled move/resize state, conflict state, and accessibility wiring. Custom event content should avoid interactive controls inside the event button unless the product has tested the keyboard and pointer behavior carefully. ## Rendering content on the bottom segment Multi-slot events (events that span more than one time slot) are rendered as a stack of slice elements. The `content` hook fires only on the **first** slice. Content placed there can grow downward through the event using `height: 100%`, but it is clipped at the bottom of the first row. To render content pinned to the **bottom of the last slice** — such as a status badge, category chip, or summary — use `badgeTray`. It fires on the final slice of every multi-slot event and its return value is placed directly inside the event button. Include the `event-scheduler-event__badge-tray` wrapper class in the returned element to get the built-in absolute bottom positioning: ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', customization: { events: { content: (h, context) => h('span', { class: 'my-event' }, [ h('strong', { class: 'my-event__title' }, context.event.title), h('span', { class: 'my-event__time' }, `${context.start.slice(11, 16)}-${context.end.slice(11, 16)}`), // For single-slot events first === last, so this renders inline context.last ? h('span', { class: 'my-event__badge' }, String(context.event.status ?? '')) : null, ]), badgeTray: (h, context) => { // Fires on the last slice of multi-slot events only. // Wrapping with event-scheduler-event__badge-tray pins it to the bottom. const status = String(context.event.status ?? ''); if (!status) return null; return h('span', { class: 'event-scheduler-event__badge-tray' }, [ h('span', { class: 'my-event__badge' }, status), ]); }, }, }, }; ``` The pattern above handles both cases consistently: - **Single-slot event** (`first === last === true`): the `content` hook renders the badge inline via the `context.last` guard. - **Multi-slot event**: `content` renders on the first slice without the badge; `badgeTray` renders it pinned to the bottom of the last slice. `badgeTray` does not fire for single-slot events or for the last slice of an event that continues into the next day (`isContinuedToNextDay`). When `badgeTray` returns `null` or `undefined`, the default badge tray — which displays type, category, and required-role chips — is rendered instead. --- # Labels and Formatters URL: https://pro.rv-grid.com/guides/event-scheduler/customization/labels-formatters/ Source: src/content/docs/guides/event-scheduler/customization/labels-formatters.mdx Description: Customize scheduler text, dates, resource labels, hover labels, and tooltips. ## Labels and formatters Use labels and formatters for text-level customization. This keeps the built-in DOM, accessibility attributes, selection, tooltips, and editing behavior intact. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', locale: 'en-US', timeZone: 'America/New_York', labels: { resourceColumn: 'Crew', unassignedResource: 'Open demand', newEvent: 'New assignment', createEvent: 'Create shift', editEvent: 'Edit shift', }, dayHeaderFormatter: (date) => date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'America/New_York', }), timeLabelFormatter: (minutes) => `${Math.floor(minutes / 60)}:00`, timelineHeaderFormatter: ({ startDateTime, endDateTime }) => `${startDateTime.slice(11, 16)}-${endDateTime.slice(11, 16)}`, hoverTimeFormatter: (minutes) => `Create at ${String(Math.floor(minutes / 60)).padStart(2, '0')}:00`, resourceLabelFormatter: (resource) => resource.name, resourceMetaFormatter: (resource) => [resource.role, resource.metadata?.location].filter(Boolean).join(' - '), tooltipFormatter: ({ event, resource }) => `${event.title}\n${resource?.name ?? 'Unassigned'}\n${event.startDateTime}`, }; ``` Built-in day, range, and timeline date labels use `locale` and `timeZone`. Formatter hooks take precedence when you need product-specific date or time strings, including date-only labels that should not shift when displayed in a non-UTC timezone. Formatters are the best first choice when the content is still a string. Move to templates only when you need nested markup, custom badges, custom attributes, or section-specific replacement. --- # Layout, Headers, and Cells URL: https://pro.rv-grid.com/guides/event-scheduler/customization/layout/ Source: src/content/docs/guides/event-scheduler/customization/layout.mdx Description: Customize scheduler column sizing, generated headers, time rows, day cells, resource rows, and timeline cells. ## Grouped customization API Use the grouped `customization` API when you need precise control over generated scheduler surfaces. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', customization: { columnSizes: { week: { timeColumnSize: 96, dayColumnSize: 220, }, resourceTimeline: { resourceColumnSize: 260, timelineColumnSize: 144, }, }, headers: { dayTemplate: (h, context) => h('span', { class: 'scheduler-day-header' }, context.defaultLabel), dayProperties: (context) => ({ class: context.today ? 'scheduler-day-header--brand-today' : '', 'data-scheduler-date': context.date, }), groupTemplate: (h, context) => h('strong', { class: 'scheduler-range-header' }, context.defaultLabel), }, cells: { resourceProperties: ({ resource }) => ({ 'data-resource-role': resource?.role, }), timelineProperties: ({ date, slotIndex }) => ({ class: 'scheduler-timeline-slot', 'data-scheduler-slot-key': `${date}:${slotIndex}`, }), }, events: { properties: ({ event }) => ({ 'data-event-status': event.status, }), content: (h, { event, resource }) => h('span', { class: 'scheduler-event-title' }, [ h('strong', {}, event.title), h('small', {}, resource?.name ?? 'Unassigned'), ]), badge: (h, context) => h('span', { class: `scheduler-event-chip scheduler-event-chip--${context.kind}`, }, context.label), resizeHandle: (h, context) => h('span', { class: `scheduler-event-resize scheduler-event-resize--${context.edge}`, }), }, createPreview: { createRangeTemplate: (context) => `${context.title}: ${context.startLabel}-${context.endLabel}`, createRangeProperties: () => ({ class: 'scheduler-create-preview' }), }, conflicts: { className: ({ severity }) => `scheduler-conflict scheduler-conflict--${severity}`, indicator: (h, context) => h('span', { class: 'scheduler-conflict-badge' }, context.severity), tooltip: ({ label }) => `Review before saving: ${label}`, }, emptyState: { template: ({ kind, label }) => `${kind}: ${label}`, properties: ({ kind }) => ({ class: `scheduler-state scheduler-state--${kind}`, }), }, }, }; ``` Template hooks receive RevoGrid's `h` helper and a scheduler context object. Return a string, a VNode, `null`, or `undefined`. Returning `null` or `undefined` keeps the built-in section. Property hooks return attributes, `class`, `style`, or data attributes for the target element. Custom properties are merged with built-in scheduler properties. Built-in interaction handlers and scheduler data attributes remain in place, so custom hooks should not attach drag, resize, selection, or mutation behavior directly. ## Column sizes `customization.columnSizes` controls scheduler-owned column widths by view. These values are resolved before legacy top-level size props such as `timeColumnSize`, `dayColumnSize`, `resourceColumnSize`, and `timelineColumnSize`. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', customization: { columnSizes: { week: { timeColumnSize: 88, dayColumnSize: 180, }, resourceTimeline: { resourceColumnSize: 280, timelineColumnSize: 120, }, }, }, }; ``` Use week sizes for `day`, `week`, and `month` style calendar grids. Use resource timeline sizes when the first column is the pinned resource column and the remaining columns are timeline slots. ## Header templates and properties Header customization targets the generated scheduler columns. For day headers, use the direct week-scheduler hooks when you only need day-header customization. Use `customization.headers` when you need to customize every generated header type. Direct day-header hooks: | Hook | Surface | | --- | --- | | `dayHeaderFormatter` | Formats the built-in day header label. | | `dayHeaderTemplate` | Replaces the day header content. | | `dayHeaderProperties` | Adds classes, styles, attributes, data fields, or event handlers to day headers. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', dayHeaderFormatter: (date) => date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC', }), dayHeaderTemplate: (h, context) => h('span', { class: 'planner-day-header' }, [ h('span', { class: 'planner-day-header__label' }, context.defaultLabel), context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null, context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null, h('span', { class: 'planner-day-header__count' }, `${context.eventCount ?? 0} events`), ]), dayHeaderProperties: (context) => ({ class: [ context.today ? 'planner-day-header--today' : '', context.weekend ? 'planner-day-header--weekend' : '', context.holiday ? 'planner-day-header--holiday' : '', ].filter(Boolean).join(' '), style: { '--planner-day-event-count': context.eventCount ?? 0, ...(context.holiday ? { '--planner-day-state': 'holiday' } : {}), }, 'data-planner-day': context.date, }), }; ``` Day header context includes `defaultLabel`, `date`, `dayIndex`, `eventCount`, `today`, `weekend`, `holiday`, and `holidayLabel`. Holiday state is date-level: it comes from the primary scheduler calendar or global `kind: 'holiday'` availability entries. Resource-specific holidays stay on the scheduler body cells. `customization.headers.dayTemplate` takes precedence over `dayHeaderTemplate` when both are provided. This lets applications keep older grouped customization config while adding direct day-header hooks where convenient. Grouped header customization has one template hook and one matching properties hook per generated header section. | Hook | Surface | | --- | --- | | `timeTemplate` / `timeProperties` | Pinned time header in day/week/month style views. | | `resourceTemplate` / `resourceProperties` | Pinned resource header in resource timeline views. | | `dayTemplate` / `dayProperties` | Day header cells. | | `timelineTemplate` / `timelineProperties` | Resource timeline slot headers. | | `groupTemplate` / `groupProperties` | Grouped range/day/timeline headers when column grouping is enabled. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', customization: { headers: { dayTemplate: (h, context) => h('span', { class: 'brand-day-header' }, [ h('strong', {}, context.defaultLabel), context.today ? h('small', {}, 'Today') : null, context.holiday ? h('small', {}, context.holidayLabel) : null, context.eventCount ? h('small', {}, `${context.eventCount} events`) : null, ]), dayProperties: (context) => ({ class: [ context.today ? 'brand-day-header--today' : '', context.weekend ? 'brand-day-header--weekend' : '', context.holiday ? 'brand-day-header--holiday' : '', ], 'data-brand-date': context.date, }), }, }, }; ``` Header contexts include the active view, the default label, date or slot fields where relevant, event counts, today/weekend/holiday flags, holiday labels, and group kind for grouped headers. ## Cell templates and properties Cell customization targets the scheduler body cells. Use property hooks when you only need classes, styles, or test/data attributes. Use templates when the default cell body should be replaced. Use the direct time-axis hooks when the product only needs to control the pinned time column in day, week, or month-style views. | Config | Purpose | | --- | --- | | `timeLabelFormatter` | Formats built-in time text such as `06:00`, `6 AM`, or `6:00`. | | `timeLabelTemplate` | Replaces the pinned time-label cell content. | | `timeRowProperties` | Adds classes, styles, attributes, data fields, or event handlers to time-label cells. | | `timeRange` | Sets the visible start/end time and hides time outside that range. | | `slotMinutes` | Sets row granularity, for example `5`, `10`, `15`, `30`, or `60`. | | `rowSize` | Controls row density in pixels. | | `nonWorkingTime` | Styles closed or outside-business-hours cells in the scheduler body. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', timeRange: { start: '06:00', end: '22:00' }, slotMinutes: 30, rowSize: 40, timeLabelFormatter: (minutes) => { const hour = Math.floor(minutes / 60); const minute = minutes % 60; return `${hour % 12 || 12}:${String(minute).padStart(2, '0')} ${hour < 12 ? 'AM' : 'PM'}`; }, timeLabelTemplate: (h, context) => h('span', { class: 'planner-time-label' }, context.timeLabel), timeRowProperties: (context) => ({ class: context.currentTimeLabel ? 'planner-time-row--current' : 'planner-time-row', style: { '--planner-time-start': context.startMinutes, }, 'data-planner-time': context.timeLabel, }), nonWorkingTime: { enabled: true, workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '18:00' }, className: 'planner-closed-hours', }, }; ``` `customization.cells.timeTemplate` and `customization.cells.timeProperties` layer over the direct `timeLabelTemplate` and `timeRowProperties` hooks when both are provided. Use `rowSize` for compact, dense, or comfortable density presets, and use `timeRange` when night hours should be hidden from the grid instead of only styled as closed. | Hook | Surface | | --- | --- | | `timeTemplate` / `timeProperties` | Time-label rows in week-style grids. | | `dayTemplate` / `dayProperties` | Day/time slots in week-style grids. | | `resourceTemplate` / `resourceProperties` | Resource rows in resource timeline grids. | | `resourceGroupTemplate` / `resourceGroupProperties` | Group rows when resource grouping is enabled. | | `timelineTemplate` / `timelineProperties` | Timeline cells in resource timeline grids. | ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', customization: { cells: { resourceProperties: ({ resource, unassigned, depth }) => ({ class: [ resource?.role ? `resource-row--${resource.role.toLowerCase()}` : '', unassigned ? 'resource-row--open-demand' : '', ], style: { '--resource-depth': depth }, 'data-resource-role': resource?.role, }), timelineProperties: ({ date, slotIndex, segmentCount }) => ({ class: segmentCount > 0 ? 'timeline-slot--busy' : 'timeline-slot--open', 'data-slot-key': `${date}:${slotIndex}`, }), }, }, }; ``` When a cell template is provided, the scheduler renders that template instead of the default cell content for that slot. For day cells, replacing the template also replaces the built-in visible event slices inside that cell, so prefer `dayProperties` when the goal is only styling or annotation. --- # Slots, Hours, and Markers URL: https://pro.rv-grid.com/guides/event-scheduler/customization/slots-hours/ Source: src/content/docs/guides/event-scheduler/customization/slots-hours.mdx Description: Customize create previews, empty slots, closed hours, availability states, and current-time markers. ## Create previews Create preview customization affects empty-slot hover labels and the drag-create range preview. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', editable: true, allowCreate: true, customization: { createPreview: { hoverTimeProperties: ({ startDateTime }) => ({ class: 'create-hover-slot', 'data-hover-start': startDateTime, }), createRangeTemplate: ({ title, startLabel, endLabel, durationLabel, resource }) => `${title} for ${resource?.name ?? 'Unassigned'}: ${startLabel}-${endLabel} (${durationLabel})`, createRangeProperties: ({ durationMinutes }) => ({ class: durationMinutes >= 240 ? 'create-range--long' : 'create-range--short', }), }, }, }; ``` Use `hoverTimeProperties` for attributes and styling on the slot while the pointer is over a creatable time. Use `createRangeTemplate` and `createRangeProperties` for the range ghost shown during drag-create interactions. ## Empty slot customization Empty slots are a primary extension point for booking, staffing, room planning, maintenance windows, pricing, capacity, and availability workflows. Use direct slot hooks when a product needs custom empty-cell content or behavior without replacing event bars. | Hook | Purpose | | --- | --- | | `slotTemplate` | Renders custom content inside empty week or resource-timeline slots. Occupied slots keep their event bars. | | `slotProperties` | Adds classes, styles, attributes, data fields, or event handlers to scheduler slot cells. | | `slotTooltip` | Overrides the native tooltip for disabled, closed, priced, or capacity-based slots. | | `onSlotClick` | Runs custom click behavior before built-in editor-open behavior. | | `onSlotDoubleClick` | Runs custom double-click behavior for immediate create or custom workflows. | | `onSlotContextMenu` | Receives the resolved slot when the scheduler context menu opens. | | `isSlotDisabled` | Marks slots as disabled and blocks built-in create/editor behavior. | | `isSlotSelectable` | Marks slots as non-selectable for create/selection UX. | ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', editable: true, allowCreate: true, slotTemplate: (h, context) => h('span', { class: 'booking-slot-capacity' }, [ context.disabled ? 'Closed' : `${getCapacity(context)} seats`, ]), slotProperties: (context) => ({ class: [ context.disabled ? 'booking-slot--closed' : '', !context.selectable ? 'booking-slot--invalid' : '', context.segmentCount > 0 ? 'booking-slot--busy' : 'booking-slot--open', ].filter(Boolean).join(' '), style: { '--booking-slot-capacity': getCapacity(context), }, 'data-booking-slot': context.startDateTime, }), slotTooltip: (context) => context.disabled ? 'Outside working hours' : `Available from ${context.startDateTime}`, isSlotDisabled: (context) => isClosedForBusiness(context), isSlotSelectable: (context) => !hasBlockedAvailability(context), onSlotClick: (context, event) => { if (context.disabled) { event.preventDefault(); showClosedSlotPopover(context); } }, onSlotDoubleClick: (context) => { if (!context.disabled) createBookingImmediately(context); }, onSlotContextMenu: (context) => { trackSlotMenuOpened(context); }, contextMenu: { enabled: true, hiddenItems: { create: true, 'assign-open': true, }, items: ({ target, slot }) => target === 'slot' && slot ? [ { name: 'Create booking', action: () => createBookingImmediately(slot) }, { name: 'Block this time', action: () => blockSlot(slot) }, ] : [], }, }; ``` `slotTemplate` is intentionally limited to empty cells so products can customize available space without removing visible event bars. Use `customization.cells.dayTemplate` or `customization.cells.timelineTemplate` only when the whole scheduler body cell, including event rendering, should be replaced. Use `isSlotDisabled` and `isSlotSelectable` with `allowCreate` or conflict rules when closed hours should block create, drag, paste, and editor flows. ## Working and closed hours Use working and closed-hour APIs when availability is part of the scheduler contract: business hours, weekends, holidays, lunch breaks, maintenance windows, or capacity holds. These hooks build on the existing `nonWorkingTime`, calendars, availability data, and conflict rules. | Config | Purpose | | --- | --- | | `workingHours` | Defines global or per-weekday working ranges. Use `false` in `byDay` to close a weekday. | | `closedHours` | Defines closed ranges such as lunch, maintenance, holidays, or blocked planning windows. | | `availabilityRules` | Applies product-specific availability rules from slot context. | | `closedSlotTemplate` | Renders custom content inside closed slots. | | `closedSlotProperties` | Adds classes, styles, attributes, data fields, or event handlers to closed slots. | | `closedSlotTooltip` | Overrides the native tooltip/title for closed slots. | | `isTimeSlotAvailable` | Blocks built-in create/editor behavior when it returns `false`. | Use `workingHours` for global business hours, `workingHours.byDay` for weekday-specific hours or closed weekends, `closedHours` for explicit closed ranges such as lunch or maintenance, and date-specific `closedHours` entries for holidays. Closed slots can be styled with `closedSlotProperties`, replaced with `closedSlotTemplate`, and explained through `closedSlotTooltip`. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', editable: true, allowCreate: true, workingHours: { byDay: { 1: { start: '08:00', end: '18:00' }, 2: { start: '08:00', end: '18:00' }, 3: { start: '08:00', end: '18:00' }, 4: { start: '08:00', end: '18:00' }, 5: { start: '08:00', end: '16:00' }, 0: false, 6: false, }, }, closedHours: [ { id: 'lunch', days: [1, 2, 3, 4, 5], ranges: { start: '12:00', end: '13:00' }, title: 'Closed', reason: 'Lunch break', className: 'planner-slot--lunch', }, { id: 'maintenance', dates: ['2026-06-10'], ranges: { start: '15:00', end: '17:00' }, reason: 'Maintenance window', className: 'planner-slot--maintenance', }, ], availabilityRules: ({ date, startMinutes }) => date === '2026-06-12' && startMinutes >= 14 * 60 ? { available: false, reason: 'Capacity hold', className: 'planner-slot--held' } : undefined, isTimeSlotAvailable: ({ date, slotIndex }) => !isCompanyHoliday(date) && slotIndex !== 0, closedSlotProperties: ({ availabilityKind, reason }) => ({ class: `planner-slot--closed planner-slot--${availabilityKind}`, style: { '--planner-closed-opacity': reason === 'Lunch break' ? 0.6 : 0.85, }, }), closedSlotTooltip: ({ title, reason }) => [title, reason].filter(Boolean).join(' - '), closedSlotTemplate: (h, { reason }) => h('span', { class: 'planner-slot__closed-label' }, reason ?? 'Closed'), }; ``` Closed-slot context extends normal slot context with `available`, `availabilityKind`, `availabilityId`, `title`, and `reason`. Built-in create/editor behavior is blocked when `isTimeSlotAvailable` returns `false`, when `closedHours`/`availabilityRules` mark the slot unavailable, or when `isSlotDisabled` / `isSlotSelectable` deny the slot. For resource-specific calendars, remote availability, and holidays, continue using `calendars` and `eventSchedulerAvailability`; the closed-slot hooks style those states too. ## Current time marker The current-time marker can use the built-in red line and badge, or it can be replaced by product-specific marker content. Week-style views render a horizontal marker on the active date. Resource timeline views render a vertical marker in the active timeline slot. | Config | Purpose | | --- | --- | | `showCurrentTimeMarker` | Simple show/hide alias. | | `currentTimeMarker` | Detailed marker config, including fixed `dateTime` for deterministic demos/tests. | | `currentTimeUpdateInterval` | Live refresh interval in milliseconds. Defaults to `60000`; use `0` to disable automatic live refresh. | | `currentTimeLabelFormatter` | Formats the marker label, such as `12:26`, `Now`, or a localized time. | | `currentTimeMarkerTemplate` | Replaces marker content in week and resource timeline cells. | | `currentTimeMarkerProperties` | Adds classes, styles, attributes, data fields, or event handlers to the marker cell. | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', showCurrentTimeMarker: true, currentTimeUpdateInterval: 60_000, currentTimeLabelFormatter: ({ dateTime }) => new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', }).format(new Date(dateTime)), currentTimeMarkerProperties: ({ orientation }) => ({ class: orientation === 'horizontal' ? 'planner-now-marker planner-now-marker--horizontal' : 'planner-now-marker planner-now-marker--vertical', style: { '--event-scheduler-current-time-color': '#dc2626', '--planner-now-marker-thickness': '2px', }, }), currentTimeMarkerTemplate: (h, context) => h('span', { class: 'planner-now-marker__label' }, context.label), }; ``` Marker context includes `view`, `date`, `dayIndex`, `slotIndex`, `startMinutes`, `endMinutes`, `dateTime`, `label`, `position`, `orientation`, and, when available, `row`, `resourceId`, and `resource`. The marker is hidden when disabled, outside the visible date range, or outside the visible time range. Use `currentTimeMarker: { dateTime }` when the marker should point at a fixed instant rather than live `now`. --- # Styling and Themes URL: https://pro.rv-grid.com/guides/event-scheduler/customization/styling/ Source: src/content/docs/guides/event-scheduler/customization/styling.mdx Description: Theme Event Scheduler with CSS variables, built-in classes, state selectors, and white-label styles. ## CSS variables and classes Use CSS when the scheduler structure is correct and only the theme needs to change. The scheduler exposes classes and CSS variables for common states, including today, weekend, current time, conflict, selected, locked, unassigned, status, type, category, non-working time, hover time, ghost ranges, create ranges, editor surfaces, and context menus. ```css .event-scheduler-plugin { --event-scheduler-accent: #0f766e; --event-scheduler-accent-soft: #ccfbf1; --event-scheduler-surface: #ffffff; --event-scheduler-surface-sunken: #f8fafc; --event-scheduler-grid-line: #d8dee6; --event-scheduler-current-time-color: #dc2626; --event-scheduler-weekend-bg: rgba(15, 118, 110, 0.04); --event-scheduler-hover-time-bg: rgba(15, 118, 110, 0.12); --event-scheduler-create-range-bg: rgba(15, 118, 110, 0.16); } .event-scheduler-event--status-confirmed { --event-scheduler-event-color: #2563eb; } .event-scheduler-event--type-maintenance { --event-scheduler-event-bg: #fff7ed; --event-scheduler-event-color: #c2410c; } .event-scheduler-day-cell__slot--non-working { opacity: 0.8; } ``` Prefer CSS variables for broad theme changes. Prefer `eventProperties` and `customization.*Properties` when classes or attributes depend on event, resource, date, slot, conflict, or projection context. ## White-label themes Scheduler styles are designed around CSS custom properties. Apply theme variables at the grid, plugin host, product shell, or global stylesheet level: ```css .booking-scheduler revo-grid { --event-scheduler-accent: #315f8c; --event-scheduler-accent-soft: #e8f1f8; --event-scheduler-grid-line: #d8dee6; --event-scheduler-surface: #ffffff; --event-scheduler-surface-sunken: #f6f8fb; --event-scheduler-event-bg: #eef7f3; --event-scheduler-event-color: #16825c; --event-scheduler-conflict-warning-bg: #fff7ed; --event-scheduler-conflict-warning-color: #c2410c; --event-scheduler-remote-state-bg: rgba(255, 255, 255, 0.96); } ``` Prefer CSS variables for broad white-label theming, and use render hooks only when the product needs different markup or data-driven content. --- # Editing and Interaction URL: https://pro.rv-grid.com/guides/event-scheduler/event-editing/ Source: src/content/docs/guides/event-scheduler/event-editing.mdx Description: Create, move, resize, delete, validate, select, copy, paste, and handle scheduler interactions with the RevoGrid Enterprise lifecycle. Enable editing with the master `editable` flag and the specific `allow*` permissions your product needs. Editing is checked in layers. The scheduler first applies `editable`, `allowCreate`, `allowMove`, `allowResize`, `allowDelete`, event/resource locks, date locks, and permission hooks. It then projects the next schedule and rejects the mutation when conflicts resolve to `severity: 'error'`. Use this to block edits outside working hours. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', editable: true, allowCreate: true, allowMove: true, allowResize: true, allowDelete: true, conflicts: { enabled: true, rules: { 'outside-availability': 'error' }, }, createEventDraft: (context) => ({ id: `event-${crypto.randomUUID()}`, resourceId: context.resourceId, title: 'New booking', startDateTime: context.startDateTime, endDateTime: context.endDateTime, status: 'draft', }), validateMutation: (detail) => { if (detail.event?.locked) { return 'Locked events cannot be changed.'; } }, }; ``` To enforce working hours, define them with `calendars` or `eventSchedulerAvailability`; then block the `outside-availability` conflict as shown above. `nonWorkingTime` can make closed cells visible, but it does not by itself reject create, move, resize, or editor changes. Conflicts are evaluated against the projected next event array before the local mutation is committed. This means move, resize, create, edit, delete, bulk, template, paste, and recurrence actions can all be stopped by the same conflict configuration. Use `conflicts.policy: 'prevent'` when every detected conflict should block by default, or keep `policy: 'mark'` and promote only selected rules such as `overlap`, `outside-availability`, or `blocked-time` to `error`. The before-change detail includes the projected `conflicts` array. Use it when the UI needs custom messages or business-specific decisions on top of configured conflict rules. ```ts grid.addEventListener('event-scheduler-before-event-change', (event) => { const blockingConflict = event.detail.conflicts?.find((conflict) => conflict.severity === 'error' && event.detail.eventId !== null && conflict.eventIds.includes(event.detail.eventId) ); if (blockingConflict) { event.preventDefault(); } }); ``` For one-off business rules that are not calendar rules, use `permissions.canCreate`, `permissions.canMutate`, or `validateMutation`. These hooks are useful for role checks, approval states, custom capacity rules, or backend-specific constraints. If you prefer event-based validation, listen for `event-scheduler-before-event-change` and call `event.preventDefault()`. This is the same cancelable-before-event pattern used by RevoGrid editing events. Do not use `event-scheduler-event-changed` for cancellation; it fires after the scheduler has already accepted and applied the change. ```ts grid.addEventListener('event-scheduler-before-event-change', (event) => { if (event.detail.action === 'move' && event.detail.event?.locked) { event.preventDefault(); } }); ``` ## Data ownership and save flows Event Scheduler does not require one persistence model. Choose the data ownership flow that matches your app: upload all events as host state, save the final projected schedule, save every accepted mutation, or let remote hooks load and commit data. Use `eventSchedulerEvents` to upload host-owned data into the scheduler: ```ts grid.eventSchedulerResources = resources; grid.eventSchedulerEvents = events; ``` The scheduler mutates its local accepted event list after permissions, before hooks, `validateMutation`, and conflict rules pass. Final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after that local state has already changed, so use them for persistence and state sync. Use before hooks or `validateMutation` for cancellation. ### Save the full projection after work Use this flow when the user can make several local edits and your app saves the whole schedule later, for example from a toolbar Save button. Read `scheduler.getProjection()?.events`; do not persist scheduler rows, columns, or segments because those are render artifacts. ```ts const plugins = await grid.getPlugins(); const scheduler = plugins.find((plugin) => plugin.constructor?.name === 'EventSchedulerPlugin' ); scheduler.createEvent({ id: `event-${crypto.randomUUID()}`, resourceId: 'room-a', title: 'Design review', startDateTime: '2026-06-08T09:00:00.000Z', endDateTime: '2026-06-08T10:00:00.000Z', }); scheduler.editEvent('booking-1', { resourceId: 'room-b', startDateTime: '2026-06-08T10:00:00.000Z', endDateTime: '2026-06-08T11:00:00.000Z', }); scheduler.deleteEvent('hold-1'); const savedEvents = scheduler.getProjection()?.events ?? []; await api.saveSchedule({ events: savedEvents }); // Later, or after clearing the scheduler, upload the saved data back. grid.eventSchedulerEvents = savedEvents; ``` ### Save incrementally while work happens Use this flow when the host app owns canonical state and wants every accepted scheduler change to update that state immediately. Each final event carries `detail.events`, the full next accepted event list after the mutation. ```ts let persistedEvents = [...initialEvents]; function persistSchedulerEvents(event: CustomEvent) { persistedEvents = event.detail.events; grid.eventSchedulerEvents = persistedEvents; void api.saveEvents(persistedEvents); } grid.addEventListener('event-scheduler-event-created', persistSchedulerEvents); grid.addEventListener('event-scheduler-event-changed', persistSchedulerEvents); grid.addEventListener('event-scheduler-event-deleted', persistSchedulerEvents); // Later, upload the persisted host state into a clean scheduler. grid.eventSchedulerEvents = persistedEvents; ``` The final event detail also includes action-specific context such as `eventId`, `event`, `previousEvent`, `action`, and `conflicts`. Use those fields for audit logs or targeted API calls, but keep `detail.events` as the simplest full-list state sync contract. ### Save through remote APIs Use remote mode when the server owns the visible range or when loading the whole schedule up front is too expensive. Remote loaders upload visible data into the scheduler. `commitMutation` receives the accepted projected mutation request and can return canonical server events. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', remote: { enabled: true, mode: 'remote', loadResources: ({ signal }) => api.loadResources({ signal }), loadEvents: ({ dateRange, resourceIds, signal }) => api.loadEvents({ dateRange, resourceIds, signal }), validateMutation: (request) => api.validateScheduleChange(request), commitMutation: async (request) => { const result = await api.saveScheduleChange({ action: request.action, eventId: request.eventId, event: request.event, previousEvent: request.previousEvent, changes: request.changes, events: request.events, conflicts: request.conflicts, }); return { accepted: true, events: result.events, }; }, }, }; const plugins = await grid.getPlugins(); const scheduler = plugins.find((plugin) => plugin.constructor?.name === 'EventSchedulerPlugin' ); // Reload the current visible range into a clean scheduler state. await scheduler.refreshVisibleRange(); ``` Remote `commitMutation` runs after the local mutation has been accepted. If the backend rejects the change, return `{ accepted: false, message }` or throw; with the default rollback behavior the scheduler restores the previous event list. See [Remote Data](/guides/event-scheduler/remote-data/) for paging, state events, errors, and refresh controls. `validateMutation` is the config-level equivalent of the cancelable before-change event. It receives the same detail shape and can return `false` or a string to reject. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', validateMutation: ({ action, event, changes }) => { if (action === 'delete' && event?.status === 'completed') { return 'Completed events cannot be deleted.'; } if (changes?.resourceId === 'locked-room') { return 'That room is locked.'; } }, }; ``` ## Custom editor Set `eventEditor: false` to disable the built-in editor, or provide a custom editor hook when the host app owns a drawer, modal, or approval flow: ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', eventEditor: (context) => { openProductDrawer({ mode: context.mode, event: context.event, resources: context.resources, readOnly: context.readonly, validationMessage: context.validationMessage, onSave: context.submit, onDelete: context.delete, onClose: context.close, }); return true; }, }; ``` The editor helpers still route changes through permissions, conflicts, `event-scheduler-before-event-change`, local mutation, post-commit events, and remote mutation hooks. ## Interaction callbacks Use named before hooks to validate or cancel user actions before local scheduler state changes. Return `false` to cancel with the default message, or return a string to cancel with a product-specific validation message. | Before hook | Action | | --- | --- | | `beforeEventCreate` | Create from slot, editor create, template create, recurrence create | | `beforeEventUpdate` | Editor/API updates and status changes | | `beforeEventMove` | Drag move and bulk move | | `beforeEventResize` | Drag resize | | `beforeEventDelete` | Delete and bulk delete | | `beforeEventDuplicate` | Duplicate selected events | | `beforeEventLock` | Lock or unlock changes | | `beforeSlotSelect` | Empty-slot selection/click before create behavior | Final callbacks fire after the scheduler has committed local state and include enough context to update app state or persist to a backend. | Final callback | Action | | --- | --- | | `onEventCreate` | Event created, including paste/duplicate/template-created events | | `onEventUpdate` | Event updated or status changed | | `onEventMove` | Event moved | | `onEventResize` | Event resized | | `onEventDelete` | Event deleted | | `onEventDuplicate` | Duplicated event created | | `onEventLock` | Event locked or unlocked | | `onEventCopy` | Event copied into the scheduler clipboard | | `onEventPaste` | Pasted event created | | `onSelectionChange` | Event selection changed | | `onSlotSelect` | Empty slot selected/clicked | | `contextMenu.onContextMenuAction` | Scheduler context-menu item selected | ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', beforeEventMove: ({ event, changes }) => { if (event?.locked) return 'Locked events cannot be moved.'; return validateMoveOnServerCache(event, changes); }, beforeEventResize: ({ event, changes }) => isOutsidePolicyWindow(event, changes) ? 'Outside policy window.' : undefined, beforeEventDelete: ({ event }) => event?.metadata?.protected ? 'Protected events cannot be deleted.' : undefined, beforeSlotSelect: ({ date, startDateTime }) => isClosedForBooking(date, startDateTime) ? 'This slot is closed.' : undefined, onEventMove: ({ event, previousEvent }) => saveEventMove({ event, previousEvent }), onEventResize: ({ event, previousEvent }) => saveEventResize({ event, previousEvent }), onEventDelete: ({ event }) => archiveDeletedEvent(event), onSelectionChange: ({ eventIds }) => setSelectedPlannerEvents(eventIds), contextMenu: { onContextMenuAction: ({ itemId, target, segment, slot }) => trackSchedulerCommand({ itemId, target, eventId: segment?.event.id, slot }), }, }; ``` The lower-level `event-scheduler-before-event-change`, `event-scheduler-before-event-select`, `event-scheduler-before-slot-select`, `event-scheduler-event-created`, `event-scheduler-event-changed`, `event-scheduler-event-deleted`, and `event-scheduler-event-selected` DOM events remain available for integrations that prefer event listeners. Call `preventDefault()` on the before events to cancel the pending scheduler action. `validateMutation` still runs in the same mutation pipeline for shared validation logic. ## Selection, clipboard, and keyboard Scheduler selection supports selected events, selected slots, selected time ranges, multi-select, range select, copy, paste, duplicate, and keyboard-driven commands. Use the cancelable `event-scheduler-before-event-select` event when the host application owns selected event state: prevent the pending selection, update application state, then pass the accepted ids back through `selection.selectedEventIds`. ```ts let selectedEventIds: readonly string[] = []; grid.addEventListener('event-scheduler-before-event-select', (event) => { event.preventDefault(); selectedEventIds = event.detail.eventIds; renderScheduler(); }); function renderScheduler() { grid.eventScheduler = { ...grid.eventScheduler, selectionMode: 'multiple', selection: { ...grid.eventScheduler?.selection, selectedEventIds, selectedSlot, selectedRange, clipboard: true, copyOffsetDays: 7, onChange: ({ eventIds }) => setSelectedEventIds(eventIds), }, onSelectionChange: ({ eventIds, events }) => syncInspectorSelection({ eventIds, events }), onSlotSelect: (slot) => setSelectedSlot(slot), }; } ``` Accepted selections still emit `event-scheduler-event-selected`, `onSelectionChange`, and `selection.onChange`. A prevented selection does not emit final selection callbacks; the host re-applies its accepted state through the config. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', beforeSlotSelect: ({ date }) => isClosedForBooking(date) ? 'This slot is closed.' : undefined, }; grid.addEventListener('event-scheduler-before-slot-select', (event) => { if (isReadOnlyResource(event.detail.resourceId)) { event.preventDefault(); } }); ``` Use `clipboardAdapter` when copy/paste must integrate with product state, permissions, or a custom clipboard store. Paste remains disabled when there is no internal payload and `clipboardAdapter.hasPayload()` is false. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', clipboardAdapter: { hasPayload: () => appClipboard.has('scheduler-events'), read: () => appClipboard.get('scheduler-events'), write: (payload) => appClipboard.set('scheduler-events', payload), clear: () => appClipboard.delete('scheduler-events'), }, onCopy: ({ eventIds }) => trackCopy(eventIds), onPaste: ({ event }) => persistCreatedEvent(event), onDuplicate: ({ event }) => persistCreatedEvent(event), }; ``` Keyboard shortcuts can be disabled globally, disabled per command, or rebound. Use `Mod` for Cmd on macOS and Ctrl on Windows/Linux. ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', keyboardShortcuts: { enabled: true, helpPanel: true, shortcuts: { copy: 'Mod+c', paste: 'Mod+v', duplicate: 'Mod+Shift+d', delete: false, lock: false, today: ['t', 'Home'], }, }, }; ``` Available keyboard commands are `escape`, `help`, `today`, `search`, `previous`, `next`, `cyclePrevious`, `cycleNext`, `open`, `create`, `edit`, `lock`, `delete`, `copy`, `paste`, `duplicate`, and `viewWeek`. --- # Examples URL: https://pro.rv-grid.com/guides/event-scheduler/examples/ Source: src/content/docs/guides/event-scheduler/examples.mdx Description: Working Event Scheduler examples for setup, templates, menus, availability, read-only mode, cancelable selection, and custom themes. This page collects small, product-neutral Event Scheduler examples. The same APIs work for shifts, bookings, appointments, staffing, rooms, maintenance, tasks, equipment, and team planning. The runnable demo catalog includes these complete scheduler stories: | Demo | Use it for | | --- | --- | | Event Staff Scheduler | Resource timeline, coverage, conflicts, templates, saved views, and remote-mode controls. | | Employee Shift Planner | Week view with days on the x-axis, time on the y-axis, drag-create, move, resize, delete, and closed hours. | | Room Booking Scheduler | Resource booking with availability windows, blocked time, conflicts, and grouped resources. | | Equipment / Machine Scheduler | Maintenance planning, utilization, capacity rows, and exportable workload data. | | Developer Sprint Scheduler | Team/task planning, custom ranges, unassigned work, templates, and workload balancing. | ## Basic weekly scheduler ```ts import { EventSchedulerPlugin, type EventSchedulerConfig, type EventSchedulerEventEntity, } from '@revolist/revogrid-enterprise'; const events: EventSchedulerEventEntity[] = [ { id: 'booking-1', title: 'Customer onboarding', resourceId: 'room-a', startDateTime: '2026-06-08T09:00:00.000Z', endDateTime: '2026-06-08T11:00:00.000Z', type: 'booking', status: 'confirmed', }, ]; const eventScheduler: EventSchedulerConfig = { view: 'week', weekStartDate: '2026-06-08', weekStartsOn: 1, visibleDays: [1, 2, 3, 4, 5], timeRange: { start: '06:00', end: '22:00' }, slotMinutes: 30, locale: 'en-US', timeZone: 'UTC', editable: true, allowCreate: true, allowMove: true, allowResize: true, allowDelete: true, todayHighlight: true, showCurrentTimeMarker: true, }; grid.plugins = [EventSchedulerPlugin]; grid.eventScheduler = eventScheduler; grid.eventSchedulerEvents = events; ``` ## Custom event template ```ts grid.eventScheduler = { ...grid.eventScheduler, eventProperties: ({ event, isSelected, hasConflict, isLocked }) => ({ class: [ 'planner-event', isSelected ? 'planner-event--selected' : '', hasConflict ? 'planner-event--conflict' : '', isLocked ? 'planner-event--locked' : '', ].filter(Boolean).join(' '), style: { '--planner-event-accent': event.color ?? '#2563eb', }, }), eventContentTemplate: (h, { event, resource, start, end, isShortEvent }) => h('span', { class: 'planner-event__body' }, [ h('strong', { class: 'planner-event__title' }, event.title), isShortEvent ? null : h('span', { class: 'planner-event__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`), resource ? h('small', { class: 'planner-event__resource' }, resource.name) : null, ]), eventTooltipTemplate: ({ event, resource, duration }) => `${event.title}\n${resource?.name ?? 'Unassigned'}\n${duration} minutes`, }; ``` ## Custom day header ```ts grid.eventScheduler = { ...grid.eventScheduler, dayHeaderFormatter: (date) => new Intl.DateTimeFormat('en-US', { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC', }).format(date), dayHeaderTemplate: (h, context) => h('span', { class: 'planner-day-header' }, [ h('strong', null, context.defaultLabel), context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null, context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null, h('small', null, `${context.eventCount ?? 0} events`), ]), dayHeaderProperties: ({ today, weekend, holiday }) => ({ class: [ today ? 'planner-day-header--today' : '', weekend ? 'planner-day-header--weekend' : '', holiday ? 'planner-day-header--holiday' : '', ].filter(Boolean).join(' '), }), }; ``` ## Custom slot context menu ```ts grid.eventScheduler = { ...grid.eventScheduler, contextMenu: { enabled: true, getSlotContextMenuItems: ({ slot }) => slot ? [ { id: 'create-booking', label: 'Create booking here', shortcut: 'C', action: () => createBooking(slot), }, { id: 'hold-slot', label: 'Block this time', group: 'availability', action: () => createHold(slot), }, ] : [], onContextMenuAction: ({ itemId, target, slot }) => analytics.track('scheduler_menu_action', { itemId, target, slot }), }, }; ``` ## Custom current time marker ```ts grid.eventScheduler = { ...grid.eventScheduler, showCurrentTimeMarker: true, currentTimeUpdateInterval: 60_000, currentTimeLabelFormatter: ({ dateTime }) => new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', }).format(new Date(dateTime)), currentTimeMarkerProperties: ({ orientation }) => ({ class: `planner-now planner-now--${orientation}`, style: { '--event-scheduler-current-time-color': '#dc2626', }, }), currentTimeMarkerTemplate: (h, { label }) => h('span', { class: 'planner-now__label' }, label), }; ``` ## Closed hours and availability ```ts grid.eventScheduler = { ...grid.eventScheduler, workingHours: { byDay: { 1: { start: '08:00', end: '18:00' }, 2: { start: '08:00', end: '18:00' }, 3: { start: '08:00', end: '18:00' }, 4: { start: '08:00', end: '18:00' }, 5: { start: '08:00', end: '16:00' }, 0: false, 6: false, }, }, closedHours: [ { id: 'lunch', days: [1, 2, 3, 4, 5], ranges: { start: '12:00', end: '13:00' }, title: 'Closed', reason: 'Lunch break', }, ], closedSlotTooltip: ({ title, reason }) => [title, reason].filter(Boolean).join(' - '), closedSlotTemplate: (h, { reason }) => h('span', { class: 'planner-slot__closed' }, reason ?? 'Closed'), isTimeSlotAvailable: ({ date, startMinutes }) => date !== '2026-06-19' && startMinutes >= 8 * 60, }; ``` ## Read-only mode ```ts grid.eventScheduler = { ...grid.eventScheduler, editable: false, allowCreate: false, allowMove: false, allowResize: false, allowDelete: false, keyboardShortcuts: { enabled: true, shortcuts: { copy: 'Mod+c', paste: false, duplicate: false, delete: false, lock: false, }, }, }; ``` Read-only mode keeps rendering, selection, tooltips, context menus, and custom templates active while blocking scheduler mutations. ## Cancelable selection state ```ts let selectedEventIds: readonly string[] = []; grid.addEventListener('event-scheduler-before-event-select', (event) => { event.preventDefault(); selectedEventIds = event.detail.eventIds.map(String); renderScheduler(); }); function renderScheduler() { grid.eventScheduler = { ...grid.eventScheduler, selectionMode: 'multiple', selection: { selectedEventIds, clipboard: true, onChange: ({ eventIds }) => { selectedEventIds = eventIds.map(String); renderScheduler(); }, }, onSelectionChange: ({ events }) => inspector.show(events), onEventMove: ({ events }) => { grid.eventSchedulerEvents = [...events]; void saveEvents(events); }, }; } ``` Use the before-select event when selection lives in application state. Prevent the pending scheduler selection, update host state, then pass the accepted ids back through `selection.selectedEventIds`. Event arrays, visible ranges, and filters follow the same ownership model: the scheduler emits callbacks and the host updates `eventScheduler`, `eventSchedulerEvents`, and related props. ## Custom styling and theme ```css .booking-scheduler revo-grid { --event-scheduler-accent: #0f766e; --event-scheduler-accent-soft: #ccfbf1; --event-scheduler-grid-line: #d8dee6; --event-scheduler-current-time-color: #dc2626; --event-scheduler-weekend-bg: rgba(15, 118, 110, 0.05); --event-scheduler-event-bg: #eef7f3; --event-scheduler-event-color: #0f766e; } .planner-event--conflict { --event-scheduler-event-color: #b45309; --event-scheduler-event-bg: #fff7ed; } .planner-day-header--today { box-shadow: inset 0 -2px 0 var(--event-scheduler-accent); } ``` Use CSS variables for broad theme changes. Use `eventProperties`, `dayHeaderProperties`, `slotProperties`, `closedSlotProperties`, and `currentTimeMarkerProperties` when styling depends on scheduler state. ## Related guides - [Week View](../week-view/) covers day/time configuration, visible range, week start, locale, timezone, and edit ownership. - [Customization](../customization/) documents the full visual customization category. Use [Event Rendering](../customization/events/) for templates and tooltips, [Slots, Hours, and Markers](../customization/slots-hours/) for slot and current-time examples, [Context Menus](../customization/context-menu/) for commands, and [Styling and Themes](../customization/styling/) for CSS hooks. - [Availability](../scheduling-rules/availability/) covers calendars, working hours, blocked time, breaks, holidays, and outside-hours styling. - [Editing and Interaction](../event-editing/) covers read-only mode, permissions, cancelable hooks, mutation events, selection, clipboard, keyboard shortcuts, custom editors, and persistence. --- # Export URL: https://pro.rv-grid.com/guides/event-scheduler/export/ Source: src/content/docs/guides/event-scheduler/export.mdx Description: Export scheduler events, workload, conflicts, coverage gaps, visible schedules, and print views. The event scheduler exports data from the current projection, so filters, visible resources, conflicts, coverage, and utilization match what the user is reviewing. ```ts const scheduler = (await grid.getPlugins()).find( (plugin) => plugin.constructor?.name === 'EventSchedulerPlugin', ); const projection = scheduler.getProjection(); const eventsCsv = scheduler.exportEvents({ format: 'csv' }); const workloadJson = scheduler.exportResourceWorkload({ format: 'json' }); const conflictsCsv = scheduler.exportConflicts({ format: 'csv' }); const coverageCsv = scheduler.exportCoverage({ format: 'csv', gapsOnly: true }); const visibleCsv = scheduler.exportVisibleSchedule({ format: 'csv' }); download(eventsCsv.content, eventsCsv.fileName, eventsCsv.mimeType); ``` For printable schedules, create an HTML view from the same projection. ```ts const print = scheduler.createPrintView({ title: 'Weekly Staff Schedule', includeEvents: true, includeWorkload: true, includeConflicts: true, includeCoverage: true, }); const printWindow = window.open('', '_blank'); printWindow?.document.write(print.html); printWindow?.print(); ``` ## Production Checklist - Use stable ids for events, resources, assignments, templates, and coverage requirements. - Store scheduler datetimes as ISO strings and define a product time-zone policy before persisting user edits. - Keep `eventSchedulerEvents` controlled by app state and persist every created, changed, and deleted event array. - Enable `allowCreate` intentionally. It defaults to false. - Add permission rules for locked resources, read-only events, completed statuses, and protected date ranges. - Choose conflict policy deliberately. Use `prevent` or error rules for hard booking constraints. - Use availability for real scheduling constraints and `nonWorkingTime` for visual closed-time guidance. - Use `resourceGrouping`, `filters`, and `savedViews` for planner navigation instead of mutating source arrays. - Use `coverage` and `utilization` only when the resource metadata needed for planning is present. - Add remote validation and rollback for server-backed schedulers. - Keep keyboard shortcuts enabled unless the host app owns all scheduler keyboard behavior. - Expose export or print workflows from the plugin projection when planners need to share schedules. - Test drag-create, move, resize, delete, selection, keyboard shortcuts, conflicts, and remote failure paths in the target framework. --- # Grouping URL: https://pro.rv-grid.com/guides/event-scheduler/grouping/ Source: src/content/docs/guides/event-scheduler/grouping.mdx Description: Group scheduler resources by team, department, location, parent tree, or custom business rules. Use `resourceGrouping` for team, department, location, room-building, machine-line, or sprint-team grouping. Parent-child resources can render as a tree. ```ts grid.eventSchedulerResources = [ { id: 'north', name: 'North Campus', role: 'Location' }, { id: 'room-a', parentId: 'north', name: 'Room A', role: 'Room', metadata: { floor: '1' } }, { id: 'room-b', parentId: 'north', name: 'Room B', role: 'Room', metadata: { floor: '2' } }, ]; grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', resourceGrouping: { enabled: true, tree: true, groupBy: (resource) => String(resource.metadata?.floor ?? resource.group ?? 'Other'), groupSummaryFormatter: ({ summary }) => `${summary.eventCount} events - ${summary.totalHours}h`, }, }; ``` Group rows aggregate workload, coverage, and utilization summaries when those features are enabled. ## Resource Planner Patterns Use `resourceTimeline` when resources are the main rows and time moves horizontally. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', dateRange: { start: '2026-06-08', end: '2026-06-14' }, resourceColumnSize: 220, timelineColumnSize: 72, resourceGrouping: { enabled: true, groupBy: (resource) => resource.metadata?.location as string, tree: true, collapsed: false, groupSummaryFormatter: ({ summary }) => `${summary.eventCount} events - ${summary.totalHours}h`, }, resourceMetaFormatter: (resource) => [resource.role, resource.metadata?.location].filter(Boolean).join(' - '), }; ``` Events without `resourceId`, `resourceIds`, or side-channel assignments render in the generated unassigned row when `unassigned.enabled` is not false. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', unassigned: { enabled: true, showCount: true, requiredRoleField: 'metadata.requiredRole', filter: { status: ['draft', 'planned'], role: 'Technician' }, counterFormatter: (summary) => `${summary.visibleCount} open requests`, requiredRoleFormatter: ({ role }) => `Need ${role}`, assignmentValidator: ({ event, resource }) => { const required = event.metadata?.requiredRole; const skills = Array.isArray(resource.metadata?.skills) ? resource.metadata.skills : []; return !required || skills.includes(required) || `${resource.name} cannot fill ${required}.`; }, }, }; ``` Coverage compares required capacity with assigned event capacity in the visible range. Utilization calculates scheduled workload by resource. ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', coverage: { enabled: true, capacityField: 'capacity', countEvent: ({ event, resource }) => event.status === 'cancelled' ? false : resource.capacity ?? 1, }, utilization: { enabled: true, minHoursField: 'metadata.minHoursPerWeek', targetHoursField: 'metadata.targetHoursPerWeek', maxHoursField: 'metadata.maxHoursPerWeek', excludeStatuses: ['cancelled', 'blocked'], }, }; ``` Use `filters` and `savedViews` to reduce projected data without mutating source arrays: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', filters: { search: 'security', resourceRoles: 'Security', eventStatuses: ['planned', 'confirmed'], conflictsOnly: false, unassignedOnly: false, }, activeSavedViewId: 'security-week', savedViews: { views: [ { id: 'conflicts', label: 'Conflicts only', filters: { conflictsOnly: true } }, { id: 'unassigned', label: 'Unassigned', filters: { unassignedOnly: true } }, { id: 'security-week', label: 'Security team', dateRange: { start: '2026-06-08', end: '2026-06-12' }, filters: { resourceRoles: 'Security' }, }, ], }, }; ``` The projection exposes `filterSummary` with visible and hidden resource/event counts. --- # Getting Started URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/ Source: src/content/docs/guides/event-scheduler/introduction/index.mdx Description: Set up RevoGrid Enterprise Event Scheduler for staff scheduling, shift planning, room booking, equipment planning, and team calendars. `EventSchedulerPlugin` turns RevoGrid Enterprise into a commercial scheduler for events on time grids. Use it for staff rosters, employee shifts, room reservations, machine plans, field crews, support queues, and sprint capacity. The scheduler supports calendar and resource timeline views, editable event blocks, resources, assignments, calendars, availability, conflicts, grouping, templates, recurrence helpers, remote data, custom renderers, and export helpers. Create an empty grid and provide scheduler data through side-channel props. The scheduler owns the generated grid projection; your app owns persistence. ## Minimal Astro setup The only required event fields are `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `status`, `type`, `metadata`, and resources when your product needs labels, capacity rows, filtering, styling, or persistence fields. ## Minimal TypeScript setup If the grid already exists in your app, set the same properties directly: ```ts import { EventSchedulerPlugin } from '@revolist/revogrid-enterprise'; grid.plugins = [EventSchedulerPlugin]; grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', slotMinutes: 60, timeRange: { start: '08:00', end: '18:00' }, }; grid.eventSchedulerEvents = [ { id: 'booking-1', startDateTime: '2026-06-08T09:00:00.000Z', endDateTime: '2026-06-08T12:00:00.000Z', }, ]; ``` ## What Event Scheduler does Event Scheduler uses RevoGrid as the rendering and interaction surface for time-based planning. Instead of building ordinary data rows and columns yourself, you give the grid scheduler configuration, events, resources, and optional planning datasets. The plugin projects that data into timeline columns, resource rows, event blocks, drag handles, conflict indicators, non-working time, and editor interactions. Use it when the main question is "who or what is booked, when, and against which capacity?" Common use cases include: - staff and employee shift scheduling - room, desk, vehicle, or equipment booking - machine and production line planning - field crew, support queue, and service dispatch calendars - sprint, delivery, or team-capacity calendars ## Core concepts - `events` are the scheduled blocks. Each event needs an `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `resourceIds`, or `assignmentIds` when the event needs a label or belongs to a person, room, machine, team, or other capacity. - `resources` are the schedulable rows in resource planning. A resource can be a person, room, vehicle, machine, location, team, department, or parent group. - `views` decide how time is shown. Use `day`, `week`, or `month` for calendar-style time grids, and `resourceTimeline` when each resource needs its own row across a visible date or time range. - `projection` is the derived scheduler model. The plugin combines config, events, resources, assignments, availability, templates, coverage requirements, and filters into visible rows, columns, event lanes, conflict markers, group summaries, and overlays. - Your app owns the data. The grid renders the schedule and emits mutation events. Your app listens to those events, replaces `eventSchedulerEvents` with the next event array, and persists changes to local state or a backend. ## Data ownership Use the scheduler props as the public data boundary: - **Events**: Every event needs `id`, `startDateTime`, and `endDateTime`. `title`, `resourceId`, `resourceIds`, `status`, and `type` are optional. Use resource fields for resource timelines or leave them out for unassigned demand. - **Resources**: Use resources for people, rooms, vehicles, machines, teams, locations, or parent groups. `calendarId`, `capacity`, `group`, `role`, and `metadata` feed calendars, grouping, coverage, utilization, and filters. - **Assignments**: Use assignments when your system stores event-to-resource links separately from the event record. They are merged with `resourceId` and `resourceIds` during projection. - **Availability**: Use dated `working`, `blocked`, `holiday`, and `break` windows for operational exceptions such as PTO, maintenance, temporary opening hours, or one-off closures. - **Templates and coverage**: Use templates for repeated event creation and coverage requirements for required capacity/headcount checks. `locked` and `readonly` events or resources reject normal edit flows. `color`, `className`, `status`, `type`, `category`, and `metadata` let products style, classify, filter, or persist scheduler records without replacing the scheduler model. ## Main props - **`eventScheduler`**: Main `EventSchedulerConfig`. Controls the active view, visible date range, slot size, visible hours, editing permissions, event layout, conflicts, calendars, filters, labels, formatters, templates, remote loading, and customization hooks. - **`eventSchedulerEvents`**: The event blocks to render. Events contain stable ids and start/end datetimes, plus optional titles, status/type metadata, and resource or assignment links. - **`eventSchedulerResources`**: The schedulable resources shown in resource-timeline flows. Use it for people, rooms, machines, teams, locations, vehicles, or parent groups. - **`eventSchedulerAssignments`**: Optional assignment link data when events and resources are connected by a separate system table instead of direct `resourceId` fields. - **`eventSchedulerAvailability`**: Optional working, blocked, holiday, or break windows for resource-specific availability and outside-availability conflict checks. - **`eventSchedulerTemplates`**: Optional reusable event templates for quick creation and recurrence helpers. - **`eventSchedulerCoverageRequirements`**: Optional staffing or capacity requirements used by coverage summaries and gap highlighting. - **`source` and `columns`**: Usually empty for scheduler pages. The scheduler projection owns the visual rows and columns, so host data should be passed through scheduler props. ## Basic setup ```ts import { EventSchedulerPlugin, type EventSchedulerConfig, type EventSchedulerEventEntity, type EventSchedulerResourceEntity, } from '@revolist/revogrid-enterprise'; const resources: EventSchedulerResourceEntity[] = [ { id: 'room-a', name: 'Room A', role: 'Room', group: 'Conference' }, { id: 'room-b', name: 'Room B', role: 'Room', group: 'Conference' }, ]; const events: EventSchedulerEventEntity[] = [ { id: 'booking-1', startDateTime: '2026-06-08T09:00:00.000Z', endDateTime: '2026-06-08T12:00:00.000Z', title: 'Customer workshop', resourceId: 'room-a', status: 'confirmed', type: 'booking', }, ]; const eventScheduler: EventSchedulerConfig = { view: 'resourceTimeline', weekStartDate: '2026-06-08', dateRange: { start: '2026-06-08', end: '2026-06-08' }, slotMinutes: 60, timeRange: { start: '08:00', end: '18:00' }, editable: true, allowCreate: true, allowMove: true, allowResize: true, conflicts: { enabled: true, policy: 'mark', scope: 'same-resource' }, }; grid.plugins = [EventSchedulerPlugin]; grid.eventScheduler = eventScheduler; grid.eventSchedulerResources = resources; grid.eventSchedulerEvents = events; grid.source = []; grid.columns = []; ``` For framework wrappers, bind `eventScheduler`, `eventSchedulerResources`, and `eventSchedulerEvents` as grid properties. ## Blocking overlap and conflicts Existing data can still render with conflict markers so users can review and fix it. To block future overlapping create, move, resize, and edit actions for the same resource, keep the scheduler in mark mode and promote only `overlap` to an error: ```ts grid.eventScheduler = { ...grid.eventScheduler, conflicts: { enabled: true, policy: 'mark', scope: 'same-resource', rules: { overlap: 'error' }, }, }; ``` Error conflicts render with the built-in red conflict state. The same conflict result is used by the mutation pipeline, so an overlap introduced by create, move, resize, or edit is rejected before local events are committed. ## Where to start 1. Choose the view. Use `week` for a shift-style calendar, or `resourceTimeline` when each resource needs a row. 2. Define the visible range with `weekStartDate`, `dateRange`, `visibleDays`, `slotMinutes`, and `timeRange`. 3. Provide `eventSchedulerResources` when events need to be placed against staff, rooms, equipment, teams, or other capacity. 4. Provide `eventSchedulerEvents` with ISO datetime strings and stable ids. 5. Add `calendars` for recurring working rules and `eventSchedulerAvailability` for dated working windows, breaks, holidays, or blocked time. 6. Enable editing only when the host app handles emitted changes. Start with `editable: false` for read-only schedules, then turn on `allowCreate`, `allowMove`, `allowResize`, and `allowDelete` as your persistence flow is ready. 7. Add conflict rules, templates, remote data, custom labels, or renderers after the basic schedule renders. ## Handling edits The scheduler emits a cancelable before-change event before it commits local event changes. This follows the RevoGrid event pattern: call `preventDefault()` on the before event to cancel the mutation. ```ts grid.addEventListener('event-scheduler-before-event-change', (event) => { if (event.detail.event?.locked) { event.preventDefault(); } }); ``` `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the mutation succeeds. Use them for persistence and state sync, not cancellation. In local-state examples, update `eventSchedulerEvents` from the emitted `detail.events` array: ```ts const syncEvents = (event: Event) => { grid.eventSchedulerEvents = [ ...(event as CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>).detail.events, ]; }; grid.addEventListener('event-scheduler-event-created', syncEvents); grid.addEventListener('event-scheduler-event-changed', syncEvents); grid.addEventListener('event-scheduler-event-deleted', syncEvents); ``` If your toolbar owns the current range or active view, also handle navigation and view requests: ```ts grid.addEventListener('event-scheduler-navigate-request', (event) => { const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail; // Update weekStartDate/dateRange in grid.eventScheduler here. }); grid.addEventListener('event-scheduler-view-request', (event) => { const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail; // Update grid.eventScheduler.view here. }); ``` For remote products, use the same ownership model: the scheduler can optimistically emit changes, but your app or `remote` hooks decide when backend mutations are committed, rejected, or rolled back. ## Next guides - [Vertical vs Horizontal Time](./vertical-vs-horizontal/) helps choose between calendar-style and resource-timeline scheduling. - [Timeline Views](../timeline-views/) explains day, week, month, and resource timeline configuration. - [Week View](../week-view/) is a practical recipe for shift-style vertical week calendars. - [Editing and Interaction](../event-editing/) covers create, move, resize, delete, editors, selection, clipboard, keyboard shortcuts, permissions, and validation. - [Conflicts](../scheduling-rules/conflicts/) covers overlap, availability, locked-change, duration, capacity, prevention events, and assignment checks. - [Event Scheduler Calendars](../scheduling-rules/calendars/) covers working days, holidays, and resource-specific operating hours. - [Scheduling Rules](../scheduling-rules/) explains how calendars, availability, visual non-working time, and conflict policies work together. - [Remote Data](../remote-data/) covers loading ranges, optimistic mutations, rollback, and load-more flows. - [Customization](../customization/) groups labels, event rendering, layout/cells, slots/hours, conflicts/states, context menus, styling, interaction, and editor hooks. - [Examples](../examples/) collects small product-neutral examples for setup, event templates, day headers, slots, current-time markers, context menus, availability, read-only mode, cancelable selection state, and custom themes. Start with the Event Staff Scheduler demo for the full business story, or the Employee Shift Planner demo for a focused week-view shift workflow. --- # Vertical vs Horizontal Time URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/vertical-vs-horizontal/ Source: src/content/docs/guides/event-scheduler/introduction/vertical-vs-horizontal.mdx Description: Choose between calendar-style vertical scheduling and resource timeline scheduling. Scheduling views usually answer one of two questions: - "What happens during this day or week?" - "Which resource is booked across this date range?" Use a vertical time view for the first question. Use a horizontal resource timeline for the second. ## Vertical Time Scheduling Vertical time scheduling is the familiar calendar layout. Time moves down the page, days are arranged across columns, and an event duration is shown by the event height. This layout is best when users think in days, hours, and appointments: | Good fit | Why | | :-- | :-- | | Staff shifts | Managers scan a day or week and adjust start/end times. | | Appointments | Users compare open slots in a familiar calendar shape. | | Room bookings for one location | The day is more important than a long resource list. | | Personal or team calendars | The main task is finding a time window. | Configure it with `day`, `week`, or `month` views: ```ts const verticalWeek = { view: 'week', weekStartDate: '2026-06-08', visibleDays: [1, 2, 3, 4, 5], slotMinutes: 30, timeRange: { start: '08:00', end: '18:00' }, timeColumnSize: 88, dayColumnSize: 180, }; ``` Choose vertical time when the primary action is creating, moving, or resizing events inside a day or week. ## Horizontal Resource Timeline Horizontal scheduling puts resources on rows and time on columns. Time moves left to right, and an event duration is shown by the event width. This layout is best when users compare many resources over the same window: | Good fit | Why | | :-- | :-- | | Multi-room booking | Rooms stay fixed on the left while the schedule scrolls horizontally. | | Equipment planning | Users compare utilization across machines, vehicles, or tools. | | Field crews | Dispatchers see who is available across several days. | | Open demand planning | Unassigned work can sit beside staffed resources. | Configure it with `resourceTimeline`: ```ts const horizontalTimeline = { view: 'resourceTimeline', dateRange: { start: '2026-06-08', end: '2026-06-12' }, slotMinutes: 60, timeRange: { start: '08:00', end: '18:00' }, resourceColumnSize: 240, timelineColumnSize: 72, }; ``` Choose a horizontal timeline when the primary action is comparing capacity, coverage, and conflicts across resources. ## Same Data, Different Projection Both orientations use the same scheduler data: - events define the booked work - resources define people, rooms, machines, or teams - assignments connect events to resources when the model needs many-to-many scheduling - availability, calendars, conflicts, templates, and remote loading work across both layouts The difference is projection. Vertical views emphasize time within a day or week. Horizontal views emphasize resources across a date range. ## Choosing a Default Start with vertical time scheduling when users mostly edit individual events and think in daily calendars. Start with a horizontal resource timeline when users mostly compare many resources, load large schedules from a server, or need coverage and utilization signals. Many applications expose both. For example, a shift planner can use a week view for fast editing and a resource timeline for staffing coverage. --- # Remote Data URL: https://pro.rv-grid.com/guides/event-scheduler/remote-data/ Source: src/content/docs/guides/event-scheduler/remote-data.mdx Description: Load scheduler resources and events from a server, refresh visible ranges, and handle optimistic save failures. Use `remote` when events or resources are too large to ship up front, or when the scheduler must load only the visible date range. For a comparison of host-owned full saves, host-owned incremental saves, and remote-backed saves, see [Data ownership and save flows](/guides/event-scheduler/event-editing/#data-ownership-and-save-flows). ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', remote: { enabled: true, loadEvents: async ({ dateRange, resourceIds }) => { const params = new URLSearchParams({ start: dateRange.start, end: dateRange.end, resources: resourceIds?.join(',') ?? '', }); const response = await fetch(`/api/scheduler/events?${params}`); return { events: await response.json() }; }, loadResources: async () => { const response = await fetch('/api/scheduler/resources'); return { resources: await response.json() }; }, commitMutation: async (request) => { const response = await fetch('/api/scheduler/mutations', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(request), }); if (!response.ok) { throw new Error('Save failed'); } return { accepted: true, events: await response.json() }; }, }, }; ``` The resource timeline demo includes remote-mode controls for refresh, load-more resources, and simulated save failure. ## Remote Options and State Remote mode can load events, resources, availability, unassigned demand, and coverage for the visible range. It can also validate and commit accepted mutations. ```ts const eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', dateRange: { start: '2026-06-08', end: '2026-06-14' }, remote: { enabled: true, mode: 'remote', debounceMs: 150, overscanDays: 1, resourcePageSize: 50, loadEvents: ({ dateRange, filters, signal }) => api.loadEvents({ dateRange, filters }, { signal }), loadResources: ({ cursor, limit, signal }) => api.loadResources({ cursor, limit }, { signal }), loadAvailability: ({ dateRange, signal }) => api.loadAvailability({ dateRange }, { signal }), loadCoverage: ({ dateRange, signal }) => api.loadCoverage({ dateRange }, { signal }), validateMutation: ({ action, event, previousEvent }) => api.validateScheduleChange({ action, event, previousEvent }), commitMutation: ({ action, event, previousEvent, events }) => api.saveScheduleChange({ action, event, previousEvent, events }), optimistic: true, rollbackOnError: true, refreshPolicy: 'visible-range', }, }; ``` Remote modes: | Mode | Behavior | | :-- | :-- | | `local` | Remote controller is effectively off unless enabled hooks are used by the host. | | `remote` | Visible data is loaded through remote hooks. | | `hybrid` | Host can combine local side-channel data with remote range data. | Remote state is available from `scheduler.getRemoteState()`. The plugin emits: | Event | Purpose | | :-- | :-- | | `event-scheduler-remote-load-start` | A visible-range load begins. | | `event-scheduler-remote-load-success` | A load target resolves. | | `event-scheduler-remote-load-error` | A load target fails. | | `event-scheduler-remote-mutation-pending` | A remote mutation starts. | | `event-scheduler-remote-mutation-committed` | The backend accepts a mutation. | | `event-scheduler-remote-mutation-rejected` | The backend rejects a mutation. | | `event-scheduler-remote-state-change` | Loading, error, pending, paging, or empty state changes. | Use `scheduler.refreshVisibleRange()` to reload the current range and `scheduler.loadMoreResources()` when paged resources report `hasMoreResources`. --- # Scheduling Rules URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/ Source: src/content/docs/guides/event-scheduler/scheduling-rules/index.mdx Description: Understand how Event Scheduler combines availability, calendars, and conflict policies into scheduling rules. Event Scheduler separates the source of a scheduling rule from the way that rule is enforced. Availability and calendars describe when a resource can be used. Conflict settings decide whether a violation is shown as a warning, ignored, confirmed, or rejected. This matters because closed time is not a single setting. A lunch break, a public holiday, an operating calendar, and a one-off shift extension all participate in the same conflict model, but they do not have the same precedence. ## Start With The Outcome Most applications choose one of these setups: | Goal | Configuration | | :-- | :-- | | Show conflicts but allow the user to save | `conflicts: { enabled: true, policy: 'mark' }` | | Block every detected conflict | `conflicts: { enabled: true, policy: 'prevent' }` | | Block only selected conflict types | `conflicts: { enabled: true, policy: 'mark', rules: { ... } }` | | Turn conflict detection off | `conflicts: { enabled: false }` | Use `rules` when different mistakes need different behavior: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', conflicts: { enabled: true, policy: 'mark', rules: { overlap: 'error', 'outside-availability': 'error', 'blocked-time': 'error', }, }, }; ``` In this example: - `overlap: 'error'` rejects an event when it overlaps another event in the same resource row. - `'outside-availability': 'error'` rejects an event when it is outside the resource's working time. - `'blocked-time': 'error'` rejects an event when it touches a blocked, holiday, or break window. `error` means the local scheduler mutation is not committed for the changed event. Create, move, resize, edit, paste, template, recurrence, and bulk changes are checked against the projected next schedule before the event list is updated. Existing unrelated conflicts can remain on screen; they do not block an unrelated edit unless the edited event is part of a blocking conflict. If you only want to display a conflict, use `warning`. If you want to hide a conflict from scheduler results, use `ignore`. If you want to require a custom confirmation flow, use `confirm`. ## Rule Names The three most common rule names answer different user questions: | Rule | User-facing meaning | Triggered by | Typical setting | | :-- | :-- | :-- | :-- | | `overlap` | "This booking collides with another booking." | Two visible event segments overlap on the same date. By default this is checked within the same resource. | Use `warning` when overlaps are allowed but should be visible. Use `error` for rooms, equipment, or people that cannot be double-booked. | | `outside-availability` | "This booking is outside allowed working time." | The event is not fully covered by a dated `kind: 'working'` availability entry, or by the resolved calendar when no dated working availability applies. | Use `error` when users must schedule only inside shifts, operating hours, or resource calendars. | | `blocked-time` | "This booking touches unavailable time." | The event overlaps a matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability entry. | Use `error` for PTO, maintenance, lunch breaks, holidays, freezes, and other hard closures. | The policy value attached to each rule controls enforcement: | Rule policy | What the user sees | Does it block the local change? | | :-- | :-- | :-- | | `warning` | Conflict styling and conflict data with warning severity. | No. | | `error` | Conflict styling and conflict data with error severity. | Yes, when the changed event is part of that conflict. | | `confirm` | Conflict data with confirm severity for custom confirmation handling. | No automatic blocking by the conflict engine. Use mutation events or `validateMutation` if your app should stop the change until the user confirms. | | `allow` | Conflict data stays visible as a warning. | No. | | `ignore` | Conflict is removed from visible conflict results. | No. | ## Rule Inputs The scheduler uses these inputs when it decides whether an event is valid for a resource and time range: | Input | Purpose | | :-- | :-- | | `eventSchedulerAvailability` | Dated working, blocked, holiday, or break intervals. Use it for operational data that happens on specific dates. | | `eventScheduler.calendars` | Recurring working days, holidays, working hours, and per-resource calendar resolution. Use it for the normal schedule baseline. | | `eventScheduler.conflicts` | Converts detected conflicts into warnings, blocking errors, confirmation states, or ignored results. | | `eventScheduler.nonWorkingTime` | Visual closed-cell styling for simple schedules. It does not create blocking conflicts by itself. | Use availability when the answer depends on a date, such as PTO, maintenance, a temporary room closure, or an extra working shift. Use calendars when the answer is recurring, such as weekday business hours or a weekend crew calendar. ## Availability Kinds `eventSchedulerAvailability` entries use `EventSchedulerAvailabilityKind`: | Kind | Meaning | Conflict behavior | | :-- | :-- | :-- | | `working` | A dated interval where matching resources are available. | If any matching `working` availability exists for the resource/date, the event must be fully covered by matching working intervals or it receives `outside-availability`. | | `blocked` | A dated unavailable interval, often for maintenance, holds, or PTO. | Events that overlap it receive `blocked-time`. | | `holiday` | A dated unavailable interval for holiday closure or non-operating time. | Events that overlap it receive `blocked-time`. | | `break` | A dated unavailable interval inside an otherwise working day. | Events that overlap it receive `blocked-time`. | Unavailable entries are checked by overlap. If an event touches a matching `blocked`, `holiday`, or `break` interval, the scheduler raises a `blocked-time` conflict for the overlapping part. Working entries are checked by coverage. If an event runs from `09:00` to `13:00`, the matching `working` availability must cover that whole range. A `09:00` to `12:00` working interval is not enough; the uncovered `12:00` to `13:00` portion creates an `outside-availability` conflict. ## Precedence For each visible event segment, Event Scheduler applies rules in this order: 1. Check matching unavailable availability entries. 2. Check matching dated `kind: 'working'` availability for that resource/date. 3. If no dated working availability matches, check the resolved calendar. 4. Apply conflict policy and per-type rules to decide whether the detected conflict is shown or blocks the mutation. Unavailable availability is always considered. Dated working availability is more specific than calendars for outside-availability checks. Calendars are the fallback only when no matching dated `kind: 'working'` availability exists for that resource/date. This means a dated working entry can narrow or extend the calendar for that date. If a room normally follows a `08:00` to `18:00` calendar but has a dated `working` entry from `10:00` to `14:00`, events for that room/date must be covered by `10:00` to `14:00`. The calendar does not fill the rest of the day for outside-availability checks. ## Resource Matching Availability can be global or resource-specific: ```ts grid.eventSchedulerAvailability = [ { id: 'all-hands-break', kind: 'break', title: 'Company break', startDateTime: '2026-06-08T12:00:00.000Z', endDateTime: '2026-06-08T13:00:00.000Z', }, { id: 'room-a-maintenance', resourceId: 'room-a', kind: 'blocked', title: 'Projector maintenance', startDateTime: '2026-06-08T15:00:00.000Z', endDateTime: '2026-06-08T16:00:00.000Z', }, { id: 'training-team-shift', resourceIds: ['trainer-a', 'trainer-b'], kind: 'working', title: 'Training shift', startDateTime: '2026-06-08T10:00:00.000Z', endDateTime: '2026-06-08T18:00:00.000Z', }, ]; ``` An availability entry without `resourceId` or `resourceIds` applies globally. An entry with `resourceId` applies to that one resource. An entry with `resourceIds` applies to any listed resource. Calendars resolve per resource in this order: 1. `resource.calendarId` 2. `eventScheduler.calendars.resourceCalendarId(resource)` 3. `eventScheduler.calendars.primaryCalendarId` If calendar resolution finds no calendar, calendar-based `outside-availability` checks are skipped for that resource/date. Availability rules can still create conflicts. ## Conflict Outcomes Rule detection and mutation blocking are separate steps. The scheduler first creates conflict records. Each conflict has a `type`, `policy`, `severity`, `message`, related event ids, related segment ids, and date/time metadata. Then the configured policy decides whether that conflict is just reported or whether it prevents a local mutation. `blocked-time` is raised when an event overlaps matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability. The conflict includes the event id, segment id, resource id when available, date, overlapping time range, and the availability id. `outside-availability` is raised when an event is outside allowed working time. This can come from dated `kind: 'working'` availability or from the resolved calendar fallback. `overlap` is raised when two events overlap on the same date. With the default `scope: 'same-resource'`, the events must also share the same resource. With `scope: 'global'`, overlaps are detected across resources. By default, conflict behavior depends on `eventScheduler.conflicts.policy`: | Policy | Default result | | :-- | :-- | | `mark` | Conflicts are warnings. Mutations are allowed unless a rule overrides the type. | | `prevent` | Conflicts are blocking errors by default. | | `allow` | Conflicts are ignored unless a rule overrides the type. | Use `rules` when some conflict types should be stricter than others: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', conflicts: { enabled: true, policy: 'mark', rules: { 'outside-availability': 'error', 'blocked-time': 'error', overlap: 'warning', }, }, }; ``` With this configuration, outside availability and blocked time reject create, move, resize, and edit mutations. Overlaps remain visible warnings. You can customize conflict text with `messages`: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', conflicts: { enabled: true, policy: 'mark', rules: { 'blocked-time': 'error', }, messages: { 'blocked-time': 'This slot is closed. Choose another time.', }, }, }; ``` ## Custom Event Prevention Conflict rules are the declarative way to stop known scheduler violations. For product-specific checks that do not fit calendars, availability, or built-in conflict types, use the scheduler mutation lifecycle. The main event-based prevention point is `event-scheduler-before-event-change`. It fires before the local event array is updated. Call `preventDefault()` to cancel the pending create, move, resize, edit, delete, paste, template, recurrence, or bulk mutation: ```ts grid.addEventListener('event-scheduler-before-event-change', (event) => { if (event.detail.action === 'move' && event.detail.event?.status === 'completed') { event.preventDefault(); } }); ``` Use `validateMutation` when you prefer a config-level hook instead of a DOM event. Return `false` or a string message to reject the same pending mutation: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', validateMutation: ({ changes }) => { if (changes?.resourceId === 'locked-room') { return 'That room is locked.'; } }, }; ``` Do not use final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, or `event-scheduler-event-deleted` for prevention. Those fire after the scheduler has accepted the change and are meant for persistence and state sync. For the full local mutation lifecycle, cancelable before events, permission hooks, and persistence events, see [Editing and Interaction](../event-editing/). ## Calendar Fallback Calendars are the normal baseline when no dated working availability exists: ```ts import { DEFAULT_CALENDAR, type CalendarEntity } from '@revolist/revogrid-enterprise'; const weekdayCalendar: CalendarEntity = { ...DEFAULT_CALENDAR, id: 'weekday', name: 'Weekday office hours', workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '18:00' }, holidays: ['2026-06-19'], }; grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', calendars: { primaryCalendarId: 'weekday', calendars: [weekdayCalendar], }, conflicts: { enabled: true, rules: { 'outside-availability': 'error' }, }, }; ``` With no matching `kind: 'working'` availability for a resource/date, the calendar decides whether the event is inside working time. An event on a holiday, a closed weekday, or outside `workingHours` receives `outside-availability`. ## Dated Working Overrides Calendar Hours A dated working entry becomes the outside-availability rule for its matching resource/date: ```ts grid.eventSchedulerAvailability = [ { id: 'room-a-short-day', resourceId: 'room-a', kind: 'working', title: 'Short operating day', startDateTime: '2026-06-08T10:00:00.000Z', endDateTime: '2026-06-08T14:00:00.000Z', }, ]; ``` If `room-a` also has a calendar with `08:00` to `18:00` working hours, the dated working entry wins for June 8, 2026. A `09:00` to `10:00` event and a `14:00` to `15:00` event both receive `outside-availability` because the event is not fully covered by the dated working interval. The calendar is not used to fill those hours. ## Lunch Breaks and Blocked Time Breaks are unavailable windows, not holes in working coverage: ```ts grid.eventSchedulerAvailability = [ { id: 'nurse-a-shift', resourceId: 'nurse-a', kind: 'working', title: 'Day shift', startDateTime: '2026-06-08T08:00:00.000Z', endDateTime: '2026-06-08T16:00:00.000Z', }, { id: 'nurse-a-lunch', resourceId: 'nurse-a', kind: 'break', title: 'Lunch', startDateTime: '2026-06-08T12:00:00.000Z', endDateTime: '2026-06-08T12:30:00.000Z', }, ]; ``` An event from `11:30` to `12:30` is covered by the working shift, but it overlaps the lunch break. The result is `blocked-time`, not `outside-availability`. ## Holidays and Maintenance Use `holiday` and `blocked` for unavailable windows that should conflict even when the calendar or working availability would otherwise allow the event: ```ts grid.eventSchedulerAvailability = [ { id: 'clinic-holiday', kind: 'holiday', title: 'Clinic closed', startDateTime: '2026-06-19T00:00:00.000Z', endDateTime: '2026-06-20T00:00:00.000Z', }, { id: 'room-b-maintenance', resourceId: 'room-b', kind: 'blocked', title: 'Equipment maintenance', reason: 'Calibration', startDateTime: '2026-06-08T13:00:00.000Z', endDateTime: '2026-06-08T15:00:00.000Z', }, ]; ``` The holiday applies globally because it has no resource id. The maintenance entry applies only to `room-b`. Events that overlap either entry receive `blocked-time`. ## Split Working Coverage Multiple working entries can combine to cover one event, but there cannot be a gap: ```ts grid.eventSchedulerAvailability = [ { id: 'room-c-morning', resourceId: 'room-c', kind: 'working', title: 'Morning setup', startDateTime: '2026-06-08T08:00:00.000Z', endDateTime: '2026-06-08T12:00:00.000Z', }, { id: 'room-c-afternoon', resourceId: 'room-c', kind: 'working', title: 'Afternoon setup', startDateTime: '2026-06-08T13:00:00.000Z', endDateTime: '2026-06-08T17:00:00.000Z', }, ]; ``` An event from `09:00` to `11:00` is covered. An event from `11:00` to `14:00` is not covered because `12:00` to `13:00` is a gap between working intervals. The scheduler raises `outside-availability` for that event. If you want the gap to be visibly unavailable and report a more specific conflict, add a `kind: 'break'` entry for the gap. The event will then receive `blocked-time` for the break overlap. ## Visual Non-Working Time `nonWorkingTime` is useful for simple visual backgrounds: ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', nonWorkingTime: { enabled: true, workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '18:00' }, className: 'closed-cell', }, }; ``` This marks closed cells, but it is not the rule source for blocking conflicts. Use `eventScheduler.calendars` or `eventSchedulerAvailability` when outside-working-time edits must produce `outside-availability`, then configure `eventScheduler.conflicts` to decide whether that conflict blocks. --- # Availability URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/availability/ Source: src/content/docs/guides/event-scheduler/scheduling-rules/availability.mdx Description: Show working time, blocked time, breaks, holidays, and resource-specific availability in Event Scheduler. Availability entries describe time ranges that are working, blocked, break, or holiday periods. They can apply globally or to specific resources. For the full precedence model between availability, calendars, and conflict policies, see the [Scheduling Rules overview](../). ```ts grid.eventSchedulerAvailability = [ { id: 'business-hours', kind: 'working', title: 'Business hours', startDateTime: '2026-06-08T08:00:00.000Z', endDateTime: '2026-06-08T18:00:00.000Z', }, { id: 'room-a-maintenance', resourceId: 'room-a', kind: 'blocked', title: 'Maintenance', reason: 'Projector replacement', startDateTime: '2026-06-08T13:00:00.000Z', endDateTime: '2026-06-08T15:00:00.000Z', }, ]; grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', nonWorkingTime: { enabled: true, workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '19:00' }, className: 'outside-shift-hours', }, }; ``` Use `nonWorkingTime.workingHours` when the whole scheduler shares one operating window. Slots outside the configured windows are marked as closed/outside working hours and receive the scheduler non-working background. They also expose `data-event-scheduler-non-working="true"` and `data-event-scheduler-availability-reason="Outside working hours"` so apps can inspect or customize the cells without replacing the renderer. Use named calendars when working hours differ by calendar or resource: ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', calendars: { primaryCalendarId: 'hospital-weekday', className: 'outside-shift-hours', calendars: [{ id: 'hospital-weekday', name: 'Hospital weekday', timeZone: 'UTC', workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '19:00' }, holidays: [], hoursPerDay: 8, }], }, }; ``` ## Availability and Calendars `eventSchedulerAvailability` and `eventScheduler.calendars` can be used together. They are resolved with these rules: - `kind: 'blocked'`, `kind: 'holiday'`, and `kind: 'break'` entries are unavailable windows. Events that overlap them receive `blocked-time` conflicts. - `kind: 'working'` entries are dated working windows. If matching working availability exists for a resource on a date, the event must be fully covered by those working intervals or it receives an `outside-availability` conflict. - Calendars are the fallback for `outside-availability` checks when no matching dated `kind: 'working'` availability exists for that resource/date. Use calendars for the recurring baseline, such as weekday hours, weekend crews, and resource calendars. Use availability for dated changes, such as extra opening hours, PTO, maintenance, one-off breaks, or temporary closures. Timeline cells expose availability data attributes so apps can style or inspect availability state without replacing the scheduler renderer. --- # Calendars URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/calendars/ Source: src/content/docs/guides/event-scheduler/scheduling-rules/calendars.mdx Description: Define working days, holidays, working hours, and per-resource calendars for Event Scheduler. Calendars describe when work is normally allowed. Event Scheduler uses them to mark closed cells, validate drag/create targets, skip non-working days during rollover, create outside-availability conflicts, and resolve the calendar that applies to a resource. Use calendars for repeating business rules such as weekday schedules, weekend crews, holiday closures, and resource-specific operating hours. Use availability entries for dated working windows or exceptions such as one-off maintenance, PTO, blocked rooms, or breaks. For detailed calendar-vs-availability precedence, including how dated working windows override calendar fallback checks, see the [Scheduling Rules overview](../). Calendars do not automatically reject every edit outside working hours. They provide the working-hours model and produce `outside-availability` conflicts. To make those conflicts block create, move, resize, and edit mutations, configure the conflict rule as an error: ```ts grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', calendars: { primaryCalendarId: 'weekday', calendars: [weekdayCalendar], }, conflicts: { enabled: true, rules: { 'outside-availability': 'error' }, }, }; ``` ## Calendar Shape Event Scheduler uses the same `CalendarEntity` shape as Gantt, so calendar definitions can be shared between planning views: ```ts import { DEFAULT_CALENDAR, EventSchedulerPlugin, type CalendarEntity, } from '@revolist/revogrid-enterprise'; const weekdayCalendar: CalendarEntity = { ...DEFAULT_CALENDAR, id: 'weekday', name: 'Weekday coverage', timeZone: 'UTC', workingDays: [1, 2, 3, 4, 5], holidays: ['2026-06-19'], hoursPerDay: 8, workingHours: { start: '08:00', end: '18:00' }, }; grid.plugins = [EventSchedulerPlugin]; grid.eventScheduler = { view: 'week', weekStartDate: '2026-06-08', calendars: { primaryCalendarId: 'weekday', calendars: [weekdayCalendar], }, }; ``` `DEFAULT_CALENDAR` is a Monday-Friday UTC calendar with no holidays and `hoursPerDay: 8`. Spread it when you want the standard defaults and only need to change the id, label, hours, or holidays. Calendar `workingDays` use ISO weekday numbers: Monday is `1` and Sunday is `7`. The older `nonWorkingTime.workingDays` option uses JavaScript day indexes, where Sunday is `0` and Saturday is `6`. ## Configuring Scheduler Calendars Add calendars through `eventScheduler.calendars`: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', timeRange: { start: '06:00', end: '22:00' }, calendars: { enabled: true, primaryCalendarId: 1001, className: 'scheduler-closed-cell', calendars: [ { ...DEFAULT_CALENDAR, id: 1001, name: 'Hospital weekday calendar', workingHours: { start: '08:00', end: '18:00' }, }, { ...DEFAULT_CALENDAR, id: 1002, name: 'Weekend coverage', workingDays: [5, 6, 7], workingHours: { start: '10:00', end: '20:00' }, }, ], }, }; ``` Calendar ids can be strings or numbers. Internally the scheduler normalizes ids for lookup, so `primaryCalendarId`, resource `calendarId`, and entries in the `calendars` array should refer to the same logical id. ## Per-Resource Calendars Each resource can point at its own calendar: ```ts grid.eventSchedulerResources = [ { id: 'alex', name: 'Alex Kim', role: 'RN', calendarId: 1001 }, { id: 'weekend-team', name: 'Weekend team', role: 'RN', calendarId: 1002 }, ]; ``` The resolver order is: 1. `resource.calendarId` 2. `calendars.resourceCalendarId(resource)` 3. `calendars.primaryCalendarId` Use `resourceCalendarId` when the calendar comes from metadata or an external rules table: ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', calendars: { primaryCalendarId: 'weekday', calendars, resourceCalendarId: (resource) => resource.metadata?.employmentType === 'weekend' ? 'weekend' : undefined, }, }; ``` ## What Calendars Affect Calendars are applied during projection and interaction: - Closed days and holidays receive the calendar closed state. - Slots outside `workingHours` are marked as outside working time. - Events placed outside the resolved calendar produce an `outside-availability` conflict. - Drag, resize, and create previews use the configured calendar when finding valid target dates. - Moving an event past the bottom of a day can roll to the next schedulable day instead of stopping on a closed weekend or holiday. - Conflict and permission hooks still run after calendar checks; calendars do not bypass app-level validation. - Blocking is controlled by `eventScheduler.conflicts`, usually with `rules: { 'outside-availability': 'error' }` or `policy: 'prevent'`. Calendar cells expose the same availability-style state used by the scheduler renderer. Use `className` on the calendars config for product-specific closed-cell styling: ```css .scheduler-closed-cell { background: #f4f6f8; color: #8b929c; } ``` ## Working Hours `workingHours` can be a single range or multiple ranges: ```ts const splitShiftCalendar: CalendarEntity = { ...DEFAULT_CALENDAR, id: 'split-shift', name: 'Split shift', workingHours: [ { start: '07:00', end: '11:00' }, { start: '15:00', end: '21:00' }, ], }; ``` The scheduler treats a slot as working when it overlaps at least one configured working-hours range. If `workingHours` is omitted, every visible time slot on a working day is treated as working. An event is considered outside availability when its visible segment is not covered by a working calendar or by matching `eventSchedulerAvailability` entries. With the default conflict policy, that state is shown as a warning. With `rules: { 'outside-availability': 'error' }`, the scheduler rejects the mutation before it updates `eventSchedulerEvents`. `nonWorkingTime.workingHours` is different: it is a visual fallback for closed cells. Use it for simple background styling when you do not need calendar/resource rules. Use `calendars` or `eventSchedulerAvailability` when outside-working-hours edits must participate in conflict detection. ## Calendars vs Availability Calendars are recurring operating rules. Availability is dated operational data. They are not mutually exclusive: the scheduler can use a calendar as the baseline and dated availability as the more specific rule for a particular resource/date. Use calendars for: - Standard weekday business hours - Weekend-only teams - Region-specific public holidays - Resource calendars copied from workforce or Gantt planning data Use availability for: - Extra dated working windows - One-time room maintenance - PTO or sick leave - Lunch breaks that vary by date - Temporary blocked equipment When both are present, availability is evaluated first for visible slot state. Unavailable entries win in this order: `kind: 'holiday'`, then `kind: 'blocked'`, then `kind: 'break'`. If any matching `kind: 'working'` availability exists for the resource/date, the slot is open only when it overlaps one of those dated working intervals; otherwise it is treated as closed for that date. If no matching dated working availability exists, the scheduler falls back to the resolved calendar. The same precedence applies to conflicts. If dated availability entries define `kind: 'working'` for a resource/date and an event is not fully covered by those working intervals, the scheduler raises `outside-availability` from availability instead of checking the calendar. If no dated working availability applies, the calendar can raise `outside-availability`. Availability entries with `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` raise `blocked-time` when events overlap them. Both conflict types can be made blocking from the conflicts configuration. ## Demo The `event-scheduler-shift-week` demo includes a calendar selector with weekday, open, and training calendars. Switch between them to see closed hours, closed days, and holiday behavior update without changing the event source. --- # Conflicts URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/conflicts/ Source: src/content/docs/guides/event-scheduler/scheduling-rules/conflicts.mdx Description: Configure warning and blocking conflict rules for overlapping scheduler events and invalid mutations. Conflict detection can mark overlaps, warn users, or block changes through the mutation lifecycle. This is also how you forbid events outside working hours: configure working time with `calendars` or `eventSchedulerAvailability`, then make the resulting `outside-availability` conflict blocking. For a detailed explanation of how availability and calendar inputs become `outside-availability` and `blocked-time`, see the [Scheduling Rules overview](../). ```ts grid.eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', conflicts: { enabled: true, policy: 'mark', scope: 'same-resource', minDurationMinutes: 30, rules: { overlap: 'error', 'outside-availability': 'error', 'blocked-time': 'error', }, }, validateMutation: (detail) => { const blocking = detail.conflicts?.some((conflict) => conflict.severity === 'error'); if (blocking) { return 'This change creates a blocking scheduling conflict.'; } }, }; ``` `policy` sets the default behavior for all conflict types: | Policy | Behavior | | :-- | :-- | | `mark` | Show conflicts as warnings and allow the mutation unless a specific rule is blocking. | | `prevent` | Treat conflicts as blocking errors by default. | | `allow` | Ignore conflicts unless a specific rule overrides the type. | `rules` override the default policy per conflict type. Use `error` to reject the mutation, `warning` to mark it but allow it, `confirm` when your product wants a confirmation flow, and `ignore` to hide the conflict. Common blocking rules: ```ts conflicts: { enabled: true, policy: 'mark', rules: { overlap: 'error', 'outside-availability': 'error', 'blocked-time': 'error', 'invalid-duration': 'error', }, } ``` `outside-availability` is raised when an event falls outside calendar working days/hours or outside explicit `kind: 'working'` availability intervals. `blocked-time` is raised when an event overlaps explicit blocked, holiday, or break availability. `nonWorkingTime` alone is visual and does not create blocking conflicts. ## Preventing Mutations Conflict rules are the declarative way to stop invalid changes. When a conflict resolves to `severity: 'error'`, the scheduler rejects the mutation before it updates `eventSchedulerEvents`. For business rules that need code, use the same cancelable event pattern as RevoGrid editing. Listen to `event-scheduler-before-event-change` and call `preventDefault()` before the local commit: ```ts grid.addEventListener('event-scheduler-before-event-change', (event) => { const hasBlockingConflict = event.detail.conflicts?.some((conflict) => conflict.severity === 'error' && event.detail.eventId !== null && conflict.eventIds.includes(event.detail.eventId) ); if (hasBlockingConflict || event.detail.event?.locked) { event.preventDefault(); } }); ``` `validateMutation` is the config-level equivalent for synchronous validation. Return `false` or a string to reject the same pending mutation. ```ts const eventScheduler = { validateMutation: (detail) => { if (detail.changes.resourceId === 'locked-room') { return 'That room cannot accept bookings.'; } }, }; ``` Do not use `event-scheduler-event-changed` for prevention. `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the scheduler has accepted the mutation; use those events to persist or sync the emitted `detail.events` array. Listen to conflict updates for side panels, badges, filters, or exports. ```ts grid.addEventListener('event-scheduler-conflicts-updated', (event) => { renderConflictPanel(event.detail.conflicts); }); ``` Event blocks expose conflict classes and data attributes for custom styles. --- # Templates and Recurrence URL: https://pro.rv-grid.com/guides/event-scheduler/templates-recurrence/ Source: src/content/docs/guides/event-scheduler/templates-recurrence.mdx Description: Create scheduler events from reusable templates and generate repeating shifts or bookings. Templates define reusable event shapes. The plugin exposes helper methods for single events and recurring series. ```ts grid.eventSchedulerTemplates = [ { id: 'morning-shift', title: 'Morning shift', durationMinutes: 480, status: 'planned', type: 'shift', color: '#2563eb', }, ]; const scheduler = (await grid.getPlugins()).find( (plugin) => plugin.constructor?.name === 'EventSchedulerPlugin', ); scheduler.createRecurringEvents({ templateId: 'morning-shift', resourceId: 'emp-alex', startDateTime: '2026-06-08T08:00:00.000Z', recurrence: { frequency: 'weekly', weekdays: [1, 3, 5], count: 6, }, seriesId: 'alex-morning-series', }); ``` Generated events keep recurrence metadata so applications can present edit-series or delete-series workflows. ## Copy, Paste, Duplicate, and Bulk Helpers Get the plugin instance to run imperative scheduler helpers: ```ts const plugins = await grid.getPlugins(); const scheduler = plugins.find( (plugin) => plugin.constructor?.name === 'EventSchedulerPlugin', ); ``` Create one event from a template: ```ts scheduler?.createEventFromTemplate({ templateId: 'morning-shift', startDateTime: '2026-06-08T08:00:00.000Z', resourceId: 'team-a', }); ``` Copy a day or pattern into another date range: ```ts scheduler?.duplicateScheduleRange({ sourceStartDateTime: '2026-06-08T00:00:00.000Z', sourceEndDateTime: '2026-06-09T00:00:00.000Z', targetStartDateTime: '2026-06-09T00:00:00.000Z', }); ``` Selection helpers support copy, paste, duplicate, bulk delete, and bulk status changes: ```ts scheduler?.setSelectedEventIds(['shift-a', 'shift-b']); scheduler?.copySelectedEvents(); scheduler?.pasteCopiedEvents({ startDateTime: '2026-06-10T08:00:00.000Z' }); scheduler?.bulkUpdateSelectedEventStatus('confirmed'); ``` All helper actions run through the same permissions, conflict, before-change, local mutation, event emission, and remote mutation pipeline as pointer and editor interactions. --- # Timeline Views URL: https://pro.rv-grid.com/guides/event-scheduler/timeline-views/ Source: src/content/docs/guides/event-scheduler/timeline-views.mdx Description: Configure day, week, and custom range scheduler views with week and resource timeline layouts. Event Scheduler supports calendar-style `day`, `week`, and `month` views plus the horizontal `resourceTimeline` layout. Use this page to choose the projection and size the generated scheduler columns. For the full grid bootstrap, start with [Getting Started](../introduction/). ## Framework bindings React, Vue, and Angular wrappers bind the same grid props. In React, keep the plugin array stable and pass empty host rows and columns: ```tsx import { useMemo } from 'react'; import { RevoGrid } from '@revolist/react-datagrid'; import { EventSchedulerPlugin } from '@revolist/revogrid-enterprise'; export function ResourceTimeline() { const plugins = useMemo(() => [EventSchedulerPlugin], []); return ( ); } ``` This example assumes `eventScheduler`, `events`, and `resources` are the same constants you would pass in Vanilla TS. Vue and Angular use the same prop names through their normal bindings. In Vue, use property bindings for direct grid properties, for example `:event-scheduler.prop="eventScheduler"`, `:event-scheduler-events.prop="events"`, and `:event-scheduler-resources.prop="resources"`. In Angular, use `[eventScheduler]="eventScheduler"`, `[eventSchedulerEvents]="events"`, and `[eventSchedulerResources]="resources"`. ## Choose a view Use `day`, `week`, and `month` when time should run vertically and days should be generated as columns. These views work well for shift calendars, appointment books, and weekly planning screens. Use `resourceTimeline` when resources should be generated as rows and time slots should run horizontally. This layout is better for room booking, equipment planning, staff coverage, machine schedules, and custom multi-day resource timelines. The main range controls are: - `dateRange` sets the explicit visible date range for resource timelines and custom ranges. - `visibleDays` filters generated weekdays, such as Monday through Friday only. - `slotMinutes` controls visible slot granularity. - `snapMinutes` controls drag, resize, paste, and create snapping. It defaults to `slotMinutes`. - `timeRange` limits the visible hours within each day. The main layout controls are: - `timeColumnSize` controls the pinned time column in `day`, `week`, and `month` views. - `dayColumnSize` controls day columns in `day`, `week`, and `month` views. - `resourceColumnSize` controls the pinned resource column in `resourceTimeline`. - `timelineColumnSize` controls each horizontal timeline slot in `resourceTimeline`. - `columnGrouping` enables or disables grouped scheduler headers. - `eventLayout`, `maxStackedEvents`, and `compactThreshold` control overlapping event presentation. ```ts const resourceDayTimeline = { view: 'resourceTimeline', weekStartDate: '2026-06-08', dateRange: { start: '2026-06-08', end: '2026-06-08' }, slotMinutes: 60, timeRange: { start: '6 AM', end: '10 PM' }, resourceColumnSize: 220, timelineColumnSize: 120, }; const resourceWeekTimeline = { ...resourceDayTimeline, dateRange: { start: '2026-06-08', end: '2026-06-14' }, customization: { columnSizes: { resourceTimeline: { resourceColumnSize: 240, timelineColumnSize: 132, }, }, headers: { timelineTemplate: (h, context) => h('span', { class: 'scheduler-slot-header' }, context.defaultLabel), timelineProperties: (context) => ({ 'data-scheduler-date': context.date, }), }, }, }; const customRange = { ...resourceDayTimeline, dateRange: { start: '2026-06-08', end: '2026-06-10' }, slotMinutes: 30, timelineColumnSize: 132, }; ``` For a practical vertical week setup, continue to [Week View](../week-view/). For raw configuration types, see the [Event Scheduler API reference](/api/event-scheduler/). ## Demo Sources There are two Event Scheduler implementation families in the examples: | Demo id | Implementation | | :-- | :-- | | `event-scheduler-shift-week` | Shift planner with day, week, month, and resource views. | | `employee-shift-planner` | Uses the shift-week implementation. | | `event-scheduler-resource-timeline` | Resource timeline with grouping, filters, coverage, utilization, unassigned demand, templates, and remote mode in Vanilla TS. | | `event-staff-scheduler` | Uses the resource-timeline implementation. | | `room-booking-scheduler` | Uses the resource-timeline implementation. | | `equipment-machine-scheduler` | Uses the resource-timeline implementation. | | `developer-sprint-scheduler` | Uses the resource-timeline implementation. | Useful source references: - `examples/components/src/components/event-scheduler/shift-week/data.ts` - `examples/components/src/components/event-scheduler/shift-week/index.ts` - `examples/components/src/components/event-scheduler/resource-timeline/data.ts` - `examples/components/src/components/event-scheduler/resource-timeline/index.ts` ## Column Widths Scheduler column widths are controlled by the scheduler config and are applied when the view is projected into RevoGrid columns: - `timeColumnSize` controls the pinned time column in day, week, and month views. - `dayColumnSize` controls day columns in day, week, and month views. - `resourceColumnSize` controls the pinned resource column in resource timeline views. - `timelineColumnSize` controls each horizontal timeline slot in resource timeline day, week, and custom ranges. Use wider `timelineColumnSize` values for custom ranges with fine-grained slots, such as 15-minute or 30-minute columns. Wider timeline slots improve header and event-label readability and naturally create more horizontal scroll space. Use `customization.columnSizes` when the same scheduler instance switches modes and each mode needs a different width profile. Values under `customization.columnSizes[view]` take precedence over the legacy top-level width options: ```ts const eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', customization: { columnSizes: { week: { timeColumnSize: 96, dayColumnSize: 220, }, resourceTimeline: { resourceColumnSize: 260, timelineColumnSize: 144, }, }, }, }; ``` ## Grouped Headers Scheduler views use grouped headers by default so day, week, month, and resource timeline ranges can show broader date context above the leaf columns. Disable them when the extra header row is not useful for a compact embedded scheduler: ```ts const eventScheduler = { view: 'week', weekStartDate: '2026-06-08', columnGrouping: false, }; ``` When `columnGrouping` is `false`, the scheduler returns flat RevoGrid columns. Day/week/month views show only day leaf headers, and resource timeline views show only timeline slot leaf headers after the pinned resource column. Header customization hooks map to RevoGrid column templates and properties. Returning `null` or `undefined` from a template keeps the built-in header content: ```ts const eventScheduler = { view: 'resourceTimeline', weekStartDate: '2026-06-08', customization: { headers: { groupTemplate: (h, context) => h('strong', { class: 'scheduler-date-group' }, context.defaultLabel), timelineProperties: (context) => ({ class: 'scheduler-slot-header', 'data-slot-start': context.startDateTime, }), }, }, }; ``` ## Troubleshooting - No rows or events render: check that `grid.plugins` includes `EventSchedulerPlugin`, `eventScheduler` is set, `eventSchedulerEvents` is populated, and each event's `resourceId` matches an item in `eventSchedulerResources` for resource timelines. - Events render but cannot be created, moved, or resized: enable `editable` and the needed `allowCreate`, `allowMove`, or `allowResize` flags. Permission callbacks, locked resources, and locked event statuses can still block a specific action. - Custom `source` or `columns` disappear: this is expected while `eventScheduler` is active. The plugin projects scheduler rows and columns into RevoGrid and restores the host grid state when the scheduler config is removed. - Range or slot labels look wrong: verify ISO `YYYY-MM-DD` dates, `dateRange`, `timeRange`, `slotMinutes`, and any `timelineHeaderFormatter` or `customization.headers` hooks. --- # Week View URL: https://pro.rv-grid.com/guides/event-scheduler/week-view/ Source: src/content/docs/guides/event-scheduler/week-view.mdx Description: Build a practical calendar-style week scheduler for shifts, appointments, and short-range planning. Use `view: 'week'` when users think in days and hours: employee shifts, appointment books, clinic rosters, classroom timetables, service windows, and team calendars. Time runs vertically, days are generated as columns, and event duration is shown by event height. This page focuses on a vertical week scheduler. For the general setup, props, and data model, start with [Getting Started](../introduction/). For choosing between vertical time and horizontal resource rows, see [Vertical vs Horizontal Time](../introduction/vertical-vs-horizontal/). For `day`, `month`, `resourceTimeline`, column widths, and grouped headers, see [Timeline Views](../timeline-views/). ## Build a Week Planner 1. Configure the visible week. ```ts import { EventSchedulerPlugin, type EventSchedulerConfig, type EventSchedulerEventEntity, type EventSchedulerResourceEntity, } from '@revolist/revogrid-enterprise'; const eventScheduler: EventSchedulerConfig = { view: 'week', weekStartDate: '2026-06-08', weekStartsOn: 1, locale: 'en-US', timeZone: 'UTC', visibleDays: [1, 2, 3, 4, 5], slotMinutes: 30, snapMinutes: 15, timeRange: { start: '06:00', end: '22:00' }, timeColumnSize: 88, dayColumnSize: 190, editable: true, allowCreate: true, allowMove: true, allowResize: true, allowDelete: true, }; ``` `slotMinutes` controls visible row granularity. Use values such as `5`, `10`, `15`, `30`, or `60` minutes depending on the planning detail your product needs. `snapMinutes` controls create, drag, resize, paste, and keyboard snapping and defaults to `slotMinutes`. Use a smaller snap interval when the grid should display 30-minute rows but allow 15-minute edits. `timeRange` controls the visible start/end time, so night hours can be hidden instead of rendered. `rowSize` controls density for compact, dense, or comfortable schedules, and `timeLabelFormatter` / `timeLabelTemplate` can replace the default `06:00` style labels. `locale` and `timeZone` control built-in day and range labels. Use `dayHeaderFormatter`, `timeLabelFormatter`, and `timelineHeaderFormatter` when your product needs a fully custom display format. 2. Provide the events and optional resources. ```ts const resources: EventSchedulerResourceEntity[] = [ { id: 'alex', name: 'Alex Kim', role: 'Coordinator', group: 'Front desk' }, { id: 'maria', name: 'Maria Cruz', role: 'Nurse', group: 'Clinical' }, ]; const events: EventSchedulerEventEntity[] = [ { id: 'shift-alex-mon', resourceId: 'alex', title: 'Front desk', startDateTime: '2026-06-08T08:00:00.000Z', endDateTime: '2026-06-08T14:00:00.000Z', status: 'confirmed', type: 'shift', }, { id: 'shift-maria-mon', resourceId: 'maria', title: 'Clinical floor', startDateTime: '2026-06-08T10:00:00.000Z', endDateTime: '2026-06-08T18:00:00.000Z', status: 'planned', type: 'shift', }, ]; ``` In week view, resources do not become rows; days and time slots define the grid. Resource ids still matter for ownership, permissions, conflicts, calendars, availability, filters, exports, and later switching the same data to `resourceTimeline`. 3. Attach the scheduler to an empty grid. ```ts const grid = document.querySelector('revo-grid')!; grid.plugins = [EventSchedulerPlugin]; grid.eventScheduler = eventScheduler; grid.eventSchedulerResources = resources; grid.eventSchedulerEvents = events; ``` ## Keep Edits Controlled The scheduler emits the next event array after accepted create, move, resize, edit, delete, template, paste, recurrence, and bulk actions. Replace `eventSchedulerEvents` with that array and persist it from your app. ```ts function syncEvents(event: CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>) { grid.eventSchedulerEvents = [...event.detail.events]; void saveSchedule(event.detail.events); } grid.addEventListener('event-scheduler-event-created', syncEvents as EventListener); grid.addEventListener('event-scheduler-event-changed', syncEvents as EventListener); grid.addEventListener('event-scheduler-event-deleted', syncEvents as EventListener); ``` Use `event-scheduler-before-event-change`, `validateMutation`, or permission hooks when your product needs to reject a pending change before it is committed. The full lifecycle is covered in [Editing and Interaction](../event-editing/). ## Block Invalid Hours To make a week planner practical, define working time with calendars or availability and make the resulting conflicts blocking. ```ts grid.eventScheduler = { ...eventScheduler, calendars: { primaryCalendarId: 'weekday', calendars: [{ id: 'weekday', name: 'Weekday shifts', timeZone: 'UTC', workingDays: [1, 2, 3, 4, 5], workingHours: { start: '08:00', end: '18:00' }, holidays: ['2026-06-19'], hoursPerDay: 8, }], }, conflicts: { enabled: true, policy: 'mark', rules: { 'outside-availability': 'error', 'blocked-time': 'error', overlap: 'warning', }, }, }; ``` Calendars are the recurring baseline. Use `eventSchedulerAvailability` for dated changes such as PTO, lunch breaks, holiday closures, maintenance, or temporary extra coverage. `nonWorkingTime` is useful for visual closed-cell styling, but it does not block edits by itself. The precedence model is covered in [Scheduling Rules](../scheduling-rules/). ## Switch the Visible Week Keep `weekStartDate`, `visibleDays`, and `timeRange` in application state when the host toolbar owns navigation. ```ts function setScheduler(next: Partial) { grid.eventScheduler = { ...grid.eventScheduler, ...next, }; } grid.addEventListener('event-scheduler-navigate-request', (event) => { const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail; setScheduler({ weekStartDate: resolveNextWeekStart(grid.eventScheduler.weekStartDate, action) }); }); grid.addEventListener('event-scheduler-view-request', (event) => { const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail; setScheduler({ view }); }); ``` Use `day` for a focused daily schedule, `week` for the main shift-planning workflow, and `month` for a higher-level calendar. Use `resourceTimeline` when resources need their own rows and users compare capacity horizontally. ## Production Checklist - Use ISO datetime strings and stable event ids. - Decide whether `weekStartsOn` and `visibleDays` follow locale, business rules, or user settings. - Keep `source` and `columns` empty while the scheduler owns the grid projection. - Enable only the edit permissions your persistence flow can handle. - Configure calendars or availability before turning outside-hours conflicts into blocking errors. - Add resources even in week view when conflicts, permissions, reporting, or future resource timelines need them. - Use the Employee Shift Planner demo as a focused week-view reference. ## Next Pages - [Timeline Views](../timeline-views/) covers all scheduler views, date ranges, column widths, framework bindings, grouped headers, and demo source locations. - [Editing and Interaction](../event-editing/) covers host-owned persistence, mutation events, validation, permissions, selection, clipboard, keyboard shortcuts, and editor replacement. - [Scheduling Rules](../scheduling-rules/) covers calendar, availability, non-working time, and conflict-rule precedence. - [Grouping](../grouping/) covers resource timelines, unassigned demand, coverage, utilization, filters, and saved views. - [Templates and Recurrence](../templates-recurrence/) covers template creation, recurrence, duplicate, paste, selection, and bulk helpers. - [Remote Data](../remote-data/) covers server-backed loading, optimistic saves, refresh, paging, and remote state events. - [Customization](../customization/) covers the full visual customization category. Start with [Labels and Formatters](../customization/labels-formatters/), [Event Rendering](../customization/events/), and [Layout, Headers, and Cells](../customization/layout/). - [Examples](../examples/) has compact weekly scheduler examples for custom event bars, day headers, current-time markers, closed hours, read-only mode, cancelable selection state, and themes. - [Export](../export/) covers export helpers and production readiness. - [Event Scheduler API reference](/api/event-scheduler/) lists raw config, hook, and event types. --- # Excel Export URL: https://pro.rv-grid.com/guides/excel-export/ Source: src/content/docs/guides/excel-export/index.mdx Description: Export XLSX files and import CSV data with RevoGrid Pro. RevoGrid Pro exports grid data to `.xlsx` workbooks through `ExportExcelPlugin`. The same plugin also keeps the historical CSV drag-and-drop import path, so existing integrations can keep using the `ExportExcelPlugin`, `grid.exportExcel`, and `export-excel` event names. Use this page for the quick setup. Provider adapters, cell formatting, CSV import, and migration details are split into focused pages linked below. ## Quick Setup Attach `ExportExcelPlugin` with your grid plugins: ```ts import { ExportExcelPlugin } from '@revolist/revogrid-pro'; grid.plugins = [ExportExcelPlugin]; ``` Trigger export through the event API: ```ts grid.dispatchEvent(new CustomEvent('export-excel', { detail: { workbookName: 'orders.xlsx', sheetName: 'Orders', }, })); ``` Or call the plugin instance directly: ```ts const plugins = await grid.getPlugins(); const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin); await excel?.export({ workbookName: 'orders.xlsx', sheetName: 'Orders', }); ``` ## Default Behavior - The bundled provider is `write-excel-file` and exports `.xlsx` files only. - Extensionless `workbookName` values receive `.xlsx`. - `sheetName` is normalized for Excel worksheet rules. - Export reads grid data stores, not rendered DOM rows, so virtualized offscreen rows are included. - Row order is pinned top rows, body rows, then pinned bottom rows. - Columns follow `providers.column.getColumns('all')`. - Grid-derived widths and freeze panes are inferred by default from column sizes, `colPinStart` columns, and pinned-top rows. ## Focused Guides - [Export Providers](./providers/) explains provider precedence, default `.xlsx` limits, `providerOptions`, `deriveGridOptions`, optional ExcelJS and SheetJS adapters, workbook-module adapters, and custom providers. - [Cell Formatting](./cell-formatting/) explains `column.excelExport`, formula/date/numeral/select helpers, grouped headers, cell merge export support, and the provider context matrix. - [CSV Import](./csv-import/) explains CSV-only drag/drop import, `allowDrag`, `allowedExtensions`, `skipColumnGeneration`, events, and plugin import helpers. - [Migrating to v2.0](/guides/migration-v2/) explains the SheetJS removal, CSV-only default import, app-owned XLSX import, and provider migration paths as a breaking change from `2.0.8`. ## Related Export Guides - [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) compares SheetJS, ExcelJS, read-excel-file, and write-excel-file for app-owned workbook workflows. - [Gantt Grid, Export, Rendering](/guides/gantt/features/grid-export-rendering/) covers Gantt task-row export mapping and reporting recipes. - [Pivot Export And State](/guides/pivot/concepts/export-state/) covers Pivot CSV, TSV, and state JSON helpers. - [Export Excel API](/api/export-excel/) is the generated API reference for the complete public type surface. --- # Cell Formatting URL: https://pro.rv-grid.com/guides/excel-export/cell-formatting/ Source: src/content/docs/guides/excel-export/cell-formatting.mdx Description: Format Excel export cells, formulas, column types, grouped headers, and merge spans. Use `column.excelExport` when workbook output needs values or styles that differ from the rendered grid. Export formatting is data-source based and runs during export only. ## Column Export Callbacks ```ts import type { ColumnRegular } from '@revolist/revogrid'; const columns: ColumnRegular[] = [ { prop: 'price', name: 'Price', excelExport: { columnProperties: ({ value }) => ({ value, fontWeight: 'bold', backgroundColor: 'E8EEF9', }), cellProperties: ({ value, rowType }) => ({ value, type: Number, format: '$#,##0.00', align: 'right', fontWeight: rowType === 'rowPinEnd' ? 'bold' : undefined, }), }, }, ]; ``` `columnProperties` formats the generated header cell. `cellProperties` formats body, pinned-top, and pinned-bottom cells. `cellProperties` receives: | Field | Meaning | | --- | --- | | `value` | Prepared export value after formula evaluation | | `rawValue` | Original source value for the column | | `model` | Original source row object | | `rowIndex` | Index inside its source row segment | | `rowType` | `rowPinStart`, `rgRow`, or `rowPinEnd` | | `column` | Current column definition | | `columnIndex` | Export column index | | `revogrid` | Grid element | | `providers` | Runtime plugin providers | Callbacks can return: - a primitive value to replace the exported value - a cell object with `value` and style fields - a style-only object, which keeps the prepared value - `undefined`, which keeps the default prepared cell ## Style Fields Common workbook style fields include: | Field | Example | | --- | --- | | `fontFamily`, `fontSize`, `fontWeight`, `fontStyle` | text styling | | `textColor`, `backgroundColor`, `fillPatternColor` | colors | | `borderColor`, `leftBorderColor`, `rightBorderColor`, `topBorderColor`, `bottomBorderColor` | borders | | `align`, `alignVertical`, `wrap`, `textRotation`, `indent` | alignment | | `format` | number/date format | | `height`, `columnSpan`, `rowSpan` | layout | The bundled `write-excel-file` provider accepts `#E8EEF9` and bare RGB hex values such as `E8EEF9` for known color fields. Bare colors are prefixed before the provider calls `write-excel-file`. Optional providers map this structural shape to their own workbook library. Unsupported fields may be ignored by a custom provider. ## Formula Export Formula cells are evaluated by default. To export workbook formulas instead of evaluated values, attach the formula helper: ```ts import { createFormulaExcelExportCellProperties } from '@revolist/revogrid-pro'; const formulaProperties = createFormulaExcelExportCellProperties({ includeEvaluatedResult: true, }); const columns = [ { prop: 'total', name: 'Total', excelExport: { cellProperties: formulaProperties, }, }, ]; ``` When `includeEvaluatedResult` is enabled, providers that support cached formula results can receive both the formula and the prepared result. ## External Column Type Helpers Date, numeral, and select column packages are optional app dependencies, so Pro exposes explicit Excel export helpers instead of importing those packages automatically: ```ts import { createDateExcelExportColumnOptions, createNumeralExcelExportColumnOptions, createSelectExcelExportColumnOptions, } from '@revolist/revogrid-pro'; const columns = [ { prop: 'createdAt', name: 'Created', columnType: 'date', excelExport: createDateExcelExportColumnOptions({ format: 'yyyy-mm-dd' }), }, { prop: 'amount', name: 'Amount', columnType: 'currency', excelExport: createNumeralExcelExportColumnOptions({ format: '$#,##0.00' }), }, { prop: 'status', name: 'Status', columnType: 'select', source: [ { value: 'open', label: 'Open' }, { value: 'closed', label: 'Closed' }, ], labelKey: 'label', valueKey: 'value', excelExport: createSelectExcelExportColumnOptions({ column: { prop: 'status', source: [ { value: 'open', label: 'Open' }, { value: 'closed', label: 'Closed' }, ], labelKey: 'label', valueKey: 'value', }, }), }, ]; ``` Date helpers convert valid date-like values to `Date` cells. Numeral helpers convert finite numbers and numeric strings to number cells. Select helpers export the user-facing label for stored values. ## Grouped Headers And Cell Merge Grouped column headers are applied automatically when the grid uses a grouped `columns` tree. Group header rows are inserted before the flattened leaf header row. Cell merge spans are applied automatically when the grid has `cellMerge` data. The export transform maps grid row and column segment coordinates into provider matrix coordinates, accounting for generated header rows. The built-in `write-excel-file` provider and ExcelJS adapter consume `columnSpan` and `rowSpan` values from `cellRows`. Custom providers can also read merge metadata from `context.exportMetadata.cellMerge`. ## Provider Context Custom providers and transformers receive a prepared `ExcelExportProviderContext`: | Field | Purpose | | --- | --- | | `cellRows` | Provider-ready matrix with header rows and formatted cells | | `rows` | Plain row objects keyed by column prop, with formulas evaluated | | `sourceRows` | Original row models with row index and row segment metadata | | `headerRowsCount` | Number of generated header rows before data rows in `cellRows` | | `gridDerivedOptions` | Width and freeze-pane hints inferred from current grid state | | `exportMetadata` | Feature metadata such as grouped headers and cell merge ranges | Use `cellRows` for formatted workbook output. Use `rows` for simple JSON-style providers that do not need formatting. ## Export Transformers Use `exportTransformers` for final app-owned changes to the provider context: ```ts grid.exportExcel = { exportTransformers: [ (context) => ({ exportMetadata: { generatedBy: 'orders-report', }, providerOptions: { hideGridLines: true, }, }), ], }; ``` Grid-level transformers run before per-export transformers: ```ts await excel.export({ exportTransformers: [ (context) => { context.cellRows[0][0] = { value: 'Exported Orders', fontWeight: 'bold' }; }, ], }); ``` --- # CSV Import URL: https://pro.rv-grid.com/guides/excel-export/csv-import/ Source: src/content/docs/guides/excel-export/csv-import.mdx Description: Import CSV files with ExportExcelPlugin drag/drop, events, and explicit import options. `ExportExcelPlugin` imports CSV files by default. `.xlsx` and `.xls` parsing is app-owned; use a parser such as SheetJS, ExcelJS, or read-excel-file and assign normalized rows to the grid yourself. For app-owned XLSX parsing choices, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/). ## Drag And Drop By default, users can drop a `.csv` file onto the grid: Control drag/drop import with `grid.exportExcel`: ```ts grid.exportExcel = { allowDrag: true, allowedExtensions: ['.csv'], }; ``` The same options are still accepted through legacy `additionalData.excel`, but new code should prefer `grid.exportExcel`. ## Import Options | Option | Default | Description | | --- | --- | --- | | `allowDrag` | `true` | Enables CSV drag/drop import unless the grid is readonly | | `allowedExtensions` | `['.csv']` | File extensions accepted by drag/drop, `importFile()`, `isCsv()`, and deprecated `isExcel()` | | `skipColumnGeneration` | `false` | Keeps current grid columns and maps CSV cells by current column order | When `skipColumnGeneration` is `false`, the first CSV row becomes generated grid columns: ```csv Name,Status Ada,Open Grace,Closed ``` The imported grid receives columns similar to: ```ts [ { prop: 0, name: 'Name' }, { prop: 1, name: 'Status' }, ] ``` When `skipColumnGeneration` is `true`, all CSV rows are imported as data and mapped to the existing export column order: ```ts grid.columns = [ { prop: 'name', name: 'Name' }, { prop: 'status', name: 'Status' }, ]; await excel.importFile(file, { skipColumnGeneration: true, }); ``` ## Plugin Methods Use `importFile()` when import is triggered by an app-owned upload control: ```ts const plugins = await grid.getPlugins(); const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin); await excel?.importFile(file, { allowedExtensions: ['.csv', '.txt'], skipColumnGeneration: true, }); ``` Use `isCsv()` to validate a file before import: ```ts if (excel?.isCsv(file, ['.csv'])) { await excel.importFile(file); } ``` `isExcel()` remains as a deprecated compatibility alias. It is CSV-only and returns `false` for `.xlsx` files unless the caller explicitly allows that extension. It does not parse workbook files. ## Import Events Before applying imported rows, the plugin dispatches CSV-oriented events: | Event | Detail | | --- | --- | | `excel-before-import` | `file`, `sheetName`, `csv`, and parsed `rows` | | `excel-before-set` | All import detail plus mapped `jsonData` | `sheetName` is a compatibility label and contains the filename. Prevent either event to cancel replacing the grid source: ```ts grid.addEventListener('excel-before-set', (event) => { const { jsonData } = event.detail; if (!jsonData.length) { event.preventDefault(); } }); ``` ## Parser Behavior The default importer reads files with `FileReader.readAsText` and parses CSV values. It handles BOMs, CRLF/LF line endings, quoted values, escaped quotes, empty cells, and trailing blank lines. Invalid file extensions are rejected before parsing. If the grid is readonly or `allowDrag` is `false`, drag/drop import does not activate. For `.xlsx` uploads, parse the workbook outside the plugin and assign normalized data: ```ts const { columns, rows } = await parseWorkbook(file); grid.columns = columns; grid.source = rows; ``` --- # Excel Import and Export Libraries URL: https://pro.rv-grid.com/guides/excel-export/excel-import-export-libraries/ Source: src/content/docs/guides/excel-export/excel-import-export-libraries.mdx Description: Compare SheetJS, ExcelJS, read-excel-file, and write-excel-file for XLSX import/export workflows with RevoGrid. RevoGrid Pro includes an Excel export plugin, but most production apps still need to choose how uploaded workbook files are parsed, validated, normalized, and streamed into the grid. Use this guide when your workflow is: 1. User uploads an Excel file. 2. Your app parses `.xlsx` data. 3. Your app converts the first sheet, selected sheet, or validated sheet into JSON. 4. RevoGrid receives normalized `columns` and `source` data. The bundled `ExportExcelPlugin` exports `.xlsx` files through `write-excel-file` and imports CSV files by default. XLSX import is app-owned: install the parser you want, parse the workbook, then assign normalized rows to RevoGrid. For plugin options, see the [Excel export guide](/guides/excel-export/). ## Quick Recommendation | Goal | Recommended library | Why | | --- | --- | --- | | Fast `.xlsx` upload to grid JSON | SheetJS | Broad format support, direct `sheet_to_json()` conversion, strong compatibility with real-world workbooks. | | Full workbook editing or styled report generation | ExcelJS | Good API for worksheets, rows, formatting, formulas, tables, merged cells, and generated reports. | | Simple validated upload forms | read-excel-file | Small API surface with schema-based parsing and validation. | | Default RevoGrid Pro `.xlsx` export | write-excel-file | Lightweight writer used by the bundled export provider. | | Huge browser imports | SheetJS or read-excel-file in a Web Worker | Keep parse work away from the UI thread and load rows into RevoGrid incrementally. | | Backend import/export processing | SheetJS or ExcelJS | Pick SheetJS for broad import compatibility; pick ExcelJS when workbook editing and formatting matter more. | ## SheetJS [SheetJS](https://docs.sheetjs.com/) is the strongest general-purpose choice for Excel upload workflows. It works in the browser and Node.js, reads workbook bytes from an `ArrayBuffer`, supports common spreadsheet formats, and converts worksheets directly into JSON. Use it when the import goal is "Excel file in, JSON rows out". ```sh pnpm add xlsx ``` ```ts import * as XLSX from 'xlsx'; import type { ColumnRegular, DataType } from '@revolist/revogrid'; export async function parseSheetJsFile(file: File): Promise<{ columns: ColumnRegular[]; rows: DataType[]; }> { const data = await file.arrayBuffer(); const workbook = XLSX.read(data, { cellDates: true }); const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json>(worksheet, { defval: '', }); const columns = Object.keys(rows[0] ?? {}).map((key) => ({ prop: key, name: key, })); return { columns, rows }; } ``` ```ts const { columns, rows } = await parseSheetJsFile(file); grid.columns = columns; grid.source = rows; ``` SheetJS is usually the best RevoGrid import fit for admin panels, ERP screens, analytics tools, and customer-provided enterprise spreadsheets where file shape is not perfectly controlled. The tradeoff is that parsing large files on the main browser thread can freeze the UI. For large files, parse in a Web Worker and only send normalized data back to the page. ```ts title="xlsx-import.worker.ts" import * as XLSX from 'xlsx'; self.onmessage = async (event: MessageEvent) => { const workbook = XLSX.read(await event.data.arrayBuffer(), { cellDates: true }); const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json>(worksheet, { defval: '', }); const columns = Object.keys(rows[0] ?? {}).map((key) => ({ prop: key, name: key, })); self.postMessage({ columns, rows }); }; ``` ```ts title="import-ui.ts" const worker = new Worker(new URL('./xlsx-import.worker.ts', import.meta.url), { type: 'module', }); worker.onmessage = ({ data }) => { grid.columns = data.columns; grid.source = data.rows; }; worker.postMessage(file); ``` ## ExcelJS [ExcelJS](https://github.com/exceljs/exceljs) is better when you need to read, edit, preserve, or generate richer workbook structures. It is a good fit for financial reports, styled exports, workbook templates, formulas, tables, and merged-cell workflows. Use it when the app needs workbook manipulation, not only raw import speed. ```sh pnpm add exceljs ``` ```ts import ExcelJS from 'exceljs'; import type { ColumnRegular, DataType } from '@revolist/revogrid'; export async function parseExcelJsFile(file: File): Promise<{ columns: ColumnRegular[]; rows: DataType[]; }> { const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(await file.arrayBuffer()); const worksheet = workbook.worksheets[0]; const headerRow = worksheet.getRow(1); const headerValues = Array.isArray(headerRow.values) ? headerRow.values.slice(1) : Object.values(headerRow.values); const headers = headerValues .map((value) => String(value ?? '').trim()) .filter(Boolean); const columns = headers.map((header) => ({ prop: header, name: header, })); const rows: DataType[] = []; worksheet.eachRow((row, rowNumber) => { if (rowNumber === 1) { return; } const model: DataType = {}; headers.forEach((header, index) => { model[header] = row.getCell(index + 1).value ?? ''; }); rows.push(model); }); return { columns, rows }; } ``` ExcelJS can also be used as an optional RevoGrid export provider when you need workbook features beyond the bundled provider. ```ts import ExcelJS from 'exceljs'; import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createExcelJsExcelExportProvider(ExcelJS), }; ``` The tradeoff is size and speed. ExcelJS is usually heavier than SheetJS for pure "parse and convert to JSON" imports. ## read-excel-file [read-excel-file](https://www.npmjs.com/package/read-excel-file) is the best fit when uploads are simple and validation matters more than workbook feature coverage. It reads `.xlsx` files in the browser or Node.js and can map columns into typed JSON with a schema. ```sh pnpm add read-excel-file ``` ```ts import readXlsxFile from 'read-excel-file'; const schema = { name: { column: 'Name', type: String, required: true, }, age: { column: 'Age', type: Number, }, }; const { rows, errors } = await readXlsxFile(file, { schema }); if (errors.length) { throw new Error(`Invalid spreadsheet: ${errors[0].error}`); } grid.columns = [ { prop: 'name', name: 'Name' }, { prop: 'age', name: 'Age' }, ]; grid.source = rows; ``` This is a strong choice for import forms, onboarding flows, and ETL-style screens where the workbook is really a typed table. The tradeoff is that it is not intended to be a full workbook manipulation layer. If users upload complex workbooks with formulas, styles, merged headers, comments, or multiple business sheets, use SheetJS or ExcelJS instead. ## write-excel-file [write-excel-file](https://www.npmjs.com/package/write-excel-file) is a lightweight `.xlsx` writer for browser and Node.js. It is also the default writer behind RevoGrid Pro's bundled Excel export provider. For most RevoGrid exports, use `ExportExcelPlugin` directly: ```ts import { ExportExcelPlugin } from '@revolist/revogrid-pro'; grid.plugins = [ExportExcelPlugin]; const plugins = await grid.getPlugins(); await plugins.find((plugin) => plugin instanceof ExportExcelPlugin)?.export({ workbookName: 'orders.xlsx', sheetName: 'Orders', }); ``` Use `write-excel-file` directly when you are exporting app data that does not need the grid export pipeline. ```sh pnpm add write-excel-file ``` ```ts import writeXlsxFile from 'write-excel-file'; await writeXlsxFile( [ [ { value: 'Name', fontWeight: 'bold' }, { value: 'Revenue', fontWeight: 'bold' }, ], [ { value: 'North', type: String }, { value: 12000, type: Number, format: '$#,##0' }, ], ], { fileName: 'summary.xlsx', sheet: 'Summary', }, ); ``` The tradeoff is intentional simplicity. If export needs template workbooks, charts, complex formulas, images, or heavy workbook editing, use ExcelJS or a custom provider. ## RevoGrid Import Pipeline For RevoGrid imports, keep the parser separate from the grid: 1. Read the uploaded file. 2. Parse workbook data with the selected library. 3. Normalize headers into stable `ColumnRegular` objects. 4. Normalize each row into a plain object keyed by `column.prop`. 5. Validate and coerce values before assigning them to the grid. 6. Assign `grid.columns` before `grid.source`. ```ts type ImportedTable = { columns: { prop: string; name: string }[]; rows: Record[]; }; async function importWorkbook(file: File): Promise { return parseSheetJsFile(file); } const imported = await importWorkbook(file); grid.columns = imported.columns; grid.source = imported.rows; ``` Avoid putting the raw workbook object into reactive application state. Workbooks can be large and contain circular or library-specific objects. Store the normalized rows, validation errors, and lightweight import metadata instead. ## Performance Notes Browser-side Excel parsing becomes visible to users once files are large enough. The exact threshold depends on column count, formulas, styles, browser, device, and parser options, but imports around `100k+` rows are where main-thread parsing commonly starts to feel expensive. For larger files: - Parse inside a Web Worker. - Limit the first preview to a small row count. - Normalize rows in chunks. - Keep the original workbook out of framework state. - Show validation errors separately from accepted rows. - Let RevoGrid render the normalized dataset with virtualization. For very large or regulated imports, parse on the backend and send RevoGrid paged or streamed JSON instead of asking the browser to own the whole workbook lifecycle. ## Practical Choice For most RevoGrid apps: - Use `ExportExcelPlugin` with the bundled `write-excel-file` provider for grid export. - Use SheetJS for flexible `.xlsx` upload and header inference. - Use `read-excel-file` for small validated upload forms. - Use ExcelJS when the workbook itself is the product: formatted reports, templates, formulas, tables, and workbook editing. That gives the fastest path for common XLSX-to-grid imports while keeping advanced export and workbook-editing cases open through RevoGrid's custom provider contract. --- # Export Providers URL: https://pro.rv-grid.com/guides/excel-export/providers/ Source: src/content/docs/guides/excel-export/providers.mdx Description: Choose the default XLSX provider, optional adapters, or a custom Excel export provider. `ExportExcelPlugin` prepares one provider context, applies built-in feature transforms, applies custom `exportTransformers`, then passes the context to the selected provider. Provider resolution is: 1. `plugin.export({ provider })` 2. `grid.exportExcel.exportProvider` 3. bundled `'write-excel-file'` ## Default Provider The bundled provider is backed by `write-excel-file/browser` and exports `.xlsx` only: ```ts await excel.export({ workbookName: 'orders', sheetName: 'Orders', }); ``` Extensionless workbook names receive `.xlsx`, so `orders` becomes `orders.xlsx`. The default provider rejects incompatible workbook output clearly: - `writingOptions.bookType` must be `xlsx`. - `workbookName` must be extensionless or end in `.xlsx`. - Use an optional adapter or custom provider for formats such as `.xlsb`, `.xls`, `.csv`, or `.ods`. ## Provider Options `providerOptions` are forwarded to the selected provider. For the bundled provider, they are merged into the options passed to `write-excel-file`. ```ts await excel.export({ workbookName: 'orders.xlsx', providerOptions: { fontFamily: 'Arial', fontSize: 12, hideGridLines: true, zoomScale: 90, }, }); ``` By default, the export pipeline derives provider hints from the live grid: | Derived option | Source | | --- | --- | | `columns` widths | Current grid column sizes | | `stickyRowsCount` | Generated header row plus pinned-top rows | | `stickyColumnsCount` | `colPinStart` columns | Explicit `providerOptions` override derived values. Disable derivation for a single export with `deriveGridOptions: false`: ```ts await excel.export({ deriveGridOptions: false, providerOptions: { stickyRowsCount: 0, stickyColumnsCount: 2, }, }); ``` ## Grid-Level Provider Use `grid.exportExcel.exportProvider` when one grid should consistently use the same workbook library: ```ts grid.exportExcel = { exportProvider: myProvider, }; ``` Override it for one export call: ```ts await excel.export({ provider: anotherProvider, workbookName: 'report.xlsx', }); ``` ## Optional ExcelJS Provider ExcelJS is not bundled. Install it in your app and pass the module object to the provider factory: ```sh pnpm add exceljs ``` ```ts import ExcelJS from 'exceljs'; import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createExcelJsExcelExportProvider(ExcelJS), }; ``` The ExcelJS adapter consumes the formatted `cellRows` matrix, maps common cell formatting to ExcelJS styles, applies grid-derived widths and freeze panes, and writes/downloads the workbook through ExcelJS. Use ExcelJS when export needs richer workbook styling, merge support, row heights, freeze panes, or library-specific worksheet setup. ## Optional SheetJS Provider SheetJS is not bundled. Install it in your app and pass the module object to the provider factory: ```sh pnpm add xlsx ``` ```ts import * as XLSX from 'xlsx'; import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createSheetJsExcelExportProvider(XLSX), }; ``` The SheetJS adapter writes plain row objects from `context.rows`. It does not consume formatted `cellRows`, grouped header rows, or merge spans. Use ExcelJS or a custom provider when workbook formatting must be preserved. ## Workbook Module Adapter Use `createWorkbookModuleExcelExportProvider()` for simple workbook-like modules that can create a worksheet from plain row objects: ```ts import { createWorkbookModuleExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createWorkbookModuleExcelExportProvider({ createWorkbook, createWorksheet, appendWorksheet, writeFile, }), }; ``` Like the SheetJS adapter, the workbook-module adapter uses `context.rows`. Use a full custom provider when the library needs formatted cells, grouped headers, merge metadata, or custom workbook setup. ## Custom Provider Any object that implements `ExcelExportProvider` can write the workbook: ```ts import type { ExcelExportProvider } from '@revolist/revogrid-pro'; const myProvider: ExcelExportProvider = { id: 'app-provider', async export(context) { await writeWorkbook({ fileName: context.workbookName, sheetName: context.sheetName, cells: context.cellRows, metadata: context.exportMetadata, }); }, }; ``` Custom providers should use the prepared context instead of reading rendered DOM rows. Virtualized rows are already included in `rows`, `sourceRows`, and `cellRows`. ## Provider Context Notes The export context is built from RevoGrid data stores first, with grid element sources as a compatibility fallback. This matters for virtualized grids: providers should not query rendered rows because offscreen records may not be mounted in the DOM. Pinned row segments are preserved in `sourceRows`: - `rowPinStart` for pinned top rows - `rgRow` for body rows - `rowPinEnd` for pinned bottom rows The prepared `rows` array keeps the same pinned-top, body, pinned-bottom order after formula evaluation. The formatted `cellRows` matrix adds generated header rows before those data rows. For workbook providers: - Use `cellRows` when you need formatted values, column-level cell properties, grouped headers, formulas, or merge spans. - Use `sourceRows` when you need row-segment semantics, original models, or source indexes. - Use `rows` only for plain row-object export where formatting and merge metadata do not matter. If a grid data store is not initialized yet, export falls back to the grid element source for that segment. Missing pinned sources are treated as empty arrays. For broader library selection guidance, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/). --- # Managing Reference Data URL: https://pro.rv-grid.com/guides/faq/data-reference/ Source: src/content/docs/guides/faq/data-reference.mdx Reference data represents static data in a key/value pair format. This type of data typically remains constant between server requests, making it ideal for scenarios where meaningful labels need to be displayed alongside coded values. For example, a car make might be represented as `'aud': 'Audi'`. Reference data mapping is a Core RevoGrid pattern. It uses column value handlers such as `cellParser` and does not require Pro plugins. {/* ### Example Data Structure The data returned from the server usually consists of codes (keys) that need to be mapped to their corresponding names (values) for display in the grid. Here’s an example of how this data can be structured: ```javascript // Data from server const rowData = [ { prod: 'toy', price: 28000 }, { prod: 'bmw', price: 55000 }, // ... ]; // Supporting reference data const carMappings = { 'toy': 'Toyota', 'bmw': 'BMW', 'aud': 'Audi', 'hnd': 'Honda' }; ``` */} ## Strategies for Managing Reference Data Value handlers provide a flexible way to map codes to display values. This method involves more coding but allows for greater customization in how data is formatted and saved. - **Value Formatter**: Converts the key to a user-friendly value for display in the grid. - **Value Parser**: Converts the user-selected value back into a key for saving in the underlying data. {/* #### Example Configuration Here’s how you can configure the grid to use value handlers: ```javascript const gridOptions = { columns: [ { prop: 'prod', cellTemplate: (h, params) => carMappings[params.value], // Convert code to name }, cellCompare: (a, b) => { // Map data for sorting return carMappings[a] - carMappings[b]; }, cellParser: (model, column) => { // Map data for filtering, editing and saving return carMappings[model[column.prop]]; } ], // Other grid options... }; ``` */} ## Conclusion Effectively managing reference data is essential for ensuring that users see meaningful labels alongside coded values. --- # Data Synchronization URL: https://pro.rv-grid.com/guides/faq/data-sync/ Source: src/content/docs/guides/faq/data-sync.mdx Description: A guide covers core concepts and advanced tips for handling data. We assume that you have already set up the basic version of RevoGrid and are familiar with configuring the `source` and `columns`. It is crucial to always mutate the data you pass to the grid, as this ensures optimal performance. RevoGrid offers several popular approaches for working with data: 1. ### Grid-Centric Data Management: - You can work with the grid and, in the end, request the data directly from it. This approach allows you to subscribe to the `afteredit` event, which is triggered after a cell edit or range edit occurs. You can retrieve the current data from the `source` whenever you need it. Try to modify the data in the grid and check how it updates in real time. Check `Source code` for more details. --- --- 2. ### Source-Centric Data Management: - This method involves managing your data independently and handling events before cell editing. In this case, you need to pass your source array to the grid each time an edit occurs, which will trigger a grid re-render to display the updated data. As part of the Pro version, we provide the [**EventManagerPlugin**](../data-manage/event-manager-explanation), which consolidates range, clipboard, and cell edit events into a single common event called `gridedit`. You can utilize this plugin to catch the event, modify it, or even create your own custom event manager. Try to modify the data in the grid and check how it updates in real time. --- --- 3. ### Pagination and Dynamic Data Loading: - These two features are two sides of the same coin: - Pagination allows you to break your data source into manageable parts and load data from a server or cache for each page. For more information, check out the [**PaginationPlugin** and its documentation](../grid-utilities/pagination). - Dynamic data loading is an advanced version of pagination that loads data in chunks based on user interactions, such as scrolling, without displaying a page panel. With this approach, you can combine the two methods mentioned above to work with a complete or partial grid dataset, updating the data and passing it to the grid as needed. ## Best Practices For best practices on data management and manipulation, please refer to our [Best Practices Guide](../faq/patterns). This section will help you optimize your grid's performance and usability. --- # Best Practices URL: https://pro.rv-grid.com/guides/faq/patterns/ Source: src/content/docs/guides/faq/patterns.mdx When working with advanced data grids designed for large datasets, several key UI patterns and practices should be followed to ensure a seamless user experience and maintain high performance. These patterns help create an interface that is intuitive, responsive, and efficient, even with significant data volumes. Here are some of the most important patterns to follow: 1. **Virtual Scrolling** - **Pattern**: Efficient handling of large datasets by rendering only visible rows and columns, reducing browser load. - **Best Practice**: Ensure custom rendering or plugins are compatible with virtual scrolling. Avoid heavy DOM manipulations outside the visible viewport to optimize performance. 2. **Lazy Loading** - **Pattern**: Load data as needed rather than all at once to improve performance. - **Best Practice**: Implement lazy loading, particularly with external data sources or APIs, ensuring data is fetched and rendered efficiently in chunks. 3. **Data Virtualization** - **Pattern**: Keep only a subset of data in memory, fetching and rendering only the data that is visible or near visible. - **Best Practice**: Ensure features like sorting, filtering, and grouping are optimized for large datasets and compatible with data virtualization. 4. **Custom Cell Renderers** - **Pattern**: Create custom renderers for complex cell content, such as images, buttons, or interactive elements. - **Best Practice**: Keep custom cell renderers lightweight to avoid performance bottlenecks. Optimize expensive operations that are repeated across many cells. Use cache mechanisms to improve performance. 5. **Responsive Design** - **Pattern**: Adapt grid layouts to varying screen sizes and resolutions. - **Best Practice**: Ensure the grid adjusts to different screen sizes with resizable columns and scrollable content. Use responsive features to manage column visibility or prioritize important data on smaller screens. 6. **Keyboard Navigation** - **Pattern**: Support keyboard navigation for seamless user interaction, especially for power users. - **Best Practice**: Ensure custom features integrate well with keyboard navigation, maintaining or enhancing keyboard accessibility. Consider custom keyboard shortcuts where applicable. 7. **Infinite Scrolling** - **Pattern**: Load additional data as the user scrolls down or across the grid. - **Best Practice**: Implement or support infinite scrolling, especially for large datasets, providing a smoother user experience compared to traditional pagination. 8. **Column and Row Pinning** - **Pattern**: Keep specific rows or columns visible while scrolling through the data. - **Best Practice**: Support pinning in customizations, especially for grids where headers or key identifiers need to remain visible. 9. **Custom Sorting and Filtering** - **Pattern**: Implement custom logic for sorting and filtering to provide more complex or domain-specific data interactions. - **Best Practice**: Ensure custom sorting and filtering are optimized for performance, potentially applying logic on the server-side for large datasets. 10. **Editable Cells and Inline Editing** - **Pattern**: Allow users to edit data directly within the grid for more intuitive data manipulation. - **Best Practice**: Ensure inline editing is user-friendly, supports keyboard shortcuts, and includes robust data validation and error handling. 11. **Event Handling and Customization** - **Pattern**: Hook into various events (e.g., cell click, row hover) for custom behaviors. - **Best Practice**: Optimize event listeners and clean them up when no longer needed to avoid performance issues and memory leaks. 12. **Context Menus and Tooltips** - **Pattern**: Enhance user interaction by providing additional options or information through context menus and tooltips. - **Best Practice**: Implement contextually aware and lightweight context menus and tooltips to ensure they don't interfere with performance. 13. **Theming and Custom Styling** - **Pattern**: Customize themes and styling to align the grid’s appearance with the overall application design. - **Best Practice**: Use CSS variables and custom properties for consistent and easy-to-manage styles, ensuring custom styles do not conflict with core styles or degrade performance. 14. **State Management** - **Pattern**: Manage the grid’s state (e.g., selected rows, applied filters) for consistency, especially in complex applications. - **Best Practice**: Integrate with a state management solution if necessary, ensuring the grid’s state is preserved across sessions or navigation. 15. **Big O Notation Considerations** - **Pattern**: Understand the computational complexity of operations to ensure performance remains optimal as data size increases. - **Best Practice**: Evaluate and optimize algorithms, especially for sorting, filtering, and rendering, to ensure they are efficient and scale well with large datasets. 16. **Accessibility (a11y)** - **Pattern**: Ensure the grid is accessible to all users, including those with disabilities. - **Best Practice**: Follow accessibility best practices, such as providing proper ARIA labels, ensuring keyboard accessibility, and supporting screen readers. Test with accessibility tools to identify and fix issues. By adhering to these patterns, you can create an implementation that is not only performant and scalable but also user-friendly and accessible. These practices will help you build grids that efficiently handle large datasets while providing a seamless and intuitive user experience. --- # Form Edit Example URL: https://pro.rv-grid.com/guides/form-edit/ Source: src/content/docs/guides/form-edit.mdx Description: Learn how to implement form-based row editing in RevoGrid This example demonstrates how to implement form-based row editing using RevoGrid's row expand functionality. Instead of editing cells directly in the grid, users can click on a row to expand it and edit the data in a form format. ## Implementation Details The form edit functionality is implemented using the `RowExpandPlugin`. When a row is expanded, a custom template renders a form with the row's data. The form is split into two columns for better organization. Key implementation points: 1. **Row Expansion**: Uses RevoGrid's `RowExpandPlugin` to show/hide the form 2. **Form Layout**: Implements a two-column grid layout using Tailwind/UnoCSS 3. **Data Handling**: Updates the grid's source data when the form is saved 4. **Validation**: Can be extended to add form validation (not implemented in this basic example) ## Features - Form layout - Save and Cancel buttons - Responsive form design - Custom form fields for different data types - Automatic grid update on save ## Usage To implement this in your own project: 1. Import required plugins 2. Configure the grid columns 3. Set up the row expand template with your form fields 4. Handle form submission and data updates ```typescript grid.additionalData = { rowExpand: { expandedRowHeight: 300, template(h, props) { // Form template implementation } } }; ``` ## Customization You can customize the form by: - Adding more form fields - Implementing validation - Changing the layout - Adding custom styling - Implementing different field types --- # Gantt Overview URL: https://pro.rv-grid.com/guides/gantt/ Source: src/content/docs/guides/gantt/index.mdx Description: Enterprise Gantt for RevoGrid with deterministic scheduling, timeline editing, dependencies, resources, baselines, and diagnostics. RevoGrid Gantt is an Enterprise plugin layered on top of the base grid. RevoGrid owns rendering, virtualization, editing, and keyboard interactions. Gantt adds task timeline projection, dependency links, scheduling rules, resources/assignments, critical path, baselines, and timeline tools. Guide pages explain behavior. For raw type signatures, use Gantt API. --- # Gantt Baselines URL: https://pro.rv-grid.com/guides/gantt/concepts/baselines/ Source: src/content/docs/guides/gantt/concepts/baselines.mdx Description: Capture a baseline snapshot of your schedule and overlay it beneath current task bars to visualise drift. A **baseline** is a saved snapshot of task dates and progress at a specific point in time (e.g. when the project was approved). The Gantt chart can overlay these baseline bars beneath the live bars, making schedule drift immediately visible. --- ## Adding a Baseline Pass one or more `BaselineSnapshot` objects via `grid.ganttBaselines`: ```typescript grid.ganttBaselines = [ { id: 'baseline-approved', name: 'Approved Plan', capturedAt: '2026-04-01T08:00:00Z', tasks: [ { taskId: 't1', startDate: '2026-04-06', endDate: '2026-04-22', durationDays: 13, progressPercent: 0 }, { taskId: 't2', startDate: '2026-04-06', endDate: '2026-04-10', durationDays: 5, progressPercent: 0 }, { taskId: 't3', startDate: '2026-04-10', endDate: '2026-04-10', durationDays: 0, progressPercent: 0 }, { taskId: 't4', startDate: '2026-04-13', endDate: '2026-04-22', durationDays: 8, progressPercent: 0 }, { taskId: 't5', startDate: '2026-04-23', endDate: '2026-05-16', durationDays: 18, progressPercent: 0 }, { taskId: 't6', startDate: '2026-04-23', endDate: '2026-05-09', durationDays: 13, progressPercent: 0 }, { taskId: 't7', startDate: '2026-04-23', endDate: '2026-05-16', durationDays: 18, progressPercent: 0 }, { taskId: 't8', startDate: '2026-05-16', endDate: '2026-05-16', durationDays: 0, progressPercent: 0 }, ], }, ]; ``` You can also capture a snapshot from the current scheduled state: ```typescript const plugins = await grid.getPlugins(); const gantt = plugins.find((plugin) => plugin.constructor?.name === 'GanttPlugin'); const baseline = gantt?.captureBaseline?.({ id: 'baseline-current', name: 'Current Approved Plan', capturedAt: '2026-04-15T08:00:00Z', }); if (baseline) { grid.ganttBaselines = [ ...(grid.ganttBaselines ?? []), baseline, ]; } ``` ## Enabling the Overlay Turn on baseline rendering through the `visuals` key in your project config: ```typescript grid.gantt = { // …required fields visuals: { showBaseline: true, baselineId: 'baseline-approved', // optional — defaults to the latest snapshot }, }; ``` When `showBaseline` is `true`, a translucent bar is drawn behind each live task bar showing its baseline position. Only tasks listed in `BaselineSnapshot.tasks` show a baseline bar. Tasks added after the snapshot was captured render without one. ## Multiple Baselines You can store several snapshots and switch between them at runtime: ```typescript // Initially show the approved plan grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-approved' } }; // Switch to a re-baseline after scope change grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-replan' } }; ``` ## Variance Columns When a baseline is visible, each projected task row also exposes optional variance fields: | Field | Description | |---|---| | `startVarianceDays` | Current scheduled start minus baseline start, in calendar days | | `finishVarianceDays` | Current scheduled finish minus baseline finish, in calendar days | | `durationVarianceDays` | Current scheduled duration minus baseline duration | | `progressVariancePercent` | Current progress minus baseline progress, in percentage points | Add the default columns when users need to inspect drift numerically: ```typescript grid.columns = [ createDefaultTaskTableColumn('name'), createDefaultTaskTableColumn('startVarianceDays'), createDefaultTaskTableColumn('finishVarianceDays'), createDefaultTaskTableColumn('durationVarianceDays'), createDefaultTaskTableColumn('progressVariancePercent'), ]; ``` ## BaselineSnapshot Fields | Field | Type | Description | |---|---|---| | `id` | `BaselineId` | Unique identifier for this snapshot | | `name` | `string` | Human-readable label (e.g. `"Q1 Approved Plan"`) | | `capturedAt` | `ISODateTimeString` | When the snapshot was taken | | `tasks` | `BaselineTaskSnapshot[]` | Per-task date and progress records | ### BaselineTaskSnapshot Fields | Field | Type | Description | |---|---|---| | `taskId` | `TaskId` | References the live task | | `startDate` | `ISODateString` | Baseline start date | | `endDate` | `ISODateString` | Baseline end date | | `durationDays` | `number` | Baseline duration in calendar days | | `progressPercent` | `number` | Baseline progress (0–100) | --- # Data Model URL: https://pro.rv-grid.com/guides/gantt/concepts/data-model/ Source: src/content/docs/guides/gantt/concepts/data-model.mdx Description: Required grid properties and Gantt entity collections. This page is for wiring the minimum data surface. Required entry points on `HTMLRevoGridElement`: - `gantt`: project config (`GanttPluginConfig`) - `source`: flat task rows - `ganttCalendars`: `CalendarEntity[]` Optional linked collections: - `ganttDependencies`: `DependencyEntity[]` - `ganttResources`: `ResourceEntity[]` - `ganttAssignments`: `AssignmentEntity[]` - `ganttBaselines`: `BaselineSnapshot[]` Public shape is declared in: - `packages/enterprise/plugins/gantt/grid/gantt-plugin.types.ts` - `packages/enterprise/plugins/gantt/core/domain.ts` ## Flat Task Source Rows Gantt reads task data from flat grid rows. Required fields are `id`, `name`, `startDate`, and either `duration` or `endDate`. The public source row names match task-table editing conventions: - `duration` maps to internal `durationDays`. - `percentDone` maps to internal `progressPercent`. - `taskMode` maps to internal `manuallyScheduled` (`manual` or `auto`). - `status` is stored as the raw task status key, such as `in-progress`. When both `duration` and `endDate` are present, `duration` is the scheduling input and Gantt resolves `endDate` from `startDate + duration`. This matches task-table editing, where duration edits drive the finish date. Use `endDate` without `duration` when the finish date should be the authored input. Projected grid rows remain display-ready. `GanttGridRow.status` is the human-readable status label, while `GanttGridRow.statusKey` is the raw status key. Custom source fields are preserved by task id across recalculation and structural task changes unless the row is replaced without those fields. ## Temporal Field Precision Task temporal fields (`startDate`, `endDate`, `actualStartDate`, `actualFinishDate`) accept: - ISO date: `YYYY-MM-DD` - UTC ISO datetime: `YYYY-MM-DDTHH:mm:ss.sssZ` Runtime normalization rules: - Datetime input is preserved and used for intraday scheduling precision. - Date-only input is normalized to `T00:00:00Z`. For new integrations, prefer explicit UTC datetime values when you need hour/minute planning behavior. --- # Dependencies URL: https://pro.rv-grid.com/guides/gantt/concepts/dependencies/ Source: src/content/docs/guides/gantt/concepts/dependencies.mdx Description: Dependency types, lag/lead, and validation behavior. Dependencies constrain automatic successors using the relationship type and optional lag: | Type | MSP token | Constraint | |---|---|---| | `finish-to-start` | `FS` | Successor starts after predecessor finishes. | | `start-to-start` | `SS` | Successor starts no earlier than predecessor starts. | | `finish-to-finish` | `FF` | Successor finishes no earlier than predecessor finishes. | | `start-to-finish` | `SF` | Successor finishes no earlier than predecessor starts. | ## Using Lag and Lead Use `lagDays` for lag or lead: ```typescript grid.ganttDependencies = [ { id: 'build-test', predecessorTaskId: 'build', successorTaskId: 'test', type: 'finish-to-start', lagDays: -2, }, ]; ``` Positive `lagDays` delays the successor. Negative `lagDays` is lead time and allows overlap, matching Microsoft Project's negative lead syntax. `lagCalendar` in scheduling config controls whether lag is interpreted as calendar days or working days. ## Inline Cell Syntax Inline predecessor/successor cells accept MSP-style tokens: ```text 1FS 2SS+3 3FF-1d task-4SF-2 ``` Blank or invalid lag normalizes to `0`. The optional `d` suffix is accepted. Whitespace around tokens is ignored. ## Validation Behavior Important dependency behavior: - Circular dependency chains are invalid schedule data. RevoGrid reports them as `dependency-cycle` issues with the involved `taskIds` and `dependencyIds`. - New dependency edits that would create a cycle are rejected before they change the Gantt store. - Task row drops that would make dependency partners parent/child through the outline are rejected before the drop is committed. - Imported projects that already contain a cycle still render, but automatic dependency propagation and drag/resize dependency clamping are not applied while the active dependency graph is cyclic. - Missing predecessor/successor references produce issues. - Dependency links are rendered in the overlay layer and can be created/edited interactively. This matches the Microsoft Project scheduling model: a loop such as `Setup load balancer -> Configure firewall -> Setup load balancer` cannot produce a valid schedule. Remove or change one of the invalid links, then rerun scheduling. When building diagnostics UI, inspect the scheduling result issues: ```typescript const cycle = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle'); if (cycle) { console.warn('Dependency cycle', { taskIds: cycle.taskIds, dependencyIds: cycle.dependencyIds, }); } ``` ## Examples ### Auto Successor Clamps to FS Dependency An automatic successor cannot silently move before its predecessor allows: ```typescript grid.source = [ { id: 'design', name: 'Design', startDate: '2026-04-01', duration: 3, taskMode: 'auto', }, { id: 'build', name: 'Build', startDate: '2026-04-01', duration: 2, taskMode: 'auto', }, ]; grid.ganttDependencies = [{ id: 'design-build', predecessorTaskId: 'design', successorTaskId: 'build', type: 'finish-to-start', lagDays: 0, }]; ``` Expected result: `design` ends on `2026-04-03`, so `build` is scheduled no earlier than `2026-04-04`. If the user drags `build` left, the effective bar snaps back to the dependency-valid date. ### Manual Successor Keeps Date and Warns Manual tasks preserve authored dates, but dependency violations remain visible: ```typescript grid.source = [ { id: 'design', startDate: '2026-04-01', duration: 3 }, { id: 'review', name: 'Manual Review', startDate: '2026-04-02', duration: 1, taskMode: 'manual', }, ]; grid.ganttDependencies = [{ id: 'design-review', predecessorTaskId: 'design', successorTaskId: 'review', type: 'finish-to-start', lagDays: 0, }]; ``` Expected result: `review` stays on `2026-04-02` and receives a `manual-dependency-violation` warning because the dependency requires `2026-04-04`. See the [Advanced dependencies demo](https://demo.rv-grid.com/gantt-dependencies-advanced/ts) for a runnable project containing FS, SS, FF, and SF links with lag and lead. Implementation sources: - `packages/enterprise/plugins/gantt/engine/dependency-graph.ts` - `packages/enterprise/plugins/gantt/engine/scheduler-engine/validation.ts` - `packages/enterprise/plugins/gantt/features/dependencies/` --- # Tasks, Summaries, And Milestones URL: https://pro.rv-grid.com/guides/gantt/concepts/tasks/ Source: src/content/docs/guides/gantt/concepts/tasks.mdx Description: Task types and hierarchy behavior. Task hierarchy is represented by `parentId`. - `type: 'task'`: normal scheduled item. - `type: 'summary'`: rollup row; dates and progress come from children. - `type: 'milestone'`: zero duration (`startDate === endDate`, `duration: 0`). Scheduling-relevant source fields include `taskMode`, `constraintType`, `constraintDate`, `deadlineDate`, `effortMode`, `workHours`, `percentDone`, `actualStartDate`, `actualFinishDate`, `remainingDurationDays`, `splitRanges`, `priority`, and `canLevel`. Source row contract: `GanttTaskSourceRow` in `packages/enterprise/plugins/gantt/grid/gantt-plugin.types.ts`. Internal task entities use `manuallyScheduled`, `durationDays`, and `progressPercent` in `packages/enterprise/plugins/gantt/core/domain.ts`. --- # How Gantt Builds On RevoGrid URL: https://pro.rv-grid.com/guides/gantt/customization/how-gantt-builds-on-revogrid/ Source: src/content/docs/guides/gantt/customization/how-gantt-builds-on-revogrid.mdx Description: Division of responsibilities between RevoGrid and Gantt plugin layers. RevoGrid provides row/column virtualization, editing, selection, scrolling, and base rendering. Gantt extends that with timeline projection, dependency overlays, scheduling, and resource planning. Implementation layers: - Grid/plugin orchestration: `packages/enterprise/plugins/gantt/grid/gantt-plugin.ts` - Scheduling engine: `packages/enterprise/plugins/gantt/engine/` - Projection and timeline model: `packages/enterprise/plugins/gantt/projection/` - Interaction features: `packages/enterprise/plugins/gantt/features/` Use this split when debugging behavior: if data rules changed, inspect `engine`; if visual output changed, inspect `projection/features`; if event routing changed, inspect `grid`. --- # Gantt Timeline and Zoom URL: https://pro.rv-grid.com/guides/gantt/customization/timeline-zoom/ Source: src/content/docs/guides/gantt/customization/timeline-zoom.mdx Description: Configure timeline scale, zoom behavior, wheel interaction, and visual timeline overlays. The Gantt timeline can be configured at two levels: 1. `zoomPreset` for fast setup with built-in levels. 2. `zoom` for full control over levels, wheel behavior, and anchor strategy. Use `weekStartsOn` when week headers or week ticks should align to a specific weekday. Gantt defaults to Sunday-start weeks. --- ## Quick Start with `zoomPreset` Use a built-in preset when you only need a standard timeline scale: ```typescript grid.gantt = { // ...required project fields zoomPreset: 'week-month', }; ``` Supported presets: - `'minute-hour'` (15-minute ticks grouped by hour) - `'hour-day'` (hour ticks grouped by day) - `'day-week'` - `'week-month'` - `'month-quarter'` - `'quarter-year'` - `'year-quarter'` - `'multi-year-quarter'` By default, the interactive zoom ladder starts at `day-week` and excludes the intraday presets for performance. Use `zoomPreset: 'hour-day'`, `zoomPreset: 'minute-hour'`, or explicit `zoom.levels` when an intraday timeline is required. ## Week Start Week-based timeline headers, week ticks, and padded week ranges use Sunday by default: ```typescript grid.gantt = { // ...required project fields weekStartsOn: 0, // Sunday, default }; ``` Set `weekStartsOn: 1` when the Gantt should use Monday-start weeks: ```typescript grid.gantt = { // ...required project fields weekStartsOn: 1, // Monday zoomPreset: 'week-month', }; ``` `weekStartsOn` accepts `0` for Sunday or `1` for Monday. It affects timeline week boundaries only; task calendars still use their own `workingDays` configuration. ## Time Modes Gantt timeline modes are defined by `tickUnit` + `headerRows` in each zoom level. - `minute-hour`: 15-minute timeline resolution for intraday planning windows. - `hour-day`: hourly timeline resolution grouped under day headers. - `day-week`: daily planning view. - `week-month`: weekly planning view. - `month-quarter`: monthly planning view. - `quarter-year`, `year-quarter`, `multi-year-quarter`: portfolio and long-range views. Recommended usage pattern: - Opt into `minute-hour` for short execution windows and handoffs. - Opt into `hour-day` for same-day and next-day coordination. - Use `day-week` or coarser for baseline and milestone planning. Intraday modes (`minute-hour`, `hour-day`) control timeline granularity and interaction scale. Temporal fields still use the same names, but when you need intraday precision you should pass UTC datetime values (`...Z`) instead of date-only strings. ## Hour Mode Use hour mode when task bars should move, resize, and create on hour boundaries instead of whole-day boundaries. The important settings are: - `timelinePrecision: 'hour'` makes timeline interactions use hour precision by default. - `zoomPreset: 'hour-day'` starts the visible timeline with one column per hour. - `snap.unit: 'hour'` forces task create, move, and resize snapping to hours even if the active zoom level changes later. - `snap.workingTime` controls whether snapping can use working-time rules. Closed weekdays, holidays, and closed hours are skipped only when `scheduling.excludeHolidaysFromDuration` is also `true`. ```typescript grid.gantt = { // ...required project fields timelinePrecision: 'hour', zoomPreset: 'hour-day', snap: { unit: 'hour', workingTime: true, }, scheduling: { excludeHolidaysFromDuration: true, }, calendars: [ { id: 'standard', name: 'Standard', workingDays: [1, 2, 3, 4, 5], workingHours: [{ start: '09:00', end: '17:00' }], hoursPerDay: 8, }, ], tasks: [ { id: 'task-1', name: 'Same-day implementation', startDate: '2026-04-06T09:00:00.000Z', endDate: '2026-04-06T13:00:00.000Z', }, ], }; ``` `timelinePrecision` controls interaction precision. `zoomPreset` and `zoom.defaultLevelId` control the initial visible scale. Keep them aligned for an hour-first experience, or set `zoom.defaultLevelId: 'day-week'` if you want hour snapping while opening on a coarser planning view. When `snap.workingTime` is `true` or omitted and `scheduling.excludeHolidaysFromDuration` is `true`, hour snapping uses the primary project calendar: - A drag before `09:00` snaps forward to `09:00`. - A drag after `17:00` snaps forward to the next working opening. - Weekend or holiday drags snap to the next or previous working opening depending on the interaction direction. Set `snap.workingTime: false` when users should be able to place tasks in non-working hours while still snapping to the start of each hour. If `scheduling.excludeHolidaysFromDuration` is `false` or omitted, hour snapping rounds to the start of the hour and does not move drags out of non-working time. ## Full Zoom Configuration Use `zoom` when you need custom levels, min/max bounds, locale, or wheel interaction rules. ```typescript grid.gantt = { // ...required project fields zoomPreset: 'week-month', zoom: { enabled: true, defaultLevelId: 'hour-day', minLevelId: 'minute-hour', maxLevelId: 'quarter-year', locale: 'en-US', zoomAnchorMode: 'pointer', wheelZoomEnabled: true, wheelZoomTrigger: 'ctrlKey', wheelZoomMode: 'discrete', invertWheelDirection: false, }, }; ``` If both `zoomPreset` and `zoom.defaultLevelId` are provided, the resolved zoom config uses the `zoom` values where specified. ## Custom Zoom Levels You can define your own `zoom.levels` list from finest to coarsest. ```typescript grid.gantt = { // ...required project fields zoom: { levels: [ { id: 'minute-hour', label: '15 Min / Hour', tickUnit: 'minute', tickCount: 15, tickWidth: 44, headerRows: [ { id: 'day', unit: 'day' }, { id: 'hour', unit: 'hour' }, { id: 'minute', unit: 'minute', count: 15 }, ], }, { id: 'hour-day', label: 'Hour / Day', tickUnit: 'hour', tickWidth: 52, headerRows: [ { id: 'day', unit: 'day' }, { id: 'hour', unit: 'hour' }, ], }, { id: 'day-week', label: 'Day / Week', tickUnit: 'day', tickWidth: 56, headerRows: [ { id: 'week', unit: 'week' }, { id: 'day', unit: 'day' }, ], }, { id: 'week-month', label: 'Week / Month', tickUnit: 'week', tickWidth: 84, headerRows: [ { id: 'month', unit: 'month' }, { id: 'week', unit: 'week' }, ], }, { id: 'month-quarter', label: 'Month / Quarter', tickUnit: 'month', tickWidth: 120, headerRows: [ { id: 'year', unit: 'year' }, { id: 'month', unit: 'month' }, ], }, ], defaultLevelId: 'hour-day', }, }; ``` ## Wheel Zoom Options Control whether users can zoom with the mouse wheel and which modifier key is required. ```typescript grid.gantt = { // ...required project fields zoom: { wheelZoomEnabled: true, wheelZoomTrigger: 'metaKey', // macOS Cmd key wheelZoomMode: 'smooth-discrete', invertWheelDirection: false, }, }; ``` Available `wheelZoomTrigger` values: - `'ctrlKey'` - `'metaKey'` - `'altKey'` - `'shiftKey'` - `'none'` ## Anchor Behavior During Zoom `zoomAnchorMode` controls which point in the viewport stays stable when switching levels: - `'pointer'`: keeps the date under the mouse pointer fixed. - `'center'`: keeps the center date fixed. - `'start'`: keeps the left edge date fixed. ```typescript grid.gantt = { // ...required project fields zoom: { zoomAnchorMode: 'center', }, }; ``` ## Timeline Overlays Timeline visuals are configured in `visuals`. ```typescript grid.gantt = { // ...required project fields visuals: { projectLineDate: '2026-04-06', timeRanges: [ { id: 'sprint-1', startDate: '2026-04-06', endDate: '2026-04-17', label: 'Sprint 1', color: '#5B8DEF', }, { id: 'freeze', startDate: '2026-05-18', endDate: '2026-05-22', label: 'Code Freeze', color: '#F59E0B', }, ], shadeNonWorkingTime: true, }, }; ``` `shadeNonWorkingTime` is accepted in both `zoom` and `visuals`. If both are set, zoom-level setting takes priority. --- # Visual Configuration URL: https://pro.rv-grid.com/guides/gantt/customization/visuals/ Source: src/content/docs/guides/gantt/customization/visuals.mdx Description: Timeline overlays, labels, baseline, critical path, and marker hooks. `gantt.visuals` controls overlays and rendering hooks: - `showBaseline`, `baselineId` - `showCriticalPath` - `showTaskLabels` - `projectLineDate`, `showTodayLine`, `milestoneLines` - `timeRanges`, `shadeNonWorkingTime` - `taskMarkerHook`, `taskBarColorHook`, `taskBarContentHook` - `taskTooltipFields`, `taskTooltipHook` Use `taskTooltipFields` when you only need to choose which built-in fields appear in task-bar tooltips: ```ts grid.gantt = { ...project, visuals: { taskTooltipFields: [ 'name', 'startDate', 'endDate', { field: 'percentDone', label: 'Complete' }, 'assignees', ], }, }; ``` ## Task tooltip API `taskTooltipFields` is an ordered array. Each entry can be a field id string or an object with a `field` and optional `label`: ```ts type GanttTaskTooltipField = | GanttTaskTooltipFieldId | { field: GanttTaskTooltipFieldId; label?: string }; ``` Object entries let you rename a built-in line. Set `label: ''` to render only the value without a `Label: ` prefix. ```ts visuals: { taskTooltipFields: [ { field: 'name', label: '' }, { field: 'percentDone', label: 'Done' }, ], } ``` Supported built-in fields: | Field | Tooltip output | |---|---| | `summary` | Task name and completion percent, for example `Design - 50%`. | | `name` | Task name. | | `percentDone` | Completion percent. | | `taskType` | Human-readable task type. | | `status` | Human-readable task status. | | `startDate` | Scheduled start date, formatted with `gantt.dateFormats.tooltip`. | | `endDate` | Scheduled end date, formatted with `gantt.dateFormats.tooltip`. | | `duration` | Duration in days. | | `taskMode` | `Auto Scheduled` or `Manually Scheduled`. | | `schedulingWarnings` | Scheduling warnings joined into one line; skipped when empty. | | `cost` | Formatted task cost; skipped when empty. | | `assignees` | Comma-separated assignee names; skipped when empty. | When `taskTooltipFields` is omitted, Gantt keeps the default tooltip: `summary`, `taskType`, `status`, `startDate`, `endDate`, `duration`, `taskMode`, `schedulingWarnings`, `cost`, and `assignees`. An empty array intentionally renders an empty tooltip. `taskTooltipHook` has priority over `taskTooltipFields` and can replace the whole tooltip string: ```ts visuals: { taskTooltipFields: ['name'], taskTooltipHook: ({ row }) => `${row.name}\n${row.status}`, } ``` Return `null` or `undefined` from `taskTooltipHook` to fall back to `taskTooltipFields` or the default tooltip. Use the hook for custom row metadata that is not part of the built-in field list. Type source: `packages/enterprise/plugins/gantt/grid/gantt-plugin.types.ts`. Rendering sources: `features/timeline-background/*`, `grid/gantt-column-type.ts`. --- # Gantt Examples URL: https://pro.rv-grid.com/guides/gantt/examples/ Source: src/content/docs/guides/gantt/examples/index.mdx Description: Runnable Gantt demos grouped by feature. Use demos for end-to-end behavior checks: - [Basic Gantt](https://demo.rv-grid.com/gantt-basic/ts) - [Resource Planning](https://demo.rv-grid.com/gantt-resource-planning/ts) - [Advanced Dependencies](https://demo.rv-grid.com/gantt-dependencies-advanced/ts) - [Feature Recipes](https://demo.rv-grid.com/gantt-example-recipes/ts) - [Task Editor Form](https://demo.rv-grid.com/gantt-task-editor-form/ts) - [Progress And Work](https://demo.rv-grid.com/gantt-progress-work/ts) - [Split Tasks](https://demo.rv-grid.com/gantt-split-tasks/ts) - [Constraints And Deadlines](https://demo.rv-grid.com/gantt-constraints-deadlines/ts) - [ALAP Project Finish](https://demo.rv-grid.com/gantt-alap/ts) - [Critical Path](https://demo.rv-grid.com/gantt-critical-path-analysis/ts) - [Baselines](https://demo.rv-grid.com/gantt-baseline/ts) --- # Gantt Context Menu URL: https://pro.rv-grid.com/guides/gantt/features/context-menu/ Source: src/content/docs/guides/gantt/features/context-menu.mdx Description: Use the built-in Gantt row, column, and timeline context menus and extend them with RevoGrid Pro menu items. Gantt installs a Gantt-aware context menu on top of the RevoGrid Pro `ContextMenuPlugin`. It uses the existing `rowContextMenu` and `columnContextMenu` grid properties instead of introducing a separate menu renderer. The Gantt plugin prepends generated menu items and preserves application-provided menu items. The integration is enabled by default for every `GanttPlugin` instance. Set `gantt.contextMenu` to `false` to opt out of generated Gantt row, column, and timeline menu items while leaving application-provided RevoGrid context menu configuration untouched: ```ts grid.gantt = { ...project, contextMenu: false, }; ``` You can also disable one generated surface at a time: ```ts grid.gantt = { ...project, contextMenu: { row: false, column: true, }, }; ``` ## Row Menu Right-click a task-table row or task bar to open task actions: - `Add` - `Convert to milestone` - `Indent` - `Outdent` - `Delete` The default actions dispatch Gantt events: ```ts grid.addEventListener('gantt-task-create', (event) => { const { parentId, insertAfterTaskId } = event.detail ?? {}; }); grid.addEventListener('gantt-task-indent', (event) => { indentTask(event.detail?.taskId); }); grid.addEventListener('gantt-task-outdent', (event) => { outdentTask(event.detail?.taskId); }); grid.addEventListener('gantt-task-delete', (event) => { deleteTask(event.detail?.taskId); }); ``` `Add` uses the focused row or clicked task bar as the insertion anchor. When the anchor has descendants, Gantt inserts the new sibling after the full descendant block so the task tree stays valid. Context-menu creation names the row `New task 1`, `New task 2`, and so on, and does not open the task editor. The generated row menu is suppressed on the pinned add-task draft row because that row is owned by the internal `GanttAddTaskRowPlugin` and is not a task entity. If you disable Gantt context-menu integration and provide your own `rowContextMenu`, use `isGanttAddTaskRow()` or the draft cell marker to skip that row. See [Add Task Row](/guides/gantt/interaction/add-task-row/). ## Column Menu Right-click main grid headers to open column actions: - `Search` - `Sort ascending` - `Sort descending` - `Filter column` Visibility follows the active column: - Search appears only when the task/name column has `filter` configured. - Sort actions appear only when the target column has `sortable: true`. - Filter appears only when the target column has `filter` configured. - If contextual filtering leaves no visible items, the menu is not opened. Search dispatches `gantt-search` with the query entered in the prompt. Sort uses RevoGrid column sorting. Filter opens the existing filter header control for the column. ## Timeline Menu Right-click the pinned Gantt timeline/canvas header to open timeline actions: - `Zoom in` - `Zoom out` Zoom actions are hidden when the current zoom level cannot move further in that direction. If neither direction is available, the menu is not opened. ## Read-Only And Locked State The row menu respects packaged Gantt protection state: - `gantt.readOnly: true` hides mutating row actions. - Selected locked tasks hide hierarchy actions. - Mutation services still guard direct calls, so blocked task operations return `readonly-task`. ## Extending The Menu Use the normal RevoGrid Pro menu config surfaces. Gantt prepends its generated items and keeps your custom items. ```ts grid.rowContextMenu = { items: [ { name: 'Open task details', action: (_event, focused, _range, _close, context) => { const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow'); openTaskDetails(task?.id); }, }, ], }; ``` ## Customization Gantt context menu customization has two layers: - `gantt.contextMenu` controls the generated Gantt row, column, and timeline actions. - `rowContextMenu` and `columnContextMenu` hold application menu config that should remain in the grid even when generated Gantt actions are disabled. Use `gantt.contextMenu = false` when the application should fully own the menu: ```ts grid.rowContextMenu = { items: [ { name: 'Open planning panel', action: (_event, focused, _range, close, context) => { const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow'); openPlanningPanel(task?.id); close(); }, }, ], }; grid.gantt = { ...project, contextMenu: false, }; ``` Use partial opt-out when you want custom row actions but still want generated column and timeline actions: ```ts grid.rowContextMenu = { items: [ { name: 'Assign owner', action: (_event, focused, _range, close, context) => { const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow'); openOwnerPicker(task?.id); close(); }, }, ], }; grid.gantt = { ...project, contextMenu: { row: false, column: true, }, }; ``` Keep `gantt.contextMenu` enabled when you want to append app actions after the default Gantt actions: ```ts grid.rowContextMenu = { items: [ { name: 'Show audit log', action: (_event, focused, _range, close, context) => { const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow'); showTaskAuditLog(task?.id); close(); }, }, ], }; grid.gantt = project; ``` You can also use a `resolve` function on the base RevoGrid menu config. Gantt preserves the resolver and passes the composed config through the context menu plugin: ```ts grid.rowContextMenu = { items: [ { name: 'Open task details', action: openTaskDetailsFromMenu }, ], resolve(context) { return context.target === 'row' ? undefined : null; }, }; ``` The packaged task editor item is controlled separately: ```ts grid.ganttTaskEditorDialog = { contextMenu: false, }; ``` Set `ganttTaskEditorDialog.contextMenu` to `true` when `gantt.contextMenu` is disabled but the application still wants the default **Edit...** item. Leave it unset to follow `gantt.contextMenu`. For packaged extension items, use the built-in Gantt context-menu helpers: ```ts import { createGanttContextMenuExtensionItems } from '@revolist/revogrid-enterprise'; const customItems = createGanttContextMenuExtensionItems({ duplicate: { copyName: (task) => `${task.name ?? 'Task'} copy`, }, assignResource: { onAssignResource: (context) => { if (context.taskId) { openResourcePicker(context.taskId); } }, }, delete: false, export: { name: 'Export task rows' }, }); grid.rowContextMenu = { ...grid.rowContextMenu, items: [...(grid.rowContextMenu?.items ?? []), ...customItems], }; ``` ## Legacy Context Menu Row actions use `grid.rowContextMenu`. Column actions use `grid.columnContextMenu`. Generated Gantt items are removed and rebuilt when the Gantt context menu tool refreshes, so application-provided items are not duplicated. The packaged task editor follows `gantt.contextMenu` by default for its **Edit...** row-menu item. Set `ganttTaskEditorDialog.contextMenu` to `false` to disable only the editor item, or `true` to keep the editor item when generated Gantt menus are disabled. --- # Dedicated Group Docs URL: https://pro.rv-grid.com/guides/gantt/features/dedicated-group-docs/ Source: src/content/docs/guides/gantt/features/dedicated-group-docs.mdx Description: Choose the right grouped Gantt feature recipe page for toolbar, editing, grid, export, rendering, integration, and framework work. The Gantt quick-win helpers are documented in four grouped recipe pages. Each group keeps related APIs together so you can start from the workflow you are building instead of searching individual source files. Use this page as the routing map. ## Toolbar And Navigation Use [Toolbar And Navigation](../toolbar-and-navigation/) when you are building buttons, menus, or command-palette entries for common Gantt actions. Coverage: - CSV export. - Excel export. - Baseline capture. - Critical-path toggle. - Today navigation. - Fit-to-project navigation. ## Editing, Validation, Diagnostics, And Popovers Use [Editing, Validation, Diagnostics](../editing-validation-diagnostics/) when you need policy decisions, task forms, popovers, or diagnostic summary data. Coverage: - Role-based editability. - Custom task editor forms. - Task detail popover model. - Dependency validation summary. - Resource over-allocation summary. - Forbidden-date, locked-phase, approval-gate, and role-guard validation recipes. ## Grid, Export, Rendering, And Context Menu Use [Grid, Export, Rendering](../grid-export-rendering/) when you are shaping projected Gantt rows for grids, exports, indicators, or menu extensions. Coverage: - Gantt-specific Excel row mapping. - Gantt column presets. - Read-only and locked-task indicators. - Context-menu extension recipes. For the built-in menu behavior itself, use [Gantt Context Menu](../context-menu/). ## Integrations And Frameworks Use [Integrations And Frameworks](../integrations-and-frameworks) when you are moving project snapshots between files, APIs, databases, print flows, or framework examples. Coverage: - REST project snapshot adapter. - GraphQL project snapshot adapter. - Supabase/PostgreSQL persistence helpers. - JSON snapshot helpers. - Print/PDF recipe. - Svelte example. - Resource filter demo. For standalone JSON snapshot behavior, use [JSON Project Import And Export](../json-project-import-export/). ## Companion Demo The grouped docs pair with the runnable Gantt Feature Recipes demo, which exposes registered helper source files through the code drawer. [Open Gantt Feature Recipes](https://demo.rv-grid.com/gantt-example-recipes/ts) --- # Editing, Validation, Diagnostics URL: https://pro.rv-grid.com/guides/gantt/features/editing-validation-diagnostics/ Source: src/content/docs/guides/gantt/features/editing-validation-diagnostics.mdx Description: Wire Gantt permission checks, editor forms, popovers, validation, and diagnostics panels. These APIs are presentation-free. They make decisions and shape data, while your application owns forms, popovers, panels, toasts, and persistence. ```ts import { GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT, GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT, GANTT_BEFORE_TASK_CHANGE_EVENT, TASK_EDITOR_FIELD_SCHEMA, buildDependencyValidationSummary, buildResourceOverallocationSummary, createApprovalGateValidator, createForbiddenDateRangeValidator, createGanttBeforeTaskChangeValidationHandler, createGanttRoleEditabilityHelpers, createLockedPhaseValidator, createRoleGuardValidator, createTaskDetailPopoverModel, createTaskEditorFormValues, normalizeTaskEditorSubmit, } from '@revolist/revogrid-enterprise'; ``` ## Role-Based Editability Before-change events are cancelable. Use role helpers to decide whether a task, dependency, or assignment mutation should proceed. Call `event.preventDefault()` to stop the packaged mutation before the Gantt store changes; timeline previews are cleared and the next render keeps the previous task, dependency, or assignment data. ```ts const policy = { roles: { viewer: [], planner: ['task:*', 'dependency:create', 'dependency:delete'], staffing: ['assignment:edit'], admin: ['*'], }, fallback: 'deny', } as const; const editability = createGanttRoleEditabilityHelpers(policy); const getRoleContext = () => ({ roles: currentUser.roles }); grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => { if (!editability.canEditTask(event.detail, getRoleContext())) { event.preventDefault(); } }); grid.addEventListener(GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT, (event) => { if (!editability.canEditDependency(event.detail, getRoleContext())) { event.preventDefault(); } }); grid.addEventListener(GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT, (event) => { if (!editability.canEditAssignment(event.detail, getRoleContext())) { event.preventDefault(); } }); ``` Task before-change actions cover `move`, `resize`, `create`, `edit`, `progress`, `split`, `indent`, `outdent`, and `delete`. Dependency before-change actions cover `create`, `delete`, `edit`, and `replace`; `replace` is emitted by predecessor/successor inline field edits and includes `previousDependencies` plus proposed `dependencies` because one cell edit can add, update, and remove multiple links. Assignment edits emit proposed `assignments` and `previousAssignments`. For task edits, `event.detail.sourcePatch` contains the flat source-row fields that the packaged Gantt store will write. Use it when persisting custom row fields or when your backend uses public source names such as `duration`, `percentDone`, and `taskMode`. ```ts grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => { const { sourceIndex, sourcePatch, previousSourceValues } = event.detail; if (sourceIndex === undefined || !sourcePatch) { return; } queueTaskRowPersistence({ rowIndex: sourceIndex, patch: sourcePatch, previousValues: previousSourceValues ?? {}, }); }); ``` ## Custom Task Editor Forms Use the field schema and initial value builder to render a framework form, then normalize submitted values into a `TaskUpdate` patch. For a complete standalone UI example, see [Task Editing](../../interaction/task-editing/). ```ts const fields = TASK_EDITOR_FIELD_SCHEMA.filter((field) => ['name', 'startDate', 'endDate', 'progressPercent', 'resourceLabels'].includes(field.id), ); const values = createTaskEditorFormValues(task, resources, assignments); renderTaskEditor({ fields, values, onSubmit(nextValues) { const result = normalizeTaskEditorSubmit(task, nextValues); if (!result.ok) { renderErrors(result.errors); return; } updateTask(task.id, result.patch); }, }); ``` ## Task Detail Popovers `createTaskDetailPopoverModel()` returns sections, fields, warnings, and resource labels. Your app handles hover/focus, positioning, and rendering. ```ts const model = createTaskDetailPopoverModel({ row, task, resources, assignments, }); renderPopover({ title: model.title, subtitle: model.subtitle, sections: model.sections, resources: model.resourceAssignments.map((assignment) => assignment.displayValue), }); ``` ## Dependency Diagnostics Use dependency summaries to power badges, side panels, or task focus commands. ```ts const summary = buildDependencyValidationSummary({ conflicts: schedulerResult.conflicts, issues: schedulerResult.issues, rows: projectedRows, }); renderDiagnosticsPanel({ count: summary.totalCount, groups: summary.groups, }); ``` ## Resource Over-Allocation Diagnostics Use resource summaries when staffing dashboards need to show who is overloaded and when. ```ts const overloads = buildResourceOverallocationSummary({ rows: resourcePlanningRows, loadSummaries: resourceLoadSummaries, conflicts: schedulerResult.conflicts, }); renderResourceHealth({ count: overloads.totalCount, peakOverloadUnits: overloads.peakOverloadUnits, groups: overloads.groups, }); ``` ## Custom Validation Recipes Compose validators and attach the generated handler to `gantt-before-task-change`. ```ts const validators = [ createRoleGuardValidator({ policy }), createForbiddenDateRangeValidator({ ranges: [{ startDate: '2026-05-20', endDate: '2026-05-27', label: 'release freeze' }], }), createLockedPhaseValidator({ lockedPhaseIds: ['closed'], getPhaseId: (detail) => detail.taskId ? phaseByTaskId[detail.taskId] : null, }), createApprovalGateValidator({ actions: ['move', 'resize', 'progress'], requiresApproval: (detail) => detail.changes.progressPercent === 100, }), ]; grid.addEventListener( GANTT_BEFORE_TASK_CHANGE_EVENT, createGanttBeforeTaskChangeValidationHandler({ context: () => validationContext, validators, onReject: ({ decision }) => showValidationToast(decision.message ?? 'Change rejected'), }), ); ``` --- # Grid, Export, Rendering URL: https://pro.rv-grid.com/guides/gantt/features/grid-export-rendering/ Source: src/content/docs/guides/gantt/features/grid-export-rendering.mdx Description: Use Gantt row export mapping, column presets, status indicators, and context-menu extension recipes. These helpers package common grid-side Gantt work without coupling your application to internal timeline layout data. ```ts import { createGanttColumnPreset, createGanttContextMenuExtensionItems, createGanttExcelExportRecipe, createGanttPdfExcelReportingRecipe, createGanttTaskStatusIndicatorRecipe, createTaskStatusIndicators, mapGanttRowsToExcelExport, } from '@revolist/revogrid-enterprise'; ``` ## Gantt Excel Export Mapping Use `mapGanttRowsToExcelExport()` when exporting projected task rows. The mapper uses an allowlist of user-facing fields and skips renderer-only timeline metadata. ```ts const rows = mapGanttRowsToExcelExport(taskRows, project, { includeResourceRows: false, }); downloadWorkbook({ sheetName: project.name, rows, }); ``` For shells that prefer a recipe object: ```ts const recipe = createGanttExcelExportRecipe({ rows: taskRows, project, }); downloadWorkbook({ fields: recipe.fieldNames, rows: recipe.rows, }); ``` ## PDF And Excel Reporting Use the combined reporting recipe when your app has separate toolbar buttons for stakeholder PDF reports and spreadsheet handoff. PDF uses the browser print dialog, so users can choose **Save as PDF**. Excel dispatches the Pro `export-excel` event, so include `ExportExcelPlugin` with the grid plugins. ```ts import { GanttPlugin, createGanttPdfExcelReportingRecipe, } from '@revolist/revogrid-enterprise'; import { ExportExcelPlugin } from '@revolist/revogrid-pro'; const grid = document.querySelector('revo-grid'); const host = document.querySelector('#gantt-report'); if (!grid || !host) { throw new Error('Gantt report target was not found'); } grid.plugins = [GanttPlugin, ExportExcelPlugin]; grid.gantt = project; const reportRange = { startDate: '2026-05-01', endDate: '2026-05-31', }; const reporting = createGanttPdfExcelReportingRecipe({ grid, target: host, document, window, excel: { workbookName: `${project.name}.xlsx`, }, }); pdfButton.addEventListener('click', () => { const result = reporting.exportPdf(project, reportRange); if (!result.ok) { showExportError(result.reason); } }); excelButton.addEventListener('click', () => { reporting.exportExcel({ sheetName: 'Tasks', }); }); ``` For a single "export report" action, call both commands together: ```ts const result = reporting.exportReport(project, { range: { label: 'Executive status report' }, excel: { sheetName: 'Task register' }, }); if (!result.pdf.ok) { showExportError(result.pdf.reason); } ``` ## Column Presets Column presets group the default Gantt task-table columns into common planning views. ```ts grid.columns = createGanttColumnPreset('schedule'); grid.source = [...taskRows]; ``` Available presets include `core`, `schedule`, `dependencies`, `resources`, `baseline`, `cost`, `progress`, and `all`. ## Read-Only And Locked Indicators Use indicator helpers to produce labels and class names for custom cell renderers. ```ts const status = createTaskStatusIndicators({ taskId: row.id, projectReadOnly, taskLocked: lockedTaskIds.has(row.id), }); renderIndicatorCell({ className: status.classNames.join(' '), title: status.label, indicators: status.indicators, }); ``` For multiple rows: ```ts const indicatorRows = createGanttTaskStatusIndicatorRecipe({ rows: taskRows, projectReadOnly, lockedTaskIds, }); ``` ## Context Menu Extensions Gantt installs separate RevoGrid Pro row and column context menus. Row menus are task-oriented (`Add`, `Convert to milestone`, `Indent`, `Outdent`, `Delete`). Main grid column menus expose search, sort, and filter actions. The pinned Gantt canvas column menu exposes timeline zoom controls. Use the built-in context-menu helpers with `rowContextMenu` to add duplicate, assign-resource, export, or custom task actions. ```ts const contextMenuItems = createGanttContextMenuExtensionItems({ duplicate: { copyName: (task) => `${task.name ?? 'Task'} copy`, }, delete: { confirm: (context) => window.confirm(`Delete ${context.task?.name ?? 'task'}?`), onDeleteTask: (context) => { if (context.taskId) { deleteTask(context.taskId); } }, }, assignResource: { onAssignResource: (context) => { if (context.taskId) { openResourcePicker(context.taskId); } }, }, export: { exportOptions: (context) => ({ workbookName: 'gantt.xlsx', sheetName: context.task?.name ?? 'Gantt', }), }, }); grid.rowContextMenu = { ...grid.rowContextMenu, items: [...(grid.rowContextMenu?.items ?? []), ...contextMenuItems], }; ``` Disable individual items with `false`. ```ts const exportOnly = createGanttContextMenuExtensionItems({ duplicate: false, delete: false, assignResource: false, export: { name: 'Export visible Gantt tasks' }, }); ``` --- # Helper Preview Wiring URL: https://pro.rv-grid.com/guides/gantt/features/helper-preview-wiring/ Source: src/content/docs/guides/gantt/features/helper-preview-wiring.mdx Description: How Gantt helper source previews connect the docs examples page to the runnable Feature Recipes demo. The Gantt helper preview wiring is implemented. It connects the examples documentation, source-code preview cards, and the runnable `gantt-example-recipes` demo so each packaged helper can be inspected from docs and opened in the demo portal. This is documentation and demo infrastructure. It does not change the Gantt runtime API. ## What Is Wired The [Gantt Examples](../../examples/) page renders `GanttExampleHelperSources`, which reads the `gantt-helper-sources` widget category and shows each helper as a card with: - grouped tags; - the registered source path; - an "Open preview" link; - an expandable inline source preview. Each preview link targets the Feature Recipes demo with the helper path in the `file` query parameter: ```text /gantt-example-recipes/ts?file= ``` The default external demo origin is `https://demo.rv-grid.com`, and docs builds can override it with `PUBLIC_DEMO_PORTAL_URL` or `DEMO_PORTAL_URL`. ## Registered Helper Sources The helper source cards are declared in `apps/demos/src/docs-widgets.ts` under the `gantt-helper-sources` category. Current helper previews include: - context menu extensions; - feature helper examples; - GraphQL adapter; - grid projection examples; - persistence examples; - PostgreSQL persistence; - print/PDF recipe; - REST adapter; - validation recipes. `docs-widgets.ts` resolves each source file through `apps/demos/src/catalog/source-registry.ts`, which imports raw portal component files and raw Enterprise Gantt example files. Missing source files fail during widget creation instead of rendering broken preview cards. ## Demo Registration The `gantt-example-recipes` demo is registered in `apps/demos/src/catalog/demo-catalog.ts`. Its TypeScript, React, Vue, and Angular variants include the shared recipe demo files plus `GANTT_EXAMPLE_HELPER_FILES`. Those helper entries are marked read-only, so the demo can expose packaged source files without treating them as user-editable sandbox files. Framework preview routes are also registered: - React: `apps/demos/src/preview/react/lifecycles.tsx`; - Vue: `apps/demos/src/preview/vue/lifecycles.ts`; - Angular: `apps/demos/src/preview/angular/lifecycles.ts`. ## Runtime Companion The runnable companion demo uses `apps/portal/src/components/gantt/GanttExampleRecipesShared.ts` to compose the exported helper recipes into a visible Gantt scenario. The source-reference list in that shared file points back to representative Enterprise helper files, so docs and demo code stay aligned. Integration-only helpers, such as REST, GraphQL, PostgreSQL, JSON snapshot, and print/PDF recipes, route to the same Feature Recipes demo. They are primarily source previews because their real backends or browser print flows are application-owned. ## Source References - `apps/portal/src/content/docs/guides/gantt/examples/index.mdx` - `apps/portal/src/components/gantt/GanttExampleHelperSources.astro` - `apps/demos/src/docs-widgets.ts` - `apps/demos/src/catalog/source-registry.ts` - `apps/demos/src/catalog/demo-catalog.ts` - `apps/portal/src/components/gantt/GanttExampleRecipesShared.ts` - `packages/enterprise/gantt-recipes/` --- # Integrations And Frameworks URL: https://pro.rv-grid.com/guides/gantt/features/integrations-and-frameworks/ Source: src/content/docs/guides/gantt/features/integrations-and-frameworks.mdx Description: Persist, import, export, print, and embed Gantt projects with framework-neutral recipes. These recipes keep transport and framework ownership in your application. Inject `fetch`, state, database clients, DOM targets, and file readers/writers from your host app. ```ts import { ProjectSnapshotGraphqlAdapter, ProjectSnapshotRestAdapter, ProjectSnapshotSupabaseAdapter, buildCreateProjectSnapshotTableSql, buildLoadProjectSnapshotSql, buildSaveProjectSnapshotSql, cloneProjectSnapshot, createJsonSnapshotRecipe, createPrintPdfExportRecipe, exportProjectSnapshot, parseProjectSnapshotJson, printGanttContainer, } from '@revolist/revogrid-enterprise'; ``` ## JSON Snapshot Helpers Use JSON helpers for local import/export, file handoff, tests, undo buffers, or backend payloads. ```ts const draft = cloneProjectSnapshot(project); const json = exportProjectSnapshot(draft, { space: 2 }); const parsed = parseProjectSnapshotJson(json); if (parsed.ok) { applyProject(parsed.project); } ``` For file picker flows: ```ts const recipe = createJsonSnapshotRecipe({ readText: () => fileInputText, writeText: (_fileName, contents) => saveTextFile(contents), }); await recipe.save(project, 2); const imported = await recipe.load(); ``` ## REST Persistence `ProjectSnapshotRestAdapter` loads and saves serialized project snapshots through an injected `fetchImpl`. ```ts const adapter = new ProjectSnapshotRestAdapter({ url: '/api/gantt/project/demo', headers: { authorization: 'Bearer token' }, fetchImpl, space: 2, }); const project = await adapter.load(); await adapter.save(project); ``` ## GraphQL Persistence Use the GraphQL adapter when the backend exposes project snapshots through operations. ```ts const adapter = new ProjectSnapshotGraphqlAdapter({ url: '/graphql', fetchImpl, loadQuery: 'query LoadProject($id: ID!) { projectSnapshot(id: $id) }', saveMutation: 'mutation SaveProject($id: ID!, $snapshot: String!) { saveProjectSnapshot(id: $id, snapshot: $snapshot) }', }); const project = await adapter.load({ variables: { id: 'demo' } }); await adapter.save(project, { variables: { id: 'demo' } }); ``` ## Supabase And PostgreSQL Use SQL builders with any PostgreSQL executor, or the Supabase-like adapter with a compatible client shape. ```ts await executor.query(buildCreateProjectSnapshotTableSql()); await executor.query(buildSaveProjectSnapshotSql(project)); const result = await executor.query( buildLoadProjectSnapshotSql(project.id), ); ``` ```ts const adapter = new ProjectSnapshotSupabaseAdapter(supabaseLikeClient); await adapter.save(project); const restored = await adapter.load(project.id); ``` ## Print And Save As PDF The print recipe prepares a Gantt container for browser printing. Users can choose Print or Save as PDF in the native dialog. ```ts const result = printGanttContainer(host, { document, window, metadata: { title: project.name, subtitle: `${project.tasks.length} tasks`, range: { startDate: '2026-05-01', endDate: '2026-05-31' }, }, }); ``` For a reusable command: ```ts const printRecipe = createPrintPdfExportRecipe({ target: host, document, window, }); printRecipe.exportProject(project, { label: 'May reporting window' }); ``` ## Framework Examples The portal contains concrete framework references: - [Svelte Gantt example](https://demo.rv-grid.com/gantt-basic/ts) uses `@revolist/svelte-datagrid` in `apps/portal/src/components/gantt/GanttSvelteExample.svelte`. - [Resource Planning demo](https://demo.rv-grid.com/gantt-resource-planning/ts) shows resource planning; `apps/portal/src/components/gantt/GanttResourceFilterExample.ts` demonstrates a host-owned resource filter UI. Resource filtering updates `grid.gantt.resourceFilterIds`: ```ts import { createFilteredGanttConfig } from '@revolist/revogrid-examples/components/gantt/GanttResourceFilterExample.ts'; grid.gantt = createFilteredGanttConfig( baseConfig, selectedResourceIds, allResourceIds, ); ``` --- # JSON Project Import And Export URL: https://pro.rv-grid.com/guides/gantt/features/json-project-import-export/ Source: src/content/docs/guides/gantt/features/json-project-import-export.mdx Description: Serialize, clone, parse, and lightly validate complete Gantt project snapshots. JSON project import/export provides a framework-neutral way to move complete Gantt project snapshots between files, APIs, tests, undo buffers, and server-side workflows. The helpers operate on `ProjectSnapshot` data only. They do not depend on the grid, DOM, or framework wrappers. Import the helpers from Enterprise: ```ts import { cloneProjectSnapshot, createJsonSnapshotRecipe, exportProjectSnapshot, parseProjectSnapshotJson, } from '@revolist/revogrid-enterprise'; ``` ## Export A Snapshot Use `exportProjectSnapshot()` to serialize the current project snapshot. ```ts const json = exportProjectSnapshot(project, { space: 2 }); ``` The optional `space` value is passed to `JSON.stringify`, so you can use it for readable file exports. ## Import A Snapshot Use `parseProjectSnapshotJson()` to parse JSON into a typed result instead of throwing for common invalid payloads. ```ts const parsed = parseProjectSnapshotJson(json); if (!parsed.ok) { showImportError(parsed.error); return; } applyProject(parsed.project); ``` The parser checks the top-level project shape: - Required string fields: `id`, `name`, `version`, `currency`, `timeZone`, `primaryCalendarId`, and `updatedAt`. - Required array fields: `tasks`, `dependencies`, `resources`, `assignments`, `calendars`, and `baselines`. It intentionally performs lightweight validation. Run scheduler validation or your application-specific validation after import when you need deeper checks for dates, task references, dependency cycles, resource assignments, or business rules. ## Clone Before Editing Use `cloneProjectSnapshot()` when you need a JSON-safe deep copy before staging edits. ```ts const draft = cloneProjectSnapshot(project); draft.name = 'Scenario copy'; ``` The clone is detached from nested task, dependency, resource, assignment, calendar, and baseline arrays. ## File Picker Recipe For host applications that own file pickers and save dialogs, use `createJsonSnapshotRecipe()` with injected text readers and writers. ```ts const recipe = createJsonSnapshotRecipe({ readText: () => selectedFile.text(), writeText: (fileName, contents) => downloadTextFile(fileName, contents), fileName: (project) => `${project.id}-snapshot.json`, }); const imported = await recipe.load(); await recipe.save(project, 2); ``` The recipe throws an `Error` when load/save hooks are missing or the imported JSON fails parsing. Use the lower-level parser directly when you prefer result objects over exceptions. ## What The JSON Contains The exported JSON is the project snapshot, not rendered grid state. It includes project metadata and entity arrays: - Tasks - Dependencies - Resources - Assignments - Calendars - Baselines It does not include projected Gantt row fields, rendered timeline geometry, current scroll position, expanded row state, active selection, filters, or toolbar state. ## Persistence Adapters The REST, GraphQL, Supabase, and PostgreSQL examples build on the same JSON snapshot helpers. Use those adapters when the snapshot should be loaded from or saved to an application backend. See [Integrations And Frameworks](../integrations-and-frameworks/) for adapter examples. --- # Locked Tasks URL: https://pro.rv-grid.com/guides/gantt/features/locked-tasks/ Source: src/content/docs/guides/gantt/features/locked-tasks.mdx Description: Use task-level locking to block packaged Gantt task mutations for protected tasks. Locked tasks let an application protect individual tasks after approval, baseline capture, phase closure, or any other workflow decision. A locked task remains visible in the task table and timeline, but packaged Gantt task mutations are rejected for that task. Set `locked: true` on the flat task source row: ```ts const tasks = [ { id: 'design-signoff', parentId: null, name: 'Design sign-off', type: 'milestone', status: 'done', startDate: '2026-04-15', endDate: '2026-04-15', duration: 1, percentDone: 100, calendarId: 'standard', tags: ['approved'], locked: true, }, ]; ``` ## What It Blocks `locked: true` blocks packaged task mutations for the locked task: - Timeline task movement and resizing. - Split creation, movement, and removal. - Progress updates. - Inline task-field edits routed through the Gantt task mutation service. - Indent and outdent actions. - Child task creation under a locked parent. - Delete when the selected task subtree contains a locked task. Blocked task mutations return `readonly-task` and leave task data unchanged. ## What Still Works Locking a task does not remove it from scheduling, rendering, or inspection flows: - The task still renders in the table and timeline. - Scheduler recalculation can still read the task and include it in dependencies, critical path, baselines, and diagnostics. - Search, filter, sort, expand/collapse, scroll, and zoom remain available. - Non-locked tasks continue to edit normally. ## Boundaries The source-row `locked` field is task-level protection, not a complete permission or checkout system. Dependency changes are not globally disabled by locking a task. Assignment/resource edits are also handled by their own mutation service. Use `gantt.readOnly` when the whole project should reject packaged task, dependency, and assignment mutations. For application-specific policy, pair locked tasks with cancelable Gantt events or validation recipes. ```ts grid.addEventListener('gantt-before-task-change', (event) => { if (lockedTaskIds.has(event.detail.taskId)) { event.preventDefault(); } }); ``` ## Visual Indicators Use the status indicator helpers when you want to show a lock label, class name, icon, or tooltip in a custom renderer. ```ts const status = createTaskStatusIndicators({ taskId: row.id, taskLocked: row.locked === true, }); renderIndicatorCell({ className: status.classNames.join(' '), title: status.label, indicators: status.indicators, }); ``` The helper returns metadata only, so the host application can choose the exact icon and placement. ## Context Menu The built-in Gantt row context menu hides hierarchy actions when the selected task is locked. Mutating row actions are also guarded by the task mutation service, so direct calls still return `readonly-task`. --- # Completed Quick-Win Batch URL: https://pro.rv-grid.com/guides/gantt/features/quick-win-batch/ Source: src/content/docs/guides/gantt/features/quick-win-batch.mdx Description: Overview of the packaged Gantt quick-win helpers, toolbar actions, integration recipes, and framework examples. The completed quick-win batch packages common Gantt application work as typed helpers, examples, and toolbar actions. The APIs are presentation-free: your application owns the toolbar, forms, panels, persistence layer, and framework UI, while the helpers provide reusable command behavior and data shaping. Use this page as the map for the full batch. Each area links to the detailed recipe page with runnable snippets. ## Toolbar And Navigation Toolbar helpers expose common Gantt commands as direct functions and command descriptors: - CSV export. - Excel export. - Baseline capture. - Critical-path toggle. - Scroll to today. - Fit timeline to project. Start with [Toolbar And Navigation](../toolbar-and-navigation/). ## Editing, Validation, Diagnostics, And Popovers Feature helpers cover policy, forms, popovers, and summaries without prescribing UI: - Role-based editability checks for task, dependency, and assignment before-change events. - Custom task editor field schema, initial values, and submit normalization. - Task detail popover model. - Dependency validation summary rows. - Resource over-allocation summary rows and load details. - Validation recipes for forbidden dates, locked phases, approval gates, and role guards. Start with [Editing, Validation, Diagnostics](../editing-validation-diagnostics/). ## Grid, Export, Rendering, And Context Menu Grid-side helpers shape projected Gantt data for export, columns, indicators, and menu extensions: - Gantt-specific Excel row mapping. - Column presets for schedule, dependencies, resources, baseline, cost, progress, and all fields. - Read-only and locked-task indicator metadata. - Context-menu extension examples for duplicate, delete, assign-resource, and export actions. Start with [Grid, Export, Rendering](../grid-export-rendering/) and [Gantt Context Menu](../context-menu/). ## Integrations And Frameworks Integration helpers keep persistence and framework ownership in the host application: - JSON project snapshot clone, export, parse, load, and save helpers. - REST adapter example. - GraphQL adapter example. - Supabase/PostgreSQL SQL builders and adapter example. - Print/PDF recipe. - Resource filter UI example. - Svelte usage example. Start with [Integrations And Frameworks](../integrations-and-frameworks/) and [JSON Project Import And Export](../json-project-import-export/). ## Demo Source Discovery The helper source files are registered for the `gantt-example-recipes` demo so docs can link to inspectable examples. Use the runnable demo when you want to compare recipe source with the companion Gantt grid. [Open Gantt Feature Recipes](https://demo.rv-grid.com/gantt-example-recipes/ts) --- # Read-Only Mode URL: https://pro.rv-grid.com/guides/gantt/features/read-only-mode/ Source: src/content/docs/guides/gantt/features/read-only-mode.mdx Description: Use Gantt read-only mode to block packaged task, dependency, and assignment mutations while keeping the schedule inspectable. Read-only mode turns the packaged Gantt editing surface into an inspection surface. Users can still view the task table and timeline, navigate, search, filter, sort, zoom, and use non-mutating UI, but packaged project mutations are rejected. Enable it on the Gantt config: ```ts grid.gantt = { id: 'project-1', name: 'Project Roadmap', version: '1', currency: 'USD', timeZone: 'UTC', primaryCalendarId: 'standard', updatedAt: new Date().toISOString(), readOnly: true, }; ``` ## What It Blocks `readOnly: true` blocks mutations routed through the packaged Gantt services: - Task changes: move, resize, split, progress edits, inline task-field edits, task creation, indent, outdent, and delete. - Dependency changes: create, update, delete, and predecessor/successor field replacement. - Assignment changes: assignee/resource field updates and assignment replacement. Blocked mutations return a failure result instead of updating the stores. Task changes return `readonly-task`; dependency and assignment changes return `readonly-project`. ## What Still Works Read-only mode does not turn the grid into a static screenshot. Non-mutating workflows remain available: - Timeline rendering, task bars, dependencies, baselines, critical-path visuals, and diagnostics. - Tree expand/collapse, search, filtering, sorting, scrolling, and zoom. - External application-controlled state replacement, such as loading a different project snapshot or changing the Gantt config. The built-in Gantt context menu also hides mutating row actions when the project is read-only. ## Difference From Grid Readonly `gantt.readOnly` is a Gantt project mutation guard. It protects the packaged task, dependency, and assignment mutation paths. The RevoGrid `readonly` property is a grid-level editing setting. Use it when you also want to prevent generic cell editing behavior outside Gantt's mutation services. ```ts grid.readonly = true; grid.gantt = { ...projectConfig, readOnly: true, }; ``` --- # Toolbar And Navigation URL: https://pro.rv-grid.com/guides/gantt/features/toolbar-and-navigation/ Source: src/content/docs/guides/gantt/features/toolbar-and-navigation.mdx Description: Build custom Gantt toolbar buttons for export, baselines, critical path, and timeline navigation. These helpers let your application render its own toolbar while reusing Gantt command behavior. The built-in `defineGanttToolbar()` command bar includes an Excel export icon button by default. The button dispatches the same Pro `export-excel` event as `exportGanttExcel()`, so add `ExportExcelPlugin` to the grid when you want the packaged plugin to create the workbook. Hide it with `controls: { exportExcel: false }` or hide the export group with `controls: { export: false }`. ## Built-In Toolbar Use `defineGanttToolbar()` when you want the packaged Gantt command bar instead of rendering your own buttons. Mount it after assigning `grid.gantt` so it can read the initial visual state. ```ts import { defineGanttToolbar } from '@revolist/revogrid-enterprise'; const toolbar = document.querySelector('#gantt-toolbar'); const grid = document.querySelector('revo-grid'); if (!toolbar || !grid) { throw new Error('Gantt toolbar host or grid was not found'); } grid.gantt = projectConfig; defineGanttToolbar(toolbar, { grid, columns: [ { prop: 'wbs', label: 'WBS' }, { prop: 'name', label: 'Name' }, { prop: 'startDate', label: 'Start Date' }, { prop: 'duration', label: 'Duration' }, ], visuals: { showDependencies: true, showBaseline: false, showCriticalPath: false, }, }); ``` ### Control Visibility Every built-in toolbar option can be hidden with `controls`. Omitted control flags are visible by default, except `baseline`, which is opt-in. ```ts defineGanttToolbar(toolbar, { grid, controls: { search: false, history: false, tree: false, zoom: true, zoomOut: false, tasks: true, indent: false, outdent: false, export: false, columns: false, dependencies: true, baseline: false, criticalPath: true, }, }); ``` Parent group flags hide their child buttons: | Parent flag | Child buttons | | --- | --- | | `history` | `undo`, `redo` | | `tree` | `expandAll`, `collapseAll` | | `zoom` | `zoomIn`, `zoomOut` | | `tasks` | `addTask`, `indent`, `outdent` | | `export` | `exportExcel` | Leaf flags hide individual controls: `search`, `columns`, `dependencies`, `baseline`, and `criticalPath`. Hidden visual controls do not write their local defaults back into `grid.gantt.visuals`. You can still set hidden visual behavior directly in `grid.gantt.visuals`. The dependency toggle is display-only. Turning it off updates `grid.gantt.visuals.showDependencies = false`, which hides dependency arrows without clearing `grid.ganttDependencies` or changing scheduler dependency inputs. ```ts import { captureGanttBaseline, createGanttToolbarQuickWinActions, exportGanttCsv, exportGanttExcel, fitGanttToProject, listGanttToolbarQuickWinActions, scrollGanttToToday, toggleGanttCriticalPath, } from '@revolist/revogrid-enterprise'; ``` ## Direct Commands Use direct helpers when you already know which button was clicked. ```ts const grid = document.querySelector('revo-grid'); if (!grid) { throw new Error('Gantt grid was not found'); } await exportGanttCsv(grid, { filename: 'launch-plan' }); exportGanttExcel(grid, { workbookName: 'launch-plan.xlsx' }); await captureGanttBaseline(grid, { name: 'Sprint baseline' }); const criticalPathVisible = toggleGanttCriticalPath(grid); await scrollGanttToToday(grid, { align: 0.5 }); await fitGanttToProject(grid, { viewportWidth: grid.clientWidth }); ``` ## Command Descriptors Use `createGanttToolbarQuickWinActions()` when a framework toolbar should render generic command objects. Creating descriptors does not export, scroll, or mutate the grid. Work happens only when `run()` is called. ```ts const actions = createGanttToolbarQuickWinActions(grid, { csv: { filename: 'launch-plan' }, excel: { workbookName: 'launch-plan.xlsx' }, baseline: { name: 'Sprint baseline' }, today: { align: 0.5 }, fitToProject: { viewportWidth: grid.clientWidth }, }); for (const action of listGanttToolbarQuickWinActions(actions)) { renderButton({ id: action.id, label: action.label, title: action.title, onClick: () => void action.run(), }); } declare function renderButton(command: { id: string; label: string; title: string; onClick: () => void; }): void; ``` Disable individual commands by passing `false`. ```ts const readonlyActions = createGanttToolbarQuickWinActions(grid, { csv: false, baseline: false, }); ``` ## Behavior Notes - CSV export uses the RevoGrid export plugin when a plugin with `exportFile()` is installed. - Excel export dispatches the Pro Excel export event on the grid. - Baseline capture looks for the Gantt runtime and appends the captured snapshot to `grid.ganttBaselines`. - Critical path toggling updates `grid.gantt.visuals.showCriticalPath`. - Today and fit-to-project navigation delegate to runtime methods when available, otherwise they compute timeline coordinates and call `grid.scrollToCoordinate()`. --- # Add Task Row URL: https://pro.rv-grid.com/guides/gantt/interaction/add-task-row/ Source: src/content/docs/guides/gantt/interaction/add-task-row.mdx Description: Use the pinned Gantt add-task row and identify it with isGanttAddTaskRow. `GanttPlugin` can show a pinned bottom row for quickly creating tasks by typing a name into the task table. Enable it with `taskCreateRow: true` and `allowTaskCreate: true`: ```ts import { GanttPlugin } from '@revolist/revogrid-enterprise'; grid.plugins = [GanttPlugin]; grid.gantt = { ...project, allowTaskCreate: true, taskCreateRow: true, }; ``` The row is hidden when Gantt is read-only or when resource planning mode is active. ## How It Works `GanttPlugin` installs `GanttAddTaskRowPlugin` internally. Do not add `GanttAddTaskRowPlugin` to `grid.plugins` yourself. The internal plugin owns a draft row in the `rowPinEnd` source. That draft row is not a task entity and is not written into `grid.source`. When the user edits the `name` cell with a non-empty value, Gantt creates a new root task at the end of the project and resets the draft row. Only the `name` cell is editable. Other cells are rendered as inert cells, row drag is disabled for the draft row, and the generated Gantt context menu is suppressed on it. ## Identifying The Draft Row Use `isGanttAddTaskRow()` when custom columns or actions need to skip the draft row: ```ts import { GanttPlugin, isGanttAddTaskRow, } from '@revolist/revogrid-enterprise'; import { RowSelectPlugin } from '@revolist/revogrid-pro'; grid.plugins = [GanttPlugin, RowSelectPlugin]; grid.columns = [ { prop: 'selected', name: '', rowSelect: ({ model }) => !isGanttAddTaskRow(model), size: 56, readonly: true, filter: false, }, // Task table columns... ]; ``` This is useful for checkbox selection, custom cell renderers, and application actions that should apply only to real task rows. ## Custom Context Menus When Gantt context-menu integration is enabled, the generated row menu does not open on the add-task draft row. If your application fully disables Gantt context-menu integration and owns `rowContextMenu` itself, keep the same rule in your resolver: ```ts grid.rowContextMenu = { items: [ { name: 'Open task details', action: openTaskDetailsFromMenu }, ], resolve(context) { if (context.triggerElement.closest('[data-gantt-add-task-row-cell="true"]')) { return null; } return undefined; }, }; grid.gantt = { ...project, contextMenu: false, }; ``` For model-based checks inside item actions, read the focused row model from the action context and pass it to `isGanttAddTaskRow()`. --- # Dependency Editing URL: https://pro.rv-grid.com/guides/gantt/interaction/dependency-editing/ Source: src/content/docs/guides/gantt/interaction/dependency-editing.mdx Description: Create, select, and delete dependency links in timeline. Dependency editing supports: - Drag-to-create links from dependency handles. - Allowed-type filtering via `gantt.allowedDependencyTypes`. - Hover/select styling and inline delete UI. - Keyboard delete on selected dependency. ## Mobile And Touch Devices On touch devices, dependency handles follow the same select-then-edit model as task-bar editing: - Tap a task bar to select it. - Gantt reveals larger dependency handles on the selected bar. - Drag from a visible start or end dependency handle to another task bar. - If the pointer lands on a target handle, Gantt uses that explicit endpoint. - If the pointer lands on the target bar but not a handle, Gantt infers start or end from the pointer position relative to the bar midpoint. Hidden dependency handles are not hit-testable on coarse pointers, so a swipe near the edge of an unselected task does not start dependency creation. This keeps timeline scrolling usable on phones and tablets while preserving precise dependency editing after task selection. Implementation: - `features/dependencies/drag-controller.ts` - `features/dependencies/dependency-tool.ts` - `features/dependencies/dependency-layer.ts` --- # Drag, Resize, And Progress Editing URL: https://pro.rv-grid.com/guides/gantt/interaction/drag-resize-progress/ Source: src/content/docs/guides/gantt/interaction/drag-resize-progress.mdx Description: Timeline gesture interactions for task bars. Implemented interactions: - Drag task bars to move. - Drag start/end handles to resize. - Drag progress handle to update completion. Each interaction has preview state and commit state and can trigger warning/diagnostic updates. ## Mobile And Touch Devices Gantt supports phones, tablets, and hybrid devices through adaptive pointer handling. Desktop users can drag task bars and handles directly. Touch users get a safer select-then-edit flow so scrolling a dense timeline does not accidentally change the schedule. On coarse-pointer devices: - Tap a task bar to select it. - Selection reveals larger resize, dependency, progress, and split handles. - Drag the selected task bar to move the task. - Drag a visible start/end handle to resize. - Drag the visible progress handle to update completion. - Start swiping from an unselected task bar when you want to pan the task table or timeline; hidden handles are not hit-testable until selection. The mobile path keeps native panning available for unselected task bars and disables browser touch panning only on selected/active bars or visible edit handles. This preserves scroll-first behavior while still allowing precise edits after the user has selected the task. The packaged task editor dialog also adapts to phone-width screens with a full-viewport, single-column layout and larger touch targets. Implementation: - `features/tasks/controllers/task-drag-controller.ts` - `features/tasks/controllers/task-resize-controller.ts` - `features/tasks/controllers/task-progress-controller.ts` - `features/tasks/controllers/pointer-gesture.ts` - `features/tasks/timeline-preview-layer.ts` --- # Row Selection URL: https://pro.rv-grid.com/guides/gantt/interaction/row-selection/ Source: src/content/docs/guides/gantt/interaction/row-selection.mdx Description: Use RowSelectPlugin with Gantt task operations and row reordering. `GanttPlugin` can work with the RevoGrid Pro `RowSelectPlugin` when you want checkbox-based task selection in the task table. Add a row-select column, register `RowSelectPlugin`, and enable the row-order bridge when selected rows should move together during Gantt row reordering. ```ts import { GanttPlugin, isGanttAddTaskRow } from '@revolist/revogrid-enterprise'; import { RowSelectPlugin } from '@revolist/revogrid-pro'; const grid = document.querySelector('revo-grid'); grid.plugins = [GanttPlugin, RowSelectPlugin]; grid.rowSelect = { rowOrder: true, }; grid.columns = [ { prop: 'selected', name: '', rowSelect: ({ model }) => !isGanttAddTaskRow(model), size: 56, readonly: true, filter: false, }, // Task table columns... ]; ``` The checkbox column uses physical task rows from `grid.source`. Gantt ignores resource rows and non-task rows when resolving checkbox selection for task operations. Use `isGanttAddTaskRow()` when `taskCreateRow` is enabled so the pinned “Add new task” row does not render a checkbox. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for the draft-row contract. ## Bulk Gantt Operations When no explicit task ids are passed, Gantt resolves task ids in this order: 1. Checkbox-selected rows from `RowSelectPlugin`. 2. Gantt task range selection. 3. The currently focused or selected Gantt task. This means toolbar buttons, context-menu actions, keyboard handlers, or application code can call the existing Gantt methods without manually collecting checkbox state: ```ts const plugins = await grid.getPlugins(); const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin); gantt?.deleteTasks(); gantt?.indentTasks(); gantt?.outdentTasks(); gantt?.convertTasksToMilestones(); ``` After a successful delete, Gantt clears the row-select checkbox state for task rows so removed tasks do not remain selected in `RowSelectPlugin`. ## Row Reordering Gantt installs `RowOrderPlugin` internally. `RowSelectPlugin` does not automatically move all checked rows unless the row-select row-order integration is enabled: ```ts grid.rowSelect = { rowOrder: true, }; ``` With `rowOrder: true`, dragging a checked task row moves the visible checked task rows as one compact block. Dragging an unchecked row keeps the normal Gantt row-order behavior. ## Framework Bindings React: ```tsx !isGanttAddTaskRow(model), size: 56, readonly: true, filter: false, }, ...taskColumns, ]} /> ``` Vue: ```vue ``` Angular: ```html ``` For Vue Gantt examples, use `.prop` for `row-select` because it is a direct grid plugin property. --- # Task Editing URL: https://pro.rv-grid.com/guides/gantt/interaction/task-editing/ Source: src/content/docs/guides/gantt/interaction/task-editing.mdx Description: Inline editing, the default task editor dialog, and scheduler-backed mutation flow. Task edits route through mutation services and scheduler recomputation. Before applying task changes, Gantt emits cancelable `gantt-before-task-change`; dependency field edits emit `gantt-before-dependency-change`; assignee edits emit `gantt-before-assignment-change`. Command/event surface is defined in: - `packages/enterprise/plugins/gantt/grid/gantt-events.ts` - `packages/enterprise/plugins/gantt/grid/gantt-plugin.ts` For audited changes, pair with Gantt history tooling (`features/history`). ## Inline Editing Inline Gantt edits update the task through the Gantt mutation services, recompute the project, and emit history snapshots for user changes. Initial project hydration is not recorded as a history step. Call `event.preventDefault()` in a before-change listener to reject a field edit before the task, dependency, or assignment store is mutated. Rejected predecessor/successor edits leave the old dependency rows in place; rejected assignee edits leave the old assignment rows in place. ## Task Editor Dialog `GanttPlugin` installs the packaged task editor dialog by default, so every Gantt grid gets the row context-menu editor without registering an extra plugin. The same editor contract is also available as presentation-free helpers: render controls from `TASK_EDITOR_FIELD_SCHEMA`, seed them with `createTaskEditorFormValues()`, and turn submitted values into a `TaskUpdate` patch with `normalizeTaskEditorSubmit()`. The packaged dialog is responsive for mobile use. On phone-width viewports it becomes a full-viewport dialog, switches task fields and dependency rows to a single column, keeps the form body scrollable, and uses larger controls for touch input. The same validation and submit flow runs on desktop, tablet, and phone layouts. ## How It Works The task editor demo opens the editor from the Gantt row context menu and includes the packaged Gantt toolbar: - The grid renders normal Gantt tasks, dependencies, calendars, resources, and assignments. - Right-click a task row and choose **Edit...**. - Use row context-menu **Add** to insert a `New task N` row without opening the editor, or toolbar **Task** to create a task and open the same editor for the new row. - The auto-installed task editor dialog adds the row-menu edit item and owns the native HTML ``. - New tasks open in the editor by default after the Gantt plugin emits its post-create lifecycle event. Set `ganttTaskEditorDialog.openOnCreate` to `false` when task creation should stay silent. - The dialog renders a tabbed task information UI from `TASK_EDITOR_FIELD_SCHEMA`. - **Details** contains core schedule fields, constraints, notes, and manual scheduling. - **Advanced** contains task type, effort-driven scheduling, work, remaining duration, actual dates, inactive state, leveling priority, can-level, and leveling delay. - **Predecessors** and **Successors** edit dependency target, dependency type, and lead/lag days. - **Assignments** renders the resource assignment picker when `ganttResources` and `ganttAssignments` are available. - The patch preview calls `normalizeTaskEditorSubmit(task, values)` on every edit. - The submit button applies the returned patch through the Gantt runtime/provider edit path, updates `grid.ganttAssignments` and `grid.ganttDependencies`, and refreshes the project. ```ts grid.plugins = [GanttPlugin]; grid.ganttTaskEditorDialog = { title: 'Task details', description: 'Update task fields and save the changes to the project schedule.', applyLabel: 'Save changes', localeText: { tabs: { assignments: 'Team', }, fields: { effortMode: 'Task type', }, resourcesSearch: { placeholder: 'Find a resource', }, }, }; const values = createTaskEditorFormValues(task, resources, assignments); const result = normalizeTaskEditorSubmit(task, values); if (result.ok) { updateTask(task.id, result.patch); } ``` ## Fields The schema includes editable task fields such as `name`, `startDate`, `endDate`, `durationDays`, `progressPercent`, constraints, deadlines, actual dates, work, effort mode, effort-driven scheduling, inactive state, leveling controls, manual scheduling, and `notes`. It also includes display fields such as `resourceLabels`. Date fields open as date-only controls by default. Use the field-level **Time** toggle in the packaged dialog when you need minute-level scheduling; the dialog stores the submitted value as a UTC ISO datetime. Set `gantt.dateFormats.editor` when the packaged dialog should show custom date text instead of native date inputs. In that mode, the dialog uses a text input, calls your parser as the user types, and still submits ISO dates to `normalizeTaskEditorSubmit()` and the Gantt mutation services: ```ts grid.gantt = { ...project, dateFormats: { locale: 'en-GB', timeZone: 'UTC', table: { day: '2-digit', month: '2-digit', year: 'numeric' }, tooltip: { day: '2-digit', month: 'short', year: 'numeric' }, editor: { options: { day: '2-digit', month: '2-digit', year: 'numeric' }, parser(value) { const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value.trim()); return match ? `${match[3]}-${match[2]}-${match[1]}` : null; }, }, }, }; ``` Custom date formats are presentation and input helpers only. Keep persisted tasks as `YYYY-MM-DD` dates or UTC datetimes ending in `Z`. `resourceLabels` is read-only in the schema helper because `TaskUpdate` does not own assignment rows. The packaged dialog upgrades that field into a resource assignment picker and writes the selected resources to `grid.ganttAssignments`. Set `ganttTaskEditorDialog.resources` to `false` when you want the field to stay read-only. Entity panels include search controls: resource assignment searches by resource name and role, while predecessor and successor panels search task names. Resource lists, task dropdowns, dependency type dropdowns, and schema-backed select options are sorted A-Z by their displayed labels. Use `ganttTaskEditorDialog.localeText` to translate or customize packaged dialog text. It covers tabs, field labels, field option labels, dependency type labels, empty states, search placeholders, switch labels, validation headings, save errors, and context-menu text. Existing top-level `title`, `description`, `applyLabel`, `resetLabel`, `closeLabel`, and `menuItemName` still work as direct overrides for those common labels. Use `ganttTaskEditorDialog.openOnCreate = false` to keep toolbar or programmatic task creation from opening the dialog. The Gantt row context menu always creates silently through the default **Add** action when task creation is allowed, and the editor plugin prepends its **Edit...** item while preserving custom `rowContextMenu` or `contextMenu` items and `resolve` behavior. Set `gantt.contextMenu = false` to opt out of generated Gantt menu items, or set `ganttTaskEditorDialog.contextMenu` when only the packaged editor menu item should change. ## Validation `normalizeTaskEditorSubmit()` validates values before returning a patch: - `name` cannot be empty. - Date values must be ISO dates like `2026-05-12` or UTC datetimes ending in `Z`. - `endDate` cannot be earlier than `startDate`. - Numeric fields must be non-negative. - `progressPercent` is clamped to `0..100`. - Unsupported constraint values return field errors. If both `startDate` and `endDate` are present, the helper recalculates `durationDays` from the date range. If values are unchanged, the default result omits them from the patch. ## Production Wiring In production, keep the same flow but replace the demo's local source update with your persistence and project-state update path: ```ts async function submitTaskEditor(task, values) { const result = normalizeTaskEditorSubmit(task, values); if (!result.ok) { renderFieldErrors(result.errors); return; } await api.updateTask(task.id, result.patch); grid.source = grid.source.map((row) => row.id === task.id ? { ...row, ...result.patch } : row, ); } ``` The Gantt plugin still owns timeline projection and scheduling when the refreshed task source is applied. The packaged dialog defaults to applying task field patches through the Gantt runtime/provider edit path and replacing the edited task's assignment rows in `grid.ganttAssignments`. This keeps task editor saves visible to `HistoryPlugin` instead of bypassing grid edit tracking with direct `grid.source` replacement. To persist edits first, provide `ganttTaskEditorDialog.onSubmit`; return `false` from that callback when your application has already applied the task and assignment updates and the plugin should skip the default patch. Dependency tabs also patch `grid.ganttDependencies` by replacing links that involve the edited task. Existing dependency ids are preserved, new rows get deterministic ids, and lag supports negative lead or positive lag days. Set `preview: true` only for diagnostics or documentation when you want to show the generated `TaskUpdate` JSON. --- # Installation and Setup URL: https://pro.rv-grid.com/guides/gantt/introduction/installation/ Source: src/content/docs/guides/gantt/introduction/installation.mdx Description: Install @revolist/revogrid-enterprise and initialize your first Gantt project. ## Installation To use the Enterprise Gantt features, you must first install the enterprise package: ```bash pnpm add @revolist/revogrid-enterprise ``` ### Registering the Plugin Register the `GanttPlugin` in your grid instance. This adds the scheduling engine, timeline projection, and Gantt-specific data handling to RevoGrid. ```typescript import { GanttPlugin } from '@revolist/revogrid-enterprise'; grid.plugins = [GanttPlugin]; ``` Gantt styles are included automatically from the enterprise package. Ensure your build system supports SCSS or CSS imports from node_modules. ## Basic Project Setup A Gantt project requires a core configuration object, at least one calendar, and a task source. Below is a minimal setup: ```typescript grid.gantt = { id: 'project-1', name: 'Project Alpha', version: '1', currency: 'USD', timeZone: 'UTC', primaryCalendarId: 'cal-standard', updatedAt: new Date().toISOString(), }; grid.ganttCalendars = [{ id: 'cal-standard', name: 'Standard', timeZone: 'UTC', workingDays: [1, 2, 3, 4, 5], // Monday to Friday holidays: [], hoursPerDay: 8, }]; grid.source = [ { id: 'task-1', name: 'Initial Research', startDate: '2026-04-06', duration: 5 } ]; ``` Once the basic project is initialized, you can incrementally add [Dependencies](../concepts/dependencies), [Resources](../scheduling/resources), and [Baselines](../concepts/baselines). ## Task Table Columns Gantt specific columns can be added to your grid using the `createDefaultTaskTableColumn` utility. These columns are designed to work with the scheduling engine and timeline. ```typescript import { createDefaultTaskTableColumn } from '@revolist/revogrid-enterprise'; grid.columns = [ createDefaultTaskTableColumn('wbs'), createDefaultTaskTableColumn('name'), createDefaultTaskTableColumn('startDate'), createDefaultTaskTableColumn('duration'), createDefaultTaskTableColumn('predecessors'), ]; ``` ### Key Column Concepts: - **Inputs**: Columns like `name`, `startDate`, and `duration` are typically editable and drive the scheduler. - **Projected Outputs**: Columns like `earlyStartDate`, `lateStartDate`, and `totalSlackDays` are computed by the scheduler and are read-only. - **Custom Columns**: You can mix standard RevoGrid columns with Gantt-specific columns in the same `grid.columns` array. --- # What Is A Gantt Chart? URL: https://pro.rv-grid.com/guides/gantt/introduction/what-is-gantt/ Source: src/content/docs/guides/gantt/introduction/what-is-gantt.mdx Description: Core Gantt concepts used by RevoGrid Enterprise Gantt. > **Gantt = structured timeline for tasks with dependencies** A **Gantt chart** is a **visual representation of a plan over time**. It’s a way to show tasks, their durations, and how they relate to each other on a timeline. It answers only three things: * **What** tasks exist * **When** they happen * **In what order / relation** they happen --- ## Core features of a pure Gantt #### 1. Task model Each task has: * id * start date * end date (or duration) * optional parent (hierarchy) 👉 This is the **data backbone** --- #### 2. Timeline rendering * Horizontal time axis * Tasks rendered as bars * Zoom levels (day / week / month) 👉 Without this, it’s not a Gantt --- #### 3. Dependencies (defining feature) Tasks can depend on each other: * Finish → Start * Start → Start * Finish → Finish 👉 This is what makes it a **planning tool**, not just visualization --- #### 4. Hierarchy (WBS — work breakdown structure) * Parent / child tasks * Expand / collapse 👉 Lets you represent real projects, not flat lists --- #### 5. Direct manipulation * Drag task → change start date * Resize → change duration 👉 If this is missing → it's just a chart, not a planning tool --- #### 6. Milestones * Tasks with zero duration * Represent key events 👉 Needed for any serious usage --- #### 7. Progress (basic tracking) * % completion per task * Visual overlay on bars 👉 Not scheduling — just status --- #### 8. Large data handling * Thousands of tasks * Smooth scroll * Virtualization 👉 This is our competitive edge --- ## What is NOT part of Gantt (important boundary) ❌ Assigning people to time slots ❌ Calendar conflicts ❌ Shift planning ❌ Workforce shift-slot scheduling across personnel calendars 👉 That’s **scheduler territory** layered on top of Gantt fundamentals --- ## Mental model **“Grid-first Gantt”** A Gantt is: ```text Hierarchy (tree) + Timeline (x-axis) + Dependencies (graph edges) ``` * Left side = grid (tasks, editable) * Right side = timeline (bars) --- # What Is A Scheduler? URL: https://pro.rv-grid.com/guides/gantt/introduction/what-is-scheduler/ Source: src/content/docs/guides/gantt/introduction/what-is-scheduler.mdx Description: Core scheduler concepts used by RevoGrid Enterprise Scheduler. > **Scheduler = Gantt + resource management + auto-calculation** Scheduling (especially in systems like Gantt/scheduler products) is built on a few **core components**. If any of these are weak or missing, the whole system becomes unreliable or hard to scale. Here’s the structured breakdown: --- ## 🧱 1. Time Model (Foundation) This defines how time is represented and computed. **Key elements:** * Time scale (minutes, hours, days, weeks) * Working vs non-working time (calendars) * Time zones * Precision (e.g., milliseconds vs day-level) 👉 Without a solid time model, everything else breaks (especially dependencies and recalculation). --- ## 📅 2. Calendar System Controls when work **can** happen. **Includes:** * Global calendar (e.g., Mon–Fri, 9–5) * Resource calendars (different schedules per person/machine) * Exceptions: * holidays * custom non-working days * overtime rules 👉 This directly affects duration calculations and task placement. --- ## 📦 3. Task Model Core data structure of scheduling. **Typical fields:** * Start date * End date * Duration * Progress (%) * Constraints (e.g., “start no earlier than”) * Priority 👉 Advanced systems treat **duration as derived**, not primary. --- ## 🔗 4. Dependency Engine (Critical) Defines relationships between tasks. **Types:** * Finish → Start (most common) * Start → Start * Finish → Finish * Start → Finish **Also includes:** * Lag / lead time * Dependency validation * Cycle detection 👉 This is where real scheduling complexity begins. --- ## ⚙️ 5. Scheduling Engine (Core Logic) This is the “brain”. **Responsibilities:** * Auto-calculate task dates * Resolve dependencies * Apply constraints * Recalculate on changes (reactivity) Two main approaches: * **Forward scheduling** (ASAP) * **Backward scheduling** (ALAP) 👉 This must be deterministic and performant. --- ## 👥 6. Resource Management Links tasks to resources. **Includes:** * Resource assignment * Capacity tracking * Over-allocation detection * Load balancing 👉 Without this, it’s just a timeline—not real scheduling. --- ## 📊 7. Aggregation & Rollups Needed for higher-level visibility. **Examples:** * Parent task duration = sum or span of children * Progress rollup * Resource usage summaries 👉 Important for portfolio-level views and reporting. --- ## 🔄 8. State & Recalculation System Handles updates efficiently. **Includes:** * Incremental recalculation * Change propagation * Undo/redo 👉 Critical for UX in large datasets. --- ## 🖥️ 9. View Layer (Gantt / Scheduler UI) How users interact with the schedule. **Components:** * Timeline grid * Drag & drop * Resize tasks * Zoom levels * Virtualization (for large datasets) 👉 This is where RevoGrid-level performance matters. --- ## 📡 10. Data Source Strategy How data flows. **Options:** * Client-side (all data loaded) * Server-side (page-based) * Hybrid 👉 For large-scale apps → server-side becomes mandatory. --- ## 🔐 11. Constraints & Rules System Business logic layer. **Examples:** * Must start on specific date * Fixed duration * Resource restrictions * Custom validation rules --- ## 🧠 12. Observability & Debugging (Often Missing) Advanced but crucial. **Includes:** * Why did task move? * Dependency trace * Conflict explanation --- ## 📌 If you reduce it to essentials: The **minimum viable scheduling system** is: 1. Time model 2. Task model 3. Dependency engine 4. Scheduling engine 5. Calendar Everything else builds on top. --- # Configuration Reference URL: https://pro.rv-grid.com/guides/gantt/reference/configuration-reference/ Source: src/content/docs/guides/gantt/reference/configuration-reference.mdx Description: Human-readable map of public Gantt config objects. Public config entry points: - `GanttPluginConfig` - `weekStartsOn?: 0 | 1` controls week header/tick alignment. Omit for Sunday (`0`); set `1` for Monday. - `timelinePrecision?: 'day' | 'hour'` controls the default precision for timeline task create, move, and resize interactions. Use `'hour'` with `zoomPreset: 'hour-day'` or `zoom.defaultLevelId: 'hour-day'` for hour-scale planning. - `snap?: { unit?: 'day' | 'hour'; workingTime?: boolean }` controls interaction snapping. `unit: 'hour'` keeps interactions on hour boundaries; `workingTime: true` or omitted skips holidays, closed weekdays, and closed `calendar.workingHours`. - `calendars?: CalendarEntity[]` can include intraday `workingHours` such as `{ start: '09:00', end: '17:00' }` plus `hoursPerDay` for hour-mode snapping and scheduling. - `taskCreateRow?: boolean` shows an opt-in pinned bottom “Add new task” row. It requires `allowTaskCreate: true` and is hidden for read-only or resource-planning views. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for `GanttAddTaskRowPlugin` behavior and `isGanttAddTaskRow()` usage. - `GanttSchedulingConfig` - `GanttResourcePlanningConfig` - `GanttVisualConfig` ## Adaptive Mobile Interactions Gantt automatically adapts pointer interactions for phones, tablets, and other coarse-pointer devices. There is no mobile-only configuration flag; the same `GanttPluginConfig` works across desktop and touch devices. Mobile behavior is designed around **select-then-edit**: - Mouse and trackpad users keep direct desktop interactions for moving, resizing, progress updates, dependency drawing, timeline panning, and split-panel resizing. - Touch users tap a task bar first to select it and reveal larger resize, progress, dependency, and split handles. - Dragging a selected task bar moves the task. Dragging a visible handle resizes, updates progress, edits splits, or creates dependencies. - Plain touch scrolling in the task table or timeline remains scroll-first and should not move, resize, or create tasks accidentally. - Hidden coarse-pointer handles are not hit-testable until the task is selected or interaction-active, so an edge swipe on an unselected task still behaves like navigation. - Task editor dialogs switch to a single-column, full-viewport layout on narrow screens with larger controls for touch input. The Enterprise E2E suite includes a dedicated mobile project using an iPhone viewport. It verifies that unselected task-bar swipes do not mutate dates, tapping selects a task and exposes handles, and the task editor dialog fits a phone-sized viewport. Canonical definitions: - `packages/enterprise/plugins/gantt/grid/gantt-plugin.types.ts` For generated signatures, see [API: Gantt](/api/gantt/). --- # Limitations And MSP Differences URL: https://pro.rv-grid.com/guides/gantt/reference/limitations/ Source: src/content/docs/guides/gantt/reference/limitations.mdx Description: Current Gantt scheduling boundaries and intentional differences. Implemented behavior is MSP-inspired, not full MSP parity. Current boundaries: - Auto-leveling is forward-schedule oriented; backward mode stays warning-only. - Leveling is delay-based and does not auto-insert new split ranges. - Constraint and validation coverage is deterministic but narrower than full MSP option matrix. - Docs describe current code behavior in `packages/enterprise/plugins/gantt`, not aspirational features. --- # Troubleshooting URL: https://pro.rv-grid.com/guides/gantt/reference/troubleshooting/ Source: src/content/docs/guides/gantt/reference/troubleshooting.mdx Description: Common Gantt setup and scheduling issues. Common checks: - Missing `ganttCalendars` or mismatched `calendarId`. - Duplicate IDs in tasks/dependencies/resources/assignments. - Dependency points to missing task ID. - A circular dependency chain such as `Setup load balancer -> Configure firewall -> Setup load balancer`. The scheduler reports this as `dependency-cycle`; inspect the issue's `taskIds` and `dependencyIds`, then remove or change one of the links. - Invalid task date ranges or malformed constraints. - No visible timeline because viewport/zoom span does not include task dates. ## Task Jumps While Dragging If a task appears to jump during drag or resize, first check for dependency validation issues. Automatic tasks normally clamp to the earliest date allowed by their predecessors. A circular dependency cannot produce a valid predecessor order, so RevoGrid reports `dependency-cycle` and leaves dependency clamping disabled until the cycle is fixed. Use the scheduling result passed to your diagnostics flow to locate the invalid links: ```ts const cycleIssue = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle'); if (cycleIssue) { console.table({ tasks: cycleIssue.taskIds.join(', '), dependencies: cycleIssue.dependencyIds.join(', '), }); } ``` Validation and diagnostics are produced by: - `engine/scheduler-engine/validation.ts` - `engine/scheduler-types.ts` --- # Resource Planning View URL: https://pro.rv-grid.com/guides/gantt/resources/resource-planning-view/ Source: src/content/docs/guides/gantt/resources/resource-planning-view.mdx Description: Visualize resource-centric timelines and daily/weekly load bars. The Resource Planning View switches the Gantt chart from a task-centric WBS to a resource-centric view. This allows managers to visualize daily or weekly load, capacity thresholds, and over-allocation hotspots. ## Enabling the Planning View Use `gantt.resourcePlanning` to toggle the view and configure its behavior: ```typescript grid.gantt = { resourcePlanning: { enabled: true, visibleResourceIds: ['alex', 'nina', 'ravi'], loadGranularity: 'day', capacityDisplay: 'line', overAllocationDisplay: 'highlight', }, }; ``` ### Configuration Options | Option | Description | |---|---| | `enabled` | Toggle between task rows and resource load rows. | | `visibleResourceIds` | Array of resource IDs to display in the planning view. | | `loadGranularity` | `'day'` or `'week'`. The time bucket for load calculation. | | `capacityDisplay` | `'line'` or `'none'`. Renders a capacity threshold on load bars. | | `overAllocationDisplay` | `'highlight'` or `'none'`. Marks buckets exceeding capacity. | ## Resource Filtering When you are in the normal task view, you can use `resourceFilterIds` to filter tasks assigned to specific resources while keeping ancestor summary tasks visible. This provides context-aware filtering within the project structure. ```typescript grid.gantt = { resourceFilterIds: ['alex'], }; ``` The Resource Planning View is highly dynamic. Changes to task dates or assignment units on the main grid are immediately reflected in the resource load bars. --- # Resources and Assignments URL: https://pro.rv-grid.com/guides/gantt/resources/resources-and-assignments/ Source: src/content/docs/guides/gantt/resources/resources-and-assignments.mdx Description: Define resource capacity, assignments, and how they interact with task work. Resources define the people, equipment, or facilities that perform work on your tasks. Assignments connect these resources to specific tasks with defined allocation units and work hours. ## Data Model Resources and assignments are managed through two distinct collections: ```typescript grid.ganttResources = [{ id: 'alex', name: 'Alex', role: 'Engineer', calendarId: 'cal-standard', allocationCapacity: 100, // 100% capacity hourlyCost: 120, }]; grid.ganttAssignments = [{ id: 'assign-1', taskId: 'task-1', resourceId: 'alex', allocationUnits: 100, // 100% assignment workHours: 40, responsibility: 'Owner', }]; ``` ### Capacity and Units `allocationCapacity` and `allocationUnits` accept either a percent scale (`100`) or a decimal scale (`1.0`) for full capacity. The scheduling engine uses these values to calculate resource load and identify over-allocations. ## Resource Over-Allocation When the `resourceLeveling` policy is set to `'warn'`, the scheduler calculates allocation by resource and working date. If assigned units exceed the resource's capacity on any given day, it reports a `resource-overallocation` conflict. ```typescript grid.gantt = { scheduling: { resourceLeveling: 'warn' }, }; ``` ### Example: Over-Allocation Warning If one resource is assigned to multiple overlapping tasks at 100% each, a warning will be triggered: ```typescript grid.ganttResources = [{ id: 'alex', name: 'Alex', allocationCapacity: 100, }]; grid.ganttAssignments = [ { taskId: 'build', resourceId: 'alex', allocationUnits: 100 }, { taskId: 'docs', resourceId: 'alex', allocationUnits: 100 }, ]; ``` **Expected result**: On days where `build` and `docs` overlap, Alex is allocated at 200%. Each affected task receives a `resource-overallocation` warning in the grid and on the timeline. --- ## Next Steps - **[Resource Planning View](../resource-planning-view)**: Visualize resource load on a dedicated timeline. - **[Resource Leveling](../scheduling/leveling)**: Automatically resolve over-allocations by delaying tasks. - **[Effort Modes](../scheduling/effort-modes)**: Control how assignments affect task duration. --- # Calendars and Duration URL: https://pro.rv-grid.com/guides/gantt/scheduling/calendars/ Source: src/content/docs/guides/gantt/scheduling/calendars.mdx Description: Define working days, holidays, and how they affect task duration calculations. By default, `duration` is measured in calendar days. Enable working-day duration when weekends and holidays should be skipped: ```typescript grid.gantt = { scheduling: { excludeHolidaysFromDuration: true, }, }; ``` ## Defining Calendars Calendars use ISO weekday numbers where Monday is `1` and Sunday is `7`: ```typescript grid.ganttCalendars = [{ id: 'cal-standard', name: 'Standard', timeZone: 'UTC', workingDays: [1, 2, 3, 4, 5], holidays: ['2026-05-25'], hoursPerDay: 8, }]; ``` Each task references a calendar through `calendarId`. Resource capacity checks use the resource calendar when a resource is assigned. ## Working-Day Lag Use working-day lag when dependency offsets should skip weekends and holidays: ```typescript grid.gantt = { scheduling: { lagCalendar: 'working-days', }, }; ``` **Example**: A one-day lag after a Friday skips the weekend and a Monday holiday, landing on Tuesday. Each task references a calendar via its `calendarId` field. Tasks on different calendars respect their own working days and holidays independently. --- # MSP Compatibility URL: https://pro.rv-grid.com/guides/gantt/scheduling/compatibility/ Source: src/content/docs/guides/gantt/scheduling/compatibility.mdx Description: Understand how RevoGrid Gantt maps to Microsoft Project scheduling concepts and behavior. The table below describes how RevoGrid Gantt maps to Microsoft Project (MSP) concepts. This ensures a familiar experience for planners transitioning from desktop project management tools. | MSP concept | RevoGrid Gantt behavior | |---|---| | Manual vs automatic task mode | `taskMode: 'manual'` preserves authored dates and warns on dependency violations. Auto tasks are dependency-safe and are clamped to valid dates. | | Scheduled Start / Finish | `startDate` and `endDate` in the rendered row are the scheduler's effective dates. For manual tasks, authored dates remain unless a warning is reported. | | ASAP / ALAP | `scheduleFrom: 'project-start'` gives ASAP-style forward scheduling. `scheduleFrom: 'project-finish'` gives ALAP-style backward scheduling. | | Dependencies | FS, SS, FF, and SF dependencies are supported. Positive `lagDays` delays a successor; negative `lagDays` is lead time. | | Constraints | Six date constraints are supported: SNET, SNLT, FNET, FNLT, MSO, and MFO. | | Deadlines | `deadlineDate` produces a warning indicator when a task finishes late. It does not move the task, matching MSP's deadline intent. | | Resource over-allocation | `resourceLeveling: 'warn'` reports capacity violations. `resourceLeveling: 'auto'` performs deterministic delay-based leveling for eligible automatic tasks. | | Effort/task type | `effortMode` supports `fixed-duration`, `fixed-work`, and `fixed-units`. Fixed-work and fixed-units can derive duration from work hours and assignment units. | | Actuals and remaining duration | `actualFinishDate` pins completed work. `actualStartDate` and `remainingDurationDays` pin in-progress work when `progressRescheduling` is enabled. | | Split tasks | `splitRanges` pause work inside a task span, render visual gaps, and extend the computed finish. | | Critical path | Critical tasks, critical dependencies, early/late dates, and total slack are recomputed after all scheduling rules. | Microsoft Project has more scheduling knobs than this implementation, including status-date rescheduling options, per-resource Can Level, elapsed duration syntax, and automatic split-based leveling. RevoGrid exposes a deterministic subset designed for web-driven scheduling. ## Reference Links Useful Microsoft Project references for comparison: - [Task Mode field](https://support.microsoft.com/en-gb/office/task-mode-task-field-3b185518-0e9a-4774-ba51-7b8d191bb84a) - [Task constraints](https://support.microsoft.com/en-us/office/set-a-task-start-or-finish-date-constraint-for-a-task-3a7544fa-e992-4647-911b-54cdbe508784) - [Predecessors field and lead/lag](https://support.microsoft.com/en-us/office/predecessors-task-field-5a5ea9c2-14e6-4be7-9a3d-d8b6cba10cab) - [Deadline field](https://support.microsoft.com/en-us/office/deadline-task-field-8c6c4acd-1324-484a-a92b-9bd2eb313dfa) - [Resource leveling](https://support.microsoft.com/en-us/office/resource-leveling-dialog-box-0d280b16-2753-4630-8cca-8c50915df9f5) - [Task types and effort-driven scheduling](https://support.microsoft.com/en-us/office/change-the-effort-driven-setting-for-task-types-18efd7c7-d146-4b06-bdbe-b6a11564bdf3) - [Actual Start and Actual Finish](https://support.microsoft.com/en-us/office/actual-start-fields-b1b833a7-07f5-4c4d-858d-f9261d1be7f7) - [Split a task](https://support.microsoft.com/en-au/office/split-a-task-20c8581b-6266-45e3-af54-cc7c3b10deca) - [Critical path](https://support.microsoft.com/en-us/office/show-the-critical-path-of-your-project-in-project-ad6e3b08-7748-4231-afc4-a2046207fd86) --- # Task Constraints URL: https://pro.rv-grid.com/guides/gantt/scheduling/constraints/ Source: src/content/docs/guides/gantt/scheduling/constraints.mdx Description: Apply explicit date constraints like SNET, MSO, and MFO to control the scheduler's behavior. Constraints are explicit fields on a task that override or refine the automatic forward-pass scheduling: ```typescript { id: 'integration', name: 'External Integration', constraintType: 'start-no-earlier-than', constraintDate: '2026-05-04', // other task fields } ``` ## Supported Constraint Types | Constraint | MSP equivalent | Behavior | |---|---|---| | `start-no-earlier-than` | SNET | Start on or after the constraint date. | | `start-no-later-than` | SNLT | Start on or before the constraint date. | | `finish-no-earlier-than` | FNET | Finish on or after the constraint date. | | `finish-no-later-than` | FNLT | Finish on or before the constraint date. | | `must-start-on` | MSO | Start exactly on the constraint date. | | `must-finish-on` | MFO | Finish exactly on the constraint date. | ## Soft vs. Hard Constraints ### Soft Constraints (SNET, SNLT, FNET, FNLT) These constraints define a legal window for the task. If the dependency propagation moves the task outside this window, the scheduler will: - Adjust the date to the nearest legal bound (if possible). - Report a `constraint-window-conflict` if the dependency and constraint windows do not overlap. ### Hard Constraints (MSO, MFO) Hard constraints are authoritative. They pin the task to an exact date, even if it violates dependencies. - If a dependency requires a different date, the scheduler reports a `hard-constraint-conflict` or `dependency-constraint-conflict`. - The task date is NOT moved to satisfy the dependency; the constraint wins. Microsoft Project also exposes flexible ASAP and ALAP as constraint types. RevoGrid models those at project level with `scheduleFrom`, not as per-task `constraintType` values. --- # Critical Path and Slack URL: https://pro.rv-grid.com/guides/gantt/scheduling/critical-path/ Source: src/content/docs/guides/gantt/scheduling/critical-path.mdx Description: Automatically compute and highlight the critical path — the longest chain of tasks that determines the project end date. The **critical path** is the chain of tasks that controls the calculated project finish date. In Microsoft Project terms, critical tasks usually have zero total slack: if one slips, the project finish slips. The Gantt plugin computes this path from the final scheduled graph and highlights it visually. --- ## Enabling Critical Path Set `showCriticalPath: true` in the `visuals` section of your project config: ```typescript grid.gantt = { visuals: { showCriticalPath: true, }, }; ``` Critical tasks and the dependency arrows connecting them are highlighted in a distinct color (red by default). ## How It Works The scheduler computes critical path after all scheduling rules (dependencies, constraints, actuals, etc.) have been applied. It runs a backward pass over the graph to compute: - **Early Start / Early Finish**: The earliest each task can begin and end. - **Late Start / Late Finish**: The latest each task can begin and end without delaying the project. - **Total Slack** = Late Start − Early Start. | Field | Meaning | |---|---| | `earlyStartDate` | Effective scheduled start after all rules are applied. | | `lateStartDate` | Latest date the task can occupy without delaying the project finish. | | `totalSlackDays` | Working-day float between early and late dates. | | `isCritical` | `true` when total slack is zero. | Summary tasks are treated as critical when at least one child task is critical. Milestone tasks with no slack also appear on the critical path. ## Dependency Types All four dependency types (FS, SS, FF, SF) participate in critical path analysis. Lags (`lagDays`) are also factored in. A positive lag delays the successor, while a negative lag (lead time) can reduce the path length. ## MSP Comparison RevoGrid follows the Microsoft Project model: dependencies and final scheduled dates drive total slack, and tasks with zero slack become critical. Deadlines and resource leveling also influence the critical path by shifting dates or changing risk profiles. ## Critical State Is Projected Do not set `isCritical` in `grid.source`. Gantt source rows describe authored task data, while `isCritical`, slack fields, early/late dates, and critical dependency styling are projected by the scheduler on each render. If an application needs to persist critical-path state, persist the scheduling inputs instead: tasks, dependencies, constraints, calendars, assignments, and scheduling options. RevoGrid will recompute the critical path from those inputs. ## CSS Customization Critical path styling uses data attributes on bar and arrow elements: ```css /* Critical task bar */ .gantt-bar[data-critical] { --gantt-bar-color: #e53e3e; } /* Critical dependency arrow */ .gantt-dependency[data-critical] { stroke: #e53e3e; } ``` See the [Critical path analysis demo](https://demo.rv-grid.com/gantt-critical-path-analysis/ts) for a live example. --- # Deadlines URL: https://pro.rv-grid.com/guides/gantt/scheduling/deadlines/ Source: src/content/docs/guides/gantt/scheduling/deadlines.mdx Description: Use deadlineDate to track target dates without constraining the scheduling engine. Use `deadlineDate` for target dates that should warn but not lock the schedule: ```typescript { id: 'frontend', name: 'Frontend Development', deadlineDate: '2026-05-15', // other task fields } ``` ## Behavior If a task finishes after its `deadlineDate`, the scheduler reports a `deadline-missed` conflict. This warning appears in: - The **Scheduling Warning** column. - The task **tooltip**. - The row/bar **warning state**. - A visual **timeline indicator** (usually a down-arrow on the deadline date). ## Deadline vs. Finish Constraint Unlike a `must-finish-on` or `finish-no-later-than` constraint, a deadline **never moves the task**. - It is purely an analytical tool to measure performance against a target. - It does not affect the critical path or slack calculations of other tasks. This matches Microsoft Project's distinction between a deadline and an inflexible finish constraint: a deadline is an alert, not a scheduling rule. --- # Scheduling Direction URL: https://pro.rv-grid.com/guides/gantt/scheduling/direction/ Source: src/content/docs/guides/gantt/scheduling/direction.mdx Description: Configure forward (ASAP) or backward (ALAP) scheduling for your Gantt project. Forward scheduling is the default and behaves like Microsoft Project's ASAP (As Soon As Possible) direction. Automatic tasks are placed as early as their dependencies and constraints allow. ```typescript grid.gantt = { scheduling: { scheduleFrom: 'project-start', projectStartDate: '2026-04-06', }, }; ``` When `projectStartDate` is set, automatic root tasks start from that anchor unless a dependency or constraint moves them later. ## Backward Scheduling (ALAP) Backward scheduling behaves like Microsoft Project's ALAP (As Late As Possible) project-finish direction: ```typescript grid.gantt = { scheduling: { scheduleFrom: 'project-finish', projectFinishDate: '2026-06-30', }, }; ``` Automatic tasks without successors are placed as late as possible against `projectFinishDate`; predecessors are pulled earlier to satisfy outgoing dependencies. If `projectFinishDate` is omitted, the scheduler uses the latest task finish in the current data. ## Example: Project Finish Scheduling Use project-finish scheduling for ALAP-style plans: ```typescript grid.gantt = { scheduling: { scheduleFrom: 'project-finish', projectFinishDate: '2026-04-30', }, }; grid.source = [ { id: 'build', name: 'Build', duration: 3, }, { id: 'release', name: 'Release', duration: 1, }, ]; grid.ganttDependencies = [{ id: 'build-release', predecessorTaskId: 'build', successorTaskId: 'release', type: 'finish-to-start', lagDays: 0, }]; ``` **Expected result**: `release` is placed as late as possible against `2026-04-30`; `build` is pulled earlier so the FS relationship remains valid. See the [Project-finish ALAP demo](https://demo.rv-grid.com/gantt-alap/ts) for a visible backward-scheduled chain anchored to a project finish date. --- # Effort Modes URL: https://pro.rv-grid.com/guides/gantt/scheduling/effort-modes/ Source: src/content/docs/guides/gantt/scheduling/effort-modes.mdx Description: Model task types (Fixed Duration, Fixed Work, Fixed Units) to control how work and duration interact. Use `effortMode` to model Microsoft Project-style task types: | Mode | Behavior | |---|---| | `fixed-duration` | Preserve `duration`. Work and units do not recalculate duration. | | `fixed-work` | Preserve `workHours` and derive duration from assigned units and calendar. | | `fixed-units` | Preserve assignment units and derive duration from `workHours` and units. | ## Formula The duration formula for fixed-work/fixed-units is: ```text duration = ceil(workHours / (hoursPerDay * totalAssignmentUnits)) ``` ## Example: Task Types ```typescript grid.source = [ { id: 'standup', name: 'Standup', effortMode: 'fixed-duration', duration: 1, workHours: 2, }, { id: 'implementation', name: 'Implementation', effortMode: 'fixed-work', workHours: 32, }, ]; grid.ganttAssignments = [ { taskId: 'implementation', resourceId: 'alex', allocationUnits: 100, }, ]; ``` **Expected result**: `standup` keeps its one-day duration. `implementation` becomes four days (32h / 8h/day) with one 100% assignment. See the [Progress and work demo](https://demo.rv-grid.com/gantt-progress-work/ts) for more examples. --- # Engine Logic URL: https://pro.rv-grid.com/guides/gantt/scheduling/engine/ Source: src/content/docs/guides/gantt/scheduling/engine.mdx Description: Deep dive into the RevoGrid Gantt scheduling engine pipeline and conflict resolution. The RevoGrid Gantt scheduling engine follows a deterministic pipeline to compute effective dates for all tasks. It processes data changes in five distinct phases to ensure dependency safety and constraint satisfaction. ## The 5-Step Pipeline ### 1. Topological Sort The engine first builds a dependency graph and performs a topological sort. This ensures that tasks are ordered such that every predecessor is processed before its successors. - Summary tasks are handled separately to ensure they can roll up dates from their children. - Circular dependencies are detected and reported as `dependency-cycle` issues with the involved task and dependency IDs. When the active dependency graph contains a cycle, the engine treats the project as structurally invalid schedule data. It does not try to calculate a dependency-valid order through the loop, and task drag/resize does not clamp against predecessor links until the cycle is resolved. ### 2. Forward Pass (ASAP) In the default `scheduleFrom: 'project-start'` mode, the engine performs a forward pass: - Root tasks (those without predecessors) are anchored to the `projectStartDate`. - Successors are shifted forward to satisfy their incoming dependencies (FS, SS, FF, SF) plus any `lagDays`. - The engine uses the successor's calendar to compute working-day offsets if `lagCalendar: 'working-days'` is enabled. ### 3. Constraint Application Once the dependency-driven dates are known, the engine applies per-task constraints: - **Soft Constraints**: SNET, SNLT, etc., refine the start date if the dependency date falls outside the constraint bound. - **Hard Constraints**: `must-start-on` and `must-finish-on` override the forward pass results. If a hard constraint violates a dependency, a conflict is reported, but the constraint date wins. ### 4. Summary Rollup After all leaf tasks have their dates resolved, the engine rolls up the schedule to summary (parent) tasks: - A summary task's `startDate` is the minimum of its children's `startDate`. - A summary task's `endDate` is the maximum of its children's `endDate`. - Rollup is recursive, traversing the entire WBS (Work Breakdown Structure) tree. ### 5. Critical Path Analysis The final phase computes the project's early and late dates using a backward pass from the project finish date: - **Total Slack**: The number of days a task can be delayed without delaying the project finish. - **Critical Path**: Tasks with zero total slack are marked as `isCritical`. - This analysis helps planners identify the "chain" of tasks that determines the total project duration. ## Conflict Resolution The engine prioritizes user intent and data integrity when scheduling rules collide: | Priority | Rule | Outcome | |---|---|---| | **High** | Actuals | `actualStartDate` and `actualFinishDate` pin the task; the engine won't move them. | | **High** | Hard Constraints | `MSO` / `MFO` dates win over dependencies; conflict reported. | | **Medium** | Dependencies | Auto-tasks are clamped to satisfy predecessors. | | **Medium** | Soft Constraints | Dates are adjusted to bounds where possible; conflict reported if windows are disjoint. | | **Low** | Authored Dates | Used as the starting point for root tasks or manual tasks. | The scheduling engine runs synchronously on each render to provide immediate feedback during drag-and-drop or inline editing. --- # Resource Leveling URL: https://pro.rv-grid.com/guides/gantt/scheduling/leveling/ Source: src/content/docs/guides/gantt/scheduling/leveling.mdx Description: Use automatic resource leveling to resolve over-allocations by delaying tasks. Enable deterministic auto-leveling with: ```typescript grid.gantt = { scheduling: { resourceLeveling: 'auto', resourceLevelingWithinSlack: false, }, }; ``` Auto-leveling delays later overlapping automatic tasks until the resource is available, then reports `resource-leveling-adjustment`. ## Leveling Controls Per-task leveling controls: ```typescript const task = { priority: 900, // 0-1000; higher priority is protected first canLevel: true, // false keeps the task fixed during auto-leveling levelingDelayDays: 2, // stored calendar-day delay applied by leveling }; ``` **Deterministic auto-leveling order**: 1. Non-levelable tasks first (fixed blockers). 2. Higher `priority` before lower `priority`. 3. Earlier start date, then task id. ## Constraints and Actuals The leveling implementation avoids moving: - Manually scheduled tasks. - Tasks with `actualStartDate` or `actualFinishDate`. - Summary tasks. - Inactive tasks. - Tasks pinned by `must-start-on` or `must-finish-on`. Microsoft Project leveling can delay or split tasks. RevoGrid leveling is deterministic and delay-based; it supports priority and slack-limited leveling but does not split tasks automatically. --- # Manual Scheduling URL: https://pro.rv-grid.com/guides/gantt/scheduling/manual/ Source: src/content/docs/guides/gantt/scheduling/manual.mdx Description: Use taskMode to lock dates and override automatic dependency propagation. Set `taskMode` on each task, or use `taskModeDefault` for tasks that omit it: ```typescript { id: 'kickoff', name: 'Fixed Kickoff Event', taskMode: 'manual', startDate: '2026-04-20', endDate: '2026-04-20', duration: 0, // other task fields } ``` Automatic tasks are dependency-safe. Dragging, resizing from the start edge, or editing an automatic successor before its predecessor allows will clamp the effective schedule to the dependency-valid date. Manual tasks keep authored dates. If the manual start violates an active dependency, the scheduler reports `manual-dependency-violation` and the row/bar renders a scheduling warning indicator. Microsoft Project's Task Mode field defaults new tasks to manually scheduled in some desktop versions. RevoGrid defaults to automatic scheduling because dependency safety is the default expectation for this Gantt plugin. Set `taskModeDefault: 'manual'` to make omitted task modes behave manually. ## Dependency Violations When a manual task is placed at a date that violates an incoming dependency (e.g., starts before a FS predecessor finishes), the scheduler does not move the task. Instead, it: 1. Records a `manual-dependency-violation` conflict. 2. Displays a warning icon in the "Scheduling Warning" column. 3. Highlights the task bar with warning styling. This allows planners to maintain "best guess" or "negotiated" dates while still seeing where the logical project structure is broken. --- # Overview Scheduling URL: https://pro.rv-grid.com/guides/gantt/scheduling/overview/ Source: src/content/docs/guides/gantt/scheduling/overview.mdx Description: Configure MSP-style scheduling with task modes, dependencies, constraints, calendars, deadlines, resources, leveling, actuals, and critical path. The Gantt scheduler computes effective task dates from authored dates, duration, task mode, dependencies, lag/lead, calendars, constraints, deadlines, resources, actuals, split ranges, and optional resource leveling. Its compatibility target is Microsoft Project scheduling behavior: automatic tasks are dependency-safe, manual tasks preserve authored dates but show warnings, deadlines warn without constraining, and leveling can delay eligible work. --- ## Scheduling Configuration Configure scheduling policies under `grid.gantt.scheduling`: ```typescript grid.gantt = { scheduling: { taskModeDefault: 'auto', scheduleFrom: 'project-start', projectStartDate: '2026-04-06', excludeHolidaysFromDuration: true, lagCalendar: 'working-days', manualDependencyViolationBehavior: 'warn', resourceLeveling: 'warn', effortModeDefault: 'fixed-duration', progressRescheduling: 'remaining-duration', }, }; ``` Definition source: `packages/enterprise/plugins/gantt/grid/gantt-plugin.types.ts`. ## Scheduling Warnings Warnings are reported as scheduler conflicts and projected into rows: | Code | Meaning | |---|---| | `manual-dependency-violation` | A manual task starts before an active dependency allows. | | `dependency-constraint-conflict` | A dependency cannot be satisfied because an explicit constraint wins. | | `hard-constraint-conflict` | A must-start/must-finish constraint overrides dependencies. | | `constraint-window-conflict` | Soft constraint and dependency windows do not overlap. | | `deadline-missed` | A task finishes after `deadlineDate`. | | `resource-overallocation` | A resource exceeds capacity on a scheduled working date. | | `resource-leveling-adjustment` | Auto-leveling shifted a task to remove over-allocation. | ## Performance Large-project scheduler performance is covered by the Enterprise benchmark suite: ```bash pnpm --filter @revolist/revogrid-enterprise bench:gantt ``` The benchmark exercises 1,000 and 3,000 task snapshots with dense dependencies and auto-leveling. --- ## Next Steps Dive deeper into specific scheduling topics: - **[MSP Compatibility](../compatibility)**: How RevoGrid maps to Microsoft Project. - **[Engine Logic](../engine)**: The 5-step pipeline and conflict resolution. - **[Scheduling Direction](../direction)**: Forward (ASAP) vs. Backward (ALAP). - **[Calendars & Duration](../calendars)**: Working days, holidays, and lag calendars. - **[Dependencies](../concepts/dependencies)**: FS, SS, FF, SF and lag/lead. - **[Task Modes](../manual)**: Manual vs. Automatic scheduling. - **[Constraints](../constraints)**: SNET, MSO, MFO and soft/hard rules. - **[Deadlines](../deadlines)**: Target dates and missed deadline warnings. - **[Resources & Assignments](../resources/resources-and-assignments)**: Resource capacity and assignments. - **[Resource Planning View](../resources/resource-planning-view)**: Resource-centric timeline view. - **[Resource Leveling](../leveling)**: Auto-leveling logic and priorities. - **[Effort Modes](../effort-modes)**: Fixed work, duration, and units. - **[Actuals & Progress](../progress)**: Tracking real-world status. - **[Split Tasks](../split-tasks)**: Managing work gaps. - **[Critical Path](../critical-path)**: Slack and schedule analysis. --- # Actuals and Progress URL: https://pro.rv-grid.com/guides/gantt/scheduling/progress/ Source: src/content/docs/guides/gantt/scheduling/progress.mdx Description: Track project progress using actual start/finish dates and remaining duration. Use actual fields to reflect progress: ```typescript { id: 'qa', name: 'QA', actualStartDate: '2026-04-08', remainingDurationDays: 2, percentDone: 50, } ``` ## Progress-Aware Scheduling Enable progress-aware scheduling: ```typescript grid.gantt = { scheduling: { progressRescheduling: 'remaining-duration', }, }; ``` When enabled: - `actualStartDate` anchors in-progress work. - `remainingDurationDays` replaces the planned duration for the remaining span. - `actualFinishDate` pins completed work to the recorded finish. - The schedule origin is shown as `Actual`. ## Example: Actuals and Remaining Work ```typescript grid.gantt = { scheduling: { progressRescheduling: 'remaining-duration' }, }; grid.source = [ { id: 'in-progress', startDate: '2026-04-01', duration: 10, actualStartDate: '2026-04-03', remainingDurationDays: 2, }, { id: 'complete', startDate: '2026-04-01', duration: 10, actualStartDate: '2026-04-02', actualFinishDate: '2026-04-04', }, ]; ``` **Expected result**: `in-progress` is anchored on `2026-04-03` and schedules 2 remaining days. `complete` is pinned to its actual dates. --- # Split Tasks URL: https://pro.rv-grid.com/guides/gantt/scheduling/split-tasks/ Source: src/content/docs/guides/gantt/scheduling/split-tasks.mdx Description: Pause work inside a task span using split ranges. Use `splitRanges` to pause work inside a task: ```typescript { id: 'qa', name: 'QA', startDate: '2026-04-01', duration: 4, splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }], } ``` The scheduler extends the computed finish by the split duration so the task retains its planned working duration. Resource allocation checks skip split dates, and task bars render split ranges as visible gaps. ## Timeline Split Editing - **Create**: Alt-drag inside a regular task bar. - **Move**: Drag an existing split gap. - **Remove**: Double-click a split gap. ## Example: Split Task with Resource Allocation ```typescript grid.source = [{ id: 'qa', startDate: '2026-04-01', duration: 4, splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }], }]; ``` **Expected result**: the task keeps four working days of duration, but its computed finish extends. Resource allocation checks skip `2026-04-03` and `2026-04-04`. --- # Clipboard with JSON Support URL: https://pro.rv-grid.com/guides/grid-utilities/clipboard-json/ Source: src/content/docs/guides/grid-utilities/clipboard-json.mdx Description: Clipboard functionality with support for JSON. Enable clipboard functionality with support for JSON and advanced objects. Easily copy and paste complex data structures within your grid and between applications. ## Example --- # Dimension Animation URL: https://pro.rv-grid.com/guides/grid-utilities/dimension-animation/ Source: src/content/docs/guides/grid-utilities/dimension-animation.mdx Description: Animate row and column dimension sizes for custom collapse, expand, and resize flows. `DimensionAnimationPlugin` is a plugin that animates grid dimension sizes. Use it when a feature needs rows or columns to collapse, expand, or move between explicit sizes without replacing the grid's own virtualization and dimension systems. The plugin only animates sizes. It does not own row visibility, trimming, grouping, ordering, or business state. Callers decide what should become hidden or visible, then use the plugin to make the size transition smooth. ## When to use it - Animate custom row or column collapse and expand flows. - Animate explicit size changes when you already know the start and end sizes. - Coordinate delayed state changes around animation completion. - Support integrations such as tree rows, where `TreeDataPlugin` handles trim state and `DimensionAnimationPlugin` handles the visual transition. ## Installation ```typescript import { DimensionAnimationPlugin } from '@revolist/revogrid-pro'; const grid = document.querySelector('revo-grid'); grid.plugins = [DimensionAnimationPlugin]; grid.dimensionAnimation = { duration: 180, easing: 'easeOutCubic', types: ['rgRow'], }; ``` Configure the plugin through `grid.dimensionAnimation`. The older `additionalData.dimensionAnimation` path is still accepted for compatibility, but `grid.dimensionAnimation` is the supported API. | Property | Default | Description | | --- | --- | --- | | `duration` | `180` | Animation duration in milliseconds. Use `0` to apply sizes synchronously. | | `easing` | `'easeOutCubic'` | Easing used while interpolating sizes. Supports `'linear'`, `'easeOutCubic'`, or a custom `(progress) => number` function. | | `types` | All row and column dimensions | Dimensions affected by requests that do not pass a specific `type`. | ## Controller API After the plugin is registered, use its controller methods to animate indexes or explicit frames. ```typescript const plugins = await grid.getPlugins(); const dimensionAnimation = plugins.find( plugin => plugin instanceof DimensionAnimationPlugin, ); await dimensionAnimation?.collapse([0, 1, 2], { type: 'rgRow', indexKind: 'visual', }); await dimensionAnimation?.expand([0, 1, 2], { type: 'rgRow', indexKind: 'visual', }); ``` Use `indexKind: 'physical'` when indexes refer to source rows or columns. Use `indexKind: 'visual'` when indexes refer to the current rendered order after sorting, filtering, trimming, pinning, or other view changes. ### Explicit frames Use `animate()` when you already know the visual indexes and target sizes. ```typescript await dimensionAnimation?.animate([ { type: 'rgCol', visualIndex: 0, from: 220, to: 80, }, ]); ``` The plugin cancels any in-flight animation that targets the same dimension index before starting the next one. Call `cancel()` directly to stop matching animations, or omit arguments to cancel all dimension animations. ```typescript dimensionAnimation?.cancel('rgRow', [0, 1]); dimensionAnimation?.cancel(); ``` ## Event API You can also request animations by dispatching the `dimensionanimate` event from the grid element. The plugin attaches `detail.done` synchronously, so event-driven integrations can wait before applying follow-up logic. ```typescript const detail = { action: 'collapse', indexes: [0], type: 'rgRow', indexKind: 'visual', }; grid.dispatchEvent(new CustomEvent('dimensionanimate', { detail })); await detail.done; ``` Listen to `beforedimensionanimate` to cancel an operation before it starts. Listen to `afterdimensionanimate` to react after the animation completes or is cancelled. ```typescript grid.addEventListener('beforedimensionanimate', event => { if (event.detail.action === 'collapse' && shouldBlockCollapse()) { event.preventDefault(); } }); grid.addEventListener('afterdimensionanimate', event => { console.log(event.detail.results); }); ``` ## Tree integration Tree rows can use dimension animation automatically. Enable `tree.animation`; `TreeDataPlugin` registers `DimensionAnimationPlugin` when it is not already present. ```typescript import { TreeDataPlugin } from '@revolist/revogrid-pro'; grid.plugins = [TreeDataPlugin]; grid.tree = { animation: true, }; grid.dimensionAnimation = { duration: 180, }; ``` In this flow, `TreeDataPlugin` owns tree trim state and expansion state. `DimensionAnimationPlugin` only animates the affected row sizes during collapse and expand. ## API reference - [Dimension Animation API](/api/dimension-animation/) - [Tree guide](/guides/data-manage/tree/) --- # Overlay Nodes URL: https://pro.rv-grid.com/guides/grid-utilities/overlay-nodes/ Source: src/content/docs/guides/grid-utilities/overlay-nodes.mdx Description: A guide to using the Overlay Plugin to display notes on grid rows. The **Overlay Plugin** in RevoGrid provides a powerful way to display additional content over the grid cells. This guide demonstrates how to use it to show notes on specific rows. 1. **Dynamic Overlay Positioning**: The Overlay Plugin automatically handles positioning and scrolling of overlay elements, ensuring they stay aligned with their corresponding grid rows. 2. **Performance Optimized**: Despite rendering additional content, the overlay layer remains virtualized, maintaining high performance even with many notes. 3. **Flexible Content**: You can display any type of content in the overlay, from simple notes to complex interactive components. ## Table of Contents - [Features](#features) - [Installation](#installation) - [Usage](#usage) - [Basic Setup](#basic-setup) - [Adding Notes](#adding-notes) - [Customizing Notes](#customizing-notes) - [API Reference](#api-reference) --- ## Features - **Row-Specific Notes**: Display notes on specific grid rows - **Automatic Positioning**: Notes automatically position themselves relative to their rows - **Scroll Synchronization**: Notes stay aligned with their rows during scrolling - **Custom Styling**: Fully customizable note appearance and behavior --- ## Basic Setup 1. **Import the Overlay Plugin:** ```javascript import { OverlayPlugin } from '@revolist/revogrid-pro'; ``` 2. **Initialize the Grid with the Plugin:** ```javascript const grid = document.querySelector('revo-grid'); grid.plugins = [OverlayPlugin]; ``` 3. **Add Notes to Rows:** ```javascript // Dispatch an event to add a note grid.dispatchEvent(new CustomEvent('overlaynode', { detail: { nodeId: `note-${rowIndex}`, vnode: h('div', { class: 'note' }, 'Your note text here') } })); ``` ### Adding Notes To add a note to a specific row, you need to: 1. Create a unique `nodeId` for the note 2. Create a virtual node (vnode) for the note content 3. Dispatch the `overlaynode` event with the note details ```javascript const addNote = (rowIndex, noteText) => { const noteNode = h('div', { class: 'note', style: { position: 'absolute', top: `${rowIndex * rowHeight}px`, left: '0', width: '100%', padding: '8px', backgroundColor: 'rgba(255, 255, 0, 0.2)', border: '1px solid #ffd700' } }, noteText); grid.dispatchEvent(new CustomEvent('overlaynode', { detail: { nodeId: `note-${rowIndex}`, vnode: noteNode } })); }; ``` ### Customizing Notes You can customize the appearance and behavior of notes by: 1. Modifying the note's CSS styles 2. Adding interactive elements 3. Implementing custom positioning logic ```javascript const createCustomNote = (rowIndex, noteData) => { return h('div', { class: 'custom-note', style: { position: 'absolute', top: `${rowIndex * rowHeight}px`, left: '0', width: '100%', padding: '12px', backgroundColor: noteData.color || 'rgba(255, 255, 0, 0.2)', border: `1px solid ${noteData.borderColor || '#ffd700'}`, borderRadius: '4px' } }, [ h('div', { class: 'note-header' }, noteData.title), h('div', { class: 'note-content' }, noteData.content) ]); }; ``` --- ## API Reference ### OverlayPlugin Events #### `overlaynode` Dispatched to add or update a note in the overlay layer. ```typescript interface OverlayNodeEvent { nodeId: string; vnode: VNode; } ``` #### `overlayclearnode` Dispatched to remove notes from the overlay layer. ```typescript interface OverlayClearNodeEvent { nodeIds: string[]; } ``` ### Methods #### `refresh()` Manually refresh the overlay layer's positioning and content. --- # Local Pagination URL: https://pro.rv-grid.com/guides/grid-utilities/pagination/ Source: src/content/docs/guides/grid-utilities/pagination.mdx Description: Manage large datasets by breaking them into smaller, more manageable pages. Enable `PaginationPlugin` to efficiently manage large datasets by breaking them into smaller, more manageable pages, enhancing both performance and user experience. Pagination allows you to divide your dataset into discrete pages, each containing a specified number of rows. Users can then navigate through these pages rather than scrolling through a long list of data. This is especially useful when dealing with large datasets where loading all rows at once could affect performance. It gives you: - **Improved Performance**: By loading only a subset of data at a time, pagination helps maintain grid performance even with large datasets. - **Enhanced User Experience**: Users can easily navigate through data without being overwhelmed by a large number of rows. --- # Tooltip URL: https://pro.rv-grid.com/guides/grid-utilities/tooltip/ Source: src/content/docs/guides/grid-utilities/tooltip.mdx Description: A comprehensive guide to using the RevoGrid Tooltip Plugin The Tooltip Plugin is a Pro plugin that displays additional information when users hover over grid cells or other elements inside the grid. It creates a single managed tooltip element, reads content from cell attributes, and shows it after a short hover delay. ## Features - Dynamic tooltip creation and positioning on `mouseover`, `mouseout`, and `mousemove` - Automatic tooltip content from `data-tooltip` or `title` attributes - Customizable tooltip appearance with `tooltip-type` and `tooltip-position` attributes - Configurable show delay, animation, and cursor-following behavior - Smart positioning to ensure tooltips remain within grid boundaries - Smooth show/hide transitions - Support for different tooltip types (info, warning, error, etc.) ## Installation The Tooltip Plugin is included in RevoGrid Pro. To use it, import and initialize it with your grid: ```typescript import { TooltipPlugin } from '@revolist/revogrid-pro'; const grid = document.querySelector('revo-grid'); grid.plugins = [TooltipPlugin]; ``` ## Usage ### Basic Usage The simplest way to use tooltips is by adding a `title` or `data-tooltip` attribute to your grid cells: ```typescript grid.columns = [ { prop: 'name', name: 'Name', cellTemplate: (createElement, props) => { return createElement('div', { title: 'User name', 'data-tooltip': 'Full user name', }, props.model.name); }, }, ]; ``` ### Showing Behavior Tooltips are shown only when the hovered element has a non-empty `data-tooltip` or `title` attribute. If both are present, `data-tooltip` is used. The plugin waits `500ms` before showing the tooltip by default, then hides it when the cursor leaves the tooltip target. Use the `tooltip` grid property to control when and how the tooltip is shown: ```typescript grid.tooltip = { enabled: true, showDelayMs: 250, animation: true, followCursor: true, }; ``` | Property | Default | Description | | --- | --- | --- | | `enabled` | `true` | Enables or disables tooltip hover behavior. Disabling it hides any open tooltip immediately. | | `showDelayMs` | `500` | Delay before showing the tooltip. Negative values are normalized to `0`. | | `animation` | `true` | Enables the tooltip entrance and exit transition. | | `followCursor` | `true` | Keeps the tooltip attached to the cursor while it is open. Set to `false` to keep the tooltip at its opening position. | ### Customizing Tooltip You can control the tooltip position using the `tooltip-position` attribute and the `tooltip-type` attribute to change the tooltip type: ```typescript createElement('div', { 'data-tooltip': 'Custom positioned tooltip', 'tooltip-position': 'right', // 'top', 'bottom', 'left', 'right' 'tooltip-type': 'info', // 'info', 'warning', 'error', etc. }, content); ``` ## Best Practices 1. Keep tooltip content concise and relevant 2. Use appropriate tooltip positions based on available space 3. Choose tooltip types that match the information context 4. Consider mobile users when designing tooltip interactions 5. Use tooltips to provide supplementary information, not critical data --- # Removing Attribution URL: https://pro.rv-grid.com/guides/hide-attribution/ Source: src/content/docs/guides/hide-attribution.md Description: Removing Attribution from RevoGrid As a Pro version user of RevoGrid, you can easily remove attribution from your projects. To do this, simply apply one of the following attributes: ### Method 1: Using `hideAttribution` Add the `hideAttribution` attribute to your RevoGrid component: ```html ``` ### Method 2: Using `hide-attribution` Alternatively, you can use the `hide-attribution` attribute: ```html ``` ### Conclusion By applying either of these attributes, the attribution will be hidden from your RevoGrid component. If you have any questions or need further assistance, feel free to reach out to the RevoGrid support team. --- # Infinity Scroll URL: https://pro.rv-grid.com/guides/infinity-scroll/ Source: src/content/docs/guides/infinity-scroll.mdx Description: Efficiently handle large datasets with dynamic loading as users scroll through the grid. The Infinity Scroll feature enables dynamic data loading as users scroll through the grid, efficiently managing large datasets by loading data in chunks and cleaning up unused data to optimize memory usage. ## Key Features - Dynamic data loading based on scroll position - Pre-loads data ahead of scroll direction - Efficient memory management by cleaning up off-screen data - Configurable chunk size and buffer sizes - Support for both known and unknown total data sizes - Compatible with pinned top and bottom rows ## Basic Setup To enable infinity scroll in your grid: ```typescript import { InfinityScrollPlugin } from '@revolist/revogrid-plugin-infinity-scroll'; const grid = document.createElement('revo-grid'); grid.plugins = [InfinityScrollPlugin]; grid.pinnedTopSource = [{ id: 'status', name: 'Pinned status row' }]; grid.pinnedBottomSource = [{ id: 'summary', name: 'Pinned summary row' }]; grid.additionalData = { infinityScroll: { chunkSize: 50, // Number of rows per chunk bufferSize: 100, // How many rows to keep in buffer preloadThreshold: 0.75, // When to start loading more data (0-1) total: 1000, // Optional: total number of rows loadData: async (skip, limit, order, filter) => { // Fetch data from your API const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(filter)}`); const data = await response.json(); return data; // You can return an array or { data, hasMore, total } } } }; ``` Pinned rows stay outside the remote loading lifecycle. Use the core `pinnedTopSource` and `pinnedBottomSource` grid properties for static status, summary, or action rows while `InfinityScrollPlugin` manages only the main scrollable source. ## Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `chunkSize` | number | undefined | Number of rows to load in each chunk. Can be set dynamically by the grid. | | `bufferSize` | number | undefined | Number of rows to keep in memory buffer. Can be set dynamically by the grid. | | `preloadThreshold` | number | 0.75 | When to trigger loading next chunk (0-1) | | `total` | number | undefined | Total number of rows. If not provided, the plugin will prefill the source on the "fly" - scroll will be growing as you scroll. | | `loadData` | function | required | Function to load data chunks. Return `row[]` or `{ data: row[], hasMore?: boolean, total?: number }`. | ### Using `hasMore` for unknown total When your backend does not provide a fixed `total`, return `hasMore` from `loadData`. This is the most reliable way to tell the plugin when to stop requesting additional chunks. ```typescript grid.additionalData = { infinityScroll: { chunkSize: 100, loadData: async (skip, limit, order, filter) => { const response = await fetch('/api/data', { method: 'POST', body: JSON.stringify({ skip, limit, order, filter }), }); const result = await response.json(); return { data: result.items, hasMore: result.hasMore, }; }, }, }; ``` ### Dynamic `total` from backend You can also return `total` from `loadData`. When provided, the plugin updates its internal total size and uses it for source sizing and end detection. ## Memory Management The plugin automatically manages memory by: - Loading new chunks of data as the user scrolls - Maintaining a buffer of rows before and after the visible area - Cleaning up data that's far from the current viewport ## Exporting to Excel Infinity scroll keeps only a moving window of rows in the grid source. If you need a complete workbook, fetch the export dataset from the same backend API and pass those rows to `ExportExcelPlugin` instead of relying on the currently rendered viewport. The demo export button uses a temporary hidden grid with the same columns so the visible infinite-scroll grid is not disturbed while the workbook is generated. ## Best Practices 1. **Choose Appropriate Chunk Size** - Smaller chunks mean more frequent loading but less memory usage - Larger chunks mean fewer API calls but more memory usage 2. **Configure Buffer Size** - Larger buffers provide smoother scrolling but use more memory - Smaller buffers save memory but might cause more loading events 3. **Optimize Data Loading** - Implement server-side pagination in your API - Return only necessary data fields - Consider data compression for large datasets 4. **Handle Loading States** - Show loading indicators during data fetching - Handle errors gracefully - Consider implementing retry logic for failed requests ## Example with Loading State ```typescript grid.additionalData = { infinityScroll: { chunkSize: 50, bufferSize: 100, loadData: async (skip, limit, order, filter) => { try { // Show loading state const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(filter)}`); const data = await response.json(); // Hide loading state return data; } catch (error) { console.error('Failed to load data:', error); return []; // Return empty array on error } finally { // Hide loading state } } } }; ``` This implementation provides efficient handling of large datasets while maintaining optimal performance and user experience. --- # Installing RevoGrid Pro and Enterprise URL: https://pro.rv-grid.com/guides/installation/ Source: src/content/docs/guides/installation.mdx Description: Learn how to install RevoGrid Pro and Enterprise plugins using either a pre-built release or our private npm package. ### Which Package Do I Need? - Install `@revolist/revogrid-pro` when you need advanced Pro features (filters, editors, grouping, tree, export, etc.) without Pivot. - Install `@revolist/revogrid-enterprise` when you need Enterprise features like Pivot. Enterprise is built on top of Pro, so keep both packages installed. - If you are evaluating the trial version, use the trial package names instead: `@revolist/rv-pro-trial` and `@revolist/rv-enterprise-trial`. ```bash npm install @revolist/revogrid-pro npm install @revolist/revogrid-enterprise ``` Trial install: ```bash npm config set "@revolist:registry=https://trial.rv-grid.com" npm install @revolist/revogrid @revolist/rv-pro-trial @revolist/rv-enterprise-trial ``` If you are unsure, start with Pro. Add Enterprise when you need Pivot and Enterprise-only capabilities. For trial projects, import CSS from `@revolist/rv-pro-trial/dist/rv-pro-trial.css`. #### Next Steps After installation, you can start using RevoGrid Pro and Enterprise in your project. Check out our other guides for: - [Basic Usage](/guides) - [Component API](/api/aggregations) - [Simple Example](/guides/rows/row-odd/) - [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git) Each framework has its own set of components and examples that demonstrate how to use RevoGrid Pro and Enterprise effectively in your chosen framework environment. --- # Github Installation Guide URL: https://pro.rv-grid.com/guides/installation-github/ Source: src/content/docs/guides/installation-github.mdx Description: Learn how to install RevoGrid Pro and Enterprise using Github [Private GitHub Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry) 1. Ensure you have access to the GitHub repository. 2. Authenticate your npm client with GitHub by creating a .npmrc file in your project directory or user home directory with the following content: ```json @revolist:registry=https://npm.pkg.github.com/ //npm.pkg.github.com/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN ``` 3. Replace `YOUR_PERSONAL_ACCESS_TOKEN` with a GitHub token that has `read:packages` and repo permissions. 4. **Install the packages**. Run the following commands to install the latest versions from the private package registry: ```bash npm install @revolist/revogrid-pro@latest npm install @revolist/revogrid-enterprise@latest ``` For additional guidance on installing from GitHub’s private registry, please refer to the [official GitHub Packages documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry). To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git). ## Supply Chain Metadata RevoGrid Pro and Enterprise releases include SBOM files and Sigstore release signatures. - SBOM files are generated in SPDX JSON and CycloneDX JSON formats. - Package tarballs are signed with Sigstore Cosign keyless signing in the release workflow. - The signed tarball is the same tarball published to GitHub Packages. For verification commands and related policy links, see [Security and Supply Chain](/legal/security). --- # Local Installation Guide URL: https://pro.rv-grid.com/guides/installation-local/ Source: src/content/docs/guides/installation-local.mdx Description: Learn how to install RevoGrid Pro and Enterprise using local files --- ### Option 1: Directly Include in Builds - Copy the into your project’s `assets` or `libs` folder. - Link the necessary JavaScript and CSS files in your HTML or import them into your JavaScript/TypeScript files. ```html ``` Or in your module-based project: ```javascript import 'path/to/revogrid-pro.js'; import 'path/to/revogrid-pro.css'; // If using build tools like Webpack or Vite: // Add the ./release folder path to your resolve or alias configuration to reference the files. resolve: { alias: { '@revogrid-pro': path.resolve(__dirname, 'path/to/release'), }, }, ``` This option is particularly suitable for quick testing or projects that don’t rely on npm for dependency management. --- ### Option 2: package.json Using Local Files For projects where you want to use local versions of RevoGrid Pro and Enterprise plugins during development, you can reference the plugin files directly in your package.json. This method is especially useful if you’re working with a cloned version of the repository or testing custom modifications. Steps to Import Using Local Files: 1. Download files from the link above and extract them to a local folder. 2. Set Up Local References in package.json. In your project’s package.json file, reference the local folders containing RevoGrid Pro and Enterprise plugins. For example: ```json { "dependencies": { "@revolist/revogrid-pro": "file:./release/pro", "@revolist/revogrid-enterprise": "file:./release/enterprise" } } ``` 3. Install Dependencies. Run the following command to install the local plugin: ```bash npm install ``` 4. NPM will create a symlink to the specified folders, allowing you to use the local files as if they were npm packages. Use the plugins in your project. Import RevoGrid Pro and Enterprise plugins as you would with any npm package: ```js import { ColumnStretchPlugin } from '@revolist/revogrid-pro'; import { PivotPlugin } from '@revolist/revogrid-enterprise'; ``` To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git). --- # No-Build JavaScript Usage URL: https://pro.rv-grid.com/guides/installation-no-build/ Source: src/content/docs/guides/installation-no-build.mdx Description: Use a delivered RevoGrid Pro JavaScript build directly from the browser without a bundler. Use this setup when you want to load a delivered RevoGrid Pro JavaScript build from a plain HTML file, a static page, or a server-rendered page that does not use a JavaScript build pipeline. RevoGrid Pro is not published as a public browser CDN package. Use the JavaScript/CSS build delivered from the portal, or install the private npm package in projects that have a build step. The no-build example below assumes the delivered Pro `dist` files are copied into your app. ## Live Preview This preview uses the same grid configuration as the no-build HTML example below. 1. Copy the delivered Pro build into your static assets. Keep the whole Pro `dist` folder together because the ESM entry imports sibling chunk files. ```txt public/ vendor/ revogrid-pro/ revogrid-pro.js revogrid-pro.css revogrid-pro*.js ``` 2. Add the Pro stylesheet from your hosted asset path. ```html ``` 3. Load the public RevoGrid Core web component from jsDelivr. ```html ``` 4. Add an import map for Core package imports and the delivered Pro bundle. The standalone Core script defines ``. The `@revolist/revogrid` import map entry is still required because the Pro ESM bundle imports Core helpers by package name. ```html ``` 5. Import the Pro plugins from a module script. ```html ``` ## Complete HTML Example Save this as an HTML file and open it through any static web server. Do not use a `file://` URL; browser module imports should be served over HTTP. The Pro ESM bundle imports `@revolist/revogrid` as a package name. Browsers do not resolve npm package names by themselves, so the import map tells the browser where each package entry points in your hosted assets. --- # NPM Installation Guide URL: https://pro.rv-grid.com/guides/installation-npm/ Source: src/content/docs/guides/installation-npm.mdx Description: Learn how to install RevoGrid Pro and Enterprise using npm **1. Configure NPM Registry** First, configure npm to use the RevoGrid registry: ```bash npm config set "@revolist:registry=https://npm.rv-grid.com" ``` The command npm config set `@revolist:registry=https://npm.rv-grid.com` is used to configure npm (Node Package Manager) to use a specific registry for packages that are scoped under the `@revolist` namespace. --- **2. Install RevoGrid Pro and Enterprise** Install the core grid, RevoGrid Pro, and RevoGrid Enterprise packages: ```bash npm install @revolist/revogrid npm install @revolist/revogrid-pro npm install @revolist/revogrid-enterprise ``` --- **3. Framework Integration Packages** For better integration with specific frameworks, you can install the corresponding framework package: **React** ```bash npm install @revolist/react-datagrid ``` **Angular** ```bash npm install @revolist/angular-datagrid ``` **Vue** ```bash npm install @revolist/vue3-datagrid ``` **Svelte** ```bash npm install @revolist/svelte-datagrid ``` Read more about [RevoGrid Pro Tokens](/guides/installation-token). To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git). --- # NPM Installation Guide URL: https://pro.rv-grid.com/guides/installation-npm-trial/ Source: src/content/docs/guides/installation-npm-trial.mdx Description: Learn how to install RevoGrid Pro and Enterprise trial packages using npm Trial access uses separate package names and asset paths from the full paid packages. After purchasing the full version, switch to `@revolist/revogrid-pro` and `@revolist/revogrid-enterprise`. Clone or inspect the working trial example: [github.com/revolist/revogrid-pro-trial](https://github.com/revolist/revogrid-pro-trial.git). Trial packages use `@revolist/rv-pro-trial`, `@revolist/rv-enterprise-trial`, `rv-pro-trial.css`, and `rv-enterprise-trial.css`. **1. Configure NPM Registry** First, configure npm to use the RevoGrid registry: ```bash npm config set "@revolist:registry=https://trial.rv-grid.com" ``` The command npm config set `@revolist:registry=https://trial.rv-grid.com` is used to configure npm (Node Package Manager) to use a specific registry for packages that are scoped under the `@revolist` namespace. --- **2. Install RevoGrid Pro Trial** Install the core grid and RevoGrid Pro trial package: ```bash npm install @revolist/revogrid npm install @revolist/rv-pro-trial ``` Use Pro exports from the trial package and import the trial CSS file: ```typescript import '@revolist/rv-pro-trial/dist/rv-pro-trial.css'; import { RowOddPlugin } from '@revolist/rv-pro-trial'; ``` Do not use the full package CSS path with the trial package: ```typescript // Incorrect for trial packages import '@revolist/rv-pro-trial/dist/revogrid-pro.css'; ``` --- **3. Install RevoGrid Enterprise Trial** Install Enterprise trial when you need Pivot or other Enterprise-only features: ```bash npm install @revolist/rv-enterprise-trial ``` Enterprise trial builds on Pro trial, so keep both `@revolist/rv-pro-trial` and `@revolist/rv-enterprise-trial` installed. ```typescript import '@revolist/rv-pro-trial/dist/rv-pro-trial.css'; import '@revolist/rv-enterprise-trial/dist/rv-enterprise-trial.css'; import { PivotPlugin, type PivotConfig } from '@revolist/rv-enterprise-trial'; import { commonAggregators } from '@revolist/rv-pro-trial'; ``` Minimal Pivot setup: ```typescript import { defineCustomElements } from '@revolist/revogrid/loader'; import '@revolist/rv-pro-trial/dist/rv-pro-trial.css'; import '@revolist/rv-enterprise-trial/dist/rv-enterprise-trial.css'; import { PivotPlugin, type PivotConfig } from '@revolist/rv-enterprise-trial'; import { commonAggregators } from '@revolist/rv-pro-trial'; defineCustomElements(); const grid = document.createElement('revo-grid'); const pivot: PivotConfig = { dimensions: [ { prop: 'age' }, { name: 'Date of Birth', prop: 'dateOfBirth', aggregators: { count: commonAggregators.count, }, }, ], rows: ['age'], values: [{ prop: 'dateOfBirth', aggregator: 'count' }], hasConfigurator: true, }; grid.plugins = [PivotPlugin]; grid.additionalData = { pivot }; grid.source = [ { name: 'John Doe', age: 25, dateOfBirth: '1998-01-15' }, { name: 'Jane Smith', age: 30, dateOfBirth: '1993-03-22' }, ]; document.getElementById('app')?.appendChild(grid); ``` --- **4. Framework Integration Packages** For better integration with specific frameworks, you can install the corresponding framework package: **React** ```bash npm install @revolist/react-datagrid ``` **Angular** ```bash npm install @revolist/angular-datagrid ``` **Vue** ```bash npm install @revolist/vue3-datagrid ``` **Svelte** ```bash npm install @revolist/svelte-datagrid ``` --- # Build from Source URL: https://pro.rv-grid.com/guides/installation-source/ Source: src/content/docs/guides/installation-source.mdx Description: Learn how to build RevoGrid Pro and Enterprise from source For maximum control, you can build the RevoGrid Pro and Enterprise plugins from source. This allows you to customize the build process if needed. 1. Clone the repository and navigate to the project directory. ```bash git clone https://github.com/revolist/revogrid-pro.git cd revogrid-pro ``` 2. Run the build commands to generate both plugin packages. ```bash pnpm build:pro pnpm build:enterprise ``` 3. After the build completes, you will find the generated files in `/packages/pro/dist` and `/packages/enterprise/dist`, ready for use in your project. Choose the method that best suits your workflow, and enjoy the powerful features of RevoGrid Pro and Enterprise plugins! --- # Access Token Installation Guide URL: https://pro.rv-grid.com/guides/installation-token/ Source: src/content/docs/guides/installation-token.mdx Description: Learn how to use access tokens with RevoGrid Pro NPM Registry Access tokens provide a secure way to authenticate with the RevoGrid Pro repository, especially useful for CI/CD environments or multi-user repository access. Instead of using password authentication, you can create and manage tokens for secure authorization. ### Prerequisites Before creating access tokens, you must first configure and login to the npm server. Please follow the [NPM Installation Guide](/guides/installation-npm) instructions first. ### Creating an Access Token To create a new token using the command line, run: ```bash npm token create --registry=https://npm.rv-grid.com ``` You will be prompted for your password. After entering it, a new token will be displayed in the console: ```bash ┌──────────┬─────────────────────────┐ │ token │ Ihj... │ ├──────────┼─────────────────────────┤ │ user │ your-username │ ├──────────┼─────────────────────────┤ │ cidr │ │ ├──────────┼─────────────────────────┤ │ readonly │ false │ ├──────────┼─────────────────────────┤ │ created │ 2025-03-21T01:02:03.00Z │ └──────────┴─────────────────────────┘ ``` Make sure to copy and securely store the token value. ### Viewing Access Tokens To view all your available tokens: ```bash npm token list --registry=https://npm.rv-grid.com ``` This will display a list of all your active tokens: ```bash ┌────────┬─────────┬────────────┬──────────┬────────────────┐ │ id │ token │ created │ readonly │ CIDR whitelist │ ├────────┼─────────┼────────────┼──────────┼────────────────┤ │ b54f12 │ eyJhb.… │ 2024-03-21 │ no │ │ └────────┴─────────┴────────────┴──────────┴────────────────┘ ``` ### Removing an Access Token To remove a token, use its ID from the token list: ```bash npm token delete tokenId --registry=https://npm.rv-grid.com ``` ### Using Tokens in Your Project You can use access tokens by creating a `.npmrc` file in your project directory with the following content: ```ini @revolist:registry=https://npm.rv-grid.com //npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN ``` Replace `YOUR-AUTH-TOKEN` with the token value you received when creating the token. ### Token File Locations The `.npmrc` file can be stored in several locations, which npm checks in the following order: 1. Project-specific: `/path/to/your/project/.npmrc` 2. User-specific: `~/.npmrc` 3. Global: `$PREFIX/etc/npmrc` 4. npm built-in: `/path/to/npm/npmrc` ### CI/CD Integration For Continuous Integration/Continuous Delivery (CI/CD) systems, you can use access tokens stored in `.npmrc`. Add the following to your CI/CD environment's `.npmrc` file: ```ini @revolist:registry=https://npm.rv-grid.com //npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN ``` ### Multi-user Access For teams working on the same project, you have two options for repository access: 1. **Individual Logins**: Each developer can use their own credentials to access the repository 2. **Shared Tokens**: Create and share access tokens among team members ### Troubleshooting If you encounter issues with token authentication: 1. Verify your token is valid by running `npm token list` 2. Ensure your `.npmrc` file is properly formatted 3. Check that you're using the correct registry URL 4. Verify your network can access the npm registry For additional support or questions, please contact our support team. To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git). --- # Migrating to 2+ URL: https://pro.rv-grid.com/guides/migration-v2/ Source: src/content/docs/guides/migration-v2.mdx Description: Step-by-step guide for upgrading to RevoGrid Pro 2+ This guide covers every breaking change introduced in **v2.0** of `@revolist/revogrid-pro` and `@revolist/revogrid-enterprise`, and explains what to update in your codebase. v2.0 is a **major release** with breaking changes in imports, plugin configuration, plugin authoring, CSS setup, and the Enterprise package structure. Read through the checklist below before upgrading. --- ## Quick migration checklist 1. Upgrade `@revolist/revogrid` core to `^4.21.4` 2. Replace all sub-path imports with flat imports from `@revolist/revogrid-pro` 3. Replace hardcoded event strings with named constants from `@revolist/revogrid-pro` 4. Import `revogrid-pro.css` in your application entry point 5. Move plugin configuration from `grid.additionalData` to direct grid properties 6. Update custom plugins to extend `CorePlugin` instead of `BasePlugin` 7. Add `@revolist/revogrid-pro` as a dependency when using Enterprise 8. Move Pivot and Gantt imports to `@revolist/revogrid-enterprise` 9. Update Excel export integrations that relied on bundled SheetJS or bundled workbook import parsing when upgrading from `2.0.8` --- ## 1. Core dependency version v2.0 requires `@revolist/revogrid` **4.21.4 or later**. Earlier core versions are not supported. ```bash npm install @revolist/revogrid@^4.21.4 # or pnpm add @revolist/revogrid@^4.21.4 ``` --- ## 2. Import paths — flat surface only v1.x supported sub-path imports that pointed to internal modules. These paths are **removed** in v2.0. All exports are now available from the single package entry point. ```ts import { commonAggregators } from '@revolist/revogrid-pro/aggregations'; import { CorePlugin } from '@revolist/revogrid-pro/core'; import { FOCUS_APPLY_EVENT } from '@revolist/revogrid-pro/events'; import { createMergedData } from '@revolist/revogrid-pro/utils'; ``` ```ts import { commonAggregators, CorePlugin, FOCUS_APPLY_EVENT, createMergedData, } from '@revolist/revogrid-pro'; ``` --- ## 3. Event strings → named constants v2.0 exports more than 135 named event constants. Replace every hardcoded event string with the corresponding export to benefit from type checking and rename refactoring. ```ts grid.addEventListener('beforecellfocus', handler); grid.addEventListener('beforefocuslost', handler); grid.addEventListener('beforekeydown', handler); grid.addEventListener('aftersourceset', handler); ``` ```ts import { EVENT_BEFORE_CELL_FOCUS, EVENT_BEFORE_FOCUS_LOST, BEFORE_KEYDOWN_EVENT, AFTER_SOURCE_SET_EVENT, } from '@revolist/revogrid-pro'; grid.addEventListener(EVENT_BEFORE_CELL_FOCUS, handler); grid.addEventListener(EVENT_BEFORE_FOCUS_LOST, handler); grid.addEventListener(BEFORE_KEYDOWN_EVENT, handler); grid.addEventListener(AFTER_SOURCE_SET_EVENT, handler); ``` The full list of available constants is exported from `@revolist/revogrid-pro`. --- ## 4. CSS — required import Starting in v2.0, the Pro plugin styles are **not** injected automatically. You must import the stylesheet once in your application entry point. ```ts // main.ts (or your app entry) import '@revolist/revogrid-pro/dist/revogrid-pro.css'; ``` ```html ``` If you are also using Enterprise, import its stylesheet separately: ```ts import '@revolist/revogrid-enterprise/dist/revogrid-enterprise.css'; ``` --- ## 5. Plugin configuration — direct grid properties v2.0 introduces a direct property system for Pro and Enterprise plugin configuration. Plugin options should now be assigned to their own grid property instead of being grouped under `grid.additionalData`. `additionalData` is deprecated for plugin configuration. It is still read as a legacy fallback during the v2 migration window, but new code should use direct properties. This is a breaking change because plugins no longer treat one large shared object as the primary configuration surface. The old pattern made `additionalData` grow too quickly: unrelated plugins shared the same object, and changing one key could notify every plugin observing `additionalData`. That could cause unrelated state updates and unnecessary redraws. Direct properties make each plugin observe only the configuration it owns. ```ts grid.additionalData = { pivot, pagination, tree: { expandedRowIds, }, rowExpand: { expandedRows, }, rowAutoSize: { mode: 'auto', }, eventManager: { applyEventsToSource: true, }, }; ``` ```ts grid.pivot = pivot; grid.pagination = pagination; grid.tree = { expandedRowIds, }; grid.rowExpand = { expandedRows, }; grid.rowAutoSize = { mode: 'auto', }; grid.eventManager = { applyEventsToSource: true, }; ``` For framework wrappers, bind the plugin property directly: ```tsx ``` ```vue ``` ```ts import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { RevoGrid } from '@revolist/angular-datagrid'; @Component({ selector: 'my-grid', standalone: true, imports: [RevoGrid], // Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs. schemas: [NO_ERRORS_SCHEMA], template: ` `, }) export class MyGridComponent {} ``` Common replacements: | v1.x `additionalData` key | v2.0 property | |---|---| | `additionalData.pivot` | `grid.pivot` | | `additionalData.pagination` | `grid.pagination` | | `additionalData.tree` | `grid.tree` | | `additionalData.rowExpand` | `grid.rowExpand` | | `additionalData.rowAutoSize` | `grid.rowAutoSize` | | `additionalData.masterRow` | `grid.masterRow` | | `additionalData.transpose` | `grid.transpose` | | `additionalData.rowContextMenu` | `grid.rowContextMenu` | | `additionalData.columnContextMenu` | `grid.columnContextMenu` | | `additionalData.eventManager` | `grid.eventManager` | | `additionalData.infinityScroll` | `grid.infinityScroll` | | `additionalData.cellMerge` | `grid.cellMerge` | | `additionalData.excel` | `grid.excel` | | `additionalData.hiddenColumns` | `grid.hideColumns` | Core grid inputs such as `source`, `columns`, `plugins`, `theme`, `stretch`, `rowDefinitions`, and `rowHeaders` are already direct RevoGrid properties. Keep binding those directly as before. --- ## 6. Custom plugins — extend `CorePlugin` In v2.0 the recommended base class for all Pro plugins changed from `BasePlugin` (re-exported from `@revolist/revogrid`) to `CorePlugin` (exported from `@revolist/revogrid-pro`). `CorePlugin` adds: - `observeAttribute(name, callback)` — reacts to DOM attribute mutations - `observeProperty(name, callback)` — reacts to direct JS property assignments ```ts import { BasePlugin } from '@revolist/revogrid'; export class MyPlugin extends BasePlugin { constructor(grid, providers) { super(grid, providers); // plugin setup } } ``` ```ts import { CorePlugin } from '@revolist/revogrid-pro'; export class MyPlugin extends CorePlugin { constructor(grid, providers) { super(grid, providers); // license banner injected here automatically // plugin setup this.observeProperty('myProp', (value) => { // react to grid.myProp = value }); } } ``` --- ## 7. Enterprise — install Pro as a dependency `@revolist/revogrid-enterprise` now **requires** `@revolist/revogrid-pro` as a runtime dependency. If you only had Enterprise installed, add Pro explicitly. ```bash npm install @revolist/revogrid-pro @revolist/revogrid-enterprise # or pnpm add @revolist/revogrid-pro @revolist/revogrid-enterprise ``` Ensure both packages are on the **same version** (they are always released together). --- ## 8. Pivot and Gantt moved to Enterprise In v1.x, Pivot and Gantt plugins were part of `@revolist/revogrid-pro`. They now live exclusively in `@revolist/revogrid-enterprise`. ```ts import { PivotPlugin, PivotConfig } from '@revolist/revogrid-pro'; import { GanttPlugin } from '@revolist/revogrid-pro'; ``` ```ts import { PivotPlugin, PivotConfig } from '@revolist/revogrid-enterprise'; import { GanttPlugin } from '@revolist/revogrid-enterprise'; ``` --- ## 9. Excel export provider changes since 2.0.8 This is a breaking change for applications upgrading from `@revolist/revogrid-pro` `2.0.8` or earlier if they relied on bundled SheetJS, non-`.xlsx` default export output, or bundled `.xlsx` / `.xls` import parsing. `ExportExcelPlugin`, `grid.exportExcel`, `export-excel`, `excel-before-import`, and `excel-before-set` keep their public names. The implementation contract changed so workbook-library choices stay app-owned and the default package is lighter. | Area | In `2.0.8` and earlier | After this change | | --- | --- | --- | | Bundled export library | SheetJS `xlsx` | `write-excel-file` | | Default export format | SheetJS-supported formats | `.xlsx` only | | Default import | Workbook parsing through bundled dependency | CSV-only import | | XLSX import | Plugin-owned | App-owned parser | | Provider integration | Mostly implicit | Explicit provider objects or factories | | Formatted workbook output | Reconstructed by provider | Prepared in `context.cellRows` | ### Default export Use the bundled `.xlsx` provider for normal grid exports: ```ts grid.plugins = [ExportExcelPlugin]; await excel.export({ workbookName: 'report.xlsx', sheetName: 'Report', }); ``` The bundled provider now rejects non-`.xlsx` output: ```ts await excel.export({ workbookName: 'report.xlsb', }); await excel.export({ writingOptions: { bookType: 'xlsb' }, }); ``` Use SheetJS, ExcelJS, workbook-module, or a custom provider when another workbook format is required. ### App-owned SheetJS If an app depended on SheetJS output formats, install `xlsx` in the app and pass the module object into the SheetJS adapter: ```sh pnpm add xlsx ``` ```ts import * as XLSX from 'xlsx'; import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createSheetJsExcelExportProvider(XLSX), }; ``` The SheetJS adapter writes plain `context.rows` data. It is the compatibility path for apps that want SheetJS-owned workbook writing. ### App-owned ExcelJS Use ExcelJS when migration requires formatted `cellRows`, merge spans, row heights, freeze panes, or richer workbook style mapping: ```sh pnpm add exceljs ``` ```ts import ExcelJS from 'exceljs'; import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro'; grid.exportExcel = { exportProvider: createExcelJsExcelExportProvider(ExcelJS), }; ``` The ExcelJS adapter does not add ExcelJS to RevoGrid Pro dependencies. Your application owns the dependency and bundle cost. ### Custom providers Custom providers should consume the prepared provider context instead of reading rendered grid rows: ```ts const provider = { id: 'custom', async export(context) { await writeWorkbook(context.cellRows, { fileName: context.workbookName, sheetName: context.sheetName, metadata: context.exportMetadata, }); }, }; ``` Use `context.cellRows` when the provider needs formatted cells, grouped headers, formulas, or merge spans. Use `context.rows` only for plain row-object output. ### CSV-only default import Default import now accepts CSV files. It reads files with `FileReader.readAsText`, dispatches `excel-before-import`, maps rows, dispatches `excel-before-set`, then updates the grid source unless either event is prevented. `.xlsx` and `.xls` parsing is no longer bundled. For workbook uploads: 1. Install a parser such as SheetJS, ExcelJS, or read-excel-file. 2. Parse the file in app code. 3. Normalize workbook data into `columns` and `rows`. 4. Assign `grid.columns`, then `grid.source`. See [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) for parser tradeoffs and examples. ### Feature-owned helpers Feature-owned export helpers are still exported from Pro: - Formula helper for exporting workbook formulas. - Cell merge and grouped column transforms run automatically. - Date, numeral, and select helpers are explicit utilities for optional column packages. - `exportTransformers` are available on both `grid.exportExcel` and individual `plugin.export()` calls. Provider contexts now include `cellRows`, `sourceRows`, `headerRowsCount`, `gridDerivedOptions`, and `exportMetadata`. Custom providers that need richer export output should consume those fields instead of reconstructing workbook state from `rows`. When migrating custom providers, keep these rules in mind: - Export data is prepared from data stores and grid source fallbacks, not rendered DOM rows. - `sourceRows` preserves pinned segment semantics through `rowType` (`rowPinStart`, `rgRow`, `rowPinEnd`). - `cellRows` includes generated headers and formatted provider cells; its data rows stay in pinned-top, body, pinned-bottom order. - Missing pinned sources are empty. If a body data store is not initialized, export falls back to `grid.source`. --- ## Versioning strategy Pro and Enterprise are always released with the **same version number** (lockstep semver). | Change type | Version bump | |---|---| | Breaking API/behavior change | **Major** (`2.0.0 → 3.0.0`) | | New plugin or feature (backward-compatible) | **Minor** (`2.0.0 → 2.1.0`) | | Bug fix | **Patch** (`2.0.0 → 2.0.1`) | The RevoGrid core package (`@revolist/revogrid`) versions independently — always check its changelog when upgrading core alongside Pro/Enterprise. --- # Pagination Remote URL: https://pro.rv-grid.com/guides/pagination-remote/ Source: src/content/docs/guides/pagination-remote.mdx Description: Manage large datasets by breaking them into smaller, more manageable pages. This example shows how to use pagination with remote data in the grid. To enable pagination you'll need to use the `PaginationPlugin`. This plugin provides the necessary functionality to paginate data within the grid. --- # Pivot Overview URL: https://pro.rv-grid.com/guides/pivot/ Source: src/content/docs/guides/pivot/index.mdx Description: Learn the Pivot mental model, when to use it, and how to build your first RevoGrid Pivot. Pivot turns a flat fact table into an analytical view. You choose which fields define the row hierarchy, which fields define generated columns, and which fields become aggregated measures. RevoGrid stays responsible for rendering and interaction, while Pivot builds the grouped rows, generated headers, totals, and summary cells. For raw type signatures, see [Pivot API](/api/pivot/) and [Pivot Config API](/api/pivot-config/). Use this guide set for behavior, defaults, and how the parts work together. ## When To Use Pivot Use Pivot when your source rows represent detailed facts such as orders, transactions, tickets, or events, and users need to answer questions like: - "Sales by region and quarter" - "Count of incidents by team and priority" - "Average margin by category and month" - "Grand totals and subtotals across multiple hierarchies" Use a normal grid when users mainly inspect or edit row-level records. Use Pivot when users need grouped analytical summaries. ## Core Vocabulary - `dimension`: A field that can be placed on the row axis, column axis, or used as a value source. - `row field`: A dimension listed in `rows`. It creates the vertical hierarchy. - `column field`: A dimension listed in `columns`. It creates generated headers. - `value`: A measure listed in `values`. It is aggregated into Pivot cells. - `generated row`: A synthetic row produced by Pivot, not a raw input row. - `generated column`: A synthetic column or column group produced by Pivot. - `subtotal`: A summary for one intermediate branch of a hierarchy. - `grand total`: A summary across the full filtered result. ## Mental Model Client-side Pivot follows this shape: ```txt source rows -> group by rows / columns -> aggregate values -> generate row hierarchy -> generate column hierarchy -> render with RevoGrid ``` Server-side Pivot keeps the same public config, but the aggregation step moves behind a remote engine boundary: ```txt source rows -> analytical engine -> visible analytical window -> RevoGrid ``` That means the same concepts stay in place even when the data becomes too large for full client-side materialization. ## Conceptual Foundations If you are new to Pivot tables or Business Intelligence (BI) concepts, we recommend starting with these conceptual guides: - [What Is Pivot?](/guides/pivot/what-is-pivot/): The basic mental model of transforming flat data. - [What is an OLAP Cube?](/guides/pivot/olap-cube-concepts/): Understanding multidimensional analysis and core operations. - [Data Modeling for Pivot](/guides/pivot/data-modeling/): How to structure your facts and dimensions for the best results. ## First Working Pivot 1. Install the Enterprise package. ```bash pnpm add @revolist/revogrid-enterprise ``` 2. Import the plugin and any aggregators you want to expose. ```ts import { PivotPlugin, commonAggregators, type PivotConfig, } from '@revolist/revogrid-enterprise'; ``` 3. Register `PivotPlugin` on the grid. ```ts grid.plugins = [PivotPlugin]; ``` 4. Define a `PivotConfig`. ```ts const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region' }, { prop: 'quarter', name: 'Quarter' }, { prop: 'sales', name: 'Sales', aggregators: { ...commonAggregators, }, }, ], rows: ['region'], columns: ['quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], }; ``` 5. Apply it through the direct `pivot` binding. ```ts grid.pivot = pivot; ``` `additionalData.pivot` is still observed for legacy integrations, but new code should bind `pivot` directly. ## How To Read A PivotConfig ```ts const pivot: PivotConfig = { dimensions: [...], rows: ['region', 'rep'], columns: ['year', 'quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, collapsed: true, }; ``` - `dimensions` describes the reusable field metadata. - `rows` defines the row hierarchy from left to right. - `columns` defines the generated header hierarchy from left to right. - `values` defines which measures are aggregated in each visible cell. - `totals` enables grand totals and subtotals. - `collapsed` enables grouped row drill-down when the row hierarchy has multiple levels. - `groupAggregations` makes grouped Pivot rows show aggregate values across generated metric columns. ## What RevoGrid Pivot Owns When Pivot is active, `PivotPlugin` temporarily owns: - the rendered `source` - the rendered `columns` - optional `pinnedBottomSource` for grand totals - grouped-row metadata used for drill-down When Pivot is cleared, the plugin restores the original source, columns, grouping, and pinned totals. ## Feature Map - Client-side Pivot rows, columns, values, totals, and drill-down are implemented. - Client-side grouped row aggregates are implemented through `pivot.groupAggregations`. - `valuesOnRows`, flat headers, and the drag-and-drop configurator are implemented. - Server-side Pivot exposes a first-class remote engine/store contract and plugin integration. - The docs below describe the current implementation in `packages/enterprise/plugins/pivot` and `packages/enterprise/plugins/pivot-config`. ## Recommended Reading Order 1. [What Is Pivot?](/guides/pivot/what-is-pivot/) 2. [What is an OLAP Cube?](/guides/pivot/olap-cube-concepts/) 3. [Data Modeling for Pivot](/guides/pivot/data-modeling/) 4. [Dimensions](/guides/pivot/dimensions/) 5. [Aggregations](/guides/pivot/aggregations/) 6. [Configuration Reference](/guides/pivot/configuration-reference/) 7. [Totals](/guides/pivot/totals/) 8. [Values On Rows](/guides/pivot/values-on-rows/) 9. [Drill-Down](/guides/pivot/drilldown/) 10. [Sorting And Filtering](/guides/pivot/sorting-filtering/) 11. [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) 12. [Configurator](/guides/pivot/configurator/) 13. [Field Panel](/guides/pivot/field-panel/) 14. [Server-Side Pivot](/guides/pivot/server-side/) 15. [Caching And Cache Keys](/guides/pivot/caching/) 16. [OLAP Best Practices](/guides/pivot/olap-best-practices/) --- # Aggregations URL: https://pro.rv-grid.com/guides/pivot/concepts/aggregations/ Source: src/content/docs/guides/pivot/concepts/aggregations.mdx Description: Learn how Pivot measures work, how aggregators are resolved, and how to design reliable summaries. Aggregations turn raw field values into Pivot measures. A value field points to a source field, and an aggregator describes how that field should be summarized inside each Pivot cell. ## Dimension vs Measure - A `dimension` groups facts. - A `measure` summarizes facts. In RevoGrid Pivot, both are built from the same field model. The difference is how you place them: - `rows` and `columns` use a field as a grouping dimension. - `values` uses a field as a measure source. ## Basic Value Configuration ```ts values: [ { prop: 'sales', aggregator: 'sum' }, { prop: 'sales', aggregator: 'avg' }, { prop: 'orders', aggregator: 'count' }, ] ``` Each value becomes a separate measure in the generated Pivot output. ## How Aggregator Resolution Works `values[].aggregator` is a string id. Pivot resolves that id against the matching dimension's `aggregators` map. If you use the configurator, those same registered aggregator ids populate the measure selector. ```ts dimensions: [ { prop: 'sales', name: 'Sales', aggregators: { sum: commonAggregators.sum, avg: commonAggregators.avg, }, }, ], values: [{ prop: 'sales', aggregator: 'avg' }] ``` If the dimension does not expose the aggregator id, the measure cannot be resolved correctly. ## Built-In Summary Functions The public remote/server-side summary model supports these stable ids: - `sum` - `min` - `max` - `avg` - `count` Client-side Pivot can also use custom aggregator functions through the dimension model. ## Multiple Measures When you define multiple values, Pivot generates one measure per configured entry. That is useful for layouts like: - Sales sum by quarter - Margin avg by quarter - Order count by quarter On the client side, each generated value column uses the measure's `prop` and `aggregator`. On the remote side, result ids are stable summary ids such as `SalesAmount_sum` and `SalesAmount_avg`. ## Formatting Measures Formatting belongs in the dimension metadata used by the generated columns. For example, attach a numeric column type or a custom template to the measure dimension so generated cells inherit the right rendering behavior. ## Custom Aggregators Custom aggregators are useful when: - you need a domain-specific summary - the display value is not one of the common numeric summaries - you are staying on the client-side path Keep them lightweight. Client-side aggregators are run while materializing Pivot output, so very expensive functions will slow down large pivots. ## Average And Derived Measures `avg` deserves special attention in OLAP-style systems: - It is not additive. - Correct remote planning often requires `sum + count` internally. - Totals must be computed from underlying facts, not from already-averaged child cells. The remote planner layer in this repository already models `avg` as a derived summary when planning server-side requests. ## Best Practices - Prefer additive measures like `sum` and `count` when possible. - Only expose aggregators that make business sense for the field. - Keep stable aggregator ids across saved states and remote APIs. - Name measures clearly in examples and UI text so users know whether they are looking at a sum, avg, or count. ## Next Continue with [Configuration Reference](/guides/pivot/configuration-reference/) for the full `PivotConfig` surface. --- # Configuration Reference URL: https://pro.rv-grid.com/guides/pivot/concepts/configuration-reference/ Source: src/content/docs/guides/pivot/concepts/configuration-reference.mdx Description: Complete reference for every public PivotConfig option in the current implementation. `PivotConfig` is the public configuration model for both client-side and server-side Pivot. This page explains every option in the current implementation and how the options interact. For raw signatures, see [Pivot Config API](/api/pivot-config/). Use this page for defaults, behavior, and practical guidance. ## Minimal Example ```ts const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region' }, { prop: 'quarter', name: 'Quarter' }, { prop: 'sales', name: 'Sales', aggregators: commonAggregators }, ], rows: ['region'], columns: ['quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], }; ``` ## Core Data Model ### `dimensions` Reusable field definitions. This is where you configure field labels, column behavior, and available aggregators. - Purpose: Define the fields Pivot can reuse. - Accepted value: `PivotConfigDimension[]` - Default: `[]` - Important interactions: `values[].aggregator` resolves against the matching dimension. ### `rows` Ordered row hierarchy. - Purpose: Define the vertical analytical hierarchy. - Accepted value: `ColumnProp[]` - Default: required - Important interactions: row drill-down only becomes meaningful when the effective row hierarchy has more than one level. ### `columns` Ordered generated column hierarchy. - Purpose: Define the horizontal analytical hierarchy. - Accepted value: `ColumnProp[]` - Default: `[]` - Important interactions: `flatHeaders`, `columnCollapse`, and column totals only matter when column fields exist. ### `values` Measures to aggregate. - Purpose: Define which fields are summarized inside each Pivot cell. - Accepted value: `PivotConfigValue[]` - Default: required - Important interactions: values can render as columns or as synthetic rows through `valuesOnRows`. ## Layout Options ### `flatHeaders` Flattens grouped column headers into single labels. - Purpose: Reduce visible nesting in generated columns. - Accepted value: `boolean` - Default: `false` - Common pitfall: flat headers still preserve analytical grouping, they only change header presentation. With `flatHeaders: false`, generated Pivot columns use RevoGrid grouped headers so repeated parent labels are visually grouped. ### `mergeValueHeaders` Merges generated value headers into the terminal column header labels. - Purpose: Remove repeated measure headers such as `Revenue` under every generated column member. - Accepted value: `boolean` - Default: `false` - Behavior: with one value, terminal columns use the column member label, for example `Q1`; with multiple values, the value label is appended, for example `Q1 Revenue`. - Important interactions: can be combined with `flatHeaders`; `valuesOnRows` already moves measures out of generated value columns. ### `valuesOnRows` Moves values into the row hierarchy. - Purpose: Render measures as synthetic row members. - Accepted value: `boolean` - Default: `false` - Important interactions: affects row drill-down behavior because values become part of the effective row structure. ### `rowTree` Controls the effective row hierarchy, including where measures appear. - Purpose: Place the `$values` pseudo-field at the beginning, middle, or end of the row tree. - Accepted value: `(ColumnProp | '$values')[]` - Default: derived from `rows`, with `$values` appended only when `valuesOnRows: true` - Important interactions: when provided, `rowTree` overrides the value placement implied by `valuesOnRows`. ## Configurator Options ### `hasConfigurator` Enables the built-in Pivot configurator panel. - Purpose: Let users rearrange fields visually. - Accepted value: `boolean` - Default: `false` ### `mountTo` Custom configurator mount target. - Purpose: Render the configurator outside the default grid wrapper. - Accepted value: `HTMLElement` - Default: internal grid wrapper mount ### `showColumns` Hide or show the configurator columns zone. - Purpose: Control configurator UI visibility. - Accepted value: `boolean` - Default: `true` ### `showRows` Hide or show the configurator rows zone. - Purpose: Control configurator UI visibility. - Accepted value: `boolean` - Default: `true` ### `showValues` Hide or show the configurator values zone. - Purpose: Control configurator UI visibility. - Accepted value: `boolean` - Default: `true` ### `i18n` Configurator strings. - Purpose: Override built-in English labels. - Accepted value: `typeof PIVOT_CONFIG_EN` - Default: built-in English copy ## Totals ### `totals` Controls grand totals and subtotals. - Purpose: Add analytical summary rows and columns. - Accepted value: `PivotConfigTotals` - Default: all disabled Nested options: - `grandTotal` - `subtotals` - `grandTotalLabel` - `subtotalLabel` - `suppressSingleChildSubtotals` - `suppressGrandTotalWhenSingleLeaf` See [Totals](/guides/pivot/totals/) for behavior examples. ## Drill-Down State ### `collapsed` Initial row drill-down state. - Purpose: Enable grouped row expansion behavior. - Accepted value: `boolean` - Default: disabled unless `expanded` is provided - Important note: only matters when the effective row hierarchy has more than one level ### `expanded` Persisted grouped row expansion state. - Purpose: Restore row drill-down state. - Accepted value: `Record` - Default: `undefined` ### `groupAggregations` Render aggregate values inside grouped Pivot drill-down rows. - Purpose: Show grouped branch aggregates under generated Pivot metric columns, not only the group label. - Accepted value: `boolean` - Default: `false` - Important note: currently applies to the client-side Pivot path. Server-side Pivot still needs backend-precomputed grouped aggregates for exact support. ### `groupLabelColumn` Controls which visible row cell renders grouped row labels. - Purpose: Keep grouped labels visible when grouped fields are hidden with column-hide. - Accepted value: `'grouped' | 'firstVisible'` - Default: `'grouped'` - Behavior: - `'grouped'` keeps labels in the grouped field column. - `'firstVisible'` renders labels in the first currently visible row cell. ### `columnCollapse` Generated column collapse config. - Purpose: Enable collapsible Pivot column groups. - Accepted value: `boolean | PivotColumnCollapseConfig` - Default: disabled Nested options when using the object form: - `enabled` - `collapsed` - `aggregator` - `placeholder` ### `collapsedColumns` Persisted generated column collapse state. - Purpose: Restore collapsed column groups. - Accepted value: `boolean | Record` - Default: `undefined` ## Engine Options ### `engine` Switches between local and remote analytical execution. - Purpose: Keep the same public Pivot config while changing where computation happens. - Accepted value: object - Default: omitted, which keeps current client-side behavior Nested options: ### `engine.mode` - Accepted value: `'client' | 'server'` - Default: omitted - Behavior: `'server'` makes `PivotPlugin` load through an adapter or remote store ### `engine.adapter` - Accepted value: `PivotEngineAdapter` - Behavior: custom execution boundary used directly by the plugin ### `engine.remoteStore` - Accepted value: `PivotRemoteStore` - Behavior: framework-agnostic HTTP or transport-backed store used by the server adapter ### `engine.viewId` - Accepted value: `string` - Default: `'default'` - Behavior: backend semantic view or dataset identifier ### `engine.fieldsVersion` - Accepted value: `string` - Default: `'local'` - Behavior: field registry version or checksum ### `engine.rowAxis` - Accepted value: `Partial` - Default: `{ offset: 0, limit: 100 }` - Behavior: initial row-axis analytical window hint ### `engine.columnAxis` - Accepted value: `Partial` - Default: `{ offset: 0, limit: 24 }` - Behavior: initial column-axis analytical window hint ## Realistic Example ```ts const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region', sortable: true }, { prop: 'rep', name: 'Rep' }, { prop: 'year', name: 'Year' }, { prop: 'quarter', name: 'Quarter' }, { prop: 'sales', name: 'Sales', aggregators: commonAggregators, }, ], rows: ['region', 'rep'], columns: ['year', 'quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, collapsed: true, groupAggregations: true, hasConfigurator: true, }; ``` ## Common Mistakes - Treating `dimensions` as optional metadata only. They are the field model Pivot depends on. - Expecting `columns` to be required. A row-only Pivot is valid. - Using `columnCollapse` without generated column groups. - Assuming `engine` changes the public config shape. It only changes the execution path. ## Related Guides - [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) - [Configurator API](/guides/pivot/configurator-api/) - [Field Panel](/guides/pivot/field-panel/) - [Server-Side Pivot](/guides/pivot/server-side/) - [OLAP Best Practices](/guides/pivot/olap-best-practices/) --- # Dimensions URL: https://pro.rv-grid.com/guides/pivot/concepts/dimensions/ Source: src/content/docs/guides/pivot/concepts/dimensions.mdx Description: Understand Pivot dimensions, how they map to fields, and which column options carry over into Pivot. A Pivot dimension is the reusable definition of a field. It tells Pivot what the field is called, how it should behave in generated headers, and which aggregators are valid when that field is used as a measure source. ## What A Dimension Is Each dimension is a `PivotConfigDimension`. In practice it is a partial RevoGrid column definition plus Pivot-specific metadata. ```ts const dimensions = [ { prop: 'region', name: 'Region', sortable: true }, { prop: 'quarter', name: 'Quarter' }, { prop: 'sales', name: 'Sales', aggregators: { sum: commonAggregators.sum, avg: commonAggregators.avg, }, }, ]; ``` `prop` is the source-of-truth field id. `rows`, `columns`, and `values` all refer back to these same `prop` values. ## How Dimensions Are Reused - In `rows`, a dimension becomes part of the leading row hierarchy. - In `columns`, a dimension becomes part of the generated header hierarchy. - In `values`, the same field acts as the carrier for the aggregated measure. This shared model is what keeps Pivot configuration consistent. You define a field once, then reuse it on whichever analytical axis makes sense. ## Column Options That Matter In Pivot `PivotConfigDimension` extends `Partial`, so the most useful inherited options are: - `prop`: The field id Pivot uses everywhere. - `name`: Display label for generated headers and configurator entries. - `sortable`: Enables sorting of generated members for that dimension. - `order`: Initial sort direction for generated members when sorting is enabled. - `filter`: Filter metadata carried into generated columns. - `columnType`: Lets generated value columns reuse an existing column type. - `template`: Custom rendering for generated cells or headers. - `cellProperties`: Per-cell visual or behavioral overrides. - `size`, `minSize`, `maxSize`: Width behavior for generated columns. The most important rule is that Pivot does not invent a second metadata model. Generated columns are still RevoGrid columns, and the dimension is where their base behavior comes from. ## Aggregators Live On The Dimension Dimensions also declare which aggregators are available when the field is used in `values`. ```ts { prop: 'margin', name: 'Margin', aggregators: { sum: commonAggregators.sum, avg: commonAggregators.avg, max: commonAggregators.max, }, } ``` This matters in two places: - `values[].aggregator` resolves against the dimension. - The configurator uses the dimension's registered aggregators to populate its value selector. ## Good Dimension Design - Keep `prop` stable. Changing it breaks saved Pivot layouts. - Use business-friendly `name` values because generated headers reuse them. - Define aggregators only where they make semantic sense. - Avoid using display labels as identifiers. `prop` should be the identifier, `name` should be presentation. ## Common Mistakes - Omitting a dimension for a field that later appears in `values`. - Registering an aggregator on a text field just because the function exists. - Expecting dimension order to control row or column placement. Placement is controlled by `rows` and `columns`, not by the `dimensions` array order. ## Next Continue with [Aggregations](/guides/pivot/aggregations/) to see how measures are resolved from these dimensions. --- # Drill-Down URL: https://pro.rv-grid.com/guides/pivot/concepts/drilldown/ Source: src/content/docs/guides/pivot/concepts/drilldown.mdx Description: Understand row expansion, column collapse, and the state that controls Pivot exploration. Drill-down lets users move between summary and detail inside the Pivot structure. In the current implementation there are two separate drill-down concepts: grouped row expansion and generated column collapse. ## Row Drill-Down Row drill-down is enabled when: - the effective row hierarchy has more than one level - `collapsed` is a boolean or `expanded` state exists ```ts const pivot: PivotConfig = { rows: ['region', 'rep'], values: [{ prop: 'sales', aggregator: 'sum' }], collapsed: true, }; ``` Behavior: - `collapsed: true` starts grouped rows collapsed - `collapsed: false` starts grouped rows expanded - `expanded` stores per-group overrides using grouped path keys `PivotPlugin` translates the Pivot row structure into RevoGrid grouping options, then emits `pivot-config-update` when users expand or collapse groups. ## Grouped Row Aggregates Set `groupAggregations: true` to render aggregate values inside grouped Pivot rows. ```ts const pivot: PivotConfig = { rows: ['region', 'rep'], columns: ['year'], values: [{ prop: 'sales', aggregator: 'sum' }], collapsed: true, groupAggregations: true, }; ``` Behavior: - the grouped row still shows the expand/collapse UI and group label - generated Pivot value columns show the aggregate for the current grouped branch - nested groups aggregate only their own descendants - collapsed groups still show their aggregate values If you hide the grouped row field column (for example with `hide-columns="region"`), you can move the grouped label into the first visible row cell: ```ts const pivot: PivotConfig = { rows: ['region', 'product'], columns: ['quarter'], values: [{ prop: 'revenue', aggregator: 'sum' }], collapsed: true, groupAggregations: true, groupLabelColumn: 'firstVisible', }; ``` `groupLabelColumn` modes: - `'grouped'` (default): keep label in the grouped field column - `'firstVisible'`: render label in the first currently visible row cell Current limitation: - `groupAggregations` currently applies to the client-side Pivot path - server-side Pivot still renders grouped labels only unless backend-precomputed grouped aggregates are added ## Column Drill-Down Column drill-down is controlled by `columnCollapse`. ```ts columnCollapse: { enabled: true, collapsed: false, aggregator: 'sum', } ``` Behavior: - generated Pivot column groups become collapsible - collapsed groups render a placeholder header/cell - placeholder cells use a collapsed-bucket aggregator, not a naive sum of visible child cells Persisted state for generated groups lives in `collapsedColumns`. ## Related Options - `collapsed`: row drill-down default - `expanded`: persisted row group expansion state - `groupAggregations`: render aggregate values inside grouped client-side Pivot rows - `groupLabelColumn`: choose where grouped row labels render (`'grouped'` or `'firstVisible'`) - `columnCollapse`: enable and configure generated column collapse - `collapsedColumns`: persisted generated column collapse state ## Placeholder Behavior When a column group is collapsed, Pivot can render: - a default placeholder header based on the group name - a custom placeholder header or cell template - a custom collapsed aggregator, globally or per value field This is useful when a collapsed analytical bucket should still show a meaningful aggregate instead of just hiding all child columns. ## State Persistence `PivotPlugin` emits `pivot-config-update` as users interact with grouped rows or collapsible columns. That event is the right place to persist the current Pivot state if you want drill-down state to survive reloads. ## Common Mistakes - Expecting row drill-down with only one row field. - Expecting grouped rows to show metric values without enabling `groupAggregations`. - Expecting `columnCollapse` to work without generated column groups. - Treating `expanded` as UI row indexes. It is persisted grouped state, not viewport position. ## Next Continue with [Sorting And Filtering](../pivot/sorting-filtering/) or [Plugin Lifecycle](../pivot/plugin-lifecycle/). --- # Export And State URL: https://pro.rv-grid.com/guides/pivot/concepts/export-state/ Source: src/content/docs/guides/pivot/concepts/export-state.mdx Description: Export visible Pivot data to CSV, copy Pivot data as TSV, and save Pivot layout as JSON. Pivot export helpers turn the current visible Pivot grid into plain text that users can download, paste, or save. Pivot state JSON saves the Pivot configuration so the same report layout can be restored later. ## What Each Format Is For CSV means comma-separated values. Use CSV when users need a file they can open in Excel, Numbers, Google Sheets, or another reporting tool. The Pivot CSV helper flattens grouped column headers into readable header paths and appends pinned bottom rows, such as grand totals, after the body rows. TSV means tab-separated values. Use TSV for copy and paste because spreadsheets understand tabs as cell boundaries. The Pivot TSV helper keeps row-axis headers and column-axis headers separated, which makes pasted Pivot output easier to read. State JSON is not table data. It is a saved Pivot configuration, including fields such as rows, columns, values, filters, totals, collapsed state, and expanded groups. Use state JSON when users need to save a report layout, share a layout, or restore a layout after reloading the page. ## Basic Setup Import the helpers from Enterprise with the Pivot plugin: ```ts import { buildPivotCopyTsv, exportVisiblePivotToCsv, parsePivotStateJson, PivotPlugin, serializePivotStateJson, type PivotConfig, } from '@revolist/revogrid-enterprise'; ``` Render Pivot as usual: ```ts const pivot: PivotConfig = { rows: ['region', 'channel'], columns: ['quarter'], values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue' }, { prop: 'orders', aggregator: 'sum', label: 'Orders' }, ], totals: { grandTotal: true, subtotals: true, }, }; grid.plugins = [PivotPlugin]; grid.pivot = pivot; grid.source = rows; ``` ## Read The Visible Grid Model CSV and TSV helpers work from the current grid model, not from the original raw dataset. Pass the visible `columns`, visible `source`, and any `pinnedBottomSource`. ```ts function readGridModel(grid: HTMLRevoGridElement & { pinnedBottomSource?: any[] }) { return { columns: grid.columns ?? [], source: grid.source ?? [], pinnedBottomSource: grid.pinnedBottomSource ?? [], }; } ``` This matters because Pivot generates columns and rows from the source data. Exporting the original array would miss generated value columns, grouped headers, subtotal rows, and grand-total rows. ## Export CSV Use `exportVisiblePivotToCsv` when the user wants a file-oriented export: ```ts const csv = exportVisiblePivotToCsv(readGridModel(grid)); ``` For browser download: ```ts const csv = exportVisiblePivotToCsv(readGridModel(grid)); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'pivot-report.csv'; link.click(); URL.revokeObjectURL(url); ``` CSV is best for file export, email attachments, imports into other systems, and audit snapshots. ## Copy TSV Use `buildPivotCopyTsv` when the user wants clipboard text: ```ts const tsv = buildPivotCopyTsv(readGridModel(grid)); await navigator.clipboard.writeText(tsv); ``` TSV is best for copy actions because pasting into a spreadsheet preserves cells without requiring the user to import a file. You can also limit copy output when you know the selected props or selected rows: ```ts const tsv = buildPivotCopyTsv({ ...readGridModel(grid), selectedProps: ['region', '__pivot_grand_total__|revenue'], selectedRowIndexes: [0, 1, 2], }); ``` Only pass selected props that exist in the current visible columns. Missing props are ignored. ## Save State JSON Use `serializePivotStateJson` to turn the current Pivot configuration into deterministic JSON: ```ts const json = serializePivotStateJson(pivot); localStorage.setItem('sales:pivot-state', json); ``` The JSON is wrapped with a kind and version so the parser can reject unsupported state: ```json { "config": { "columns": ["quarter"], "rows": ["region", "channel"], "values": [{ "aggregator": "sum", "prop": "revenue" }] }, "kind": "revogrid:pivot.state", "version": 1 } ``` Use `parsePivotStateJson` to validate and restore it: ```ts try { const saved = localStorage.getItem('sales:pivot-state'); if (saved) { grid.pivot = parsePivotStateJson(saved) as PivotConfig; } } catch (error) { console.error('Could not restore Pivot state:', error); } ``` State JSON is best for saved views, shared report layouts, user preferences, and restoring drill-down state after refresh. ## Practical Example This example exposes four buttons: - `CSV`: reads the visible Pivot grid and writes CSV text into the output box. - `TSV`: reads the visible Pivot grid and writes clipboard-ready TSV into the output box. - `State JSON`: serializes the current Pivot configuration. - `Apply JSON`: parses the edited JSON and applies it back to the grid. The important part is keeping the data export path separate from the state path: ```ts let pivotConfig: PivotConfig = { rows: ['region', 'channel'], columns: ['quarter'], values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue' }, { prop: 'orders', aggregator: 'sum', label: 'Orders' }, ], }; function showCsv() { output.value = exportVisiblePivotToCsv(readGridModel(grid)); } function showTsv() { output.value = buildPivotCopyTsv(readGridModel(grid)); } function showState() { output.value = serializePivotStateJson(pivotConfig); } function applyState() { pivotConfig = parsePivotStateJson(output.value) as PivotConfig; grid.pivot = pivotConfig; } ``` ## Common Mistakes - Exporting the original source rows instead of the visible grid model. Use `grid.columns`, `grid.source`, and `grid.pinnedBottomSource`. - Using CSV for copy and paste. TSV usually pastes into spreadsheet cells more reliably. - Expecting state JSON to contain row data. It stores Pivot configuration, not the source dataset or exported result. - Applying edited JSON without `try` / `catch`. Invalid JSON, unsupported versions, and invalid field shapes throw errors. - Forgetting pinned bottom rows. If grand totals are enabled, include `pinnedBottomSource` so totals appear in CSV and TSV output. - Saving functions in state JSON. State JSON can only contain JSON-safe configuration values; custom runtime callbacks must be restored by application code. ## Next Continue with [Sorting And Filtering](../sorting-filtering/) or [Field Panel](../field-panel/). --- # Field Metadata URL: https://pro.rv-grid.com/guides/pivot/concepts/field-metadata/ Source: src/content/docs/guides/pivot/concepts/field-metadata.mdx Description: Configure Pivot field labels, hidden dimensions, empty group labels, measure aliases, and selective subtotals. Field metadata is the information Pivot uses to describe fields before they become row groups, column groups, filters, or measures. Use it to give users clearer labels, keep internal fields out of normal field lists, and make generated totals easier to read. ## Hidden Dimensions Mark a dimension as `hidden` when it should still be available to the Pivot config but should not appear in normal field lists. ```ts const dimensions = [ { prop: 'region', name: 'Region' }, { prop: 'quarter', name: 'Quarter' }, { prop: 'internalSegment', name: 'Internal Segment', description: 'Internal reporting segment.', hidden: true, }, ]; const pivot = { dimensions, rows: ['region'], columns: ['quarter', 'internalSegment'], filters: ['internalSegment'], values: [{ prop: 'revenue', aggregator: 'sum' }], }; ``` Hidden does not remove the field from the Pivot engine. In this example, `internalSegment` can still be used in `columns` and `filters`; it is only hidden from helper-driven field lists unless you explicitly show hidden fields. ## Show Hidden Fields In Search Use `filterPivotDimensions` for searchable field lists. Hidden dimensions are omitted by default. Pass `{ showHidden: true }` when you want an admin or advanced-user mode that reveals them. ```ts import { filterPivotDimensions } from '@revolist/revogrid-enterprise'; const visibleFields = filterPivotDimensions(dimensions, searchText); const allFields = filterPivotDimensions(dimensions, searchText, { showHidden: true, }); ``` The helper searches `prop`, `name`, and `description`, so descriptions are useful for discovery even when the visible label is short. ## Empty And Null Group Labels Real data often contains empty strings, `null`, or `undefined`. Pivot can replace those values with user-facing labels in row and column groups. ```ts const pivot = { rows: ['region', 'channel'], columns: ['quarter'], values: [{ prop: 'revenue', aggregator: 'sum' }], groupLabels: { empty: '(Empty)', null: '(No value)', }, }; ``` `groupLabels.empty` is used for empty strings (`''`). `groupLabels.null` is used for `null` and `undefined`. Without these options, the current implementation displays empty labels for those group members. ## Measure Aliases Each value can have its own display label. Use `label` for the preferred generated measure name. Use `name` as a fallback when `label` is not set. ```ts const pivot = { values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue $' }, { prop: 'units', aggregator: 'sum', name: 'Units Sold' }, ], }; ``` Aliases are useful when the source field name is technical, abbreviated, or shared by multiple reports. ## Disabled Subtotals Global subtotals can stay enabled while specific subtotal rows or subtotal columns are skipped. Target subtotals by axis field or by zero-based axis level. ```ts const pivot = { rows: ['region', 'channel', 'product'], columns: ['quarter', 'internalSegment'], values: [{ prop: 'revenue', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true, disabledSubtotals: { rows: { fields: ['channel'], }, columns: { levels: [0], }, }, }, }; ``` In this example, Pivot keeps subtotal generation enabled overall, skips row subtotals for the `channel` field, and skips column subtotals at level `0`. ## Complete Config ```ts const pivot = { dimensions: [ { prop: 'region', name: 'Region', description: 'Sales territory.' }, { prop: 'channel', name: 'Channel', description: 'Go-to-market channel.' }, { prop: 'product', name: 'Product' }, { prop: 'quarter', name: 'Quarter' }, { prop: 'internalSegment', name: 'Internal Segment', hidden: true }, { prop: 'revenue', name: 'Revenue' }, { prop: 'units', name: 'Units' }, ], rows: ['region', 'channel', 'product'], columns: ['quarter', 'internalSegment'], filters: ['internalSegment'], values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue $' }, { prop: 'units', aggregator: 'sum', name: 'Units Sold' }, ], groupLabels: { empty: '(Empty)', null: '(No value)', }, totals: { grandTotal: true, subtotals: true, disabledSubtotals: { rows: { fields: ['channel'] }, columns: { levels: [0] }, }, }, }; ``` ## Common Mistakes - Expecting `hidden: true` to prevent a field from being used in `rows`, `columns`, `filters`, or `values`. It only affects field-list helpers. - Passing `{ showHidden: true }` to the Pivot config. It belongs to `filterPivotDimensions`, not `pivot`. - Using only `groupLabels.empty` when the source data contains `null` or `undefined`. - Disabling `totals.subtotals` and then expecting `disabledSubtotals` to do anything. Selective disabling only matters when `subtotals: true`. --- # Field Panel URL: https://pro.rv-grid.com/guides/pivot/concepts/field-panel/ Source: src/content/docs/guides/pivot/concepts/field-panel.mdx Description: Show Pivot row, column, data, and filter fields above the grid. The Pivot field panel renders the active field layout above the grid. Use it when users need to see or rearrange row, column, data, and filter fields without opening a separate configurator panel. ## Enable It ```ts const pivot = { dimensions: [...], rows: ['region', 'rep'], columns: ['year', 'quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], filters: ['year'], fieldPanel: { visible: true, allowFieldDragging: true, allowFieldRemoving: false, showDataFields: true, showRowFields: true, showColumnFields: true, showFilterFields: true, texts: { dropFilterFieldsHere: 'Drop Filter Fields Here', }, }, }; ``` ## Options - `fieldPanel.visible`: mounts the compact in-grid field panel. - `allowFieldDragging`: enables drag-and-drop between panel areas. - `allowFieldRemoving`: enables explicit removal from areas. Default is `false`. - `showDataFields`, `showRowFields`, `showColumnFields`, `showFilterFields`: toggle individual areas. - `fieldPanel.texts`: overrides field panel labels and placeholders. - `filters`: stores the ordered filter fields shown in the filter area. `fieldPanel.visible` is separate from `hasConfigurator`. Use `fieldPanel.visible` for the compact in-grid field strip, and `hasConfigurator` for the full side configurator. --- # Formatting Presets URL: https://pro.rv-grid.com/guides/pivot/concepts/formatting-presets/ Source: src/content/docs/guides/pivot/concepts/formatting-presets.mdx Description: Format Pivot values with locale-aware number, currency, percent, and date presets, then highlight analytical cells with conditional formatting. Formatting presets help Pivot reports show numbers, money, percentages, and dates in a readable way. Conditional formatting presets then help users spot high revenue, low margin, or other important analytical values. ## Basic Setup Import the helpers from Enterprise and create reusable formatters for the value types in your report: ```ts import { createPivotValueFormatter, formatPivotValue, pivotConditionalFormattingPresets, createPivotConditionalCellProperties, PivotPlugin, type PivotConfig, } from '@revolist/revogrid-enterprise'; const revenueFormatter = createPivotValueFormatter({ preset: 'currency', currency: 'USD', locale: 'en-US', options: { maximumFractionDigits: 0 }, }); const marginFormatter = createPivotValueFormatter({ preset: 'percent', locale: 'en-US', options: { minimumFractionDigits: 1, maximumFractionDigits: 1 }, }); ``` `createPivotValueFormatter()` returns a function. Use it when the same formatting rule is reused by many cells. Use `formatPivotValue()` when you only need to format one value: ```ts const formattedLabel = formatPivotValue(null, { preset: 'date', nullDisplay: 'No close date', }); ``` ## Value Formatters Pivot formatters are display helpers. They do not change the original source data or the aggregated number that Pivot calculates. Keep your source values as real numbers and dates, then format them at render time. ```ts const columnTypes = { pivotCurrency: { cellTemplate: (_h, props) => revenueFormatter(props.value), cellProperties: () => ({ class: { 'align-right': true } }), }, pivotPercent: { cellTemplate: (_h, props) => marginFormatter(props.value), cellProperties: () => ({ class: { 'align-right': true } }), }, }; ``` Attach those column types to the Pivot dimensions that produce analytical value cells: ```ts const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region' }, { prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' }, { prop: 'segment', name: 'Segment' }, { prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency' }, { prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent' }, ], rows: ['region', 'closeDate'], columns: ['segment'], values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue' }, { prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' }, ], }; ``` ## Locale And Presets The formatter presets use the browser `Intl` formatters. Choose the preset that matches the value: - `number`: plain numeric values such as counts, units, or scores. - `currency`: money values. Always provide `currency`, for example `"USD"` or `"EUR"`. - `percent`: ratio values such as `0.315`, displayed as `31.5%`. - `date`: date-only values. - `datetime`: date and time values. ```ts const integerFormatter = createPivotValueFormatter({ preset: 'number', locale: 'en-US', options: { maximumFractionDigits: 0 }, }); const eurFormatter = createPivotValueFormatter({ preset: 'currency', currency: 'EUR', locale: 'de-DE', }); const dateFormatter = createPivotValueFormatter({ preset: 'date', locale: 'en-US', options: { month: 'short', day: '2-digit', year: 'numeric' }, }); ``` ## Conditional Formatting Presets Use `pivotConditionalFormattingPresets` to describe matching rules, and `createPivotConditionalCellProperties()` to turn those rules into a RevoGrid `cellProperties` function. ```ts const conditionalCellProperties = createPivotConditionalCellProperties( [ pivotConditionalFormattingPresets.gt(300000, { field: 'revenue', style: { backgroundColor: 'rgba(22, 163, 74, 0.16)', color: '#166534', fontWeight: '600', }, }), pivotConditionalFormattingPresets.lt(0.28, { field: 'marginRate', style: { backgroundColor: 'rgba(220, 38, 38, 0.14)', color: '#991b1b', fontWeight: '600', }, }), pivotConditionalFormattingPresets.between(0.28, 0.34, { field: 'marginRate', style: { backgroundColor: 'rgba(234, 179, 8, 0.14)', color: '#854d0e', }, }), ], { match: 'all' }, ); ``` Available presets: - `gt(value, props)`: greater than a number. - `lt(value, props)`: less than a number. - `between(min, max, props)`: between two numbers, including both bounds. - `equal(value, props)`: equal to a value. - `textContains(value, props)`: contains text. The optional `field` narrows a rule to a generated value field. For example, `field: 'revenue'` applies to generated revenue cells even when Pivot creates column paths such as `Software|revenue`. ## Analytical Cells By default, `createPivotConditionalCellProperties()` only formats Pivot analytical cells. These are generated cells from the `values` area, not row header fields such as `region` or `closeDate`. This default prevents a rule like `gt(300000)` from accidentally styling row dimensions that happen to contain comparable values. If you intentionally want rules to apply outside analytical value cells, enable `includeNonAnalytical`: ```ts const allCellProperties = createPivotConditionalCellProperties(rules, { includeNonAnalytical: true, }); ``` Most Pivot reports should keep the default and attach conditional formatting to value dimensions: ```ts const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region' }, { prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency', cellProperties: conditionalCellProperties, }, { prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent', cellProperties: conditionalCellProperties, }, ], rows: ['region'], columns: ['segment'], values: [ { prop: 'revenue', aggregator: 'sum' }, { prop: 'marginRate', aggregator: 'avg' }, ], }; ``` ## Practical Example This report groups opportunities by region and close date, splits generated value columns by segment, formats revenue as currency, formats margin as a percent, and highlights high revenue or weak margin. ```ts const rows = [ { region: 'North America', closeDate: '2026-01-16', segment: 'Software', revenue: 184000, marginRate: 0.37 }, { region: 'North America', closeDate: '2026-01-22', segment: 'Services', revenue: 126000, marginRate: 0.31 }, { region: 'EMEA', closeDate: '2026-02-06', segment: 'Services', revenue: 118000, marginRate: 0.28 }, ]; const pivot: PivotConfig = { dimensions: [ { prop: 'region', name: 'Region' }, { prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' }, { prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency', cellProperties: conditionalCellProperties, }, { prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent', cellProperties: conditionalCellProperties, }, ], rows: ['region', 'closeDate'], columns: ['segment'], values: [ { prop: 'revenue', aggregator: 'sum', label: 'Revenue' }, { prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' }, ], totals: { subtotals: true, grandTotal: true, }, groupLabels: { null: formatPivotValue(null, { preset: 'date', nullDisplay: 'No close date' }), }, }; const grid = document.querySelector('revo-grid'); grid.columnTypes = columnTypes; grid.plugins = [PivotPlugin]; grid.pivot = pivot; grid.source = rows; ``` ## Common Mistakes - Formatting the source data before Pivot aggregates it. Keep values numeric so `sum`, `avg`, and comparisons work correctly. - Using `preset: 'percent'` with whole percentages. `31.5%` should usually be stored as `0.315`, not `31.5`. - Forgetting `currency` when using the `currency` preset. - Expecting conditional formatting to affect row fields by default. It targets analytical value cells unless `includeNonAnalytical` is enabled. - Omitting `field` on a conditional rule when the report has several value fields. Without `field`, the rule can match every analytical value cell. - Expecting `cellTemplate` formatting to change conditional comparisons. Conditional rules evaluate the raw cell value, not the formatted display string. ## Next Continue with [Sorting And Filtering](../sorting-filtering/) or [Configuration Reference](/guides/pivot/configuration-reference/). --- # Plugin Lifecycle URL: https://pro.rv-grid.com/guides/pivot/concepts/plugin-lifecycle/ Source: src/content/docs/guides/pivot/concepts/plugin-lifecycle.mdx Description: Understand how PivotPlugin applies, updates, and clears Pivot state on a RevoGrid instance. `PivotPlugin` is the runtime layer that turns the direct `grid.pivot` config into rendered rows, generated columns, and grouped state. Understanding its lifecycle helps you persist user choices, avoid conflicting updates, and reason about when Pivot owns the grid payload. ## Activation Pivot activates when: - `PivotPlugin` is present in `grid.plugins` - `grid.pivot` contains a config The plugin observes the direct `pivot` property and reapplies Pivot whenever the config changes. Legacy `additionalData.pivot` updates are still supported for compatibility. ## What Happens On Apply When `applyPivot` runs: 1. the plugin captures the original source, columns, grouping, and pinned-bottom rows on first activation 2. it decides whether to use the client or server execution path 3. it generates or loads Pivot rows and columns 4. it writes those generated structures back to the grid This means Pivot is temporarily the owner of the rendered grid payload. ## Client Path In client mode: - raw grid source is read from the captured original data - `createPivotData` materializes Pivot rows - `pivotColumns` generates the column tree - grand total rows are moved to `pinnedBottomSource` ## Server Path In server mode: - the plugin resolves a `PivotEngineAdapter` - it builds a `PivotLoadRequest` - it aborts any stale in-flight remote request - only the latest response is allowed to update the grid This is the last-write-wins behavior that protects the UI when filters or layouts change quickly. ## Configurator Integration If `hasConfigurator` is enabled, the plugin renders the configurator and wires its updates back into the Pivot state. Each configurator update: - emits `pivot-config-update` - applies the updated config unless the event was prevented ## `pivot-config-update` This event is the main integration point for external state ownership. Use it to: - persist layout changes - sync Pivot state into a framework store - restore `expanded` or `collapsedColumns` later ## Reapplication Triggers Pivot recalculates when: - `grid.pivot` changes - legacy `additionalData.pivot` changes - the underlying non-Pivot source changes - the underlying non-Pivot columns change - row drill-down or column collapse state changes through plugin-managed interactions The plugin intentionally avoids treating already-generated Pivot rows or columns as new base inputs. ## Clearing Pivot `clearPivot()`: - aborts any remote load - removes managed Pivot state - restores the original source, columns, grouping, and pinned-bottom rows Removing `grid.pivot` has the same practical effect. Removing legacy `additionalData.pivot` also clears Pivot when that legacy path is the active source. ## Best Practices - Treat direct `grid.pivot` / framework `pivot` binding as the durable state you own. - Listen to `pivot-config-update` if user changes should persist. - Do not manually overwrite `grid.source` or `grid.columns` with Pivot active unless you mean to replace the base data and trigger a reapply. ## Related Guides - [Configuration Reference](/guides/pivot/configuration-reference/) - [Configurator API](/guides/pivot/configurator-api/) - [Field Panel](/guides/pivot/field-panel/) - [Server-Side Pivot](/guides/pivot/server-side/) --- # Sorting And Filtering URL: https://pro.rv-grid.com/guides/pivot/concepts/sorting-filtering/ Source: src/content/docs/guides/pivot/concepts/sorting-filtering.mdx Description: Learn what sorting and filtering affect in Pivot output and how client-side and remote/server-side behavior differ. Sorting and filtering in Pivot operate on analytical members and generated measures, not on the final rendered cells alone. The same ideas exist in both client-side and server-side Pivot, but the execution layer is different. ## Client-Side Sorting Client-side Pivot uses dimension metadata when generating row and column members. Relevant dimension options: - `sortable` - `order` If a Pivot column dimension is marked sortable, generated members for that dimension are ordered using the configured comparer and sort direction. ## Client-Side Filtering Generated value columns inherit configured filter metadata from their dimensions, so the resulting grid can expose filter-ready columns. This is still based on the generated Pivot output, not on the raw fact table semantics of a remote analytical engine. ## What Actually Gets Sorted In client mode: - row hierarchy ordering comes from the grouped source data - column hierarchy ordering comes from the configured dimension metadata - measure cells are results of aggregation, not independently sorted records ## Server-Side Sorting And Filtering In server mode: - filter expressions are part of `PivotLoadRequest.loadOptions.filter` - sort directives are part of `PivotLoadRequest.loadOptions.sort` - the analytical engine is responsible for applying them before returning the visible window That distinction matters because server-side Pivot should sort and filter the cube window, not just the already-rendered grid cells. ## Sort By Summary The remote request contract also supports sort-by-summary semantics through `bySummary` on sort descriptors. Use that when your backend can rank groups by a measure rather than only by a dimension label. ## Best Practices - Keep client-side sorting expectations simple: it orders generated members, not facts. - Use remote/server-side filtering for large datasets or security-sensitive selectors. - Do not rely on client-generated columns as the trust boundary for secure filtering. The field registry belongs on the server side. ## Next Continue with [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) to see how Pivot owns and restores grid state. --- # Totals URL: https://pro.rv-grid.com/guides/pivot/concepts/totals/ Source: src/content/docs/guides/pivot/concepts/totals.mdx Description: Learn how Pivot grand totals and subtotals work across rows and generated columns. Totals help users move from detailed branch-level summaries to whole-table summaries. RevoGrid Pivot supports grand totals and subtotals through `pivot.totals`. ## The Totals Config ```ts totals: { grandTotal: true, subtotals: true, grandTotalLabel: 'All Sales', subtotalLabel: 'Subtotal', } ``` ## What Each Option Does - `grandTotal`: Adds a grand-total row. When column dimensions exist, it also adds grand-total value columns. - `subtotals`: Adds subtotal rows for intermediate row branches and subtotal value columns for intermediate column branches. - `grandTotalLabel`: Overrides the default `"Grand Total"` label. - `subtotalLabel`: Overrides the default `"Subtotal"` label. - `suppressSingleChildSubtotals`: Hides subtotal rows for single-source branches where the subtotal duplicates the only leaf. - `suppressGrandTotalWhenSingleLeaf`: Hides the grand-total row when the whole result is one source-backed leaf. ## Smart Suppression Use suppression when totals are enabled for broad reports, but small filtered results should avoid duplicate summary rows: ```ts totals: { subtotals: true, grandTotal: true, suppressSingleChildSubtotals: true, suppressGrandTotalWhenSingleLeaf: true, } ``` Suppression is conservative. Multiple source rows, multiple meaningful leaves, or disabled `subtotals` / `grandTotal` keep the existing total behavior. ## Row Totals When row fields exist: - subtotal rows are inserted after the descendants of each branch - the grand-total row is rendered separately and pinned at the bottom This keeps the detailed body rows readable while still making the full-table total visible. ## Column Totals When column fields exist: - intermediate column paths can expose subtotal value columns - the full column hierarchy can expose grand-total value columns Pivot does not create a separate total table. Totals become part of the generated analytical structure. ## Example For: ```ts rows: ['region', 'rep'], columns: ['year', 'quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, ``` You get: - leaf rows for each `region -> rep` - subtotal rows for each `region` - grand total at the bottom - leaf value columns for each `year -> quarter` - subtotal value columns for each `year` - grand-total value columns across all years and quarters ## Important Notes - Subtotals only appear when a hierarchy has more than one level to summarize. - Grand totals use the configured label only for display. The internal total keys stay synthetic. - In remote/server mode, totals stay part of the analytical contract and should be computed by the engine, not recomputed from visible UI cells. ## Common Mistakes - Expecting `subtotals: true` to add subtotal rows when there is only one row field. - Expecting grand totals to stay inside the body rows. In the current implementation, grand total rows are pinned bottom rows. - Confusing column subtotals with collapsed-column placeholders. Those are different features. ## Next Continue with [Values On Rows](/guides/pivot/values-on-rows/) or [Drill-Down](/guides/pivot/drilldown/). --- # Values On Rows URL: https://pro.rv-grid.com/guides/pivot/concepts/values-on-rows/ Source: src/content/docs/guides/pivot/concepts/values-on-rows.mdx Description: Move Pivot measures into the row hierarchy and understand when that layout works best. By default, Pivot renders measures as generated value columns. `valuesOnRows` flips that layout so measures become synthetic row members instead. ## Default Layout vs Values On Rows Default layout: ```txt rows: Region columns: Quarter values: Sales, Margin ``` This usually produces: - one row branch per region - one generated value column per quarter and measure With `valuesOnRows: true`, the same measures become row members: - Region - Sales - Margin That is useful when: - you have many measures and few row dimensions - you want a more compact column area - users compare measures vertically rather than horizontally ## Configuration ```ts const pivot: PivotConfig = { rows: ['region'], columns: ['quarter'], values: [ { prop: 'sales', aggregator: 'sum' }, { prop: 'margin', aggregator: 'avg' }, ], valuesOnRows: true, }; ``` ## Explicit Values Position Use `rowTree` with the `$values` pseudo-field when measures need to appear at a specific hierarchy level: ```ts rowTree: ['carModel', 'cellId', '$values'] rowTree: ['carModel', '$values', 'cellId'] rowTree: ['$values', 'carModel', 'cellId'] ``` `rowTree` is the explicit advanced API. If it is provided, it controls value placement even when `valuesOnRows` is also set. ## What Changes Internally - Pivot adds a synthetic row field called `Values`. - Generated value columns become value labels in the row hierarchy. - Grouping and row drill-down now treat the value label as part of the row structure. This is why row drill-down helpers account for `valuesOnRows` when deriving row props. ## Tradeoffs Benefits: - fewer generated columns - easier comparison of multiple measures - clearer layouts on narrow screens Costs: - more synthetic rows - row drill-down can feel deeper because the value label becomes part of the hierarchy - users expecting a spreadsheet-style “measures across columns” layout may find it less familiar ## When Not To Use It Avoid `valuesOnRows` when: - you only have one measure - your users think in terms of wide analytical columns - you already have a deep row hierarchy and want to keep rows compact ## Next Continue with [Drill-Down](/guides/pivot/drilldown/) to see how row and column expansion behave in both layouts. --- # Configurator URL: https://pro.rv-grid.com/guides/pivot/configurator/ Source: src/content/docs/guides/pivot/configurator/index.mdx --- # Configurator URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator/ Source: src/content/docs/guides/pivot/configurator/configurator.mdx Description: Learn what the Pivot configurator does and how users interact with rows, columns, and values. The configurator is the drag-and-drop field panel for Pivot. It gives users a visual way to rearrange dimensions between rows, columns, and values without writing a new `PivotConfig` by hand. ## What It Does The configurator renders: - a dimension list - a rows drop zone - a columns drop zone - a values drop zone When users move a field: - the configurator emits updated rows, columns, or values - `PivotPlugin` re-emits `pivot-config-update` - the Pivot result is recomputed and the grid updates ## Enable It ```ts const pivot: PivotConfig = { dimensions: [...], rows: ['region'], columns: ['quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], hasConfigurator: true, }; ``` ## Visibility Controls Use these options to hide zones: - `showRows` - `showColumns` - `showValues` These only affect the configurator UI. They do not remove support for the corresponding Pivot feature. ## Mounting By default, `PivotPlugin` creates the configurator inside the grid wrapper. If you set `mountTo`, it mounts into the supplied element instead. ## Aggregator Selection The configurator’s value zone reads available aggregators from the matching dimension. That means aggregator choices stay consistent with the field definition, rather than being hard-coded in the panel. ## i18n Configurator labels come from `i18n`. If omitted, Pivot uses the built-in English defaults from `PIVOT_CONFIG_EN`. ## Common Mistakes - Enabling `hasConfigurator` without providing useful `dimensions`. - Expecting hidden zones to disable Pivot features globally. - Treating the configurator as the state owner. It is a UI for editing Pivot state, not the persistence layer. ## Next Continue with [Field Panel](/guides/pivot/field-panel/) for the compact in-grid layout, [Configurator API](/guides/pivot/configurator-api/) for the standalone embedding surface, or [Server-Side Pivot](/guides/pivot/server-side/) for remote analytical usage. --- # Configurator API URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator-api/ Source: src/content/docs/guides/pivot/configurator/configurator-api.mdx Description: Use the standalone Pivot configurator and understand its callbacks, mounting model, and integration points. The built-in configurator can be used through `PivotPlugin`, but it is also available as a standalone API. Use this when you want the field panel in a custom shell, sidebar, or framework-managed layout. ## Main Entry Point ```ts import { definePivotConfigurator } from '@revolist/revogrid-enterprise'; ``` `definePivotConfigurator(el, config, actions)` mounts the configurator into any element. ## Example ```ts definePivotConfigurator(container, pivotConfig, { onUpdateColumns: (columns) => { pivotConfig = { ...pivotConfig, columns }; }, onUpdateRows: (rows) => { pivotConfig = { ...pivotConfig, rows }; }, onUpdateValues: (values) => { pivotConfig = { ...pivotConfig, values }; }, }); ``` ## `PivotConfigurationActions` The current callback surface is: - `onUpdateColumns` - `onUpdateRows` - `onUpdateValues` These callbacks receive the new ordered configuration for the edited zone. ## What The Configurator Does Not Own The configurator does not: - execute Pivot by itself - persist state for you - own the grid instance It is purely a configuration UI. You decide where the updated state goes next. ## Embedded Via `PivotPlugin` When `hasConfigurator` is enabled on `PivotConfig`, `PivotPlugin` internally uses the same standalone configurator surface and wires it to `pivot-config-update`. That means: - standalone usage and plugin-managed usage stay conceptually aligned - examples built around the standalone API still apply to plugin-managed usage ## Visibility And i18n The configurator reads: - `showRows` - `showColumns` - `showValues` - `i18n` These options let you tailor the panel for a simpler or more controlled layout. ## Best Practices - Keep the configurator state source-of-truth in one place, usually the same state object you pass to the grid. - Provide stable `dimensions` metadata so labels and aggregators stay consistent. - Persist the resulting `PivotConfig` rather than trying to persist the configurator UI separately. ## Related Guides - [Configurator](/guides/pivot/configurator/) - [Field Panel](/guides/pivot/field-panel/) - [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) - [Configuration Reference](/guides/pivot/configuration-reference/) --- # Pivot Features URL: https://pro.rv-grid.com/guides/pivot/features/ Source: src/content/docs/guides/pivot/features.mdx Description: Current Pivot feature status sourced from the Enterprise Pivot feature plan. --- # Introduction URL: https://pro.rv-grid.com/guides/pivot/introduction/ Source: src/content/docs/guides/pivot/introduction/index.mdx --- # Data Modeling for Pivot URL: https://pro.rv-grid.com/guides/pivot/introduction/data-modeling/ Source: src/content/docs/guides/pivot/introduction/data-modeling.mdx Description: Learn how to structure your data for optimal performance and analytical flexibility in RevoGrid Pivot. The quality of your Pivot analysis depends entirely on how your data is structured. To get the most out of RevoGrid Pivot, you should understand the difference between **Facts** and **Dimensions**. ## Facts vs. Dimensions In the world of data modeling (specifically the **Star Schema**), data is divided into two categories: ### 1. Fact Tables (The "Events") A Fact Table contains the quantitative data you want to measure. Each row represents a specific occurrence or transaction. - **Characteristics**: Usually has many rows, contains numeric "measures," and "foreign keys" to dimension tables. - **Example Fields**: `Price`, `Quantity`, `Duration`, `DiscountAmount`. ### 2. Dimension Tables (The "Context") A Dimension Table contains descriptive attributes that provide context to your facts. - **Characteristics**: Usually has fewer rows, contains categorical text or dates. - **Example Fields**: `Product Name`, `Color`, `Region`, `Customer Segment`, `Date (Year/Month/Day)`. ## The "Perfect" Pivot Source For RevoGrid's client-side Pivot, the "perfect" input is a **Denormalized Flat Table**. This means you have "joined" your facts and dimensions into a single list of objects before passing them to the grid. ### ❌ Bad Data Shape (Normalized) The grid has to look up values in multiple places. Hard to pivot. ```json // Transactions [{ "id": 1, "product_id": 101, "amount": 50 }] // Products [{ "id": 101, "name": "Apple" }] ``` ### ✅ Good Data Shape (Denormalized) Everything the pivot needs is in one object. ```json [ { "id": 1, "product": "Apple", "category": "Fruit", "region": "North", "amount": 50, "date": "2024-05-01" } ] ``` ## Modeling Best Practices ### 1. Numeric vs. Categorical - **Numbers** should be raw, unformatted values (e.g., `1200.50`, not `"$1,200.50"`). This allows aggregators like `sum` and `avg` to work correctly. - **Categories** should be stable strings. Avoid using ID numbers (like `1`, `2`, `3`) as labels; use human-readable names (like `"Active"`, `"Pending"`, `"Closed"`) so the generated headers make sense. ### 2. Date Hierarchies Dates are the most common pivot dimension. Instead of just a raw ISO string, it is often helpful to provide pre-calculated date parts if you are doing client-side pivoting: ```json { "orderDate": "2024-05-21", "year": 2024, "quarter": "Q2", "month": "May" } ``` In your `PivotConfig`, you can then set `rows: ['year', 'quarter', 'month']` to create an instant time-drilldown experience. ### 3. Stability of Fields The `prop` in your [Dimensions](/guides/pivot/dimensions/) should be stable. If you change a field name in your data, you must update the `prop` in your configuration, or the Pivot will fail to find the data. ## Large Scale Modeling (Server-Side) If your "Fact Table" has millions of rows, you shouldn't denormalize it into a single JSON array for the browser. Instead, you should keep it in an **OLAP-optimized database** (like ClickHouse, BigQuery, or Snowflake) and use the [Server-Side Pivot](/guides/pivot/server-side/) mode. In this mode, RevoGrid sends the "Analytical Request" (the rows, columns, and filters) to your backend, which performs the SQL `JOIN` and `GROUP BY` efficiently and returns only the visible summary. ## Next Steps Now that your data is modeled correctly, learn how to define your [Dimensions](/guides/pivot/dimensions/) to tell RevoGrid how to use these fields. --- # What is an OLAP Cube? URL: https://pro.rv-grid.com/guides/pivot/introduction/olap-cube-concepts/ Source: src/content/docs/guides/pivot/introduction/olap-cube-concepts.mdx Description: Understand the multidimensional "Cube" metaphor and how Online Analytical Processing (OLAP) powers advanced data discovery. When working with RevoGrid Enterprise Pivot, you will often hear the term **OLAP** (Online Analytical Processing) or **OLAP Cube**. While the name sounds complex, the concept is fundamental to modern business intelligence. ## The "Cube" Metaphor In a traditional spreadsheet, data is 2-dimensional (Rows and Columns). In an **OLAP Cube**, data is **Multidimensional**. Think of a "Cube" where each side represents a different category (a **Dimension**): 1. **Product** (what we sold) 2. **Time** (when we sold it) 3. **Geography** (where we sold it) Inside the cube, at the intersection of these three dimensions, lies the **Measure** (e.g., Sales Amount). By "rotating" this cube, you can view the data from any angle. In RevoGrid Pivot, this "rotation" happens when you move fields between the Rows and Columns axes. ## Core OLAP Operations OLAP cubes are designed to support four primary analytical operations, all of which are supported by RevoGrid: ### 1. Slicing Slicing is like taking a single "slice" of the cube. - **Concept**: Picking one value for a dimension. - **Example**: "Show me sales only for the **Year 2024**." - **In RevoGrid**: This is achieved through [Filtering](/guides/pivot/sorting-filtering/). ### 2. Dicing Dicing is like cutting a smaller "sub-cube" out of the big one. - **Concept**: Picking specific values across multiple dimensions. - **Example**: "Show me sales for **Electronics** in the **North Region** during **Q1**." - **In RevoGrid**: This is achieved by combining filters and row/column grouping. ### 3. Drill-Down / Drill-Up Moving through the levels of a hierarchy. - **Concept**: Going from summarized data to detailed data (Drill-Down) or back (Drill-Up). - **Example**: Looking at sales by **Year**, then clicking to see **Quarters**, then **Months**. - **In RevoGrid**: Enabled via the `collapsed` property in [Drill-Down](/guides/pivot/drilldown/). ### 4. Pivoting (Rotating) Changing the orientation of the axes. - **Concept**: Swapping Rows and Columns to see the data differently. - **Example**: Moving "Region" from the Row axis to the Column axis to compare regions side-by-side. - **In RevoGrid**: Performed via the [Configurator](/guides/pivot/configurator/) or by updating the `rows` and `columns` arrays in your code. ## Why "Cube" vs. "Flat Table"? | Feature | Flat Table (SQL) | OLAP Cube | | :--- | :--- | :--- | | **Speed** | Slow for large aggregations (calculates on the fly) | Fast (aggregations are often pre-calculated) | | **Complexity** | Requires complex `JOIN` and `GROUP BY` logic | Logical, business-friendly dimensions | | **Scale** | Hard to scale to billions of rows for real-time UI | Designed for massive analytical scale | ## How RevoGrid Fits In RevoGrid acts as the **Viewer** for your OLAP data. - **Client-Side**: RevoGrid builds a "virtual cube" in memory from your flat JSON data. - **Server-Side**: RevoGrid communicates with a real OLAP engine (like Snowflake, ClickHouse, or Cube.js) using the [Remote API](/guides/pivot/remote-api-reference/). ## Next Steps Now that you understand the "Cube" metaphor, learn how to prepare your data for these operations in [Data Modeling for Pivot](/guides/pivot/data-modeling/). --- # What Is Pivot? URL: https://pro.rv-grid.com/guides/pivot/introduction/what-is-pivot/ Source: src/content/docs/guides/pivot/introduction/what-is-pivot.mdx Description: A conceptual introduction to Pivot tables, the mental model, and how they transform flat data into analytical insights. A **Pivot Table** is a data summarization tool that automatically sorts, counts, totals, or averages data stored in one table and displays the results in a second, organized table. In RevoGrid, the Pivot plugin takes a "flat" list of records and transforms it into a multidimensional view where you can see relationships between different data points at a glance. ## The Transformation: From Facts to Insights Imagine you have a list of sales transactions. Each row represents a single event: | Date | Region | Category | Sales | | :--- | :--- | :--- | :--- | | 2024-01-01 | North | Electronics | $500 | | 2024-01-02 | South | Electronics | $300 | | 2024-01-03 | North | Furniture | $1,200 | | 2024-01-04 | North | Electronics | $450 | This "flat" view is great for data entry, but hard to read for analysis. A **Pivot** transforms it by choosing axes: 1. **Row Axis**: Grouping data vertically (e.g., by **Region**). 2. **Column Axis**: Grouping data horizontally (e.g., by **Category**). 3. **Values**: The numbers you want to calculate (e.g., **Sum of Sales**). ### The Resulting Pivot View: | Region | Electronics | Furniture | Grand Total | | :--- | :--- | :--- | :--- | | **North** | $950 | $1,200 | **$2,150** | | **South** | $300 | $0 | **$300** | | **Grand Total** | **$1,250** | **$1,200** | **$2,450** | ## Core Axes of a Pivot To work with Pivot, you need to think in terms of three primary axes: ### 1. Rows (The Hierarchy) Rows define the vertical structure. If you add multiple fields to rows (e.g., `['Region', 'Manager']`), Pivot creates a nested hierarchy. Users can "drill down" from Region to see the individual Managers within that region. ### 2. Columns (The Breakdown) Columns define the horizontal structure. Unlike normal grid columns, Pivot columns are often **generated** based on the data. If you put `Quarter` in columns, Pivot will automatically create a column for every unique quarter found in your dataset. ### 3. Values (The Measures) Values are the "meat" of the table. They are the numeric fields that get crunched using **Aggregators** (like `sum`, `avg`, `min`, `max`, or `count`). Every cell in a Pivot table is the result of an aggregation of all raw records that match that specific row and column intersection. ## Why Use RevoGrid Pivot? - **Massive Data**: RevoGrid's virtualization allows you to pivot millions of rows without crashing the browser. - **Dynamic Discovery**: Users can drag and drop fields to explore data from different angles. - **Enterprise Features**: Subtotals, Grand Totals, and Drill-down are built-in and optimized. - **Server-Side Scaling**: For datasets too large for the browser, RevoGrid can connect to an [OLAP Backend](/guides/pivot/connect-olap-backend/) to perform the heavy lifting on the server while maintaining a smooth UI. ## Next Steps To understand how these concepts map to actual code, read about [Dimensions](/guides/pivot/dimensions/) or explore the [Mental Model](/guides/pivot/#mental-model) in the Overview. --- # Server-Side URL: https://pro.rv-grid.com/guides/pivot/server-side/ Source: src/content/docs/guides/pivot/server-side/index.mdx --- # Caching And Cache Keys URL: https://pro.rv-grid.com/guides/pivot/server-side/caching/ Source: src/content/docs/guides/pivot/server-side/caching.mdx Description: Understand how RevoGrid Pivot generates deterministic cache keys for safe and efficient remote analytical execution. In server-side Pivot, caching is critical for performance and cost management. Analytical queries can be expensive and slow; deduplicating identical requests ensures that the backend only performs the work once. RevoGrid Pivot uses **deterministic cache keys** to identify identical analytical windows. ## What Is A Cache Key? A cache key is a unique string that represents the exact state of an analytical request. If any part of the request changes (filters, grouping, viewport, or data version), a new key is generated. This allows both the client-side `HttpPivotRemoteStore` and your backend analytical engine to: 1. **Deduplicate**: Prevent multiple identical requests from hitting the database simultaneously. 2. **Memoize**: Return previously computed results for the same analytical window. 3. **Invalidate Safely**: Ensure that if the underlying data or field definitions change, old cached results are never shown. ## Key Components A stable Pivot cache key is composed of several semantic layers: | Component | Description | Why It Matters | | :--- | :--- | :--- | | `tenantId` | Identifies the client or organization scope. | Prevents data leakage between different users/tenants. | | `viewId` | Identifies the specific analytical view or dashboard. | Keeps caches separate for different reports using the same fields. | | `fieldsVersion` | A checksum or version of the field registry. | If you change a field's calculation or data type, old summaries must be invalidated. | | `datasetWatermark` | A timestamp or batch ID representing data freshness. | Automatically invalidates the cache when new data is loaded into the warehouse. | | `request` | A stable stringification of filters, rows, columns, and windows. | Ensures that changing a single filter or scrolling the viewport yields a specific key. | ## How Keys Are Generated RevoGrid provides a standard `createPivotCacheKey` utility that builds these keys. It uses a **stable stringification** algorithm, meaning that the order of keys in a JSON object does not change the resulting string. ```ts // Internal logic simplified const cacheKey = `pivot:${tenantId}:${viewId}:${fieldsVersion}:${datasetWatermark}:${stableStringify(request)}`; ``` ### Stable Stringification Example Even if the UI sends filters in a different order, the cache key remains the same: ```ts // Request A { filter: { region: 'US', category: 'Tech' } } // Request B { filter: { category: 'Tech', region: 'US' } } // Both result in the same stable key: // "...:{"category":"Tech","region":"US"}" ``` ## Observing Cache Status When using `HttpPivotRemoteStore` or the recommended backend response format, the grid receives cache metadata in the `meta` object: ```ts { "data": [...], "meta": { "cacheKey": "pivot:tenant-1:sales:v1:20240424:...", "cacheStatus": "hit", // or 'miss', 'warm', 'bypass' "elapsedMs": 12 } } ``` This is useful for UI diagnostics and understanding whether your analytical engine is performing efficiently. ## Best Practices - **Keep `viewId` Stable**: Do not use random IDs for views if you want users to benefit from shared caching across sessions. - **Update `datasetWatermark`**: Your backend should provide a watermark (like a `max(updated_at)` or a batch ID) to the client so the cache key updates automatically when data changes. - **Normalize Requests**: If you are implementing a custom `PivotRemoteStore`, ensure you normalize filters and descriptors before generating keys to maximize cache hits. ## Next - [Server-Side Pivot](/guides/pivot/server-side/) - [Remote API Reference](/guides/pivot/remote-api-reference/) - [OLAP Best Practices](/guides/pivot/olap-best-practices/) --- # Connect An OLAP Backend URL: https://pro.rv-grid.com/guides/pivot/server-side/connect-olap-backend/ Source: src/content/docs/guides/pivot/server-side/connect-olap-backend.mdx Description: Wire RevoGrid Pivot to an OLAP-capable backend through HttpPivotRemoteStore and an application API. This guide shows the practical wiring pattern for connecting RevoGrid Pivot to an OLAP-capable backend. The important rule is simple: the browser talks to your application API, and your application API talks to the cube, warehouse, or semantic layer. For the conceptual background, read [Server-Side Pivot](/guides/pivot/server-side/) first. For the transport contract, see [Remote API Reference](/guides/pivot/remote-api-reference/). ## Live Simulated Server Example The example below uses the real `HttpPivotRemoteStore` API with a simulated in-memory `/api/pivot/*` server. The Request Log panel on the right shows the actual client and server events as they happen — `load:started`, `load:received`, `load:responded`, and `load:succeeded`. ## Architecture The recommended production boundary looks like this: ```txt RevoGrid + PivotPlugin -> HttpPivotRemoteStore -> your /api/pivot/* endpoints -> field registry + validation + planner/adapter -> OLAP engine / warehouse / cube API ``` Do not connect the browser directly to the OLAP database. Your backend layer must own: - authentication - tenant isolation - selector whitelisting - request normalization - query translation - caching and observability ## Minimum Pieces You Need 1. A client `PivotConfig` with `engine.mode = 'server'` 2. An `HttpPivotRemoteStore` 3. Backend `POST /api/pivot/load` 4. Backend `POST /api/pivot/drilldown` 5. A field registry for safe public field ids 6. A planner or adapter that translates Pivot requests into your backend’s OLAP query shape ## Client Setup The client stays small. It only needs to describe the Pivot layout and point the plugin at your application API. ```ts import { HttpPivotRemoteStore, PivotPlugin, commonAggregators, type PivotConfig, } from '@revolist/revogrid-enterprise'; import { PaginationPlugin } from '@revolist/revogrid-pro'; const remoteStore = new HttpPivotRemoteStore({ baseUrl: '/api', tenantId: 'tenant-a', datasetWatermark: 'sales-cube-v42', authProvider: async () => ({ Authorization: `Bearer ${await getAccessToken()}`, }), }); const pivot: PivotConfig = { dimensions: [ { prop: 'Region', name: 'Region' }, { prop: 'Category', name: 'Category' }, { prop: 'Date', name: 'Date' }, { prop: 'SalesAmount', name: 'Sales', aggregators: commonAggregators, }, ], rows: ['Region', 'Category'], columns: ['Date'], values: [{ prop: 'SalesAmount', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, engine: { mode: 'server', remoteStore, viewId: 'sales-by-region', fieldsVersion: 'sha256:fields-v1', rowAxis: { offset: 0 }, // limit driven by pagination.itemsPerPage columnAxis: { offset: 0, limit: 24 }, }, }; // Add PaginationPlugin to enable server-side row paging. // PivotPlugin will read itemsPerPage as the rowAxis limit and keep // pagination.total in sync with response.rowAxis.totalCount. grid.plugins = [PivotPlugin, PaginationPlugin]; grid.pivot = pivot; grid.additionalData = { pagination: { itemsPerPage: 50, initialPage: 0, total: 0, }, }; ``` ## What The Client Sends At runtime, `PivotPlugin` converts the visible Pivot state into a `PivotLoadRequest` and sends it through `HttpPivotRemoteStore`. That request includes: - `rows`, `columns`, and `values` - filters and sort directives - `rowAxis` and `columnAxis` windows - expansion state represented as analytical paths The client does not build SQL and it does not know how your cube works internally. ## Backend Load Endpoint This example uses a lightweight Node/Express style handler for readability. The contract itself is framework-agnostic. ```ts import type { Request, Response } from 'express'; import { createFieldRegistry, normalizePivotLoadRequest, type PivotLoadRequest, type PivotLoadResponse, } from '@revolist/revogrid-enterprise'; const registry = createFieldRegistry([ { id: 'Region', label: 'Region', dataType: 'string', expression: { kind: 'column', value: 'sales.region' }, allowedOperations: ['=', '<>', 'in'], }, { id: 'Category', label: 'Category', dataType: 'string', expression: { kind: 'column', value: 'sales.category' }, allowedOperations: ['=', '<>', 'in'], }, { id: 'Date', label: 'Date', dataType: 'date', expression: { kind: 'column', value: 'sales.order_date' }, allowedOperations: ['=', '>=', '<='], allowedGroupIntervals: ['year', 'quarter', 'month', 'day'], }, { id: 'SalesAmount', label: 'Sales Amount', dataType: 'number', expression: { kind: 'column', value: 'sales.amount' }, allowedOperations: ['=', '>', '>=', '<', '<='], allowedSummaries: ['sum', 'avg', 'min', 'max', 'count'], drilldownVisible: true, }, ]); export async function postPivotLoad(req: Request, res: Response) { const tenantId = await requireTenant(req); const request = req.body as PivotLoadRequest; const normalized = normalizePivotLoadRequest(request, registry); const cubeResult = await runCubeLoad({ tenantId, viewId: normalized.viewId, fieldsVersion: normalized.fieldsVersion, request: normalized, }); const response: PivotLoadResponse = { requestId: request.requestId, version: 1, data: cubeResult.data, summary: cubeResult.summary, rowAxis: cubeResult.rowAxis, columnAxis: cubeResult.columnAxis, meta: cubeResult.meta, }; res.json(response); } ``` ## Translating The Pivot Request Into An OLAP Query Your adapter layer is where Pivot concepts become backend-native OLAP concepts. Recommended mapping: - Pivot `rows` -> cube row dimensions - Pivot `columns` -> cube column dimensions - Pivot `totalSummary` and `groupSummary` -> cube measures - Pivot `filter` -> cube filter tree - `expandedPaths` -> hierarchy expansion state - `rowAxis` / `columnAxis` -> returned analytical windows Example adapter: ```ts type RunCubeLoadArgs = { tenantId: string; viewId: string; fieldsVersion: string; request: ReturnType; }; async function runCubeLoad(args: RunCubeLoadArgs) { const { request } = args; const cubeQuery = { dimensions: { rows: (request.loadOptions.rows ?? []).map((item) => ({ id: item.selector, interval: item.groupInterval, desc: item.desc, })), columns: (request.loadOptions.columns ?? []).map((item) => ({ id: item.selector, interval: item.groupInterval, desc: item.desc, })), }, measures: (request.loadOptions.totalSummary ?? []).map((summary) => ({ id: summary.selector, aggregate: summary.summaryType, })), filters: request.loadOptions.filter, sort: request.loadOptions.sort ?? [], window: request.viewport, uiState: request.uiState, }; const result = await cubeClient.loadPivotWindow(cubeQuery); return { data: result.cells, summary: result.summaryRows, rowAxis: { totalCount: result.rowAxis.totalCount, returned: result.rowAxis.returned, offset: request.viewport.rowAxis.offset, limit: request.viewport.rowAxis.limit, expandedPaths: request.viewport.rowAxis.expandedPaths, paths: result.rowAxis.paths, nodes: result.rowAxis.nodes, }, columnAxis: { totalCount: result.columnAxis.totalCount, returned: result.columnAxis.returned, offset: request.viewport.columnAxis.offset, limit: request.viewport.columnAxis.limit, expandedPaths: request.viewport.columnAxis.expandedPaths, paths: result.columnAxis.paths, nodes: result.columnAxis.nodes, }, meta: { cacheKey: result.cacheKey, generatedAt: result.generatedAt, elapsedMs: result.elapsedMs, cacheStatus: result.cacheStatus, warnings: result.warnings ?? [], }, }; } ``` ## Drilldown Endpoint Drilldown should return the underlying facts for a visible summary cell, not a second aggregated result. ```ts import type { Request, Response } from 'express'; import { createFieldRegistry, normalizePivotDrilldownRequest, type PivotDrilldownRequest, } from '@revolist/revogrid-enterprise'; export async function postPivotDrilldown(req: Request, res: Response) { await requireTenant(req); const request = req.body as PivotDrilldownRequest; const normalized = normalizePivotDrilldownRequest(request, registry); const facts = await cubeClient.drilldown({ rowPath: normalized.cell.rowPath, columnPath: normalized.cell.columnPath, dataIndex: normalized.cell.dataIndex, fields: normalized.customColumns ?? ['Region', 'Category', 'Date', 'SalesAmount'], offset: normalized.offset, limit: normalized.limit, }); res.json({ requestId: request.requestId, data: facts.rows, totalCount: facts.totalCount, meta: { cacheStatus: facts.cacheStatus ?? 'bypass', }, }); } ``` ## Request Lifecycle 1. The grid shows a Pivot layout with row and column windows. 2. `PivotPlugin` builds a `PivotLoadRequest`. 3. `HttpPivotRemoteStore` sends the request to your application API. 4. Your backend authenticates, validates selectors, and normalizes the request. 5. Your planner or cube adapter translates the normalized request into backend-native OLAP calls. 6. The cube or warehouse returns only the visible analytical block. 7. Your backend maps that result into `PivotLoadResponse`. 8. RevoGrid materializes the returned window into visible columns and rows. ## What Changes For A True OLAP Cube If your backend already exposes dimensions, measures, and drilldown concepts, the frontend setup stays the same. Only the backend adapter changes. Typical differences: - date grouping may map directly to cube hierarchy levels - drilldown may map to cube “drill through” APIs - row and column windows may map to axis slicing instead of SQL paging - `avg` and derived measures may already be modeled by the cube That is why the recommended contract stays generic HTTP. The transport is stable even when the backend engine changes. ## Do Not Do This - Do not connect the browser directly to the OLAP database. - Do not trust selectors from the client. - Do not return the full cube by default. - Do not compute totals from rendered UI values. ## Related Guides - [Server-Side Pivot](/guides/pivot/server-side/) - [Remote API Reference](/guides/pivot/remote-api-reference/) - [OLAP Best Practices](/guides/pivot/olap-best-practices/) --- # OLAP Best Practices URL: https://pro.rv-grid.com/guides/pivot/server-side/olap-best-practices/ Source: src/content/docs/guides/pivot/server-side/olap-best-practices.mdx Description: Design Pivot-friendly analytical models with stable dimensions, secure field registries, and viewport-aware execution. RevoGrid Pivot is not itself an OLAP database, but the server-side foundation is designed to work well with OLAP-style engines, semantic layers, and warehouse-backed pivot services. This page explains the practices that keep that integration correct and maintainable. ## Start With Facts And Dimensions The best Pivot source model is: - a fact table with additive events or transactions - dimensions that describe those facts Examples: - facts: orders, sales lines, support incidents, page views - dimensions: region, product, customer segment, time This model keeps grouping and drilldown semantics predictable. ## Treat The Field Registry As A Semantic Layer Your client should use public field ids. Your backend should map them through a field registry. Good registry properties: - stable field ids - business-friendly labels - safe backend expressions - explicit allowed operations - explicit allowed summaries - explicit allowed grouping intervals This prevents raw selector leakage and becomes the trust boundary for every analytical request. ## Design Hierarchies Intentionally Rows and columns should represent real analytical hierarchies, not arbitrary field lists. Good examples: - `['Region', 'Rep']` - `['Year', 'Quarter', 'Month']` - `['Category', 'Subcategory']` Poor examples: - mixing unrelated dimensions only because the UI allows it - saving unstable intermediate fields that are likely to change name or meaning ## Prefer Additive Measures `sum` and `count` are easiest to reason about across subtotals and grand totals. Be careful with: - `avg` - ratios - percentages - any metric derived from already-aggregated cells For those, totals should come from base facts or planner-managed derived summaries, not by averaging visible children. ## Separate UI State From Analytical State Keep these concerns separate: - analytical definition: fields, filters, summaries, sort - UI state: expanded paths, collapsed columns, viewport windows This separation makes saved state more stable and keeps backends from mixing presentation-only state with semantic query meaning. ## Return Windows, Not The Whole Cube For large datasets, the backend should return: - only the visible row-axis window - only the visible column-axis window - only the summaries needed for the visible analytical block Returning the full cube defeats the whole purpose of server-side Pivot. ## Drilldown Should Mean Facts When users drill into a summary cell, the backend should return the underlying facts scoped by: - the current filters - the row path - the column path - the selected measure if needed That keeps drilldown trustworthy and consistent with visible summaries. ## Keep Cache Keys Semantic Stable, semantic cache keys are the difference between a useful analytical cache and an unsafe one. Because Pivot requests are complex, your backend must identify identical windows deterministically. Good Pivot cache keys should include: - tenant and view identifiers - field registry versions - dataset watermarks (freshness) - normalized filters, groupings, and summaries - viewport windows See [Caching and Cache Keys](/guides/pivot/caching/) for the full breakdown of how these keys are constructed and why they matter for safety and performance. ## Recommended Implementation Pattern The most reliable production wiring is: ```txt browser Pivot UI -> HttpPivotRemoteStore -> application API -> field registry + normalization + planner/adapter -> OLAP cube / warehouse ``` This keeps the public Pivot model stable while allowing your backend to translate the same request into SQL, a semantic-layer API, or a cube-native query format. Use [Connect An OLAP Backend](/guides/pivot/connect-olap-backend/) for a practical client + backend example. ## What To Avoid - exposing backend column names to the client - concatenating raw selectors into SQL - computing totals from already-rendered UI cells - using row indexes as expansion identifiers - coupling the grid to a specific backend framework ## Current Product Position Today, RevoGrid Pivot already provides: - a shared client/server config model - a field registry and normalization layer - a planner abstraction - a transport-agnostic remote store - visible-window materialization into RevoGrid What it does not claim to be: - a full OLAP engine - a warehouse execution service - a finished large-scale remote virtualization system on its own That distinction is important. RevoGrid should remain the UI and viewport layer, while the analytical engine remains replaceable. ## Related Guides - [Server-Side Pivot](/guides/pivot/server-side/) - [Connect An OLAP Backend](/guides/pivot/connect-olap-backend/) - [Remote API Reference](/guides/pivot/remote-api-reference/) - [Configuration Reference](/guides/pivot/configuration-reference/) --- # Remote API Reference URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-api-reference/ Source: src/content/docs/guides/pivot/server-side/remote-api-reference.mdx Description: Technical specification for the RevoGrid Pivot remote analytical contract. The Remote Pivot API is a transport-agnostic JSON contract that allows RevoGrid to communicate with analytical backends, OLAP cubes, or custom SQL planners. ## Load Request The `PivotLoadRequest` is sent whenever the grid needs to materialize a visible window of analytical data. ### Root Properties - `viewId`: Semantic identifier for the dataset (e.g., `sales_cube`). - `fieldsVersion`: Version of the field registry used to resolve selectors. - `loadOptions`: The analytical query definition. - `viewport`: Request windows for row and column axes. - `uiState`: Optional hints for engine-side formatting. ### Load Options The `loadOptions` object defines the shape of the analytical query: - `rows`: Array of group descriptors for the row axis. - `columns`: Array of group descriptors for the column axis. - `values`: Measures to aggregate. - `filter`: Complex filter expression tree. - `sort`: Sorting directives (can include sort-by-summary). - `groupSummary`: Measures to calculate at every group level. ### Viewport The `viewport` defines independent analytical windows: - `rowAxis`: Contains `offset`, `limit`, and `expandedPaths`. - `columnAxis`: Contains `offset`, `limit`, and `expandedPaths`. Use analytical paths for expansion state, not visible row indexes. ### Normalization Limits Before planning/execution, requests are typically normalized against backend limits (`PivotLimits`) to keep payloads and query complexity bounded. Common limits include: - `maxRowWindowSize`: Maximum allowed `rowAxis.limit`. - `maxColumnWindowSize`: Maximum allowed `columnAxis.limit`. - `maxExpandedPaths`: Maximum number of expansion paths per axis. - `maxExpansionDepth`: Maximum hierarchy depth of a single expansion path. - `maxDrilldownLimit`: Maximum page size for drilldown requests. For `maxExpansionDepth`, depth is the number of path segments. Example: `['Region', 'Country', 'City']` has depth `3`. ## Load Response `PivotLoadResponse` returns only the data needed for the current visible window. ### Root Properties - `data`: Array of row records (expected to have `__rowPath` and `__columnPath`). - `summary`: Optional grand-total or pinned rows. - `rowAxis`: Window metadata for the row axis. - `columnAxis`: Window metadata for the column axis. - `groupAggregates`: Optional grouped-row measures. - `meta`: Diagnostic metadata. Important response metadata: - `cacheStatus`: 'hit', 'miss', 'warm', or 'bypass'. ## Axis Metadata Both `rowAxis` and `columnAxis` use the same metadata structure. | Field | Role | Description | | :--- | :---: | :--- | | `paths` | Required (Columns) | Array of analytical paths used to generate headers. | | `totalCount` | Required (Rows) | Total members on server. Required for row-axis scrollbars and pagination. | ## Drilldown Drilldown uses `PivotDrilldownRequest` and `PivotDrilldownResponse`. The request identifies a visible summary cell by: - `rowPath` - `columnPath` - optional `dataIndex` The backend should reuse the same analytical filter context as the Pivot request, then return matching fact rows with pagination. ## UI State Hints `uiState` hints allow the backend to optimize its output format: - `rowHeaderLayout`: 'tree' or 'flat'. - `showTotalsPrior`: Placement of subtotals. - `collapsedByDefault`: Initial hierarchy state. --- # Remote Tools URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-tools/ Source: src/content/docs/guides/pivot/server-side/remote-tools.mdx Description: Add diagnostics, source-row drilldown, and saved views to a server-side Pivot screen. Remote tools are small UI and application-layer helpers for server-side Pivot. They help you show request diagnostics, load source rows for a summarized cell, track remote activity, and manage saved Pivot layouts that can be restored into the grid. These helpers are not a required backend. They sit around a `PivotRemoteStore` or `PivotEngineAdapter` that your app already uses for remote Pivot loading. In the demo, the remote tools panel is outside the dedicated RevoGrid wrapper so Pivot-owned UI such as the field panel can mount directly beside the grid. ## What This Page Covers - Diagnostics model: converts remote response metadata into rows and chips your UI can render. - Drilldown controller: turns a summarized Pivot cell into table-ready source rows. - Field panel: lets users reorder and move row, column, value, and filter fields before saving a layout. The demo keeps unused fields in the Filters area so they can be moved into the active layout. - Saved-view helpers: serialize, load, rename, duplicate, and delete user-owned Pivot configs that can be written back to `grid.pivot`. - Remote store hooks: expose request activity such as `load`, `drilldown`, `saveState`, and `loadState` for support panels or audit trails. The grid loads aggregated data through the remote store. Your surrounding app UI can reuse the same store to ask follow-up questions: "How fast was that request?", "Which source rows produced this number?", "Which saved layout should this user load?", or "What remote operation just ran?". ## Import The Helpers All helpers below are exported from the Enterprise Pivot package. ```ts import { PivotDrilldownController, createPivotDiagnosticsModel, deletePivotSavedView, duplicatePivotSavedView, loadPivotView, renamePivotSavedView, savePivotView, type PivotConfig, type PivotLoadRequest, type PivotRemoteStoreHooks, type PivotSavedViewRecord, } from '@revolist/revogrid-enterprise'; ``` ## Diagnostics Model Remote Pivot responses can include `meta` data such as elapsed time, cache status, generated timestamp, and warnings. `createPivotDiagnosticsModel` converts that raw response into a simple display model. Use it when you want a side panel, footer, debug drawer, or support view that explains what the last remote request did. ```ts const response = await remoteStore.load({ requestId: `pivot-diagnostics-${Date.now()}`, viewId: 'sales', fieldsVersion: 'sha256:fields-v1', loadOptions: { rows: [{ selector: 'region' }], columns: [{ selector: 'quarter' }], totalSummary: [{ selector: 'sales', summaryType: 'sum' }], groupSummary: [{ selector: 'sales', summaryType: 'sum' }], }, viewport: { rowAxis: { offset: 0, limit: 50 }, columnAxis: { offset: 0, limit: 12 }, }, uiState: { collapsedByDefault: true, rowHeaderLayout: 'tree', }, } satisfies PivotLoadRequest); const diagnostics = createPivotDiagnosticsModel(response, { formatGeneratedAt: (value) => new Date(value).toLocaleString(), }); for (const row of diagnostics.rows) { console.log(row.label, row.value); } for (const chip of diagnostics.chips) { console.log(chip.label, chip.tone); } ``` The returned model has: - `rows`: stable rows such as elapsed time, generated time, visible rows, and visible columns. - `chips`: compact status items such as cache status or warnings. - `warnings`: warning strings from the response metadata. - `hasWarnings`: a boolean for showing warning states in your UI. The helper does not make network requests. It only formats a `PivotLoadResponse` that your remote store already returned. In the live demo, the diagnostics panel refreshes against the currently selected layout so the visible rows and columns reflect that saved view. ## Drilldown Controller And Table State Aggregated Pivot cells often need a "show source rows" action. `PivotDrilldownController` wraps the remote drilldown contract and keeps table state for you. The controller owns: - current status: `idle`, `loading`, `success`, or `error` - table columns inferred from returned rows or `customColumns` - source rows for the selected summary cell - total matching row count - request cancellation when a newer drilldown request starts ```ts const drilldown = new PivotDrilldownController(remoteStore, { defaultLimit: 100, }); const unsubscribe = drilldown.subscribe((state) => { renderDrilldownTable({ loading: state.loading, columns: state.columns, rows: state.rows, totalCount: state.totalCount, error: state.error, }); }); await drilldown.load({ viewId: 'sales', fieldsVersion: 'sha256:fields-v1', cell: { rowPath: ['North', 'Jane'], columnPath: [2024, 'Q1'], }, customColumns: ['region', 'rep', 'year', 'quarter', 'sales'], offset: 0, limit: 25, }); const state = drilldown.getState(); console.log(state.status, state.rows.length, state.totalCount); unsubscribe(); drilldown.abort(); ``` The `cell` value should identify the analytical cell, not the visible DOM cell. Use Pivot paths like `rowPath` and `columnPath` so your backend can apply the same grouping context and return matching fact rows. The controller state is table-ready: render `state.columns`, `state.rows`, `state.totalCount`, and `state.status` directly in your surrounding UI. ## Saved-View Helpers Saved views are user-owned Pivot configurations. They are useful when users build several layouts, such as "Revenue by Region" and "Quarterly Margin". The helpers use `store.saveState()` and `store.loadState()` for persistence. Rename, duplicate, and delete operate on your app's list of saved-view records, so you can combine them with your own sync logic, menus, permissions, or undo UI. In an interactive screen, listen for `pivot-config-update` from the field panel and save that latest config. Loading a view should apply the returned config back to the direct Pivot binding (`grid.pivot`, `pivot`, or framework equivalent) so the report changes immediately. ```ts const USER_ID = 'user-123'; let views: PivotSavedViewRecord[] = []; const config: Partial = { dimensions: [ { prop: 'region' }, { prop: 'rep' }, { prop: 'quarter' }, { prop: 'sales' }, ], rows: ['region', 'rep'], columns: ['quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, }; const saved = await savePivotView(remoteStore, { userId: USER_ID, viewId: 'north-sales', name: 'North Sales', config, }); views = [...views.filter((view) => view.viewId !== saved.viewId), saved]; const loaded = await loadPivotView(remoteStore, { userId: USER_ID, viewId: 'north-sales', }); grid.pivot = { ...loaded.config, engine: { mode: 'server', remoteStore, viewId: 'sales', fieldsVersion: 'sha256:fields-v1', }, }; views = renamePivotSavedView(views, { userId: USER_ID, viewId: 'north-sales', name: 'North Sales by Quarter', }); views = duplicatePivotSavedView(views, { userId: USER_ID, viewId: 'north-sales', newViewId: 'north-sales-copy', name: 'North Sales Copy', }); views = deletePivotSavedView(views, { userId: USER_ID, viewId: 'north-sales', }); ``` `savePivotView` stores a JSON-serializable Pivot config. It validates common Pivot fields before sending state to the store, which helps catch accidental functions, class instances, or invalid axis values before they become a saved view. ## Remote Store Activity `HttpPivotRemoteStore` accepts lifecycle hooks for telemetry. They are useful for request logs, support drawers, cache badges, and lightweight audit trails near the grid. ```ts const hooks: PivotRemoteStoreHooks = { requestStarted: ({ type }) => { activity.unshift(`${type}: started`); }, requestSucceeded: ({ type, cacheStatus }) => { activity.unshift(`${type}: succeeded${cacheStatus ? ` (${cacheStatus})` : ''}`); }, requestFailed: ({ type }) => { activity.unshift(`${type}: failed`); }, cacheStatusChanged: ({ cacheStatus }) => { activity.unshift(`cache: ${cacheStatus}`); }, }; ``` The live example uses those hooks with the same mocked `HttpPivotRemoteStore` that powers the grid. Running diagnostics, loading source rows, saving a view, and loading a view all update the activity log. ## Backend Expectations These helpers assume the remote store supports the operation you call: - Diagnostics needs `load()` responses with useful `meta`, `rowAxis`, and `columnAxis` data. - Drilldown needs an adapter or store with a `drilldown()` method. - Saved views need `saveState()` and `loadState()`. - Activity logs need store hooks or equivalent instrumentation around your request layer. You can implement those methods against any backend: REST, GraphQL, local storage for a demo, or an internal state service. RevoGrid does not require a specific saved-view backend. ## Recommended Reading - [Server-Side Pivot](/guides/pivot/server-side/) - How remote Pivot loading works. - [Remote API Reference](/guides/pivot/remote-api-reference/) - Request, response, and drilldown contracts. - [Caching And Cache Keys](/guides/pivot/caching/) - Cache metadata used by diagnostics. --- # Server-Side Pivot URL: https://pro.rv-grid.com/guides/pivot/server-side/server-side/ Source: src/content/docs/guides/pivot/server-side/server-side.mdx Description: Connect RevoGrid Pivot to a remote analytical engine without changing the public Pivot configuration model. Server-side Pivot is for datasets that are too large, too sensitive, or too analytically expensive to fully materialize on the client. RevoGrid still owns the viewport and interaction model, but the analytical engine owns aggregation, windowing, drilldown facts, and cache-aware execution. For raw type signatures, see [Pivot API](/api/pivot/). For the full request and store contract, continue with [Remote API Reference](/guides/pivot/remote-api-reference/). ## Architecture The key boundary is: ```txt Pivot UI / RevoGrid Plugin -> Pivot engine adapter -> remote store or analytical backend ``` In practical terms: - `PivotPlugin` receives the direct `pivot` config - `pivot.engine.mode = 'server'` switches the plugin to remote loading - `PivotEngineAdapter` or `PivotRemoteStore` handles the analytical request - the grid only receives the visible block to render ## Why The Public Config Stays The Same The core Pivot model does not change: - `rows` - `columns` - `values` - `totals` - drill-down related UI state That is deliberate. The UI should not need one config model for client Pivot and another for server Pivot. ## Basic Setup ```ts import { PivotPlugin, HttpPivotRemoteStore, type PivotConfig, } from '@revolist/revogrid-enterprise'; import { PaginationPlugin } from '@revolist/revogrid-pro'; const remoteStore = new HttpPivotRemoteStore({ baseUrl: 'https://example.internal', tenantId: 'tenant-a', datasetWatermark: 'sales-v42', authProvider: async () => ({ Authorization: `Bearer ${await getToken()}`, }), }); const pivot: PivotConfig = { dimensions: [...], rows: ['Category', 'Subcategory'], columns: ['Date'], values: [{ prop: 'SalesAmount', aggregator: 'sum' }], totals: { grandTotal: true, subtotals: true }, collapsed: true, engine: { mode: 'server', remoteStore, viewId: 'sales-by-region', fieldsVersion: 'sha256:fields-v1', rowAxis: { }, columnAxis: { }, }, }; // PaginationPlugin drives row-axis paging automatically. // PivotPlugin reads itemsPerPage as rowAxis.limit and writes // response.rowAxis.totalCount back to pagination.total after each load. grid.plugins = [PivotPlugin, PaginationPlugin]; grid.pivot = pivot; grid.additionalData = { pagination: { itemsPerPage: 50, initialPage: 0, total: 0, // auto-populated from the first server response }, }; ``` ## Pagination With Remote Pivot `PivotPlugin` integrates directly with `PaginationPlugin`. When both plugins are active: - `pagination.itemsPerPage` becomes the row-axis page size (`rowAxis.limit`). - When the user changes page, `PivotPlugin` reloads the server window at the new `rowAxis.offset`. - After each successful load, `response.rowAxis.totalCount` is written back to `pagination.total` so the pagination panel shows the correct count. - Changing the pivot structure (rows, columns, values) resets the offset to 0 automatically. You do not need to handle page-change events yourself. Drop both plugins into `grid.plugins`, bind the Pivot config to `grid.pivot`, and add `pagination` to `additionalData`. ## What RevoGrid Owns vs What The Engine Owns RevoGrid and `PivotPlugin` own: - visible grid rendering - generated RevoGrid columns - visible row block - grouped-row metadata used by current drill-down UI - cancellation and last-response-wins behavior in the plugin lifecycle The remote engine or backend owns: - selector validation - filter execution - analytical grouping - summary execution - row-axis and column-axis windows - drilldown fact pages - cache policy and invalidation ## Request Flow 1. `PivotPlugin` sees `engine.mode = 'server'`. 2. It builds a `PivotLoadRequest` from the public Pivot config and current window hints. 3. The adapter or remote store executes the request. 4. The response is materialized into generated columns, visible rows, and pinned totals. 5. RevoGrid renders only the visible analytical window. ## Field Registry And Planner - Public selectors must be resolved through a field registry. - Backend expressions must come from the registry, never from client strings. - Query planning belongs behind a planner abstraction so SQL, OLAP, or warehouse backends can share the same request model. This repository already includes: - a field registry model - request normalization and limit enforcement - a typed error model - an SQL-oriented logical planner shape - a framework-agnostic `HttpPivotRemoteStore` ## Complete Backend Implementation A production Pivot backend needs to translate the analytical request into your engine's native query format. This example demonstrates a TypeScript handler using the core request and response types. ```ts import { type PivotLoadRequest, type PivotLoadResponse, } from '@revolist/revogrid-enterprise'; /** * Handle POST /api/pivot/load */ export async function handlePivotLoad(req: Request): Promise { const request = req.body as PivotLoadRequest; const { loadOptions, viewport, viewId, fieldsVersion, uiState } = request; // 1. Authenticate and resolve tenant const tenantId = await getTenant(req); // 2. Extract analytical parameters const { rows = [], // Group descriptors for rows columns = [], // Group descriptors for columns filter, // Complex filter expression tree sort = [], // Sorting directives totalSummary = [], // Grand-total measures groupSummary = [], // Group-level measures (for groupAggregations) } = loadOptions; // 3. Extract viewport windows const { rowAxis, columnAxis } = viewport; // 4. Execute analytical query (e.g., against SQL or OLAP) const result = await myAnalyticalEngine.execute({ tenantId, viewId, fieldsVersion, rows, columns, filter, sort, measures: [...totalSummary, ...groupSummary], rowWindow: rowAxis, columnWindow: columnAxis, // uiState.rowHeaderLayout, uiState.showTotalsPrior, etc. }); // 5. Build the response return { data: result.cells, // REQUIRED: Visible analytical payload summary: result.totals, // OPTIONAL: Grand-total or pinned rows rowAxis: { totalCount: result.rowCount, // REQUIRED: Drives scrollbars/pagination }, columnAxis: { paths: result.colPaths, // REQUIRED: Defines the generated headers }, // Optional: Grouped-row aggregate payloads (if groupSummary requested) groupAggregates: result.groupSummaries, meta: { cacheStatus: result.fromCache ? 'hit' : 'miss', // Used for telemetry }, }; } ``` ## Current Scope Server-side Pivot is a powerful but complex feature. The standard `PivotPlugin` implements the core windowing and materialization logic, but some advanced scenarios require custom engine adapters: - **Implemented**: Row-axis windowing, generated columns from `paths`, tree-mode row headers, grouped aggregations, and grand totals. - **Custom Adapters**: Complex sorting-by-summary or calculated measures typically require a custom `PivotEngineAdapter` to map your specific backend capabilities to the Pivot contract. - **Not implemented here**: Infinite horizontal scrolling (past the requested column window) or full server-side column grouping collapse (currently managed on client). ## Recommended Reading - [Remote API Reference](/guides/pivot/remote-api-reference/) — Detailed JSON schema for requests and responses. - [Caching And Cache Keys](/guides/pivot/caching/) — How to ensure deterministic and efficient analytical execution. - [OLAP Best Practices](/guides/pivot/olap-best-practices/) — Performance and security tips for backend implementations. --- # Pivot Use Case URL: https://pro.rv-grid.com/guides/pivot/showcase/ Source: src/content/docs/guides/pivot/showcase.mdx Description: Explore a richer Pivot example that combines configuration, totals, drill-down, and multiple measures. This page is a guided tour of the more advanced combinations available in the current Pivot implementation. Use it after you understand the basic model and want to see several features working together. ## What This Example Demonstrates - multiple row levels - generated column groups - multiple value measures - totals and subtotals - interactive configurator updates - `pivot-config-update` for external state sync ## How To Read It Watch how the layout changes as you: - move fields between rows, columns, and values - change aggregators - expand or collapse grouped rows - enable or disable totals The example is useful because it shows that Pivot is not a separate reporting component. It is still RevoGrid, with generated analytical rows and columns layered on top. ## Related Guides - [Configuration Reference](/guides/pivot/configuration-reference/) - [Plugin Lifecycle](/guides/pivot/plugin-lifecycle/) - [Configurator API](/guides/pivot/configurator-api/) - [Field Panel](/guides/pivot/field-panel/) - [Server-Side Pivot](/guides/pivot/server-side/) --- # Comprehensive Guide to Using RevoGrid Plugins URL: https://pro.rv-grid.com/guides/plugin/ Source: src/content/docs/guides/plugin.mdx RevoGrid is a highly flexible and extensible grid component that supports a powerful plugin system. This guide will walk you through everything you need to know about using RevoGrid plugins, from understanding the basics to creating and integrating your custom plugins. #### 1. **Understanding RevoGrid Plugins** RevoGrid plugins are modular pieces of code that extend the functionality of the RevoGrid component. These plugins can: - Add new features. - Modify existing behavior. - Integrate with other libraries or frameworks. All RevoGrid plugins extend from the `BasePlugin` class, which provides a minimal core and utility methods for interacting with the RevoGrid component. #### 2. **Setting Up Your Development Environment** Before you can start working with RevoGrid plugins, ensure you have a development environment set up with the following: - **Node.js** and **npm** (Node Package Manager) installed. - A code editor (such as **Visual Studio Code**). - A basic understanding of TypeScript, as RevoGrid and its plugins are TypeScript-based. To get started, you need to install RevoGrid in your project: ```bash npm install @revolist/revogrid ``` #### 3. **Creating a Simple Plugin** Let's start by creating a simple plugin that add class to odd rows. 1. **Create a New Plugin Class**: Your plugin should extend the `BasePlugin` class provided by RevoGrid. ```typescript import { BasePlugin, type PluginProviders } from '@revolist/revogrid'; export class RowOddPlugin extends BasePlugin { constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) { super(revogrid, providers); this.addEventListener( 'beforerowrender', ({ detail, }: CustomEvent) => { detail.node.$attrs$ = { ...detail.node.$attrs$, odd: detail.item.itemIndex % 2 !== 0, }; } ); } } ``` 2. **Integrate the Plugin**: Once your plugin is created, integrate it with your RevoGrid instance. ```typescript const gridElement = document.querySelector('revo-grid'); gridElement.plugins.push(new RowOddPlugin(gridElement, providers)); ``` #### 4. **Exploring Core Plugin Methods** When creating more complex plugins, you'll likely use several core methods provided by the `BasePlugin` class: - **`addEventListener(eventName: string, callback: (e: CustomEvent) => void): void`**: Subscribes to a RevoGrid event. Example: ```typescript this.addEventListener('beforecellrender', this.handleCellRender); ``` - **`watch(prop: string, callback: (arg: T) => boolean | void, config?: Partial): void`**: Watches a property on the RevoGrid component for changes. Example: ```typescript this.watch('rowSelection', (newValue) => { console.log('Row selection changed:', newValue); }); ``` - **`emit(eventName: string, detail?: any): CustomEvent`**: Emits a custom event from the RevoGrid component. Example: ```typescript this.emit('customEvent', { data: 'someData' }); ``` - **`clearSubscriptions(): void`**: Clears all event listeners subscribed by the plugin. - **`destroy(): void`**: Cleans up the plugin by removing all subscriptions and performing any necessary teardown. #### 5. **Advanced Plugin Development** RevoGrid provides a set of providers that you can use to interact with its internal state. These providers include: - **Data Provider**: Manipulate the grid's data. - **Dimension Provider**: Manage row and column dimensions. - **Selection Provider**: Handle cell and row selections. - **Column Provider**: Manage column data and properties. - **Viewport Provider**: Handle scrolling and viewport rendering. Here's how you might use these providers in a more advanced plugin: ```typescript class HighlightSelectionPlugin extends BasePlugin { constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) { super(revogrid, providers); this.addEventListener('afterselectionchange', this.highlightSelectedCells); } private highlightSelectedCells = () => { const selectedCells = this.providers.selection.getSelectedCells(); selectedCells.forEach(cell => { const model = this.providers.data.getModel(cell.rowIndex, cell.type); model.cellProperties = { style: { backgroundColor: 'yellow' } }; }); }; } ``` #### 6. **Styling with Plugins** Plugins can also be used to dynamically style the grid. For example, using a plugin to flash cells after they are edited: ```typescript import './cell-flash.style.css'; class CellFlashPlugin extends BasePlugin { constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) { super(revogrid, providers); this.addEventListener('onEdit', this.handleEdit); } private handleEdit = (e: CustomEvent) => { const { rowIndex, prop } = e.detail; const cell = this.revogrid.getCell(rowIndex, prop); cell.classList.add('flash'); setTimeout(() => cell.classList.remove('flash'), 1000); }; } // cell-flash.style.css .flash { animation: flash 1s ease; } @keyframes flash { from { background-color: yellow; } to { background-color: transparent; } } ``` #### 7. **Best Practices for Plugin Development** - **Keep Plugins Modular**: Each plugin should have a single responsibility and be as modular as possible. - **Manage Subscriptions**: Always clear subscriptions when they are no longer needed to avoid memory leaks. - **Utilize Providers Wisely**: Only use the necessary providers to keep your plugin efficient. - **Ensure Compatibility**: Test your plugins thoroughly to ensure they work well with other plugins and configurations. #### 8. **Deploying and Sharing Plugins** Once you've created a plugin, you can package it as an npm module and share it with the community. Here's a basic setup for packaging your plugin: 1. **Create a `package.json` file**: ```json { "name": "revogrid-cell-logger", "version": "1.0.0", "main": "dist/index.js", "scripts": { "build": "tsc" }, "dependencies": { "@revolist/revogrid": "^4.0.0" } } ``` 2. **Build your plugin**: ```bash npm run build ``` 3. **Publish to npm**: ```bash npm publish ``` #### 9. **Conclusion** RevoGrid's plugin system is a powerful way to extend its functionality and adapt it to your needs. Whether you're creating simple event listeners or complex data manipulation tools, the flexibility provided by the `BasePlugin` class and the various providers make it easy to build, deploy, and share custom plugins. By following this guide, you should now be equipped to start creating your own plugins, enhancing the capabilities of RevoGrid, and contributing to a richer ecosystem for all users. --- # Drag and Drop (Advanced) URL: https://pro.rv-grid.com/guides/rows/row-advanced-drag-drop/ Source: src/content/docs/guides/rows/row-advanced-drag-drop.mdx Description: Custom drag-and-drop behavior and interactions. Implement sophisticated drag-and-drop functionality within your grid Reorder plugin in RevoGrid may appear simple at first glance, but it’s quite a complex and powerful feature. It supports **multi-item reordering**, allowing users to move multiple rows or columns at once while preserving the integrity of the dataset. Leveraging **`proxyItems`** for efficient handling of the full dataset, the plugin ensures that **filter positioning remains correct** even after reordering, maintaining a seamless user experience. Beyond its core functionality, plugin plays a **crucial role in supporting other plugins** within RevoGrid. It facilitates **data transfer** across different operations and plugins, extending the overall capabilities of the grid. This makes it an essential component for managing dynamic datasets in RevoGrid Pro. - **Custom Drag-and-Drop Behavior**: Tailor the drag-and-drop interactions to fit your application’s specific needs. You can configure how rows are moved, allowing for both simple and complex drag-and-drop operations. - **Enhanced Interactivity**: Improve the overall usability of your grid. With responsive drag-and-drop functionality, users can quickly reorganize data, making it easier to manage and visualize information. - **Multi-Row Dragging**: Users can drag and drop multiple rows at once, simplifying bulk operations. This is particularly useful for scenarios where users need to rearrange large datasets quickly. - **Flexible Dimensions**: Allow dragging in any direction—vertically or horizontally—supporting both pinned and classic rows. This flexibility enables users to interact with the grid in a way that feels natural to them. - **Custom Classes During Dragging**: Apply custom styles during drag operations to provide visual feedback. This feature helps users understand what they are dragging and where they can drop it, enhancing the overall experience. - **Drag Handle Customization**: Implement your own drag handles and apply them to custom cells without limitations. This means you can design specific elements within your rows to serve as drag handles, making it clear to users which parts of the UI are draggable. - **Extendable Event Source**: Enhance functionality by extending the drag event source. You can create custom events and interactions that suit your application’s specific requirements, allowing for a more tailored user experience. ### How to use To enable drag and drop functionality for specific columns, first, import the `RowOrderPlugin` from `@revolist/revogrid-pro` and then simply add the `rowDrag` property to the column configuration: ```ts import { RowOrderPlugin } from '@revolist/revogrid-pro'; grid.columns = [ { name: '🆔 ID', prop: 'id', rowDrag: true }, ]; // or in row headers grid.rowHeaders = { rowDrag: true }; // don't forget to add the plugin grid.plugins = [RowOrderPlugin]; ``` ### Row Order Preview Use `grid.rowOrder` to customize what appears in the drag preview. `prop` keeps the existing compact label behavior, and `previewProp` can be a column prop or callback when the preview label needs application-specific text: ```ts grid.rowOrder = { prop: 'name', preview: 'compact', previewProp: ({ firstItem, itemCount }) => { const label = firstItem?.name ?? 'Rows'; return itemCount > 1 ? `${label} and ${itemCount - 1} more` : label; }, }; ``` `additionalData.rowOrder` is still supported for compatibility, but direct `grid.rowOrder` configuration is preferred. ### Flash Moved Rows If `CellFlashPlugin` is installed with `RowOrderPlugin`, successfully moved rows flash automatically after the reorder is committed. The flash applies only to the dragged row or dragged selection block at its new visible position: ```ts import { CellFlashPlugin, RowOrderPlugin } from '@revolist/revogrid-pro'; grid.plugins = [RowOrderPlugin, CellFlashPlugin]; grid.cellFlash = { rowDuration: 1200, }; ``` Invalid, canceled, or no-op drops do not flash. Existing `cellFlash` options control the row flash duration, CSS class, reduced-motion behavior, and whether flashing is enabled. To keep `CellFlashPlugin` active for edits or manual flash calls but disable row-order drop flashes, set `rowOrder.flashOnDrop` to `false`: ```ts grid.rowOrder = { prop: 'name', flashOnDrop: false, }; ``` ### Live Drop Validation Use `validateDrop` when some positions should be blocked while the user is dragging. Invalid targets are highlighted immediately and are not committed on mouseup: ```ts grid.rowOrder = { prop: 'name', validateDrop: ({ targetIndex }) => { if (targetIndex === 0) { return { valid: false, reason: 'The first row is locked.', }; } return { valid: true }; }, }; ``` You can also validate with events when a plugin or application controller owns the rule: ```ts grid.addEventListener('beforerowdropvalidate', (event) => { if (event.detail.targetIndex === 0) { event.detail.result = { valid: false, reason: 'The first row is locked.', }; } }); grid.addEventListener('rowdropstatechange', (event) => { if (!event.detail.result.valid) { console.info(event.detail.result.reason); }; }); ``` ### Grouped Row Drops When row grouping is active, row order keeps data rows inside their current group by default. This also applies when rows are selected through `RowSelectPlugin`: synthetic group rows are ignored, and only real data rows are validated. For multi-level grouping, the full configured `grouping.props` path must match before the drop is committed. If your workflow should let users move rows between groups, enable `applyTargetGroupOnDrop`. The row-order plugin will apply the target group values to every moved row and emit normal `beforeedit` and `afteredit` events for each changed group field. Calling `event.preventDefault()` in `beforeedit` cancels the drop. ```ts grid.grouping = { props: ['team', 'status'], expandedAll: true, }; grid.rowOrder = { prop: 'name', preview: 'compact', applyTargetGroupOnDrop: true, }; grid.addEventListener('beforeedit', (event) => { if (event.detail.prop === 'team' && event.detail.val === 'Archived') { event.preventDefault(); } }); ``` ### User-Centric Benefits By leveraging the advanced drag-and-drop capabilities, users will experience: - **Increased Efficiency**: Streamline workflows by allowing users to quickly rearrange rows, making data management less cumbersome. - **Improved Data Visualization**: Users can reorganize data in a way that makes the most sense to them, leading to better insights and understanding. - **Intuitive Interface**: A well-implemented drag-and-drop feature provides an intuitive interface, reducing the learning curve for new users. - **Customization Flexibility**: Allowing for custom drag handles and styles gives users the ability to tailor their experience according to their preferences. Advanced drag-and-drop functionality empowers users to interact with their data in a more dynamic and intuitive way. By customizing the behavior and appearance of drag-and-drop actions, you can significantly enhance the user experience within your grid application. Embrace these capabilities to create a more engaging and efficient interface that meets your users' needs. --- # Header (Advanced) URL: https://pro.rv-grid.com/guides/rows/row-advanced-header/ Source: src/content/docs/guides/rows/row-advanced-header.mdx Description: Custom Row Header Sophisticated Row Header example based on `RowHeaderPlugin`. The demo uses a task pipeline layout so the row header has a clear job: it anchors each row, opens row-level actions, and keeps row focus visible while the cells provide richer operational context. - Use `rowHeaders({ showHeaderFocusBtn: true })` for a dedicated row action rail - Open a row `ContextMenuPlugin` menu directly from the row-header click - Combine row headers with custom column templates for status, priority, owner, due date, and progress - Focus the whole row from the row header while preserving readable business data in the grid body - Style the row-header rail and content cells together so the row action area feels intentional --- --- # Auto Size Row URL: https://pro.rv-grid.com/guides/rows/row-autosize/ Source: src/content/docs/guides/rows/row-autosize.mdx Description: Automatically adjust row heights based on content The **Row Auto Size Plugin** enhances RevoGrid by automatically calculating and adjusting row heights based on their content. This ensures optimal display of data, particularly useful for cells containing multi-line text, rich content, or varying amounts of information. When the plugin is enabled, normal grid cells wrap text so the rendered content matches the height calculation. Row heights are recalculated from the wrapped content after source changes, edits, scrolling into new virtual rows, configuration changes, and manual column resizing. It is a **sophisticated feature** that significantly improves data presentation and user experience: 1. **Dynamic Content Adaptation**: Automatically adjusts row heights to accommodate varying content lengths, ensuring all data is visible without truncation. 2. **Performance Optimization**: Uses efficient caching and virtualization to maintain smooth scrolling even with large datasets and dynamic heights. 3. **Precise Calculations**: Offers both approximate and precise height calculations, allowing you to balance between performance and accuracy. 4. **Event-Driven Updates**: Intelligently recalculates heights only when necessary, such as after edits or viewport changes. 5. **Memory Management**: Implements cleanup strategies to prevent memory leaks and maintain optimal performance during long sessions. - **Automatic Height Calculation**: Dynamically adjusts row heights based on content. - **Content-Aware**: Considers line breaks, text wrapping, and column widths. - **Resize-Aware**: Recalculates visible row heights after manual column resizing. - **Configurable Limits**: Set minimum and maximum heights to maintain consistency. - **Custom Calculator Support**: Implement your own height calculation logic. - **Efficient Caching**: Caches calculated heights to optimize performance. - **Precise Mode**: Optional precise calculations using DOM measurements. ## Basic Setup To enable the Row Auto Size Plugin in your RevoGrid setup: ```typescript import { RowAutoSizePlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.plugins = [RowAutoSizePlugin]; grid.rowAutoSize = { minHeight: 24, maxHeight: 200, preciseSize: true, // Use DOM-based precise calculations precalculate: true, // Warm approximate row sizes ahead of scroll precalculateBatchSize: 500, // Rows processed per frame during warmup }; grid.resize = true; // Optional: row heights adapt after user column resizing ``` ## Configuration Options The plugin supports several configuration options to customize its behavior: ```typescript type RowAutoSizeConfig = { // Minimum row height in pixels (default: 24) minHeight?: number; // Maximum row height in pixels (default: 200) maxHeight?: number; // Use precise DOM-based calculations (slower but more accurate) preciseSize?: boolean; // Warm all approximate row heights in chunks before they are scrolled into view (default: true) precalculate?: boolean; // Rows processed per animation frame during non-precise warmup (default: 500) precalculateBatchSize?: number; // Custom height calculator function calculateHeight?: (rowData: DataType, columns: ColumnRegular[]) => number | Promise; }; ``` ### Custom Height Calculator You can provide your own height calculation logic through the `calculateHeight` function: ```typescript grid.rowAutoSize = { calculateHeight: (rowData, columns) => { // Custom logic to determine row height const maxLines = columns.reduce((max, col) => { const content = rowData[col.prop]?.toString() || ''; const lines = content.split('\n').length; return Math.max(max, lines); }, 1); return maxLines * 24; // 24px per line } }; ``` ## Events and Updates The plugin automatically responds to various grid events to maintain accurate row heights: - **Data Changes**: Recalculates heights when the source data is updated - **Cell Edits**: Updates heights for edited rows - **Viewport Changes**: Calculates heights for newly visible rows - **Configuration Changes**: Recomputes all heights when plugin settings change - **Column Resizing**: Recomputes visible row heights after resized widths are rendered ## Performance Considerations The plugin is designed with performance in mind, but there are some considerations: 1. **Precise Mode**: Using `preciseSize: true` provides more accurate calculations but is slower as it requires DOM operations. 2. **Precalculation**: In non-precise mode, `precalculate` is enabled by default. The plugin warms row height cache in chunks so rows already have a height when they are scrolled into view. 3. **Batch Size**: `precalculateBatchSize` controls how many rows are processed per animation frame. Larger values warm the cache faster but make each frame heavier. 4. **Large Datasets**: For very large datasets, set `precalculate: false` to calculate only visible rows and preserve demand-driven behavior. 5. **Custom Calculators**: When implementing a custom calculator, ensure it's efficient as it will be called frequently during scrolling. To disable background warmup: ```typescript grid.rowAutoSize = { minHeight: 24, maxHeight: 200, precalculate: false, }; ``` ## Best Practices 1. **Default Mode for Large Datasets**: Use the default approximate calculation mode for large datasets to maintain performance. 2. **Custom Height Logic**: Use `calculateHeight` when your data needs application-specific sizing. 3. **Reasonable Limits**: Set appropriate `minHeight` and `maxHeight` to prevent extreme row sizes. 4. **Cache Management**: The plugin automatically manages its cache, but consider clearing it when making major data changes. ## Example Use Cases 1. **Multi-line Text**: ```typescript grid.columns = [{ prop: 'description', name: 'Description', size: 200, }]; grid.source = [{ description: 'This is a long description\nthat spans multiple lines\nand needs appropriate height' }]; ``` 2. **Rich Content**: ```typescript grid.rowAutoSize = { calculateHeight: (rowData) => { if (rowData.hasImage) { return 100; // Taller rows for rows with images } return 24; // Default height for other rows } }; ``` ## Conclusion The Row Auto Size Plugin provides a powerful solution for handling varying content heights in RevoGrid. By automatically adjusting row heights while maintaining performance, it significantly improves the presentation and usability of your grid, especially when dealing with dynamic or rich content. --- # Rows and Columns Context Menu URL: https://pro.rv-grid.com/guides/rows/row-context-menu/ Source: src/content/docs/guides/rows/row-context-menu.mdx Description: Custom context menu for rows and columns This example demonstrates how to create separate context menus with interactive actions for grid rows and columns. Use the same `ContextMenuPlugin`, but pass row actions through `rowContextMenu` and column-header actions through `columnContextMenu`. **Key Features**: - Separate row and column context menu configurations - Actions with visual indicators - Styled menu buttons with hover effects --- --- # Auto-Focus on Next Row WCAG URL: https://pro.rv-grid.com/guides/rows/row-next-line/ Source: src/content/docs/guides/rows/row-next-line.mdx Description: Enhances user navigation within the grid by automatically moving the focus to the next row The plugin enhances user navigation within the grid by automatically moving the focus to the next row when the user reaches the last cell of the current row. This feature streamlines data entry and editing processes by eliminating the need for manual navigation between rows. **Key Features**: - **Automatic Navigation**: Focus shifts seamlessly to the next row once the user navigates to the last cell in the current row. - **Enhanced Efficiency**: Improves data entry and editing efficiency by reducing the need for manual row transitions. - **Configurable Behavior**: Allows customization of focus behavior to fit specific user needs and grid configurations. **Benefits:** - **Improved User Experience**: Simplifies navigation within the grid, making data handling more intuitive. - **Increased Productivity**: Accelerates data entry tasks by automating row transitions. **Usage:** Integrate this plugin into your setup to enable automatic focus shifting, enhancing navigation and overall grid interaction. --- # Row Odd URL: https://pro.rv-grid.com/guides/rows/row-odd/ Source: src/content/docs/guides/rows/row-odd.mdx Description: Configurable row striping Apply configurable row striping for better readability and data distinction with the help of the `RowOddPlugin`. ```ts import { RowOddPlugin } from '@revolist/revogrid-pro'; grid.plugins = [RowOddPlugin]; grid.rowOdd = { mode: 'custom', interval: 3, offset: 1, className: 'priority-band', }; ``` `RowOddPlugin` is still a small rendering plugin, but it now covers production theming needs: odd/even striping, custom intervals, runtime updates, and predicate-based row bands. --- # Row Selection URL: https://pro.rv-grid.com/guides/rows/row-select/ Source: src/content/docs/guides/rows/row-select.mdx Description: Row selection in grid Apply selection to rows in grid. This is an advanced feature that allows users to select multiple rows in grid and explains how to implement this with the help of the `RowSelectPlugin`. It is an extremely **powerful and flexible feature**, despite its straightforward functionality. 1. **Handles Complex Edge Cases**: The selection plugin is built to support **extreme edge case scenarios**, ensuring it remains reliable and performant in even the most challenging use cases. 2. **Efficient Redraw Management**: Plugin provides **better control** over the selection updates, boosting **performance** and responsiveness for large datasets. Also it doesn't modify original data set. 3. **Visual Feedback with Highlighted Rows**: The plugin highlights **selected rows** and applies relevant CSS classes, providing **instant visual feedback** to users, making the grid more interactive and intuitive. 4. **Bulk Selection Support**: The plugin simplifies bulk operations by enabling **select all** functionality, allowing users to select and manipulate **multiple rows** at once. 5. **Event-Driven Row Selection**: The plugin emits a `rowselected` event whenever a row is selected, enabling easy collection of **selected rows** and providing flexibility for further actions. 6. **Interaction with Other Features**: The plugin works smoothly with **filtering, sorting, grouping, tree data, and drag-and-drop**, allowing users to **select rows** while still interacting with other grid features without interference. 7. **Advanced Plugin Example**: The `RowSelectPlugin` serves as an **advanced example** of how to create sophisticated, flexible grid plugins, demonstrating how to implement custom solutions for specific needs. 8. **Foundation for Future Plugins**: The plugin sets a strong **foundation** for building more advanced system features, providing a **reusable and extensible pattern** for other interactive grid-based plugins. - **Row Selection**: Allows users to select multiple rows in the grid. - **Column Selection**: Enables users to select entire columns. - **Grouped Row Selection**: Can render optional group-level checkboxes that select all descendant data rows. - **Checkbox-Based Row Reorder**: Can hand checkbox-selected rows to `RowOrderPlugin` so checked rows drag as one compact block. - **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings. - **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further. To enable the `RowSelectPlugin` in your RevoGrid setup, you have two options: 1. **Using `rowSelect` Prop in Columns** 2. **Using `columnType` with `RowSelectColumnType`** ## Option 1: Using `rowSelect` Prop in Columns In this approach, the rowSelect property in the column definition enables checkbox-based row selection for the specific column. ```typescript grid.columns = [ { prop: '_check', rowSelect: true, }, ... ]; // Define plugin grid.plugins = [RowSelectPlugin]; ``` ## Option 2: Using columnType with RowSelectColumnType Alternatively, you can use a custom columnType and the RowSelectColumnType to enable row selection. ```typescript import { RowSelectColumnType, RowSelectPlugin, DEFAULT_SEL_PROP } from '@revolist/revogrid-pro'; grid.source = []; // Define columns grid.columns = [{ prop: DEFAULT_SEL_PROP, columnType: 'select' // Use columnType 'select' }, ...]; // Define plugin grid.plugins = [RowSelectPlugin]; // Define the custom column type for selection grid.columnTypes = { select: new RowSelectColumnType()}; ``` ## Grouped Row Selection Grouped row checkboxes are opt-in so existing grouped grids keep their current rendering. ```typescript grid.columns = [ { prop: 'selected', rowSelect: true }, { prop: 'name', name: 'Name' }, { prop: 'department', name: 'Department' }, ]; grid.grouping = { props: ['department'], expandedAll: true }; grid.rowSelect = { grouping: true, }; grid.plugins = [RowSelectPlugin]; ``` You can also use the future-compatible object form: ```typescript grid.rowSelect = { grouping: { enabled: true, }, }; ``` When enabled, selecting a group checkbox selects all descendant data rows. Selecting only part of a group reflects an indeterminate state on the group checkbox. Synthetic grouping rows are not included in selected row counts. ## Checkbox-Based Row Reorder `RowSelectPlugin` can opt into `RowOrderPlugin` integration. When enabled, dragging a checked row moves all visible checked data rows in the current row viewport as a compact block. This feature is disabled by default. Existing row-order behavior is unchanged unless `rowSelect.rowOrder` is enabled. ```typescript import { RowOrderPlugin, RowSelectPlugin } from '@revolist/revogrid-pro'; grid.columns = [ { prop: 'drag', name: '', rowDrag: true, size: 42 }, { prop: 'selected', name: '', rowSelect: true, size: 56 }, { prop: 'name', name: 'Name' }, { prop: 'team', name: 'Team' }, ]; grid.rowSelect = { rowOrder: true, }; grid.rowOrder = { prop: 'name', preview: 'compact', }; grid.plugins = [RowSelectPlugin, RowOrderPlugin]; ``` You can also use the object form: ```typescript grid.rowSelect = { rowOrder: { enabled: true, }, }; ``` Checkbox selection takes priority over range selection only when the dragged row is checked. If the dragged row is not checked, `RowOrderPlugin` falls back to its current range-selection or single-row behavior. Only visible checked data rows move during the drag. Rows hidden by filtering, collapsed grouping, or collapsed tree branches remain selected but are not part of that drag operation. Synthetic grouping rows are never moved as selected data rows. ## Grouping And Row Reorder Together Grouped row checkboxes and checkbox-based row reorder can be enabled together: ```typescript grid.grouping = { props: ['team'], expandedAll: true }; grid.rowSelect = { grouping: true, rowOrder: true, }; grid.rowOrder = { prop: 'name', preview: 'compact', }; grid.plugins = [RowSelectPlugin, RowOrderPlugin]; ``` With this setup: - Group checkboxes select or clear all descendant data rows. - Dragging a checked child row moves visible checked child rows as a compact block. - Parent and ancestor group checkboxes reflect child selection with checked or indeterminate state. - Group headers are excluded from select-all counts and row-order moved items. - Row-order grouping validation still applies, so invalid cross-group drops remain blocked unless your row-order configuration explicitly allows applying target group values. ## Filtering, Sorting, Row Drag, And Tree Data Row selection is stored against real data row indexes, not the rendered row position. This keeps checkbox state stable when the visible row topology changes. | Case | Behavior | | --- | --- | | Filtering | Selected hidden rows remain selected. The `rowselected` event is re-emitted so `visibleCount` and `visibleRowsCount` reflect the filtered view. | | Sorting | Checkbox state follows the same data rows after sort order changes. | | Row drag | Checkbox state follows moved data rows after row order changes. | | Checkbox row-order | When `rowSelect.rowOrder` is enabled, visible checked rows are moved as a compact block before range selection is considered. | | Grouping | Group checkboxes are opt-in through `rowSelect.grouping`; synthetic group rows are not counted as selected rows. | | Tree data | When `TreeDataPlugin` is registered, selecting a tree parent toggles the parent and descendants. Dragging a checkbox-selected tree parent with descendants preserves the subtree relationship. | ## API Quick Reference | API | Default | Meaning | | --- | --- | --- | | `ColumnRegular.rowSelect` | `false` | Renders row-selection checkboxes in normal data cells for that column. A predicate can hide checkboxes for specific cells. | | `grid.rowSelect.grouping` | `false` | Enables group-level row-selection checkboxes. Accepts `true` or `{ enabled: true }`. | | `grid.rowSelect.rowOrder` | `false` | Enables checkbox-selected row drag integration with `RowOrderPlugin`. Accepts `true` or `{ enabled: true }`. | | `additionalData.rowSelect` | none | Legacy alias for `grid.rowSelect`. Prefer direct `grid.rowSelect`. | The `rowselected` event exposes totals for both all selectable rows and the currently visible selectable rows: | Event field | Meaning | | --- | --- | | `count` | Selected real data rows across the full dataset. | | `allRowsCount` | Selectable real data rows across the full dataset. | | `visibleCount` | Selected real data rows currently visible. | | `visibleRowsCount` | Selectable real data rows currently visible. | ## Bulk Row Selection Demo The live demo on this page is the common **Bulk Row Selection** example. It includes flat, grouped, and tree modes, a row checkbox column, row drag handles, group checkbox selection, filtering, sorting, selected/visible totals, and opt-in checkbox-based row reorder. --- # Row Transpose URL: https://pro.rv-grid.com/guides/rows/row-transpose/ Source: src/content/docs/guides/rows/row-transpose.mdx Description: Learn how to use the Row Transpose Plugin in RevoGrid Pro to switch rows and columns dynamically. The Row Transpose Plugin modifies the grid by transposing its data, meaning that the original rows are converted into columns and vice versa. This can be especially useful when dealing with datasets that require pivoting or when users need to explore data from different perspectives. - **Toggle Transpose**: Switch between the original view and the transposed view. - **Dynamic Headers**: Customizable transposed headers for better data representation. - **Custom Transpose Configuration**: Define how the columns and rows should be transformed, including whether to transpose column headers into rows. ## How Transposing Works The plugin offers two primary functionalities: - **Original View**: Displays rows and columns as defined in your data model. - **Transposed View**: When activated, this feature converts rows into columns and columns into rows. The `RowTransposePlugin` listens for the `row-transpose`. When triggered, it switches between the original view and the transposed view while preserving the original structure, allowing you to revert to the standard view at any time. Alternatively, you can access the plugin directly using the following JavaScript code: ```javascript document.querySelector('revo-grid').getPlugins().then(plugins => { const plugin = plugins.find(p => p instanceof RowTransposePlugin); plugin?.transpose(); }); ``` When the rows are transposed, RevoGrid enters a virtual transposed mode. This means that events related to row and column edits will return instances of the `TransposedRow` class, which represent the transposed data model. If you wish to retrieve the original model, you can use the `TransposedRow.getOriginalModels` method. This method takes transposed column properties as input and converts the corresponding transposed cell back to the original model. For example: ```javascript document.querySelector('revo-grid').addEventListener('beforeedit', async (e) => { const model = e.detail.model; console.log('beforeedit', model.getOriginalModels([e.detail.prop])); // returns original row model }); ``` ### Customizing the Transpose The plugin configuration allows you to fine-tune the transposing process. For example, you can choose to transpose the column headers into rows, or leave them unchanged. ```javascript document.querySelector('revo-grid').additionalData = { transpose: { transposedRowHeader: { cellTemplate: () => '', columnTemplate: () => '' }, // defined row/header template transposedColumnHeader: { columnTemplate: () => '' }, // template for transposed columns } }; ``` ## Conclusion The **Row Transpose Plugin** is a powerful tool for displaying data in different formats within RevoGrid. Whether you're working with complex datasets that require pivoting or simply need to view your data from a different perspective, this plugin provides the flexibility to switch between views seamlessly. With customizable options for how rows and columns are transposed, you can easily tailor the grid to your needs. --- # Server-side Row Grouping URL: https://pro.rv-grid.com/guides/server-side-grouping/ Source: src/content/docs/guides/server-side-grouping.mdx Description: Group, expand, and lazy-load millions of rows without loading the full dataset into the browser. Server-side row grouping for large datasets lets RevoGrid render grouped data from a backend route API. The browser keeps only visible grouped blocks and cached route blocks, while your server owns grouping, sorting, filtering, quick filtering, counts, and aggregates. Use it for ERP, financial analytics, logs, operations, and reporting tools where a complete client-side dataset is too large. - Group by one or multiple columns. - Lazy-load group children on expand. - Works with server-side Infinity Scroll semantics. - Supports unknown row counts. - Keeps browser memory low with block caching. - Sends sort, filter, quick-filter, and group state to your backend. ## Setup ```ts import { ServerSideGroupingPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.serverSideGrouping = { groupBy: ['country', 'sport'], loadRows: async (request, signal) => { const response = await fetch('/api/grid/grouping', { method: 'POST', signal, headers: { 'content-type': 'application/json' }, body: JSON.stringify(request), }); return response.json(); }, }; grid.plugins = [ServerSideGroupingPlugin]; ``` `groupBy` and `loadRows` are the only required options. The full configuration can add request tuning, a quick-filter payload, an imperative plugin controller, and an optimized expand-all datasource: ```ts let groupingPluginControllerApi; grid.serverSideGrouping = { groupBy: ['country', 'sport'], blockSize: 100, purgeClosedGroups: false, maxConcurrentRequests: 4, blockLoadDebounceMs: 25, quickFilter: { text: '' }, api: api => { groupingPluginControllerApi = api; }, loadRows, loadExpandedGroups: async (request, signal) => { const response = await fetch('/api/grid/grouping/expand-all', { method: 'POST', signal, headers: { 'content-type': 'application/json' }, body: JSON.stringify(request), }); return response.json(); }, }; ``` `api` is not a server endpoint. It is a callback that gives the application an imperative controller after the plugin is initialized. Use it from toolbar buttons, route changes, or refresh actions: ```ts groupingPluginControllerApi?.refreshServerSide(); groupingPluginControllerApi?.expandGroup(['Germany']); groupingPluginControllerApi?.purgeServerSideCache(); ``` `loadRows` is the required server datasource. It loads one route block at a time: root groups, child groups, or final leaf rows. `loadExpandedGroups` is optional and only optimizes `expandAllGroups()` by letting the backend return a flattened group tree in one request. The plugin owns the main row source while active. `InfinityScrollPlugin` is complementary in behavior and request shape, but should not also own the same `rgRow` source. If both plugins are registered and `serverSideGrouping` is configured, Infinity Scroll no-ops with a warning. When combining server-side grouping with selection filters, provide `filter.selection.getItems` from the same backing datasource used by your server loader. The grid source contains generated group rows and only the currently loaded route blocks, so deriving selection options from the visible grid source can produce incomplete values such as only `Empty`. ## Request Contract `loadRows(request, signal)` receives: ```ts { route: ['Germany'], level: 1, groupBy: ['country', 'sport'], start: 0, limit: 100, sort: { revenue: 'desc' }, filter: { country: { type: 'contains', value: 'Ger' } }, quickFilter: { text: 'premium' }, parent: { key: 'Germany', count: 12450 }, levelParams: { tenantId: 'acme' } } ``` Return group rows before the final grouping level: ```ts { groups: [ { key: 'Swimming', count: 2100, aggregates: { Revenue: 'EUR 9.2M' } }, { key: 'Cycling', count: 1540 } ], rowCount: 2, grandTotals: { Revenue: 'EUR 18.7M' } } ``` Return leaf rows at the final level: ```ts { rows: [ { id: 1, country: 'Germany', sport: 'Swimming', city: 'Berlin', revenue: 1200 } ], rowCount: 2100, hasMore: true } ``` When `rowCount` is omitted, RevoGrid treats the count as unknown and keeps a loading tail while `hasMore` is true. ## Runtime API The API is exposed through the config callback, not by mutating the grid instance. ```ts grid.serverSideGrouping = { groupBy: ['country'], loadRows, api: api => { groupingPluginControllerApi = api; groupingPluginControllerApi?.expandGroup(['Germany']); groupingPluginControllerApi?.expandAllGroups(); groupingPluginControllerApi?.refreshRoute(['Germany']); groupingPluginControllerApi?.purgeServerSideCache(); console.log(groupingPluginControllerApi?.getServerSideCacheState()); }, }; ``` Available methods: | Method | Description | |---|---| | `expandGroup(id)` | Expands and lazy-loads a group route. | | `expandAllGroups()` | Loads group blocks recursively and expands every server group route. Leaf-row blocks still load from the viewport. | | `collapseGroup(id)` | Collapses a group route. | | `collapseAllGroups()` | Collapses all group routes currently known by the cache. | | `refreshServerSide()` | Invalidates all route blocks and reloads root. | | `refreshRoute(route)` | Invalidates only one route branch when possible. | | `purgeServerSideCache(route?)` | Clears all cache or one route branch. | | `getServerSideCacheState()` | Returns debug route, block, loading, and queue state. | `expandAllGroups()` opens every group route. It does not load every leaf row, so large final groups stay virtualized and fetch visible row blocks through the normal server-side scroll path. When `loadExpandedGroups` is configured, `expandAllGroups()` uses that single endpoint instead of recursively loading route blocks: ```ts grid.serverSideGrouping = { groupBy: ['country', 'sport'], loadRows, loadExpandedGroups: async request => ({ groups: [ { route: ['Germany'], key: 'Germany', count: 12450 }, { route: ['Germany', 'Swimming'], key: 'Swimming', count: 2100 }, { route: ['Germany', 'Cycling'], key: 'Cycling', count: 1540 }, ], }), }; ``` Each returned group row must include its full `route`. The plugin rebuilds group route blocks from this flattened tree and keeps leaf-row blocks unloaded until they enter the viewport. ## Configuration | Option | Default | Description | |---|---:|---| | `groupBy` | required | Grouping column props in server route order. | | `blockSize` | `100` | Direct children requested per block. | | `purgeClosedGroups` | `false` | Purge child route cache on collapse. | | `maxConcurrentRequests` | `4` | Limits simultaneous route block requests. | | `blockLoadDebounceMs` | `0` | Delays block load dispatch after scroll/materialization. | | `quickFilter` | `undefined` | Global filter payload forwarded to the server. | | `api` | `undefined` | Receives the client-side plugin controller. This is not a server request callback. | | `loadRows` | required | Server datasource for root groups, nested group routes, and final leaf rows. | | `loadExpandedGroups` | `undefined` | Optional one-request expand-all endpoint that returns flattened group rows with routes. | --- # Cell Validation with Renderers URL: https://pro.rv-grid.com/guides/validate-basic/ Source: src/content/docs/guides/validate-basic.mdx Description: Explore different methods for validating cell data in RevoGrid and ensuring data integrity. One straightforward way to validate cell data is by combining `validate`, `validationTooltip`, and `validationRenderer`. This marks invalid cells during rendering without changing edit behavior, which is useful for audits, imported data review, and soft warnings. ```ts import { validationRenderer } from '@revolist/revogrid-pro'; const columns = [ { prop: 'price', name: 'Price', validate: (value, row) => value >= row.floor && value <= row.ceiling, validationTooltip: (value, row) => `Price ${value} must stay between ${row.floor} and ${row.ceiling}`, ...validationRenderer({ severity: 'error', indicatorPlacement: 'corner', messagePlacement: 'tooltip', }), }, ]; ``` `validationRenderer` returns `cellProperties` and `cellTemplate`, so it can wrap your existing cell renderer while keeping validation styling in one place. The helper supports: - `severity`: `error`, `warning`, or `info`. - `indicatorPlacement`: `corner`, `start`, `end`, or `none`. - `messagePlacement`: `tooltip`, `inline`, `both`, or `none`. - `message`: a custom message resolver when `validationTooltip` is not enough. - `invalidProperties`: extra cell properties for invalid cells. `messagePlacement` controls where the validation message is exposed: - `tooltip` adds `title` and tooltip data attributes. - `inline` renders the message inside the cell without tooltip attributes. - `both` renders inline text and tooltip attributes. - `none` keeps the visual indicator but hides the message text. :::note This renderer-only approach shows invalid data already present in the grid. To prevent invalid edits from being committed, use `CellValidatePlugin` from the input validation guide. ::: --- # Validate user data input URL: https://pro.rv-grid.com/guides/validate-input/ Source: src/content/docs/guides/validate-input.mdx Description: Explore strict and soft cell validation for spreadsheet-style data entry. This demo exposes the rules as controls. Try strict mode to reject invalid edits, soft mode to save invalid data with markers, and the seed/reset actions to simulate cleanup workflows. The **CellValidatePlugin** validates edits before they are written to the grid source. Use it when the grid should enforce spreadsheet-style rules such as required fields, numeric ranges, enum membership, cross-field limits, and approval gates. Install it together with **EventManagerPlugin** so edit events are collected through the Pro event pipeline: ```ts import { CellValidatePlugin, EventManagerPlugin, } from '@revolist/revogrid-pro'; grid.plugins = [ EventManagerPlugin, CellValidatePlugin, ]; grid.eventManager = { applyEventsToSource: true, }; ``` :::note `EventManagerPlugin` should be registered before `CellValidatePlugin`. The event manager emits the unified `gridedit` event, and `CellValidatePlugin` cancels that event when strict validation fails. ::: ## Strict and soft validation By default validation is strict. If `validate(value, model)` returns `false`, the edit is rejected and the source keeps the previous value. ```ts const columns: ColumnRegular[] = [ { name: 'Price', prop: 'price', validate: (value, model) => { const price = Number(value); return Number.isFinite(price) && price >= 50 && price <= 1200; }, validationTooltip: (value) => `Price ${value} must be between 50 and 1200`, }, ]; ``` Set `softValidation: true` when invalid values should still be saved and marked visually. This is useful for imported data cleanup flows where users need to see and fix invalid rows in place. Pair it with `validationRenderer` when you want saved invalid values to stay visible after the edit. ```ts { prop: 'discount', softValidation: true, ...validationRenderer(), validate: (value, model) => Number(value) <= Number(model.price) * 0.3, validationTooltip: (value, model) => `Discount cannot exceed 30% of price (${Math.round(Number(model.price) * 0.3)})`, } ``` ## Candidate row model Both `validate(value, model)` and `validationTooltip(value, model)` receive the row model. During strict edit validation, the model includes the pending row patch, so cross-field validation works for single edits and pasted batches. If the edit is rejected, the tooltip renderer receives the rejected value and candidate model, which lets the message explain what failed even though the source was not changed. ```ts { name: 'Approved', prop: 'approved', validate: (value, model) => { const netPrice = Number(model.price) - Number(model.discount ?? 0); return netPrice <= 900 || value === true; }, validationTooltip: () => 'High-value rows require approval', } ``` `validationTooltip` also receives the column as a third argument: `validationTooltip(value, model, column)`. Use that when one shared message resolver needs column metadata. ## Cross-column example The validation function can also block a cell based on another column in the same row. This example prevents fees on expired services. --- # Welcome to the RevoGrid Pro URL: https://pro.rv-grid.com/ Source: src/content/docs/index.mdx Description: Get started building your apps with RevoGrid Pro. --- Thank you for switching to the Pro version of RevoGrid! You now have the option to [remove attribution from your projects](./guides/hide-attribution). Enjoy the freedom and flexibility that comes with your Pro membership! Thank you for supporting the RevoGrid team! ✌🏻 Connect Codex, Cursor, Claude Code, or VS Code to the hosted RevoGrid MCP and let your AI tools retrieve Pro examples, docs, migration notes, and typed plugin context before generating code. [Read the Pro MCP guide](/guides/ai-mcp) ## Top Features --- # Aggregations URL: https://pro.rv-grid.com/api/aggregations/ Source: src/content/docs/api/aggregations.md ### `commonAggregators` Defines a collection of common aggregation functions used for table calculations. These aggregators transform an array of numerical values into meaningful statistical summaries. ```ts commonAggregators: { sum: (values: any[]) => any; count: (values: any[]) => number; avg: (values: any[]) => number; min: (values: any[]) => number; max: (values: any[]) => number; median: (values: any[]) => number; mode: (values: any[]) => string | 0; range: (values: any[]) => number; variance: (values: any[]) => number; stdDev: (values: any[]) => number; first: (values: any[]) => any; last: (values: any[]) => any; distinct: (values: any[]) => number; }; ``` --- ### `advancedAggregators` ```ts advancedAggregators: { '%oftotal': (values: any[]) => number[]; accSum: (values: any[]) => any; }; ``` --- ### `Aggregations` ```ts export type Aggregations = Record any>; ``` --- # Array Renderer URL: https://pro.rv-grid.com/api/array-renderer/ Source: src/content/docs/api/array-renderer.md ### `arrayRenderer` The `arrayRenderer` is a custom cell renderer for RevoGrid that can handle both single values and arrays of values. It applies different renderers based on the value type. **Features**: - Detects if the value is an array or a single value - For arrays, renders multiple elements using the provided renderer - For single values, renders a single element using the provided renderer - Supports custom separator between array items - Supports custom wrapper for array items **Usage**: - Import `arrayRenderer` and use it as a wrapper around your existing renderer - Configure the renderer with options like separator and wrapper ### Example ```typescript import { arrayRenderer } from '@revolist/revogrid-pro'; import { linkRenderer } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'links', name: 'Links', cellTemplate: arrayRenderer(linkRenderer, { separator: ', ', wrapper: 'div', }), }, ]; ``` Example of using multiRenderer to handle different types of values prop: 'data', name: 'Data', cellTemplate: multiRenderer({ default: progressLineRenderer, renderers: [ { // Use linkRenderer for URLs condition: (value) => typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://')), renderer: linkRenderer, }, { // Use progressLineRenderer for numbers condition: (value) => typeof value === 'number', renderer: progressLineRenderer, }, ], separator: ' | ', wrapper: 'div', wrapperClass: 'multi-data-container', }), }; ```ts export function arrayRenderer( renderer: ColumnRegular['cellTemplate'], options: ArrayRendererOptions =; ``` --- ### `multiRenderer` The `multiRenderer` is a custom cell renderer for RevoGrid that can apply different renderers based on the value type. It's similar to `arrayRenderer` but allows for more complex rendering logic. **Features**: - Detects if the value is an array or a single value - For arrays, applies a different renderer for each item based on a condition - For single values, applies a default renderer - Supports custom separator between array items - Supports custom wrapper for array items **Usage**: - Import `multiRenderer` and use it to define different renderers for different value types - Configure the renderer with options like separator and wrapper ### Example ```typescript import { multiRenderer } from '@revolist/revogrid-pro'; import { linkRenderer } from '@revolist/revogrid-pro'; import { progressLineRenderer } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'data', name: 'Data', cellTemplate: multiRenderer({ default: progressLineRenderer, renderers: [ { condition: (value) => typeof value === 'string' && value.startsWith('http'), renderer: linkRenderer, }, ], }), }, ]; ``` ```ts export function multiRenderer(config:; ``` --- ### `ArrayRendererOptions` ```ts export type ArrayRendererOptions = { /** * Separator between array items * @default ', ' */ separator?: string; /** * Wrapper element for array items * @default 'div' */ wrapper?: string; /** * CSS class for the wrapper element */ wrapperClass?: string; /** * CSS style for the wrapper element */ wrapperStyle?: Record; }; ``` --- # Audit History URL: https://pro.rv-grid.com/api/audit-history/ Source: src/content/docs/api/audit-history.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`) ```ts interface HTMLRevoGridElement { auditHistory?: AuditHistoryConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * @deprecated Use the direct `grid.auditHistory` property instead. */ auditHistory?: AuditHistoryConfig } ``` --- ### `HTMLRevoGridElementEventMap` (Extended from `global`) ```ts interface HTMLRevoGridElementEventMap { auditrecord: AuditRecord; auditrecordsloaded: AuditRecord[]; auditerror: AuditHistoryError; beforeauditrestore: AuditRestoreEvent; auditrestore: AuditRestoreEvent } ``` ## Plugin API ### `createLocalStorageAuditHistoryAdapter` ```ts export function createLocalStorageAuditHistoryAdapter(storageKey = DEFAULT_STORAGE_KEY): AuditHistoryStorageAdapter; ``` --- ### `AuditHistoryPlugin` #### Dependencies - **Required** `EventManagerPlugin`: Requires EventManagerPlugin to receive normalized edit and paste events. - **Auto-installed** `HistoryPlugin`: Auto-installs or reuses HistoryPlugin for undo/redo shortcuts and replay integration. ```ts class AuditHistoryPlugin { getRecords(filter?: AuditHistoryFilter): AuditRecord[]; getCellHistory(rowId: string | number, column: ColumnProp): AuditRecord[]; getRowHistory(rowId: string | number): AuditRecord[]; getRecord(id: string): AuditRecord | undefined; getTransaction(transactionId: string): AuditRecord[]; getLastChange(rowId: string | number, column?: ColumnProp): AuditChange | undefined; getStats(filter?: AuditHistoryFilter): AuditHistoryStats; getRowIdentity(row: DataType | undefined, rowIndex: number, rowType: DimensionRows): string | undefined; clear(): boolean; replaceRecords(records: AuditRecord[], options:; recordEvent(input: AuditRecordEventInput): AuditRecord | undefined; createSnapshot(label: string, options: AuditSnapshotOptions =; restoreSnapshot(recordOrId: AuditRecord | string): boolean; async refreshRecords(options:; exportRecords(options: AuditHistoryExportOptions =; canRestoreRecord(context: AuditHistoryRestoreContext): boolean; restoreCell(change: AuditChange): boolean; restoreRow(recordOrRowId: AuditRecord | string | number): boolean; restoreTransaction(transactionId: string): boolean; } ``` --- ### `AuditUser` ```ts export type AuditUser = { id: string; name?: string; email?: string; }; ``` --- ### `BuiltInAuditActionType` ```ts export type BuiltInAuditActionType = | 'cell-change' | 'bulk-paste' | 'row-added' | 'row-deleted' | 'row-restored' | 'undo' | 'redo' | 'restore-cell' | 'restore-row' | 'restore-transaction' | 'api-sync' | 'formula-update' | 'snapshot-created' | 'snapshot-restored'; ``` --- ### `AuditActionType` ```ts export type AuditActionType = BuiltInAuditActionType | (string & {}); ``` --- ### `AuditHistorySource` ```ts export type AuditHistorySource = | 'manual' | 'paste' | 'api' | 'formula' | 'restore' | 'snapshot' | 'system'; ``` --- ### `AuditRecordPresentation` ```ts export type AuditRecordPresentation = { source?: AuditHistorySource; verb?: string; targetLabel?: string; detailLabel?: string; rangeLabel?: string; avatarLabel?: string; avatarIndex?: number; avatarColor?: string; accent?: boolean; }; ``` --- ### `AuditChangePresentation` ```ts export type AuditChangePresentation = { cellLabel?: string; columnLabel?: string; }; ``` --- ### `AuditChange` ```ts export type AuditChange = { id: string; rowId?: string; rowIndex?: number; rowType: DimensionRows; column?: ColumnProp; oldValue?: unknown; newValue?: unknown; oldRow?: DataType; newRow?: DataType; metadata?: Record; presentation?: AuditChangePresentation; }; ``` --- ### `AuditRecord` ```ts export type AuditRecord = { id: string; transactionId: string; type: AuditActionType; changedAt: string; changedBy: AuditUser; changes: AuditChange[]; metadata?: Record; presentation?: AuditRecordPresentation; }; ``` --- ### `AuditHistoryFilter` ```ts export type AuditHistoryFilter = { rowId?: string | number; column?: ColumnProp; userId?: string; userEmail?: string; actionType?: AuditActionType; transactionId?: string; dateFrom?: string | Date; dateTo?: string | Date; }; ``` --- ### `AuditHistoryStorageAdapter` ```ts export type AuditHistoryStorageAdapter = { load?: () => AuditRecord[] | Promise; save?: (records: AuditRecord[]) => void | Promise; append?: (record: AuditRecord, records: AuditRecord[]) => void | Promise; clear?: () => void | Promise; }; ``` --- ### `AuditHistoryError` ```ts export type AuditHistoryError = { phase: | 'get-current-user' | 'metadata' | 'sanitize' | 'storage-load' | 'storage-save' | 'storage-append' | 'storage-clear' | 'on-record' | 'restore' | 'export' | 'replace-records' | 'record-event' | 'track-record' | 'snapshot'; error: unknown; record?: AuditRecord; }; ``` --- ### `AuditHistoryRestoreContext` ```ts export type AuditHistoryRestoreContext = { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string; }; ``` --- ### `AuditHistoryStats` ```ts export type AuditHistoryStats = { totalRecords: number; totalChanges: number; firstChangedAt?: string; lastChangedAt?: string; byType: Partial>; byUser: Record; }; ``` --- ### `AuditHistoryExportFormat` ```ts export type AuditHistoryExportFormat = 'json' | 'csv'; ``` --- ### `AuditHistoryExportOptions` ```ts export type AuditHistoryExportOptions = { format?: AuditHistoryExportFormat; filter?: AuditHistoryFilter; includeMetadata?: boolean; }; ``` --- ### `AuditRecordTrackContext` ```ts export type AuditRecordTrackContext = { type: AuditActionType; changes: AuditChange[]; source?: AuditHistorySource; metadata?: Record; record: AuditRecord; event?: unknown; }; ``` --- ### `AuditHistoryConfig` ```ts export type AuditHistoryConfig = { getCurrentUser?: () => AuditUser; getMetadata?: (context: { type: AuditActionType; changes: AuditChange[] }) => Record | undefined; rowIdProp?: string; getRowId?: ( row: DataType, context: { rowIndex: number; rowType: DimensionRows }, ) => string | number | undefined; sanitizeValue?: ( value: unknown, context: { rowId?: string; rowIndex?: number; rowType: DimensionRows; column?: ColumnProp; direction: 'old' | 'new'; }, ) => unknown; shouldTrackChange?: ( change: Omit, context: { type: AuditActionType }, ) => boolean; shouldTrackRecord?: (context: AuditRecordTrackContext) => boolean; trackedActionTypes?: AuditActionType[]; ignoredActionTypes?: AuditActionType[]; ignoredColumns?: ColumnProp[] | ((context: { column?: ColumnProp; rowType: DimensionRows; rowId?: string }) => boolean); captureSourceUpdates?: boolean; immutable?: boolean; canRestore?: (context: AuditHistoryRestoreContext) => boolean; storage?: 'memory' | 'localStorage' | 'custom'; storageKey?: string; storageAdapter?: AuditHistoryStorageAdapter; storageProviders?: AuditHistoryStorageAdapter[]; mergeStorageProviders?: boolean; records?: AuditRecord[]; maxRecords?: number; disabled?: boolean; onAuditRecord?: (record: AuditRecord) => void | Promise; onAuditError?: (error: AuditHistoryError) => void; onRecordsLoaded?: (records: AuditRecord[]) => void; }; ``` --- ### `AuditEventChangeInput` (Extended from `index.ts`) ```ts export type AuditEventChangeInput = Omit & { id?: string }; ``` --- ### `AuditRecordEventInput` ```ts export type AuditRecordEventInput = { type: AuditActionType; changes?: AuditEventChangeInput[]; changedAt?: string; changedBy?: AuditUser; transactionId?: string; metadata?: Record; presentation?: AuditRecordPresentation; allowEmpty?: boolean; }; ``` --- ### `AuditSnapshotOptions` ```ts export type AuditSnapshotOptions = { rowType?: DimensionRows; rows?: DataType[]; metadata?: Record; presentation?: AuditRecordPresentation; }; ``` --- ### `AuditRestoreType` ```ts export type AuditRestoreType = 'cell' | 'row' | 'transaction' | 'snapshot'; ``` --- ### `AuditRestoreEvent` ```ts export type AuditRestoreEvent = { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string; }; ``` --- ### `defineAuditHistoryPanel` Renders the Audit History panel into `el` for the given RevoGrid element. The grid must have `AuditHistoryPlugin` installed. ```ts export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions =; ``` --- ### `AuditHistoryPanelDateFormatContext` ```ts export type AuditHistoryPanelDateFormatContext = { mode: 'time' | 'day' | 'filter'; record?: AuditRecord; }; ``` --- ### `AuditHistoryPanelEvents` ```ts export type AuditHistoryPanelEvents = { beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void; beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void; beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void; beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void; beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void; }; ``` --- ### `AuditHistoryPanelExportContext` ```ts export type AuditHistoryPanelExportContext = { format: AuditHistoryExportFormat; }; ``` --- ### `AuditHistoryPanelJumpContext` ```ts export type AuditHistoryPanelJumpContext = { record: AuditRecord; change: AuditChange; }; ``` --- ### `AuditHistoryPanelLabels` ```ts export type AuditHistoryPanelLabels = { title: string; liveStatus: string; recordsSummary: (count: number) => string; changesSummary: (count: number) => string; allTab: string; rowTab: string; cellTab: string; tableTab: string; selectedCell: (rowId: string, column: ColumnProp) => string; selectedRow: (rowId: string) => string; export: string; exportCsv: string; exportJson: string; exportCsvTitle: string; exportJsonTitle: string; close: string; openPanel: string; searchPlaceholder: string; searchAriaLabel: string; searchShortcut: string; userPlaceholder: string; userAriaLabel: string; columnAriaLabel: string; actionAriaLabel: string; dateFromAriaLabel: string; dateToAriaLabel: string; allColumns: string; allActions: string; clearFilters: string; addFilter: string; removeFilter: (label: string) => string; noRecords: string; noComparableRecord: string; scopedRecordCount: (visibleChanges: number, totalChanges: number) => string; changedBy: (changedAt: string, user: string) => string; rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string; cellValues: (oldValue: string, newValue: string) => string; rowSnapshotAvailable: string; rowRemoved: string; restoreCell: string; restoreRow: string; restoreTransaction: string; restoreSnapshot: string; jumpToCell: string; revertAll: string; moreActions: string; compare: string; compareTitle: string; compareBefore: string; compareAfter: string; closeCompare: string; compareEmpty: string; viewChanges: (count: number) => string; previousPage: string; nextPage: string; pageStatus: (currentPage: number, pageCount: number) => string; dayToday: string; dayYesterday: string; sourceLabels: Record; actions: Partial>; actionVerbs: Partial>; }; ``` --- ### `AuditHistoryPanelLabelsOptions` (Extended from `index.ts`) ```ts export type AuditHistoryPanelLabelsOptions = Partial> & { actions?: Partial>; actionVerbs?: Partial>; sourceLabels?: Partial>; }; ``` --- ### `AuditHistoryPanelHandle` ```ts export type AuditHistoryPanelHandle = { destroy: () => void; setPlacement: (placement: AuditHistoryPanelPlacement) => void; refresh: () => Promise; }; ``` --- ### `AuditHistoryPanelOptions` ```ts export type AuditHistoryPanelOptions = { pageSize?: number; allowExport?: boolean; allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean); allowClose?: boolean; miniOnClose?: boolean; restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean); formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string; events?: AuditHistoryPanelEvents; placement?: AuditHistoryPanelPlacement; tooltips?: false | Partial>; labels?: AuditHistoryPanelLabelsOptions; }; ``` --- ### `AuditHistoryPanelRecordContext` ```ts export type AuditHistoryPanelRecordContext = { record: AuditRecord; changes: AuditChange[]; }; ``` --- ### `AuditHistoryPanelRestoreContext` ```ts export type AuditHistoryPanelRestoreContext = { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string; }; ``` --- ### `AuditHistoryPanelPlacement` ```ts export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none'; ``` --- ### `AuditHistoryPanelTooltipKey` ```ts export type AuditHistoryPanelTooltipKey = | 'totalRecords' | 'totalChanges' | 'selectedScope' | 'searchFilter' | 'userFilter' | 'columnFilter' | 'actionFilter' | 'dateFromFilter' | 'dateToFilter' | 'clearFilters'; ``` --- ### `AUDIT_HISTORY_STORAGE_KEY` ```ts AUDIT_HISTORY_STORAGE_KEY: string; ``` --- ### `cloneValue` ```ts export function cloneValue(value: T): T; ``` --- ### `cloneChange` ```ts export function cloneChange(change: AuditChange): AuditChange; ``` --- ### `cloneRecord` ```ts export function cloneRecord(record: AuditRecord): AuditRecord; ``` --- ### `normalizeRowId` ```ts export function normalizeRowId(rowId: string | number | undefined): string | undefined; ``` --- ### `toTime` ```ts export function toTime(value: string | Date | undefined, fallback: number); ``` --- ### `shouldIncludeRecord` ```ts export function shouldIncludeRecord(record: AuditRecord, filter?: AuditHistoryFilter); ``` --- ### `trimRecords` ```ts export function trimRecords(records: AuditRecord[], maxRecords: number): AuditRecord[]; ``` --- ### `mergeRecords` ```ts export function mergeRecords(current: AuditRecord[], next: AuditRecord[]): AuditRecord[]; ``` --- ### `createAuditStats` ```ts export function createAuditStats(records: AuditRecord[], filter?: AuditHistoryFilter): AuditHistoryStats; ``` --- ### `stringifyExportValue` ```ts export function stringifyExportValue(value: unknown): string; ``` --- ### `exportAuditRecords` ```ts export function exportAuditRecords(records: AuditRecord[], options: AuditHistoryExportOptions =; ``` --- # Audit History Panel URL: https://pro.rv-grid.com/api/audit-history-panel/ Source: src/content/docs/api/audit-history-panel.md ### `createAuditHistoryPanelDock` ```ts export function createAuditHistoryPanelDock( panel: HTMLElement, grid: HTMLRevoGridElement, placement: AuditHistoryPanelPlacement, ); ``` --- ### `defineAuditHistoryPanel` Renders the Audit History panel into `el` for the given RevoGrid element. The grid must have `AuditHistoryPlugin` installed. ```ts export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions =; ``` --- ### `AuditHistoryPanelDateFormatContext` ```ts export type AuditHistoryPanelDateFormatContext = { mode: 'time' | 'day' | 'filter'; record?: AuditRecord; }; ``` --- ### `AuditHistoryPanelEvents` ```ts export type AuditHistoryPanelEvents = { beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void; beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void; beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void; beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void; beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void; }; ``` --- ### `AuditHistoryPanelExportContext` ```ts export type AuditHistoryPanelExportContext = { format: AuditHistoryExportFormat; }; ``` --- ### `AuditHistoryPanelJumpContext` ```ts export type AuditHistoryPanelJumpContext = { record: AuditRecord; change: AuditChange; }; ``` --- ### `AuditHistoryPanelLabels` ```ts export type AuditHistoryPanelLabels = { title: string; liveStatus: string; recordsSummary: (count: number) => string; changesSummary: (count: number) => string; allTab: string; rowTab: string; cellTab: string; tableTab: string; selectedCell: (rowId: string, column: ColumnProp) => string; selectedRow: (rowId: string) => string; export: string; exportCsv: string; exportJson: string; exportCsvTitle: string; exportJsonTitle: string; close: string; openPanel: string; searchPlaceholder: string; searchAriaLabel: string; searchShortcut: string; userPlaceholder: string; userAriaLabel: string; columnAriaLabel: string; actionAriaLabel: string; dateFromAriaLabel: string; dateToAriaLabel: string; allColumns: string; allActions: string; clearFilters: string; addFilter: string; removeFilter: (label: string) => string; noRecords: string; noComparableRecord: string; scopedRecordCount: (visibleChanges: number, totalChanges: number) => string; changedBy: (changedAt: string, user: string) => string; rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string; cellValues: (oldValue: string, newValue: string) => string; rowSnapshotAvailable: string; rowRemoved: string; restoreCell: string; restoreRow: string; restoreTransaction: string; restoreSnapshot: string; jumpToCell: string; revertAll: string; moreActions: string; compare: string; compareTitle: string; compareBefore: string; compareAfter: string; closeCompare: string; compareEmpty: string; viewChanges: (count: number) => string; previousPage: string; nextPage: string; pageStatus: (currentPage: number, pageCount: number) => string; dayToday: string; dayYesterday: string; sourceLabels: Record; actions: Partial>; actionVerbs: Partial>; }; ``` --- ### `AuditHistoryPanelLabelsOptions` (Extended from `index.tsx`) ```ts export type AuditHistoryPanelLabelsOptions = Partial> & { actions?: Partial>; actionVerbs?: Partial>; sourceLabels?: Partial>; }; ``` --- ### `AuditHistoryPanelHandle` ```ts export type AuditHistoryPanelHandle = { destroy: () => void; setPlacement: (placement: AuditHistoryPanelPlacement) => void; refresh: () => Promise; }; ``` --- ### `AuditHistoryPanelOptions` ```ts export type AuditHistoryPanelOptions = { pageSize?: number; allowExport?: boolean; allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean); allowClose?: boolean; miniOnClose?: boolean; restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean); formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string; events?: AuditHistoryPanelEvents; placement?: AuditHistoryPanelPlacement; tooltips?: false | Partial>; labels?: AuditHistoryPanelLabelsOptions; }; ``` --- ### `AuditHistoryPanelRecordContext` ```ts export type AuditHistoryPanelRecordContext = { record: AuditRecord; changes: AuditChange[]; }; ``` --- ### `AuditHistoryPanelRestoreContext` ```ts export type AuditHistoryPanelRestoreContext = { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string; }; ``` --- ### `AuditHistoryPanelPlacement` ```ts export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none'; ``` --- ### `AuditHistoryPanelTooltipKey` ```ts export type AuditHistoryPanelTooltipKey = | 'totalRecords' | 'totalChanges' | 'selectedScope' | 'searchFilter' | 'userFilter' | 'columnFilter' | 'actionFilter' | 'dateFromFilter' | 'dateToFilter' | 'clearFilters'; ``` --- ### `resolveLabels` ```ts export function resolveLabels(labels?: AuditHistoryPanelLabelsOptions): AuditHistoryPanelLabels; ``` --- ### `getAuditActionLabel` ```ts export function getAuditActionLabel(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string; ``` --- ### `getAuditActionVerb` ```ts export function getAuditActionVerb(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string; ``` --- ### `actionTypes` ```ts actionTypes: AuditActionType[]; ``` --- ### `titleizeActionType` ```ts titleizeActionType: (value: string) => string; ``` --- ### `createInitialFilters` ```ts export function createInitialFilters(): AuditPanelFilters; ``` --- ### `stringifyValue` ```ts export function stringifyValue(value: unknown): string; ``` --- ### `formatTime` ```ts export function formatTime(value: string, mode: 'compact' | 'day' = 'compact'); ``` --- ### `formatPanelDate` ```ts export function formatPanelDate( value: string, options: AuditHistoryPanelOptions, context:; ``` --- ### `getUserLabel` ```ts export function getUserLabel(record: AuditRecord); ``` --- ### `getAvatarLabel` ```ts export function getAvatarLabel(record: AuditRecord); ``` --- ### `matchesFilters` ```ts export function matchesFilters(record: AuditRecord, filters: AuditPanelFilters); ``` --- ### `getScopedChanges` ```ts export function getScopedChanges(record: AuditRecord, tab: AuditPanelTab, selected: SelectedAuditTarget): AuditChange[]; ``` --- ### `getRecordSource` ```ts export function getRecordSource(type: AuditActionType, record?: AuditRecord): AuditHistorySource; ``` --- ### `getTargetLabel` ```ts export function getTargetLabel(record: AuditRecord, changes: AuditChange[], labels: AuditHistoryPanelLabels); ``` --- ### `getDetailLabel` ```ts export function getDetailLabel(record: AuditRecord, changes: AuditChange[]); ``` --- ### `isComparable` ```ts export function isComparable(record: AuditRecord, changes: AuditChange[]); ``` --- ### `toTimelineRecord` ```ts export function toTimelineRecord( record: AuditRecord, changes: AuditChange[], labels: AuditHistoryPanelLabels, options: AuditHistoryPanelOptions =; ``` --- ### `groupTimelineRecords` ```ts export function groupTimelineRecords(records: TimelineRecord[]): TimelineGroup[]; ``` --- ### `getPlugin` ```ts export async function getPlugin(grid: HTMLRevoGridElement): Promise; ``` --- ### `panelRefreshEvents` ```ts panelRefreshEvents: readonly ["auditrecord", "auditrecordsloaded", "auditrestore"]; ``` --- ### `canSelectRecord` ```ts export function canSelectRecord(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext); ``` --- ### `canOpenCompare` ```ts export function canOpenCompare(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext); ``` --- ### `canJumpToCell` ```ts export function canJumpToCell(options: AuditHistoryPanelOptions, context: AuditHistoryPanelJumpContext); ``` --- ### `canUseRestoreAction` ```ts export function canUseRestoreAction(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRestoreContext); ``` --- ### `canExportAuditHistory` ```ts export function canExportAuditHistory(options: AuditHistoryPanelOptions, format: AuditHistoryExportFormat); ``` --- ### `AuditHistoryTooltip` ```ts export function AuditHistoryTooltip(; ``` --- ### `AuditPanelTab` ```ts export type AuditPanelTab = 'cell' | 'row' | 'table'; ``` --- ### `SelectedAuditTarget` ```ts export type SelectedAuditTarget = { rowId?: string; column?: ColumnProp; columnLabel?: ColumnProp; rowIndex?: number; rowType?: DimensionRows; }; ``` --- ### `AuditPanelFilters` ```ts export type AuditPanelFilters = { search: string; user: string; column: string; actionType: '' | AuditActionType; dateFrom: string; dateTo: string; }; ``` --- ### `AuditHistoryPluginRuntime` ```ts export type AuditHistoryPluginRuntime = { getRecords: () => AuditRecord[]; getCellHistory: (rowId: string, column: ColumnProp) => AuditRecord[]; getRowHistory: (rowId: string) => AuditRecord[]; getStats: () => AuditHistoryStats; exportRecords: (options?: AuditHistoryExportOptions) => string; getRowIdentity: (row: DataType | undefined, rowIndex: number, rowType: DimensionRows) => string | undefined; canRestoreRecord: (context: { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string }) => boolean; restoreCell: (change: AuditChange) => boolean; restoreRow: (record: AuditRecord) => boolean; restoreTransaction: (transactionId: string) => boolean; restoreSnapshot?: (recordOrId: AuditRecord | string) => boolean; recordEvent?: (input: AuditRecordEventInput) => AuditRecord | undefined; createSnapshot?: (label: string, options?: AuditSnapshotOptions) => AuditRecord | undefined; }; ``` --- ### `VisibleAuditRecord` ```ts export type VisibleAuditRecord = { record: AuditRecord; changes: AuditChange[]; }; ``` --- ### `TimelineRecord` (Extended from `types.ts`) ```ts export type TimelineRecord = VisibleAuditRecord & { id: string; source: AuditHistorySource; verb: string; userLabel: string; avatarLabel: string; avatarIndex?: number; avatarColor?: string; targetLabel: string; detailLabel?: string; rangeLabel?: string; changedAtLabel: string; dayKey: string; dayLabel: string; daySubLabel?: string; accent: boolean; comparable: boolean; }; ``` --- ### `TimelineGroup` ```ts export type TimelineGroup = { key: string; label: string; subLabel?: string; records: TimelineRecord[]; }; ``` --- ### `AuditHistoryPanel` ```ts export function AuditHistoryPanel(; ``` --- ### `CompareDrawer` ```ts export function CompareDrawer(; ``` --- ### `Filters` ```ts export function Filters(; ``` --- ### `MiniAuditHistoryPanel` ```ts export function MiniAuditHistoryPanel(; ``` --- ### `Pagination` ```ts export function Pagination(; ``` --- ### `RecordsList` ```ts export function RecordsList(; ``` --- ### `Toolbar` ```ts export function Toolbar(; ``` --- # Autofill URL: https://pro.rv-grid.com/api/autofill/ Source: src/content/docs/api/autofill.md ### `createDefaultAutoFillStrategies` Creates the default AutoFill strategy list in evaluation order. Strategies are ordered from specific to general, with copy fallback last. The same list is used by AutoFillPlugin and AutoFillPreviewPlugin when no active AutoFillPlugin instance can be resolved. **@returns** Default AutoFill strategies. ```ts export function createDefaultAutoFillStrategies(): AutoFillStrategy[]; ``` --- ### `AutoFillPlugin` The `AutoFillPlugin` enhances RevoGrid with advanced autofill capabilities, allowing users to automatically fill cell ranges based on various sequence strategies. This plugin supports numeric, date, and text sequences, making it versatile for different data types and user needs. **Features**: - Utilizes multiple pre-defined strategies for numbers, dates, time values, text-number labels, weekday/month names, boolean cycles, and copy fallback. - Listens for the `beforeautofill` to trigger autofill actions based on user interactions. - Allows registration of additional custom autofill strategies. - Applies autofilled data directly to the grid, updating the data store efficiently. **Usage**: - Import `AutoFillPlugin` and add it to the RevoGrid's plugin list. - The grid will automatically handle range selection and autofilling based on the registered strategies. ### Example ```typescript import { AutoFillPlugin } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [AutoFillPlugin]; // Add autofill functionality to the grid ``` This plugin is ideal for enhancing user productivity in data entry tasks by providing intelligent filling of repetitive or sequential data patterns, commonly used in spreadsheets and data management applications. ```ts class AutoFillPlugin { /** * Registers a custom autofill strategy. * * Custom strategies are inserted before the copy fallback strategy so they can * override generic copy behavior. * * @param strategy Strategy function to register. */ registerStrategy(strategy: AutoFillStrategy); /** * Returns the active strategy list. * * The preview plugin reads this list from the active AutoFillPlugin instance so * preview and final commit use identical strategy order. * * @returns Registered strategy list in evaluation order. */ getStrategies(); } ``` --- ### `computeAutoFillData` ```ts export function computeAutoFillData(; ``` --- ### `mapAutoFillMatrixToData` ```ts export function mapAutoFillMatrixToData( matrix: any[][], target: RangeArea, providers: Pick, colType: DimensionCols, ): Record; ``` --- ### `AutoFillComputedData` ```ts export type AutoFillComputedData = { matrix: any[][]; data: Record; }; ``` --- ### `AutoFillComputeOptions` (Extended from `index.ts`) ```ts export type AutoFillComputeOptions = Pick & { type: DimensionRows; colType: DimensionCols; providers: PluginProviders; strategies: AutoFillStrategy[]; }; ``` --- ### `AutoFillPreviewPlugin` ```ts class AutoFillPreviewPlugin { destroy(); } ``` --- ### `AutoFillDirection` Direction in which the autofill drag expands from the selected source range. `both` is used when the source and target ranges cannot be reduced to a single horizontal or vertical direction. ```ts /** * Direction in which the autofill drag expands from the selected source range. * * `both` is used when the source and target ranges cannot be reduced to a single * horizontal or vertical direction. */ export type AutoFillDirection = 'up' | 'down' | 'left' | 'right' | 'both'; ``` --- ### `AutoFillStrategyContext` Rich context passed to autofill strategies. Existing custom strategies can keep using the legacy three-argument signature. New strategies can read this object when they need selection metadata, drag direction, dimension types, or plugin providers. ```ts /** * Rich context passed to autofill strategies. * * Existing custom strategies can keep using the legacy three-argument signature. * New strategies can read this object when they need selection metadata, drag * direction, dimension types, or plugin providers. */ export type AutoFillStrategyContext = { /** Original selected/source range used as the autofill seed. */ oldRange: RangeArea; /** Target range that will be previewed or committed. */ newRange: RangeArea; /** Direction in which the target range expands from the source range. */ direction: AutoFillDirection; /** Row dimension type for the active autofill operation. */ rowType: DimensionRows; /** Column dimension type for the active autofill operation. */ colType: DimensionCols; /** Plugin providers available to advanced strategy implementations. */ providers: PluginProviders; /** * Cross-viewport autofill metadata when a plugin maps one visual gesture to * several viewport-scoped target ranges. */ crossViewport?: { source: { rowType: DimensionRows; colType: DimensionCols; range: RangeArea; }; targets: { rowType: DimensionRows; colType: DimensionCols; range: RangeArea; }[]; }; }; ``` --- ### `AutoFillStrategy` Autofill strategy function. **@param** selectedData Matrix of selected source values, ordered row-major. * **@param** direction Direction in which the autofill drag expands. * **@param** newRange Target range that should be generated. * **@param** context Optional rich strategy context for advanced strategies. * **@returns** Generated target matrix, or `null` when the strategy does not support * the selected values. * * **Example:** ```typescript * ```typescript * const copyStrategy: AutoFillStrategy = selectedData => selectedData; * AutoFillPlugin.registerStrategy(copyStrategy); * ``` ``` ```ts /** * Autofill strategy function. * * @param selectedData Matrix of selected source values, ordered row-major. * @param direction Direction in which the autofill drag expands. * @param newRange Target range that should be generated. * @param context Optional rich strategy context for advanced strategies. * @returns Generated target matrix, or `null` when the strategy does not support * the selected values. * * @example * ```typescript * const copyStrategy: AutoFillStrategy = selectedData => selectedData; * AutoFillPlugin.registerStrategy(copyStrategy); * ``` */ export type AutoFillStrategy = ( selectedData: any[][], direction: AutoFillDirection, newRange: RangeArea, context?: AutoFillStrategyContext, ) => any[][] | null; ``` --- ### `getMatrixSize` Calculates the row and column count for a RevoGrid range. **@param** range Range to measure. * **@returns** Matrix size matching the inclusive range coordinates. ```ts export function getMatrixSize(range: RangeArea): MatrixSize; ``` --- ### `flattenMatrix` Flattens a row-major matrix into a one-dimensional sequence. **@param** data Matrix to flatten. * **@returns** Row-major sequence of values. ```ts export function flattenMatrix(data: T[][]): T[]; ``` --- ### `createMatrix` Creates a matrix for a target range. **@param** range Target range that defines output dimensions. * **@param** getValue Callback invoked for each cell position. * **@returns** Generated matrix in row-major order. ```ts export function createMatrix( range: RangeArea, getValue: (position: number, rowIndex: number, colIndex: number) => T, ): T[][]; ``` --- ### `repeatMatrix` Tiles source values across the target range while preserving source rows and columns. **@param** selectedData Source matrix to repeat. * **@param** targetRange Target range that defines output dimensions. * **@returns** Generated matrix containing repeated source values. ```ts export function repeatMatrix(selectedData: T[][], targetRange: RangeArea): T[][]; ``` --- ### `isNil` Checks whether a value is `null` or `undefined`. **@param** value Value to check. * **@returns** `true` when the value is missing. ```ts export function isNil(value: unknown): value is null | undefined; ``` --- ### `isNumericValue` Checks whether a value can be treated as a finite number. **@param** value Value to check. * **@returns** `true` for finite numbers and numeric strings. ```ts export function isNumericValue(value: unknown): value is number | string; ``` --- ### `parseTextNumber` Parses the first integer segment inside a text value. **@param** value Value to parse. * **@returns** Prefix, numeric value, suffix, and zero-padding width, or `null` * when the value does not contain an integer segment. ```ts export function parseTextNumber(value: unknown); ``` --- ### `formatTextNumber` Formats a text value with a numeric segment and preserved padding. **@param** prefix Text before the number. * **@param** value Number to format. * **@param** suffix Text after the number. * **@param** width Minimum digit width for zero padding. * **@returns** Formatted text-number value. ```ts export function formatTextNumber(prefix: string, value: number, suffix: string, width: number); ``` --- ### `parseDateValue` Parses a Date object or supported date string. **@param** value Value to parse. * **@returns** Parsed date and original format, or `null` for unsupported values. ```ts export function parseDateValue(value: unknown): ParsedDateValue | null; ``` --- ### `formatDateValue` Formats a date using the preserved input format. **@param** date Date to format. * **@param** format Output format captured from the selected source value. * **@returns** Date object copy or formatted date string. ```ts export function formatDateValue(date: Date, format: ParsedDateValue['format']); ``` --- ### `addUtcDays` Adds a number of days using UTC date math. **@param** date Source date. * **@param** days Number of days to add. * **@returns** New Date instance. ```ts export function addUtcDays(date: Date, days: number); ``` --- ### `addUtcMonths` Adds a number of months using UTC date math. **@param** date Source date. * **@param** months Number of months to add. * **@returns** New Date instance. ```ts export function addUtcMonths(date: Date, months: number); ``` --- ### `monthDiff` Calculates whole-month distance between two UTC dates. **@param** a Start date. * **@param** b End date. * **@returns** Number of calendar months from `a` to `b`. ```ts export function monthDiff(a: Date, b: Date); ``` --- ### `parseTimeValue` Parses a supported clock-time string. **@param** value Value to parse. * **@returns** Parsed seconds and original format, or `null` for unsupported values. ```ts export function parseTimeValue(value: unknown): ParsedTimeValue | null; ``` --- ### `formatTimeValue` Formats seconds since midnight as a clock-time string. **@param** totalSeconds Seconds since midnight. Values wrap around 24 hours. * **@param** format Output format captured from the selected source value. * **@returns** Formatted clock-time string. ```ts export function formatTimeValue(totalSeconds: number, format: ParsedTimeValue['format']); ``` --- ### `MatrixSize` Size of a two-dimensional autofill matrix. ```ts /** * Size of a two-dimensional autofill matrix. */ export type MatrixSize = { /** Number of rows in the target matrix. */ rows: number; /** Number of columns in the target matrix. */ cols: number; }; ``` --- ### `ParsedDateValue` Parsed date value with the visible input format needed for output. ```ts /** * Parsed date value with the visible input format needed for output. */ export type ParsedDateValue = { /** Parsed date normalized to a Date object. */ date: Date; /** Original visible input format. */ format: 'date-object' | 'yyyy-mm-dd' | 'yyyy/mm/dd' | 'mm/dd/yyyy'; }; ``` --- ### `ParsedTimeValue` Parsed clock-time value with the visible input format needed for output. ```ts /** * Parsed clock-time value with the visible input format needed for output. */ export type ParsedTimeValue = { /** Seconds elapsed since midnight. */ seconds: number; /** Original visible input format. */ format: 'hh:mm' | 'hh:mm:ss'; }; ``` --- ### `linearNumericSequenceStrategy` The `linearNumericSequenceStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin designed to generate numeric sequences with a constant interval. This strategy is ideal for filling grid cells with linear numeric progressions. **Features**: - Validates that all selected data elements are numeric, filtering out non-numeric sequences to prevent errors. - Computes the common difference between numbers in the sequence to establish a consistent interval. - Extends the numeric sequence across the specified range, maintaining the calculated interval throughout the grid. - Supports dynamic autofill in both horizontal and vertical directions. **Usage**: - Import `linearNumericSequenceStrategy` and register it with the AutoFillPlugin for use in RevoGrid to enable linear numeric autofill functionality. ### Example ```typescript import { linearNumericSequenceStrategy } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [AutoFillPlugin]; AutoFillPlugin.registerStrategy(linearNumericSequenceStrategy); // Register strategy ``` This strategy is particularly suited for applications requiring predictable numeric data entry, such as financial models, inventory lists, or any scenario where arithmetic progression is needed. ```ts linearNumericSequenceStrategy: AutoFillStrategy; ``` --- ### `dateSequenceStrategy` The `dateSequenceStrategy` is an autofill strategy designed for RevoGrid's AutoFillPlugin, which facilitates the automatic generation of consistent date sequences within grid cells. This strategy is used to extend a selected range of date values either as Date objects or ISO date strings. **Features**: - Validates that all selected data elements are either Date objects or valid ISO date strings, ensuring consistency in input types. - Computes the common difference between consecutive dates to determine the sequence's interval. - Generates a new date sequence extending the original, maintaining the identified interval across the target range specified. - Supports output as either Date objects or 'YYYY-MM-DD' formatted strings, depending on the input type. **Usage**: - Import `dateSequenceStrategy` and register it with the AutoFillPlugin for use in RevoGrid to enable date autofill functionality. ### Example ```typescript import { dateSequenceStrategy } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [AutoFillPlugin]; AutoFillPlugin.registerStrategy(dateSequenceStrategy); // Register date sequence strategy ``` This strategy is ideal for applications dealing with chronological data entry, where users benefit from automatic date progression, such as scheduling tools or timeline management systems. ```ts dateSequenceStrategy: AutoFillStrategy; ``` --- ### `dateSequenceWithIntervalStrategy` The `dateSequenceWithIntervalStrategy` is an advanced autofill strategy for RevoGrid's AutoFillPlugin that facilitates filling grids with date sequences that maintain a consistent interval. It extends upon basic date sequence strategies by using a defined interval between consecutive dates. **Features**: - Validates that input data consists of either Date objects or ISO date strings. - Ensures consistency in date intervals across the sequence, filtering out inconsistent sequences. - Generates a date sequence extending beyond the initial selection, maintaining the calculated interval throughout the target range. - Supports output in both Date objects and 'YYYY-MM-DD' strings, based on input type. **Usage**: - Import `dateSequenceWithIntervalStrategy` and register it with the AutoFillPlugin to enable interval-based date autofill in RevoGrid. ### Example ```typescript import { dateSequenceWithIntervalStrategy } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [AutoFillPlugin]; AutoFillPlugin.registerStrategy(dateSequenceWithIntervalStrategy); // Register strategy ``` This strategy is particularly useful for applications that require consistent scheduling or timeline management, where maintaining a regular interval between dates is essential for data accuracy and usability. ```ts dateSequenceWithIntervalStrategy: AutoFillStrategy; ``` --- ### `textSequenceStrategy` ```ts textSequenceStrategy: AutoFillStrategy; ``` --- ### `copyFallbackStrategy` The `copyFallbackStrategy` is the final default autofill strategy for RevoGrid's AutoFillPlugin. It repeats the selected source values when no sequence-specific strategy can safely infer a smarter progression. **Features**: - Repeats the selected matrix in row-major order across the target range. - Supports plain text, numbers, dates, booleans, objects, and mixed values. - Avoids surprising inferred sequences for ambiguous input. - Provides deterministic preview and final apply data for every non-empty selection. **Usage**: - Included by default as the last built-in strategy. - Custom strategies registered through AutoFillPlugin are inserted before this fallback so they can override copy behavior. ### Example ```typescript import { copyFallbackStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(copyFallbackStrategy); ``` ```ts copyFallbackStrategy: AutoFillStrategy; ``` --- ### `singleNumericStepStrategy` The `singleNumericStepStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that expands a single numeric source value into a simple incrementing numeric sequence. **Features**: - Activates only when exactly one selected value is numeric. - Uses a default step of `+1`. - Supports numeric strings and number values. - Falls through for mixed, empty, or multi-value selections so more specific strategies or the copy fallback can handle them. **Usage**: - Included by default before the copy fallback. - Useful for spreadsheet-like fills such as `1` -> `1, 2, 3`. ### Example ```typescript import { singleNumericStepStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(singleNumericStepStrategy); ``` ```ts singleNumericStepStrategy: AutoFillStrategy; ``` --- ### `textNumberSequenceStrategy` The `textNumberSequenceStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that continues strings containing an embedded number. **Features**: - Supports numeric suffixes, prefixes, and middle-number patterns. - Preserves leading zero width, for example `INV-099` -> `INV-100`. - Supports ordinal text such as `1st Floor`, `2nd Floor`, `3rd Floor`. - Requires a consistent prefix/suffix shape and numeric interval. - Falls through for ambiguous mixed text patterns. **Usage**: - Included by default before the legacy text suffix strategy. - Use it for identifiers, labels, invoice numbers, and numbered locations. ### Example ```typescript import { textNumberSequenceStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(textNumberSequenceStrategy); ``` ```ts textNumberSequenceStrategy: AutoFillStrategy; ``` --- ### `weekdayMonthNameStrategy` The `weekdayMonthNameStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that continues English weekday and month name sequences. **Features**: - Supports full names such as `Monday` and `January`. - Supports three-letter names such as `Mon` and `Jan`. - Preserves the selected full-name or short-name format. - Wraps around calendar boundaries, for example `Sat, Sun` -> `Mon`. - Falls through when weekday and month labels are mixed or ambiguous. **Usage**: - Included by default before generic text and copy strategies. - Use it for spreadsheet-like calendar labels in planning grids. ### Example ```typescript import { weekdayMonthNameStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(weekdayMonthNameStrategy); ``` ```ts weekdayMonthNameStrategy: AutoFillStrategy; ``` --- ### `dateUnitSequenceStrategy` The `dateUnitSequenceStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that continues date sequences using calendar-aware day and month units. **Features**: - Supports Date objects and common date strings: `YYYY-MM-DD`, `YYYY/MM/DD`, and `MM/DD/YYYY`. - Preserves the selected input format in generated values. - Infers daily, weekly, monthly, quarterly, and yearly progressions through consistent day or month steps. - Uses a one-day step for a single selected date. - Falls through for invalid, mixed-format, or irregular date selections. **Usage**: - Included by default before legacy date strategies. - Use it for scheduling, planning, and timeline grids where visible date formatting should remain stable. ### Example ```typescript import { dateUnitSequenceStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(dateUnitSequenceStrategy); ``` ```ts dateUnitSequenceStrategy: AutoFillStrategy; ``` --- ### `timeSequenceStrategy` The `timeSequenceStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that continues clock-time strings. **Features**: - Supports `HH:mm` and `HH:mm:ss` formats. - Preserves the selected time format in generated values. - Infers a consistent interval from multiple selected times. - Uses a one-hour step for a single selected time. - Wraps around 24-hour boundaries. **Usage**: - Included by default before generic text and copy strategies. - Use it for scheduling grids, time slots, and recurring interval data. ### Example ```typescript import { timeSequenceStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(timeSequenceStrategy); ``` ```ts timeSequenceStrategy: AutoFillStrategy; ``` --- ### `booleanCycleStrategy` The `booleanCycleStrategy` is an autofill strategy for RevoGrid's AutoFillPlugin that repeats boolean-like toggle values. **Features**: - Supports boolean values: `true` and `false`. - Supports common string pairs: `true/false`, `yes/no`, and `on/off`. - Preserves the original selected values while repeating the selected cycle. - Requires both string pair values to be present before string cycling. - Falls through for single string values so plain copy fallback can handle them. **Usage**: - Included by default before the generic copy fallback. - Use it for toggles, status columns, and simple alternating flags. ### Example ```typescript import { booleanCycleStrategy } from '@revolist/revogrid-pro'; AutoFillPlugin.registerStrategy(booleanCycleStrategy); ``` ```ts booleanCycleStrategy: AutoFillStrategy; ``` --- # Avatar URL: https://pro.rv-grid.com/api/avatar/ Source: src/content/docs/api/avatar.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { avatarIndexProp?: ColumnProp; avatarLabelProp?: ColumnProp; avatarProp?: ColumnProp; avatarSize?: number; rectangular?: boolean } ``` ## Plugin API ### `getAvatarCellBackground` ```ts export function getAvatarCellBackground(index = 0); ``` --- ### `getAvatarInitials` ```ts export function getAvatarInitials(value: unknown); ``` --- ### `getAvatarIndex` ```ts export function getAvatarIndex(value: unknown); ``` --- ### `avatarTemplate` ```ts export function avatarTemplate(h: any, options: AvatarTemplateOptions =; ``` --- ### `AvatarTemplateOptions` ```ts export type AvatarTemplateOptions = { ariaLabel?: string; className?: string; index?: number; initials?: string; key?: string | number; label?: string; rectangular?: boolean; size?: number; style?: Record; value?: unknown; }; ``` --- ### `AVATAR_CELL_BACKGROUNDS` ```ts AVATAR_CELL_BACKGROUNDS: readonly ["linear-gradient(135deg, #7dd3fc, #2563eb)", "linear-gradient(135deg, #facc15, #b45309)", "linear-gradient(135deg, #f9a8d4, #be185d)", "linear-gradient(135deg, #a5b4fc, #4f46e5)", "linear-gradient(135deg, #86efac, #15803d)", "linear-gradient(135deg, #fda4af, #be123c)", "linear-gradient(135deg, #c4b5fd, #7c3aed)", "linear-gradient(135deg, #fdba74, #c2410c)"]; ``` --- ### `avatarRenderer` ```ts avatarRenderer: CellTemplate> | undefined; ``` --- ### `avatarWithTextRenderer` ```ts avatarWithTextRenderer: CellTemplate> | undefined; ``` --- # Buttons URL: https://pro.rv-grid.com/api/buttons/ Source: src/content/docs/api/buttons.md ### `DeleteButton` ```ts DeleteButton: ({ onClick, locale, }: { onClick: (e: MouseEvent) => void; locale?: { title?: string | undefined; ariaLabel?: string | undefined; } | undefined; }) => any; ``` --- # Calendars URL: https://pro.rv-grid.com/api/calendars/ Source: src/content/docs/api/calendars.md ### `calendarKey` ```ts export function calendarKey(calendarId: CalendarId): string; ``` --- ### `normalizeIsoWorkingDays` ```ts export function normalizeIsoWorkingDays( days: readonly number[] | undefined, fallback: readonly number[] = DEFAULT_CALENDAR.workingDays, ): readonly number[]; ``` --- ### `jsWeekdayFromIsoWeekday` ```ts export function jsWeekdayFromIsoWeekday(day: number): number; ``` --- ### `jsWeekdaysFromIsoWeekdays` ```ts export function jsWeekdaysFromIsoWeekdays(days: readonly number[]): readonly number[]; ``` --- ### `normalizeCalendarHolidaySet` ```ts export function normalizeCalendarHolidaySet(holidays: readonly ISODateString[] | undefined): ReadonlySet; ``` --- ### `normalizeCalendarWorkingHours` ```ts export function normalizeCalendarWorkingHours( workingHours: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[] | undefined, options:; ``` --- ### `normalizeCalendar` ```ts export function normalizeCalendar( calendar: CalendarEntity, options:; ``` --- ### `createResolvedCalendarMap` ```ts export function createResolvedCalendarMap( calendars: readonly CalendarEntity[] | undefined, options:; ``` --- ### `createCalendarMap` ```ts export function createCalendarMap(calendars: readonly CalendarEntity[] | undefined): Map; ``` --- ### `resolveCalendarFromMap` ```ts export function resolveCalendarFromMap( target: CalendarLookupTarget, calendarsById: ReadonlyMap, primaryCalendar: CalendarEntity | null, ): CalendarEntity | null; ``` --- ### `resolveCalendarFromList` ```ts export function resolveCalendarFromList( context: CalendarListLookupContext, target?: CalendarLookupTarget, ): CalendarEntity | null; ``` --- ### `resolveCalendarItemFromMap` ```ts export function resolveCalendarItemFromMap( target: CalendarLookupTarget, items: ReadonlyMap, ): T | null; ``` --- ### `isCalendarHoliday` ```ts export function isCalendarHoliday( date: ISODateString | string, calendar: Pick | Pick | null | undefined, ): boolean; ``` --- ### `isCalendarWorkingWeekday` ```ts export function isCalendarWorkingWeekday( weekday: number, calendar: Pick | Pick | null | undefined, ): boolean; ``` --- ### `isWorkingCalendarDate` ```ts export function isWorkingCalendarDate( date: ISODateString | string, weekday: number, calendar: ( Pick | Pick | null | undefined ), ): boolean; ``` --- ### `isCalendarWorkingHour` ```ts export function isCalendarWorkingHour( date: ISODateString | string, calendar: CalendarEntity | ResolvedCalendarEntity | null | undefined, ): boolean; ``` --- ### `CalendarId` Unique identifier for a working calendar. ```ts /** Unique identifier for a working calendar. */ export type CalendarId = string | number; ``` --- ### `CalendarWorkingTimeRange` Time-of-day working window for calendar-aware schedulers. ```ts interface CalendarWorkingTimeRange { /** Start time in `HH:mm` or AM/PM format. */ readonly start: string; /** End time in `HH:mm` or AM/PM format. */ readonly end: string } ``` --- ### `CalendarResolvedWorkingTimeRange` Parsed working time window expressed as minutes from midnight. ```ts interface CalendarResolvedWorkingTimeRange { readonly startMinutes: number; readonly endMinutes: number } ``` --- ### `CalendarEntity` Defines the working schedule for tasks, events, and resources. Working days use ISO weekday numbers: 1 = Monday ... 7 = Sunday. ```ts interface CalendarEntity { /** Unique calendar identifier. */ readonly id: CalendarId; /** Display name, for example `"Standard"` or `"Night Shift"`. */ readonly name: string; /** IANA time zone, for example `"America/New_York"`. */ readonly timeZone: string; /** ISO weekday numbers that are working days. */ readonly workingDays: readonly number[]; /** Specific dates that are non-working regardless of weekday. */ readonly holidays: readonly ISODateString[]; /** Optional working time windows inside a working day. */ readonly workingHours?: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[]; /** Number of working hours per day, used for effort/capacity calculations. */ readonly hoursPerDay: number } ``` --- ### `DEFAULT_CALENDAR` Default Monday-Friday calendar shared by Gantt and Event Scheduler. ```ts DEFAULT_CALENDAR: { id: string; name: string; timeZone: string; workingDays: number[]; holidays: never[]; workingHours: { start: string; end: string; }; hoursPerDay: number; }; ``` --- ### `ResolvedCalendarEntity` Calendar with derived lookup-friendly fields used by schedulers. ```ts interface ResolvedCalendarEntity { readonly calendar: CalendarEntity; readonly workingDays: readonly number[]; readonly workingHours: readonly CalendarResolvedWorkingTimeRange[]; readonly holidays: ReadonlySet } ``` --- ### `CalendarListLookupContext` ```ts interface CalendarListLookupContext { readonly calendars: readonly CalendarEntity[]; readonly primaryCalendarId?: CalendarId } ``` --- ### `CalendarLookupTarget` ```ts export type CalendarLookupTarget = CalendarId | { readonly calendarId?: CalendarId } | undefined; ``` --- # Cell Flash URL: https://pro.rv-grid.com/api/cell-flash/ Source: src/content/docs/api/cell-flash.md ## Module Extensions ### `CellTemplateProp` (Extended from `@revolist/revogrid`) ```ts interface CellTemplateProp { /** * Previous value captured when the flash was requested. */ previousValue?: any; /** * Direction resolved by the flash plugin. */ flashDirection?: CellFlashDirection; /** * Active flash state for the rendered cell. */ flashState?: any } ``` --- ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { /** * Enables flash feedback for this column. * * Legacy `(value) => boolean` predicates are supported. Context-aware * predicates can inspect the previous value, row model, row type, and edit * event metadata, and can return a decision object to customize a flash. * * @example * ```ts * const columns = [ * { prop: 'name', name: 'Name', flash: value => Boolean(value) }, * { * prop: 'price', * name: 'Price', * flash: (value, context) => ({ * flash: value !== context.previousValue, * rowFlash: true, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * }), * }, * ]; * ``` */ flash?: CellFlashPredicate } ``` --- ### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`) ```ts interface HTMLRevoGridElement { /** * Cell flash plugin configuration. * * @example * ```ts * const grid = document.querySelector('revo-grid'); * grid.plugins = [CellFlashPlugin, EventManagerPlugin]; * grid.cellFlash = { * duration: 1200, * mode: 'cell-and-row', * queue: 'merge', * }; * ``` */ cellFlash?: CellFlashConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * Additional data property for cell flash configuration. * * Useful for framework wrappers where all plugin options are passed through * one memoized or computed `additionalData` object. * * @example * ```tsx * const additionalData = useMemo(() => ({ * cellFlash: { * duration: 900, * mode: 'cell-and-row', * }, * }), []); * ``` */ cellFlash?: CellFlashConfig } ``` ## Plugin API ### `CellFlashPlugin` Highlights recently changed cells and rows. Existing usage with `flash: (value) => true`, `FLASH_CELL_EVENT`, and `cellFlashArrowTemplate()` remains supported. Configure richer behavior through `grid.cellFlash` or call the instance methods for manual dashboard updates. **Example:** ```typescript * import { CellFlashPlugin, EventManagerPlugin } from '@revolist/revogrid-pro'; * * const grid = document.querySelector('revo-grid'); * grid.plugins = [CellFlashPlugin, EventManagerPlugin]; * grid.columns = [ * { prop: 'name', name: 'Name' }, * { prop: 'price', name: 'Price', flash: value => value !== undefined }, * ]; * * ``` **Example:** ```typescript * grid.cellFlash = { * mode: 'cell-and-row', * queue: 'merge', * duration: 900, * rowDuration: 1400, * aria: { enabled: true }, * }; * * ``` **Example:** ```typescript * grid.columns = [ * { * prop: 'inventory', * name: 'Inventory', * flash: (value, context) => ({ * flash: value !== context.previousValue, * rowFlash: Number(value) === 0, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * ariaLabel: `Inventory changed to ${value}`, * }), * }, * ]; ``` #### Dependencies - **Event integration** `EventManagerPlugin`: Integrates with EventManagerPlugin edit events for automatic edit flashes. - **Event integration** `HistoryPlugin`: Integrates with HistoryPlugin undo and redo replay edit events when present. - **Config integration** `additionalData.cellFlash`: Reads cell flash configuration from additionalData.cellFlash. ```ts class CellFlashPlugin { /** * Flashes one cell manually. * * Use this for live-feed updates or external actions that do not come through * RevoGrid edit events. The method keeps the old event-driven behavior intact * and only applies an additional manual flash state. * * @param target Cell coordinates and optional flash metadata. * * @example * ```ts * const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin); * plugin?.flashCell({ * rowIndex: 3, * prop: 'price', * previousValue: 42, * value: 45, * direction: 'up', * duration: 800, * }); * ``` */ flashCell(target: CellFlashCellTarget); /** * Flashes a batch of changed cells using the same detail shape as the * `flashcell` event. * * Column `flash` predicates are evaluated for each changed prop. Queue and * mode options override the configured defaults for this call only. * * @param detail Batch detail containing changed values, row type, and optional previous values. * @param options Optional queue, mode, and duration overrides for this batch. * * @example * ```ts * plugin.flashCells( * { * type: 'rgRow', * data: { 0: { price: 45, status: 'Updated' } }, * previousData: { 0: { price: 42, status: 'Draft' } }, * eventTypes: ['live-feed'], * }, * { queue: 'merge', mode: 'cell-and-row' }, * ); * ``` * * @example * ```ts * grid.dispatchEvent(new CustomEvent('flashcell', { * detail: { * type: 'rgRow', * data: { 1: { price: 50 } }, * previousData: { 1: { price: 48 } }, * }, * })); * ``` */ flashCells(detail: FlashBatchDetail, options?: CellFlashApplyOptions); /** * Flashes one or more rows manually. * * Row flashes are rendered through `beforerowrender`, so virtualized rows keep * the correct visual state when they re-enter the viewport before expiry. * * @param targets One row target or an array of row targets. * @param options Optional queue, mode, and row duration overrides. * * @example * ```ts * plugin.flashRows( * [ * { rowIndex: 0, direction: 'changed' }, * { rowIndex: 4, direction: 'down', rowClassName: 'risk-row-flash' }, * ], * { queue: 'merge', rowDuration: 1200 }, * ); * ``` */ flashRows(targets: CellFlashRowTarget | CellFlashRowTarget[], options?: CellFlashApplyOptions); /** * Clears active flash state. * * Without a target, all active cell and row flashes are cleared. Passing a * row target clears the row plus all active cell flashes in that row. Passing * both `rowIndex` and `prop` clears one cell. * * @param target Optional cell or row target to clear. * * @example * ```ts * plugin.clearFlash(); // clear all flashes * plugin.clearFlash({ rowIndex: 2 }); // clear row 2 and its cell flashes * plugin.clearFlash({ rowIndex: 2, prop: 'price' }); // clear one cell * ``` */ clearFlash(target?: Partial); destroy(); } ``` --- ### `cellFlashArrowTemplate` ```ts cellFlashArrowTemplate: (templateOrOptions?: CellTemplate> | CellFlashArrowTemplateOptions | undefined, templateOptions?: CellFlashArrowTemplateOptions | undefined) => CellTemplate> | undefined; ``` --- ### `resolveCellFlashConfig` Resolves partial public configuration into the plugin's internal defaults. **Example:** ```typescript * ```ts * const config = resolveCellFlashConfig({ duration: 1500, queue: 'merge' }); * console.log(config.mode); // "cell" * ``` ``` ```ts export function resolveCellFlashConfig( config?: CellFlashConfig, ): ResolvedCellFlashConfig; ``` --- ### `CellFlashDirection` Direction metadata attached to an active cell or row flash. The default resolver returns `up` or `down` for numeric changes, `changed` for non-numeric value changes, and `none` when no previous value is available. **Example:** ```typescript * ```ts * const columns = [ * { * prop: 'price', * name: 'Price', * flash: (value, context) => ({ * flash: true, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * }), * }, * ]; * ``` ``` ```ts /** * Direction metadata attached to an active cell or row flash. * * The default resolver returns `up` or `down` for numeric changes, * `changed` for non-numeric value changes, and `none` when no previous value * is available. * * @example * ```ts * const columns = [ * { * prop: 'price', * name: 'Price', * flash: (value, context) => ({ * flash: true, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * }), * }, * ]; * ``` */ export type CellFlashDirection = 'up' | 'down' | 'changed' | 'none'; ``` --- ### `CellFlashMode` Defines whether flash requests affect cells, rows, or both. **Example:** ```typescript * ```ts * grid.cellFlash = { * mode: 'cell-and-row', * rowDuration: 1400, * }; * ``` ``` ```ts /** * Defines whether flash requests affect cells, rows, or both. * * @example * ```ts * grid.cellFlash = { * mode: 'cell-and-row', * rowDuration: 1400, * }; * ``` */ export type CellFlashMode = 'cell' | 'row' | 'cell-and-row'; ``` --- ### `CellFlashQueueMode` Controls how new flash requests interact with active flashes. `replace` preserves the legacy behavior by clearing existing flashes before applying a new batch. `merge` keeps existing flashes active until their own timers expire. **Example:** ```typescript * ```ts * grid.cellFlash = { * queue: 'merge', * maxActive: 300, * }; * ``` ``` ```ts /** * Controls how new flash requests interact with active flashes. * * `replace` preserves the legacy behavior by clearing existing flashes before * applying a new batch. `merge` keeps existing flashes active until their own * timers expire. * * @example * ```ts * grid.cellFlash = { * queue: 'merge', * maxActive: 300, * }; * ``` */ export type CellFlashQueueMode = 'replace' | 'merge'; ``` --- ### `CellFlashContext` Runtime information passed to context-aware column flash predicates and direction resolvers. **Example:** ```typescript * ```ts * const columns = [ * { * prop: 'status', * name: 'Status', * flash: (value, context) => { * return context.previousValue !== value && context.eventTypes?.includes('edit'); * }, * }, * ]; * ``` ``` ```ts interface CellFlashContext { /** New value from the edit/manual flash batch. */ value: unknown; /** Previous value captured from the event detail or current row model. */ previousValue: unknown; /** Row index in the target row type. */ rowIndex: number; /** Column property for the changed cell. */ prop: ColumnProp; /** Column definition for the changed cell. */ column: ColumnRegular; /** Current row model, when available from the data provider. */ model?: DataType; /** RevoGrid row bucket, for example `rgRow`, `rowPinStart`, or `rowPinEnd`. */ type: DimensionRows; /** Edit/history event type metadata when emitted by EventManagerPlugin. */ eventTypes?: string[]; /** Original flash/edit event detail for advanced consumers. */ detail?: Partial } ``` --- ### `CellFlashDecision` Per-change decision returned from a context-aware column `flash` predicate. Returning `true` is equivalent to `{ flash: true }`. Return a decision object to customize duration, direction, classes, row flashing, or accessibility text for a specific value change. **Example:** ```typescript * ```ts * const columns = [ * { * prop: 'inventory', * name: 'Inventory', * flash: (value, context) => ({ * flash: true, * rowFlash: Number(value) === 0, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * className: Number(value) === 0 ? 'cell-flash-danger' : undefined, * ariaLabel: `Inventory changed to ${value}`, * }), * }, * ]; * ``` ``` ```ts interface CellFlashDecision { /** Whether the changed cell should flash. Defaults to true for decision objects. */ flash?: boolean; /** Whether the containing row should flash for this change. */ rowFlash?: boolean; /** Cell flash duration in milliseconds for this change. */ duration?: number; /** Row flash duration in milliseconds for this change. */ rowDuration?: number; /** Direction metadata for templates, attributes, and CSS selectors. */ direction?: CellFlashDirection; /** Additional class applied to the flashing cell. */ className?: string; /** Additional class applied to the flashing row. */ rowClassName?: string; /** Message announced through the plugin aria-live region when enabled. */ ariaLabel?: string } ``` --- ### `CellFlashPredicate` Column flash configuration. Legacy predicates that only accept `(value) => boolean` still work. New predicates may accept the second `CellFlashContext` argument and return a `CellFlashDecision`. **Example:** ```typescript * ```ts * const columns = [ * { prop: 'name', name: 'Name', flash: true }, * { * prop: 'price', * name: 'Price', * flash: (value, context) => ({ * flash: value !== context.previousValue, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * }), * }, * ]; * ``` ``` ```ts /** * Column flash configuration. * * Legacy predicates that only accept `(value) = > boolean` still work. New * predicates may accept the second `CellFlashContext` argument and return a * `CellFlashDecision`. * * @example * ```ts * const columns = [ * { prop: 'name', name: 'Name', flash: true }, * { * prop: 'price', * name: 'Price', * flash: (value, context) => ({ * flash: value !== context.previousValue, * direction: Number(value) > Number(context.previousValue) ? 'up' : 'down', * }), * }, * ]; * ``` */ export type CellFlashPredicate = | boolean | ((value: unknown, context: CellFlashContext) => boolean | CellFlashDecision); ``` --- ### `CellFlashAriaConfig` Accessibility announcement configuration for cell and row flashes. **Example:** ```typescript * ```ts * grid.cellFlash = { * aria: { * enabled: true, * label: state => `Row ${state.rowIndex + 1} changed`, * }, * }; * ``` ``` ```ts interface CellFlashAriaConfig { /** Enables aria-live announcements for flash requests. */ enabled?: boolean; /** Live-region politeness. Defaults to `polite`. */ live?: 'off' | 'polite' | 'assertive'; /** Controls the `aria-atomic` attribute on the live region. Defaults to true. */ atomic?: boolean; /** CSS class applied to the generated live region. */ liveRegionClassName?: string; /** Static label or callback that returns the announcement text. */ label?: string | ((state: CellFlashState) => string) } ``` --- ### `CellFlashLabels` User-facing labels used by the plugin. These labels are used for default accessibility announcements. A decision or manual target `ariaLabel` still has highest priority, followed by `aria.label`, then these per-kind labels. **Example:** ```typescript * ```ts * grid.cellFlash = { * aria: true, * labels: { * cellUpdated: state => `Updated ${String(state.prop)} on row ${state.rowIndex + 1}`, * rowUpdated: state => `Updated row ${state.rowIndex + 1}`, * }, * }; * ``` ``` ```ts interface CellFlashLabels { /** Default announcement for cell flashes. */ cellUpdated?: string | ((state: CellFlashState) => string); /** Default announcement for row flashes. */ rowUpdated?: string | ((state: CellFlashState) => string) } ``` --- ### `CellFlashConfig` Public plugin configuration accepted by `grid.cellFlash` and `additionalData.cellFlash`. Defaults preserve the original plugin behavior: flashing is enabled, cell-only, lasts 1000ms, replaces active flashes on each event, and clears on source reset. **Example:** ```typescript * ```ts * grid.plugins = [CellFlashPlugin, EventManagerPlugin]; * grid.cellFlash = { * duration: 1200, * mode: 'cell-and-row', * queue: 'merge', * rowClassName: 'price-row-flash', * aria: { enabled: true }, * labels: { * cellUpdated: state => `Price cell changed on row ${state.rowIndex + 1}`, * rowUpdated: state => `Market row ${state.rowIndex + 1} changed`, * }, * directionResolver: ({ value, previousValue }) => { * if (Number(value) > Number(previousValue)) return 'up'; * if (Number(value) < Number(previousValue)) return 'down'; * return 'changed'; * }, * }; * ``` ``` ```ts interface CellFlashConfig { /** Enables or disables new flash requests. */ enabled?: boolean; /** Default cell flash duration in milliseconds. */ duration?: number; /** Default row flash duration in milliseconds. */ rowDuration?: number; /** Default flash target mode. */ mode?: CellFlashMode; /** Default active flash queue behavior. */ queue?: CellFlashQueueMode; /** Maximum number of active cell and row flashes retained at once. */ maxActive?: number; /** Clears active flash state when the grid source changes. */ clearOnSourceChange?: boolean; /** Disables animation when the user prefers reduced motion. */ respectReducedMotion?: boolean; /** Enables or configures aria-live announcements. */ aria?: boolean | CellFlashAriaConfig; /** User-facing labels used by default plugin announcements. */ labels?: CellFlashLabels; /** Base class applied to flashing cells. */ className?: string; /** Base class applied to flashing rows. */ rowClassName?: string; /** Optional custom direction resolver used before the default resolver. */ directionResolver?: (context: CellFlashContext) => CellFlashDirection } ``` --- ### `ResolvedCellFlashConfig` ```ts interface ResolvedCellFlashConfig { enabled: boolean; duration: number; rowDuration: number; mode: CellFlashMode; queue: CellFlashQueueMode; maxActive: number; clearOnSourceChange: boolean; respectReducedMotion: boolean; aria: Required> & Pick; labels: Required; className: string; rowClassName: string; directionResolver?: (context: CellFlashContext) => CellFlashDirection } ``` --- ### `CellFlashState` ```ts interface CellFlashState { rowIndex: number; prop?: ColumnProp; type: DimensionRows; value?: unknown; previousValue?: unknown; direction: CellFlashDirection; className?: string; rowClassName?: string; ariaLabel?: string; startedAt: number; duration: number } ``` --- ### `CellFlashCellTarget` Target accepted by `CellFlashPlugin.flashCell()`. **Example:** ```typescript * ```ts * const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin); * plugin?.flashCell({ * rowIndex: 0, * prop: 'price', * previousValue: 42, * value: 47, * direction: 'up', * }); * ``` ``` ```ts interface CellFlashCellTarget { rowIndex: number; prop: ColumnProp; type?: DimensionRows; value?: unknown; previousValue?: unknown; duration?: number; direction?: CellFlashDirection; className?: string; ariaLabel?: string } ``` --- ### `CellFlashRowTarget` Target accepted by `CellFlashPlugin.flashRows()`. **Example:** ```typescript * ```ts * const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin); * plugin?.flashRows([ * { rowIndex: 2, direction: 'changed', rowClassName: 'row-flash-warning' }, * { rowIndex: 5, direction: 'down' }, * ]); * ``` ``` ```ts interface CellFlashRowTarget { rowIndex: number; type?: DimensionRows; duration?: number; direction?: CellFlashDirection; rowClassName?: string; ariaLabel?: string } ``` --- ### `CellFlashApplyOptions` Per-call options for manual and event-driven flash batches. **Example:** ```typescript * ```ts * plugin.flashCells( * { type: 'rgRow', data: { 0: { price: 12 } }, previousData: { 0: { price: 10 } } }, * { queue: 'merge', mode: 'cell-and-row', duration: 800 }, * ); * ``` ``` ```ts interface CellFlashApplyOptions { queue?: CellFlashQueueMode; mode?: CellFlashMode; duration?: number; rowDuration?: number } ``` --- ### `CellFlashTemplatePropExtension` ```ts interface CellFlashTemplatePropExtension { previousValue?: any; flashDirection?: CellFlashDirection; flashState?: any } ``` --- ### `CellFlashArrowTemplateOptions` Options for `cellFlashArrowTemplate()`. Defaults preserve the original helper output: wrapper class `cell-flash`, arrow class `cell-flash-arrow`, `up`/`down`/`changed` direction classes, and `↑` / `↓` symbols. **Example:** ```typescript * ```ts * cellTemplate: cellFlashArrowTemplate({ * symbols: { up: '+', down: '-', changed: '~' }, * className: 'market-flash', * arrowClassName: 'market-flash-icon', * directionClassNames: { up: 'positive', down: 'negative' }, * }) * ``` ``` ```ts interface CellFlashArrowTemplateOptions { /** Direction symbols rendered before the cell content. */ symbols?: Partial>; /** Wrapper class applied around the arrow and cell content. */ className?: string; /** Class applied to the symbol element. */ arrowClassName?: string; /** Direction-specific wrapper classes. */ directionClassNames?: Partial> } ``` --- ### `CellFlashTemplateParams` (Extended from `index.ts`) ```ts export type CellFlashTemplateParams = CellTemplateProp & CellFlashTemplatePropExtension; ``` --- # Cell Focus URL: https://pro.rv-grid.com/api/cell-focus/ Source: src/content/docs/api/cell-focus.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { /** * The focus function for the column. */ focus?: (detail: BeforeSaveDataDetails) => boolean } ``` ## Plugin API ### `CellColumnFocusVerifyPlugin` The CellColumnFocusVerifyPlugin is a RevoGrid plugin that enables custom focus verification logic for cells based on their column configurations. This plugin intercepts the cell focus initialization event to apply additional validation rules defined at the column level, ensuring that only cells meeting specific criteria can receive focus. **Key Features**: - Listens to the `beforecellfocusinit` event, allowing for pre-focus validation. - Utilizes column-level focus functions to determine whether a cell can be focused. - Prevents default focus behavior if the column's focus condition is not satisfied, enabling dynamic user interaction control based on custom logic. **Usage**: - Integrate this plugin into RevoGrid by adding it to the grid's plugin array. - Define custom focus validation logic within the column definition using the `focus` property, which should be a function that returns a boolean based on the event detail. ### Example ```ts import { CellColumnFocusVerifyPlugin } from './cell-column-focus-verify'; const grid = document.createElement('revo-grid'); grid.plugins = [CellColumnFocusVerifyPlugin]; grid.columns = [ { prop: 'num', name: 'Numeric Column', focus: (detail) => detail.value > 10, // Custom focus validation logic }, ]; ``` ```ts class CellColumnFocusVerifyPlugin {} ``` --- # Cell Link URL: https://pro.rv-grid.com/api/cell-link/ Source: src/content/docs/api/cell-link.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { link?: LinkConfig | any } ``` ## Plugin API ### `linkRenderer` The `linkRenderer` is a custom cell editor for RevoGrid that provides a link input to edit URL values directly within the grid cells. **Features**: - Renders a link element for cells, allowing users to click and navigate to URLs - Supports different link configurations (target, styling, etc.) - Extracts protocols from displayed text while preserving the full URL for navigation - Automatically dispatches a `celledit` event upon change, updating the grid's data model **Usage**: - Import `linkRenderer` and assign it to the `cellTemplate` property of a column in the RevoGrid. - Configure link behavior using the `linkConfig` property. - Ideal for columns that contain URLs or links to external resources. ### Example ```typescript import { linkRenderer } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'website', name: 'Website', cellTemplate: linkRenderer, link: { target: '_blank', showProtocol: false, className: 'custom-link', }, }, ]; ``` **Event Handling**: - The `linkRenderer` dispatches a `celledit` event whenever the link value changes. - The event detail contains: - `rgCol`: The column index of the edited cell. - `rgRow`: The row index of the edited cell. - `type`: The type of the cell. - `prop`: The property of the cell being edited. - `val`: The new URL value. - `preventFocus`: A flag to control grid focus behavior (default: `false`). This editor is particularly useful in scenarios where users need to view and interact with URLs directly from the grid, providing a quick and user-friendly interface for link data. ```ts linkRenderer: CellTemplate> | undefined; ``` --- # Cell Merge URL: https://pro.rv-grid.com/api/cell-merge/ Source: src/content/docs/api/cell-merge.md ## Module Extensions ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * Additional data property for cell merge configuration. * * @deprecated Use `grid.cellMerge` instead. * @example * grid.additionalData = { * cellMerge: [ * { * row: 0, * column: 0, * rowSpan: 2, * colSpan: 2, * rowType: 'rgRow', * colType: 'rgCol', * } * ] * } */ cellMerge?: MergeData[] } ``` --- ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { cellMerge?: MergeData[]; 'cell-merge'?: MergeData[] } ``` ## Plugin API ### `CellMergePlugin` The `CellMergePlugin` is a RevoGrid plugin designed to manage and render merged cells within a grid. It allows users to visually and functionally combine multiple adjacent cells into a single larger cell, offering a more cohesive data presentation within the grid interface. **Key Features**: - Merging Logic: Extends the size of specified cells and hides cells covered by the merged area. - Event Management: Overrides grid events and focus behavior to accommodate hidden cells due to merging. - Dynamic Updates: Listens for data changes and adjusts merged configurations accordingly. - UI Rendering: Adjusts cell sizes and parameters for merged cells during rendering. **Usage**: - This plugin should be included in the grid's plugin array to enable cell merging functionality. - Configure the merge data through the grid's `cellMerge` property to define which cells should be merged. ### Example ```typescript import { CellMergePlugin } from './cell-merge-plugin'; const grid = document.createElement('revo-grid'); grid.plugins = [CellMergePlugin]; // Add merge plugin grid.cellMerge = [ { row: 1, column: 1, rowSpan: 2, colSpan: 2 }, ]; ``` Events: - Listens to a variety of events like `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, etc., to handle merge configurations and rendering. #### Dependencies - **Event integration** `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, `EDIT_RENDER_EVENT`: Integrates with range, focus, and edit overlay events to preserve merged-cell behavior. - **Config integration** `grid.cellMerge`: Reads merge configuration from the grid cellMerge DOM property. ```ts class CellMergePlugin { destroy(): void; } ``` --- ### `MergeData` Represents a range of cells that should be merged. The coordinates are relative to the top-left cell of the range. ```ts /** * Represents a range of cells that should be merged. * The coordinates are relative to the top-left cell of the range. */ export type MergeData = { /** * The row index of the top-left cell of the range. */ row: number; /** * The column index of the top-left cell of the range. */ column: number; /** * The number of rows to span. If not specified, the range will span only one row. */ rowSpan?: number; /** * The type of the row dimension. If not specified, it will default to 'rgRow'. */ rowType?: DimensionRows; /** * The number of columns to span. If not specified, the range will span only one column. */ colSpan?: number; /** * The type of the column dimension. If not specified, it will default to 'rgCol'. */ colType?: DimensionCols; }; ``` --- ### `MergeRowCache` ```ts export type MergeRowCache = Partial>; ``` --- ### `MergeCache` ```ts export type MergeCache = Partial>; ``` --- ### `getCellMergeExcelRanges` ```ts export function getCellMergeExcelRanges( context: CellMergeExcelExportContext =; ``` --- ### `getCellMergeExcelProviderOptions` ```ts export function getCellMergeExcelProviderOptions( context: CellMergeExcelExportContext =; ``` --- ### `applyCellMergeWriteExcelFileSpans` ```ts export function applyCellMergeWriteExcelFileSpans(; ``` --- ### `CellMergeExcelSegment` ```ts export type CellMergeExcelSegment = { /** * Segment start in the export matrix before the generated header offset is * applied. Omit when segment lengths are supplied in grid order. */ start?: number; /** Number of rows or columns in the segment. */ length?: number; }; ``` --- ### `CellMergeExcelSegments` ```ts export type CellMergeExcelSegments = Partial>; ``` --- ### `CellMergeExcelExportContext` ```ts export type CellMergeExcelExportContext = { /** Explicit merge data. Wins over merge data read from `revogrid`. */ cellMerge?: MergeData[]; /** Optional grid element source for `cellMerge` or legacy `cell-merge`. */ revogrid?: Pick & Partial>; /** * Generated header rows before data rows. ExportExcel currently generates one * header row, so this defaults to 1. */ headerRows?: number; /** * Row segment lengths or starts in export data-row order. Starts are counted * after generated header rows; the helper adds `headerRows`. */ rowSegments?: CellMergeExcelSegments; /** * Column segment lengths or starts in export column order. */ columnSegments?: CellMergeExcelSegments; }; ``` --- ### `CellMergeExcelRange` ```ts export type CellMergeExcelRange = { /** Zero-based start row in the provider matrix, including generated headers. */ startRow: number; /** Zero-based start column in the provider matrix. */ startColumn: number; /** Zero-based end row in the provider matrix, including generated headers. */ endRow: number; /** Zero-based end column in the provider matrix. */ endColumn: number; rowSpan: number; columnSpan: number; rowType: DimensionRows; colType: DimensionCols; source: MergeData; }; ``` --- ### `CellMergeExcelProviderOptions` ```ts export type CellMergeExcelProviderOptions = { /** Generic provider merge ranges in zero-based export matrix coordinates. */ mergeRanges: CellMergeExcelRange[]; }; ``` --- ### `CellMergeWriteExcelFileCell` ```ts export type CellMergeWriteExcelFileCell = Record | null | undefined; ``` --- ### `ApplyCellMergeWriteExcelFileSpansOptions` (Extended from `index.ts`) ```ts export type ApplyCellMergeWriteExcelFileSpansOptions = CellMergeExcelExportContext & { cellRows: TCell[][]; /** Mutate the supplied `cellRows` matrix instead of returning a patched copy. */ inPlace?: boolean; }; ``` --- ### `MergeCacheService` Service responsible for managing the cell merge cache. This service handles all operations related to storing, retrieving, and manipulating the cache of merged cells, separating these concerns from the main plugin logic. ```ts class MergeCacheService { /** * Clears the entire merge cache */ public clearCache(): void; /** * Gets the current merge cache */ public getCache(): MergeCache; /** * Check cache for merged cells and return spans */ public getSpans(types: AllDimensionType, startX: number, startY: number); /** * High level mapping start */ public setMerge(data: MergeData[] | undefined); } ``` --- # Cell Validate URL: https://pro.rv-grid.com/api/cell-validate/ Source: src/content/docs/api/cell-validate.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) Adds validation properties to ColumnRegular. ```ts interface ColumnRegular { /** * Optional property to enable soft validation, when set to true, the cell will be saved if it is invalid. */ softValidation?: boolean; /** * Optional function to provide validation message text for invalid cells. * @param cellValue - The value of the cell. * @param model - The full row model. During CellValidatePlugin edit validation, * this includes the pending row patch for the edit being validated. * @param column - The column definition that owns the validator. * @returns The validation message for the cell. */ validationTooltip?(cellValue?: any, model?: any, column?: ColumnRegular): any; /** * Optional severity used by validation renderers for invalid cell styling. * @param cellValue - The value of the cell. * @param model - The full row model for the cell. * @param column - The column definition that owns the validator. * Defaults to `error`. */ validationSeverity?(cellValue?: any, model?: any, column?: ColumnRegular): ValidationSeverity; /** * Optional properties applied by validationRenderer when a cell is invalid. */ invalidProperties?: ColumnRegular['cellProperties']; /** * Optional function to validate the cell value. * @param cellValue - The value of the cell. * @param model - The full row model containing all column values. During * CellValidatePlugin edit validation, this includes the pending row patch. * @returns `true` if the value is valid, otherwise `false`. */ validate?(cellValue?: any, model?: any): boolean } ``` ## Plugin API ### `CellValidatePlugin` The `CellValidatePlugin` is a RevoGrid plugin designed to enforce cell validation within the grid, ensuring data integrity by preventing invalid data entries from being saved. It integrates seamlessly with RevoGrid's event system and requires the `EventManagerPlugin` for full functionality. **Features**: - Listens to grid edit events (`ON_EDIT_EVENT`) to validate cell data upon modification. If a cell is deemed invalid based on the column's validation logic, it prevents the data from being saved and refreshes the grid. The validator receives a candidate row model that includes the pending row patch, so cross-column validation works for single-cell edits and pasted row batches. - Adds visual indicators to invalid cells by tagging them during the `BEFORE_CELL_RENDER_EVENT`, providing immediate feedback to users about validation errors. - Integrates custom validation logic through column definitions, supporting both strict and soft validation modes. - Utilizes `validationRenderer` for rendering cells with validation feedback, allowing customization of how invalid cells are displayed using tooltips or other UI elements. **Usage**: - Implement this plugin in a RevoGrid instance to enable cell validation logic and improve data quality by preventing erroneous entries. ### Example ```ts import { CellValidatePlugin } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [CellValidatePlugin]; ``` #### Dependencies - **Required** `EventManagerPlugin`: Requires EventManagerPlugin to receive edit events for validation. ```ts class CellValidatePlugin {} ``` --- ### `isValidationRendererFunction` ```ts export function isValidationRendererFunction( fn: T | undefined, ): fn is ValidationRendererFunction; ``` --- ### `invalidCellProps` ```ts export function invalidCellProps(invalid = false); ``` --- ### `ValidationSeverity` Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type. ```ts /** Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type. */ export type ValidationSeverity = 'error' | 'warning' | 'info'; ``` --- ### `ValidationIndicatorPlacement` Position of the invalid-cell indicator rendered by `validationRenderer`. ```ts /** Position of the invalid-cell indicator rendered by `validationRenderer`. */ export type ValidationIndicatorPlacement = 'corner' | 'start' | 'end' | 'none'; ``` --- ### `ValidationMessagePlacement` Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden. ```ts /** Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden. */ export type ValidationMessagePlacement = 'tooltip' | 'inline' | 'both' | 'none'; ``` --- ### `ValidationRendererContext` Runtime context passed to advanced validation renderer callbacks. ```ts interface ValidationRendererContext { /** Cell value passed by RevoGrid into the cell renderer. */ value: any; /** Full row model for the rendered cell. */ model?: any; /** Column definition being rendered. */ column: ColumnRegular; /** Whether `column.validate(value, model)` failed for this cell. */ invalid: boolean; /** Resolved severity for the invalid cell. */ severity: ValidationSeverity } ``` --- ### `ValidationRendererOptions` Options for `validationRenderer`. Existing `template` and `invalidProperties` usage remains supported. New options let applications choose severity-aware styling, move the validation indicator, and render messages as tooltips, inline text, both, or neither. ```ts interface ValidationRendererOptions { /** Existing cell template to wrap with validation feedback. */ template?: ColumnRegular['cellTemplate']; /** Extra cell properties applied only when the cell is invalid. */ invalidProperties?: ColumnRegular['cellProperties']; /** Static or callback-driven severity for invalid cell styling. */ severity?: ValidationSeverity | ((context: Omit) => ValidationSeverity); /** Where to render the validation indicator. Defaults to `corner`. */ indicatorPlacement?: ValidationIndicatorPlacement; /** Where to expose the validation message. Defaults to `tooltip`. */ messagePlacement?: ValidationMessagePlacement; /** Optional message resolver. Falls back to `column.validationTooltip`. */ message?: (context: ValidationRendererContext) => any } ``` --- ### `validationRenderer` Extends the `ColumnRegular` interface from the RevoGrid framework to include properties and functions for validating cell data and rendering validation feedback. It facilitates cell validation and provides visual indicators for invalid cells. **Features**: - Extends `ColumnRegular` with: - `softValidation`: An optional boolean indicating if a cell should be saved even when invalid. - `validationTooltip`: A function to provide validation message text for invalid cells. - `validationSeverity`: A function to choose `error`, `warning`, or `info` styling per invalid cell. - `validate`: A function to determine if a cell's value is valid. - The `invalidCellProps` function returns cell properties indicating whether a cell is invalid. - The `validationRenderer` function returns a renderer with: - `cellProperties`: Adds validation styles and properties to cells, highlighting them when invalid. - `cellTemplate`: Renders invalid-cell indicators and optional tooltip or inline messages. **Usage**: - Integrate these extensions into the RevoGrid column configuration to enable cell validation. - Customize cell templates and properties using `validationRenderer` to enhance the grid's UI. ### Example ```typescript import { validationRenderer } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'num', name: 'Linear', validate: (value) => value >= 0 && value <= 40, validationTooltip: (value) => `Value ${value} is out of range`, ...validationRenderer({ severity: 'error', indicatorPlacement: 'corner', messagePlacement: 'tooltip', }), }, ]; ``` This module is crucial for applications requiring data validation, improving user experience by providing immediate feedback for data entry errors within the RevoGrid framework. ```ts validationRenderer: ({ template, invalidProperties, severity, indicatorPlacement, messagePlacement, message, }?: ValidationRendererOptions) => Pick>>, "cellTemplate" | "cellProperties">; ``` --- ### `validateNumber` Validates if the value is a number. **@param** value - The value to validate. * **@returns** `true` if the value is a number, otherwise `false`. ```ts export function validateNumber(value: any): boolean; ``` --- ### `validateBoolean` Validates if the value is a boolean. **@param** value - The value to validate. * **@returns** `true` if the value is a boolean, otherwise `false`. ```ts export function validateBoolean(value: any): boolean; ``` --- ### `validateString` Validates if the value is a string. **@param** value - The value to validate. * **@returns** `true` if the value is a string, otherwise `false`. ```ts export function validateString(value: any): boolean; ``` --- ### `validateInteger` Validates if the value is an integer. **@param** value - The value to validate. * **@returns** `true` if the value is an integer, otherwise `false`. ```ts export function validateInteger(value: any): boolean; ``` --- ### `validateDecimal` Validates if the value is a decimal. **@param** value - The value to validate. * **@returns** `true` if the value is a decimal, otherwise `false`. ```ts export function validateDecimal(value: any): boolean; ``` --- ### `validateRange` Validates if the value is within a specific range. **@param** value - The value to validate. * **@param** min - The minimum value. * **@param** max - The maximum value. * **@returns** `true` if the value is within the range, otherwise `false`. ```ts export function validateRange(value: any, min: number, max: number): boolean; ``` --- ### `validateArray` Validates if the value is an array. **@param** value - The value to validate. * **@returns** `true` if the value is an array, otherwise `false`. ```ts export function validateArray(value: any): boolean; ``` --- ### `validateObject` Validates if the value is an object. **@param** value - The value to validate. * **@returns** `true` if the value is an object, otherwise `false`. ```ts export function validateObject(value: any): boolean; ``` --- ### `validateNull` Validates if the value is null. **@param** value - The value to validate. * **@returns** `true` if the value is null, otherwise `false`. ```ts export function validateNull(value: any): boolean; ``` --- ### `validateUndefined` Validates if the value is undefined. **@param** value - The value to validate. * **@returns** `true` if the value is undefined, otherwise `false`. ```ts export function validateUndefined(value: any): boolean; ``` --- ### `validateDate` Validates if the value is a date. **@param** value - The value to validate. * **@returns** `true` if the value is a date, otherwise `false`. ```ts export function validateDate(value: any): boolean; ``` --- ### `validateRegex` Validates if the value matches the provided regular expression. **@param** value - The value to validate. * **@param** regex - The regular expression to test against. * **@returns** `true` if the value matches the regular expression, otherwise `false`. ```ts export function validateRegex(value: any, regex: RegExp): boolean; ``` --- ### `validateEnum` Validates if the value is one of the allowed values. **@param** value - The value to validate. * **@param** allowedValues - The array of allowed values. * **@returns** `true` if the value is one of the allowed values, otherwise `false`. ```ts export function validateEnum(value: any, allowedValues: any[]): boolean; ``` --- ### `validatePositive` Validates if the value is a positive number. **@param** value - The value to validate. * **@returns** `true` if the value is a positive number, otherwise `false`. ```ts export function validatePositive(value: any): boolean; ``` --- ### `validateNegative` Validates if the value is a negative number. **@param** value - The value to validate. * **@returns** `true` if the value is a negative number, otherwise `false`. ```ts export function validateNegative(value: any): boolean; ``` --- ### `validateFinite` Validates if the value is a finite number. **@param** value - The value to validate. * **@returns** `true` if the value is a finite number, otherwise `false`. ```ts export function validateFinite(value: any): boolean; ``` --- ### `validateEmptyString` Validates if the value is an empty string. **@param** value - The value to validate. * **@returns** `true` if the value is an empty string, otherwise `false`. ```ts export function validateEmptyString(value: any): boolean; ``` --- ### `validateNonEmptyString` Validates if the value is a non-empty string. **@param** value - The value to validate. * **@returns** `true` if the value is a non-empty string, otherwise `false`. ```ts export function validateNonEmptyString(value: any): boolean; ``` --- ### `validateInstance` Validates if the value is an instance of a class. **@param** value - The value to validate. * **@param** cls - The class to test against. * **@returns** `true` if the value is an instance of the class, otherwise `false`. ```ts export function validateInstance(value: any, cls: any): boolean; ``` --- ### `parseBoolean` Utilities used in the RevoGrid project to assist in converting various data types to a boolean value. This function is essential for data processing tasks where boolean values may be represented in different formats. **Features**: - Accepts inputs of type boolean, number, or string, and attempts to convert them to a boolean value. - For numbers, returns `true` if the value is `1`, otherwise `false`. - For strings, it normalizes the input to lowercase and trims whitespace, returning `true` for common affirmative representations ('true', 'yes', '1') and `false` for negative representations ('false', 'no', '0'). - Returns `undefined` if the input cannot be reliably converted to a boolean, allowing for error handling or default value assignment in calling code. **Usage**: - Call `parseBoolean` when you need a flexible utility to standardize various input representations into a boolean format, particularly useful in grid configuration or data validation scenarios. ### Example ```typescript const isEnabled = parseBoolean('yes'); // Returns true const isDisabled = parseBoolean(0); // Returns false ``` ```ts export function parseBoolean(value: any): boolean | undefined; ``` --- # Charts URL: https://pro.rv-grid.com/api/charts/ Source: src/content/docs/api/charts.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { /** * The position of the bars in the chart. */ barPosition?: 'top' | 'bottom'; /** * Define badge styles for the cell. */ badgeStyles?: Record; /** * Whether to render the shape as a rectangular. */ rectangular?: boolean; /** * Maximum number of stars in the rating. Default is 5. */ maxStars?: number; /** * Minimum value for the progress line. Default is 0. */ minValue?: number; /** * Maximum value for the progress line. Default is 100. */ maxValue?: number; /** * Define thresholds for the cell. */ thresholds?: ThresholdConfig[]; /** * Format the value of the cell. */ formatValue?: (value: number, column: ColumnRegular) => string; /** * Function to calculate progress based on row data. * This allows connecting multiple fields to determine progress. * @param config Configuration object containing model, data, and prop * @returns A number between 0 and 100 representing progress */ progress?: (config: { model: DataType; data: DataType[]; prop: ColumnProp }) => number } ``` ## Plugin API ### `progressLineRenderer` The `progressLineRenderer` is a custom cell renderer for RevoGrid that provides a visual representation of progress as a horizontal line within grid cells. This renderer effectively communicates the completion level of a numeric value relative to a defined range. **Features**: - Displays a progress line that visually indicates the percentage completion within specified minimum and maximum values. - Shows a percentage label beside the progress bar, formatted to one decimal place for precision. - Automatically normalizes values to ensure accurate representation within the defined range. - Configurable through column properties such as `minValue` and `maxValue`. - Supports a custom progress function to calculate progress based on row data. **Usage**: - Import `progressLineRenderer` and use it as the `cellTemplate` in your RevoGrid's column configuration. - Specify `minValue` and `maxValue` for each column to define the range for progress calculation. - Optionally provide a `progress` function to calculate progress based on row data. ### Example ```typescript import { progressLineRenderer } from './charts/progress-line.renderer'; const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'score', name: 'Completion Rate', minValue: 0, maxValue: 100, cellTemplate: progressLineRenderer, // Optional: Custom progress calculation progress: (config) => { // Calculate progress based on multiple fields const completed = config.model.completedTasks || 0; const total = config.model.totalTasks || 1; return (completed / total) * 100; } }, ]; ``` This renderer is suitable for applications where tracking progression or percentage completion is necessary, such as performance dashboards, goal tracking systems, or any scenario where a visual progress indicator is beneficial. ```ts progressLineRenderer: CellTemplate>; ``` --- ### `progressLineWithValueRenderer` ```ts progressLineWithValueRenderer: CellTemplate>; ``` --- ### `sparklineRenderer` ```ts sparklineRenderer: CellTemplate> | undefined; ``` --- ### `barChartRenderer` RevoGrid Renderer Generates a bar chart visualization based on the provided data values and column configuration. ```ts barChartRenderer: (h: HyperFunc, { value, column, }: { value?: any; column: Partial>>; }) => VNode; ``` --- ### `heatmapRenderer` ```ts heatmapRenderer: CellTemplate> | undefined; ``` --- ### `badgeRenderer` RevoGrid Renderer Renderers a badge cell with custom background color, text Supports both badgeStyles and thresholds for styling ```ts badgeRenderer: CellTemplate> | undefined; ``` --- ### `ratingStarRenderer` ```ts ratingStarRenderer: CellTemplate>; ``` --- ### `timelineRenderer` ```ts timelineRenderer: CellTemplate> | undefined; ``` --- ### `changeRenderer` RevoGrid Renderer Formats a numeric cell value with corresponding styles and icons based on whether the value is positive, negative, or zero, and exports a cell template renderer for a grid that utilizes this formatting. ```ts changeRenderer: CellTemplate>; ``` --- ### `thumbsRenderer` ```ts thumbsRenderer: CellTemplate> | undefined; ``` --- ### `renderColumnTypeIcon` Helper to render icons based on the columnType. **@param** h Hyperscript function. * **@param** columnType The column type to determine the icon. * **@returns** A VNode containing the corresponding SVG icon. ```ts export function renderColumnTypeIcon( h: HyperFunc, columnType: string ); ``` --- ### `columnTypeRenderer` ```ts columnTypeRenderer: ColumnTemplateFunc | undefined; ``` --- ### `PieData` ```ts interface PieData { value: number; color?: string; name?: string } ``` --- ### `pieChartRenderer` ```ts pieChartRenderer: (h: HyperFunc, { value, column, }: { value?: number[] | PieData[] | undefined; column?: Partial, ColumnRegular>, ColumnProp>> | undefined; }) => VNode; ``` --- ### `summaryHeaderRenderer` ```ts summaryHeaderRenderer: (h: HyperFunc, summary: Record, column?: { maxItems?: number | undefined; }) => VNode; ``` --- ### `summaryAggregateRenderer` ```ts summaryAggregateRenderer: (h: HyperFunc, summary: Record, column?: { showSum?: boolean | undefined; showAvg?: boolean | undefined; }) => VNode; ``` --- ### `avatarRenderer` ```ts avatarRenderer: CellTemplate> | undefined; ``` --- ### `avatarTemplate` ```ts export function avatarTemplate(h: any, options: AvatarTemplateOptions =; ``` --- ### `avatarWithTextRenderer` ```ts avatarWithTextRenderer: CellTemplate> | undefined; ``` --- ### `thresholdRenderer` The `thresholdRenderer` is a custom cell renderer for RevoGrid that applies CSS classes based on defined thresholds. It uses the existing threshold system to apply classes to cells based on their values. **Features**: - Supports multiple thresholds with custom CSS classes - Simple and straightforward threshold-based styling - Works with the existing threshold system **Usage**: ```typescript import { thresholdRenderer } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'score', name: 'Score', thresholds: [ { value: 0, className: 'high' }, // Red for negative values { value: 50, className: 'medium' }, // Yellow for values 0-50 { value: 100, className: 'low' }, // Green for values 50-100 ], cellProperties: thresholdRenderer, }, ]; ``` ```ts thresholdRenderer: PropertiesFunc>; ``` --- ### `circularProgressRenderer` ```ts circularProgressRenderer: CellTemplate>; ``` --- ### `getThresholdClasses` Get the threshold classes for a given value and column. ### Example ```typescript const columns: ColumnRegular[] = [ { name: 'Avg Rating', prop: 'Average Rating', filter: ['number', 'slider'], sortable: true, maxStars: 5, maxValue: 5, thresholds: [ { value: 4, className: 'high' }, { value: 3, className: 'medium' }, { value: 0, className: 'low' }, ], }, ]; ``` ```ts export function getThresholdClasses( value: number, column: Partial, ); ``` --- ### `ThresholdConfig` The `ThresholdConfig` interface defines the structure of a threshold configuration object. It specifies the value and the class name to be applied when the value exceeds or equals the threshold. ```ts interface ThresholdConfig { /** * The value of the threshold. */ value: number; /** * The class name to be applied when the value exceeds or equals the threshold. */ className: string } ``` --- # Clipboard Json URL: https://pro.rv-grid.com/api/clipboard-json/ Source: src/content/docs/api/clipboard-json.md ### `ClipboardJsonPlugin` The `ClipboardJsonPlugin` is a custom plugin for the RevoGrid framework that enhances the clipboard functionality by enabling JSON data parsing during copy and paste operations. This plugin allows complex data structures to be serialized and deserialized correctly when copying to or pasting from the clipboard, supporting advanced data handling scenarios within the grid. **Features**: - Intercepts the `CLIPBOARD_COPY_EVENT` to serialize objects in the grid cells into JSON strings prefixed with a special marker (`$rv-parse_`). This ensures that complex data types are preserved during copy operations. - Intercepts the `CLIPBOARD_PASTE_EVENT` to deserialize JSON strings back into objects when pasted into the grid, restoring the original data structure. - Provides methods `parserCopy` and `parserPaste` to handle the conversion logic, allowing for easy customization and extension. **Usage**: - Integrate `ClipboardJsonPlugin` into a RevoGrid instance to enable JSON data support in clipboard operations. This is particularly useful for applications that handle structured data or require seamless data interchange with external systems. ### Example ```typescript import { ClipboardJsonPlugin } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [ClipboardJsonPlugin]; ``` This plugin is essential for developers looking to enhance the data exchange capabilities of RevoGrid, providing robust support for copying and pasting complex data structures. ```ts class ClipboardJsonPlugin { parserPaste(data: DataFormat[][]); parserCopy(data: DataFormat[][]); } ``` --- ### `parseJsonClipboardMatrix` ```ts export function parseJsonClipboardMatrix(data: unknown[][]); ``` --- ### `serializeJsonClipboardCell` ```ts export function serializeJsonClipboardCell(value: unknown); ``` --- # Collaborative Presence URL: https://pro.rv-grid.com/api/collaborative-presence/ Source: src/content/docs/api/collaborative-presence.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`) ```ts interface HTMLRevoGridElement { collaborativePresence?: CollaborativePresenceConfig; 'collaborative-presence'?: CollaborativePresenceConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { collaborativePresence?: CollaborativePresenceConfig } ``` ## Plugin API ### `CollaborativePresencePlugin` Renders remote collaborator focus and range presence without mutating local grid focus, selection, data, or history. #### Dependencies - **Config integration** `additionalData.collaborativePresence`: Reads remote collaborator presence from grid.collaborativePresence or additionalData.collaborativePresence. - **Event integration** `afterrender`: Refreshes visible remote focus and range markers after grid render updates. ```ts class CollaborativePresencePlugin { destroy(); } ``` --- ### `CollaborativePresenceActivity` ```ts export type CollaborativePresenceActivity = 'viewing' | 'editing' | 'idle'; ``` --- ### `CollaborativePresenceCellRef` ```ts interface CollaborativePresenceCellRef { x: number; y: number; colType?: DimensionCols; rowType?: DimensionRows } ``` --- ### `CollaborativePresenceRangeRef` (Extended from `index.ts`) ```ts interface CollaborativePresenceRangeRef { colType?: DimensionCols; rowType?: DimensionRows } ``` --- ### `CollaborativePresenceUser` ```ts interface CollaborativePresenceUser { id: string; name: string; initials?: string; color?: string; activity?: CollaborativePresenceActivity; focus?: CollaborativePresenceCellRef | null; range?: CollaborativePresenceRangeRef | null; lastActiveAt?: number | string | Date } ``` --- ### `CollaborativePresenceRowPatch` ```ts interface CollaborativePresenceRowPatch { rowIndex: number; data: Record; rowType?: DimensionRows } ``` --- ### `CollaborativePresenceConfig` ```ts interface CollaborativePresenceConfig { enabled?: boolean; showLabels?: boolean; staleAfterMs?: number; users?: CollaborativePresenceUser[]; remoteEdits?: CollaborativePresenceRowPatch[] } ``` --- ### `ResolvedCollaborativePresenceConfig` ```ts interface ResolvedCollaborativePresenceConfig { enabled: boolean; showLabels: boolean; staleAfterMs?: number; users: CollaborativePresenceUser[]; remoteEdits: CollaborativePresenceRowPatch[] } ``` --- ### `CollaborativePresenceRect` ```ts interface CollaborativePresenceRect { left: number; top: number; width: number; height: number } ``` --- ### `CollaborativePresenceMarker` ```ts interface CollaborativePresenceMarker { kind: 'focus' | 'range'; user: CollaborativePresenceUser; rect: CollaborativePresenceRect; colType: DimensionCols; rowType: DimensionRows } ``` --- ### `normalizeCollaborativePresenceCell` ```ts export function normalizeCollaborativePresenceCell( cell: CollaborativePresenceCellRef, ): Required; ``` --- ### `normalizeCollaborativePresenceRange` ```ts export function normalizeCollaborativePresenceRange( range: CollaborativePresenceRangeRef, ): Required; ``` --- ### `resolveCollaborativePresenceConfig` ```ts export function resolveCollaborativePresenceConfig( config?: CollaborativePresenceConfig, ): ResolvedCollaborativePresenceConfig; ``` --- ### `getVisibleCollaborativePresenceUsers` ```ts export function getVisibleCollaborativePresenceUsers( config: ResolvedCollaborativePresenceConfig, now = Date.now(), ): CollaborativePresenceUser[]; ``` --- ### `resolveCollaborativePresenceMarkers` ```ts export function resolveCollaborativePresenceMarkers( _grid: HTMLElement, users: CollaborativePresenceUser[], providers?: PluginProviders, ): CollaborativePresenceMarker[]; ``` --- ### `applyCollaborativePresenceRowPatches` Applies row patches directly into the RevoGrid data store without triggering a full source reset or plugin re-initialization. `rowIndex` is treated as a **source (physical) index** — the position in the original data array — so patches always target the correct row regardless of the current sort or reorder state. Per rowType: 1. Mutate source[rowIndex] directly for each patch 2. store.set('source', [...]) once — triggers a single targeted reactive update Mirrors the low-level write pattern in spreadsheet.simulation.ts. ```ts export function applyCollaborativePresenceRowPatches( providers: PluginProviders, patches: CollaborativePresenceRowPatch[], ): void; ``` --- ### `CollaborativePresenceLayer` Manages per-viewport slot overlay elements and draws presence markers into them. Each viewport type combination (colType × rowType) gets its own slot div so markers clip and scroll correctly within their viewport. ```ts class CollaborativePresenceLayer { render(markers: CollaborativePresenceMarker[], showLabels: boolean): void; destroy(): void; } ``` --- ### `resolveCollaborativePresenceCellRect` ```ts export function resolveCollaborativePresenceCellRect( providers: PluginProviders | undefined, cell: PresenceCell, ): CollaborativePresenceRect | undefined; ``` --- ### `resolveCollaborativePresenceRangeRect` ```ts export function resolveCollaborativePresenceRangeRect( providers: PluginProviders | undefined, range: PresenceRange, ): CollaborativePresenceRect | undefined; ``` --- # Column Add Popup URL: https://pro.rv-grid.com/api/column-add-popup/ Source: src/content/docs/api/column-add-popup.md ## Module Extensions ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * Column add popup plugin configuration. * * @deprecated Prefer the direct `grid.columnAddPopup` property. */ columnAddPopup?: ColumnAddPopupConfig } ``` --- ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { /** * Column add popup plugin configuration. */ columnAddPopup?: ColumnAddPopupConfig } ``` ## Plugin API ### `ColumnAddPopupPlugin` ```ts class ColumnAddPopupPlugin { open(trigger: HTMLElement, originalEvent: MouseEvent); destroy(); } ``` --- ### `ColumnAddPopupTone` ```ts export type ColumnAddPopupTone = | 'green' | 'blue' | 'cyan' | 'red' | 'orange' | 'yellow' | 'violet' | 'pink' | 'gray'; ``` --- ### `ColumnAddPopupItem` ```ts interface ColumnAddPopupItem { id: string; label: string; description?: string; icon?: string; tone?: ColumnAddPopupTone; disabled?: boolean; selected?: boolean } ``` --- ### `ColumnAddPopupSection` ```ts interface ColumnAddPopupSection { title: string; items: ColumnAddPopupItem[] } ``` --- ### `ColumnAddPopupSelectContext` ```ts interface ColumnAddPopupSelectContext { item: ColumnAddPopupItem; selected: boolean; grid: HTMLRevoGridElement; originalEvent: MouseEvent; close: () => void } ``` --- ### `ColumnAddPopupItemStateContext` ```ts interface ColumnAddPopupItemStateContext { item: ColumnAddPopupItem; grid: HTMLRevoGridElement } ``` --- ### `ColumnAddPopupFooterContext` ```ts interface ColumnAddPopupFooterContext { grid: HTMLRevoGridElement; originalEvent: MouseEvent; close: () => void } ``` --- ### `ColumnAddPopupConfig` ```ts interface ColumnAddPopupConfig { sections?: ColumnAddPopupSection[]; title?: string; footerLabel?: string; className?: string; isSelected?: (context: ColumnAddPopupItemStateContext) => boolean; onSelect?: (context: ColumnAddPopupSelectContext) => void; onFooterClick?: (context: ColumnAddPopupFooterContext) => void } ``` --- # Column Autosize URL: https://pro.rv-grid.com/api/column-autosize/ Source: src/content/docs/api/column-autosize.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { /** * Kebab-case alias for framework bindings. */ 'auto-size-column'?: AutoSizeColumnProp } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) Add autoSizeColumnConfig property to AdditionalData interface ```ts interface AdditionalData { /** * @deprecated Use `grid.autoSizeColumn` instead. */ autoSizeColumnConfig?: AutoSizeColumnConfig } ``` --- ### `ColumnRegular` (Extended from `@revolist/revogrid`) Add autoSize property to ColumnRegular interface ```ts interface ColumnRegular { autoSize?: boolean } ``` ## Plugin API ### `AutoSizeColumnConfig` ```ts export type AutoSizeColumnConfig = { // ui behavior mode mode?: ColumnAutoSizeMode; /** * autoSize for all columns * if allColumnes true all columns treated as autoSize, worse for performance * false by default */ allColumns?: boolean; /** * assumption per characted size * by default defined as 8, can be changed in this config */ letterBlockSize?: number; /** make size calculation exact * by default it based on assumption each character takes some space defined in letterBlockSize */ preciseSize?: boolean; }; ``` --- ### `ColumnAutoSizeMode` ```ts enum ColumnAutoSizeMode { // increases column width on header click according the largest text value headerClickAutosize = 'headerClickAutoSize', // increases column width on data set and text edit, decreases performance autoSizeOnTextOverlap = 'autoSizeOnTextOverlap', // increases and decreases column width based on all items sizes, worst for performance autoSizeAll = 'autoSizeAll' } ``` --- ### `AutoSizeColumnPlugin` The `AutoSizeColumnPlugin` is a plugin for the RevoGrid framework that provides automatic column resizing functionality. It allows users to resize columns based on the content of the cells, providing a flexible and user-friendly interface for data presentation. **Features**: - Automatically resizes columns based on the content of the cells. - Supports different modes of automatic resizing: - Header click autosize: Resizes columns when the header is clicked. - Text overlap autosize: Resizes columns when text is edited. - All columns autosize: Resizes all columns based on the content of the cells. **Usage**: - Add the `AutoSizeColumnPlugin` to the grid's plugins array. - Configure the plugin using the `additionalData` property with the `autoSizeColumnConfig` key. ### Example ```typescript import { AutoSizeColumnPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.plugins = [AutoSizeColumnPlugin]; // Add the plugin to the grid grid.additionalData = { autoSizeColumnConfig: { mode: ColumnAutoSizeMode.autoSizeOnTextOverlap, }, }; ``` #### Dependencies - **Event integration** `aftersourceset`, `afteredit`, `headerdblclick`: Integrates with grid lifecycle events to recalculate column widths. - **Config integration** `additionalData.autoSizeColumnConfig`: Reads legacy auto-size configuration from additionalData.autoSizeColumnConfig. ```ts class AutoSizeColumnPlugin { async setSource(source: DataType[]): Promise; getLength(len?: any): number; afteredit(e: EditEvent); afterEditAll(e: EditEvent); getColumnSize(index: number, type: DimensionCols): number; columnSet(columns: Record); clearPromise(); isRangeEdit(e: EditEvent): e is BeforeRangeSaveDataDetails; initiatePresizeElement(): HTMLElement; destroy(); } ``` --- # Column Collapse URL: https://pro.rv-grid.com/api/column-collapse/ Source: src/content/docs/api/column-collapse.md ## Module Extensions ### `Group` (Extended from `@revolist/revogrid`) Add collapsible property to Group interface ```ts interface Group { /** * Whether the group is collapsible */ collapsible?: boolean; /** * Whether the group is collapsed */ collapsed?: boolean; /** * Custom placeholder configuration used when a collapsed group has no sealed columns. */ placeholder?: string | ColumnTemplateFunc | ColumnCollapsePlaceholder } ``` --- ### `ColumnRegular` (Extended from `@revolist/revogrid`) Add sealed property to ColumnRegular interface ```ts interface ColumnRegular { /** * Whether the column is sealed, if true the column will not be trimmed/collapsed */ sealed?: boolean } ``` --- ### `HTMLRevoGridElementEventMap` (Extended from `global`) Add column collapse event to HTMLRevoGridElementEventMap ```ts interface HTMLRevoGridElementEventMap { /** * Column collapse event */ [COLUMN_COLLAPSE_EVENT]: { group: Group }; /** * Column expand event */ [COLUMN_EXPAND_EVENT]: { group: Group } } ``` ## Plugin API ### `ColumnCollapsePlaceholder` ```ts interface ColumnCollapsePlaceholder { header?: string | ColumnTemplateFunc; value?: (schema: CellTemplateProp, state: CollapsedGroupState) => unknown; cellTemplate?: NonNullable; cellProperties?: PropertiesFunc } ``` --- ### `ColumnCollapsePlugin` The `ColumnCollapsePlugin` is a plugin for the RevoGrid framework that enables collapsible columns with content trimming functionality. It allows users to collapse and expand columns, reducing the visual clutter and improving the overall user experience when dealing with large datasets. **Features**: - Enables collapsible columns with content trimming - Provides a visual indicator (▼) in the header to collapse and expand columns **Usage**: - Add the `ColumnCollapsePlugin` to the grid's plugins array. - Add the `collapsible` property to the column configuration to enable collapsible columns. - Add the `collapsed` property to the column configuration to set the initial collapsed state. - Add the `children` property to the column configuration to define the columns to be collapsed. - Add the `sealed` property to the column configuration to prevent the column from being trimmed. ### Example ```typescript import { ColumnCollapsePlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.columns = [ { collapsible: true, collapsed: true, children: [ { prop: 'age', sealed: true, }, ], }, ]; grid.plugins = [ColumnCollapsePlugin]; ``` ```ts class ColumnCollapsePlugin { destroy(); } ``` --- ### `CollapsedGroupMode` ```ts export type CollapsedGroupMode = 'sealed' | 'placeholder'; ``` --- ### `CollapsedGroupState` ```ts interface CollapsedGroupState { key: string; mode: CollapsedGroupMode; group: Group; indexes: number[]; visibleIndexes: number[]; carrierIndex?: number; hiddenCount: number } ``` --- # Column Dialog URL: https://pro.rv-grid.com/api/column-dialog/ Source: src/content/docs/api/column-dialog.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { columnDialog?: ColumnDialogConfig } ``` --- ### `HTMLRevoGridElementEventMap` (Extended from `global`) ```ts interface HTMLRevoGridElementEventMap { [COLUMN_DIALOG_OPEN_EVENT]: undefined; [COLUMN_DIALOG_CLOSE_EVENT]: undefined; [COLUMN_DIALOG_TOGGLE_EVENT]: undefined; [COLUMN_DIALOG_CHANGE_EVENT]: ColumnDialogChangeDetail } ``` ## Plugin API ### `buildColumnDialogRows` ```ts export function buildColumnDialogRows(; ``` --- ### `buildColumnDialogRowsFromProviders` ```ts export function buildColumnDialogRowsFromProviders( providers: PluginProviders, hiddenColumns: ReadonlySet, ): ColumnDialogRow[]; ``` --- ### `filterColumnDialogRows` ```ts export function filterColumnDialogRows( rows: readonly ColumnDialogRow[], query: string, ): ColumnDialogRow[]; ``` --- ### `getDialogRowDescendantProps` ```ts export function getDialogRowDescendantProps( rows: readonly ColumnDialogRow[], rowId: string, ): ColumnProp[]; ``` --- ### `resolveNextHiddenColumns` ```ts export function resolveNextHiddenColumns( rows: readonly ColumnDialogRow[], hiddenColumns: ReadonlySet, rowId: string, visible: boolean, ): ColumnProp[]; ``` --- ### `resolveColumnDialogReorder` ```ts export function resolveColumnDialogReorder(; ``` --- ### `normalizeHiddenColumns` ```ts export function normalizeHiddenColumns(value: unknown): ColumnProp[]; ``` --- ### `ColumnCollection` Column collection definition. Used to access indexed data for columns. Can be accessed via different events. ```ts /** * Column collection definition. * Used to access indexed data for columns. * Can be accessed via different events. */ export type ColumnCollection = { /** * Columns as they are in stores */ columns: Record; /** * Columns indexed by prop for quick access, it's possible to have multiple columns with same prop but not recommended */ columnByProp: Record; /** * Grouped columns */ columnGrouping: ColumnGroupingCollection; /** * Max level of grouping */ maxLevel: number; /** * Sorting */ sort: Record; }; ``` --- ### `ColumnDialog` ```ts export function ColumnDialog(; ``` --- ### `COLUMN_DIALOG_OPEN_EVENT` ```ts COLUMN_DIALOG_OPEN_EVENT: string; ``` --- ### `COLUMN_DIALOG_CLOSE_EVENT` ```ts COLUMN_DIALOG_CLOSE_EVENT: string; ``` --- ### `COLUMN_DIALOG_TOGGLE_EVENT` ```ts COLUMN_DIALOG_TOGGLE_EVENT: string; ``` --- ### `COLUMN_DIALOG_CHANGE_EVENT` ```ts COLUMN_DIALOG_CHANGE_EVENT: string; ``` --- ### `ColumnDialogApplyMode` ```ts export type ColumnDialogApplyMode = 'immediate'; ``` --- ### `ColumnDialogConfig` ```ts interface ColumnDialogConfig { title?: string; searchPlaceholder?: string; closeLabel?: string; width?: number | string; height?: number | string } ``` --- ### `ColumnDialogRowKind` ```ts export type ColumnDialogRowKind = 'group' | 'column'; ``` --- ### `ColumnDialogVisibilityState` ```ts export type ColumnDialogVisibilityState = 'checked' | 'unchecked' | 'indeterminate'; ``` --- ### `ColumnDialogRow` ```ts interface ColumnDialogRow { id: string; parentId: string | null; name: string; prop: ColumnProp | null; kind: ColumnDialogRowKind; type: DimensionCols; physicalIndex: number | null; sourceIndexes: number[]; order: number; depth: number; visible: boolean; visibilityState: ColumnDialogVisibilityState; rowDrag: boolean } ``` --- ### `ColumnDialogChangeDetail` ```ts interface ColumnDialogChangeDetail { hiddenColumns: ColumnProp[] } ``` --- ### `ColumnDialogReorderRequest` ```ts interface ColumnDialogReorderRequest { rows: readonly ColumnDialogRow[]; draggedRowId: string; targetRowId: string; position: 'before' | 'after' } ``` --- ### `ColumnDialogReorderResult` ```ts interface ColumnDialogReorderResult { valid: boolean; reason?: string; type?: DimensionCols; proxyItems?: number[] } ``` --- ### `ColumnDialogPlugin` Column manager dialog for RevoGrid Pro. `ColumnDialogPlugin` renders a modal column-management dialog backed by an internal RevoGrid tree. It lists all host grid columns, including columns currently hidden by `ColumnHidePlugin`, and lets users search, toggle visibility, and reorder columns or column groups inside their current parent/pin section. Visibility is intentionally delegated to `ColumnHidePlugin`; this plugin auto-installs it when it is not already registered and writes changes through `grid.hideColumns`. ### Usage ```ts import { ColumnDialogPlugin } from '@revolist/revogrid-pro'; const grid = document.querySelector('revo-grid')!; grid.plugins = [ColumnDialogPlugin]; grid.columnDialog = { title: 'Columns', searchPlaceholder: 'Search columns...', }; // Open with a grid event. grid.dispatchEvent(new CustomEvent('revocolumndialogopen')); // Or open through the plugin instance. const plugins = await grid.getPlugins(); plugins.getByClass(ColumnDialogPlugin)?.open(); grid.addEventListener('revocolumndialogchange', (event) => { console.log(event.detail.hiddenColumns); }); ``` Public events: - `revocolumndialogopen` opens the dialog. - `revocolumndialogclose` closes the dialog. - `revocolumndialogtoggle` toggles open state. - `revocolumndialogchange` emits after visibility or ordering changes. #### Dependencies - **Auto-installed** `ColumnHidePlugin`: Uses ColumnHidePlugin as the only host visibility engine. - **Event integration** `COLUMN_UPDATED_EVENT`, `HIDDEN_COLUMNS_UPDATED_EVENT`, `COLUMN_DRAG_END_EVENT`: Tracks host column definitions, hidden-column state, and host column reorder. ```ts class ColumnDialogPlugin { open(): void; close(): void; toggle(): void; destroy(): void; } ``` --- # Column Group Panel URL: https://pro.rv-grid.com/api/column-group-panel/ Source: src/content/docs/api/column-group-panel.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { /** * Configuration for the column group panel. */ columnGroupPanel?: ColumnGroupPanelConfig; /** * Kebab-case alias for framework bindings. */ 'column-group-panel'?: ColumnGroupPanelConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * Additional data property for column group panel * @deprecated Use `grid.columnGroupPanel` instead. * * @example * ```typescript * const grid = document.createElement('revo-grid'); * grid.additionalData = { * columnGroupPanel: { * emptyPanelText: 'Drop columns here to create groups' * } * }; * ``` */ columnGroupPanel?: ColumnGroupPanelConfig; /** * Legacy additional data property for column group panel. * @deprecated Use `grid.columnGroupPanel` instead. */ columnGroupingPanel?: ColumnGroupPanelConfig } ``` ## Plugin API ### `ColumnGroupPanelConfig` Configuration options for the ColumnGroupPanelPlugin ```ts interface ColumnGroupPanelConfig { /** * Text to display in the group panel when no columns are grouped * @default 'Drag columns here to group by row.' */ emptyPanelText?: string } ``` --- ### `ColumnGroupPanelPlugin` The `ColumnGroupPanelPlugin` is a plugin for RevoGrid that introduces a user-friendly interface for grouping columns. It enables users to drag and drop columns into a designated panel to create groupings, facilitating data organization and management. **Features**: - Creates a visual group panel in the grid header for column grouping. - Supports drag-and-drop functionality to add, reorder, or remove column groupings. - Updates the grid's grouping state dynamically as users interact with the panel. - Integrates drag events to enhance user interactions with column headers. **Usage**: - Instantiate `ColumnGroupPanelPlugin` and add it to the RevoGrid plugins array. - Drag columns into the group panel to group them or manage existing groupings through the UI. ### Example ```typescript import { ColumnGroupPanelPlugin } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [ColumnGroupPanelPlugin]; ``` ### Configuration ```typescript const grid = document.createElement('revo-grid'); grid.plugins = [ColumnGroupPanelPlugin]; grid.additionalData = { columnGroupPanel: { emptyPanelText: 'Drop columns here to create groups' } }; ``` This plugin is ideal for users who need to organize grid data by grouping columns, providing an intuitive interface for managing complex data sets. #### Dependencies - **Config integration** `core-grouping`: Writes grid grouping props through the core grouping API. ```ts class ColumnGroupPanelPlugin {} ``` --- # Column Group Render Sync URL: https://pro.rv-grid.com/api/column-group-render-sync/ Source: src/content/docs/api/column-group-render-sync.md ### `ensureColumnGroupRenderSyncPlugin` ```ts export function ensureColumnGroupRenderSyncPlugin( revogrid: HTMLRevoGridElement, providers: PluginProviders, ): ColumnGroupRenderSyncPlugin; ``` --- ### `ColumnGroupRenderSyncPlugin` #### Dependencies - **Event integration** `BEFORE_GROUP_HEADER_RENDER_EVENT`: Projects source-indexed column groups to visible render indexes during grouped header rendering. - **Event integration** `COLUMN_UPDATED_EVENT`: Refreshes source-indexed group snapshots when columns are replaced. ```ts class ColumnGroupRenderSyncPlugin { refreshSourceGroups(type?: DimensionCols): void; } ``` --- # Column Hide URL: https://pro.rv-grid.com/api/column-hide/ Source: src/content/docs/api/column-hide.md ## Module Extensions ### `HTMLRevoGridElementEventMap` (Extended from `global`) ```ts interface HTMLRevoGridElementEventMap { /** * Event triggered when hidden columns are updated */ hiddencolumnsupdated: { hiddenColumns: Set } } ``` --- ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { /** * Columns to hide by prop. */ hideColumns?: ColumnHideConfig; /** * Kebab-case alias for framework bindings. */ 'hide-columns'?: ColumnHideConfig; /** * Columns to hide by prop. * @deprecated Use `grid.hideColumns` instead. */ columnHide?: ColumnHideConfig; /** * Kebab-case alias for the legacy `columnHide` property. * @deprecated Use `grid.hideColumns` or `hide-columns` instead. */ 'column-hide'?: ColumnHideConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) Additional data property for column hide configuration ```ts interface AdditionalData { /** * Additional data property for column hide configuration * @deprecated Use `grid.hideColumns` instead. * @example * grid.additionalData = { * // Hide columns with indices 1, 2, and 3 * hiddenColumns: [1, 2, 3], * } */ hiddenColumns?: HiddenColumnsConfig } ``` ## Plugin API ### `ColumnHidePlugin` The `ColumnHidePlugin` is a plugin for the RevoGrid framework that enables column hiding functionality based on the `hide-columns` attribute, `hideColumns` property, or `additionalData.hiddenColumns`. It allows users to hide specific columns by providing their column `prop` values. **Features**: - Enables column hiding through the `hide-columns` attribute - Supports runtime updates through the `hideColumns` property - Supports legacy runtime updates through the `columnHide` property - Supports hiding columns through `additionalData.hiddenColumns` - Automatically updates when the attribute, property, or additionalData changes - Preserves column state when columns are updated - Supports multiple framework attribute formats: - Web component: `hide-columns="name,age"` - React: `hideColumns={['name', 'age']}` - Angular/Vue/Svelte: bind `hideColumns` as an array **Usage**: ```html grid.hideColumns = ['name', 'age']; grid.columnHide = ['name', 'age']; ``` #### Dependencies - **Event integration** `COLUMN_UPDATED_EVENT`: Integrates with column update events to preserve hidden-column state. - **Event integration** `COLUMN_DRAG_END_EVENT`: Keeps hidden-column trim order aligned after visible columns are moved. - **Config integration** `grid.hideColumns`, `additionalData.hiddenColumns`: Reads direct hidden-column configuration from grid.hideColumns or legacy additionalData.hiddenColumns. ```ts class ColumnHidePlugin {} ``` --- ### `HiddenColumnsConfig` The `HiddenColumnsConfig` type specifies which columns should be hidden in the grid. **Usage**: - Import the `HiddenColumnsConfig` type to define hidden columns in RevoGrid components. - Assign the desired hidden columns configuration to `grid.hideColumns` to control which columns are hidden. **Example:** ```typescript : * ```typescript * import { ColumnHidePlugin } from '@revolist/revogrid-pro'; * * const grid = document.createElement('revo-grid'); * grid.plugins = [ColumnHidePlugin]; * * grid.hideColumns = [1, 2, 3]; // Hide columns with indices 1, 2, and 3 * ``` * * This configuration type allows developers to programmatically control which columns * are hidden in the grid, enhancing the flexibility of the grid's display options. ``` ```ts /** * The `HiddenColumnsConfig` type * specifies which columns should be hidden in the grid. * * **Usage**: * - Import the `HiddenColumnsConfig` type to define hidden columns in RevoGrid components. * - Assign the desired hidden columns configuration to `grid.hideColumns` to control which columns * are hidden. * * @example: * ```typescript * import { ColumnHidePlugin } from '@revolist/revogrid-pro'; * * const grid = document.createElement('revo-grid'); * grid.plugins = [ColumnHidePlugin]; * * grid.hideColumns = [1, 2, 3]; // Hide columns with indices 1, 2, and 3 * ``` * * This configuration type allows developers to programmatically control which columns * are hidden in the grid, enhancing the flexibility of the grid's display options. */ export type HiddenColumnsConfig = ColumnProp[]; ``` --- ### `ColumnHideConfig` ```ts export type ColumnHideConfig = HiddenColumnsConfig | string; ``` --- # Column Move With Groups URL: https://pro.rv-grid.com/api/column-move-with-groups/ Source: src/content/docs/api/column-move-with-groups.md ### `ColumnMoveAdvancedPlugin` #### Dependencies - **Required** `ColumnMovePlugin`: Extends the core ColumnMovePlugin implementation for grouped columns. ```ts class ColumnMoveAdvancedPlugin { dragStart(; doMove(e: MouseEvent); onMouseUp(e: MouseEvent); } ``` --- # Column Selection URL: https://pro.rv-grid.com/api/column-selection/ Source: src/content/docs/api/column-selection.md ### `ColumnSelectionPlugin` The `ColumnSelectionPlugin` is an essential plugin for RevoGrid, designed to enhance user interaction by enabling column selection functionalities. It allows users to focus on and select entire columns, facilitating workflows that require column-based data manipulation and analysis. **Key Features**: - **Column Focus and Selection**: Allows users to focus on specific columns, highlighting them for operations like data editing, copying, or analysis. - **Event-Driven Architecture**: Listens to and dispatches various events such as `COLUMN_UPDATED_EVENT`, `EVENT_HEADER_FOCUS`, `EVENT_BEFORE_CELL_FOCUS`, and `EVENT_BEFORE_FOCUS_LOST` to manage column selection and focus logic effectively. - **Dynamic Column Updates**: Adjusts selections based on column updates, ensuring that the active selection remains consistent with any changes in column configuration. - **Integration with Selection and Data Stores**: Utilizes observable stores for managing selection states, interfacing with the grid's data and selection stores to apply and clear selections. - **Header Interaction**: Reacts to header clicks to initiate column selection, allowing seamless user interaction with column headers. **Usage**: - Integrate this plugin into a RevoGrid instance to enable column selection features. Add the plugin to the grid's plugin array to activate its functionality. ### Example ```typescript import { ColumnSelectionPlugin } from '@revolist/revogrid-pro'; const grid = document.createElement('revo-grid'); grid.plugins = [ColumnSelectionPlugin]; ``` This plugin is ideal for applications where column-centric interaction is critical, providing an intuitive interface for managing and selecting columns in complex data grids. ```ts class ColumnSelectionPlugin { destroy(): void; } ``` --- # Column Stretch URL: https://pro.rv-grid.com/api/column-stretch/ Source: src/content/docs/api/column-stretch.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`) ```ts interface HTMLRevoGridElement { /** * Direct column stretch configuration. */ stretch?: StretchConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) Additional data property for column stretch configuration ```ts interface AdditionalData { /** * * Additional data property for column stretch configuration * @deprecated Use `grid.stretch` instead. * @example * grid.additionalData = { * // Stretch all columns to utilize available space * stretch: 'all', * // Stretch the first column to utilize available space * stretch: 1, * // Stretch the last column to utilize available space * stretch: 'last', * // Stretch the last column and preserve column sizes when columns are replaced * stretch: { * mode: 'last', * preserveSizesOnColumnChange: true, * }, * } */ stretch?: StretchConfig } ``` ## Plugin API ### `ColumnStretchPlugin` The ColumnStretchPlugin is a RevoGrid plugin designed to dynamically adjust column widths based on available space within the grid. It ensures that extra space is efficiently utilized by increasing the width of specified columns, enhancing the presentation of data without manual resizing. **Key Features**: - Automatically stretches the width of columns to fill any remaining space in the grid. - Supports configurations for stretching all columns, a specific column by index, or only the last column. - Observes direct `grid.stretch` and legacy `additionalData.stretch` config updates, then adjusts column sizes based on grid dimensions or configuration changes. - Utilizes `ResizeObserver` and a debounced `applyStretch` function to efficiently manage size recalculations during viewport size changes. **Usage**: - Integrate the plugin into a RevoGrid instance by adding it to the grid's plugin array. - Configure the stretching behavior through direct `grid.stretch` or legacy `additionalData.stretch`, specifying modes like 'none', 'all', or a specific index. ### Example ```typescript import { ColumnStretchPlugin } from './column-stretch'; const grid = document.createElement('revo-grid'); grid.plugins = [ColumnStretchPlugin]; grid.stretch = 'last'; // Stretch the last column to fit available space ``` By using this plugin, developers can enhance the adaptability and visual layout of their RevoGrid, ensuring optimal use of grid space and improving data display without manual intervention. #### Dependencies - **Config integration** `auto-size-column`: Skips columns marked with autoSize when applying stretch sizing. - **Config integration** `additionalData.stretch`: Reads legacy stretch configuration from additionalData.stretch. ```ts class ColumnStretchPlugin {} ``` --- ### `StretchMode` The `StretchConfig` type specifies how extra space in the grid should be utilized by adjusting the width of columns. **Usage**: - Import the `StretchConfig` type to define stretching behavior in RevoGrid components. - Assign the desired stretching configuration to the grid's `additionalData.stretch` property to control how columns adjust to available space. **Example:** ```typescript : * ```typescript * import { ColumnStretchPlugin } from '@revolist/revogrid-pro'; * * const grid = document.createElement('revo-grid'); * grid.plugins = [ColumnStretchPlugin]; * * grid.additionalData = { * stretch: 'all', // Stretch all columns to utilize available space * }; * ``` * * This configuration type allows developers to customize how columns adapt to * changes in grid size, ensuring optimal data presentation and grid layout. ``` ```ts // column-stretch/stretch.types.ts /** * The `StretchConfig` type * specifies how extra space in the grid should be utilized by adjusting the width * of columns. * * **Usage**: * - Import the `StretchConfig` type to define stretching behavior in RevoGrid components. * - Assign the desired stretching configuration to the grid's `additionalData.stretch` * property to control how columns adjust to available space. * * @example: * ```typescript * import { ColumnStretchPlugin } from '@revolist/revogrid-pro'; * * const grid = document.createElement('revo-grid'); * grid.plugins = [ColumnStretchPlugin]; * * grid.additionalData = { * stretch: 'all', // Stretch all columns to utilize available space * }; * ``` * * This configuration type allows developers to customize how columns adapt to * changes in grid size, ensuring optimal data presentation and grid layout. */ export type StretchMode = 'none' | 'last' | 'all' | number; ``` --- ### `StretchOptions` ```ts export type StretchOptions = { /** * Stretch mode. */ mode: StretchMode; /** * Preserve rendered column sizes for matching column props when columns are replaced. * Matching uses the column dimension type and `prop`, so reordered columns keep their * previous width while new columns use their configured/default width. */ preserveSizesOnColumnChange?: boolean; }; ``` --- ### `StretchConfig` ```ts export type StretchConfig = false | StretchMode | StretchOptions; ``` --- ### `ColumnStretchSizePreserve` ```ts class ColumnStretchSizePreserve { snapshot(); restore(columns: ColumnCollection['columns']); } ``` --- # Column Type Progress URL: https://pro.rv-grid.com/api/column-type-progress/ Source: src/content/docs/api/column-type-progress.md ### `ProgressColumnType` The `ProgressColumnType` class implements a custom column type for the RevoGrid, designed to visually display numeric values as progress bars within grid cells. It extends the functionality of the standard `ColumnType` by providing validation and rendering logic specific to progress indicators. **Features**: - Validation: Ensures the value is a number between 0 and 100, representing a valid percentage for progress visualization. - Custom cell rendering: Uses a `cellTemplate` function to render the progress bar, adapting the visual fill based on the cell value. How to use: - Integrate `ProgressColumnType` into your grid's column configuration to enable progress bar visualization. - Assign the `cellTemplate` of this class to the corresponding column definition to render the progress bars. ### Example ```typescript import { ProgressColumnType } from './progress-column-type'; const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'progress', name: 'Progress', cellTemplate: ProgressColumnType.prototype.cellTemplate, // Use progress bar template }, ]; ``` This class enhances user experience by providing a clear visual representation of progress data directly within the grid interface, making data interpretation more intuitive and visually appealing. ```ts class ProgressColumnType {} ``` --- # Column Type Rate URL: https://pro.rv-grid.com/api/column-type-rate/ Source: src/content/docs/api/column-type-rate.md ### `RatingColumnType` The RatingColumnType class defines a custom column type for the RevoGrid, specifically designed to handle and render rating values as a series of star icons. This custom type includes a validation method to ensure rating values are within an acceptable range (0 to 5) and a `cellTemplate` method for rendering the visual representation of these ratings. **Key Features**: - Validation: Ensures rating values are between 0 and 5, inclusive. - Custom cell rendering: Displays rating as stars, using a `cellTemplate` function to map numeric values to visual elements. **Usage**: - Implement the RatingColumnType in the grid's column configuration to apply this rating visualization logic. - Utilize the `cellTemplate` function provided by this class within your column definitions to render ratings. ### Example ```typescript import { RatingColumnType } from './rating-column-type'; const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'rating', name: 'User Rating', cellTemplate: RatingColumnType.prototype.cellTemplate, // Apply custom rating cell template }, ]; ``` This class enhances the grid's capability to visually represent rating data, facilitating a more intuitive user experience for reviewing rating values. ```ts class RatingColumnType {} ``` --- # Context Menu URL: https://pro.rv-grid.com/api/context-menu/ Source: src/content/docs/api/context-menu.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { /** * Context menu configuration for row and body-cell targets. */ rowContextMenu?: ContextMenuConfig; /** * Context menu configuration for column-header targets. */ columnContextMenu?: ContextMenuConfig; /** * Legacy/shared context menu configuration. Used as a fallback when target-specific * row/column configs are not provided. */ contextMenu?: ContextMenuConfig; /** Kebab-case alias for framework/native custom element bindings. */ 'row-context-menu'?: ContextMenuConfig; /** Kebab-case alias for framework/native custom element bindings. */ 'column-context-menu'?: ContextMenuConfig; /** Kebab-case alias for framework/native custom element bindings. */ 'context-menu'?: ContextMenuConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * The context menu configuration for row and body-cell targets. * * @example * ```typescript * grid.additionalData = { * rowContextMenu: { items: [{ name: 'Edit', action: () => console.log('Edit action') }] }, * }; * ``` * * Prefer the direct `grid.rowContextMenu` property when the framework wrapper supports it. */ rowContextMenu?: ContextMenuConfig; /** * The context menu configuration for column-header targets. * * @example * ```typescript * grid.additionalData = { * columnContextMenu: { items: [{ name: 'Autosize', action: () => console.log('Autosize') }] }, * }; * ``` * * Prefer the direct `grid.columnContextMenu` property when the framework wrapper supports it. */ columnContextMenu?: ContextMenuConfig } ``` ## Plugin API ### `ContextMenuPlugin` Adds configurable context menus to RevoGrid. The plugin supports separate `rowContextMenu` and `columnContextMenu` configurations while keeping `contextMenu` as a legacy fallback. Column menus receive the current column, virtual column index, and column section, so a single `columnContextMenu` can resolve different menu items for regular, left-pinned, right-pinned, or specific columns. **Example:** ```typescript * ```typescript * import { ContextMenuPlugin } from '@revolist/revogrid-pro' * * const grid = document.createElement('revo-grid'); * grid.plugins = [ContextMenuPlugin]; * grid.rowContextMenu = { items: [{ name: 'Delete row' }] }; * grid.columnContextMenu = { * items: [{ name: 'Autosize column' }], * resolve(context) { * if (context.target === 'column' && context.columnType === 'colPinStart') { * return { items: [{ name: 'Unpin left column' }] }; * } * }, * }; * ``` ``` #### Dependencies - **Event integration** `RowHeaderPlugin`: Integrates with RowHeaderPlugin row menu events when present. ```ts class ContextMenuPlugin { /** * Applies defaults to a partial context menu config. * * @param config - User-provided menu config. * @param targetSelectors - Default selectors for the target-specific config. */ getConfig(config: Partial =; /** * Hides the context menu popup without clearing the active config. */ close(); /** * Opens the context menu for a DOM target. * * If `menuTarget` is provided, that target config is used directly. Otherwise * the plugin inspects the DOM target and chooses the column menu before the row * menu. The selected config is passed through `resolve` before rendering. * * @param target - Element that should anchor or identify the menu target. * @param event - Pointer event used for positioning and resolver context. * @param menuTarget - Optional explicit target used by row-header menu events. */ async open(target: EventTarget | null, event?: MouseEvent, menuTarget?: ContextMenuTarget); clearSubscriptions(); } ``` --- ### `ContextMenuTarget` Supported high-level targets for the context menu. `row` covers body cells and row-menu buttons. `column` covers column header cells, including pinned column sections. ```ts /** * Supported high-level targets for the context menu. * * `row` covers body cells and row-menu buttons. `column` covers column header * cells, including pinned column sections. */ export type ContextMenuTarget = 'row' | 'column'; ``` --- ### `ContextMenuOpenContextBase` Shared context passed to dynamic menu resolvers and menu item actions. ```ts /** * Shared context passed to dynamic menu resolvers and menu item actions. */ export type ContextMenuOpenContextBase = { /** The active menu target type. */ target: ContextMenuTarget; /** Element that caused the menu to open. */ triggerElement: HTMLElement; /** Mouse event that opened the menu, when opened from pointer interaction. */ triggerEvent?: MouseEvent; /** Grid element that owns the plugin instance. */ revogrid: HTMLRevoGridElement; /** Core plugin providers for data, dimensions, selection, columns, viewport, and plugins. */ providers: PluginProviders; }; ``` --- ### `ContextMenuColumnGroup` ```ts export type ContextMenuColumnGroup = { /** Display label rendered by the grouped header. */ name: string; /** Source child columns covered by this grouped header. */ children: (ColumnGrouping | ColumnRegular)[]; /** Physical indexes covered by this column group. */ indexes: number[]; /** Grouping row depth in the header. */ depth?: number; }; ``` --- ### `ColumnContextMenuOpenContext` (Extended from `index.ts`) Context available when a menu opens from a column header target. ```ts /** * Context available when a menu opens from a column header target. */ export type ColumnContextMenuOpenContext = ContextMenuOpenContextBase & { target: 'column'; /** Column model resolved from the header section and virtual column index. */ column?: ColumnRegular; /** Column group model resolved when opening from a grouped header cell. */ columnGroup?: ContextMenuColumnGroup; /** Physical column indexes covered by this menu target when it is a grouped header cell. */ columnIndexes?: number[]; /** Grouping row depth when opening from a grouped header cell. */ columnGroupDepth?: number; /** Virtual column index inside the current column section. */ columnIndex?: number; /** Column section that opened the menu: regular, left-pinned, or right-pinned. */ columnType?: DimensionCols; }; ``` --- ### `RowContextMenuOpenContext` (Extended from `index.ts`) Context available when a menu opens from a row/body target. ```ts /** * Context available when a menu opens from a row/body target. */ export type RowContextMenuOpenContext = ContextMenuOpenContextBase & { target: 'row'; }; ``` --- ### `ContextMenuOpenContext` Union of all target-specific open contexts. ```ts /** * Union of all target-specific open contexts. */ export type ContextMenuOpenContext = ColumnContextMenuOpenContext | RowContextMenuOpenContext; ``` --- ### `ContextMenuConfigResolver` Dynamic menu resolver. Return `undefined` to keep the base config, return a partial config to override settings/items for this invocation, or return `null` to suppress the menu. **Example:** ```typescript * ```ts * const columnContextMenu = { * items: defaultItems, * resolve(context) { * if (context.target === 'column' && context.columnType === 'colPinStart') { * return { items: pinnedColumnItems }; * } * }, * }; * ``` ``` ```ts /** * Dynamic menu resolver. * * Return `undefined` to keep the base config, return a partial config to override * settings/items for this invocation, or return `null` to suppress the menu. * * @example * ```ts * const columnContextMenu = { * items: defaultItems, * resolve(context) { * if (context.target === 'column' && context.columnType === 'colPinStart') { * return { items: pinnedColumnItems }; * } * }, * }; * ``` */ export type ContextMenuConfigResolver = ( context: ContextMenuOpenContext, ) => Partial | null | undefined; ``` --- ### `ContextMenuActionContext` Context passed as the fifth argument to `ContextMenuItem.action`. ```ts /** * Context passed as the fifth argument to `ContextMenuItem.action`. */ export type ContextMenuActionContext = { /** Grid element that owns the plugin instance. */ revogrid: HTMLRevoGridElement, /** Core plugin providers for data, dimensions, selection, columns, viewport, and plugins. */ providers: PluginProviders, /** Context captured when this menu was opened. */ menu?: ContextMenuOpenContext, }; ``` --- ### `ContextMenuItem` Single menu item definition. ```ts /** * Single menu item definition. */ export type ContextMenuItem = { /** Text or dynamic text rendered inside the item. */ name: string | ((focused?: Cell | null, range?: RangeArea | null) => string); /** CSS class added to the `
  • ` item. */ class?: string; /** Icon rendered before the item name. Accepts CSS classes or inline SVG markup. */ icon?: string; /** CSS class added to the `
  • ` item, optionally calculated from selection state. */ rowClass?: string | ((item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null) => string); /** Hide the item statically or from current selection state. */ hidden?: boolean | ((item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null) => boolean); /** Disable the item statically or from current selection state while keeping it visible. */ disabled?: boolean | ((item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null) => boolean); /** Called when the item is selected. */ action?: (event: MouseEvent, focused?: Cell | null, range?: RangeArea | null, close?: () => void, context?: ContextMenuActionContext) => void; /** Keep the popup open after this item action runs. */ keepOpen?: boolean; /** Custom item renderer. */ template?: (h: HyperFunc, item: ContextMenuItem, focused?: Cell | null, range?: RangeArea | null, close?: () => void) => any; }; ``` --- ### `ContextMenuConfig` Context menu configuration. Use `rowContextMenu` for row/body targets and `columnContextMenu` for header targets. `resolve` can refine either menu at open time for column sections, individual columns, or any other target-specific condition. ```ts /** * Context menu configuration. * * Use `rowContextMenu` for row/body targets and `columnContextMenu` for header * targets. `resolve` can refine either menu at open time for column sections, * individual columns, or any other target-specific condition. */ export type ContextMenuConfig = { /** Items rendered in the popup. */ items: ContextMenuItem[]; /** Open the menu on the native `contextmenu` event. Defaults to `true`. */ rightClick?: boolean; /** Open the menu on left click. Defaults to `false`. */ leftClick?: boolean; /** CSS selectors that can trigger this menu. */ targetSelectors?: string[]; /** Position the popup relative to the target element instead of pointer coordinates. */ anchorToTarget?: boolean; /** * Resolves a menu override for the current target before the menu opens. * Return `null` to suppress the menu for that target, `undefined` to use the base config, * or a partial config to override items/settings for this invocation. */ resolve?: ContextMenuConfigResolver; }; ``` --- ### `renderMenuItemIcon` ```ts export function renderMenuItemIcon(icon?: string); ``` --- ### `contextPopUp` Extra VNode renderer registered by `ContextMenuPlugin`. It reads the plugin instance state (`config`, `activeContext`, selection, and close handler) and renders the current popup items. Menu item actions receive the latest `activeContext`, which lets column menu actions know the exact pinned section and column that opened the menu. ```ts export function contextPopUp(this:; ``` --- ### `POP_EL` Registered extra VNode name for the context-menu popup. The plugin uses this value to replace any previous popup renderer when the plugin is re-created, preventing duplicate popup nodes. ```ts POP_EL: string; ``` --- # Core URL: https://pro.rv-grid.com/api/core/ Source: src/content/docs/api/core.md ### `CorePlugin` ```ts class CorePlugin { /** * Observe attribute changes on the grid element * @param attrName - The attribute name to observe (supports both kebab-case and camelCase) * @param callback - Callback function when attribute changes * * Note: * This observer tracks DOM attribute mutations only (e.g. setAttribute). * It does not react to direct property assignments like grid.someProp = value. */ observeAttribute(attrName: string, callback: (value: string | null) => void); /** * Stop observing an attribute * @param attrName - The attribute name to stop observing */ unobserveAttribute(attrName: string); observeProperty( propName: string, callback: (value: T) => void, ); unobserveProperty(propName: string); /** * Set the trimmed state for a column */ setColumnTrimmed(trimmed: Record>, type: DimensionCols); /** * Destroy plugin and clean up all observers */ destroy(); } ``` --- # Dates URL: https://pro.rv-grid.com/api/dates/ Source: src/content/docs/api/dates.md ### `addDays` Adds calendar days using UTC math. Datetime inputs preserve datetime output; date-only inputs return date-only output. ```ts export function addDays(date: string, days: number): ISODateString; ``` --- ### `subtractDays` ```ts export function subtractDays(date: string, days: number): ISODateString; ``` --- ### `addHours` ```ts export function addHours(date: string, hours: number): ISODateTimeString; ``` --- ### `addMinutes` ```ts export function addMinutes(date: string, minutes: number): ISODateTimeString; ``` --- ### `addMonths` ```ts export function addMonths(date: string, months: number): ISODateString; ``` --- ### `diffInDays` ```ts export function diffInDays(startDate: string, endDate: string): number; ``` --- ### `diffInCalendarDays` ```ts export function diffInCalendarDays(startDate: string, endDate: string): number; ``` --- ### `diffInTaskDurationDays` Returns the Gantt duration between two task dates. Date-only ranges are inclusive; datetime ranges use elapsed days and keep a non-zero positive duration at one minute minimum. ```ts export function diffInTaskDurationDays(startDate: string, endDate: string): number; ``` --- ### `diffInHours` ```ts export function diffInHours(startDate: string, endDate: string): number; ``` --- ### `diffInMinutes` ```ts export function diffInMinutes(startDate: string, endDate: string): number; ``` --- ### `compareIsoDates` ```ts export function compareIsoDates(left: string, right: string): number; ``` --- ### `isBefore` ```ts export function isBefore(left: string, right: string): boolean; ``` --- ### `isAfter` ```ts export function isAfter(left: string, right: string): boolean; ``` --- ### `minDate` ```ts export function minDate(left: string, right: string): ISODateString; ``` --- ### `maxDate` ```ts export function maxDate(left: string, right: string): ISODateString; ``` --- ### `shiftDate` ```ts export function shiftDate(date: string, deltaDays: number): ISODateString; ``` --- ### `shiftDateRange` ```ts export function shiftDateRange(range: T, deltaDays: number): T; ``` --- ### `shiftDateRanges` ```ts export function shiftDateRanges( ranges: readonly T[] | undefined, deltaDays: number, ): readonly T[] | undefined; ``` --- ### `startOfWeek` ```ts export function startOfWeek(date: string): ISODateString; ``` --- ### `startOfMonth` ```ts export function startOfMonth(date: string): ISODateString; ``` --- ### `startOfQuarter` ```ts export function startOfQuarter(date: string): ISODateString; ``` --- ### `startOfYear` ```ts export function startOfYear(date: string): ISODateString; ``` --- ### `getWeekStartIsoDate` Returns the UTC Monday for the week containing `date`. ```ts export function getWeekStartIsoDate(date: Date): ISODateString; ``` --- ### `getMonthStartIsoDate` ```ts export function getMonthStartIsoDate(date: Date): ISODateString; ``` --- ### `getIsoWeekNumber` ```ts export function getIsoWeekNumber(date: Date): number; ``` --- ### `todayIsoDate` ```ts export function todayIsoDate(): ISODateString; ``` --- ### `createDateList` Returns an inclusive date list and throws when the range is invalid. ```ts export function createDateList( startDate: string, endDate: string, options:; ``` --- ### `createSafeDateList` Returns an inclusive date list or `[]` for invalid input. Use this when remote/user data should not throw during projection. ```ts export function createSafeDateList(startDate: string, endDate: string): readonly ISODateString[]; ``` --- ### `rangesOverlap` Tests overlap for half-open numeric ranges: `[start, end)` and `[rangeStart, rangeEnd)`. ```ts export function rangesOverlap(start: number, end: number, rangeStart: number, rangeEnd: number): boolean; ``` --- ### `startOfUtcDayMs` Returns the UTC midnight timestamp for the date containing `value`. ```ts export function startOfUtcDayMs(value: string | number | Date): number; ``` --- ### `getDateDayBounds` Returns the half-open UTC day bounds `[startMs, endMs)` for a date. ```ts export function getDateDayBounds(date: string):; ``` --- ### `getDateRangeBounds` Returns half-open UTC bounds for an inclusive date range. ```ts export function getDateRangeBounds(range: ISODateBoundsRange):; ``` --- ### `MINUTE_IN_MS` ```ts MINUTE_IN_MS: number; ``` --- ### `HOUR_IN_MS` ```ts HOUR_IN_MS: number; ``` --- ### `DAY_IN_MS` ```ts DAY_IN_MS: number; ``` --- ### `WEEK_IN_MS` ```ts WEEK_IN_MS: number; ``` --- ### `MINUTE_IN_DAYS` ```ts MINUTE_IN_DAYS: number; ``` --- ### `ISODateRange` ```ts interface ISODateRange { readonly startDate: ISODateString; readonly endDate: ISODateString } ``` --- ### `ISODateBoundsRange` ```ts interface ISODateBoundsRange { readonly start: string; readonly end: string } ``` --- ### `parseIsoDate` Parses date-only strings at UTC midnight instead of local midnight. Datetime strings are delegated to the JavaScript `Date` parser. ```ts export function parseIsoDate(date: string): Date; ``` --- ### `isIsoDateTimeInput` ```ts export function isIsoDateTimeInput(value: string): boolean; ``` --- ### `getIsoWeekdayUtc` ```ts export function getIsoWeekdayUtc(date: Date): number; ``` --- ### `getUtcWeekday` ```ts export function getUtcWeekday(value: string | Date): number; ``` --- ### `getIsoWeekday` ```ts export function getIsoWeekday(value: string | Date): number; ``` --- ### `isValidIsoWeekday` ```ts export function isValidIsoWeekday(value: number): boolean; ``` --- ### `formatIsoDate` ```ts export function formatIsoDate(date: Date): ISODateString; ``` --- ### `formatIsoDateTime` ```ts export function formatIsoDateTime(date: Date): ISODateTimeString; ``` --- ### `isValidIsoDate` ```ts export function isValidIsoDate(value: string | undefined): value is ISODateString; ``` --- ### `isValidIsoDateTime` ```ts export function isValidIsoDateTime(value: string | undefined): value is ISODateTimeString; ``` --- ### `isValidIsoDateValue` Validates either a date-only ISO value or a UTC datetime value. Date-only values must round-trip as `YYYY-MM-DD`; datetimes must round-trip exactly through `toISOString()`. ```ts export function isValidIsoDateValue(value: string | undefined): value is ISODateString; ``` --- ### `normalizeIsoDate` Returns a valid `YYYY-MM-DD` value or throws with a user-facing message. ```ts export function normalizeIsoDate(value: string): ISODateString; ``` --- ### `parseDateTime` Parses scheduler datetime input as UTC. Values without a trailing `Z` are treated as UTC, not local time. ```ts export function parseDateTime(value: string): Date; ``` --- ### `toISODate` ```ts export function toISODate(date: Date): ISODateString; ``` --- ### `toIsoDate` ```ts export function toIsoDate(date: Date): ISODateString; ``` --- ### `ISODateString` ```ts export type ISODateString = `${number}-${number}-${number}` | `${number}-${number}-${number}T${string}`; ``` --- ### `ISODateTimeString` ```ts export type ISODateTimeString = `${number}-${number}-${number}T${string}`; ``` --- ### `ISO_DATE_ONLY_PATTERN` ```ts ISO_DATE_ONLY_PATTERN: RegExp; ``` --- ### `ISO_DATE_PATTERN` ```ts ISO_DATE_PATTERN: RegExp; ``` --- ### `ISO_DATE_PREFIX_PATTERN` ```ts ISO_DATE_PREFIX_PATTERN: RegExp; ``` --- ### `ISO_DATETIME_PATTERN` ```ts ISO_DATETIME_PATTERN: RegExp; ``` --- ### `ISO_DATETIME_INPUT_PATTERN` ```ts ISO_DATETIME_INPUT_PATTERN: RegExp; ``` --- ### `ISO_LOCAL_DATETIME_INPUT_PATTERN` ```ts ISO_LOCAL_DATETIME_INPUT_PATTERN: RegExp; ``` --- ### `ISO_UTC_DATETIME_WITHOUT_ZONE_PATTERN` ```ts ISO_UTC_DATETIME_WITHOUT_ZONE_PATTERN: RegExp; ``` --- ### `parseTimeOfDay` Parses `HH:mm` or `h:mm AM/PM` into minutes from midnight. `24:00` is accepted only when `allowEndOfDay` is enabled. ```ts export function parseTimeOfDay(value: string, options:; ``` --- ### `formatTimeOfDay` ```ts export function formatTimeOfDay(totalMinutes: number): string; ``` --- ### `formatCompactTimeOfDay` ```ts export function formatCompactTimeOfDay(totalMinutes: number): string; ``` --- ### `formatAmPmTimeOfDay` ```ts export function formatAmPmTimeOfDay(totalMinutes: number): string; ``` --- ### `formatDisplayTimeOfDay` ```ts export function formatDisplayTimeOfDay( totalMinutes: number, options:; ``` --- ### `formatDurationMinutes` ```ts export function formatDurationMinutes(totalMinutes: number): string; ``` --- ### `formatDurationDays` ```ts export function formatDurationDays( durationDays: number | undefined | null, options:; ``` --- ### `hasAmPmMarker` ```ts export function hasAmPmMarker(value: string): boolean; ``` --- ### `getDateMinutes` ```ts export function getDateMinutes(date: Date): number; ``` --- ### `createDateTime` Creates a UTC datetime from a date-only value and minutes since midnight. ```ts export function createDateTime(date: string, minutes: number): ISODateTimeString; ``` --- ### `createDateTimeFromTimeOfDay` ```ts export function createDateTimeFromTimeOfDay(date: string, time: string, dayOffset = 0): ISODateTimeString; ``` --- ### `getLocalDateTimeAsUtcIsoDateTime` Serializes a local `Date` clock value as a UTC ISO datetime. This preserves the user's local wall-clock fields for scheduler form inputs. ```ts export function getLocalDateTimeAsUtcIsoDateTime(date: Date): ISODateTimeString; ``` --- ### `shiftDateTime` ```ts export function shiftDateTime(value: string, deltaMinutes: number): ISODateTimeString; ``` --- ### `getDateTimeDurationMinutes` ```ts export function getDateTimeDurationMinutes(startDateTime: string, endDateTime: string): number; ``` --- ### `getDurationMinutes` ```ts export function getDurationMinutes(event:; ``` --- ### `getClampedDateTimeDurationMinutes` Returns an event duration in minutes, clamped to zero for inverted ranges. ```ts export function getClampedDateTimeDurationMinutes(startDateTime: string, endDateTime: string): number; ``` --- ### `snapMinutes` ```ts export function snapMinutes(minutes: number, snap: number): number; ``` --- ### `snapDateTime` Snaps a UTC datetime to the nearest minute interval within the same UTC day. ```ts export function snapDateTime(value: string, snap: number): ISODateTimeString; ``` --- ### `normalizeTimelineWeekStartsOn` ```ts export function normalizeTimelineWeekStartsOn(value: unknown): TimelineWeekStartsOn; ``` --- ### `startOfTimelineWeek` ```ts export function startOfTimelineWeek(date: string, weekStartsOn: TimelineWeekStartsOn): ISODateString; ``` --- ### `parseTimelineDateValue` Parses a timeline boundary as UTC for deterministic scale math. ```ts export function parseTimelineDateValue(date: string): Date; ``` --- ### `startOfTimelineUnit` Returns the UTC-aligned start for a timeline unit. Intraday units preserve datetime precision; day and larger units return date-only values. ```ts export function startOfTimelineUnit( unit: TimelineUnit, date: string, weekStartsOn: TimelineWeekStartsOn = 0, ): ISODateString | ISODateTimeString; ``` --- ### `addTimelineUnit` Adds a timeline unit while preserving the timeline's UTC normalization rules. ```ts export function addTimelineUnit( unit: TimelineUnit, date: string, amount: number, ): ISODateString | ISODateTimeString; ``` --- ### `getTimelineEndExclusiveDate` Converts an inclusive Gantt date boundary to an exclusive timeline boundary. Datetime values already represent instants and are returned unchanged. ```ts export function getTimelineEndExclusiveDate(date: string): ISODateString | ISODateTimeString; ``` --- ### `getTimelineQuarter` ```ts export function getTimelineQuarter(date: string): number; ``` --- ### `formatTimelineDateLabel` Formats timeline labels in UTC so headers do not drift by local timezone. ```ts export function formatTimelineDateLabel( date: string, locale: string, options: Intl.DateTimeFormatOptions, ): string; ``` --- ### `startOfHour` ```ts export function startOfHour(date: string): ISODateTimeString; ``` --- ### `startOfMinute` ```ts export function startOfMinute(date: string): ISODateTimeString; ``` --- ### `TimelineUnit` ```ts export type TimelineUnit = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; ``` --- ### `TimelineWeekStartsOn` ```ts export type TimelineWeekStartsOn = 0 | 1; ``` --- ### `isWorkingDate` Checks a date against calendar working weekdays and holiday exclusions. ```ts export function isWorkingDate( date: ISODateString, calendar: WorkingCalendar | null | undefined, ): boolean; ``` --- ### `moveToNextWorkingDate` Moves forward until a working date is found. Returns the original date when no working date is found before `iterationLimit`. ```ts export function moveToNextWorkingDate( date: ISODateString, calendar: WorkingCalendar | null | undefined, iterationLimit = 3660, ): ISODateString; ``` --- ### `moveToNextWorkingDateTime` ```ts export function moveToNextWorkingDateTime( date: ISODateString, calendar: WorkingCalendar | null | undefined, iterationLimit = 3660, ): ISODateString; ``` --- ### `moveToPreviousWorkingDate` Moves backward until a working date is found. Returns the original date when no working date is found before `iterationLimit`. ```ts export function moveToPreviousWorkingDate( date: ISODateString, calendar: WorkingCalendar | null | undefined, iterationLimit = 3660, ): ISODateString; ``` --- ### `addWorkingDays` Adds working days, excluding the start date from the counted offset. ```ts export function addWorkingDays( startDate: ISODateString, days: number, calendar: WorkingCalendar | null | undefined, ): ISODateString; ``` --- ### `subtractWorkingDays` Subtracts working days, excluding the start date from the counted offset. ```ts export function subtractWorkingDays( startDate: ISODateString, days: number, calendar: WorkingCalendar | null | undefined, ): ISODateString; ``` --- ### `offsetByLagCalendar` Applies dependency lag using either calendar days or working days. ```ts export function offsetByLagCalendar( date: ISODateString, days: number, calendar: WorkingCalendar | null | undefined, options: LagCalendarOptions, ): ISODateString; ``` --- ### `resolveTaskEndDate` Resolves a task end date from start and duration. Date-only durations are inclusive; datetime durations support fractional days. ```ts export function resolveTaskEndDate( startDate: ISODateString, durationDays: number, calendar: WorkingCalendar | null | undefined, options: WorkingDayOptions, ): ISODateString; ``` --- ### `resolveTaskStartDateFromEnd` Resolves a task start date from end and duration. Date-only durations are inclusive; datetime durations support fractional days. ```ts export function resolveTaskStartDateFromEnd( endDate: ISODateString, durationDays: number, calendar: WorkingCalendar | null | undefined, options: WorkingDayOptions, ): ISODateString; ``` --- ### `countWorkingDaysInclusive` Counts working dates inclusively between start and end. ```ts export function countWorkingDaysInclusive( startDate: ISODateString, endDate: ISODateString, calendar: WorkingCalendar | null | undefined, ): number; ``` --- ### `countWorkingDurationDays` ```ts export function countWorkingDurationDays( startDate: ISODateString, endDate: ISODateString, calendar: WorkingCalendar | null | undefined, ): number; ``` --- ### `snapToWorkingDate` Snaps a date to the nearest working date in the requested direction. Returns the original date when no working date is found before `iterationLimit`. ```ts export function snapToWorkingDate( date: ISODateString, calendar: WorkingCalendar | null | undefined, direction: 'forward' | 'back' = 'forward', iterationLimit = 366, ): ISODateString; ``` --- ### `WorkingTimeRange` ```ts interface WorkingTimeRange { readonly start: string; readonly end: string } ``` --- ### `WorkingCalendar` ```ts interface WorkingCalendar { readonly workingDays: readonly number[]; readonly holidays: readonly string[]; readonly workingHours?: WorkingTimeRange | readonly WorkingTimeRange[] } ``` --- ### `WorkingDayOptions` ```ts interface WorkingDayOptions { readonly excludeHolidaysFromDuration: boolean } ``` --- ### `LagCalendarOptions` ```ts interface LagCalendarOptions { readonly lagCalendar: 'calendar-days' | 'working-days' } ``` --- # Dimension Animation URL: https://pro.rv-grid.com/api/dimension-animation/ Source: src/content/docs/api/dimension-animation.md ## Module Extensions ### `HTMLRevoGridElement` (Extended from `global`) ```ts interface HTMLRevoGridElement { dimensionAnimation?: DimensionAnimationConfig } ``` --- ### `AdditionalData` (Extended from `@revolist/revogrid`) ```ts interface AdditionalData { /** * Additional data property dimensionAnimation * @deprecated Use `grid.dimensionAnimation` instead. */ dimensionAnimation?: DimensionAnimationConfig } ``` --- ### `HTMLRevoGridElementEventMap` (Extended from `global`) ```ts interface HTMLRevoGridElementEventMap { dimensionanimate: DimensionAnimationEventDetail; beforedimensionanimate: DimensionAnimationEventDetail; afterdimensionanimate: DimensionAnimationEventDetail } ``` ## Plugin API ### `DimensionAnimationPlugin` RevoGrid Pro plugin that animates dimension sizes without owning visibility state. Rows and columns can be addressed by physical source index or by current visual index. Callers own follow-up state changes such as applying trim or mutating proxy order. ```ts class DimensionAnimationPlugin { /** Collapse dimensions by animating their current size to zero. */ collapse(indexes: DimensionAnimationIndexes, options: DimensionAnimationOptions =; /** Expand dimensions from zero size to their cached/default size. */ expand(indexes: DimensionAnimationIndexes, options: DimensionAnimationOptions =; /** Animate explicit dimension-size frames. */ animate(frames: DimensionAnimationFrameInput[], options: DimensionAnimationOptions =; /** Cancels in-flight animations. Omit arguments to cancel all animations. */ cancel(type?: MultiDimensionType, indexes?: DimensionAnimationIndexes); destroy(); } ``` --- ### `DimensionAnimationAction` Supported dimension size animation operations for `DimensionAnimationPlugin`. ```ts /** Supported dimension size animation operations for `DimensionAnimationPlugin`. */ export type DimensionAnimationAction = 'collapse' | 'expand' | 'animate'; ``` --- ### `DimensionAnimationEasing` Animation easing used when interpolating dimension sizes. - `linear`: direct progress mapping. - `easeOutCubic`: default easing, fast start with soft finish. - function: receives normalized progress from `0` to `1`. ```ts /** * Animation easing used when interpolating dimension sizes. * * - `linear`: direct progress mapping. * - `easeOutCubic`: default easing, fast start with soft finish. * - function: receives normalized progress from `0` to `1`. */ export type DimensionAnimationEasing = | 'linear' | 'easeOutCubic' | ((progress: number) => number); ``` --- ### `DimensionAnimationConfig` Plugin-level configuration for dimension size animation behavior. ```ts interface DimensionAnimationConfig { /** Animation duration in milliseconds. Use `0` to apply synchronously. */ duration?: number; /** Easing function or named easing. Defaults to `easeOutCubic`. */ easing?: DimensionAnimationEasing; /** Dimensions affected by requests that do not specify a single `type`. */ types?: MultiDimensionType[] } ``` --- ### `DimensionAnimationOptions` (Extended from `index.ts`) Per-call options for controller methods and event requests. ```ts interface DimensionAnimationOptions { /** Single dimension to target for the request. */ type?: MultiDimensionType; /** Whether `indexes` are source physical indexes or current visible indexes. */ indexKind?: DimensionAnimationIndexKind } ``` --- ### `DimensionAnimationIndexes` Dimension indexes accepted by the controller. Iterables such as arrays are treated as indexes to affect. Records use truthy values to mark affected indexes. ```ts /** * Dimension indexes accepted by the controller. * * Iterables such as arrays are treated as indexes to affect. Records use * truthy values to mark affected indexes. */ export type DimensionAnimationIndexes = Iterable | Record; ``` --- ### `DimensionAnimationIndexKind` ```ts export type DimensionAnimationIndexKind = 'physical' | 'visual'; ``` --- ### `DimensionAnimationIndexesByType` ```ts export type DimensionAnimationIndexesByType = Partial>; ``` --- ### `DimensionAnimationFrameInput` Explicit animation frame input used by callers that already know visual positions. ```ts interface DimensionAnimationFrameInput { /** Dimension containing this frame. Defaults to request `type` or `rgRow`. */ type?: MultiDimensionType; /** Current visual index in the dimension. */ visualIndex: number; /** Optional physical index used for cancellation/cache identity. */ physicalIndex?: number; /** Start size. If omitted, the current/cached size is used. */ from?: number; /** End size. If omitted, the current/cached size is used. */ to?: number } ``` --- ### `DimensionAnimationRequest` (Extended from `index.ts`) Event/controller request payload for dimension animation operations. ```ts interface DimensionAnimationRequest { /** Operation to perform. */ action: DimensionAnimationAction; /** Indexes for `collapse` and `expand`. */ indexes?: DimensionAnimationIndexes; /** Target indexes keyed by dimension. */ byType?: DimensionAnimationIndexesByType; /** Explicit frame inputs for `animate`. */ frames?: DimensionAnimationFrameInput[] } ``` --- ### `DimensionAnimationResult` Result emitted for each dimension touched by a request. ```ts interface DimensionAnimationResult { /** Operation that produced this result. */ action: DimensionAnimationAction; /** Dimension affected by this result. */ type: MultiDimensionType; /** Input indexes included in the operation. Empty for explicit frame requests. */ indexes: number[]; /** Whether the animation was cancelled before normal completion. */ cancelled: boolean; /** Internal frames resolved and animated for this result. */ frames: DimensionAnimationFrame[] } ``` --- ### `DimensionAnimationEventDetail` (Extended from `index.ts`) Detail payload for dimension animation events. ```ts interface DimensionAnimationEventDetail { /** Promise attached synchronously by the plugin for event-driven callers that need completion ordering. */ done?: Promise; /** Filled by the plugin after a `dimensionanimate` event request completes. */ results?: DimensionAnimationResult[] } ``` --- ### `DimensionAnimationController` Public controller API implemented by `DimensionAnimationPlugin`. ```ts interface DimensionAnimationController { /** Collapse dimensions with animation by animating their size to zero. */ collapse(indexes: DimensionAnimationIndexes, options?: DimensionAnimationOptions): Promise; /** Expand dimensions by starting them at zero, then animating to their cached/default size. */ expand(indexes: DimensionAnimationIndexes, options?: DimensionAnimationOptions): Promise; /** Animate explicit dimension-size frames. */ animate(frames: DimensionAnimationFrameInput[], options?: DimensionAnimationOptions): Promise; /** Cancel matching in-flight animations. */ cancel(type?: MultiDimensionType, indexes?: DimensionAnimationIndexes): void } ``` --- ### `DimensionAnimationFrame` Internal dimension-size animation frame resolved from physical to visual index. ```ts interface DimensionAnimationFrame { type: MultiDimensionType; physicalIndex: number; visualIndex: number; from: number; to: number } ``` --- ### `DimensionAnimationSizeCache` Internal cache of sizes keyed by dimension and physical index. ```ts /** Internal cache of sizes keyed by dimension and physical index. */ export type DimensionAnimationSizeCache = Partial>>; ``` --- ### `DimensionAnimationSizePatch` Internal patch of visual sizes passed to the dimension provider. ```ts /** Internal patch of visual sizes passed to the dimension provider. */ export type DimensionAnimationSizePatch = ViewSettingSizeProp; ``` --- ### `normalizeDimensionAnimationConfig` Normalizes partial dimension-animation plugin config into the complete runtime shape. ```ts export function normalizeDimensionAnimationConfig( config?: DimensionAnimationConfig, ): NormalizedDimensionAnimationConfig; ``` --- ### `DEFAULT_DIMENSION_ANIMATION_DURATION` Default dimension animation duration, in milliseconds. ```ts DEFAULT_DIMENSION_ANIMATION_DURATION: 180; ``` --- ### `DEFAULT_DIMENSION_ANIMATION_FALLBACK_ROW_SIZE` Last-resort row size used when neither dimension nor grid defaults are configured. ```ts DEFAULT_DIMENSION_ANIMATION_FALLBACK_ROW_SIZE: 24; ``` --- ### `DEFAULT_DIMENSION_ANIMATION_FALLBACK_COLUMN_SIZE` Last-resort column size used when neither dimension nor grid defaults are configured. ```ts DEFAULT_DIMENSION_ANIMATION_FALLBACK_COLUMN_SIZE: 100; ``` --- ### `DEFAULT_DIMENSION_ANIMATION_TYPES` ```ts DEFAULT_DIMENSION_ANIMATION_TYPES: (DimensionRows | DimensionCols)[]; ``` --- ### `NormalizedDimensionAnimationConfig` ```ts export type NormalizedDimensionAnimationConfig = Required>; ``` --- ### `DEFAULT_DIMENSION_ANIMATION_CONFIG` ```ts DEFAULT_DIMENSION_ANIMATION_CONFIG: { duration: number; easing: string; types: (DimensionRows | DimensionCols)[]; }; ``` --- ### `DIMENSION_ANIMATE_EVENT` Event dispatched to request a dimension-size animation from the grid element. The plugin attaches `detail.done` synchronously so event-driven integrations can await completion before running follow-up logic such as trimming or reorder. ```ts DIMENSION_ANIMATE_EVENT: string; ``` --- ### `BEFORE_DIMENSION_ANIMATE_EVENT` Cancelable event emitted before a dimension animation operation starts. ```ts BEFORE_DIMENSION_ANIMATE_EVENT: string; ``` --- ### `AFTER_DIMENSION_ANIMATE_EVENT` Event emitted after a dimension animation operation finishes or is cancelled. ```ts AFTER_DIMENSION_ANIMATE_EVENT: string; ``` --- ### `DimensionAnimationAnimator` Manages dimension size animation frames and cancellation. ```ts class DimensionAnimationAnimator { /** Animates frame sizes and returns `false` if the run is cancelled. */ animate( frames: DimensionAnimationFrame[], options: NormalizedDimensionAnimationConfig, ): Promise; /** Cancels matching dimension animations. Omitted arguments cancel all animations. */ cancel(type?: MultiDimensionType, indexes?: DimensionAnimationIndexes); /** Applies interpolated sizes for a group of animation frames. */ applyFrameSizes(type: MultiDimensionType, frames: DimensionAnimationFrame[], progress: number); } ``` --- ### `resolveDimensionAnimationEasing` Resolves a named or custom easing into a normalized progress function. ```ts export function resolveDimensionAnimationEasing( easing: DimensionAnimationEasing, ): (progress: number) => number; ``` --- ### `normalizeDimensionAnimationIndexes` Converts iterable or record input into a finite index array. ```ts export function normalizeDimensionAnimationIndexes( indexes: DimensionAnimationIndexes | undefined, ): number[]; ``` --- ### `resolveDimensionAnimationRequestIndexes` Resolves a request into dimension buckets with normalized indexes. ```ts export function resolveDimensionAnimationRequestIndexes( request: DimensionAnimationRequest, defaultTypes: MultiDimensionType[], ): Partial>; ``` --- # Dropdown URL: https://pro.rv-grid.com/api/dropdown/ Source: src/content/docs/api/dropdown.md ### `DropdownPopupContent` ```ts export function DropdownPopupContent(; ``` --- ### `DropdownContent` ```ts export function DropdownContent(; ``` --- ### `useDropdownService` ```ts export function useDropdownService( options: DropdownOption[], value: T | T[] | undefined, onChange: (value: T | T[]) => void, config: DropdownConfig =; ``` --- ### `DropdownRenderable` ```ts export type DropdownRenderable = any; ``` --- ### `DropdownVNode` ```ts export type DropdownVNode = any; ``` --- ### `DropdownStyle` ```ts export type DropdownStyle = string | Record; ``` --- ### `DropdownOption` ```ts // Types export type DropdownOption = { value: T; label: string; disabled?: boolean; [key: string]: any; }; ``` --- ### `SortDirection` ```ts export type SortDirection = 'asc' | 'desc' | 'none'; ``` --- ### `FilterFunction` ```ts export type FilterFunction = ( option: DropdownOption, searchValue: string, ) => boolean; ``` --- ### `SortFunction` ```ts export type SortFunction = ( a: DropdownOption, b: DropdownOption, direction: SortDirection, ) => number; ``` --- ### `DropdownConfig` ```ts export type DropdownConfig = { // Search configuration search?: boolean; searchPlaceholder?: string; filterFunction?: FilterFunction; // Sorting configuration sortDirection?: SortDirection; sortFunction?: SortFunction; // Behavior configuration closeOnClickOutside?: boolean; autoFocus?: boolean; autoOpen?: boolean; multiSelect?: boolean; autocomplete?: boolean; /** Initial text for searchable dropdowns, used by grid quick-edit. */ initialSearch?: string; /** Close the dropdown when Tab is pressed inside the open popup. */ closeOnTab?: boolean; // Portal configuration portalTarget?: HTMLElement | null; // Accessibility ariaLabel?: string; ariaLabelledBy?: string; ariaDescribedBy?: string; // Layout configuration maxHeight?: number; theme?: string | null; // Custom class for popup popupClassName?: string; }; ``` --- ### `DropdownProps` ```ts // Types export type DropdownProps = { // Core props source: DropdownOption[]; value?: T | T[]; placeholder?: string; disabled?: boolean; name?: string; id?: string; className?: string; style?: DropdownStyle; // Configuration config?: DropdownConfig; onChange?: (value: T | T[]) => void; onClose?: (focusNext?: boolean) => void; // Templates renderOption?: ( h: HyperFunc, option: DropdownOption, isSelected: boolean, ) => DropdownRenderable; renderPlaceholder?: ( h: HyperFunc, placeholder: string, children: DropdownRenderable, ) => DropdownRenderable; /** * Render the selected value */ renderSelectedValue?: ( h: HyperFunc, selectedOptions: DropdownOption[], children: DropdownRenderable, ) => DropdownRenderable; renderNoResults?: (h: HyperFunc) => DropdownRenderable; }; ``` --- ### `Dropdown` Dropdown component ```ts export function Dropdown(; ``` --- ### `defineDropdown` Define the dropdown component ```ts defineDropdown: (el: HTMLElement, props: DropdownProps) => void; ``` --- ### `destroyDropdown` ```ts destroyDropdown: (el: HTMLElement) => void; ``` --- ### `DropdownPopup` Dropdown popup component **@param** triggerRef - The reference to the trigger element * **@param** children - The children of the dropdown * **@param** portalTarget - The target element to render the dropdown * **@param** theme - The theme of the dropdown * **@param** dropdownMenuId - The id of the dropdown menu ```ts export function DropdownPopup(; ``` --- # Editor Checkbox URL: https://pro.rv-grid.com/api/editor-checkbox/ Source: src/content/docs/api/editor-checkbox.md ### `editorCheckbox` The `editorCheckbox` is a custom cell editor for RevoGrid that provides a checkbox input to edit boolean values directly within the grid cells. **Features**: - Renders a checkbox input element for cells, allowing users to toggle boolean values (`true/false`) with a simple click. - Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value. - Ensures seamless integration with RevoGrid by providing row and column details in the event payload. **Usage**: - Import `editorCheckbox` and assign it to the `cellTemplate` property of a column in the RevoGrid. - Ideal for columns that require quick boolean editing, such as toggling statuses or enabling/disabling options. ### Example ```typescript import { editorCheckbox } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'enabled', name: 'Enabled', cellTemplate: editorCheckbox, }, ]; ``` **Event Handling**: - The `editorCheckbox` dispatches a `celledit` event whenever the checkbox value changes. - The event detail contains: - `rgCol`: The column index of the edited cell. - `rgRow`: The row index of the edited cell. - `type`: The type of the cell. - `prop`: The property of the cell being edited. - `val`: The new value (`true/false`) after the checkbox toggle. - `preventFocus`: A flag to control grid focus behavior (default: `false`). This editor is particularly useful in scenarios where users need to enable or disable specific options directly from the grid, providing a quick and user-friendly interface for boolean data editing. ```ts editorCheckbox: CellTemplate> | undefined; ``` --- # Editor Counter URL: https://pro.rv-grid.com/api/editor-counter/ Source: src/content/docs/api/editor-counter.md ### `editorCounter` The `editorCounter` is a custom cell editor for RevoGrid that provides plus/minus buttons to increment/decrement numeric values within a specified range directly within the grid cells. **Features**: - Renders plus and minus buttons for easy value adjustment - Supports customizable minimum and maximum values through column properties - Configurable step size for increments/decrements - Shows current value with optional display toggle - Automatically dispatches a `celledit` event upon change - Seamless integration with RevoGrid's data model - Modern, responsive design with customizable theming **Usage**: ```typescript import { editorCounter } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'quantity', name: 'Quantity', cellTemplate: editorCounter, min: 0, max: 100, step: 1, hideValue: false, // Set to true to hide the value display }, ]; ``` **Event Handling**: - Dispatches a `celledit` event on value change - Event detail includes row, column, property, and new value information **Theming**: Customizable through CSS variables: - `--counter-button-size`: Size of plus/minus buttons (default: 24px) - `--counter-value-size`: Font size of the value display (default: 14px) - `--counter-spacing`: Spacing between elements (default: 4px) - `--counter-button-bg`: Background color of buttons - `--counter-button-color`: Color of button icons - `--counter-button-hover-bg`: Button background on hover - `--counter-value-color`: Color of the value text - `--counter-disabled-opacity`: Opacity for disabled buttons ```ts editorCounter: CellTemplate> | undefined; ``` --- # Editor Dropdown URL: https://pro.rv-grid.com/api/editor-dropdown/ Source: src/content/docs/api/editor-dropdown.md ## Module Extensions ### `ColumnRegular` (Extended from `@revolist/revogrid`) ```ts interface ColumnRegular { /** * Configuration for dropdown editor */ dropdown?: DropdownProps; /** * Column-select compatible dropdown source. */ source?: DropdownColumnSourceEntry[]; /** * Object source label field used by dropdown columns. */ labelKey?: string; /** * Object source value field used by dropdown columns. */ valueKey?: string } ``` ## Plugin API ### `DropdownColumnSourceEntry` ```ts export type DropdownColumnSourceEntry = string | Record; ``` --- ### `normalizeDropdownOptions` ```ts normalizeDropdownOptions: (source?: unknown[], labelKey?: string | undefined, valueKey?: string | undefined) => DropdownOption[]; ``` --- ### `normalizeColumnDropdown` ```ts normalizeColumnDropdown: (column?: ColumnRegular> | null | undefined) => DropdownProps | undefined; ``` --- ### `editorDropdown` ```ts editorDropdown: CellTemplate> | undefined; ``` --- ### `DropdownEditor` ```ts class DropdownEditor { async componentDidRender(): Promise; getValue(); beforeDisconnect(); disconnectedCallback(); render(h: HyperFunc); } ``` --- ### `dropdownEditor` ```ts dropdownEditor: typeof DropdownEditor; ``` --- ### `ColumnDropdown` Column type for dropdown editor **Key Features**: - **Cell Template**: Uses the `editorDropdown` template for rendering the dropdown editor. - **Cell Properties**: Applies a small padding to the cell to prevent layout issues. - **Readonly**: Ensures the dropdown is not editable by default. ```ts ColumnDropdown: { beforeSetup: (column: ColumnRegular>) => void; cellTemplate: CellTemplate>; editor: typeof DropdownEditor; cellProperties: (props: CellTemplateProp, ColumnRegular>, ColumnProp>) => { style: { padding: string; }; class: { disabled: false; }; }; }; ``` --- # Editor Row URL: https://pro.rv-grid.com/api/editor-row/ Source: src/content/docs/api/editor-row.md ### `RowEditPlugin` The `RowEditPlugin` is a custom plugin for RevoGrid that provides row-level editing capabilities, allowing users to edit entire rows inline with dedicated action buttons for saving or canceling changes. **Features**: - **Row Editing Mode:** Enables editing of entire rows by rendering editors for all cells in the selected row. - **Inline Editors:** Supports inline editing of multiple cells in a row simultaneously. - **Action Buttons:** Provides intuitive buttons for `Save` and `Cancel` actions: - `Save`: Dispatches `CELL_EDIT_SAVE_EVENT` to persist changes. - `Cancel`: Dispatches `CELL_EDIT_CANCEL_EVENT` to discard changes. - **Event Handling:** Listens for key events to manage row editing: - `CELL_EDIT_EVENT`: Initiates row editing mode. - `CELL_EDIT_SAVE_EVENT`: Saves edited data and updates the grid model. - `CELL_EDIT_CANCEL_EVENT`: Cancels editing and restores original data. - `BEFORE_CELL_RENDER_EVENT`: Ensures cells in the editing row render with editors. **Usage**: 1. **Import and Initialize the Plugin:** ```typescript import { RowEditPlugin } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.plugins = [RowEditPlugin]; ``` 2. **Define Action Column:** Use the `editorRowActionColumn` to render action buttons (`Edit`, `Save`, `Cancel`): ```typescript import { editorRowActionColumn } from '@revolist/revogrid-pro' grid.columns = [ { prop: 'name', name: 'Name' }, { prop: 'age', name: 'Age' }, { prop: 'edit', ...editorRowActionColumn, }, // Action column for row editing ]; ``` Action Buttons: - **Edit Button:** Dispatches `CELL_EDIT_EVENT` to enable row editing. - **Save Button:** Dispatches `CELL_EDIT_SAVE_EVENT` to save changes. - **Cancel Button:** Dispatches `CELL_EDIT_CANCEL_EVENT` to cancel editing and reset the row. **Icon Details**: - **Save Icon:** Embedded SVG icon representing a "Save" action. - **Cancel Icon:** Embedded SVG icon representing a "Cancel" action. - **Edit Icon:** Embedded SVG icon representing an "Edit" action. **Scenarios**: This plugin is particularly useful for applications where users need to edit multiple cells in a row at once. Typical use cases include updating tabular records or form-like data directly within a grid. **Conclusion**: The `RowEditPlugin` enhances RevoGrid’s editing capabilities by offering a flexible row editing solution with intuitive controls, efficient event handling, and seamless data integration. ```ts class RowEditPlugin {} ``` --- ### `editorRowActionColumn` The editorRowActionColumn is a partial implementation of the ColumnRegular interface. It defines the cellTemplate and editor properties for a column that renders action buttons for row editing. ```ts editorRowActionColumn: { cellProperties: (props: CellTemplateProp, ColumnRegular>, ColumnProp>) => { onDblClick: (event: MouseEvent) => void; providers: Providers; previousValue?: any; flashDirection?: CellFlashDirection | undefined; flashState?: any; prop: ColumnProp; model: DataType; column: ColumnRegular>; rowIndex: number; colIndex: number; colType: DimensionCols; type: DimensionRows; data: DataType[]; value?: any; }; cellTemplate: (h: HyperFunc, props: CellTemplateProp, ColumnRegular>, ColumnProp>) => VNode; editor: () => { render(h: HyperFunc): VNode[]; }; }; ``` --- # Editor Slider URL: https://pro.rv-grid.com/api/editor-slider/ Source: src/content/docs/api/editor-slider.md ### `editorSlider` The `editorSlider` is a custom cell editor for RevoGrid that provides a slider input to edit numeric values within a specified range directly within the grid cells. **Features**: - Renders a slider input element for cells, allowing users to select numeric values by dragging a slider handle. - Supports customizable minimum and maximum values through column properties. - Supports visual thresholds with custom CSS classes for different value ranges. - Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value. - Ensures seamless integration with RevoGrid by providing row and column details in the event payload. - Shows current value and visual fill indicator for better UX. - Supports theming through CSS variables. - Optional value display that can be hidden through column properties. **Usage**: - Import `editorSlider` and assign it to the `cellTemplate` property of a column in the RevoGrid. - Specify `min` and `max` values in the column properties to define the slider range. - Use `hideValue` property to hide the numeric value display. - Add `thresholds` property to define custom CSS classes for different value ranges. ### Example ```typescript import { editorSlider } from '@revolist/revogrid-pro' const grid = document.createElement('revo-grid'); grid.columns = [ { prop: 'rating', name: 'Rating', cellTemplate: editorSlider, min: 0, max: 100, hideValue: false, thresholds: [ { value: 70, className: 'high' }, { value: 30, className: 'medium' }, { value: 0, className: 'low' } ] }, ]; ``` **Event Handling**: - The `editorSlider` dispatches a `celledit` event whenever the slider value changes. - The event detail contains: - `rgCol`: The column index of the edited cell. - `rgRow`: The row index of the edited cell. - `type`: The type of the cell. - `prop`: The property of the cell being edited. - `val`: The new numeric value after the slider change. - `preventFocus`: A flag to control grid focus behavior (default: `true`). **Theming**: The slider can be themed using CSS variables: - `--slider-thumb-size`: Size of the slider handle (default: 12px) - `--slider-track-height`: Height of the slider track (default: 4px) - `--slider-value-size`: Font size of the value display (default: 12px) - `--slider-spacing`: Spacing between elements (default: 8px) - `--slider-thumb-bg`: Background color of the slider handle - `--slider-thumb-border`: Border color of the slider handle - `--slider-track-bg`: Background color of the unfilled track - `--slider-fill-start`: Start color of the fill gradient - `--slider-fill-end`: End color of the fill gradient - `--slider-value-color`: Color of the value text These variables inherit from RevoGrid theme variables when available: - `--revo-primary` - `--revo-primary-light` - `--revo-background` - `--revo-border-color` - `--revo-text-color-secondary` ```ts editorSlider: CellTemplate> | undefined; ``` --- # Editor Textarea URL: https://pro.rv-grid.com/api/editor-textarea/ Source: src/content/docs/api/editor-textarea.md ### `TextAreaEditor` The `TextAreaEditor` is a custom external editor for RevoGrid that enables multi-line text editing directly within grid cells using a `