@astral/ui 4.79.0 → 4.80.1

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.
@@ -20,7 +20,7 @@ import { ErrorMessage } from './ErrorMessage';
20
20
  import { List } from './List';
21
21
  import { NoData } from './NoData';
22
22
  import { Row } from './Row';
23
- import { Backdrop, DataGridWrapper, DisabledDataGridWrapper, FooterRow, StyledContainer, StyledScrollToTopButton, } from './styles';
23
+ import { Backdrop, DataGridWrapper, DisabledDataGridWrapper, FooterRow, HeaderRow, StyledContainer, StyledScrollToTopButton, } from './styles';
24
24
  import { useLogic } from './useLogic';
25
25
  const dataGridInfiniteCva = cva(dataGridClassnames.root, {
26
26
  variants: {
@@ -42,7 +42,7 @@ const dataGridInfiniteCva = cva(dataGridClassnames.root, {
42
42
  });
43
43
  export const DataGridInfinite = (props) => {
44
44
  const { imagesMap, emptySymbol } = useContext(ConfigContext);
45
- const { isNoData, isDataGridDisabled, treeRenderConfig, virtuosoProps, headProps, rowProps, scrollToTopButtonProps, isEndReached, isHideHead, containerCssVars, isMobile, mobileSkeletonProps, hasGroupedColumns, gridViewMode, gridColumns, handleSelectRow, mobileDisplayVariant, isSelfContained, } = useLogic(props);
45
+ const { isNoData, isDataGridDisabled, treeRenderConfig, virtuosoProps, headProps, rowProps, scrollToTopButtonProps, isEndReached, isHideHead, containerCssVars, isMobile, mobileSkeletonProps, hasGroupedColumns, gridViewMode, gridColumns, handleSelectRow, mobileDisplayVariant, isSelfContained, isNotRenderHeader, initialTopMostItemIndex, } = useLogic(props);
46
46
  const { columns, rows = [], selectedRows = [], sorting, isLoading, isDisabled, keyId, emptyCellValue = emptySymbol, variant = Variant.Tree, subrows, className, isError, endOfScrollMsg, errorMsg, noDataPlaceholder, onRowClick, onSort, onRetry, headPreAddon, activeRowId, isTabularNums, renderRow, } = props;
47
47
  const { moreButtonColumnPosition = 1, isVisibleCollapseButton = true } = subrows || {};
48
48
  const { isInitialExpanded = false, expandedLevel = EXPANDED_LEVEL_BY_DEFAULT, initialVisibleChildrenCount = INITIAL_OPENED_NESTED_CHILDREN_COUNT_BY_DEFAULT, } = treeRenderConfig || {};
@@ -58,7 +58,9 @@ export const DataGridInfinite = (props) => {
58
58
  errorList: [errorMsg || ''],
59
59
  imgSrc: imagesMap.defaultErrorImgSrc,
60
60
  onRetry,
61
- }, children: _jsx(Virtuoso, { ...virtuosoProps, style: { height: '100%' }, overscan: OVERSCAN_COUNT, data: rows, components: {
61
+ }, children: _jsx(Virtuoso, { ...virtuosoProps, ...(initialTopMostItemIndex && {
62
+ initialTopMostItemIndex,
63
+ }), style: { height: '100%' }, overscan: OVERSCAN_COUNT, data: rows, components: {
62
64
  // biome-ignore lint/suspicious/noTsIgnore: Требует HTMLDivElement, а для элемента списка используем HTMLUListElement (https://github.com/petyosi/react-virtuoso/issues/864)
63
65
  // @ts-ignore
64
66
  List,
@@ -70,5 +72,11 @@ export const DataGridInfinite = (props) => {
70
72
  }
71
73
  return (_jsxs(FooterRow, { children: [isLoading && _jsx(Loader, {}), isError && _jsx(ErrorMessage, { onRetry: onRetry }), isEndReached && (_jsx(EndData, { endOfScrollMsg: endOfScrollMsg }))] }));
72
74
  },
75
+ Header: () => {
76
+ if (isNotRenderHeader) {
77
+ return null;
78
+ }
79
+ return (_jsxs(HeaderRow, { children: [isLoading && _jsx(Loader, {}), isError && _jsx(ErrorMessage, { onRetry: onRetry })] }));
80
+ },
73
81
  } }) }), _jsx(StyledScrollToTopButton, { ...scrollToTopButtonProps, className: dataGridInfiniteClassnames.scrollToTopButton }), isDisabled && _jsx(Backdrop, {})] })] }) }) }));
74
82
  };
@@ -6,5 +6,5 @@ import { useLogic } from './useLogic';
6
6
  const INITIAL_LEVEL = 0;
7
7
  export const Row = ({ children, ...props }) => {
8
8
  const { item, memoizedItemProps, onSelectRow, renderRow, flattenColumns, gridColumns, options, keyId, variant, isSelectable, isSelected, nestedChildren, isInitialExpanded, expandedLevel, initialVisibleChildrenCount, moreButtonColumnPosition, isVisibleCollapseButton, emptyCellValue, activeRowId, onRowClick, } = useLogic(props);
9
- return (_jsx(RowContextProvider, { row: item, itemProps: memoizedItemProps, onSelectRow: onSelectRow, isSelectable: isSelectable, isSelected: isSelected, children: renderRow ? (_jsx(CustomRowWrapper, { index: memoizedItemProps['data-item-index'], renderRow: renderRow, row: item })) : (_jsx(RowComponent, { ...memoizedItemProps, row: item, columns: flattenColumns, gridColumns: gridColumns, options: options, keyId: keyId, variant: variant, isSelectable: isSelectable, isSelected: isSelected, level: INITIAL_LEVEL, nestedChildren: nestedChildren, isInitialExpanded: isInitialExpanded || false, expandedLevel: expandedLevel || 1, initialVisibleChildrenCount: initialVisibleChildrenCount || 2, activeRowId: activeRowId, moreButtonColumnPosition: moreButtonColumnPosition || 1, isVisibleCollapseButton: isVisibleCollapseButton || true, emptyCellValue: emptyCellValue, onRowClick: onRowClick, onSelectRow: onSelectRow })) }));
9
+ return (_jsx(RowContextProvider, { row: item, itemProps: memoizedItemProps, onSelectRow: onSelectRow, isSelectable: isSelectable, isSelected: isSelected, children: renderRow ? (_jsx(CustomRowWrapper, { index: memoizedItemProps['data-index'], renderRow: renderRow, row: item })) : (_jsx(RowComponent, { ...memoizedItemProps, row: item, columns: flattenColumns, gridColumns: gridColumns, options: options, keyId: keyId, variant: variant, isSelectable: isSelectable, isSelected: isSelected, level: INITIAL_LEVEL, nestedChildren: nestedChildren, isInitialExpanded: isInitialExpanded || false, expandedLevel: expandedLevel || 1, initialVisibleChildrenCount: initialVisibleChildrenCount || 2, activeRowId: activeRowId, moreButtonColumnPosition: moreButtonColumnPosition || 1, isVisibleCollapseButton: isVisibleCollapseButton || true, emptyCellValue: emptyCellValue, onRowClick: onRowClick, onSelectRow: onSelectRow })) }));
10
10
  };
@@ -1,2 +1,2 @@
1
1
  export { DataGridInfinite } from './DataGridInfinite';
2
- export type { DataGridInfiniteProps } from './types';
2
+ export type { DataGridInfiniteProps, DataGridInfiniteVirtuosoHandle, DataGridInfiniteVirtuosoRef, } from './types';
@@ -1,2 +1,2 @@
1
1
  export { DataGridInfinite } from './DataGridInfinite';
2
- export type { DataGridInfiniteProps } from './types';
2
+ export type { DataGridInfiniteProps, DataGridInfiniteVirtuosoHandle, DataGridInfiniteVirtuosoRef, } from './types';
@@ -13,6 +13,10 @@ export declare const FooterRow: import("../styled").StyledComponent<{
13
13
  theme?: import("@emotion/react").Theme | undefined;
14
14
  as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
15
15
  }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, {}>;
16
+ export declare const HeaderRow: import("../styled").StyledComponent<{
17
+ theme?: import("@emotion/react").Theme | undefined;
18
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
19
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, {}>;
16
20
  export declare const Backdrop: import("../styled").StyledComponent<{
17
21
  theme?: import("@emotion/react").Theme | undefined;
18
22
  as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
@@ -26,6 +26,14 @@ export const FooterRow = styled.footer `
26
26
 
27
27
  min-height: 68px;
28
28
  `;
29
+ export const HeaderRow = styled.header `
30
+ display: flex;
31
+ flex-shrink: 0;
32
+ align-items: center;
33
+ justify-content: center;
34
+
35
+ min-height: 68px;
36
+ `;
29
37
  export const Backdrop = styled.div `
30
38
  position: absolute;
31
39
  top: 0;
@@ -1,6 +1,14 @@
1
+ import { type Ref, type RefObject } from 'react';
2
+ import { type VirtuosoHandle } from 'react-virtuoso';
1
3
  import type { DataGridProps, DataGridRow } from '../DataGrid';
2
4
  import { type CellValue } from '../DataGrid/types';
5
+ export type DataGridInfiniteVirtuosoHandle = VirtuosoHandle;
6
+ export type DataGridInfiniteVirtuosoRef = RefObject<DataGridInfiniteVirtuosoHandle | null>;
3
7
  export type DataGridInfiniteProps<TData extends Record<string, CellValue> = DataGridRow, TSortField extends keyof TData = keyof TData> = Omit<DataGridProps<TData, TSortField>, 'footer' | 'minDisplayRows'> & {
8
+ /**
9
+ * Ref для доступа к методам Virtuoso
10
+ */
11
+ virtuosoRef?: Ref<DataGridInfiniteVirtuosoHandle>;
4
12
  /**
5
13
  * Флаг достижения конца списка
6
14
  */
@@ -17,4 +25,16 @@ export type DataGridInfiniteProps<TData extends Record<string, CellValue> = Data
17
25
  * Убирает шапку таблицы, когда нет данных
18
26
  */
19
27
  isHideHeaderIfNoData?: boolean;
28
+ /**
29
+ * Обработчик подгрузки данных при скроле до начала списка
30
+ */
31
+ onStartReached?: () => void;
32
+ /**
33
+ * Флаг достижения начала списка
34
+ */
35
+ isStartReached?: boolean;
36
+ /**
37
+ * Индекс первого видимого элемента
38
+ */
39
+ initialVisibleItemIndex?: number;
20
40
  };
@@ -1,10 +1,10 @@
1
1
  import { type ChangeEvent } from 'react';
2
- import type { ListRange, VirtuosoHandle } from 'react-virtuoso';
2
+ import type { ListRange } from 'react-virtuoso';
3
3
  import { type DataGridRow } from '../../DataGrid';
4
4
  import { type CellValue } from '../../DataGrid/types';
5
- import { type DataGridInfiniteProps } from '../types';
5
+ import { type DataGridInfiniteProps, type DataGridInfiniteVirtuosoHandle } from '../types';
6
6
  type UseLogicParams<TData extends Record<string, CellValue> = DataGridRow, TSortField extends keyof TData = keyof TData> = DataGridInfiniteProps<TData, TSortField>;
7
- export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isDisabled, isEndReached, onEndReached, onSelectRow, isHideSelectAll, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, mobileDisplayVariant, }: UseLogicParams<TData, TSortField>) => {
7
+ export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, mobileDisplayVariant, virtuosoRef, }: UseLogicParams<TData, TSortField>) => {
8
8
  containerCssVars: import("react").CSSProperties;
9
9
  mobileSkeletonProps: {
10
10
  cellsCount: number;
@@ -24,12 +24,17 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
24
24
  hasGroupedColumns: boolean;
25
25
  gridViewMode: "standard" | "full";
26
26
  gridColumns: string;
27
+ isStartReached: boolean | undefined;
28
+ isNotRenderHeader: boolean | undefined;
29
+ initialTopMostItemIndex: number | undefined;
27
30
  virtuosoProps: {
28
31
  id: string;
29
- ref: import("react").RefObject<VirtuosoHandle | null>;
32
+ ref: (node: DataGridInfiniteVirtuosoHandle | null) => void;
30
33
  endReached: () => void;
31
34
  rangeChanged: (range: ListRange) => void;
32
35
  totalListHeightChanged: () => void;
36
+ startReached: () => void;
37
+ firstItemIndex: number;
33
38
  };
34
39
  headProps: {
35
40
  rowsCount: number;
@@ -50,5 +55,6 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
50
55
  handleSelectRow: (row: TData) => (event: ChangeEvent<HTMLInputElement>) => void;
51
56
  mobileDisplayVariant: "list" | "cards";
52
57
  isSelfContained: boolean;
58
+ firstItemIndex: number;
53
59
  };
54
60
  export {};
@@ -7,12 +7,45 @@ import { useViewportType } from '../../useViewportType';
7
7
  import { prop } from '../../utils/prop';
8
8
  import { uniqueBy } from '../../utils/uniqueBy';
9
9
  import { dataGridInfiniteClassnames, VIRTUOSO_CONTAINER_ID, VIRTUOSO_LIST_ID, } from '../constants';
10
- export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isDisabled, isEndReached, onEndReached, onSelectRow, isHideSelectAll, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', }) => {
10
+ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', virtuosoRef, }) => {
11
11
  const virtuoso = useRef(null);
12
+ const handleVirtuosoRef = useCallback((node) => {
13
+ virtuoso.current = node;
14
+ if (virtuosoRef) {
15
+ virtuosoRef.current = node;
16
+ }
17
+ }, [virtuosoRef]);
12
18
  const scrollToTopButtonRef = useRef(null);
13
19
  const { isMobile } = useViewportType();
14
20
  // Реф добавлен для избежания замыкания selectedRows в handleSelectRow (иначе в handleSelectRow приходят некорректные значения selectedRows).
15
21
  const selectedRowsRef = useRef(selectedRows);
22
+ // Рефы для вычисления firstItemIndex
23
+ // baseIndex - базовый индекс для вычисления firstItemIndex. Делаем очень большим, что бы при подгрузке данных вверх(начало списка) virtuoso ставил им индексы меньше чем этот.
24
+ const baseIndex = useRef(10000);
25
+ const initialLength = useRef(rows.length);
26
+ const firstRowId = useRef(rows[0]?.[keyId]);
27
+ // Вычисляем firstItemIndex для динамической подгрузки данных вверх. Что бы virtuoso понимал что данные сверху еще есть и не надо скролить до верха списка.
28
+ const firstItemIndex = useMemo(() => {
29
+ const currentLength = rows.length;
30
+ const currentFirstId = rows[0]?.[keyId];
31
+ if (currentLength !== initialLength.current) {
32
+ // Если первый элемент изменился и длина увеличилась → подгрузка вверх
33
+ if (currentFirstId !== firstRowId.current &&
34
+ currentLength > initialLength.current) {
35
+ const addedCount = currentLength - initialLength.current;
36
+ baseIndex.current -= addedCount;
37
+ firstRowId.current = currentFirstId;
38
+ }
39
+ else if (currentLength < initialLength.current) {
40
+ baseIndex.current = 10000;
41
+ firstRowId.current = currentFirstId;
42
+ }
43
+ // Если длина увеличилась, но первый элемент не изменился → подгрузка вниз
44
+ // baseIndex не меняем
45
+ initialLength.current = currentLength;
46
+ }
47
+ return baseIndex.current;
48
+ }, [rows.length]);
16
49
  useEffect(() => {
17
50
  selectedRowsRef.current = selectedRows;
18
51
  }, [selectedRows]);
@@ -43,12 +76,17 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
43
76
  onEndReached();
44
77
  }
45
78
  }, [isEndReached, onEndReached]);
79
+ const handleStartReach = useCallback(() => {
80
+ if (!isStartReached && onStartReached) {
81
+ onStartReached();
82
+ }
83
+ }, [isStartReached, onStartReached]);
46
84
  const uncheckedRowsCount = useMemo(() => {
47
85
  return availableRows.filter((row) => !selectedRows.find((selectedRow) => selectedRow[keyId] === row[keyId])).length;
48
86
  }, [availableRows, selectedRows, keyId]);
49
87
  const handleRangeChanged = useCallback((range) => {
50
88
  if (scrollToTopButtonRef.current) {
51
- scrollToTopButtonRef.current.classList.toggle(dataGridInfiniteClassnames.visibleScrollToTopButton, range.startIndex > 2);
89
+ scrollToTopButtonRef.current.classList.toggle(dataGridInfiniteClassnames.visibleScrollToTopButton, range.startIndex > baseIndex.current + 2);
52
90
  }
53
91
  }, []);
54
92
  const handleSelectAllRows = (event) => {
@@ -87,6 +125,11 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
87
125
  mobileDisplayVariant,
88
126
  });
89
127
  const containerCssVars = getDataGridCssVars(maxHeight);
128
+ const foundIndex = rows.findIndex((row) => String(row[keyId]) === String(initialVisibleItemIndex));
129
+ const initialTopMostItemIndex = foundIndex === -1 ? undefined : foundIndex;
130
+ const isNotRenderHeader = (!isLoading && !isError && !isStartReached) ||
131
+ !onStartReached ||
132
+ isStartReached;
90
133
  return {
91
134
  containerCssVars,
92
135
  mobileSkeletonProps,
@@ -99,12 +142,17 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
99
142
  hasGroupedColumns,
100
143
  gridViewMode,
101
144
  gridColumns,
145
+ isStartReached,
146
+ isNotRenderHeader,
147
+ initialTopMostItemIndex,
102
148
  virtuosoProps: {
103
149
  id: VIRTUOSO_CONTAINER_ID,
104
- ref: virtuoso,
150
+ ref: handleVirtuosoRef,
105
151
  endReached: handleEndReach,
106
152
  rangeChanged: handleRangeChanged,
107
153
  totalListHeightChanged: recalculateVerticalScroll,
154
+ startReached: handleStartReach,
155
+ firstItemIndex,
108
156
  },
109
157
  headProps: {
110
158
  rowsCount: availableRows.length,
@@ -125,5 +173,6 @@ export const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selecte
125
173
  handleSelectRow,
126
174
  mobileDisplayVariant,
127
175
  isSelfContained: mobileDisplayVariant === 'cards',
176
+ firstItemIndex,
128
177
  };
129
178
  };
@@ -248,7 +248,17 @@ export const getMimeTypesMap = () => ({
248
248
  'image/ief': ['ief'],
249
249
  'image/jls': ['jls'],
250
250
  'image/jp2': ['jp2', 'jpg2'],
251
- 'image/jpeg': ['jpeg', 'jpg', 'jpe'],
251
+ 'image/jpeg': ['jpeg', 'jpg', 'jpe', '.pjpg', 'jfif', 'jfif-tbnl', 'jif'],
252
+ 'image/pjpeg': [
253
+ '.jpe',
254
+ '.jpeg',
255
+ '.jpg',
256
+ '.pjpg',
257
+ '.jfi',
258
+ ' .jfif',
259
+ '.jfif-tbnl',
260
+ ' .jif',
261
+ ],
252
262
  'image/jph': ['jph'],
253
263
  'image/jphc': ['jhc'],
254
264
  'image/jpm': ['jpm'],
@@ -45,7 +45,7 @@ const dataGridInfiniteCva = (0, cva_1.cva)(constants_2.dataGridClassnames.root,
45
45
  });
46
46
  const DataGridInfinite = (props) => {
47
47
  const { imagesMap, emptySymbol } = (0, react_1.useContext)(ConfigProvider_1.ConfigContext);
48
- const { isNoData, isDataGridDisabled, treeRenderConfig, virtuosoProps, headProps, rowProps, scrollToTopButtonProps, isEndReached, isHideHead, containerCssVars, isMobile, mobileSkeletonProps, hasGroupedColumns, gridViewMode, gridColumns, handleSelectRow, mobileDisplayVariant, isSelfContained, } = (0, useLogic_1.useLogic)(props);
48
+ const { isNoData, isDataGridDisabled, treeRenderConfig, virtuosoProps, headProps, rowProps, scrollToTopButtonProps, isEndReached, isHideHead, containerCssVars, isMobile, mobileSkeletonProps, hasGroupedColumns, gridViewMode, gridColumns, handleSelectRow, mobileDisplayVariant, isSelfContained, isNotRenderHeader, initialTopMostItemIndex, } = (0, useLogic_1.useLogic)(props);
49
49
  const { columns, rows = [], selectedRows = [], sorting, isLoading, isDisabled, keyId, emptyCellValue = emptySymbol, variant = enums_1.Variant.Tree, subrows, className, isError, endOfScrollMsg, errorMsg, noDataPlaceholder, onRowClick, onSort, onRetry, headPreAddon, activeRowId, isTabularNums, renderRow, } = props;
50
50
  const { moreButtonColumnPosition = 1, isVisibleCollapseButton = true } = subrows || {};
51
51
  const { isInitialExpanded = false, expandedLevel = constants_2.EXPANDED_LEVEL_BY_DEFAULT, initialVisibleChildrenCount = constants_2.INITIAL_OPENED_NESTED_CHILDREN_COUNT_BY_DEFAULT, } = treeRenderConfig || {};
@@ -61,7 +61,9 @@ const DataGridInfinite = (props) => {
61
61
  errorList: [errorMsg || ''],
62
62
  imgSrc: imagesMap.defaultErrorImgSrc,
63
63
  onRetry,
64
- }, children: (0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ...virtuosoProps, style: { height: '100%' }, overscan: constants_3.OVERSCAN_COUNT, data: rows, components: {
64
+ }, children: (0, jsx_runtime_1.jsx)(react_virtuoso_1.Virtuoso, { ...virtuosoProps, ...(initialTopMostItemIndex && {
65
+ initialTopMostItemIndex,
66
+ }), style: { height: '100%' }, overscan: constants_3.OVERSCAN_COUNT, data: rows, components: {
65
67
  // biome-ignore lint/suspicious/noTsIgnore: Требует HTMLDivElement, а для элемента списка используем HTMLUListElement (https://github.com/petyosi/react-virtuoso/issues/864)
66
68
  // @ts-ignore
67
69
  List: List_1.List,
@@ -73,6 +75,12 @@ const DataGridInfinite = (props) => {
73
75
  }
74
76
  return ((0, jsx_runtime_1.jsxs)(styles_1.FooterRow, { children: [isLoading && (0, jsx_runtime_1.jsx)(Loader_1.Loader, {}), isError && (0, jsx_runtime_1.jsx)(ErrorMessage_1.ErrorMessage, { onRetry: onRetry }), isEndReached && ((0, jsx_runtime_1.jsx)(EndData_1.EndData, { endOfScrollMsg: endOfScrollMsg }))] }));
75
77
  },
78
+ Header: () => {
79
+ if (isNotRenderHeader) {
80
+ return null;
81
+ }
82
+ return ((0, jsx_runtime_1.jsxs)(styles_1.HeaderRow, { children: [isLoading && (0, jsx_runtime_1.jsx)(Loader_1.Loader, {}), isError && (0, jsx_runtime_1.jsx)(ErrorMessage_1.ErrorMessage, { onRetry: onRetry })] }));
83
+ },
76
84
  } }) }), (0, jsx_runtime_1.jsx)(styles_1.StyledScrollToTopButton, { ...scrollToTopButtonProps, className: constants_3.dataGridInfiniteClassnames.scrollToTopButton }), isDisabled && (0, jsx_runtime_1.jsx)(styles_1.Backdrop, {})] })] }) }) }));
77
85
  };
78
86
  exports.DataGridInfinite = DataGridInfinite;
@@ -9,6 +9,6 @@ const useLogic_1 = require("./useLogic");
9
9
  const INITIAL_LEVEL = 0;
10
10
  const Row = ({ children, ...props }) => {
11
11
  const { item, memoizedItemProps, onSelectRow, renderRow, flattenColumns, gridColumns, options, keyId, variant, isSelectable, isSelected, nestedChildren, isInitialExpanded, expandedLevel, initialVisibleChildrenCount, moreButtonColumnPosition, isVisibleCollapseButton, emptyCellValue, activeRowId, onRowClick, } = (0, useLogic_1.useLogic)(props);
12
- return ((0, jsx_runtime_1.jsx)(RowContext_1.RowContextProvider, { row: item, itemProps: memoizedItemProps, onSelectRow: onSelectRow, isSelectable: isSelectable, isSelected: isSelected, children: renderRow ? ((0, jsx_runtime_1.jsx)(CustomRowWrapper_1.CustomRowWrapper, { index: memoizedItemProps['data-item-index'], renderRow: renderRow, row: item })) : ((0, jsx_runtime_1.jsx)(Row_1.Row, { ...memoizedItemProps, row: item, columns: flattenColumns, gridColumns: gridColumns, options: options, keyId: keyId, variant: variant, isSelectable: isSelectable, isSelected: isSelected, level: INITIAL_LEVEL, nestedChildren: nestedChildren, isInitialExpanded: isInitialExpanded || false, expandedLevel: expandedLevel || 1, initialVisibleChildrenCount: initialVisibleChildrenCount || 2, activeRowId: activeRowId, moreButtonColumnPosition: moreButtonColumnPosition || 1, isVisibleCollapseButton: isVisibleCollapseButton || true, emptyCellValue: emptyCellValue, onRowClick: onRowClick, onSelectRow: onSelectRow })) }));
12
+ return ((0, jsx_runtime_1.jsx)(RowContext_1.RowContextProvider, { row: item, itemProps: memoizedItemProps, onSelectRow: onSelectRow, isSelectable: isSelectable, isSelected: isSelected, children: renderRow ? ((0, jsx_runtime_1.jsx)(CustomRowWrapper_1.CustomRowWrapper, { index: memoizedItemProps['data-index'], renderRow: renderRow, row: item })) : ((0, jsx_runtime_1.jsx)(Row_1.Row, { ...memoizedItemProps, row: item, columns: flattenColumns, gridColumns: gridColumns, options: options, keyId: keyId, variant: variant, isSelectable: isSelectable, isSelected: isSelected, level: INITIAL_LEVEL, nestedChildren: nestedChildren, isInitialExpanded: isInitialExpanded || false, expandedLevel: expandedLevel || 1, initialVisibleChildrenCount: initialVisibleChildrenCount || 2, activeRowId: activeRowId, moreButtonColumnPosition: moreButtonColumnPosition || 1, isVisibleCollapseButton: isVisibleCollapseButton || true, emptyCellValue: emptyCellValue, onRowClick: onRowClick, onSelectRow: onSelectRow })) }));
13
13
  };
14
14
  exports.Row = Row;
@@ -1,2 +1,2 @@
1
1
  export { DataGridInfinite } from './DataGridInfinite';
2
- export type { DataGridInfiniteProps } from './types';
2
+ export type { DataGridInfiniteProps, DataGridInfiniteVirtuosoHandle, DataGridInfiniteVirtuosoRef, } from './types';
@@ -1,2 +1,2 @@
1
1
  export { DataGridInfinite } from './DataGridInfinite';
2
- export type { DataGridInfiniteProps } from './types';
2
+ export type { DataGridInfiniteProps, DataGridInfiniteVirtuosoHandle, DataGridInfiniteVirtuosoRef, } from './types';
@@ -13,6 +13,10 @@ export declare const FooterRow: import("@emotion/styled/dist/declarations/src/ty
13
13
  theme?: import("@emotion/react").Theme | undefined;
14
14
  as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
15
15
  }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, {}>;
16
+ export declare const HeaderRow: import("@emotion/styled/dist/declarations/src/types").StyledComponent<{
17
+ theme?: import("@emotion/react").Theme | undefined;
18
+ as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
19
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, {}>;
16
20
  export declare const Backdrop: import("@emotion/styled/dist/declarations/src/types").StyledComponent<{
17
21
  theme?: import("@emotion/react").Theme | undefined;
18
22
  as?: import("react").ElementType<any, keyof import("react").JSX.IntrinsicElements> | undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StyledContainer = exports.StyledScrollToTopButton = exports.Backdrop = exports.FooterRow = exports.DisabledDataGridWrapper = exports.DataGridWrapper = void 0;
3
+ exports.StyledContainer = exports.StyledScrollToTopButton = exports.Backdrop = exports.HeaderRow = exports.FooterRow = exports.DisabledDataGridWrapper = exports.DataGridWrapper = void 0;
4
4
  const styles_1 = require("../DataGrid/styles");
5
5
  const ScrollToTopButton_1 = require("../ScrollToTopButton");
6
6
  const styled_1 = require("../styled");
@@ -29,6 +29,14 @@ exports.FooterRow = styled_1.styled.footer `
29
29
 
30
30
  min-height: 68px;
31
31
  `;
32
+ exports.HeaderRow = styled_1.styled.header `
33
+ display: flex;
34
+ flex-shrink: 0;
35
+ align-items: center;
36
+ justify-content: center;
37
+
38
+ min-height: 68px;
39
+ `;
32
40
  exports.Backdrop = styled_1.styled.div `
33
41
  position: absolute;
34
42
  top: 0;
@@ -1,6 +1,14 @@
1
+ import { type Ref, type RefObject } from 'react';
2
+ import { type VirtuosoHandle } from 'react-virtuoso';
1
3
  import type { DataGridProps, DataGridRow } from '../DataGrid';
2
4
  import { type CellValue } from '../DataGrid/types';
5
+ export type DataGridInfiniteVirtuosoHandle = VirtuosoHandle;
6
+ export type DataGridInfiniteVirtuosoRef = RefObject<DataGridInfiniteVirtuosoHandle | null>;
3
7
  export type DataGridInfiniteProps<TData extends Record<string, CellValue> = DataGridRow, TSortField extends keyof TData = keyof TData> = Omit<DataGridProps<TData, TSortField>, 'footer' | 'minDisplayRows'> & {
8
+ /**
9
+ * Ref для доступа к методам Virtuoso
10
+ */
11
+ virtuosoRef?: Ref<DataGridInfiniteVirtuosoHandle>;
4
12
  /**
5
13
  * Флаг достижения конца списка
6
14
  */
@@ -17,4 +25,16 @@ export type DataGridInfiniteProps<TData extends Record<string, CellValue> = Data
17
25
  * Убирает шапку таблицы, когда нет данных
18
26
  */
19
27
  isHideHeaderIfNoData?: boolean;
28
+ /**
29
+ * Обработчик подгрузки данных при скроле до начала списка
30
+ */
31
+ onStartReached?: () => void;
32
+ /**
33
+ * Флаг достижения начала списка
34
+ */
35
+ isStartReached?: boolean;
36
+ /**
37
+ * Индекс первого видимого элемента
38
+ */
39
+ initialVisibleItemIndex?: number;
20
40
  };
@@ -1,10 +1,10 @@
1
1
  import { type ChangeEvent } from 'react';
2
- import type { ListRange, VirtuosoHandle } from 'react-virtuoso';
2
+ import type { ListRange } from 'react-virtuoso';
3
3
  import { type DataGridRow } from '../../DataGrid';
4
4
  import { type CellValue } from '../../DataGrid/types';
5
- import { type DataGridInfiniteProps } from '../types';
5
+ import { type DataGridInfiniteProps, type DataGridInfiniteVirtuosoHandle } from '../types';
6
6
  type UseLogicParams<TData extends Record<string, CellValue> = DataGridRow, TSortField extends keyof TData = keyof TData> = DataGridInfiniteProps<TData, TSortField>;
7
- export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isDisabled, isEndReached, onEndReached, onSelectRow, isHideSelectAll, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, mobileDisplayVariant, }: UseLogicParams<TData, TSortField>) => {
7
+ export declare const useLogic: <TData extends Record<string, unknown> = DataGridRow, TSortField extends keyof TData = keyof TData>({ keyId, columns, rows, variant, tree, subrows, selectedRows, maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData, gridViewMode: gridViewModeProp, mobileDisplayVariant, virtuosoRef, }: UseLogicParams<TData, TSortField>) => {
8
8
  containerCssVars: import("react").CSSProperties;
9
9
  mobileSkeletonProps: {
10
10
  cellsCount: number;
@@ -24,12 +24,17 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
24
24
  hasGroupedColumns: boolean;
25
25
  gridViewMode: "standard" | "full";
26
26
  gridColumns: string;
27
+ isStartReached: boolean | undefined;
28
+ isNotRenderHeader: boolean | undefined;
29
+ initialTopMostItemIndex: number | undefined;
27
30
  virtuosoProps: {
28
31
  id: string;
29
- ref: import("react").RefObject<VirtuosoHandle | null>;
32
+ ref: (node: DataGridInfiniteVirtuosoHandle | null) => void;
30
33
  endReached: () => void;
31
34
  rangeChanged: (range: ListRange) => void;
32
35
  totalListHeightChanged: () => void;
36
+ startReached: () => void;
37
+ firstItemIndex: number;
33
38
  };
34
39
  headProps: {
35
40
  rowsCount: number;
@@ -50,5 +55,6 @@ export declare const useLogic: <TData extends Record<string, unknown> = DataGrid
50
55
  handleSelectRow: (row: TData) => (event: ChangeEvent<HTMLInputElement>) => void;
51
56
  mobileDisplayVariant: "list" | "cards";
52
57
  isSelfContained: boolean;
58
+ firstItemIndex: number;
53
59
  };
54
60
  export {};
@@ -10,12 +10,45 @@ const useViewportType_1 = require("../../useViewportType");
10
10
  const prop_1 = require("../../utils/prop");
11
11
  const uniqueBy_1 = require("../../utils/uniqueBy");
12
12
  const constants_2 = require("../constants");
13
- const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isDisabled, isEndReached, onEndReached, onSelectRow, isHideSelectAll, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', }) => {
13
+ const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows = [], maxHeight, isLoading, isError, isDisabled, isEndReached, onEndReached, onStartReached, isStartReached, onSelectRow, isHideSelectAll, initialVisibleItemIndex, isHideHeaderIfNoData = false, gridViewMode: gridViewModeProp = 'standard', mobileDisplayVariant = variant === 'subrows' ? 'cards' : 'list', virtuosoRef, }) => {
14
14
  const virtuoso = (0, react_1.useRef)(null);
15
+ const handleVirtuosoRef = (0, react_1.useCallback)((node) => {
16
+ virtuoso.current = node;
17
+ if (virtuosoRef) {
18
+ virtuosoRef.current = node;
19
+ }
20
+ }, [virtuosoRef]);
15
21
  const scrollToTopButtonRef = (0, react_1.useRef)(null);
16
22
  const { isMobile } = (0, useViewportType_1.useViewportType)();
17
23
  // Реф добавлен для избежания замыкания selectedRows в handleSelectRow (иначе в handleSelectRow приходят некорректные значения selectedRows).
18
24
  const selectedRowsRef = (0, react_1.useRef)(selectedRows);
25
+ // Рефы для вычисления firstItemIndex
26
+ // baseIndex - базовый индекс для вычисления firstItemIndex. Делаем очень большим, что бы при подгрузке данных вверх(начало списка) virtuoso ставил им индексы меньше чем этот.
27
+ const baseIndex = (0, react_1.useRef)(10000);
28
+ const initialLength = (0, react_1.useRef)(rows.length);
29
+ const firstRowId = (0, react_1.useRef)(rows[0]?.[keyId]);
30
+ // Вычисляем firstItemIndex для динамической подгрузки данных вверх. Что бы virtuoso понимал что данные сверху еще есть и не надо скролить до верха списка.
31
+ const firstItemIndex = (0, react_1.useMemo)(() => {
32
+ const currentLength = rows.length;
33
+ const currentFirstId = rows[0]?.[keyId];
34
+ if (currentLength !== initialLength.current) {
35
+ // Если первый элемент изменился и длина увеличилась → подгрузка вверх
36
+ if (currentFirstId !== firstRowId.current &&
37
+ currentLength > initialLength.current) {
38
+ const addedCount = currentLength - initialLength.current;
39
+ baseIndex.current -= addedCount;
40
+ firstRowId.current = currentFirstId;
41
+ }
42
+ else if (currentLength < initialLength.current) {
43
+ baseIndex.current = 10000;
44
+ firstRowId.current = currentFirstId;
45
+ }
46
+ // Если длина увеличилась, но первый элемент не изменился → подгрузка вниз
47
+ // baseIndex не меняем
48
+ initialLength.current = currentLength;
49
+ }
50
+ return baseIndex.current;
51
+ }, [rows.length]);
19
52
  (0, react_1.useEffect)(() => {
20
53
  selectedRowsRef.current = selectedRows;
21
54
  }, [selectedRows]);
@@ -46,12 +79,17 @@ const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows =
46
79
  onEndReached();
47
80
  }
48
81
  }, [isEndReached, onEndReached]);
82
+ const handleStartReach = (0, react_1.useCallback)(() => {
83
+ if (!isStartReached && onStartReached) {
84
+ onStartReached();
85
+ }
86
+ }, [isStartReached, onStartReached]);
49
87
  const uncheckedRowsCount = (0, react_1.useMemo)(() => {
50
88
  return availableRows.filter((row) => !selectedRows.find((selectedRow) => selectedRow[keyId] === row[keyId])).length;
51
89
  }, [availableRows, selectedRows, keyId]);
52
90
  const handleRangeChanged = (0, react_1.useCallback)((range) => {
53
91
  if (scrollToTopButtonRef.current) {
54
- scrollToTopButtonRef.current.classList.toggle(constants_2.dataGridInfiniteClassnames.visibleScrollToTopButton, range.startIndex > 2);
92
+ scrollToTopButtonRef.current.classList.toggle(constants_2.dataGridInfiniteClassnames.visibleScrollToTopButton, range.startIndex > baseIndex.current + 2);
55
93
  }
56
94
  }, []);
57
95
  const handleSelectAllRows = (event) => {
@@ -90,6 +128,11 @@ const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows =
90
128
  mobileDisplayVariant,
91
129
  });
92
130
  const containerCssVars = (0, utils_1.getDataGridCssVars)(maxHeight);
131
+ const foundIndex = rows.findIndex((row) => String(row[keyId]) === String(initialVisibleItemIndex));
132
+ const initialTopMostItemIndex = foundIndex === -1 ? undefined : foundIndex;
133
+ const isNotRenderHeader = (!isLoading && !isError && !isStartReached) ||
134
+ !onStartReached ||
135
+ isStartReached;
93
136
  return {
94
137
  containerCssVars,
95
138
  mobileSkeletonProps,
@@ -102,12 +145,17 @@ const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows =
102
145
  hasGroupedColumns,
103
146
  gridViewMode,
104
147
  gridColumns,
148
+ isStartReached,
149
+ isNotRenderHeader,
150
+ initialTopMostItemIndex,
105
151
  virtuosoProps: {
106
152
  id: constants_2.VIRTUOSO_CONTAINER_ID,
107
- ref: virtuoso,
153
+ ref: handleVirtuosoRef,
108
154
  endReached: handleEndReach,
109
155
  rangeChanged: handleRangeChanged,
110
156
  totalListHeightChanged: recalculateVerticalScroll,
157
+ startReached: handleStartReach,
158
+ firstItemIndex,
111
159
  },
112
160
  headProps: {
113
161
  rowsCount: availableRows.length,
@@ -128,6 +176,7 @@ const useLogic = ({ keyId, columns, rows, variant, tree, subrows, selectedRows =
128
176
  handleSelectRow,
129
177
  mobileDisplayVariant,
130
178
  isSelfContained: mobileDisplayVariant === 'cards',
179
+ firstItemIndex,
131
180
  };
132
181
  };
133
182
  exports.useLogic = useLogic;
@@ -251,7 +251,17 @@ const getMimeTypesMap = () => ({
251
251
  'image/ief': ['ief'],
252
252
  'image/jls': ['jls'],
253
253
  'image/jp2': ['jp2', 'jpg2'],
254
- 'image/jpeg': ['jpeg', 'jpg', 'jpe'],
254
+ 'image/jpeg': ['jpeg', 'jpg', 'jpe', '.pjpg', 'jfif', 'jfif-tbnl', 'jif'],
255
+ 'image/pjpeg': [
256
+ '.jpe',
257
+ '.jpeg',
258
+ '.jpg',
259
+ '.pjpg',
260
+ '.jfi',
261
+ ' .jfif',
262
+ '.jfif-tbnl',
263
+ ' .jif',
264
+ ],
255
265
  'image/jph': ['jph'],
256
266
  'image/jphc': ['jhc'],
257
267
  'image/jpm': ['jpm'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astral/ui",
3
- "version": "4.79.0",
3
+ "version": "4.80.1",
4
4
  "browser": "./index.js",
5
5
  "main": "./node/index.js",
6
6
  "dependencies": {