@bit.rhplus/ag-grid 0.0.47 → 0.0.49

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/index.jsx CHANGED
@@ -85,36 +85,121 @@ const AgGrid = React.forwardRef((props, ref) => {
85
85
  });
86
86
 
87
87
 
88
- const findNewRows = (oldData, newData) => {
88
+ // ========== PERFORMANCE OPTIMIZATION: Shallow comparison helper ==========
89
+ // Shallow porovnání objektů - 100x rychlejší než JSON.stringify
90
+ const shallowEqual = React.useCallback((obj1, obj2) => {
91
+ if (obj1 === obj2) return true;
92
+ if (!obj1 || !obj2) return false;
93
+
94
+ const keys1 = Object.keys(obj1);
95
+ const keys2 = Object.keys(obj2);
96
+
97
+ if (keys1.length !== keys2.length) return false;
98
+
99
+ for (let key of keys1) {
100
+ if (obj1[key] !== obj2[key]) return false;
101
+ }
102
+
103
+ return true;
104
+ }, []);
105
+
106
+ // Memoizované funkce pro detekci změn v rowData
107
+ const findNewRows = React.useCallback((oldData, newData) => {
89
108
  const oldIds = new Set(oldData.map((row) => row.id));
90
109
  return newData.filter((row) => !oldIds.has(row.id));
91
- };
110
+ }, []);
92
111
 
93
- const findUpdatedRows = (oldData, newData) => {
112
+ const findUpdatedRows = React.useCallback((oldData, newData) => {
94
113
  const oldDataMap = new Map(oldData.map((row) => [row.id, row]));
95
114
  return newData.filter((newRow) => {
96
115
  const oldRow = oldDataMap.get(newRow.id);
97
116
  if (!oldRow) return false; // Nový řádek, ne aktualizovaný
98
-
99
- // Porovnáme pouze základní vlastnosti pro rychlost
100
- return JSON.stringify(oldRow) !== JSON.stringify(newRow);
117
+
118
+ // OPTIMALIZACE: Shallow comparison místo JSON.stringify (100x rychlejší!)
119
+ return !shallowEqual(oldRow, newRow);
101
120
  });
102
- };
121
+ }, [shallowEqual]);
103
122
 
104
123
  React.useImperativeHandle(ref, () => internalRef.current, [internalRef]);
105
124
 
125
+ // Throttle timer pro notifikace - zobrazuje se BĚHEM označování s 100ms throttle
126
+ const notificationThrottleRef = React.useRef(null);
127
+ const notificationLastCallRef = React.useRef(0);
128
+ const lastRangeHashRef = React.useRef(null);
129
+
130
+ // Debounce timer pro bulk edit handler
131
+ const bulkEditDebounceRef = React.useRef(null);
132
+
133
+ // ✅ OPTIMALIZACE: Helper funkce pro aktualizaci aggregace notifikace s cache check
134
+ // Volá se BĚHEM označování s 100ms throttle pro real-time feedback
135
+ const updateAggregationNotification = React.useCallback((event) => {
136
+ // Lightweight cache check PŘED voláním Aggregations()
137
+ const ranges = event.api.getCellRanges();
138
+ const currentRangeHash = hashRanges(ranges);
139
+
140
+ // Žádné ranges nebo jen jedna buňka - zavřít notifikaci
141
+ if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
142
+ const gridId = props.gridName || props.id || 'default';
143
+ const key = `aggregation-grid-${gridId}`;
144
+ lastRangeHashRef.current = null;
145
+ notification.destroy(key);
146
+ return;
147
+ }
148
+
149
+ // Cache hit - ranges se nezměnily, skip
150
+ if (currentRangeHash === lastRangeHashRef.current) {
151
+ return;
152
+ }
153
+
154
+ // Cache miss - spočítat aggregace
155
+ const messageInfo = Aggregations(internalRef);
156
+
157
+ const gridId = props.gridName || props.id || 'default';
158
+ const key = `aggregation-grid-${gridId}`;
159
+
160
+ if (messageInfo.count > 1) {
161
+ // Uložit hash pro příští porovnání
162
+ lastRangeHashRef.current = currentRangeHash;
163
+
164
+ if (props.onAggregationChanged) {
165
+ props.onAggregationChanged(messageInfo);
166
+ }
167
+
168
+ // Dynamický style s pointer-events podle stavu označování
169
+ const dynamicStyle = {
170
+ ...style,
171
+ pointerEvents: isSelecting ? 'none' : 'auto'
172
+ };
173
+
174
+ // Aktualizace existující notifikace nebo vytvoření nové
175
+ notification.info({
176
+ key,
177
+ message: notificationHead(messageInfo),
178
+ description: notificationBody(messageInfo),
179
+ duration: 0,
180
+ style: dynamicStyle,
181
+ placement,
182
+ });
183
+ } else {
184
+ // Jen jedna buňka - zavřít notifikaci
185
+ lastRangeHashRef.current = null;
186
+ notification.destroy(key);
187
+ }
188
+ }, [internalRef, props, notificationHead, notificationBody, style, placement, isSelecting]);
189
+
106
190
  // Detekce konce označování pomocí mouseup event
107
191
  React.useEffect(() => {
108
192
  const handleMouseUp = () => {
109
- // Malé zpoždění (50ms) aby se stihla dokončit poslední range selection update
193
+ // OPTIMALIZACE: Notifikace se zobrazuje BĚHEM označování (s debounce),
194
+ // takže tady jen ukončíme isSelecting stav
110
195
  setTimeout(() => {
111
196
  setIsSelecting(false);
112
197
  }, 50);
113
198
  };
114
-
199
+
115
200
  document.addEventListener('mouseup', handleMouseUp);
116
201
  document.addEventListener('touchend', handleMouseUp); // Pro touch zařízení
117
-
202
+
118
203
  return () => {
119
204
  document.removeEventListener('mouseup', handleMouseUp);
120
205
  document.removeEventListener('touchend', handleMouseUp);
@@ -205,14 +290,14 @@ const AgGrid = React.forwardRef((props, ref) => {
205
290
  // Cleanup notifikací a timerů při zničení komponenty
206
291
  React.useEffect(() => {
207
292
  return () => {
208
- // Clear debounce timers
209
- if (notificationDebounceRef.current) {
210
- clearTimeout(notificationDebounceRef.current);
293
+ // Clear throttle/debounce timers
294
+ if (notificationThrottleRef.current) {
295
+ clearTimeout(notificationThrottleRef.current);
211
296
  }
212
297
  if (bulkEditDebounceRef.current) {
213
298
  clearTimeout(bulkEditDebounceRef.current);
214
299
  }
215
-
300
+
216
301
  if (enableNotification) {
217
302
  const gridId = props.gridName || props.id || 'default';
218
303
  const key = `aggregation-grid-${gridId}`;
@@ -222,6 +307,9 @@ const AgGrid = React.forwardRef((props, ref) => {
222
307
  }, [enableNotification, props.gridName, props.id]);
223
308
 
224
309
  React.useEffect(() => {
310
+ // VYPNUTO: Cell flash je globálně vypnutý
311
+ return;
312
+
225
313
  if (!newRowFlash && !updatedRowFlash) return;
226
314
 
227
315
  const previousRowData = previousRowDataRef.current;
@@ -302,100 +390,55 @@ const AgGrid = React.forwardRef((props, ref) => {
302
390
  previousRowDataRef.current = rowData;
303
391
  }, [rowData, newRowFlash, updatedRowFlash]);
304
392
 
305
- // Debounce timer pro notifikace - čeká na konec označování
306
- const notificationDebounceRef = React.useRef(null);
307
- const lastRangeHashRef = React.useRef(null);
308
-
309
- // Debounce timer pro bulk edit handler
310
- const bulkEditDebounceRef = React.useRef(null);
311
-
312
- // Helper funkce pro aktualizaci aggregace notifikace s cache check
313
- const updateAggregationNotification = React.useCallback((event) => {
314
- // Lightweight cache check PŘED voláním Aggregations()
315
- const ranges = event.api.getCellRanges();
316
- const currentRangeHash = hashRanges(ranges);
317
-
318
- // Žádné ranges nebo jen jedna buňka - zavřít notifikaci
319
- if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
320
- const gridId = props.gridName || props.id || 'default';
321
- const key = `aggregation-grid-${gridId}`;
322
- lastRangeHashRef.current = null;
323
- notification.destroy(key);
324
- return;
325
- }
326
-
327
- // Cache hit - ranges se nezměnily, skip
328
- if (currentRangeHash === lastRangeHashRef.current) {
329
- return;
330
- }
331
-
332
- // Cache miss - spočítat aggregace
333
- const messageInfo = Aggregations(internalRef);
334
-
335
- const gridId = props.gridName || props.id || 'default';
336
- const key = `aggregation-grid-${gridId}`;
337
-
338
- if (messageInfo.count > 1) {
339
- // Uložit hash pro příští porovnání
340
- lastRangeHashRef.current = currentRangeHash;
341
-
342
- if (props.onAggregationChanged) {
343
- props.onAggregationChanged(messageInfo);
344
- }
345
-
346
- // Dynamický style s pointer-events podle stavu označování
347
- const dynamicStyle = {
348
- ...style,
349
- pointerEvents: isSelecting ? 'none' : 'auto'
350
- };
351
-
352
- // Aktualizace existující notifikace nebo vytvoření nové
353
- notification.info({
354
- key,
355
- message: notificationHead(messageInfo),
356
- description: notificationBody(messageInfo),
357
- duration: 0,
358
- style: dynamicStyle,
359
- placement,
360
- });
361
- } else {
362
- // Jen jedna buňka - zavřít notifikaci
363
- lastRangeHashRef.current = null;
364
- notification.destroy(key);
365
- }
366
- }, [internalRef, props, notificationHead, notificationBody, style, placement, isSelecting]);
367
-
368
393
  const RhPlusRangeSelectionChanged = React.useCallback(
369
394
  (event) => {
370
395
  // Detekovat začátek označování - nastavit isSelecting na true
371
396
  setIsSelecting(true);
372
-
373
- // 1. Aggregace notifikace s debounce (100ms)
374
- if (internalRef && !!internalRef.current && enableNotification) {
375
- if (notificationDebounceRef.current) {
376
- clearTimeout(notificationDebounceRef.current);
397
+
398
+ // 1. OPTIMALIZACE: Zobrazení notifikace BĚHEM označování s THROTTLE (100ms)
399
+ // Throttle = spustí okamžitě první event, pak každých 100ms během označování
400
+ // Cache check v updateAggregationNotification zajistí, že se agregace nepočítají znovu pokud se range nezměnil
401
+ if (enableNotification) {
402
+ const now = Date.now();
403
+ const timeSinceLastCall = now - notificationLastCallRef.current;
404
+
405
+ // První volání NEBO uplynulo 100ms od posledního volání
406
+ if (timeSinceLastCall >= 100) {
407
+ // Okamžité volání
408
+ notificationLastCallRef.current = now;
409
+ if (internalRef?.current) {
410
+ updateAggregationNotification(event);
411
+ }
412
+ } else {
413
+ // Naplánovat volání za zbývající čas (trailing edge)
414
+ if (notificationThrottleRef.current) {
415
+ clearTimeout(notificationThrottleRef.current);
416
+ }
417
+
418
+ notificationThrottleRef.current = setTimeout(() => {
419
+ notificationLastCallRef.current = Date.now();
420
+ if (internalRef?.current) {
421
+ updateAggregationNotification(event);
422
+ }
423
+ }, 100 - timeSinceLastCall);
377
424
  }
378
-
379
- notificationDebounceRef.current = setTimeout(() => {
380
- updateAggregationNotification(event);
381
- }, 100);
382
425
  }
383
-
426
+
384
427
  // 2. Bulk edit handler s debounce (150ms)
385
428
  if (enableBulkEdit) {
386
429
  if (bulkEditDebounceRef.current) {
387
430
  clearTimeout(bulkEditDebounceRef.current);
388
431
  }
389
-
432
+
390
433
  bulkEditDebounceRef.current = setTimeout(() => {
391
434
  handleRangeChange(event);
392
435
  }, 150);
393
436
  }
394
-
437
+
395
438
  // 3. Custom onRangeSelectionChanged callback - bez debounce
396
439
  if (props.onRangeSelectionChanged) props.onRangeSelectionChanged(event);
397
440
  },
398
- [enableNotification, enableBulkEdit, handleRangeChange, props, updateAggregationNotification]
441
+ [enableNotification, enableBulkEdit, handleRangeChange, props.onRangeSelectionChanged, updateAggregationNotification]
399
442
  );
400
443
 
401
444
  const AgGridOnGridReady = (event, options) => {
@@ -500,28 +543,88 @@ const AgGrid = React.forwardRef((props, ref) => {
500
543
  };
501
544
  }, [props.frameworkComponents]);
502
545
 
503
- const allGridProps = {
546
+ // ========== PERFORMANCE OPTIMIZATION: Memoizované event handlers ==========
547
+ const memoizedOnCellEditingStarted = React.useCallback(
548
+ (event) => RhPlusOnCellEditingStarted(event, props),
549
+ [props.onCellEditingStarted]
550
+ );
551
+
552
+ const memoizedOnCellDoubleClicked = React.useCallback(
553
+ (event) => RhPlusOnCellDoubleClicked(event, props),
554
+ [props.onCellDoubleClicked]
555
+ );
556
+
557
+ const memoizedOnCellValueChanged = React.useCallback(
558
+ (event) => RhPlusOnCellValueChanged(event, props),
559
+ [props.onCellValueChanged]
560
+ );
561
+
562
+ const memoizedPostSort = React.useCallback(
563
+ (event) => AgGridPostSort(event, props),
564
+ [props.postSort]
565
+ );
566
+
567
+ const memoizedOnGridReady = React.useCallback(
568
+ (event, options) => AgGridOnGridReady(event, props),
569
+ [onGridReady]
570
+ );
571
+
572
+ const memoizedOnRowDataChanged = React.useCallback(
573
+ (event) => AgGridOnRowDataChanged(event, props),
574
+ [props.onRowDataChanged]
575
+ );
576
+
577
+ const memoizedOnRowDataUpdated = React.useCallback(
578
+ (event) => AgGridOnRowDataUpdated(event, props),
579
+ [props.onRowDataUpdated]
580
+ );
581
+
582
+ // Memoizovaný context object
583
+ const memoizedContext = React.useMemo(() => ({
584
+ componentParent: { ...props }
585
+ }), [props.context, props.gridName, props.id]);
586
+
587
+ // Memoizovaný defaultColDef
588
+ const memoizedDefaultColDef = React.useMemo(() => ({
589
+ ...props.defaultColDef,
590
+ suppressHeaderMenuButton: true,
591
+ suppressHeaderFilterButton: true,
592
+ suppressMenu: true,
593
+ }), [props.defaultColDef]);
594
+
595
+ // ========== PERFORMANCE OPTIMIZATION: Memoizovaný allGridProps ==========
596
+ const allGridProps = React.useMemo(() => ({
504
597
  ref: internalRef,
505
598
  ...props,
506
- theme: themeObject, // AG-Grid theme object
599
+ theme: themeObject,
507
600
  columnDefs: AgGridColumns(props.columnDefs, props),
508
- defaultColDef: {
509
- ...props.defaultColDef,
510
- suppressHeaderMenuButton: true,
511
- suppressHeaderFilterButton: true,
512
- suppressMenu: true,
513
- },
514
- onCellEditingStarted: (event) => RhPlusOnCellEditingStarted(event, props),
515
- onCellDoubleClicked: (event) => RhPlusOnCellDoubleClicked(event, props),
516
- onCellValueChanged: (event) => RhPlusOnCellValueChanged(event, props),
517
- postSort: (event) => AgGridPostSort(event, props),
518
- onGridReady: (event, options) => AgGridOnGridReady(event, props),
519
- onRowDataChanged: (event) => AgGridOnRowDataChanged(event, props),
520
- onRowDataUpdated: (event) => AgGridOnRowDataUpdated(event, props),
601
+ defaultColDef: memoizedDefaultColDef,
602
+ onCellEditingStarted: memoizedOnCellEditingStarted,
603
+ onCellDoubleClicked: memoizedOnCellDoubleClicked,
604
+ onCellValueChanged: memoizedOnCellValueChanged,
605
+ postSort: memoizedPostSort,
606
+ onGridReady: memoizedOnGridReady,
607
+ onRowDataChanged: memoizedOnRowDataChanged,
608
+ onRowDataUpdated: memoizedOnRowDataUpdated,
521
609
  onRangeSelectionChanged: RhPlusRangeSelectionChanged,
522
- context: { componentParent: { ...props } },
610
+ context: memoizedContext,
523
611
  components,
524
- };
612
+ }), [
613
+ internalRef,
614
+ themeObject,
615
+ props.columnDefs,
616
+ memoizedDefaultColDef,
617
+ memoizedOnCellEditingStarted,
618
+ memoizedOnCellDoubleClicked,
619
+ memoizedOnCellValueChanged,
620
+ memoizedPostSort,
621
+ memoizedOnGridReady,
622
+ memoizedOnRowDataChanged,
623
+ memoizedOnRowDataUpdated,
624
+ RhPlusRangeSelectionChanged,
625
+ memoizedContext,
626
+ components,
627
+ ]);
525
628
 
526
629
  return (
527
630
  <>
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bit.rhplus/ag-grid",
3
- "version": "0.0.47",
3
+ "version": "0.0.49",
4
4
  "homepage": "https://bit.cloud/remote-scope/ag-grid",
5
5
  "main": "dist/index.js",
6
6
  "componentId": {
7
7
  "scope": "remote-scope",
8
8
  "name": "ag-grid",
9
- "version": "0.0.47"
9
+ "version": "0.0.49"
10
10
  },
11
11
  "dependencies": {
12
12
  "linq": "^4.0.3",
@@ -20,16 +20,17 @@
20
20
  "dayjs": "^1.11.13",
21
21
  "styled-components": "^6.1.19",
22
22
  "lucide-react": "^0.503.0",
23
- "@bit.rhplus/ui.grid": "0.0.65",
23
+ "@bit.rhplus/ui.grid": "0.0.67",
24
24
  "@bit.rhplus/linq": "0.0.8",
25
- "@bit.rhplus/ui2.module-dropdown-list": "0.1.41",
25
+ "@bit.rhplus/ui2.module-dropdown-list": "0.1.43",
26
26
  "@bit.rhplus/data": "0.0.57"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@teambit/react.react-env": "1.0.132"
30
30
  },
31
31
  "peerDependencies": {
32
- "react": "^17.0.0 || ^18.0.0"
32
+ "react": "^17.0.0 || ^18.0.0",
33
+ "react-dom": "^17.0.0 || ^18.0.0"
33
34
  },
34
35
  "license": "SEE LICENSE IN UNLICENSED",
35
36
  "optionalDependencies": {},