@gridstorm/react 0.1.2

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/dist/index.cjs ADDED
@@ -0,0 +1,1079 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var core = require('@gridstorm/core');
5
+ var domRenderer = require('@gridstorm/dom-renderer');
6
+ var reactDom = require('react-dom');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ // src/GridStorm.tsx
10
+ var GridContext = react.createContext(null);
11
+ function useGridContext() {
12
+ const ctx = react.useContext(GridContext);
13
+ if (!ctx) {
14
+ throw new Error(
15
+ "[GridStorm] Hook must be used within a <GridStorm> component."
16
+ );
17
+ }
18
+ return ctx;
19
+ }
20
+
21
+ // src/renderers/ReactCellRenderer.ts
22
+ var REACT_CELL_RENDERER = /* @__PURE__ */ Symbol.for("gridstorm:reactCellRenderer");
23
+ function reactCellRenderer(Component2) {
24
+ const fn = () => "";
25
+ fn[REACT_CELL_RENDERER] = Component2;
26
+ return fn;
27
+ }
28
+ function isReactCellRenderer(fn) {
29
+ return typeof fn === "function" && REACT_CELL_RENDERER in fn;
30
+ }
31
+ function getReactCellComponent(fn) {
32
+ if (isReactCellRenderer(fn)) {
33
+ return fn[REACT_CELL_RENDERER];
34
+ }
35
+ return null;
36
+ }
37
+
38
+ // src/renderers/ReactHeaderRenderer.ts
39
+ var REACT_HEADER_RENDERER = /* @__PURE__ */ Symbol.for("gridstorm:reactHeaderRenderer");
40
+ function reactHeaderRenderer(Component2) {
41
+ const fn = () => "";
42
+ fn[REACT_HEADER_RENDERER] = Component2;
43
+ return fn;
44
+ }
45
+ function isReactHeaderRenderer(fn) {
46
+ return typeof fn === "function" && REACT_HEADER_RENDERER in fn;
47
+ }
48
+ function getReactHeaderComponent(fn) {
49
+ if (isReactHeaderRenderer(fn)) {
50
+ return fn[REACT_HEADER_RENDERER];
51
+ }
52
+ return null;
53
+ }
54
+ function CellRendererPortalInner(props) {
55
+ const { Component: Component2, rendererProps } = props;
56
+ return /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...rendererProps });
57
+ }
58
+ var CellRendererPortal = react.memo(
59
+ CellRendererPortalInner,
60
+ (prev, next) => prev.nodeVersion === next.nodeVersion && prev.rendererProps.value === next.rendererProps.value && prev.rendererProps.rowIndex === next.rendererProps.rowIndex && prev.rendererProps.colId === next.rendererProps.colId
61
+ );
62
+ function CellEditorPortal(props) {
63
+ const { state, api, engine, EditorComponent, editorParams, gridRootRect } = props;
64
+ const { editing, cellRect } = state;
65
+ const [value, setValue] = react.useState(editing.value);
66
+ const containerRef = react.useRef(null);
67
+ const onValueChange = react.useCallback(
68
+ (newValue) => {
69
+ setValue(newValue);
70
+ engine.commandBus.dispatch("editing:setValue", { value: newValue });
71
+ },
72
+ [engine]
73
+ );
74
+ const stopEditing = react.useCallback(
75
+ (cancel) => {
76
+ api.stopEditing(cancel);
77
+ },
78
+ [api]
79
+ );
80
+ react.useEffect(() => {
81
+ requestAnimationFrame(() => {
82
+ const el = containerRef.current;
83
+ if (!el) return;
84
+ const focusable = el.querySelector(
85
+ "input, textarea, select, [tabindex]"
86
+ );
87
+ focusable?.focus();
88
+ });
89
+ }, []);
90
+ const column = engine.store.getState().columns.find((c) => c.colId === editing.colId);
91
+ const node = engine.store.getState().rowNodes.get(editing.rowId);
92
+ const top = cellRect.top - gridRootRect.top;
93
+ const left = cellRect.left - gridRootRect.left;
94
+ return /* @__PURE__ */ jsxRuntime.jsx(
95
+ "div",
96
+ {
97
+ ref: containerRef,
98
+ className: "gs-editor-portal",
99
+ style: {
100
+ position: "absolute",
101
+ top,
102
+ left,
103
+ width: cellRect.width,
104
+ height: cellRect.height,
105
+ zIndex: 10,
106
+ boxSizing: "border-box",
107
+ background: "var(--gs-color-cell-editing-bg, #fff)",
108
+ border: "2px solid var(--gs-color-cell-editing-border, #2196f3)"
109
+ },
110
+ children: /* @__PURE__ */ jsxRuntime.jsx(
111
+ EditorComponent,
112
+ {
113
+ value,
114
+ data: node?.data,
115
+ colId: editing.colId,
116
+ rowId: editing.rowId,
117
+ column,
118
+ editorParams,
119
+ onValueChange,
120
+ stopEditing,
121
+ api
122
+ }
123
+ )
124
+ }
125
+ );
126
+ }
127
+ function ContextMenuPortal(props) {
128
+ const { x, y, menuProps, Component: Component2 } = props;
129
+ const ref = react.useRef(null);
130
+ react.useEffect(() => {
131
+ const handler = (e) => {
132
+ if (ref.current && !ref.current.contains(e.target)) {
133
+ menuProps.closeMenu();
134
+ }
135
+ };
136
+ const id = setTimeout(() => {
137
+ document.addEventListener("mousedown", handler);
138
+ }, 0);
139
+ return () => {
140
+ clearTimeout(id);
141
+ document.removeEventListener("mousedown", handler);
142
+ };
143
+ }, [menuProps]);
144
+ react.useEffect(() => {
145
+ const handler = (e) => {
146
+ if (e.key === "Escape") menuProps.closeMenu();
147
+ };
148
+ document.addEventListener("keydown", handler);
149
+ return () => document.removeEventListener("keydown", handler);
150
+ }, [menuProps]);
151
+ return /* @__PURE__ */ jsxRuntime.jsx(
152
+ "div",
153
+ {
154
+ ref,
155
+ className: "gs-context-menu-portal",
156
+ style: {
157
+ position: "absolute",
158
+ top: y,
159
+ left: x,
160
+ zIndex: 100,
161
+ minWidth: 160
162
+ },
163
+ children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...menuProps })
164
+ }
165
+ );
166
+ }
167
+ var WRAPPER_ATTR = "data-gs-portal";
168
+ function getOrCreateWrapper(cell) {
169
+ for (let i = 0; i < cell.children.length; i++) {
170
+ const child = cell.children[i];
171
+ if (child.hasAttribute(WRAPPER_ATTR)) {
172
+ return child;
173
+ }
174
+ }
175
+ const wrapper = document.createElement("div");
176
+ wrapper.setAttribute(WRAPPER_ATTR, "");
177
+ wrapper.style.display = "contents";
178
+ cell.appendChild(wrapper);
179
+ return wrapper;
180
+ }
181
+ function PortalManager(props) {
182
+ const { engine, api, columns, rootElement, contextMenuComponent } = props;
183
+ const [cellPortals, setCellPortals] = react.useState(
184
+ () => /* @__PURE__ */ new Map()
185
+ );
186
+ const [headerPortals, setHeaderPortals] = react.useState(
187
+ () => /* @__PURE__ */ new Map()
188
+ );
189
+ const [editorPortal, setEditorPortal] = react.useState(null);
190
+ const [contextMenu, setContextMenu] = react.useState(null);
191
+ const columnsRef = react.useRef(columns);
192
+ columnsRef.current = columns;
193
+ const engineRef = react.useRef(engine);
194
+ engineRef.current = engine;
195
+ const scanningRef = react.useRef(false);
196
+ const reactCellRendererMap = react.useRef(/* @__PURE__ */ new Map());
197
+ const reactHeaderRendererMap = react.useRef(/* @__PURE__ */ new Map());
198
+ const reactEditorMap = react.useRef(/* @__PURE__ */ new Map());
199
+ react.useEffect(() => {
200
+ const cellMap = /* @__PURE__ */ new Map();
201
+ const headerMap = /* @__PURE__ */ new Map();
202
+ const editorMap = /* @__PURE__ */ new Map();
203
+ for (const col of columns) {
204
+ const colId = col.colId ?? col.field ?? "";
205
+ if (col.cellRenderer && isReactCellRenderer(col.cellRenderer)) {
206
+ cellMap.set(colId, getReactCellComponent(col.cellRenderer));
207
+ }
208
+ if (col.headerRenderer && isReactHeaderRenderer(col.headerRenderer)) {
209
+ headerMap.set(colId, getReactHeaderComponent(col.headerRenderer));
210
+ }
211
+ if (col.cellEditorComponent) {
212
+ editorMap.set(colId, col.cellEditorComponent);
213
+ }
214
+ }
215
+ reactCellRendererMap.current = cellMap;
216
+ reactHeaderRendererMap.current = headerMap;
217
+ reactEditorMap.current = editorMap;
218
+ }, [columns]);
219
+ const buildCellProps = react.useCallback(
220
+ (node, col, rowIndex) => {
221
+ const colDef = col.originalDef;
222
+ let value;
223
+ if (colDef.valueGetter) {
224
+ value = colDef.valueGetter({
225
+ data: node.data,
226
+ node,
227
+ colDef,
228
+ colId: col.colId
229
+ });
230
+ } else {
231
+ value = core.getValueFromData(node.data, col.field);
232
+ }
233
+ let formattedValue = value != null ? String(value) : "";
234
+ if (colDef.valueFormatter) {
235
+ formattedValue = colDef.valueFormatter({ value, data: node.data, node, colDef });
236
+ }
237
+ return {
238
+ value,
239
+ formattedValue,
240
+ data: node.data,
241
+ node,
242
+ colDef,
243
+ colId: col.colId,
244
+ rowIndex,
245
+ api
246
+ };
247
+ },
248
+ [api]
249
+ );
250
+ const scanVisibleRows = react.useCallback(() => {
251
+ if (!rootElement || scanningRef.current) return;
252
+ scanningRef.current = true;
253
+ try {
254
+ const bodyContainer = rootElement.querySelector(".gs-body");
255
+ if (!bodyContainer) return;
256
+ const state = engineRef.current.store.getState();
257
+ const cellRenderers = reactCellRendererMap.current;
258
+ if (cellRenderers.size === 0) return;
259
+ const newPortals = /* @__PURE__ */ new Map();
260
+ const rowElements = bodyContainer.querySelectorAll(".gs-row");
261
+ const rowIdIndexMap = /* @__PURE__ */ new Map();
262
+ for (let i = 0; i < state.displayedRowIds.length; i++) {
263
+ rowIdIndexMap.set(state.displayedRowIds[i], i);
264
+ }
265
+ const columnMap = /* @__PURE__ */ new Map();
266
+ for (const col of state.columns) {
267
+ columnMap.set(col.colId, col);
268
+ }
269
+ for (const rowEl of rowElements) {
270
+ const rowId = rowEl.getAttribute("data-row-id");
271
+ if (!rowId) continue;
272
+ const node = state.rowNodes.get(rowId);
273
+ if (!node) continue;
274
+ const cells = rowEl.querySelectorAll(".gs-cell");
275
+ for (const cellEl of cells) {
276
+ const colId = cellEl.getAttribute("data-col-id");
277
+ if (!colId || !cellRenderers.has(colId)) continue;
278
+ const key = `${rowId}:${colId}`;
279
+ const Component2 = cellRenderers.get(colId);
280
+ const colState = columnMap.get(colId);
281
+ if (!colState) continue;
282
+ const rowIndex = rowIdIndexMap.get(rowId) ?? -1;
283
+ const rendererProps = buildCellProps(node, colState, rowIndex);
284
+ const wrapper = getOrCreateWrapper(cellEl);
285
+ newPortals.set(key, {
286
+ key,
287
+ container: wrapper,
288
+ component: Component2,
289
+ props: rendererProps,
290
+ nodeVersion: node.version
291
+ });
292
+ }
293
+ }
294
+ setCellPortals(newPortals);
295
+ } finally {
296
+ scanningRef.current = false;
297
+ }
298
+ }, [rootElement, buildCellProps]);
299
+ const scanHeaderCells = react.useCallback(() => {
300
+ if (!rootElement) return;
301
+ const headerRenderers = reactHeaderRendererMap.current;
302
+ if (headerRenderers.size === 0) return;
303
+ const headerContainer = rootElement.querySelector(".gs-header");
304
+ if (!headerContainer) return;
305
+ const state = engineRef.current.store.getState();
306
+ const newPortals = /* @__PURE__ */ new Map();
307
+ const headerCells = headerContainer.querySelectorAll(".gs-header-cell");
308
+ for (const cellEl of headerCells) {
309
+ const colId = cellEl.getAttribute("data-col-id");
310
+ if (!colId || !headerRenderers.has(colId)) continue;
311
+ const Component2 = headerRenderers.get(colId);
312
+ const colState = state.columns.find((c) => c.colId === colId);
313
+ if (!colState) continue;
314
+ const sortItem = state.sortModel.find((s) => s.colId === colId);
315
+ const headerProps = {
316
+ colDef: colState.originalDef,
317
+ colId,
318
+ displayName: colState.headerName,
319
+ sortDirection: sortItem?.sort ?? null,
320
+ sortIndex: colState.sortIndex,
321
+ api,
322
+ onSortRequested: (multiSort) => {
323
+ engineRef.current.commandBus.dispatch("sort:toggle", { colId, multiSort });
324
+ }
325
+ };
326
+ const wrapper = getOrCreateWrapper(cellEl);
327
+ newPortals.set(colId, {
328
+ key: colId,
329
+ container: wrapper,
330
+ element: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...headerProps })
331
+ });
332
+ }
333
+ setHeaderPortals(newPortals);
334
+ }, [rootElement, api]);
335
+ react.useEffect(() => {
336
+ if (!rootElement) return;
337
+ const bodyContainer = rootElement.querySelector(".gs-body");
338
+ if (!bodyContainer) return;
339
+ const observer = new MutationObserver(() => {
340
+ scanVisibleRows();
341
+ });
342
+ observer.observe(bodyContainer, { childList: true });
343
+ scanVisibleRows();
344
+ scanHeaderCells();
345
+ return () => observer.disconnect();
346
+ }, [rootElement, scanVisibleRows, scanHeaderCells]);
347
+ const getVersionSnapshot = react.useCallback(() => engine.store.getVersion(), [engine]);
348
+ const subscribeStore = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
349
+ const stateVersion = react.useSyncExternalStore(
350
+ subscribeStore,
351
+ getVersionSnapshot,
352
+ getVersionSnapshot
353
+ );
354
+ react.useEffect(() => {
355
+ scanVisibleRows();
356
+ }, [stateVersion, scanVisibleRows]);
357
+ react.useEffect(() => {
358
+ const unsubs = [
359
+ engine.eventBus.on("column:sort:changed", () => {
360
+ requestAnimationFrame(() => scanHeaderCells());
361
+ }),
362
+ engine.eventBus.on("columns:changed", () => {
363
+ requestAnimationFrame(() => scanHeaderCells());
364
+ })
365
+ ];
366
+ return () => unsubs.forEach((u) => u());
367
+ }, [engine, scanHeaderCells]);
368
+ react.useEffect(() => {
369
+ const unsubStart = engine.eventBus.on(
370
+ "cell:editingStarted",
371
+ (event) => {
372
+ const { node, colId } = event;
373
+ const editorComponent = reactEditorMap.current.get(colId);
374
+ if (!editorComponent || !rootElement) return;
375
+ const cellEl = rootElement.querySelector(
376
+ `.gs-row[data-row-id="${CSS.escape(node.id)}"] .gs-cell[data-col-id="${CSS.escape(colId)}"]`
377
+ );
378
+ if (!cellEl) return;
379
+ const state = engine.store.getState();
380
+ const editing = state.editing;
381
+ if (!editing) return;
382
+ setEditorPortal({
383
+ editing,
384
+ cellElement: cellEl,
385
+ cellRect: cellEl.getBoundingClientRect()
386
+ });
387
+ }
388
+ );
389
+ const unsubStop = engine.eventBus.on("cell:editingStopped", () => {
390
+ setEditorPortal(null);
391
+ });
392
+ return () => {
393
+ unsubStart();
394
+ unsubStop();
395
+ };
396
+ }, [engine, rootElement]);
397
+ react.useEffect(() => {
398
+ if (!rootElement || !contextMenuComponent) return;
399
+ const handler = (e) => {
400
+ e.preventDefault();
401
+ const target = e.target;
402
+ const cellEl = target.closest(".gs-cell");
403
+ const rowEl = target.closest(".gs-row");
404
+ if (!cellEl || !rowEl) return;
405
+ const rowId = rowEl.getAttribute("data-row-id");
406
+ const colId = cellEl.getAttribute("data-col-id");
407
+ if (!rowId || !colId) return;
408
+ const state = engine.store.getState();
409
+ const node = state.rowNodes.get(rowId);
410
+ if (!node) return;
411
+ const colState = state.columns.find((c) => c.colId === colId);
412
+ const value = colState ? core.getValueFromData(node.data, colState.field) : void 0;
413
+ const rootRect = rootElement.getBoundingClientRect();
414
+ const rowIndex = state.displayedRowIds.indexOf(rowId);
415
+ setContextMenu({
416
+ x: e.clientX - rootRect.left,
417
+ y: e.clientY - rootRect.top,
418
+ menuProps: {
419
+ position: { rowIndex, colId },
420
+ node,
421
+ colId,
422
+ value,
423
+ api,
424
+ closeMenu: () => setContextMenu(null)
425
+ }
426
+ });
427
+ };
428
+ rootElement.addEventListener("contextmenu", handler);
429
+ return () => rootElement.removeEventListener("contextmenu", handler);
430
+ }, [rootElement, contextMenuComponent, api, engine]);
431
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
432
+ Array.from(cellPortals.values()).map(
433
+ (entry) => reactDom.createPortal(
434
+ /* @__PURE__ */ jsxRuntime.jsx(
435
+ CellRendererPortal,
436
+ {
437
+ Component: entry.component,
438
+ rendererProps: entry.props,
439
+ nodeVersion: entry.nodeVersion
440
+ },
441
+ entry.key
442
+ ),
443
+ entry.container,
444
+ entry.key
445
+ )
446
+ ),
447
+ Array.from(headerPortals.values()).map(
448
+ (entry) => reactDom.createPortal(entry.element, entry.container, entry.key)
449
+ ),
450
+ editorPortal && rootElement && (() => {
451
+ const editorComponent = reactEditorMap.current.get(editorPortal.editing.colId);
452
+ if (!editorComponent) return null;
453
+ const colDef = columnsRef.current.find(
454
+ (c) => (c.colId ?? c.field) === editorPortal.editing.colId
455
+ );
456
+ return reactDom.createPortal(
457
+ /* @__PURE__ */ jsxRuntime.jsx(
458
+ CellEditorPortal,
459
+ {
460
+ state: editorPortal,
461
+ api,
462
+ engine,
463
+ EditorComponent: editorComponent,
464
+ editorParams: colDef?.cellEditorParams ?? {},
465
+ gridRootRect: rootElement.getBoundingClientRect()
466
+ }
467
+ ),
468
+ rootElement,
469
+ "gs-editor"
470
+ );
471
+ })(),
472
+ contextMenu && contextMenuComponent && rootElement && reactDom.createPortal(
473
+ /* @__PURE__ */ jsxRuntime.jsx(
474
+ ContextMenuPortal,
475
+ {
476
+ x: contextMenu.x,
477
+ y: contextMenu.y,
478
+ menuProps: contextMenu.menuProps,
479
+ Component: contextMenuComponent
480
+ }
481
+ ),
482
+ rootElement,
483
+ "gs-context-menu"
484
+ )
485
+ ] });
486
+ }
487
+ var GridErrorBoundary = class extends react.Component {
488
+ constructor() {
489
+ super(...arguments);
490
+ this.state = { hasError: false, error: null };
491
+ }
492
+ static getDerivedStateFromError(error) {
493
+ return { hasError: true, error };
494
+ }
495
+ componentDidCatch(error, errorInfo) {
496
+ console.error("[GridStorm] Rendering error:", error, errorInfo.componentStack);
497
+ }
498
+ render() {
499
+ if (this.state.hasError) {
500
+ if (this.props.fallback) {
501
+ return this.props.fallback;
502
+ }
503
+ return null;
504
+ }
505
+ return this.props.children;
506
+ }
507
+ };
508
+ function useGridEngine(config) {
509
+ const [engine, setEngine] = react.useState(null);
510
+ const configRef = react.useRef(config);
511
+ configRef.current = config;
512
+ const rowDataMountedRef = react.useRef(false);
513
+ const columnsMountedRef = react.useRef(false);
514
+ react.useEffect(() => {
515
+ const eng = core.createGrid(configRef.current);
516
+ setEngine(eng);
517
+ rowDataMountedRef.current = false;
518
+ columnsMountedRef.current = false;
519
+ return () => {
520
+ eng.destroy();
521
+ setEngine(null);
522
+ };
523
+ }, []);
524
+ react.useEffect(() => {
525
+ if (!engine) return;
526
+ if (!rowDataMountedRef.current) {
527
+ rowDataMountedRef.current = true;
528
+ return;
529
+ }
530
+ if (config.rowData) {
531
+ engine.api.setRowData(config.rowData);
532
+ }
533
+ }, [config.rowData, engine]);
534
+ react.useEffect(() => {
535
+ if (!engine) return;
536
+ if (!columnsMountedRef.current) {
537
+ columnsMountedRef.current = true;
538
+ return;
539
+ }
540
+ if (config.columns) {
541
+ engine.api.setColumnDefs(config.columns);
542
+ }
543
+ }, [config.columns, engine]);
544
+ return engine;
545
+ }
546
+ function processColumns(reactColumns) {
547
+ return reactColumns.map((col) => {
548
+ const coreDef = { ...col };
549
+ delete coreDef.cellEditorComponent;
550
+ return coreDef;
551
+ });
552
+ }
553
+ function GridStorm(props) {
554
+ const {
555
+ // GridConfig props
556
+ columns: reactColumns,
557
+ rowData,
558
+ dataSource,
559
+ rowModelType,
560
+ getRowId,
561
+ plugins,
562
+ defaultColDef,
563
+ rowHeight,
564
+ headerHeight,
565
+ domLayout,
566
+ pinnedTopRowData,
567
+ pinnedBottomRowData,
568
+ suppressScrollX,
569
+ suppressScrollY,
570
+ rowSelection,
571
+ editType,
572
+ undoRedoCellEditing,
573
+ pagination,
574
+ paginationPageSize,
575
+ animateRows,
576
+ ariaLabel,
577
+ locale,
578
+ theme,
579
+ // Controlled state props
580
+ sortModel: controlledSortModel,
581
+ onSortModelChange,
582
+ filterModel: controlledFilterModel,
583
+ onFilterModelChange,
584
+ selectedRowIds: controlledSelectedRowIds,
585
+ onSelectedRowIdsChange,
586
+ currentPage: controlledCurrentPage,
587
+ onCurrentPageChange,
588
+ // Event props
589
+ onGridReady,
590
+ onRowDataChanged,
591
+ onSelectionChanged,
592
+ onSortChanged,
593
+ onFilterChanged,
594
+ onCellValueChanged,
595
+ onCellClicked,
596
+ onCellDoubleClicked,
597
+ onRowClicked,
598
+ onCellEditingStarted,
599
+ onCellEditingStopped,
600
+ onPaginationChanged,
601
+ onColumnResized,
602
+ // Renderer config props
603
+ enableCellEditing,
604
+ enableGrouping,
605
+ groupIndent,
606
+ checkboxSelection,
607
+ checkboxColumnWidth,
608
+ floatingFilter,
609
+ floatingFilterDebounce,
610
+ enablePagination,
611
+ pageSizeOptions,
612
+ // Component props
613
+ height = 400,
614
+ width = "100%",
615
+ containerClass,
616
+ containerStyle,
617
+ contextMenu,
618
+ children
619
+ // Rest are ignored (no HTML div passthrough to avoid TS errors)
620
+ } = props;
621
+ const containerRef = react.useRef(null);
622
+ const rendererRef = react.useRef(null);
623
+ const [rootElement, setRootElement] = react.useState(null);
624
+ const coreColumns = react.useMemo(
625
+ () => processColumns(reactColumns),
626
+ [reactColumns]
627
+ );
628
+ const config = react.useMemo(
629
+ () => ({
630
+ columns: coreColumns,
631
+ rowData,
632
+ dataSource,
633
+ rowModelType,
634
+ getRowId,
635
+ plugins,
636
+ defaultColDef,
637
+ rowHeight,
638
+ headerHeight,
639
+ domLayout,
640
+ pinnedTopRowData,
641
+ pinnedBottomRowData,
642
+ suppressScrollX,
643
+ suppressScrollY,
644
+ rowSelection,
645
+ editType,
646
+ undoRedoCellEditing,
647
+ pagination,
648
+ paginationPageSize,
649
+ animateRows,
650
+ ariaLabel,
651
+ locale,
652
+ theme
653
+ }),
654
+ // Only recreate config on structural changes
655
+ // eslint-disable-next-line react-hooks/exhaustive-deps
656
+ [coreColumns, plugins, rowModelType, getRowId]
657
+ );
658
+ const engine = useGridEngine(config);
659
+ react.useEffect(() => {
660
+ if (!containerRef.current || !engine) return;
661
+ const renderer = new domRenderer.DomRenderer({
662
+ container: containerRef.current,
663
+ engine,
664
+ enableCellEditing,
665
+ enableGrouping,
666
+ groupIndent,
667
+ checkboxSelection,
668
+ checkboxColumnWidth,
669
+ floatingFilter,
670
+ floatingFilterDebounce,
671
+ enablePagination,
672
+ pageSizeOptions
673
+ });
674
+ renderer.mount();
675
+ rendererRef.current = renderer;
676
+ const root = containerRef.current.querySelector(".gs-root");
677
+ setRootElement(root);
678
+ requestAnimationFrame(() => {
679
+ engine.eventBus.emit("grid:ready", { api: engine.api });
680
+ });
681
+ return () => {
682
+ renderer.destroy();
683
+ rendererRef.current = null;
684
+ setRootElement(null);
685
+ };
686
+ }, [engine]);
687
+ const controlledCallbacksRef = react.useRef({
688
+ onSortModelChange,
689
+ onFilterModelChange,
690
+ onSelectedRowIdsChange,
691
+ onCurrentPageChange
692
+ });
693
+ controlledCallbacksRef.current = {
694
+ onSortModelChange,
695
+ onFilterModelChange,
696
+ onSelectedRowIdsChange,
697
+ onCurrentPageChange
698
+ };
699
+ react.useEffect(() => {
700
+ if (!engine) return;
701
+ const removeMw = engine.commandBus.use((ctx) => {
702
+ const cbs = controlledCallbacksRef.current;
703
+ if (ctx.commandType === "sort:toggle" && cbs.onSortModelChange) {
704
+ return;
705
+ }
706
+ if (ctx.commandType === "selection:select" && cbs.onSelectedRowIdsChange) {
707
+ return;
708
+ }
709
+ });
710
+ return removeMw;
711
+ }, [engine]);
712
+ react.useEffect(() => {
713
+ if (controlledSortModel !== void 0 && engine) {
714
+ engine.api.setSortModel(controlledSortModel);
715
+ }
716
+ }, [controlledSortModel, engine]);
717
+ react.useEffect(() => {
718
+ if (controlledFilterModel !== void 0 && engine) {
719
+ engine.api.setFilterModel(controlledFilterModel);
720
+ }
721
+ }, [controlledFilterModel, engine]);
722
+ react.useEffect(() => {
723
+ if (controlledCurrentPage !== void 0 && engine) {
724
+ engine.api.paginationGoToPage(controlledCurrentPage);
725
+ }
726
+ }, [controlledCurrentPage, engine]);
727
+ react.useEffect(() => {
728
+ if (controlledSelectedRowIds !== void 0 && engine) {
729
+ engine.commandBus.dispatch("selection:set", {
730
+ selectedRowIds: new Set(controlledSelectedRowIds)
731
+ });
732
+ }
733
+ }, [controlledSelectedRowIds, engine]);
734
+ const eventCallbacksRef = react.useRef({
735
+ onGridReady,
736
+ onRowDataChanged,
737
+ onSelectionChanged,
738
+ onSortChanged,
739
+ onFilterChanged,
740
+ onCellValueChanged,
741
+ onCellClicked,
742
+ onCellDoubleClicked,
743
+ onRowClicked,
744
+ onCellEditingStarted,
745
+ onCellEditingStopped,
746
+ onPaginationChanged,
747
+ onColumnResized
748
+ });
749
+ eventCallbacksRef.current = {
750
+ onGridReady,
751
+ onRowDataChanged,
752
+ onSelectionChanged,
753
+ onSortChanged,
754
+ onFilterChanged,
755
+ onCellValueChanged,
756
+ onCellClicked,
757
+ onCellDoubleClicked,
758
+ onRowClicked,
759
+ onCellEditingStarted,
760
+ onCellEditingStopped,
761
+ onPaginationChanged,
762
+ onColumnResized
763
+ };
764
+ react.useEffect(() => {
765
+ if (!engine) return;
766
+ const eb = engine.eventBus;
767
+ const cbs = () => eventCallbacksRef.current;
768
+ const unsubs = [
769
+ eb.on("rowData:changed", (e) => cbs().onRowDataChanged?.(e)),
770
+ eb.on("selection:changed", (e) => {
771
+ cbs().onSelectionChanged?.(e);
772
+ controlledCallbacksRef.current.onSelectedRowIdsChange?.(
773
+ engine.store.getState().selection.selectedRowIds,
774
+ e.source ?? "api"
775
+ );
776
+ }),
777
+ eb.on("column:sort:changed", (e) => {
778
+ cbs().onSortChanged?.(e);
779
+ controlledCallbacksRef.current.onSortModelChange?.(e.sortModel);
780
+ }),
781
+ eb.on("filter:changed", (e) => {
782
+ cbs().onFilterChanged?.(e);
783
+ controlledCallbacksRef.current.onFilterModelChange?.(e.filterModel);
784
+ }),
785
+ eb.on("cell:valueChanged", (e) => cbs().onCellValueChanged?.(e)),
786
+ eb.on("cell:clicked", (e) => cbs().onCellClicked?.(e)),
787
+ eb.on("cell:doubleClicked", (e) => cbs().onCellDoubleClicked?.(e)),
788
+ eb.on("row:clicked", (e) => cbs().onRowClicked?.(e)),
789
+ eb.on("cell:editingStarted", (e) => cbs().onCellEditingStarted?.(e)),
790
+ eb.on("cell:editingStopped", (e) => cbs().onCellEditingStopped?.(e)),
791
+ eb.on("pagination:changed", (e) => {
792
+ cbs().onPaginationChanged?.(e);
793
+ controlledCallbacksRef.current.onCurrentPageChange?.(e.currentPage);
794
+ }),
795
+ eb.on("column:resized", (e) => cbs().onColumnResized?.(e))
796
+ ];
797
+ return () => unsubs.forEach((u) => u());
798
+ }, [engine]);
799
+ react.useEffect(() => {
800
+ if (engine) {
801
+ onGridReady?.(engine.api);
802
+ }
803
+ }, [engine]);
804
+ const style = {
805
+ height: typeof height === "number" ? `${height}px` : height,
806
+ width: typeof width === "number" ? `${width}px` : width,
807
+ ...containerStyle
808
+ };
809
+ const contextValue = react.useMemo(
810
+ () => engine ? { engine, api: engine.api, rootElement } : null,
811
+ [engine, rootElement]
812
+ );
813
+ if (!engine || !contextValue) {
814
+ return /* @__PURE__ */ jsxRuntime.jsx(GridErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(
815
+ "div",
816
+ {
817
+ ref: containerRef,
818
+ className: `gs-container ${containerClass ?? ""}`.trim(),
819
+ style
820
+ }
821
+ ) });
822
+ }
823
+ return /* @__PURE__ */ jsxRuntime.jsx(GridErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsxs(GridContext.Provider, { value: contextValue, children: [
824
+ children,
825
+ /* @__PURE__ */ jsxRuntime.jsx(
826
+ "div",
827
+ {
828
+ ref: containerRef,
829
+ className: `gs-container ${containerClass ?? ""}`.trim(),
830
+ style
831
+ }
832
+ ),
833
+ /* @__PURE__ */ jsxRuntime.jsx(
834
+ PortalManager,
835
+ {
836
+ engine,
837
+ api: engine.api,
838
+ columns: reactColumns,
839
+ rootElement,
840
+ contextMenuComponent: contextMenu
841
+ }
842
+ )
843
+ ] }) });
844
+ }
845
+
846
+ // src/hooks/useGridApi.ts
847
+ function useGridApi() {
848
+ return useGridContext().api;
849
+ }
850
+ function useGridState(selector) {
851
+ const { engine } = useGridContext();
852
+ const getSnapshot = react.useCallback(
853
+ () => selector(engine.store.getState()),
854
+ [selector, engine]
855
+ );
856
+ return react.useSyncExternalStore(
857
+ (onStoreChange) => engine.store.subscribe(onStoreChange),
858
+ getSnapshot,
859
+ getSnapshot
860
+ );
861
+ }
862
+ function useGridSelection() {
863
+ const { engine, api } = useGridContext();
864
+ const getSelectionSnapshot = react.useCallback(() => engine.store.getState().selection.selectedRowIds, [engine]);
865
+ const subscribe = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
866
+ const selectedRowIds = react.useSyncExternalStore(
867
+ subscribe,
868
+ getSelectionSnapshot,
869
+ getSelectionSnapshot
870
+ );
871
+ const selectedCount = selectedRowIds.size;
872
+ const isRowSelected = react.useCallback(
873
+ (rowId) => engine.store.getState().selection.selectedRowIds.has(rowId),
874
+ [engine]
875
+ );
876
+ const getSelectedRows = react.useCallback(() => api.getSelectedRows(), [api]);
877
+ const getSelectedNodes = react.useCallback(() => api.getSelectedNodes(), [api]);
878
+ const selectAll = react.useCallback(() => api.selectAll(), [api]);
879
+ const deselectAll = react.useCallback(() => api.deselectAll(), [api]);
880
+ return {
881
+ selectedRowIds,
882
+ selectedCount,
883
+ getSelectedRows,
884
+ getSelectedNodes,
885
+ isRowSelected,
886
+ selectAll,
887
+ deselectAll
888
+ };
889
+ }
890
+ function useGridSort() {
891
+ const { engine, api } = useGridContext();
892
+ const getSortSnapshot = react.useCallback(() => engine.store.getState().sortModel, [engine]);
893
+ const subscribe = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
894
+ const sortModel = react.useSyncExternalStore(
895
+ subscribe,
896
+ getSortSnapshot,
897
+ getSortSnapshot
898
+ );
899
+ const isSorted = sortModel.length > 0;
900
+ const setSortModel = react.useCallback(
901
+ (model) => api.setSortModel(model),
902
+ [api]
903
+ );
904
+ const toggleSort = react.useCallback(
905
+ (colId, multiSort = false) => {
906
+ engine.commandBus.dispatch("sort:toggle", { colId, multiSort });
907
+ },
908
+ [engine]
909
+ );
910
+ const clearSort = react.useCallback(() => api.setSortModel([]), [api]);
911
+ return { sortModel, isSorted, setSortModel, toggleSort, clearSort };
912
+ }
913
+ function useGridFilter() {
914
+ const { engine, api } = useGridContext();
915
+ const getFilterSnapshot = react.useCallback(() => engine.store.getState().filterModel, [engine]);
916
+ const getQuickFilterSnapshot = react.useCallback(() => engine.store.getState().quickFilterText, [engine]);
917
+ const subscribe = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
918
+ const filterModel = react.useSyncExternalStore(
919
+ subscribe,
920
+ getFilterSnapshot,
921
+ getFilterSnapshot
922
+ );
923
+ const quickFilterText = react.useSyncExternalStore(
924
+ subscribe,
925
+ getQuickFilterSnapshot,
926
+ getQuickFilterSnapshot
927
+ );
928
+ const isFiltered = Object.keys(filterModel).length > 0 || quickFilterText.length > 0;
929
+ const setFilterModel = react.useCallback(
930
+ (model) => api.setFilterModel(model),
931
+ [api]
932
+ );
933
+ const setQuickFilter = react.useCallback(
934
+ (text) => api.setQuickFilter(text),
935
+ [api]
936
+ );
937
+ const clearFilters = react.useCallback(() => {
938
+ api.setFilterModel({});
939
+ api.setQuickFilter("");
940
+ }, [api]);
941
+ return {
942
+ filterModel,
943
+ quickFilterText,
944
+ isFiltered,
945
+ setFilterModel,
946
+ setQuickFilter,
947
+ clearFilters
948
+ };
949
+ }
950
+ function useGridPagination() {
951
+ const { engine, api } = useGridContext();
952
+ const getPaginationSnapshot = react.useCallback(() => engine.store.getState().pagination, [engine]);
953
+ const subscribe = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
954
+ const paginationState = react.useSyncExternalStore(
955
+ subscribe,
956
+ getPaginationSnapshot,
957
+ getPaginationSnapshot
958
+ );
959
+ const { currentPage, pageSize, totalRows } = paginationState;
960
+ const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));
961
+ const hasNextPage = currentPage < totalPages - 1;
962
+ const hasPreviousPage = currentPage > 0;
963
+ const goToPage = react.useCallback(
964
+ (page) => api.paginationGoToPage(page),
965
+ [api]
966
+ );
967
+ const nextPage = react.useCallback(() => {
968
+ if (hasNextPage) api.paginationGoToPage(currentPage + 1);
969
+ }, [api, currentPage, hasNextPage]);
970
+ const previousPage = react.useCallback(() => {
971
+ if (hasPreviousPage) api.paginationGoToPage(currentPage - 1);
972
+ }, [api, currentPage, hasPreviousPage]);
973
+ const firstPage = react.useCallback(() => api.paginationGoToPage(0), [api]);
974
+ const lastPage = react.useCallback(
975
+ () => api.paginationGoToPage(totalPages - 1),
976
+ [api, totalPages]
977
+ );
978
+ return react.useMemo(
979
+ () => ({
980
+ currentPage,
981
+ totalPages,
982
+ pageSize,
983
+ totalRows,
984
+ hasNextPage,
985
+ hasPreviousPage,
986
+ goToPage,
987
+ nextPage,
988
+ previousPage,
989
+ firstPage,
990
+ lastPage
991
+ }),
992
+ [
993
+ currentPage,
994
+ totalPages,
995
+ pageSize,
996
+ totalRows,
997
+ hasNextPage,
998
+ hasPreviousPage,
999
+ goToPage,
1000
+ nextPage,
1001
+ previousPage,
1002
+ firstPage,
1003
+ lastPage
1004
+ ]
1005
+ );
1006
+ }
1007
+ function useGridEvent(event, handler) {
1008
+ const { engine } = useGridContext();
1009
+ const handlerRef = react.useRef(handler);
1010
+ handlerRef.current = handler;
1011
+ react.useEffect(() => {
1012
+ const unsub = engine.eventBus.on(event, (payload) => {
1013
+ handlerRef.current(payload);
1014
+ });
1015
+ return unsub;
1016
+ }, [engine, event]);
1017
+ }
1018
+ function useGridColumn() {
1019
+ const { engine, api } = useGridContext();
1020
+ const getColumnsSnapshot = react.useCallback(() => engine.store.getState().columns, [engine]);
1021
+ const subscribe = react.useCallback((cb) => engine.store.subscribe(cb), [engine]);
1022
+ const allColumns = react.useSyncExternalStore(
1023
+ subscribe,
1024
+ getColumnsSnapshot,
1025
+ getColumnsSnapshot
1026
+ );
1027
+ const visibleColumns = react.useMemo(
1028
+ () => allColumns.filter((c) => !c.hide),
1029
+ [allColumns]
1030
+ );
1031
+ const setColumnVisible = react.useCallback(
1032
+ (colId, visible) => api.setColumnVisible(colId, visible),
1033
+ [api]
1034
+ );
1035
+ const setColumnWidth = react.useCallback(
1036
+ (colId, width) => api.setColumnWidth(colId, width),
1037
+ [api]
1038
+ );
1039
+ const moveColumn = react.useCallback(
1040
+ (colId, toIndex) => api.moveColumn(colId, toIndex),
1041
+ [api]
1042
+ );
1043
+ const setColumnPinned = react.useCallback(
1044
+ (colId, pinned) => api.setColumnPinned(colId, pinned),
1045
+ [api]
1046
+ );
1047
+ const getColumn = react.useCallback(
1048
+ (colId) => api.getColumn(colId),
1049
+ [api]
1050
+ );
1051
+ return {
1052
+ allColumns,
1053
+ visibleColumns,
1054
+ setColumnVisible,
1055
+ setColumnWidth,
1056
+ moveColumn,
1057
+ setColumnPinned,
1058
+ getColumn
1059
+ };
1060
+ }
1061
+
1062
+ exports.GridContext = GridContext;
1063
+ exports.GridErrorBoundary = GridErrorBoundary;
1064
+ exports.GridStorm = GridStorm;
1065
+ exports.isReactCellRenderer = isReactCellRenderer;
1066
+ exports.isReactHeaderRenderer = isReactHeaderRenderer;
1067
+ exports.reactCellRenderer = reactCellRenderer;
1068
+ exports.reactHeaderRenderer = reactHeaderRenderer;
1069
+ exports.useGridApi = useGridApi;
1070
+ exports.useGridColumn = useGridColumn;
1071
+ exports.useGridContext = useGridContext;
1072
+ exports.useGridEvent = useGridEvent;
1073
+ exports.useGridFilter = useGridFilter;
1074
+ exports.useGridPagination = useGridPagination;
1075
+ exports.useGridSelection = useGridSelection;
1076
+ exports.useGridSort = useGridSort;
1077
+ exports.useGridState = useGridState;
1078
+ //# sourceMappingURL=index.cjs.map
1079
+ //# sourceMappingURL=index.cjs.map