@astral/ui 4.114.0 → 4.115.0

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.
Files changed (151) hide show
  1. package/components/DataGrid/Body/Body.d.ts +1 -1
  2. package/components/DataGrid/Body/Body.js +7 -3
  3. package/components/DataGrid/Body/types.d.ts +11 -3
  4. package/components/DataGrid/DataGrid.js +0 -1
  5. package/components/DataGrid/PinnedSections/PinnedSections.js +2 -2
  6. package/components/DataGrid/PinnedSections/useLogic/useLogic.d.ts +1 -0
  7. package/components/DataGrid/PinnedSections/useLogic/useLogic.js +4 -3
  8. package/components/DataGrid/Row/Row.js +13 -9
  9. package/components/DataGrid/Row/RowContext/RowContext.d.ts +15 -4
  10. package/components/DataGrid/Row/RowContext/RowContext.js +3 -3
  11. package/components/DataGrid/Row/RowContext/RowProvider/RowProvider.d.ts +10 -1
  12. package/components/DataGrid/Row/RowContext/RowProvider/RowProvider.js +23 -17
  13. package/components/DataGrid/Row/types.d.ts +7 -3
  14. package/components/DataGrid/Row/useLogic/useLogic.d.ts +6 -4
  15. package/components/DataGrid/Row/useLogic/useLogic.js +21 -12
  16. package/components/DataGrid/RowBase/RowBase.js +5 -2
  17. package/components/DataGrid/RowBase/types.d.ts +9 -5
  18. package/components/DataGrid/RowBase/useLogic/useLogic.d.ts +5 -4
  19. package/components/DataGrid/RowBase/useLogic/useLogic.js +16 -10
  20. package/components/DataGrid/RowCheckbox/RowCheckbox.d.ts +5 -0
  21. package/components/DataGrid/RowCheckbox/RowCheckbox.js +13 -0
  22. package/components/DataGrid/RowCheckbox/constants.d.ts +4 -0
  23. package/components/DataGrid/RowCheckbox/constants.js +4 -0
  24. package/components/DataGrid/RowCheckbox/index.d.ts +1 -0
  25. package/components/DataGrid/RowCheckbox/index.js +1 -0
  26. package/components/DataGrid/RowCheckbox/types.d.ts +7 -0
  27. package/components/DataGrid/RowCheckbox/types.js +1 -0
  28. package/components/DataGrid/hooks/index.d.ts +1 -0
  29. package/components/DataGrid/hooks/index.js +1 -0
  30. package/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/index.d.ts +1 -0
  31. package/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/index.js +1 -0
  32. package/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/useWarnRowsWithoutDisabledReason.d.ts +5 -0
  33. package/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/useWarnRowsWithoutDisabledReason.js +17 -0
  34. package/components/DataGrid/index.d.ts +1 -1
  35. package/components/DataGrid/public.d.ts +1 -1
  36. package/components/DataGrid/tests/performance/testDataGridPerformance.d.ts +3 -1
  37. package/components/DataGrid/tests/performance/testDataGridPerformance.js +234 -2
  38. package/components/DataGrid/tests/selection/testDataGridSelection.d.ts +14 -0
  39. package/components/DataGrid/tests/selection/testDataGridSelection.js +562 -0
  40. package/components/DataGrid/types.d.ts +34 -1
  41. package/components/DataGrid/useLogic/useLogic.d.ts +6 -3
  42. package/components/DataGrid/useLogic/useLogic.js +15 -10
  43. package/components/DataGrid/utils/getRowDisabledState/getRowDisabledState.d.ts +31 -0
  44. package/components/DataGrid/utils/getRowDisabledState/getRowDisabledState.js +20 -0
  45. package/components/DataGrid/utils/getRowDisabledState/index.d.ts +1 -0
  46. package/components/DataGrid/utils/getRowDisabledState/index.js +1 -0
  47. package/components/DataGrid/utils/index.d.ts +3 -0
  48. package/components/DataGrid/utils/index.js +3 -0
  49. package/components/DataGrid/utils/resolveRowDisabled/index.d.ts +1 -0
  50. package/components/DataGrid/utils/resolveRowDisabled/index.js +1 -0
  51. package/components/DataGrid/utils/resolveRowDisabled/resolveRowDisabled.d.ts +5 -0
  52. package/components/DataGrid/utils/resolveRowDisabled/resolveRowDisabled.js +17 -0
  53. package/components/DataGrid/utils/resolveRowsDisabled/index.d.ts +1 -0
  54. package/components/DataGrid/utils/resolveRowsDisabled/index.js +1 -0
  55. package/components/DataGrid/utils/resolveRowsDisabled/resolveRowsDisabled.d.ts +16 -0
  56. package/components/DataGrid/utils/resolveRowsDisabled/resolveRowsDisabled.js +17 -0
  57. package/components/DataGridActionCell/hooks/useBlockingActions/useBlockingActions.js +3 -3
  58. package/components/DataGridCollapsibleRow/useLogic/useLogic.d.ts +3 -2
  59. package/components/DataGridCollapsibleRow/useLogic/useLogic.js +4 -3
  60. package/components/DataGridInfinite/DataGridInfinite.js +4 -4
  61. package/components/DataGridInfinite/DataGridInfiniteContext/DataGridInfiniteContext.d.ts +1 -1
  62. package/components/DataGridInfinite/DataGridInfiniteContext/DataGridInfiniteContext.js +1 -1
  63. package/components/DataGridInfinite/Footer/Footer.js +2 -2
  64. package/components/DataGridInfinite/Row/Row.js +2 -2
  65. package/components/DataGridInfinite/Row/index.d.ts +1 -0
  66. package/components/DataGridInfinite/Row/types.d.ts +17 -2
  67. package/components/DataGridInfinite/Row/useLogic/useLogic.d.ts +4 -2
  68. package/components/DataGridInfinite/Row/useLogic/useLogic.js +12 -4
  69. package/components/DataGridInfinite/constants.d.ts +2 -0
  70. package/components/DataGridInfinite/constants.js +2 -0
  71. package/components/DataGridInfinite/faker.d.ts +1 -5
  72. package/components/DataGridInfinite/faker.js +1 -2
  73. package/components/DataGridInfinite/useLogic/useLogic.d.ts +3 -2
  74. package/components/DataGridInfinite/useLogic/useLogic.js +25 -13
  75. package/components/index.d.ts +1 -1
  76. package/node/components/DataGrid/Body/Body.d.ts +1 -1
  77. package/node/components/DataGrid/Body/Body.js +7 -3
  78. package/node/components/DataGrid/Body/types.d.ts +11 -3
  79. package/node/components/DataGrid/DataGrid.js +0 -1
  80. package/node/components/DataGrid/PinnedSections/PinnedSections.js +2 -2
  81. package/node/components/DataGrid/PinnedSections/useLogic/useLogic.d.ts +1 -0
  82. package/node/components/DataGrid/PinnedSections/useLogic/useLogic.js +4 -3
  83. package/node/components/DataGrid/Row/Row.js +13 -9
  84. package/node/components/DataGrid/Row/RowContext/RowContext.d.ts +15 -4
  85. package/node/components/DataGrid/Row/RowContext/RowContext.js +3 -3
  86. package/node/components/DataGrid/Row/RowContext/RowProvider/RowProvider.d.ts +10 -1
  87. package/node/components/DataGrid/Row/RowContext/RowProvider/RowProvider.js +23 -17
  88. package/node/components/DataGrid/Row/types.d.ts +7 -3
  89. package/node/components/DataGrid/Row/useLogic/useLogic.d.ts +6 -4
  90. package/node/components/DataGrid/Row/useLogic/useLogic.js +23 -14
  91. package/node/components/DataGrid/RowBase/RowBase.js +5 -2
  92. package/node/components/DataGrid/RowBase/types.d.ts +9 -5
  93. package/node/components/DataGrid/RowBase/useLogic/useLogic.d.ts +5 -4
  94. package/node/components/DataGrid/RowBase/useLogic/useLogic.js +16 -10
  95. package/node/components/DataGrid/RowCheckbox/RowCheckbox.d.ts +5 -0
  96. package/node/components/DataGrid/RowCheckbox/RowCheckbox.js +17 -0
  97. package/node/components/DataGrid/RowCheckbox/constants.d.ts +4 -0
  98. package/node/components/DataGrid/RowCheckbox/constants.js +7 -0
  99. package/node/components/DataGrid/RowCheckbox/index.d.ts +1 -0
  100. package/node/components/DataGrid/RowCheckbox/index.js +17 -0
  101. package/node/components/DataGrid/RowCheckbox/types.d.ts +7 -0
  102. package/node/components/DataGrid/RowCheckbox/types.js +2 -0
  103. package/node/components/DataGrid/hooks/index.d.ts +1 -0
  104. package/node/components/DataGrid/hooks/index.js +1 -0
  105. package/node/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/index.d.ts +1 -0
  106. package/node/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/index.js +17 -0
  107. package/node/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/useWarnRowsWithoutDisabledReason.d.ts +5 -0
  108. package/node/components/DataGrid/hooks/useWarnRowsWithoutDisabledReason/useWarnRowsWithoutDisabledReason.js +21 -0
  109. package/node/components/DataGrid/index.d.ts +1 -1
  110. package/node/components/DataGrid/public.d.ts +1 -1
  111. package/node/components/DataGrid/tests/performance/testDataGridPerformance.d.ts +3 -1
  112. package/node/components/DataGrid/tests/performance/testDataGridPerformance.js +232 -0
  113. package/node/components/DataGrid/tests/selection/testDataGridSelection.d.ts +14 -0
  114. package/node/components/DataGrid/tests/selection/testDataGridSelection.js +566 -0
  115. package/node/components/DataGrid/types.d.ts +34 -1
  116. package/node/components/DataGrid/useLogic/useLogic.d.ts +6 -3
  117. package/node/components/DataGrid/useLogic/useLogic.js +17 -12
  118. package/node/components/DataGrid/utils/getRowDisabledState/getRowDisabledState.d.ts +31 -0
  119. package/node/components/DataGrid/utils/getRowDisabledState/getRowDisabledState.js +24 -0
  120. package/node/components/DataGrid/utils/getRowDisabledState/index.d.ts +1 -0
  121. package/node/components/DataGrid/utils/getRowDisabledState/index.js +5 -0
  122. package/node/components/DataGrid/utils/index.d.ts +3 -0
  123. package/node/components/DataGrid/utils/index.js +7 -1
  124. package/node/components/DataGrid/utils/resolveRowDisabled/index.d.ts +1 -0
  125. package/node/components/DataGrid/utils/resolveRowDisabled/index.js +5 -0
  126. package/node/components/DataGrid/utils/resolveRowDisabled/resolveRowDisabled.d.ts +5 -0
  127. package/node/components/DataGrid/utils/resolveRowDisabled/resolveRowDisabled.js +21 -0
  128. package/node/components/DataGrid/utils/resolveRowsDisabled/index.d.ts +1 -0
  129. package/node/components/DataGrid/utils/resolveRowsDisabled/index.js +5 -0
  130. package/node/components/DataGrid/utils/resolveRowsDisabled/resolveRowsDisabled.d.ts +16 -0
  131. package/node/components/DataGrid/utils/resolveRowsDisabled/resolveRowsDisabled.js +21 -0
  132. package/node/components/DataGridActionCell/hooks/useBlockingActions/useBlockingActions.js +3 -3
  133. package/node/components/DataGridCollapsibleRow/useLogic/useLogic.d.ts +3 -2
  134. package/node/components/DataGridCollapsibleRow/useLogic/useLogic.js +4 -3
  135. package/node/components/DataGridInfinite/DataGridInfinite.js +4 -4
  136. package/node/components/DataGridInfinite/DataGridInfiniteContext/DataGridInfiniteContext.d.ts +1 -1
  137. package/node/components/DataGridInfinite/DataGridInfiniteContext/DataGridInfiniteContext.js +1 -1
  138. package/node/components/DataGridInfinite/Footer/Footer.js +1 -1
  139. package/node/components/DataGridInfinite/Row/Row.js +2 -2
  140. package/node/components/DataGridInfinite/Row/index.d.ts +1 -0
  141. package/node/components/DataGridInfinite/Row/types.d.ts +17 -2
  142. package/node/components/DataGridInfinite/Row/useLogic/useLogic.d.ts +4 -2
  143. package/node/components/DataGridInfinite/Row/useLogic/useLogic.js +12 -4
  144. package/node/components/DataGridInfinite/constants.d.ts +2 -0
  145. package/node/components/DataGridInfinite/constants.js +2 -0
  146. package/node/components/DataGridInfinite/faker.d.ts +1 -5
  147. package/node/components/DataGridInfinite/faker.js +1 -2
  148. package/node/components/DataGridInfinite/useLogic/useLogic.d.ts +3 -2
  149. package/node/components/DataGridInfinite/useLogic/useLogic.js +26 -14
  150. package/node/components/index.d.ts +1 -1
  151. package/package.json +1 -1
@@ -1,16 +1,21 @@
1
1
  import { useRef, useState, } from 'react';
2
2
  import { redirectToLink } from '../../../utils/redirectToLink';
3
3
  import { useMobileLogic } from '../../Row/useLogic/hooks';
4
+ import { getRowDisabledState } from '../../utils';
4
5
  import { DISABLE_ROW_ATTR } from '../constants';
5
- export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOffset, customContent, renderStartAdornment, isSelected, isDisabled, disabledReason, onSelectRow, onRowEnter, onRowLeave, registerRowElement, unregisterRowElement, gridColumns, activeRowId, emptyCellValue, isSelectable, rowId, mobileDisplayVariant, ...dataGridInfiniteItemProps }) => {
6
+ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOffset, customContent, renderStartAdornment, isSelected, hasBlockingAction, blockingActionReason, rowDisabled, onSelectRow, onRowEnter, onRowLeave, registerRowElement, unregisterRowElement, gridColumns, activeRowId, emptyCellValue, isSelectable, rowId, mobileDisplayVariant, ...dataGridInfiniteItemProps }) => {
6
7
  const [isVisibleTooltip, setVisibleTooltip] = useState(false);
7
8
  const savedElementRef = useRef(null);
8
9
  const { isDrawerOpen, handleOpenDrawer, handleCloseDrawer } = useMobileLogic();
9
10
  const { options } = row;
10
11
  const { style: dataGridInfiniteItemStyle, ...restDataGridInfiniteItemProps } = dataGridInfiniteItemProps || {};
11
- const { disabledReason: propsDisabledReason, isDisabled: propsIsDisabled, isDisabledLastCell, href, isNotSelectable, target, } = options || {};
12
- const disabled = isDisabled || propsIsDisabled;
13
- const disabledReasonText = propsDisabledReason || disabledReason;
12
+ const { href, isNotSelectable, target } = options || {};
13
+ const { isDisabled, isDisabledLastCell, disabledReason, isCheckboxDisabled, checkboxDisabledReason, } = getRowDisabledState({
14
+ rowDisabled,
15
+ hasBlockingAction,
16
+ blockingActionReason,
17
+ isDisabledLastCellOption: options?.isDisabledLastCell,
18
+ });
14
19
  const handleOpenTooltip = (event) => {
15
20
  const element = event.target;
16
21
  const isDisabledCell = element?.getAttribute(DISABLE_ROW_ATTR) === 'true';
@@ -23,7 +28,7 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
23
28
  '--row-base-grid-columns': gridColumns,
24
29
  ...(dataGridInfiniteItemStyle || {}),
25
30
  };
26
- const isHovered = Boolean(!disabled && (onRowClick || href));
31
+ const isHovered = Boolean(!isDisabled && (onRowClick || href));
27
32
  const isActive = activeRowId === rowId;
28
33
  const rowRef = (element) => {
29
34
  if (element) {
@@ -36,7 +41,7 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
36
41
  }
37
42
  };
38
43
  const handleRowClick = () => {
39
- if (disabled) {
44
+ if (isDisabled) {
40
45
  handleOpenDrawer();
41
46
  return undefined;
42
47
  }
@@ -49,7 +54,7 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
49
54
  handleRowClick,
50
55
  tooltipProps: {
51
56
  open: isVisibleTooltip,
52
- title: disabled ? disabledReasonText : undefined,
57
+ title: isDisabled ? disabledReason : undefined,
53
58
  onOpen: handleOpenTooltip,
54
59
  onClose: handleCloseTooltip,
55
60
  followCursor: true,
@@ -57,7 +62,8 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
57
62
  },
58
63
  checkboxProps: {
59
64
  checked: isSelected,
60
- disabled,
65
+ disabled: isCheckboxDisabled,
66
+ disabledReason: checkboxDisabledReason,
61
67
  onChange: onSelectRow(row),
62
68
  },
63
69
  drawerProps: {
@@ -65,7 +71,7 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
65
71
  onClose: handleCloseDrawer,
66
72
  title: 'Заблокировано',
67
73
  },
68
- drawerContent: disabledReasonText,
74
+ drawerContent: disabledReason,
69
75
  rowBaseVars,
70
76
  isHovered,
71
77
  isActive,
@@ -78,7 +84,7 @@ export const useLogic = ({ className, row, keyId, onRowClick, columns, sectionOf
78
84
  onRowLeave,
79
85
  rowId,
80
86
  row,
81
- isDisabled: disabled,
87
+ isDisabled,
82
88
  sectionOffset,
83
89
  customContent,
84
90
  isDisabledLastCell,
@@ -0,0 +1,5 @@
1
+ import { type RowCheckboxProps } from './types';
2
+ /**
3
+ * Checkbox строки. Для заблокированного выбора показывает причину в tooltip
4
+ */
5
+ export declare const RowCheckbox: ({ disabledReason, ...checkboxProps }: RowCheckboxProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Checkbox } from '../../Checkbox';
3
+ import { Tooltip } from '../../Tooltip';
4
+ import { DISABLED_REASON_TOOLTIP_DELAY } from './constants';
5
+ /**
6
+ * Checkbox строки. Для заблокированного выбора показывает причину в tooltip
7
+ */
8
+ export const RowCheckbox = ({ disabledReason, ...checkboxProps }) => {
9
+ if (!disabledReason) {
10
+ return _jsx(Checkbox, { ...checkboxProps });
11
+ }
12
+ return (_jsx(Tooltip, { title: disabledReason, enterDelay: DISABLED_REASON_TOOLTIP_DELAY, enterNextDelay: DISABLED_REASON_TOOLTIP_DELAY, withoutContainer: false, children: _jsx(Checkbox, { ...checkboxProps }) }));
13
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Задержка открытия tooltip с причиной блокировки выбора, мс
3
+ */
4
+ export declare const DISABLED_REASON_TOOLTIP_DELAY = 300;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Задержка открытия tooltip с причиной блокировки выбора, мс
3
+ */
4
+ export const DISABLED_REASON_TOOLTIP_DELAY = 300;
@@ -0,0 +1 @@
1
+ export * from './RowCheckbox';
@@ -0,0 +1 @@
1
+ export * from './RowCheckbox';
@@ -0,0 +1,7 @@
1
+ import { type CheckboxProps } from '../../Checkbox';
2
+ export type RowCheckboxProps = CheckboxProps & {
3
+ /**
4
+ * Причина блокировки выбора
5
+ */
6
+ disabledReason?: string;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,2 @@
1
1
  export * from './useRowHover';
2
+ export * from './useWarnRowsWithoutDisabledReason';
@@ -1 +1,2 @@
1
1
  export * from './useRowHover';
2
+ export * from './useWarnRowsWithoutDisabledReason';
@@ -0,0 +1 @@
1
+ export * from './useWarnRowsWithoutDisabledReason';
@@ -0,0 +1 @@
1
+ export * from './useWarnRowsWithoutDisabledReason';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Выводит одно агрегированное предупреждение о строках с блокировкой без причины.
3
+ * Строки, о которых уже предупреждали, повторно не попадают в предупреждение
4
+ */
5
+ export declare const useWarnRowsWithoutDisabledReason: (idsWithoutReason: unknown[]) => void;
@@ -0,0 +1,17 @@
1
+ import { useEffect, useRef } from 'react';
2
+ /**
3
+ * Выводит одно агрегированное предупреждение о строках с блокировкой без причины.
4
+ * Строки, о которых уже предупреждали, повторно не попадают в предупреждение
5
+ */
6
+ export const useWarnRowsWithoutDisabledReason = (idsWithoutReason) => {
7
+ const warnedIdsRef = useRef(new Set());
8
+ useEffect(() => {
9
+ const newIds = idsWithoutReason.filter((id) => !warnedIdsRef.current.has(id));
10
+ if (!newIds.length) {
11
+ return;
12
+ }
13
+ newIds.forEach((id) => warnedIdsRef.current.add(id));
14
+ // biome-ignore lint/suspicious/noConsole: Ошибка для интеграторов
15
+ console.warn(`UIKIT. DataGrid: getRowDisabled вернул блокировку без reason для строк: ${newIds.join(', ')}`);
16
+ }, [idsWithoutReason]);
17
+ };
@@ -1,5 +1,5 @@
1
1
  export { DataGrid } from './DataGrid';
2
- export type { DataGridProps, DataGridRenderLink, DataGridRenderLinkParams, DataGridColumns, DataGridSingleColumn, DataGridRow, DataGridRowOptions, DataGridSort, DataGridRowWithOptions, DataGridRowOptionColumns, } from './types';
2
+ export type { DataGridProps, DataGridRenderLink, DataGridRenderLinkParams, DataGridColumns, DataGridSingleColumn, DataGridRow, DataGridRowOptions, DataGridRowDisabled, DataGridSort, DataGridRowWithOptions, DataGridRowOptionColumns, } from './types';
3
3
  export { dataGridClassnames } from './constants';
4
4
  export { MobileSkeleton } from './MobileSkeleton';
5
5
  export { RowBase, type RowBaseProps } from './RowBase';
@@ -1,3 +1,3 @@
1
1
  export { dataGridClassnames } from './constants';
2
2
  export { DataGrid } from './DataGrid';
3
- export type { DataGridColumns, DataGridProps, DataGridRenderLink, DataGridRenderLinkParams, DataGridRow, DataGridRowOptionColumns, DataGridRowOptions, DataGridRowWithOptions, DataGridSort, } from './types';
3
+ export type { DataGridColumns, DataGridProps, DataGridRenderLink, DataGridRenderLinkParams, DataGridRow, DataGridRowDisabled, DataGridRowOptionColumns, DataGridRowOptions, DataGridRowWithOptions, DataGridSort, } from './types';
@@ -1,11 +1,13 @@
1
1
  import { type ReactElement, type ReactNode } from 'react';
2
- import { type DataGridColumns, type DataGridRowWithOptions } from '../../types';
2
+ import { type DataGridColumns, type DataGridProps, type DataGridRowDisabled, type DataGridRowWithOptions } from '../../types';
3
3
  type RenderRow = (row: Record<string, unknown>, index: number) => ReactNode;
4
4
  type GridRenderProps = {
5
5
  rows: DataGridRowWithOptions<DataItem>[];
6
6
  columns: DataGridColumns<DataItem>[];
7
7
  selectedRows?: DataItem[];
8
8
  onSelectRow?: (rows: DataItem[]) => void;
9
+ getRowDisabled?: (row: DataItem) => DataGridRowDisabled | undefined;
10
+ tree?: DataGridProps<DataItem>['tree'];
9
11
  renderRow?: RenderRow;
10
12
  onRetry: () => void;
11
13
  };
@@ -1,12 +1,28 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { screen, userEvents } from '@ui/dev/tests';
3
- import { useMemo, useState } from 'react';
4
- import { expect, it, vi } from 'vitest';
3
+ import { useCallback, useMemo, useState, } from 'react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
5
  const TEST_ROWS = [
6
6
  { id: '1', name: 'Vasya' },
7
7
  { id: '2', name: 'Petya' },
8
8
  { id: '3', name: 'Ivan' },
9
9
  ];
10
+ const TEST_ROWS_WITH_CHILDREN = [
11
+ ...TEST_ROWS,
12
+ {
13
+ id: '4',
14
+ name: 'Package',
15
+ children: [{ id: '4-1', name: 'Nested' }],
16
+ },
17
+ ];
18
+ const SELECTION_DISABLED = {
19
+ mode: 'selection',
20
+ reason: 'Документ отправляется',
21
+ };
22
+ const ROW_DISABLED = {
23
+ mode: 'row',
24
+ reason: 'Документ отправляется',
25
+ };
10
26
  export const testDataGridPerformance = ({ render, renderGrid, }) => {
11
27
  const getRenderCountFor = (spy, id) => spy.mock.calls.filter(([row]) => row.id === id).length;
12
28
  it('При клике на чекбокс перерендеривается только выбранная строка', async () => {
@@ -99,4 +115,220 @@ export const testDataGridPerformance = ({ render, renderGrid, }) => {
99
115
  expect(getRenderCountFor(renderCellSpy, '2')).toBe(0);
100
116
  expect(getRenderCountFor(renderCellSpy, '3')).toBe(1);
101
117
  });
118
+ describe('getRowDisabled', () => {
119
+ const createColumns = (renderCell) => [
120
+ { field: 'name', label: 'Наименование', renderCell },
121
+ ];
122
+ const getRenderCounts = (spy, ids) => Object.fromEntries(ids.map((id) => [id, getRenderCountFor(spy, id)]));
123
+ const setupSelectableGrid = () => {
124
+ const renderCellSpy = vi.fn((row) => row.name);
125
+ const getRowDisabledSpy = vi.fn(() => undefined);
126
+ const TestComponent = () => {
127
+ const [selectedRows, setSelectedRows] = useState([]);
128
+ const columns = useMemo(() => createColumns(renderCellSpy), []);
129
+ return renderGrid({
130
+ rows: TEST_ROWS,
131
+ columns,
132
+ selectedRows,
133
+ onSelectRow: setSelectedRows,
134
+ getRowDisabled: getRowDisabledSpy,
135
+ onRetry: () => { },
136
+ });
137
+ };
138
+ render(_jsx(TestComponent, {}));
139
+ renderCellSpy.mockClear();
140
+ getRowDisabledSpy.mockClear();
141
+ return { renderCellSpy, getRowDisabledSpy };
142
+ };
143
+ const setupParentRerender = () => {
144
+ const renderCellSpy = vi.fn((row) => row.name);
145
+ const getRowDisabledSpy = vi.fn(() => undefined);
146
+ const TestComponent = () => {
147
+ const [, setCounter] = useState(0);
148
+ const columns = useMemo(() => createColumns(renderCellSpy), []);
149
+ return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setCounter((value) => value + 1), children: "Rerender" }), renderGrid({
150
+ rows: TEST_ROWS,
151
+ columns,
152
+ selectedRows: [],
153
+ onSelectRow: () => { },
154
+ getRowDisabled: getRowDisabledSpy,
155
+ onRetry: () => { },
156
+ })] }));
157
+ };
158
+ render(_jsx(TestComponent, {}));
159
+ renderCellSpy.mockClear();
160
+ getRowDisabledSpy.mockClear();
161
+ return { renderCellSpy, getRowDisabledSpy };
162
+ };
163
+ const setupChangedResult = () => {
164
+ const renderCellSpy = vi.fn((row) => row.name);
165
+ const getRowDisabledSpy = vi.fn();
166
+ const TestComponent = () => {
167
+ const [lockedId, setLockedId] = useState();
168
+ const columns = useMemo(() => createColumns(renderCellSpy), []);
169
+ const getRowDisabled = useCallback((row) => {
170
+ getRowDisabledSpy(row);
171
+ return row.id === lockedId ? SELECTION_DISABLED : undefined;
172
+ }, [lockedId]);
173
+ return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setLockedId('2'), children: "Lock row 2" }), renderGrid({
174
+ rows: TEST_ROWS,
175
+ columns,
176
+ selectedRows: [],
177
+ onSelectRow: () => { },
178
+ getRowDisabled,
179
+ onRetry: () => { },
180
+ })] }));
181
+ };
182
+ render(_jsx(TestComponent, {}));
183
+ renderCellSpy.mockClear();
184
+ getRowDisabledSpy.mockClear();
185
+ return { renderCellSpy, getRowDisabledSpy };
186
+ };
187
+ const setupSameResult = () => {
188
+ const renderCellSpy = vi.fn((row) => row.name);
189
+ const getRowDisabledSpy = vi.fn();
190
+ const TestComponent = () => {
191
+ const [counter, setCounter] = useState(0);
192
+ const columns = useMemo(() => createColumns(renderCellSpy), []);
193
+ const getRowDisabled = useCallback((row) => {
194
+ getRowDisabledSpy(row);
195
+ return undefined;
196
+ }, [counter]);
197
+ return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setCounter((value) => value + 1), children: "New function" }), renderGrid({
198
+ rows: TEST_ROWS_WITH_CHILDREN,
199
+ columns,
200
+ selectedRows: [],
201
+ onSelectRow: () => { },
202
+ getRowDisabled,
203
+ tree: { isInitialExpanded: true },
204
+ onRetry: () => { },
205
+ })] }));
206
+ };
207
+ render(_jsx(TestComponent, {}));
208
+ renderCellSpy.mockClear();
209
+ getRowDisabledSpy.mockClear();
210
+ return { renderCellSpy, getRowDisabledSpy };
211
+ };
212
+ describe('Клик на чекбокс', () => {
213
+ it('Перерендеривается только выбранная строка', async () => {
214
+ const { renderCellSpy } = setupSelectableGrid();
215
+ // 0 - "Выбрать все", 1 - первая строка
216
+ await userEvents.click(screen.getAllByRole('checkbox')[2]);
217
+ expect(getRenderCounts(renderCellSpy, ['1', '2', '3'])).toEqual({
218
+ '1': 0,
219
+ '2': 1,
220
+ '3': 0,
221
+ });
222
+ });
223
+ it('getRowDisabled не вызывается', async () => {
224
+ const { getRowDisabledSpy } = setupSelectableGrid();
225
+ await userEvents.click(screen.getAllByRole('checkbox')[2]);
226
+ expect(getRowDisabledSpy).not.toHaveBeenCalled();
227
+ });
228
+ });
229
+ it('getRowDisabled не вызывается при «Выбрать все»', async () => {
230
+ const { getRowDisabledSpy } = setupSelectableGrid();
231
+ await userEvents.click(screen.getAllByRole('checkbox')[0]);
232
+ expect(getRowDisabledSpy).not.toHaveBeenCalled();
233
+ });
234
+ describe('Перерисовка родителя со стабильной функцией', () => {
235
+ it('Строки не перерисовываются', async () => {
236
+ const { renderCellSpy } = setupParentRerender();
237
+ await userEvents.click(screen.getByText('Rerender'));
238
+ expect(renderCellSpy).not.toHaveBeenCalled();
239
+ });
240
+ it('getRowDisabled не вызывается', async () => {
241
+ const { getRowDisabledSpy } = setupParentRerender();
242
+ await userEvents.click(screen.getByText('Rerender'));
243
+ expect(getRowDisabledSpy).not.toHaveBeenCalled();
244
+ });
245
+ });
246
+ describe('Новая функция с изменившимся результатом', () => {
247
+ it('Перерендеривается только строка с изменившимся результатом', async () => {
248
+ const { renderCellSpy } = setupChangedResult();
249
+ await userEvents.click(screen.getByText('Lock row 2'));
250
+ expect(getRenderCounts(renderCellSpy, ['1', '2', '3'])).toEqual({
251
+ '1': 0,
252
+ '2': 1,
253
+ '3': 0,
254
+ });
255
+ });
256
+ it('getRowDisabled вызывается один раз на строку', async () => {
257
+ const { getRowDisabledSpy } = setupChangedResult();
258
+ await userEvents.click(screen.getByText('Lock row 2'));
259
+ expect(getRowDisabledSpy).toHaveBeenCalledTimes(TEST_ROWS.length);
260
+ });
261
+ });
262
+ describe('Новая функция с тем же результатом', () => {
263
+ it('Перерисовываются только строки с дочерними элементами', async () => {
264
+ const { renderCellSpy } = setupSameResult();
265
+ await userEvents.click(screen.getByText('New function'));
266
+ expect(getRenderCounts(renderCellSpy, ['1', '2', '3', '4', '4-1'])).toEqual({ '1': 0, '2': 0, '3': 0, '4': 1, '4-1': 1 });
267
+ });
268
+ it('getRowDisabled вызывается один раз на строку, включая вложенную', async () => {
269
+ const { getRowDisabledSpy } = setupSameResult();
270
+ await userEvents.click(screen.getByText('New function'));
271
+ // 4 строки и 1 вложенная
272
+ expect(getRowDisabledSpy).toHaveBeenCalledTimes(5);
273
+ });
274
+ });
275
+ // Virtuoso отдаёт строкам новые данные на рендер позже родителя: состояние строки
276
+ // не должно приходить раньше данных и перерисовывать строку дважды
277
+ describe('Новые rows вместе с изменением состояния строки', () => {
278
+ const createVersionRows = (version) => TEST_ROWS.map((row) => ({ ...row, name: `${row.name} v${version}` }));
279
+ const setupRowsUpdate = () => {
280
+ const renderCellSpy = vi.fn((row) => row.name);
281
+ const TestComponent = () => {
282
+ const [version, setVersion] = useState(0);
283
+ const [isOptionsLocked, setOptionsLocked] = useState(false);
284
+ const [lockedId, setLockedId] = useState();
285
+ const [selectedRows, setSelectedRows] = useState([]);
286
+ const columns = useMemo(() => createColumns(renderCellSpy), []);
287
+ const versionRows = useMemo(() => createVersionRows(version), [version]);
288
+ // Заново создаётся только заблокированная строка, остальные сохраняют ссылки
289
+ const rows = useMemo(() => versionRows.map((row) => isOptionsLocked && row.id === '2'
290
+ ? { ...row, options: { isDisabled: true } }
291
+ : row), [versionRows, isOptionsLocked]);
292
+ const getRowDisabled = useCallback((row) => (row.id === lockedId ? ROW_DISABLED : undefined), [lockedId]);
293
+ return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setOptionsLocked(true), children: "Lock via options" }), _jsx("button", { onClick: () => {
294
+ setVersion(1);
295
+ setSelectedRows([TEST_ROWS[1]]);
296
+ }, children: "Refetch and select" }), _jsx("button", { onClick: () => {
297
+ setVersion(1);
298
+ setLockedId('2');
299
+ }, children: "Refetch and lock" }), renderGrid({
300
+ rows,
301
+ columns,
302
+ selectedRows,
303
+ onSelectRow: setSelectedRows,
304
+ getRowDisabled,
305
+ onRetry: () => { },
306
+ })] }));
307
+ };
308
+ render(_jsx(TestComponent, {}));
309
+ renderCellSpy.mockClear();
310
+ return { renderCellSpy };
311
+ };
312
+ it('Строка перерисовывается один раз при блокировке через options', async () => {
313
+ const { renderCellSpy } = setupRowsUpdate();
314
+ await userEvents.click(screen.getByText('Lock via options'));
315
+ expect(getRenderCounts(renderCellSpy, ['1', '2', '3'])).toEqual({
316
+ '1': 0,
317
+ '2': 1,
318
+ '3': 0,
319
+ });
320
+ });
321
+ it.each([
322
+ { condition: 'изменении выбора', action: 'Refetch and select' },
323
+ {
324
+ condition: 'блокировке через getRowDisabled',
325
+ action: 'Refetch and lock',
326
+ },
327
+ ])('Строки перерисовываются один раз и сразу с новыми данными при $condition', async ({ action }) => {
328
+ const { renderCellSpy } = setupRowsUpdate();
329
+ await userEvents.click(screen.getByText(action));
330
+ expect(renderCellSpy.mock.calls.map(([row]) => row.name)).toEqual(['Vasya v1', 'Petya v1', 'Ivan v1']);
331
+ });
332
+ });
333
+ });
102
334
  };
@@ -0,0 +1,14 @@
1
+ import { type RenderResult } from '@ui/dev/tests';
2
+ import { type ReactElement } from 'react';
3
+ import { type DataGridProps } from '../../types';
4
+ type DataItem = {
5
+ id: string | number;
6
+ name: string;
7
+ };
8
+ type GridRenderProps = Pick<DataGridProps<DataItem>, 'rows' | 'columns' | 'selectedRows' | 'onSelectRow' | 'getRowDisabled' | 'onRowClick' | 'variant' | 'tree' | 'onRetry'>;
9
+ type TestDataGridSelectionParams = {
10
+ render: (ui: ReactElement) => Pick<RenderResult, 'rerender'>;
11
+ renderGrid: (props: GridRenderProps) => ReactElement;
12
+ };
13
+ export declare const testDataGridSelection: ({ render, renderGrid, }: TestDataGridSelectionParams) => void;
14
+ export {};