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