@revolist/react-datagrid 4.23.3 → 4.23.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,12 +13,21 @@
13
13
  <img src="https://badgen.net/bundlephobia/tree-shaking/@revolist/revogrid" alt="Tree shaking"/>
14
14
  <img src="https://img.shields.io/bundlephobia/min/@revolist/revogrid" alt="Bundle size"/>
15
15
  <img src="https://sonarcloud.io/api/project_badges/measure?project=revolist_revogrid&metric=alert_status" alt="Sonar Quality Gate"/>
16
+ <a href="https://github.com/revolist/revogrid/actions/workflows/unit.yml">
17
+ <img src="https://github.com/revolist/revogrid/actions/workflows/unit.yml/badge.svg" alt="Workflow status badge" loading="lazy" height="20">
18
+ </a>
19
+ <a href="https://github.com/revolist/revogrid/actions/workflows/e2e.yml">
20
+ <img src="https://github.com/revolist/revogrid/actions/workflows/e2e.yml/badge.svg" alt="Workflow status badge" loading="lazy" height="20">
21
+ </a>
16
22
  </p>
17
23
 
18
24
 
19
25
  <h3 align="center">Powerful React Data Grid component built on top of <a href="https://github.com/revolist/revogrid" target="_blank">RevoGrid</a>.</h3>
20
26
  <p align="center">
21
- Support Millions of cells and thousands of columns easy and efficiently for fast data rendering. Easy to use.
27
+ Render 1M+ rows, millions of cells, and thousands of columns efficiently with no hard row limit in the grid.
28
+ </p>
29
+ <p align="center">
30
+ Used by some of the largest companies in Europe and the United States.
22
31
  </p>
23
32
 
24
33
  <p align="center">
@@ -37,7 +46,7 @@ Support Millions of cells and thousands of columns easy and efficiently for fast
37
46
 
38
47
  ## Key Features
39
48
 
40
- - **High Performance**: Handles millions of cells in the viewport with a powerful core built by default.
49
+ - **High Performance**: Render 1M+ rows and millions of cells with no hard row limit in the grid. Virtualization keeps the DOM focused on the visible viewport.
41
50
 
42
51
  - **[Accessibility](https://rv-grid.com/guide/wcag)**: Follows WAI-ARIA best practices.
43
52
 
@@ -52,7 +61,7 @@ Support Millions of cells and thousands of columns easy and efficiently for fast
52
61
 
53
62
  - **[Intelligent Virtual DOM](https://rv-grid.com/guide/overview#VNode-Reactive-DOM)**: Smart row recombination to minimize redraws.
54
63
 
55
- - **[Virtual Scroll](https://rv-grid.com/guide/viewports)**: Handles large datasets with infinite scroll.
64
+ - **[Virtual Scroll](https://rv-grid.com/guide/viewports)**: Handles large datasets without rendering every row or column into the DOM.
56
65
 
57
66
  - **[Drag and Drop](https://rv-grid.com/guide/row/order)**: Drag and drop in [rows](https://rv-grid.com/guide/row/order) and [columns](https://rv-grid.com/guide/column/order).
58
67
 
@@ -105,20 +114,17 @@ Support Millions of cells and thousands of columns easy and efficiently for fast
105
114
 
106
115
  - **[Plugin System](https://rv-grid.com/guide/plugin/)**: Create custom plugins or extend existing ones easily.
107
116
 
108
- - **[Formula Support](https://rv-grid.com/guide/cell/formula)**: Evaluate formulas in cell data with Excel-like syntax, including basic arithmetic, statistical functions, and cell references.
109
- - **[Pivot Table](https://rv-grid.com/demo/pivot)**: Transform and analyze data dynamically with drag-and-drop field arrangement, aggregation functions, and interactive filtering capabilities.
110
-
111
- - **[Master Detail/Subtables/Forms](https://rv-grid.com/guide/row/master.pro)**: Expand rows to reveal child data.
112
- - **[Cell/Column/Row Span/Merge](https://rv-grid.com/guide/cell/merge)**: Merge cells to form groups.
113
- - **Auto Merge**: Automatically merges cells with identical values in a column.
114
- - **Form editig**: Edit forms directly within the grid, featuring all necessary fields, including custom options and markdown support for a fast and enhanced data entry experience.
115
-
116
117
  - **Customizations**:
117
118
  - [Column header template](https://rv-grid.com/guide/column/header.template).
118
119
  - [Row header template](https://rv-grid.com/guide/row/headers).
119
120
  - [Cell properties](https://rv-grid.com/guide/cell/) (define custom properties for rendered cells).
120
- - Nested grids: Build a grid inside a grid, showcasing advanced editing options and user interactions for a more dynamic data presentation.
121
- - Context Menu: Build context menus for any grid element - from cells to headers. Cut, copy, paste, add rows, modify columns, and more. Fully customizable with your own actions and behaviors.
121
+ - [Cell template](https://rv-grid.com/guide/cell/renderer) (create your own cell views).
122
+ - [Cell editor](https://rv-grid.com/guide/cell/editor) (use predefined or apply your own custom editors and cell types).
123
+
124
+ - **[AI Agents and MCP](https://rv-grid.com/guide/mcp)**: Connect Codex, Cursor, Claude Code, and VS Code to version-aware RevoGrid docs, examples, migrations, feature availability, and typed API context.
125
+
126
+ - **Rich API & Additional Improvements**: Explore hundreds of other small customizations and improvements in [RevoGrid](https://rv-grid.com/).
127
+
122
128
 
123
129
  - [Cell template](https://rv-grid.com/guide/react/renderer) (create your own cell views).
124
130
  - [Cell editor](https://rv-grid.com/guide/react/editor) (use predefined or apply your own custom editors and cell types).
@@ -321,6 +327,29 @@ If you or your company would like to support the ongoing development of RevoGrid
321
327
  Thank you for supporting RevoGrid! 🙏
322
328
 
323
329
 
330
+ ## Testing
331
+
332
+ [![Unit Tests](https://github.com/revolist/revogrid/actions/workflows/ci-unit.yml/badge.svg?branch=main)](https://github.com/revolist/revogrid/actions/workflows/ci-unit.yml)
333
+ [![E2E Tests](https://github.com/revolist/revogrid/actions/workflows/ci-e2e.yml/badge.svg?branch=main)](https://github.com/revolist/revogrid/actions/workflows/ci-e2e.yml)
334
+
335
+ RevoGrid is thoroughly tested to ensure reliability and stability.
336
+
337
+ | Suite | Command | Scope |
338
+ |---|---|---|
339
+ | Unit | `npm run test` | Services, utilities, pure logic |
340
+ | E2E (Playwright) | `npm run test:e2e` | Real browser rendering & interaction |
341
+
342
+ ### E2E Tests
343
+
344
+ End-to-end tests use [@stencil/playwright](https://www.npmjs.com/package/@stencil/playwright) to run `<revo-grid>` in a real Chromium browser. The dev server starts automatically when you run:
345
+
346
+ ```bash
347
+ npm run test:e2e
348
+ ```
349
+
350
+ Test files live in `e2e/` and share helpers from `e2e/helpers.ts`:
351
+
352
+
324
353
  ## Contributing
325
354
 
326
355
  By getting involved, you'll have the opportunity to enhance your skills, gain valuable experience, and make a significant impact on an innovative project. Your contribution, no matter how big or small, is valuable.
package/dist/index.d.ts CHANGED
@@ -44,11 +44,17 @@ export declare type EditorType = {
44
44
 
45
45
  export declare interface ReactElement extends HTMLElement {
46
46
  _root?: Root;
47
+ _renderedComponent?: RenderedComponent<any>;
48
+ _templateComponent?: ComponentType<any>;
49
+ _templateKey?: string;
47
50
  }
48
51
 
49
52
  export declare interface RenderedComponent<T> {
53
+ el: ReactElement;
50
54
  update: (newProps: T) => void;
51
55
  destroy: () => void;
56
+ scheduleDestroy: () => void;
57
+ cancelDestroy: () => void;
52
58
  }
53
59
 
54
60
  export declare const RevoGrid: ForwardRefExoticComponent<JSX_2.RevoGrid & Omit<HTMLAttributes<HTMLRevoGridElement>, "style"> & StyleReactProps & RefAttributes<HTMLRevoGridElement>>;
@@ -1,13 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => {
4
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- return value;
6
- };
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
4
  import { defineCustomElements } from "@revolist/revogrid/loader";
8
5
  export * from "@revolist/revogrid/loader";
9
6
  export * from "@revolist/revogrid";
10
7
  import { createRoot } from "react-dom/client";
8
+ import { flushSync } from "react-dom";
11
9
  import React, { createElement } from "react";
12
10
  import { jsx } from "react/jsx-runtime";
13
11
  const dashToPascalCase = (str) => str.toLowerCase().split("-").map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
@@ -171,40 +169,85 @@ const createReactComponent = (tagName, ReactComponentContext, manipulatePropsFun
171
169
  };
172
170
  return createForwardRef(ReactComponent, displayName);
173
171
  };
174
- function TemplateConstructor(el, ReactComponent, initialProps, key) {
172
+ function TemplateConstructor(el, ReactComponent, initialProps, key, lastEl = null) {
173
+ if (!el) {
174
+ lastEl == null ? void 0 : lastEl.scheduleDestroy();
175
+ return lastEl;
176
+ }
177
+ if (lastEl && lastEl.el !== el) {
178
+ lastEl.destroy();
179
+ }
180
+ if (!el._root) {
181
+ el._root = createRoot(el);
182
+ }
183
+ const renderedComponent = el._renderedComponent;
184
+ if (renderedComponent && el._templateComponent === ReactComponent && el._templateKey === key) {
185
+ renderedComponent.cancelDestroy();
186
+ renderedComponent.update(initialProps);
187
+ return renderedComponent;
188
+ }
189
+ renderedComponent == null ? void 0 : renderedComponent.destroy();
175
190
  if (!el._root) {
176
191
  el._root = createRoot(el);
177
192
  }
193
+ const root = el._root;
194
+ let destroyFrame = null;
195
+ let destroyed = false;
178
196
  const renderComponent = (props) => {
179
- var _a;
180
197
  const vNode = createElement(ReactComponent, { ...props, key });
181
- (_a = el._root) == null ? void 0 : _a.render(vNode);
198
+ flushSync(() => {
199
+ root.render(vNode);
200
+ });
182
201
  };
183
202
  renderComponent(initialProps);
184
203
  const update = (newProps) => {
185
204
  renderComponent(newProps);
186
205
  };
187
206
  const destroy = () => {
188
- var _a;
189
- (_a = el._root) == null ? void 0 : _a.unmount();
190
- el._root = void 0;
207
+ if (destroyed) return;
208
+ destroyed = true;
209
+ if (destroyFrame !== null) {
210
+ cancelAnimationFrame(destroyFrame);
211
+ destroyFrame = null;
212
+ }
213
+ root.unmount();
214
+ if (el._root === root) {
215
+ el._root = void 0;
216
+ el._renderedComponent = void 0;
217
+ el._templateComponent = void 0;
218
+ el._templateKey = void 0;
219
+ }
220
+ };
221
+ const scheduleDestroy = () => {
222
+ if (destroyFrame !== null) return;
223
+ destroyFrame = requestAnimationFrame(() => {
224
+ destroyFrame = null;
225
+ if (el.isConnected && el._renderedComponent === component) {
226
+ return;
227
+ }
228
+ destroy();
229
+ });
230
+ };
231
+ const cancelDestroy = () => {
232
+ if (destroyFrame === null) return;
233
+ cancelAnimationFrame(destroyFrame);
234
+ destroyFrame = null;
191
235
  };
192
- return { update, destroy };
236
+ const component = { el, update, destroy, scheduleDestroy, cancelDestroy };
237
+ el._renderedComponent = component;
238
+ el._templateComponent = ReactComponent;
239
+ el._templateKey = key;
240
+ return component;
193
241
  }
194
242
  const Template = (ReactComponent, customProps) => {
195
243
  return (h, p, addition) => {
196
- const props = customProps ? { ...customProps, ...p } : p;
197
- props.addition = addition;
244
+ const props = customProps ? { ...customProps, ...p, addition } : { ...p, addition };
198
245
  const key = `${p.prop}-${p.rowIndex || 0}`;
199
246
  let lastEl = null;
200
247
  return h("span", {
248
+ key,
201
249
  ref: (el) => {
202
- var _a;
203
- if (!el) {
204
- (_a = lastEl == null ? void 0 : lastEl.destroy) == null ? void 0 : _a.call(lastEl);
205
- } else {
206
- lastEl = TemplateConstructor(el, ReactComponent, props, key);
207
- }
250
+ lastEl = TemplateConstructor(el, ReactComponent, props, key, lastEl);
208
251
  }
209
252
  });
210
253
  };
@@ -1,12 +1,9 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@revolist/revogrid/loader"), require("@revolist/revogrid"), require("react-dom/client"), require("react"), require("react/jsx-runtime")) : typeof define === "function" && define.amd ? define(["exports", "@revolist/revogrid/loader", "@revolist/revogrid", "react-dom/client", "react", "react/jsx-runtime"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ReactDatagrid = {}, global.RevogridLoader, global.Revogrid, global.ReactDOM, global.React, global.ReactJSXR));
3
- })(this, function(exports2, loader, revogrid, client, React, jsxRuntime) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@revolist/revogrid/loader"), require("@revolist/revogrid"), require("react-dom/client"), require("react-dom"), require("react"), require("react/jsx-runtime")) : typeof define === "function" && define.amd ? define(["exports", "@revolist/revogrid/loader", "@revolist/revogrid", "react-dom/client", "react-dom", "react", "react/jsx-runtime"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ReactDatagrid = {}, global.RevogridLoader, global.Revogrid, global.ReactDOM, global.ReactDOM, global.React, global.ReactJSXR));
3
+ })(this, function(exports2, loader, revogrid, client, ReactDOM, React, jsxRuntime) {
4
4
  "use strict";var __defProp = Object.defineProperty;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __publicField = (obj, key, value) => {
7
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
- return value;
9
- };
6
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
10
7
 
11
8
  const dashToPascalCase = (str) => str.toLowerCase().split("-").map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
12
9
  const camelToDashCase = (str) => str.replace(/([A-Z])/g, (m) => `-${m[0].toLowerCase()}`);
@@ -169,40 +166,85 @@ var __publicField = (obj, key, value) => {
169
166
  };
170
167
  return createForwardRef(ReactComponent, displayName);
171
168
  };
172
- function TemplateConstructor(el, ReactComponent, initialProps, key) {
169
+ function TemplateConstructor(el, ReactComponent, initialProps, key, lastEl = null) {
170
+ if (!el) {
171
+ lastEl == null ? void 0 : lastEl.scheduleDestroy();
172
+ return lastEl;
173
+ }
174
+ if (lastEl && lastEl.el !== el) {
175
+ lastEl.destroy();
176
+ }
177
+ if (!el._root) {
178
+ el._root = client.createRoot(el);
179
+ }
180
+ const renderedComponent = el._renderedComponent;
181
+ if (renderedComponent && el._templateComponent === ReactComponent && el._templateKey === key) {
182
+ renderedComponent.cancelDestroy();
183
+ renderedComponent.update(initialProps);
184
+ return renderedComponent;
185
+ }
186
+ renderedComponent == null ? void 0 : renderedComponent.destroy();
173
187
  if (!el._root) {
174
188
  el._root = client.createRoot(el);
175
189
  }
190
+ const root = el._root;
191
+ let destroyFrame = null;
192
+ let destroyed = false;
176
193
  const renderComponent = (props) => {
177
- var _a;
178
194
  const vNode = React.createElement(ReactComponent, { ...props, key });
179
- (_a = el._root) == null ? void 0 : _a.render(vNode);
195
+ ReactDOM.flushSync(() => {
196
+ root.render(vNode);
197
+ });
180
198
  };
181
199
  renderComponent(initialProps);
182
200
  const update = (newProps) => {
183
201
  renderComponent(newProps);
184
202
  };
185
203
  const destroy = () => {
186
- var _a;
187
- (_a = el._root) == null ? void 0 : _a.unmount();
188
- el._root = void 0;
204
+ if (destroyed) return;
205
+ destroyed = true;
206
+ if (destroyFrame !== null) {
207
+ cancelAnimationFrame(destroyFrame);
208
+ destroyFrame = null;
209
+ }
210
+ root.unmount();
211
+ if (el._root === root) {
212
+ el._root = void 0;
213
+ el._renderedComponent = void 0;
214
+ el._templateComponent = void 0;
215
+ el._templateKey = void 0;
216
+ }
189
217
  };
190
- return { update, destroy };
218
+ const scheduleDestroy = () => {
219
+ if (destroyFrame !== null) return;
220
+ destroyFrame = requestAnimationFrame(() => {
221
+ destroyFrame = null;
222
+ if (el.isConnected && el._renderedComponent === component) {
223
+ return;
224
+ }
225
+ destroy();
226
+ });
227
+ };
228
+ const cancelDestroy = () => {
229
+ if (destroyFrame === null) return;
230
+ cancelAnimationFrame(destroyFrame);
231
+ destroyFrame = null;
232
+ };
233
+ const component = { el, update, destroy, scheduleDestroy, cancelDestroy };
234
+ el._renderedComponent = component;
235
+ el._templateComponent = ReactComponent;
236
+ el._templateKey = key;
237
+ return component;
191
238
  }
192
239
  const Template = (ReactComponent, customProps) => {
193
240
  return (h, p, addition) => {
194
- const props = customProps ? { ...customProps, ...p } : p;
195
- props.addition = addition;
241
+ const props = customProps ? { ...customProps, ...p, addition } : { ...p, addition };
196
242
  const key = `${p.prop}-${p.rowIndex || 0}`;
197
243
  let lastEl = null;
198
244
  return h("span", {
245
+ key,
199
246
  ref: (el) => {
200
- var _a;
201
- if (!el) {
202
- (_a = lastEl == null ? void 0 : lastEl.destroy) == null ? void 0 : _a.call(lastEl);
203
- } else {
204
- lastEl = TemplateConstructor(el, ReactComponent, props, key);
205
- }
247
+ lastEl = TemplateConstructor(el, ReactComponent, props, key, lastEl);
206
248
  }
207
249
  });
208
250
  };
@@ -264,18 +306,16 @@ var __publicField = (obj, key, value) => {
264
306
  exports2.RevoGrid = RevoGrid;
265
307
  exports2.Template = Template;
266
308
  Object.keys(loader).forEach((k) => {
267
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports2, k))
268
- Object.defineProperty(exports2, k, {
269
- enumerable: true,
270
- get: () => loader[k]
271
- });
309
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports2, k)) Object.defineProperty(exports2, k, {
310
+ enumerable: true,
311
+ get: () => loader[k]
312
+ });
272
313
  });
273
314
  Object.keys(revogrid).forEach((k) => {
274
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports2, k))
275
- Object.defineProperty(exports2, k, {
276
- enumerable: true,
277
- get: () => revogrid[k]
278
- });
315
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports2, k)) Object.defineProperty(exports2, k, {
316
+ enumerable: true,
317
+ get: () => revogrid[k]
318
+ });
279
319
  });
280
320
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
281
321
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revolist/react-datagrid",
3
3
  "sideEffects": false,
4
- "version": "4.23.3",
4
+ "version": "4.23.5",
5
5
  "description": "React DataGrid Spreadsheet component with native cell render support",
6
6
  "main": "./dist/react-datagrid.umd.cjs",
7
7
  "module": "./dist/react-datagrid.js",
@@ -64,7 +64,7 @@
64
64
  "homepage": "https://github.com/revolist/revogrid#readme",
65
65
  "license": "MIT",
66
66
  "dependencies": {
67
- "@revolist/revogrid": "4.23.3"
67
+ "@revolist/revogrid": "4.23.5"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@stencil/core": "^4.43.2",