@bit.rhplus/ui.grid 0.0.94 → 0.0.96

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/ColumnBuilder.jsx CHANGED
@@ -12,139 +12,6 @@ import { CircleHelp, Check, X } from 'lucide-react';
12
12
  // Globální cache pro názvy zemí (sdílená napříč všemi instancemi ColumnBuilder)
13
13
  const COUNTRY_NAMES_CACHE = {};
14
14
 
15
- /**
16
- * Custom React hook pro zpracování interakcí s link buňkou v AG Grid.
17
- * Obsluhuje hover efekty, kliky a přepínání overview módu.
18
- * @param {Object} params - AG Grid parametry buňky
19
- * @param {Function} onClick - Callback funkce při kliku na link
20
- * @param {Object|Function} linkStyle - CSS styl pro link nebo funkce vracející styl
21
- * @param {Object|Function} hoverStyle - CSS styl pro hover stav nebo funkce vracející styl
22
- * @param {boolean} overviewToggle - Příznak zda aktivovat overview toggle funkčnost
23
- * @returns {Object} Objekt s handlry a styly pro link buňku
24
- */
25
- const useLinkCellRenderer = (
26
- params,
27
- onClick,
28
- linkStyle,
29
- hoverStyle,
30
- overviewToggle
31
- ) => {
32
- const [isHovered, setIsHovered] = React.useState(false);
33
-
34
- const handleMouseEnter = React.useCallback(() => {
35
- setIsHovered(true);
36
- }, []);
37
-
38
- const handleMouseLeave = React.useCallback(() => {
39
- setIsHovered(false);
40
- }, []);
41
-
42
- const handleClick = React.useCallback(
43
- (event) => {
44
- event.stopPropagation();
45
-
46
- if (overviewToggle && params.api) {
47
- params.api.dispatchEvent({
48
- type: 'overviewToggle',
49
- data: params.data,
50
- params,
51
- });
52
- }
53
-
54
- if (onClick) {
55
- onClick(params);
56
- }
57
- },
58
- [onClick, overviewToggle, params]
59
- );
60
-
61
- // Přesunuto do useMemo, aby se vytvořilo pouze jednou
62
- const defaultLinkStyle = React.useMemo(
63
- () => ({
64
- color: '#1a73e8',
65
- textDecoration: 'none',
66
- cursor: 'pointer',
67
- }),
68
- []
69
- );
70
-
71
- // Přesunuto do useMemo, aby se vytvořilo pouze jednou
72
- const defaultHoverStyle = React.useMemo(
73
- () => ({
74
- textDecoration: 'underline',
75
- }),
76
- []
77
- );
78
-
79
- const computedLinkStyle = React.useMemo(
80
- () => ({
81
- ...defaultLinkStyle,
82
- ...(typeof linkStyle === 'function'
83
- ? linkStyle(params)
84
- : linkStyle || {}),
85
- }),
86
- [linkStyle, params, defaultLinkStyle]
87
- );
88
-
89
- const computedHoverStyle = React.useMemo(
90
- () => ({
91
- ...defaultLinkStyle,
92
- ...computedLinkStyle,
93
- ...defaultHoverStyle,
94
- ...(typeof hoverStyle === 'function'
95
- ? hoverStyle(params)
96
- : hoverStyle || {}),
97
- }),
98
- [computedLinkStyle, hoverStyle, params, defaultLinkStyle, defaultHoverStyle]
99
- );
100
-
101
- return {
102
- isHovered,
103
- handleMouseEnter,
104
- handleMouseLeave,
105
- handleClick,
106
- computedLinkStyle,
107
- computedHoverStyle,
108
- };
109
- };
110
-
111
- /**
112
- * Custom React hook pro zpracování interakcí s object buňkou v AG Grid.
113
- * Poskytuje funkcionalitu pro editovatelné objekty s hover efekty a edit tlačítkem.
114
- * @param {Object} params - AG Grid parametry buňky
115
- * @param {boolean} editable - Příznak zda je buňka editovatelná
116
- * @param {Function} onEditClick - Callback funkce při kliku na edit tlačítko
117
- * @returns {Object} Objekt s handlry pro object buňku
118
- */
119
- const useObjectCellRenderer = (params, editable, onEditClick) => {
120
- const [isHovered, setIsHovered] = React.useState(false);
121
-
122
- const handleMouseEnter = React.useCallback(() => {
123
- setIsHovered(true);
124
- }, []);
125
-
126
- const handleMouseLeave = React.useCallback(() => {
127
- setIsHovered(false);
128
- }, []);
129
-
130
- const handleEditClick = React.useCallback(
131
- (event) => {
132
- event.stopPropagation();
133
- if (onEditClick) {
134
- onEditClick(params);
135
- }
136
- },
137
- [onEditClick, params]
138
- );
139
-
140
- return {
141
- isHovered,
142
- handleMouseEnter,
143
- handleMouseLeave,
144
- handleEditClick,
145
- };
146
- };
147
-
148
15
  /**
149
16
  * Custom React hook pro detekci hover stavu řádku v AG Grid.
150
17
  * Používá MutationObserver pro sledování CSS tříd řádků a detekci hover stavu.
@@ -453,73 +320,55 @@ class ColumnBuilder {
453
320
  * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
454
321
  * @returns {ColumnBuilder} Instance pro chaining
455
322
  */
323
+ /**
324
+ * Přidá sloupec s odkazem (link) - Používá extrahovaný LinkRenderer.
325
+ * Optimalizovaná implementace s extrahovaným rendererem pro lepší výkon.
326
+ * @param {Object} config - Konfigurace link sloupce
327
+ * @param {string} [config.cellAlign='left'] - Horizontální zarovnání obsahu ('left', 'center', 'right')
328
+ * @param {Function} [config.onClick] - Callback funkce při kliku na link
329
+ * @param {Object|Function} [config.linkStyle] - CSS styl pro link nebo funkce vracející styl
330
+ * @param {Object|Function} [config.hoverStyle] - CSS styl pro hover stav nebo funkce vracející styl
331
+ * @param {boolean} [config.overviewToggle=false] - Příznak zda aktivovat overview toggle funkčnost
332
+ * @param {boolean} [config.editable=false] - Editovatelnost buňky
333
+ * @param {Function} [config.visibleGetter] - Funkce určující viditelnost linku
334
+ * @param {boolean} [config.showOnGroup=false] - Zobrazit link i v group řádcích
335
+ * @param {Object} config.restProps - Další AG-Grid colDef parametry
336
+ * @returns {ColumnBuilder} Instance pro fluent API
337
+ */
456
338
  addLinkColumn({
339
+ cellAlign = 'left',
457
340
  onClick,
458
341
  linkStyle,
459
342
  hoverStyle,
460
- cellAlign = 'left',
461
- editable,
462
343
  overviewToggle = false,
344
+ editable = false,
345
+ visibleGetter,
346
+ showOnGroup = false,
347
+ contentTooltip,
348
+ tooltipField,
349
+ tooltipInteraction,
350
+ tooltipShowDelay,
463
351
  color,
464
352
  bgColor,
465
353
  colorField,
466
354
  bgColorField,
467
355
  ...restProps
468
356
  }) {
469
- // Vytvořím komponentu pro cell renderer, která používá hook
470
- const LinkCellRenderer = React.memo((params) => {
471
- const {
472
- isHovered,
473
- handleMouseEnter,
474
- handleMouseLeave,
475
- handleClick,
476
- computedLinkStyle,
477
- computedHoverStyle,
478
- } = useLinkCellRenderer(
479
- params,
357
+ this.#addPreparedColumn({
358
+ cellAlign,
359
+ cellRenderer: 'linkRenderer',
360
+ cellRendererParams: {
480
361
  onClick,
481
362
  linkStyle,
482
363
  hoverStyle,
483
- overviewToggle
484
- );
485
-
486
- return (
487
- <div
488
- className="link-cell-container"
489
- style={{
490
- width: '100%',
491
- height: '100%',
492
- display: 'flex',
493
- alignItems: 'center',
494
- }}
495
- >
496
- <span
497
- onClick={handleClick}
498
- onMouseEnter={handleMouseEnter}
499
- onMouseLeave={handleMouseLeave}
500
- style={isHovered ? computedHoverStyle : computedLinkStyle}
501
- role="button"
502
- tabIndex={0}
503
- onKeyPress={(event) => {
504
- if (event.key === 'Enter' || event.key === ' ') {
505
- handleClick(event);
506
- }
507
- }}
508
- >
509
- {params.value}
510
- </span>
511
- </div>
512
- );
513
- });
514
-
515
- // Používáme funkci, která vrátí React komponentu
516
- const cellRenderer = (params) => {
517
- return <LinkCellRenderer {...params} />;
518
- };
519
-
520
- this.#addPreparedColumn({
521
- cellAlign,
522
- cellRenderer,
364
+ overviewToggle,
365
+ visibleGetter,
366
+ showOnGroup,
367
+ },
368
+ contentTooltip,
369
+ tooltipField,
370
+ tooltipInteraction,
371
+ tooltipShowDelay,
523
372
  editable: this.#resolveEditable(editable),
524
373
  overviewToggle,
525
374
  color,
@@ -585,73 +434,47 @@ class ColumnBuilder {
585
434
  * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
586
435
  * @returns {ColumnBuilder} Instance pro chaining
587
436
  */
437
+ /**
438
+ * Přidá sloupec pro zobrazení objektů - Používá extrahovaný ObjectRenderer.
439
+ * Optimalizovaná implementace s extrahovaným rendererem pro lepší výkon.
440
+ * @param {Object} config - Konfigurace object sloupce
441
+ * @param {boolean} [config.editable=false] - Příznak zda je buňka editovatelná s edit tlačítkem
442
+ * @param {Function} [config.onEditClick] - Callback funkce při kliku na edit tlačítko
443
+ * @param {Function} [config.visibleGetter] - Funkce určující viditelnost objektu
444
+ * @param {boolean} [config.showOnGroup=false] - Zobrazit objekt i v group řádcích
445
+ * @param {string|Function} [config.displayField] - Název pole nebo funkce pro získání zobrazované hodnoty
446
+ * @param {Object} config.restProps - Další AG-Grid colDef parametry
447
+ * @returns {ColumnBuilder} Instance pro fluent API
448
+ */
588
449
  addObjectColumn({
450
+ editable = false,
451
+ onEditClick,
452
+ visibleGetter,
453
+ showOnGroup = false,
454
+ displayField,
589
455
  contentTooltip,
590
456
  tooltipField,
591
457
  tooltipInteraction,
592
- tooltipShowDelay = 100,
593
- editable,
594
- onEditClick,
458
+ tooltipShowDelay,
595
459
  color,
596
460
  bgColor,
597
461
  colorField,
598
462
  bgColorField,
599
463
  ...restProps
600
464
  }) {
601
- // Vytvořím komponentu pro cell renderer, která používá hook
602
- const ObjectCellRenderer = React.memo((params) => {
603
- const { isHovered, handleMouseEnter, handleMouseLeave, handleEditClick } =
604
- useObjectCellRenderer(params, editable, onEditClick);
605
-
606
- return (
607
- <div
608
- className="object-cell-container"
609
- style={{
610
- position: 'relative',
611
- width: '100%',
612
- height: '100%',
613
- display: 'flex',
614
- alignItems: 'center',
615
- }}
616
- onMouseEnter={handleMouseEnter}
617
- onMouseLeave={handleMouseLeave}
618
- >
619
- <div style={{ flexGrow: 1 }}>{params.value}</div>
620
- {isHovered && editable && (
621
- <button
622
- className="edit-object-button"
623
- style={{
624
- position: 'absolute',
625
- right: '4px',
626
- background: 'transparent',
627
- border: 'none',
628
- cursor: 'pointer',
629
- padding: '4px',
630
- display: 'flex',
631
- alignItems: 'center',
632
- justifyContent: 'center',
633
- }}
634
- onClick={handleEditClick}
635
- title="Upravit"
636
- >
637
- <span style={{ fontSize: '16px' }}>⋮</span>
638
- </button>
639
- )}
640
- </div>
641
- );
642
- });
643
-
644
- // Používáme funkci, která vrátí React komponentu
645
- const cellRenderer = (params) => {
646
- return <ObjectCellRenderer {...params} />;
647
- };
648
-
649
465
  this.#addPreparedColumn({
466
+ cellRenderer: 'objectRenderer',
467
+ cellRendererParams: {
468
+ editable,
469
+ onEditClick,
470
+ visibleGetter,
471
+ showOnGroup,
472
+ displayField,
473
+ },
650
474
  contentTooltip,
651
475
  tooltipField,
652
476
  tooltipInteraction,
653
477
  tooltipShowDelay,
654
- cellRenderer,
655
478
  editable: this.#resolveEditable(editable),
656
479
  color,
657
480
  bgColor,
@@ -886,22 +709,70 @@ class ColumnBuilder {
886
709
  bgColorField,
887
710
  ...restProps
888
711
  }) {
712
+ // Memoizovaná CountryFlag sub-komponenta s lazy loading a error handling
713
+ const CountryFlag = React.memo(({ code, size }) => {
714
+ const [hasError, setHasError] = React.useState(false);
715
+
716
+ const flagUrl = React.useMemo(() => {
717
+ if (!code) return null;
718
+ return `https://flagcdn.com/w40/${code.toLowerCase()}.png`;
719
+ }, [code]);
720
+
721
+ const handleError = React.useCallback(() => {
722
+ setHasError(true);
723
+ }, []);
724
+
725
+ React.useEffect(() => {
726
+ setHasError(false);
727
+ }, [flagUrl]);
728
+
729
+ const flagStyle = React.useMemo(() => ({
730
+ width: `${size}px`,
731
+ height: `${size}px`,
732
+ borderRadius: '50%',
733
+ objectFit: 'cover',
734
+ flexShrink: 0,
735
+ }), [size]);
736
+
737
+ if (hasError || !flagUrl) return null;
738
+
739
+ return (
740
+ <img
741
+ src={flagUrl}
742
+ alt={`${code} flag`}
743
+ style={flagStyle}
744
+ loading="lazy"
745
+ onError={handleError}
746
+ />
747
+ );
748
+ });
749
+
750
+ CountryFlag.displayName = 'CountryFlag';
751
+
889
752
  const CountryCellRenderer = React.memo((params) => {
890
753
  const [countryName, setCountryName] = React.useState(null);
891
754
  const [isLoading, setIsLoading] = React.useState(false);
892
755
 
893
756
  // Získání kódu země
894
- const code = countryCodeField
895
- ? getValueByPath(params.data, countryCodeField)
896
- : (typeof countryCode === 'function' ? countryCode(params) : countryCode);
897
-
898
- if (!code) return null;
757
+ const code = React.useMemo(() => {
758
+ return countryCodeField
759
+ ? getValueByPath(params.data, countryCodeField)
760
+ : (typeof countryCode === 'function' ? countryCode(params) : countryCode);
761
+ }, [params.data, params.value]);
762
+
763
+ const upperCode = React.useMemo(() =>
764
+ code ? code.toUpperCase() : null,
765
+ [code]
766
+ );
899
767
 
900
- const upperCode = code.toUpperCase();
901
- const cacheKey = `${upperCode}_${language}`;
768
+ const cacheKey = React.useMemo(() =>
769
+ upperCode ? `${upperCode}_${language}` : null,
770
+ [upperCode, language]
771
+ );
902
772
 
903
773
  // Načtení názvu země z REST Countries API
904
774
  React.useEffect(() => {
775
+ if (!upperCode || !cacheKey) return;
905
776
  if (displayType !== 'name') return;
906
777
  if (getCountryName) return;
907
778
 
@@ -912,16 +783,16 @@ class ColumnBuilder {
912
783
  }
913
784
 
914
785
  setIsLoading(true);
915
-
916
- // REST Countries API: https://restcountries.com/v3.1/alpha/{code}
786
+
787
+ // REST Countries API
917
788
  fetch(`https://restcountries.com/v3.1/alpha/${upperCode}`)
918
789
  .then(response => response.json())
919
790
  .then(data => {
920
791
  if (data && data[0]) {
921
792
  // Získání překladu podle jazyka
922
793
  const translations = data[0].translations;
923
- let name = data[0].name.common; // fallback na anglický název
924
-
794
+ let name = data[0].name.common;
795
+
925
796
  if (language === 'cs' && translations?.ces) {
926
797
  name = translations.ces.common;
927
798
  } else if (language === 'cze' && translations?.cze) {
@@ -929,79 +800,78 @@ class ColumnBuilder {
929
800
  } else if (translations && translations[language]) {
930
801
  name = translations[language].common;
931
802
  }
932
-
933
-
803
+
934
804
  // Uložení do cache
935
805
  COUNTRY_NAMES_CACHE[cacheKey] = name;
936
806
  setCountryName(name);
937
807
  }
938
808
  })
939
- .catch((error) => {
940
- console.error('Error fetching country:', error);
809
+ .catch(() => {
941
810
  // V případě chyby použijeme kód země
942
811
  setCountryName(upperCode);
943
812
  })
944
813
  .finally(() => {
945
814
  setIsLoading(false);
946
815
  });
947
- }, [upperCode, cacheKey, displayType, getCountryName, params.value, language]);
948
-
949
- // Získání názvu země
950
- let displayText = upperCode;
951
- if (displayType === 'name') {
952
- if (getCountryName) {
953
- displayText = getCountryName(code, params);
954
- } else if (countryName) {
955
- displayText = countryName;
956
- } else if (isLoading) {
957
- displayText = '...';
958
- } else if (params.value) {
959
- displayText = params.value;
960
- }
961
- }
816
+ }, [upperCode, cacheKey, displayType, getCountryName]);
962
817
 
963
- // URL veřejného API pro vlajky (flagcdn.com)
964
- const flagUrl = `https://flagcdn.com/w40/${upperCode.toLowerCase()}.png`;
818
+ // Memoizovaný displayText
819
+ const displayText = React.useMemo(() => {
820
+ if (!upperCode) return '';
965
821
 
966
- const flagStyle = {
967
- width: `${flagSize}px`,
968
- height: `${flagSize}px`,
969
- borderRadius: '50%',
970
- objectFit: 'cover',
971
- flexShrink: 0,
972
- };
822
+ if (displayType === 'name') {
823
+ if (getCountryName) {
824
+ return getCountryName(code, params);
825
+ }
826
+ if (countryName) {
827
+ return countryName;
828
+ }
829
+ if (isLoading) {
830
+ return '...';
831
+ }
832
+ if (params.value) {
833
+ return params.value;
834
+ }
835
+ }
973
836
 
974
- const containerStyle = {
837
+ return upperCode;
838
+ }, [upperCode, displayType, getCountryName, code, params, countryName, isLoading]);
839
+
840
+ // Memoizovaný containerStyle
841
+ const containerStyle = React.useMemo(() => ({
975
842
  display: 'flex',
976
843
  alignItems: 'center',
977
- justifyContent: cellAlign === 'center' ? 'center' : cellAlign === 'right' ? 'flex-end' : 'flex-start',
844
+ justifyContent:
845
+ cellAlign === 'center' ? 'center' :
846
+ cellAlign === 'right' ? 'flex-end' :
847
+ 'flex-start',
978
848
  width: '100%',
979
849
  height: '100%',
980
850
  gap: `${textGap}px`,
981
851
  flexDirection: flagPosition === 'right' ? 'row-reverse' : 'row',
982
- };
852
+ }), [cellAlign, textGap, flagPosition]);
853
+
854
+ if (!code) return null;
983
855
 
984
856
  return (
985
857
  <div style={containerStyle}>
986
- <img
987
- src={flagUrl}
988
- alt={`${upperCode} flag`}
989
- style={flagStyle}
990
- onError={(e) => {
991
- e.target.style.display = 'none';
992
- }}
993
- />
858
+ <CountryFlag code={upperCode} size={flagSize} />
994
859
  <span>{displayText}</span>
995
860
  </div>
996
861
  );
997
862
  });
998
863
 
864
+ CountryCellRenderer.displayName = 'CountryCellRenderer';
865
+
999
866
  const cellRenderer = (params) => {
1000
867
  return <CountryCellRenderer {...params} />;
1001
868
  };
1002
869
 
1003
870
  this.#addPreparedColumn({
1004
871
  cellRenderer,
872
+ cellRendererParams: {
873
+ deferRender: true, // AG-Grid deferred rendering pro optimalizaci výkonu při scrollování
874
+ },
1005
875
  color,
1006
876
  bgColor,
1007
877
  colorField,
@@ -1051,58 +921,76 @@ class ColumnBuilder {
1051
921
  bgColorField,
1052
922
  ...restProps
1053
923
  }) {
1054
- // Create a React component for boolean cell renderer
1055
- const BooleanCellRenderer = React.memo((params) => {
1056
- const { data, value } = params;
1057
-
1058
- const visibleResult = visibleGetter ? visibleGetter(data) : true;
924
+ // Konstanty mimo komponentu pro lepší výkon
925
+ const ICON_STYLE = {
926
+ display: 'inline-block',
927
+ height: '100%',
928
+ };
1059
929
 
1060
- const Icon = ({ innerValue, ...iconProps }) => {
1061
- if (innerValue === undefined || innerValue === null) {
1062
- if (defaultIcon)
1063
- return <CircleHelp size={size} color={defaultIconColor} {...iconProps} />;
930
+ // Memoizovaná Icon sub-komponenta
931
+ const Icon = React.memo(({ innerValue, size, colorTrue, colorFalse, visibleTrue, visibleFalse, defaultIcon, defaultIconColor }) => {
932
+ if (innerValue === undefined || innerValue === null) {
933
+ if (defaultIcon) {
934
+ return <CircleHelp size={size} color={defaultIconColor} style={ICON_STYLE} />;
1064
935
  }
936
+ return null;
937
+ }
1065
938
 
1066
- if ((!visibleFalse && !innerValue) ||
1067
- (!visibleTrue && (innerValue))) {
1068
- return <div />
1069
- }
939
+ if ((!visibleFalse && !innerValue) || (!visibleTrue && innerValue)) {
940
+ return null;
941
+ }
1070
942
 
1071
- if (innerValue)
1072
- return <Check size={size} color={colorTrue} {...iconProps} />;
1073
- return <X size={size} color={colorFalse} {...iconProps} />;
1074
- };
943
+ if (innerValue) {
944
+ return <Check size={size} color={colorTrue} style={ICON_STYLE} />;
945
+ }
946
+
947
+ return <X size={size} color={colorFalse} style={ICON_STYLE} />;
948
+ });
1075
949
 
1076
- const showCondition = () => {
950
+ Icon.displayName = 'BooleanIcon';
951
+
952
+ // Create a React component for boolean cell renderer
953
+ const BooleanCellRenderer = React.memo((params) => {
954
+ const { data, value } = params;
955
+
956
+ const visibleResult = React.useMemo(() =>
957
+ visibleGetter ? visibleGetter(data) : true,
958
+ [data]
959
+ );
960
+
961
+ const showCondition = React.useMemo(() => {
1077
962
  const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
1078
963
  return !newItem && (showOnGroup || !!data) && visibleResult;
1079
- };
964
+ }, [data, visibleResult]);
1080
965
 
1081
- if (!showCondition()) return null;
966
+ const containerStyle = React.useMemo(() => ({
967
+ width: '100%',
968
+ display: 'flex',
969
+ justifyContent: cellAlign ?? 'center',
970
+ alignItems: 'center',
971
+ height: '100%',
972
+ }), [cellAlign]);
973
+
974
+ if (!showCondition) return null;
1082
975
 
1083
976
  return (
1084
- <span
1085
- style={{
1086
- width: '100%',
1087
- display: 'flex',
1088
- justifyContent: cellAlign,
1089
- alignItems: 'center',
1090
- height: '100%',
1091
- }}
1092
- >
977
+ <span style={containerStyle}>
1093
978
  <Icon
1094
- style={{
1095
- display: 'inline-block',
1096
- height: '100%',
1097
- }}
1098
979
  innerValue={value}
980
+ size={size}
981
+ colorTrue={colorTrue}
982
+ colorFalse={colorFalse}
1099
983
  visibleTrue={visibleTrue}
1100
984
  visibleFalse={visibleFalse}
985
+ defaultIcon={defaultIcon}
986
+ defaultIconColor={defaultIconColor}
1101
987
  />
1102
988
  </span>
1103
989
  );
1104
990
  });
1105
991
 
992
+ BooleanCellRenderer.displayName = 'BooleanCellRenderer';
993
+
1106
994
  // Use the function that returns React component
1107
995
  const cellRenderer = (params) => {
1108
996
  return <BooleanCellRenderer {...params} />;
@@ -1110,6 +998,9 @@ class ColumnBuilder {
1110
998
 
1111
999
  this.#addPreparedColumn({
1112
1000
  cellRenderer,
1001
+ cellRendererParams: {
1002
+ deferRender: true, // AG-Grid deferred rendering pro optimalizaci výkonu při scrollování
1003
+ },
1113
1004
  editable: this.#resolveEditable(editable),
1114
1005
  color,
1115
1006
  bgColor,
@@ -1182,9 +1073,10 @@ class ColumnBuilder {
1182
1073
 
1183
1074
  // Získáme row index z params
1184
1075
  React.useEffect(() => {
1185
- if (params.node && params.node.rowIndex !== undefined) {
1076
+ // FIX AG-Grid v35: rowIndex může být null - musíme ošetřit
1077
+ if (params.node && params.node.rowIndex != null) {
1186
1078
  rowIndexRef.current = params.node.rowIndex.toString();
1187
- } else if (params.rowIndex !== undefined) {
1079
+ } else if (params.rowIndex != null) {
1188
1080
  rowIndexRef.current = params.rowIndex.toString();
1189
1081
  }
1190
1082
  }, [params.node, params.rowIndex]);
@@ -1530,6 +1422,9 @@ class ColumnBuilder {
1530
1422
  filter: false,
1531
1423
  resizable: false,
1532
1424
  cellRenderer: ActionDropdownRenderer,
1425
+ cellRendererParams: {
1426
+ deferRender: true, // AG-Grid deferred rendering pro optimalizaci výkonu při scrollování
1427
+ },
1533
1428
  cellClass: 'action-button-cell-observer',
1534
1429
  ...restProps,
1535
1430
  });