@archbase/components 4.0.23 → 4.0.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archbase/components",
3
- "version": "4.0.23",
3
+ "version": "4.0.25",
4
4
  "description": "UI Components for Archbase React v3 - Form editors, data visualization, and business components",
5
5
  "author": "Edson Martins <edsonmartins2005@gmail.com>",
6
6
  "license": "MIT",
@@ -118,9 +118,9 @@
118
118
  "vis-timeline": "^7.7.3",
119
119
  "xlsx": "^0.18.5",
120
120
  "yet-another-react-lightbox": "^3.21.0",
121
- "@archbase/core": "4.0.23",
122
- "@archbase/data": "4.0.23",
123
- "@archbase/layout": "4.0.23"
121
+ "@archbase/core": "4.0.25",
122
+ "@archbase/data": "4.0.25",
123
+ "@archbase/layout": "4.0.25"
124
124
  },
125
125
  "devDependencies": {
126
126
  "@types/d3": "^7.4.3",
@@ -90,6 +90,8 @@ export interface ArchbaseDataGridAGColumnProps<T = any> {
90
90
  maxSize?: number;
91
91
  /** Custom cell renderer */
92
92
  render?: (data: any) => ReactNode;
93
+ /** Truncate long content with ellipsis (and show a native tooltip with the full value) */
94
+ truncate?: boolean;
93
95
  /** Column visibility */
94
96
  visible: boolean;
95
97
  /** Column width */
@@ -13,8 +13,81 @@ import React, {
13
13
  useCallback,
14
14
  Children,
15
15
  isValidElement,
16
+ type ReactNode,
17
+ type ReactElement,
16
18
  } from 'react';
19
+
20
+ /**
21
+ * Returns a stable reference to `children` as long as the structural shape
22
+ * (number of valid elements, their `type` and `key`) does not change.
23
+ *
24
+ * JSX like `<Comp>{a}{b}</Comp>` creates a NEW children array on every render,
25
+ * even when `a` and `b` are stable references. That makes useMemo deps that
26
+ * include `children` recompute on every parent render. For consumers like
27
+ * AG-Grid where rebuilding column defs invalidates virtualization, this is
28
+ * expensive. This hook lets the consumer skip recomputation when the
29
+ * children only "look the same".
30
+ */
31
+ function useStableChildren(children: ReactNode): ReactNode {
32
+ const ref = useRef<ReactNode>(children);
33
+ const prevSignature = useRef<string>('');
34
+
35
+ const currentElements = Children.toArray(children).filter(isValidElement) as ReactElement[];
36
+ const currentSignature = currentElements
37
+ .map((el) => `${String((el.type as any)?.displayName || (el.type as any)?.name || el.type)}::${String(el.key ?? '')}`)
38
+ .join('|');
39
+
40
+ if (currentSignature !== prevSignature.current) {
41
+ prevSignature.current = currentSignature;
42
+ ref.current = children;
43
+ }
44
+ return ref.current;
45
+ }
46
+
47
+ /**
48
+ * Class applied to AG cells when the user opts into `truncate` on a column.
49
+ * The companion CSS (injected once per document below) makes the React
50
+ * container shrink in the cell's flex layout and clips overflow with an
51
+ * ellipsis. Native `title` attribute on the rendered span shows the full
52
+ * value on hover.
53
+ */
54
+ const ARCHBASE_TRUNCATE_CELL_CLASS = 'archbase-ag-truncate-cell';
55
+ const ARCHBASE_TRUNCATE_TEXT_CLASS = 'archbase-ag-truncate-text';
56
+ const ARCHBASE_TRUNCATE_STYLE_ID = 'archbase-ag-truncate-style';
57
+
58
+ function ensureTruncateStyleInjected(): void {
59
+ if (typeof document === 'undefined') return;
60
+ if (document.getElementById(ARCHBASE_TRUNCATE_STYLE_ID)) return;
61
+ const style = document.createElement('style');
62
+ style.id = ARCHBASE_TRUNCATE_STYLE_ID;
63
+ style.textContent = `
64
+ .ag-cell.${ARCHBASE_TRUNCATE_CELL_CLASS} {
65
+ overflow: hidden !important;
66
+ }
67
+ .ag-cell.${ARCHBASE_TRUNCATE_CELL_CLASS} .ag-react-container,
68
+ .ag-cell.${ARCHBASE_TRUNCATE_CELL_CLASS} > * {
69
+ flex: 1 1 0 !important;
70
+ min-width: 0 !important;
71
+ max-width: 100% !important;
72
+ overflow: hidden !important;
73
+ }
74
+ .${ARCHBASE_TRUNCATE_TEXT_CLASS} {
75
+ display: block;
76
+ width: 100%;
77
+ max-width: 100%;
78
+ overflow: hidden;
79
+ text-overflow: ellipsis;
80
+ white-space: nowrap;
81
+ }
82
+ `;
83
+ document.head.appendChild(style);
84
+ }
17
85
  import { AgGridReact } from 'ag-grid-react';
86
+
87
+ // Memoize AgGridReact — recommended by AG-Grid's official performance demo. This
88
+ // is by far the biggest scroll-perf win: it skips the full AG-Grid re-render when
89
+ // the parent re-renders but every prop reference is the same.
90
+ const AgGridReactMemo = React.memo(AgGridReact);
18
91
  import {
19
92
  AllCommunityModule,
20
93
  ModuleRegistry,
@@ -175,7 +248,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
175
248
  csvOptions,
176
249
  toolbarAlignment = 'right',
177
250
  positionActionsColumn = 'first',
178
- actionsColumnWidth = 60,
251
+ actionsColumnWidth = 120,
179
252
  toolbarLeftContent,
180
253
  bottomToolbarMinHeight,
181
254
  paginationLabels,
@@ -199,6 +272,11 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
199
272
  const gridApiRef = useRef<GridApi | null>(null);
200
273
  const gridContainerRef = useRef<HTMLDivElement>(null);
201
274
 
275
+ // Reference-stable children: only changes when the structural shape changes.
276
+ // Prevents columnDefs useMemo from invalidating on every parent render and
277
+ // forcing AG-Grid to rebuild all columns (which destroys virtualization perf).
278
+ const stableChildren = useStableChildren(children);
279
+
202
280
  // Detect DataSource version
203
281
  const isV2 = isDataSourceV2(dataSource);
204
282
 
@@ -348,8 +426,9 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
348
426
  }
349
427
  }
350
428
 
351
- // Extract column definitions from children
352
- Children.forEach(children, (child) => {
429
+ // Extract column definitions from children (using the structurally-stable
430
+ // reference so this useMemo doesn't recompute on every parent render).
431
+ Children.forEach(stableChildren, (child) => {
353
432
  if (isValidElement(child) && child.type === Columns) {
354
433
  Children.forEach((child.props as any).children, (column) => {
355
434
  if (isValidElement(column)) {
@@ -361,7 +440,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
361
440
  const alignment = getAlignmentByDataType(dataType, columnProps.align);
362
441
 
363
442
  // Get cell renderer
364
- const cellRenderer = getCellRendererByDataType(
443
+ const baseCellRenderer = getCellRendererByDataType(
365
444
  dataType,
366
445
  columnProps.render
367
446
  ? (params) =>
@@ -380,6 +459,24 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
380
459
  }
381
460
  );
382
461
 
462
+ // Quando truncate=true, envolve o conteúdo num span que ativa o
463
+ // ellipsis (cssClass adicionada à cell faz o flex item interno
464
+ // encolher — ver ensureTruncateStyleInjected).
465
+ const cellRenderer = columnProps.truncate
466
+ ? (params: any) => {
467
+ const titleValue = params.value == null ? '' : String(params.value);
468
+ return (
469
+ <span title={titleValue} className={ARCHBASE_TRUNCATE_TEXT_CLASS}>
470
+ {baseCellRenderer(params)}
471
+ </span>
472
+ );
473
+ }
474
+ : baseCellRenderer;
475
+
476
+ if (columnProps.truncate) {
477
+ ensureTruncateStyleInjected();
478
+ }
479
+
383
480
  // Create value getter for nested fields
384
481
  const valueGetter = columnProps.dataField.includes('.')
385
482
  ? (params: any) => {
@@ -427,7 +524,9 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
427
524
  : alignment === 'right'
428
525
  ? 'flex-end'
429
526
  : 'center',
527
+ ...(columnProps.truncate ? { overflow: 'hidden' } : {}),
430
528
  },
529
+ cellClass: columnProps.truncate ? ARCHBASE_TRUNCATE_CELL_CLASS : undefined,
431
530
  // Custom properties
432
531
  enableGlobalFilter: columnProps.enableGlobalFilter,
433
532
  dataType: columnProps.dataType,
@@ -441,7 +540,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
441
540
 
442
541
  return cols;
443
542
  }, [
444
- children,
543
+ stableChildren,
445
544
  enableRowActions,
446
545
  renderRowActions,
447
546
  actionsColumnWidth,
@@ -494,6 +593,18 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
494
593
  };
495
594
  }, [enableRowSelection]);
496
595
 
596
+ // Estabiliza a referência do getRowId — recomendação da AG-Grid: prop
597
+ // instável invalida o mapeamento interno de linhas e quebra o React.memo do
598
+ // AgGridReactMemo. Junto com columnDefs estável (via useStableChildren),
599
+ // garante que o memo realmente faça o seu trabalho.
600
+ const stableGetRowIdAG = useCallback(
601
+ (params: GetRowIdParams<T>) => {
602
+ const id = safeGetRowId(params.data, getRowId);
603
+ return id !== undefined ? String(id) : '';
604
+ },
605
+ [getRowId]
606
+ );
607
+
497
608
  // Grid ready handler
498
609
  const onGridReady = useCallback((event: GridReadyEvent) => {
499
610
  gridApiRef.current = event.api;
@@ -1164,7 +1275,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
1164
1275
 
1165
1276
  {/* AG Grid */}
1166
1277
  <Box style={{ flex: 1, overflow: 'hidden', position: 'relative', minHeight: 0, ...cssVars }}>
1167
- <AgGridReact
1278
+ <AgGridReactMemo
1168
1279
  theme={agGridTheme}
1169
1280
  rowData={rows}
1170
1281
  columnDefs={columnDefs}
@@ -1175,10 +1286,7 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
1175
1286
  onCellDoubleClicked={onCellDoubleClicked}
1176
1287
  onSortChanged={onSortChanged}
1177
1288
  onFilterChanged={onFilterChanged}
1178
- getRowId={(params: GetRowIdParams<T>) => {
1179
- const id = safeGetRowId(params.data, getRowId);
1180
- return id !== undefined ? String(id) : '';
1181
- }}
1289
+ getRowId={stableGetRowIdAG}
1182
1290
  rowHeight={rowHeight}
1183
1291
  headerHeight={40}
1184
1292
  loading={isLoadingInternal || isLoading}
@@ -1190,6 +1298,11 @@ function ArchbaseDataGridAG<T extends object = any, ID = any>(
1190
1298
  suppressScrollOnNewData={true}
1191
1299
  enableCellTextSelection={true}
1192
1300
  ensureDomOrder={true}
1301
+ // Perf — recomendações da doc oficial AG-Grid (scrolling-performance):
1302
+ // mantém apenas N linhas extra renderizadas fora do viewport.
1303
+ rowBuffer={10}
1304
+ // Desliga o highlight de hover quando o usuário pediu (perf win).
1305
+ suppressRowHoverHighlight={!highlightOnHover}
1193
1306
  />
1194
1307
  </Box>
1195
1308
 
@@ -156,7 +156,7 @@ function ArchbaseDataGrid<T extends object = any, ID = any>(props: ArchbaseDataG
156
156
  csvOptions,
157
157
  toolbarAlignment = 'right',
158
158
  positionActionsColumn = 'first',
159
- actionsColumnWidth = 60,
159
+ actionsColumnWidth = 120,
160
160
  toolbarLeftContent,
161
161
  columnAutoWidth = false,
162
162
  rowHeight = 52,
Binary file