@snack-uikit/table 0.25.17 → 0.25.18-preview-cfe0e263.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 (34) hide show
  1. package/README.md +8 -0
  2. package/dist/cjs/components/Table/Table.d.ts +1 -1
  3. package/dist/cjs/components/Table/Table.js +59 -29
  4. package/dist/cjs/components/Table/hooks/useRowVirtualizer.d.ts +2 -0
  5. package/dist/cjs/components/Table/hooks/useRowVirtualizer.js +34 -0
  6. package/dist/cjs/components/Table/styles.module.css +4 -0
  7. package/dist/cjs/components/Table/utils.d.ts +10 -0
  8. package/dist/cjs/components/Table/utils.js +19 -0
  9. package/dist/cjs/components/types.d.ts +7 -2
  10. package/dist/cjs/helperComponents/Rows/VirtualRow.d.ts +7 -0
  11. package/dist/cjs/helperComponents/Rows/VirtualRow.js +43 -0
  12. package/dist/cjs/helperComponents/Rows/styles.module.css +8 -0
  13. package/dist/cjs/types.d.ts +2 -0
  14. package/dist/esm/components/Table/Table.d.ts +1 -1
  15. package/dist/esm/components/Table/Table.js +38 -18
  16. package/dist/esm/components/Table/hooks/useRowVirtualizer.d.ts +2 -0
  17. package/dist/esm/components/Table/hooks/useRowVirtualizer.js +13 -0
  18. package/dist/esm/components/Table/styles.module.css +4 -0
  19. package/dist/esm/components/Table/utils.d.ts +10 -0
  20. package/dist/esm/components/Table/utils.js +14 -0
  21. package/dist/esm/components/types.d.ts +7 -2
  22. package/dist/esm/helperComponents/Rows/VirtualRow.d.ts +7 -0
  23. package/dist/esm/helperComponents/Rows/VirtualRow.js +21 -0
  24. package/dist/esm/helperComponents/Rows/styles.module.css +8 -0
  25. package/dist/esm/types.d.ts +2 -0
  26. package/package.json +4 -2
  27. package/src/components/Table/Table.tsx +67 -23
  28. package/src/components/Table/hooks/useRowVirtualizer.ts +43 -0
  29. package/src/components/Table/styles.module.scss +6 -0
  30. package/src/components/Table/{utils.ts → utils.tsx} +18 -0
  31. package/src/components/types.ts +11 -2
  32. package/src/helperComponents/Rows/VirtualRow.tsx +24 -0
  33. package/src/helperComponents/Rows/styles.module.scss +8 -0
  34. package/src/types.ts +3 -0
package/README.md CHANGED
@@ -148,6 +148,10 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
148
148
  | scrollContainerRef | `RefObject<HTMLElement>` | - | Ссылка на контейнер, который скроллится |
149
149
  | rowPinning | `Pick<RowPinningState, "top">` | { top: [], } | Определение какие строки должны быть закреплены в таблице |
150
150
  | savedState | `{ id: string; resize?: boolean; }` | - | Конфиг для сохранения состояния в localStorage. <br> Поле id должно быть уникальным для разных таблиц в рамках приложения. <br> Для корректной работы необходимо наличие id в конфиге columnDefinitions |
151
+ | onScroll | `(event?: Event) => void` | - | |
152
+ | enableRowVirtualization | `boolean` | - | |
153
+ | rowVirtualizerOptions | `PartialKeys<VirtualizerOptions<Element, Element>, "observeElementRect" \| "observeElementOffset" \| "scrollToFn">` | - | |
154
+ | rowVirtualizerInstanceRef | `MutableRefObject<Virtualizer<Element, Element>>` | - | |
151
155
  ## Table.getStatusColumnDef
152
156
  Вспомогательная функция для создания ячейки со статусом
153
157
  ### Props
@@ -208,6 +212,10 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
208
212
  | scrollContainerRef | `RefObject<HTMLElement>` | - | Ссылка на контейнер, который скроллится |
209
213
  | rowPinning | `Pick<RowPinningState, "top">` | - | Определение какие строки должны быть закреплены в таблице |
210
214
  | savedState | `{ id: string; resize?: boolean; }` | - | Конфиг для сохранения состояния в localStorage. <br> Поле id должно быть уникальным для разных таблиц в рамках приложения. <br> Для корректной работы необходимо наличие id в конфиге columnDefinitions |
215
+ | onScroll | `(event?: Event) => void` | - | |
216
+ | enableRowVirtualization | `boolean` | - | |
217
+ | rowVirtualizerOptions | `PartialKeys<VirtualizerOptions<Element, Element>, "observeElementRect" \| "observeElementOffset" \| "scrollToFn">` | - | |
218
+ | rowVirtualizerInstanceRef | `MutableRefObject<Virtualizer<Element, Element>>` | - | |
211
219
  | items | `TData[]` | - | Данные для отрисовки |
212
220
  | total | `number` | 10 | Общее кол-во строк |
213
221
  | limit | `number` | 10 | Кол-во строк на страницу |
@@ -1,6 +1,6 @@
1
1
  import { TableProps } from '../types';
2
2
  /** Компонент таблицы */
3
- export declare function Table<TData extends object>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, toolbarBefore, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Table<TData extends object>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, toolbarBefore, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, onScroll, enableRowVirtualization, rowVirtualizerOptions, rowVirtualizerInstanceRef, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
4
4
  export declare namespace Table {
5
5
  var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
6
6
  var statusAppearances: Record<string, string>;
@@ -20,22 +20,25 @@ exports.Table = Table;
20
20
  const jsx_runtime_1 = require("react/jsx-runtime");
21
21
  const react_table_1 = require("@tanstack/react-table");
22
22
  const classnames_1 = __importDefault(require("classnames"));
23
+ const merge_refs_1 = __importDefault(require("merge-refs"));
23
24
  const react_1 = require("react");
24
25
  const locale_1 = require("@snack-uikit/locale");
25
26
  const scroll_1 = require("@snack-uikit/scroll");
26
27
  const skeleton_1 = require("@snack-uikit/skeleton");
27
28
  const toolbar_1 = require("@snack-uikit/toolbar");
28
- const truncate_string_1 = require("@snack-uikit/truncate-string");
29
29
  const utils_1 = require("@snack-uikit/utils");
30
30
  const constants_1 = require("../../constants");
31
31
  const helperComponents_1 = require("../../helperComponents");
32
+ const VirtualRow_1 = require("../../helperComponents/Rows/VirtualRow");
32
33
  const utils_2 = require("../../utils");
33
34
  const hooks_1 = require("./hooks");
34
35
  const usePageReset_1 = require("./hooks/usePageReset");
36
+ const useRowVirtualizer_1 = require("./hooks/useRowVirtualizer");
35
37
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
36
38
  const utils_3 = require("./utils");
37
39
  /** Компонент таблицы */
38
40
  function Table(_a) {
41
+ var _b, _c;
39
42
  var {
40
43
  data,
41
44
  rowPinning = {
@@ -77,9 +80,13 @@ function Table(_a) {
77
80
  scrollContainerRef,
78
81
  getRowId,
79
82
  enableFuzzySearch,
80
- savedState
83
+ savedState,
84
+ onScroll,
85
+ enableRowVirtualization,
86
+ rowVirtualizerOptions,
87
+ rowVirtualizerInstanceRef
81
88
  } = _a,
82
- rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "toolbarBefore", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState"]);
89
+ rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "toolbarBefore", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "onScroll", "enableRowVirtualization", "rowVirtualizerOptions", "rowVirtualizerInstanceRef"]);
83
90
  const {
84
91
  state: globalFilter,
85
92
  onStateChange: onGlobalFilterChange
@@ -101,6 +108,7 @@ function Table(_a) {
101
108
  onStateChange: onPaginationChange
102
109
  } = (0, hooks_1.useStateControl)(paginationProp, defaultPaginationState);
103
110
  const enableSelection = Boolean(rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable);
111
+ const tableContainerRef = (0, react_1.useRef)(null);
104
112
  const tableColumns = (0, react_1.useMemo)(() => {
105
113
  let cols = columnDefinitions;
106
114
  if (enableSelection) {
@@ -138,10 +146,7 @@ function Table(_a) {
138
146
  enableSorting: false,
139
147
  enableResizing: false,
140
148
  minSize: 40,
141
- cell: cell => (0, jsx_runtime_1.jsx)(truncate_string_1.TruncateString, {
142
- text: String(cell.getValue()),
143
- maxLines: 1
144
- })
149
+ cell: utils_3.truncateCell
145
150
  },
146
151
  manualSorting,
147
152
  manualPagination,
@@ -164,7 +169,13 @@ function Table(_a) {
164
169
  getPaginationRowModel: (0, react_table_1.getPaginationRowModel)(),
165
170
  getCoreRowModel: (0, react_table_1.getCoreRowModel)(),
166
171
  columnResizeMode: 'onEnd',
167
- keepPinnedRows
172
+ keepPinnedRows,
173
+ meta: {
174
+ tableContainerRef,
175
+ rowVirtualizerInstanceRef,
176
+ rowVirtualizerOptions,
177
+ enableRowVirtualization
178
+ }
168
179
  });
169
180
  const {
170
181
  loadingTable
@@ -204,10 +215,22 @@ function Table(_a) {
204
215
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
205
216
  const columnSizeVarsRef = (0, react_1.useRef)();
206
217
  const headers = table.getFlatHeaders();
207
- const columnSizeVars = (0, react_1.useMemo)(() => {
218
+ const tableRows = table.getRowModel().rows;
219
+ const tableCenterRows = table.getCenterRows();
220
+ const tableFilteredRows = table.getFilteredRowModel().rows;
221
+ const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
222
+ const topRows = table.getTopRows();
223
+ const loadingTableRows = loadingTable.getRowModel().rows;
224
+ const tablePagination = table.getState().pagination;
225
+ const [isScrollBeenInitialized, setScrollInitialization] = (0, react_1.useState)(false);
226
+ const rowVirtualizer = (0, useRowVirtualizer_1.useRowVirtualizer)(table, isScrollBeenInitialized);
227
+ const overallVirtualHeight = (_b = rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.getTotalSize()) !== null && _b !== void 0 ? _b : 0;
228
+ const cssSizeVars = (0, react_1.useMemo)(() => {
208
229
  var _a;
209
230
  const originalColumnDefs = table._getColumnDefs();
210
- const colSizes = {};
231
+ const colSizes = overallVirtualHeight > 0 ? {
232
+ height: `${overallVirtualHeight}px`
233
+ } : {};
211
234
  for (let i = 0; i < headers.length; i++) {
212
235
  const header = headers[i];
213
236
  const {
@@ -265,17 +288,10 @@ function Table(_a) {
265
288
  table.getTotalSize() will trigger re-render after double-click size reset
266
289
  */
267
290
  // eslint-disable-next-line react-hooks/exhaustive-deps
268
- }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
291
+ }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize(), overallVirtualHeight]);
269
292
  (0, react_1.useEffect)(() => {
270
- columnSizeVarsRef.current = columnSizeVars;
271
- }, [columnSizeVars]);
272
- const tableRows = table.getRowModel().rows;
273
- const tableCenterRows = table.getCenterRows();
274
- const tableFilteredRows = table.getFilteredRowModel().rows;
275
- const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
276
- const topRows = table.getTopRows();
277
- const loadingTableRows = loadingTable.getRowModel().rows;
278
- const tablePagination = table.getState().pagination;
293
+ columnSizeVarsRef.current = cssSizeVars;
294
+ }, [cssSizeVars]);
279
295
  const filteredTopRows = table.getState().globalFilter ? topRows.filter(tr => tableFilteredRowsIds.includes(tr.id)) : topRows;
280
296
  const centerRows = copyPinnedRows ? tableRows : tableCenterRows;
281
297
  const {
@@ -290,6 +306,12 @@ function Table(_a) {
290
306
  const tempPageSize = (!suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize) + filteredTopRows.length;
291
307
  return !tableRows.length ? Math.min(Math.max(tempPageSize, 5), constants_1.DEFAULT_PAGE_SIZE) : tempPageSize;
292
308
  }, [filteredTopRows.length, pageSize, suppressPagination, tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize, tableRows.length]);
309
+ const tableCtx = (0, react_1.useMemo)(() => ({
310
+ table
311
+ }), [table]);
312
+ const onInitializeScroll = (0, react_1.useCallback)(() => {
313
+ setScrollInitialization(true);
314
+ }, []);
293
315
  (0, usePageReset_1.usePageReset)({
294
316
  manualPagination,
295
317
  maximumAvailablePage: pageCount || data.length / pagination.pageSize,
@@ -341,15 +363,15 @@ function Table(_a) {
341
363
  "data-outline": outline || undefined,
342
364
  children: (0, jsx_runtime_1.jsxs)(scroll_1.Scroll, {
343
365
  size: 's',
366
+ ref: (0, merge_refs_1.default)(tableContainerRef, scrollContainerRef),
344
367
  className: styles_module_scss_1.default.table,
345
- ref: scrollContainerRef,
368
+ onScroll: onScroll,
369
+ onInitialized: onInitializeScroll,
346
370
  children: [(0, jsx_runtime_1.jsx)("div", {
347
371
  className: styles_module_scss_1.default.tableContent,
348
- style: columnSizeVars,
372
+ style: cssSizeVars,
349
373
  children: (0, jsx_runtime_1.jsx)(helperComponents_1.TableContext.Provider, {
350
- value: {
351
- table
352
- },
374
+ value: tableCtx,
353
375
  children: loading ? (0, jsx_runtime_1.jsxs)(skeleton_1.SkeletonContextProvider, {
354
376
  loading: true,
355
377
  children: [(0, jsx_runtime_1.jsx)(helperComponents_1.HeaderRow, {}), loadingTableRows.map(row => (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
@@ -362,10 +384,18 @@ function Table(_a) {
362
384
  row: row,
363
385
  onRowClick: onRowClick
364
386
  }, row.id))
365
- }) : null, centerRows.map(row => (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
366
- row: row,
367
- onRowClick: onRowClick
368
- }, row.id)), (0, jsx_runtime_1.jsx)(helperComponents_1.TableEmptyState, {
387
+ }) : null, ((_c = rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.getVirtualItems()) !== null && _c !== void 0 ? _c : centerRows).map((rowOrVirtualRow, index) => {
388
+ const row = (0, utils_3.isVirtualRow)(rowOrVirtualRow) ? centerRows[rowOrVirtualRow.index] : centerRows[index];
389
+ return (0, jsx_runtime_1.jsx)(VirtualRow_1.VirtualRow, {
390
+ ref: rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.measureElement,
391
+ "data-index": row.index,
392
+ virtualRow: (0, utils_3.isVirtualRow)(rowOrVirtualRow) ? rowOrVirtualRow : undefined,
393
+ children: (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
394
+ row: row,
395
+ onRowClick: onRowClick
396
+ })
397
+ }, row.id);
398
+ }), (0, jsx_runtime_1.jsx)(helperComponents_1.TableEmptyState, {
369
399
  emptyStates: emptyStates,
370
400
  dataError: dataError,
371
401
  dataFiltered: dataFiltered || Boolean(table.getState().globalFilter),
@@ -0,0 +1,2 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ export declare function useRowVirtualizer<T extends object>(table: Table<T>, isScrollInitialized: boolean): import("@tanstack/virtual-core").Virtualizer<Element, Element> | undefined;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useRowVirtualizer = useRowVirtualizer;
7
+ const react_virtual_1 = require("@tanstack/react-virtual");
8
+ function useRowVirtualizer(table, isScrollInitialized) {
9
+ const {
10
+ getRowModel,
11
+ options: {
12
+ meta = {}
13
+ }
14
+ } = table;
15
+ const {
16
+ rowVirtualizerInstanceRef,
17
+ rowVirtualizerOptions,
18
+ tableContainerRef,
19
+ enableRowVirtualization
20
+ } = meta;
21
+ const normalRowHeight = 40;
22
+ const rowVirtualizer = (0, react_virtual_1.useVirtualizer)(Object.assign({
23
+ count: getRowModel().rows.length,
24
+ estimateSize: () => normalRowHeight,
25
+ getScrollElement: () => isScrollInitialized ? tableContainerRef.current : null,
26
+ measureElement: typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1 ? element => element === null || element === void 0 ? void 0 : element.getBoundingClientRect().height : undefined,
27
+ overscan: 28,
28
+ enabled: enableRowVirtualization
29
+ }, rowVirtualizerOptions));
30
+ if (rowVirtualizerInstanceRef && enableRowVirtualization) {
31
+ rowVirtualizerInstanceRef.current = rowVirtualizer;
32
+ }
33
+ return enableRowVirtualization ? rowVirtualizer : undefined;
34
+ }
@@ -1,8 +1,11 @@
1
1
  .table{
2
2
  border-width:var(--border-width-table, 1px);
3
3
  border-radius:var(--radius-table-container, 14px);
4
+ will-change:transform;
5
+ overflow-anchor:none;
4
6
  position:relative;
5
7
  z-index:0;
8
+ contain:paint;
6
9
  overflow:hidden !important;
7
10
  display:flex;
8
11
  box-sizing:border-box;
@@ -26,6 +29,7 @@
26
29
  }
27
30
 
28
31
  .tableContent{
32
+ content-visibility:auto;
29
33
  min-width:-moz-max-content;
30
34
  min-width:max-content;
31
35
  }
@@ -1,3 +1,5 @@
1
+ import { CellContext } from '@tanstack/react-table';
2
+ import { VirtualItem } from '@tanstack/react-virtual';
1
3
  export declare function getCurrentlyConfiguredHeaderWidth(id: string): number;
2
4
  export declare function getColumnStyleVars(id: string): {
3
5
  sizeKey: string;
@@ -14,4 +16,12 @@ type SaveStateToLocalStorageProps = {
14
16
  size: string;
15
17
  };
16
18
  export declare function saveStateToLocalStorage({ id, columnId, size }: SaveStateToLocalStorageProps): void;
19
+ /**
20
+ * check the key, because index is contained in a common table row
21
+ */
22
+ export declare function isVirtualRow(row: unknown): row is VirtualItem;
23
+ /**
24
+ * @description prevent permanent recreation of the function on rerender
25
+ */
26
+ export declare function truncateCell<TData>(cell: CellContext<TData, unknown>): import("react/jsx-runtime").JSX.Element;
17
27
  export {};
@@ -7,6 +7,10 @@ exports.getCurrentlyConfiguredHeaderWidth = getCurrentlyConfiguredHeaderWidth;
7
7
  exports.getColumnStyleVars = getColumnStyleVars;
8
8
  exports.getInitColumnSizeFromLocalStorage = getInitColumnSizeFromLocalStorage;
9
9
  exports.saveStateToLocalStorage = saveStateToLocalStorage;
10
+ exports.isVirtualRow = isVirtualRow;
11
+ exports.truncateCell = truncateCell;
12
+ const jsx_runtime_1 = require("react/jsx-runtime");
13
+ const truncate_string_1 = require("@snack-uikit/truncate-string");
10
14
  const utils_1 = require("@snack-uikit/utils");
11
15
  function getCurrentlyConfiguredHeaderWidth(id) {
12
16
  if ((0, utils_1.isBrowser)()) {
@@ -53,4 +57,19 @@ function saveStateToLocalStorage(_ref2) {
53
57
  localStorage.setItem(id, JSON.stringify(Object.assign(Object.assign({}, savedStateFromStorage || {}), {
54
58
  resizeState: newResizeState
55
59
  })));
60
+ }
61
+ /**
62
+ * check the key, because index is contained in a common table row
63
+ */
64
+ function isVirtualRow(row) {
65
+ return typeof row === 'object' && row != null && 'key' in row;
66
+ }
67
+ /**
68
+ * @description prevent permanent recreation of the function on rerender
69
+ */
70
+ function truncateCell(cell) {
71
+ return (0, jsx_runtime_1.jsx)(truncate_string_1.TruncateString, {
72
+ text: String(cell.getValue()),
73
+ maxLines: 1
74
+ });
56
75
  }
@@ -1,9 +1,10 @@
1
1
  import { PaginationState, Row, RowPinningState, RowSelectionOptions, RowSelectionState, SortingState } from '@tanstack/react-table';
2
- import { ReactNode, RefObject } from 'react';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ import { MutableRefObject, ReactNode, RefObject } from 'react';
3
4
  import { ToolbarProps } from '@snack-uikit/toolbar';
4
5
  import { WithSupportProps } from '@snack-uikit/utils';
5
6
  import { EmptyStateProps, ExportButtonProps, RowClickHandler } from '../helperComponents';
6
- import { ColumnDefinition } from '../types';
7
+ import { ColumnDefinition, RowVirtualizer } from '../types';
7
8
  export type TableProps<TData extends object> = WithSupportProps<{
8
9
  /** Данные для отрисовки */
9
10
  data: TData[];
@@ -129,6 +130,10 @@ export type TableProps<TData extends object> = WithSupportProps<{
129
130
  id: string;
130
131
  resize?: boolean;
131
132
  };
133
+ onScroll?: (event?: Event) => void;
134
+ enableRowVirtualization?: boolean;
135
+ rowVirtualizerOptions?: Parameters<typeof useVirtualizer>[0];
136
+ rowVirtualizerInstanceRef?: MutableRefObject<RowVirtualizer>;
132
137
  }>;
133
138
  export type ServerTableProps<TData extends object> = Omit<TableProps<TData>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
134
139
  /** Данные для отрисовки */
@@ -0,0 +1,7 @@
1
+ import { VirtualItem } from '@tanstack/react-virtual';
2
+ import { HTMLAttributes } from 'react';
3
+ export declare const VirtualRow: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
4
+ virtualRow?: VirtualItem;
5
+ } & {
6
+ children?: import("react").ReactNode | undefined;
7
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ var __rest = void 0 && (void 0).__rest || function (s, e) {
4
+ var t = {};
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
8
+ }
9
+ return t;
10
+ };
11
+ var __importDefault = void 0 && (void 0).__importDefault || function (mod) {
12
+ return mod && mod.__esModule ? mod : {
13
+ "default": mod
14
+ };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", {
17
+ value: true
18
+ });
19
+ exports.VirtualRow = void 0;
20
+ const jsx_runtime_1 = require("react/jsx-runtime");
21
+ const react_1 = require("react");
22
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
23
+ exports.VirtualRow = (0, react_1.forwardRef)((_a, ref) => {
24
+ var {
25
+ virtualRow,
26
+ children
27
+ } = _a,
28
+ attributes = __rest(_a, ["virtualRow", "children"]);
29
+ if (!virtualRow) {
30
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {
31
+ children: children
32
+ });
33
+ }
34
+ return (0, jsx_runtime_1.jsx)("div", Object.assign({
35
+ ref: ref,
36
+ className: styles_module_scss_1.default.virtualRow,
37
+ style: {
38
+ '--virtual-row-deviation': `${virtualRow.start}px`
39
+ }
40
+ }, attributes, {
41
+ children: children
42
+ }));
43
+ });
@@ -85,4 +85,12 @@
85
85
  z-index:3;
86
86
  top:0;
87
87
  border:none;
88
+ }
89
+
90
+ .virtualRow{
91
+ position:absolute;
92
+ top:0;
93
+ left:0;
94
+ width:100%;
95
+ transform:translateY(var(--virtual-row-deviation));
88
96
  }
@@ -1,4 +1,5 @@
1
1
  import { CellContext, ColumnDef, ColumnMeta, HeaderContext, PaginationState, RowSelectionOptions, RowSelectionState, SortingState } from '@tanstack/react-table';
2
+ import { Virtualizer } from '@tanstack/react-virtual';
2
3
  import { ToolbarProps } from '@snack-uikit/toolbar';
3
4
  import { ValueOf } from '@snack-uikit/utils';
4
5
  import { COLUMN_ALIGN, COLUMN_PIN_POSITION } from './constants';
@@ -39,5 +40,6 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
39
40
  size: number;
40
41
  };
41
42
  export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
43
+ export type RowVirtualizer = Virtualizer<Element, Element> | null;
42
44
  export type { RowActionsColumnDefProps, StatusColumnDefinitionProps, RowInfo, RowClickHandler, ActionsGenerator, CopyCellProps, MapStatusToAppearanceFnType, } from './helperComponents';
43
45
  export type { ColumnPinPosition, PaginationState, SortingState, RowSelectionState, RowSelectionOptions, EmptyStateProps, ToolbarProps, HeaderContext, CellContext, };
@@ -1,6 +1,6 @@
1
1
  import { TableProps } from '../types';
2
2
  /** Компонент таблицы */
3
- export declare function Table<TData extends object>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, toolbarBefore, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Table<TData extends object>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, toolbarBefore, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, onScroll, enableRowVirtualization, rowVirtualizerOptions, rowVirtualizerInstanceRef, ...rest }: TableProps<TData>): import("react/jsx-runtime").JSX.Element;
4
4
  export declare namespace Table {
5
5
  var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
6
6
  var statusAppearances: Record<string, string>;
@@ -12,25 +12,28 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
14
14
  import cn from 'classnames';
15
- import { useCallback, useEffect, useMemo, useRef } from 'react';
15
+ import mergeRefs from 'merge-refs';
16
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
16
17
  import { useLocale } from '@snack-uikit/locale';
17
18
  import { Scroll } from '@snack-uikit/scroll';
18
19
  import { SkeletonContextProvider } from '@snack-uikit/skeleton';
19
20
  import { Toolbar } from '@snack-uikit/toolbar';
20
- import { TruncateString } from '@snack-uikit/truncate-string';
21
21
  import { extractSupportProps } from '@snack-uikit/utils';
22
22
  import { DEFAULT_PAGE_SIZE } from '../../constants';
23
23
  import { BodyRow, ExportButton, getColumnId, getRowActionsColumnDef, getSelectionCellColumnDef, getStatusColumnDef, HeaderRow, STATUS_APPEARANCE, TableContext, TableEmptyState, TablePagination, useEmptyState, } from '../../helperComponents';
24
+ import { VirtualRow } from '../../helperComponents/Rows/VirtualRow';
24
25
  import { fuzzyFilter } from '../../utils';
25
26
  import { useLoadingTable, useStateControl } from './hooks';
26
27
  import { usePageReset } from './hooks/usePageReset';
28
+ import { useRowVirtualizer } from './hooks/useRowVirtualizer';
27
29
  import styles from './styles.module.css';
28
- import { getColumnStyleVars, getCurrentlyConfiguredHeaderWidth, getInitColumnSizeFromLocalStorage, saveStateToLocalStorage, } from './utils';
30
+ import { getColumnStyleVars, getCurrentlyConfiguredHeaderWidth, getInitColumnSizeFromLocalStorage, isVirtualRow, saveStateToLocalStorage, truncateCell, } from './utils';
29
31
  /** Компонент таблицы */
30
32
  export function Table(_a) {
33
+ var _b, _c;
31
34
  var { data, rowPinning = {
32
35
  top: [],
33
- }, columnDefinitions, keepPinnedRows = false, copyPinnedRows = false, enableSelectPinned = false, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize = DEFAULT_PAGE_SIZE, pageCount, loading = false, outline = false, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar = false, toolbarBefore, toolbarAfter, suppressPagination = false, manualSorting = false, manualPagination = false, manualFiltering = false, autoResetPageIndex = false, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState } = _a, rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "toolbarBefore", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState"]);
36
+ }, columnDefinitions, keepPinnedRows = false, copyPinnedRows = false, enableSelectPinned = false, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, onDelete, pageSize = DEFAULT_PAGE_SIZE, pageCount, loading = false, outline = false, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar = false, toolbarBefore, toolbarAfter, suppressPagination = false, manualSorting = false, manualPagination = false, manualFiltering = false, autoResetPageIndex = false, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, onScroll, enableRowVirtualization, rowVirtualizerOptions, rowVirtualizerInstanceRef } = _a, rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "onDelete", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "toolbarBefore", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "onScroll", "enableRowVirtualization", "rowVirtualizerOptions", "rowVirtualizerInstanceRef"]);
34
37
  const { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl(search, '');
35
38
  const { state: rowSelection, onStateChange: onRowSelectionChange } = useStateControl(rowSelectionProp, {});
36
39
  const defaultPaginationState = useMemo(() => ({
@@ -40,6 +43,7 @@ export function Table(_a) {
40
43
  const { state: sorting, onStateChange: onSortingChange } = useStateControl(sortingProp, []);
41
44
  const { state: pagination, onStateChange: onPaginationChange } = useStateControl(paginationProp, defaultPaginationState);
42
45
  const enableSelection = Boolean(rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable);
46
+ const tableContainerRef = useRef(null);
43
47
  const tableColumns = useMemo(() => {
44
48
  let cols = columnDefinitions;
45
49
  if (enableSelection) {
@@ -74,7 +78,7 @@ export function Table(_a) {
74
78
  enableSorting: false,
75
79
  enableResizing: false,
76
80
  minSize: 40,
77
- cell: (cell) => _jsx(TruncateString, { text: String(cell.getValue()), maxLines: 1 }),
81
+ cell: truncateCell,
78
82
  },
79
83
  manualSorting,
80
84
  manualPagination,
@@ -98,6 +102,12 @@ export function Table(_a) {
98
102
  getCoreRowModel: getCoreRowModel(),
99
103
  columnResizeMode: 'onEnd',
100
104
  keepPinnedRows,
105
+ meta: {
106
+ tableContainerRef,
107
+ rowVirtualizerInstanceRef,
108
+ rowVirtualizerOptions,
109
+ enableRowVirtualization,
110
+ },
101
111
  });
102
112
  const { loadingTable } = useLoadingTable({
103
113
  pageSize: Math.min(Math.max(pageSize, 5), DEFAULT_PAGE_SIZE),
@@ -135,10 +145,20 @@ export function Table(_a) {
135
145
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
136
146
  const columnSizeVarsRef = useRef();
137
147
  const headers = table.getFlatHeaders();
138
- const columnSizeVars = useMemo(() => {
148
+ const tableRows = table.getRowModel().rows;
149
+ const tableCenterRows = table.getCenterRows();
150
+ const tableFilteredRows = table.getFilteredRowModel().rows;
151
+ const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
152
+ const topRows = table.getTopRows();
153
+ const loadingTableRows = loadingTable.getRowModel().rows;
154
+ const tablePagination = table.getState().pagination;
155
+ const [isScrollBeenInitialized, setScrollInitialization] = useState(false);
156
+ const rowVirtualizer = useRowVirtualizer(table, isScrollBeenInitialized);
157
+ const overallVirtualHeight = (_b = rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.getTotalSize()) !== null && _b !== void 0 ? _b : 0;
158
+ const cssSizeVars = useMemo(() => {
139
159
  var _a;
140
160
  const originalColumnDefs = table._getColumnDefs();
141
- const colSizes = {};
161
+ const colSizes = overallVirtualHeight > 0 ? { height: `${overallVirtualHeight}px` } : {};
142
162
  for (let i = 0; i < headers.length; i++) {
143
163
  const header = headers[i];
144
164
  const { sizeKey, flexKey } = getColumnStyleVars(header.id);
@@ -187,17 +207,10 @@ export function Table(_a) {
187
207
  table.getTotalSize() will trigger re-render after double-click size reset
188
208
  */
189
209
  // eslint-disable-next-line react-hooks/exhaustive-deps
190
- }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
210
+ }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize(), overallVirtualHeight]);
191
211
  useEffect(() => {
192
- columnSizeVarsRef.current = columnSizeVars;
193
- }, [columnSizeVars]);
194
- const tableRows = table.getRowModel().rows;
195
- const tableCenterRows = table.getCenterRows();
196
- const tableFilteredRows = table.getFilteredRowModel().rows;
197
- const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
198
- const topRows = table.getTopRows();
199
- const loadingTableRows = loadingTable.getRowModel().rows;
200
- const tablePagination = table.getState().pagination;
212
+ columnSizeVarsRef.current = cssSizeVars;
213
+ }, [cssSizeVars]);
201
214
  const filteredTopRows = table.getState().globalFilter
202
215
  ? topRows.filter(tr => tableFilteredRowsIds.includes(tr.id))
203
216
  : topRows;
@@ -208,6 +221,10 @@ export function Table(_a) {
208
221
  const tempPageSize = (!suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize) + filteredTopRows.length;
209
222
  return !tableRows.length ? Math.min(Math.max(tempPageSize, 5), DEFAULT_PAGE_SIZE) : tempPageSize;
210
223
  }, [filteredTopRows.length, pageSize, suppressPagination, tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize, tableRows.length]);
224
+ const tableCtx = useMemo(() => ({ table }), [table]);
225
+ const onInitializeScroll = useCallback(() => {
226
+ setScrollInitialization(true);
227
+ }, []);
211
228
  usePageReset({
212
229
  manualPagination,
213
230
  maximumAvailablePage: pageCount || data.length / pagination.pageSize,
@@ -222,7 +239,10 @@ export function Table(_a) {
222
239
  onChange: onGlobalFilterChange,
223
240
  loading: search === null || search === void 0 ? void 0 : search.loading,
224
241
  placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || t('searchPlaceholder'),
225
- }, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', before: toolbarBefore, after: toolbarAfter || exportSettings ? (_jsxs(_Fragment, { children: [toolbarAfter, exportSettings && (_jsx(ExportButton, { settings: exportSettings, columnDefinitions: columnDefinitions, data: data, topRows: filteredTopRows, centerRows: centerRows }))] })) : undefined, moreActions: moreActions }), columnFilters && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFilters, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsxs(Scroll, { size: 's', className: styles.table, ref: scrollContainerRef, children: [_jsx("div", { className: styles.tableContent, style: columnSizeVars, children: _jsx(TableContext.Provider, { value: { table }, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [centerRows.length || filteredTopRows.length ? _jsx(HeaderRow, {}) : null, filteredTopRows.length ? (_jsx("div", { className: styles.topRowWrapper, children: filteredTopRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))) })) : null, centerRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))), _jsx(TableEmptyState, { emptyStates: emptyStates, dataError: dataError, dataFiltered: dataFiltered || Boolean(table.getState().globalFilter), tableRowsLength: tableRows.length + filteredTopRows.length })] })) }) }), _jsx("div", { className: styles.scrollStub, ref: scrollRef })] }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount, optionsRender: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsRender }))] })) }));
242
+ }, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', before: toolbarBefore, after: toolbarAfter || exportSettings ? (_jsxs(_Fragment, { children: [toolbarAfter, exportSettings && (_jsx(ExportButton, { settings: exportSettings, columnDefinitions: columnDefinitions, data: data, topRows: filteredTopRows, centerRows: centerRows }))] })) : undefined, moreActions: moreActions }), columnFilters && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFilters, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsxs(Scroll, { size: 's', ref: mergeRefs(tableContainerRef, scrollContainerRef), className: styles.table, onScroll: onScroll, onInitialized: onInitializeScroll, children: [_jsx("div", { className: styles.tableContent, style: cssSizeVars, children: _jsx(TableContext.Provider, { value: tableCtx, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [centerRows.length || filteredTopRows.length ? _jsx(HeaderRow, {}) : null, filteredTopRows.length ? (_jsx("div", { className: styles.topRowWrapper, children: filteredTopRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))) })) : null, ((_c = rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.getVirtualItems()) !== null && _c !== void 0 ? _c : centerRows).map((rowOrVirtualRow, index) => {
243
+ const row = isVirtualRow(rowOrVirtualRow) ? centerRows[rowOrVirtualRow.index] : centerRows[index];
244
+ return (_jsx(VirtualRow, { ref: rowVirtualizer === null || rowVirtualizer === void 0 ? void 0 : rowVirtualizer.measureElement, "data-index": row.index, virtualRow: isVirtualRow(rowOrVirtualRow) ? rowOrVirtualRow : undefined, children: _jsx(BodyRow, { row: row, onRowClick: onRowClick }) }, row.id));
245
+ }), _jsx(TableEmptyState, { emptyStates: emptyStates, dataError: dataError, dataFiltered: dataFiltered || Boolean(table.getState().globalFilter), tableRowsLength: tableRows.length + filteredTopRows.length })] })) }) }), _jsx("div", { className: styles.scrollStub, ref: scrollRef })] }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount, optionsRender: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsRender }))] })) }));
226
246
  }
227
247
  Table.getStatusColumnDef = getStatusColumnDef;
228
248
  Table.statusAppearances = STATUS_APPEARANCE;
@@ -0,0 +1,2 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ export declare function useRowVirtualizer<T extends object>(table: Table<T>, isScrollInitialized: boolean): import("@tanstack/virtual-core").Virtualizer<Element, Element> | undefined;
@@ -0,0 +1,13 @@
1
+ import { useVirtualizer } from '@tanstack/react-virtual';
2
+ export function useRowVirtualizer(table, isScrollInitialized) {
3
+ const { getRowModel, options: { meta = {} }, } = table;
4
+ const { rowVirtualizerInstanceRef, rowVirtualizerOptions, tableContainerRef, enableRowVirtualization } = meta;
5
+ const normalRowHeight = 40;
6
+ const rowVirtualizer = useVirtualizer(Object.assign({ count: getRowModel().rows.length, estimateSize: () => normalRowHeight, getScrollElement: () => (isScrollInitialized ? tableContainerRef.current : null), measureElement: typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1
7
+ ? element => element === null || element === void 0 ? void 0 : element.getBoundingClientRect().height
8
+ : undefined, overscan: 28, enabled: enableRowVirtualization }, rowVirtualizerOptions));
9
+ if (rowVirtualizerInstanceRef && enableRowVirtualization) {
10
+ rowVirtualizerInstanceRef.current = rowVirtualizer;
11
+ }
12
+ return enableRowVirtualization ? rowVirtualizer : undefined;
13
+ }
@@ -1,8 +1,11 @@
1
1
  .table{
2
2
  border-width:var(--border-width-table, 1px);
3
3
  border-radius:var(--radius-table-container, 14px);
4
+ will-change:transform;
5
+ overflow-anchor:none;
4
6
  position:relative;
5
7
  z-index:0;
8
+ contain:paint;
6
9
  overflow:hidden !important;
7
10
  display:flex;
8
11
  box-sizing:border-box;
@@ -26,6 +29,7 @@
26
29
  }
27
30
 
28
31
  .tableContent{
32
+ content-visibility:auto;
29
33
  min-width:-moz-max-content;
30
34
  min-width:max-content;
31
35
  }
@@ -1,3 +1,5 @@
1
+ import { CellContext } from '@tanstack/react-table';
2
+ import { VirtualItem } from '@tanstack/react-virtual';
1
3
  export declare function getCurrentlyConfiguredHeaderWidth(id: string): number;
2
4
  export declare function getColumnStyleVars(id: string): {
3
5
  sizeKey: string;
@@ -14,4 +16,12 @@ type SaveStateToLocalStorageProps = {
14
16
  size: string;
15
17
  };
16
18
  export declare function saveStateToLocalStorage({ id, columnId, size }: SaveStateToLocalStorageProps): void;
19
+ /**
20
+ * check the key, because index is contained in a common table row
21
+ */
22
+ export declare function isVirtualRow(row: unknown): row is VirtualItem;
23
+ /**
24
+ * @description prevent permanent recreation of the function on rerender
25
+ */
26
+ export declare function truncateCell<TData>(cell: CellContext<TData, unknown>): import("react/jsx-runtime").JSX.Element;
17
27
  export {};
@@ -1,3 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { TruncateString } from '@snack-uikit/truncate-string';
1
3
  import { isBrowser } from '@snack-uikit/utils';
2
4
  export function getCurrentlyConfiguredHeaderWidth(id) {
3
5
  if (isBrowser()) {
@@ -32,3 +34,15 @@ export function saveStateToLocalStorage({ id, columnId, size }) {
32
34
  newResizeState[`${RESIZED_KEY}-${columnId}`] = size;
33
35
  localStorage.setItem(id, JSON.stringify(Object.assign(Object.assign({}, (savedStateFromStorage || {})), { resizeState: newResizeState })));
34
36
  }
37
+ /**
38
+ * check the key, because index is contained in a common table row
39
+ */
40
+ export function isVirtualRow(row) {
41
+ return typeof row === 'object' && row != null && 'key' in row;
42
+ }
43
+ /**
44
+ * @description prevent permanent recreation of the function on rerender
45
+ */
46
+ export function truncateCell(cell) {
47
+ return _jsx(TruncateString, { text: String(cell.getValue()), maxLines: 1 });
48
+ }
@@ -1,9 +1,10 @@
1
1
  import { PaginationState, Row, RowPinningState, RowSelectionOptions, RowSelectionState, SortingState } from '@tanstack/react-table';
2
- import { ReactNode, RefObject } from 'react';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ import { MutableRefObject, ReactNode, RefObject } from 'react';
3
4
  import { ToolbarProps } from '@snack-uikit/toolbar';
4
5
  import { WithSupportProps } from '@snack-uikit/utils';
5
6
  import { EmptyStateProps, ExportButtonProps, RowClickHandler } from '../helperComponents';
6
- import { ColumnDefinition } from '../types';
7
+ import { ColumnDefinition, RowVirtualizer } from '../types';
7
8
  export type TableProps<TData extends object> = WithSupportProps<{
8
9
  /** Данные для отрисовки */
9
10
  data: TData[];
@@ -129,6 +130,10 @@ export type TableProps<TData extends object> = WithSupportProps<{
129
130
  id: string;
130
131
  resize?: boolean;
131
132
  };
133
+ onScroll?: (event?: Event) => void;
134
+ enableRowVirtualization?: boolean;
135
+ rowVirtualizerOptions?: Parameters<typeof useVirtualizer>[0];
136
+ rowVirtualizerInstanceRef?: MutableRefObject<RowVirtualizer>;
132
137
  }>;
133
138
  export type ServerTableProps<TData extends object> = Omit<TableProps<TData>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
134
139
  /** Данные для отрисовки */
@@ -0,0 +1,7 @@
1
+ import { VirtualItem } from '@tanstack/react-virtual';
2
+ import { HTMLAttributes } from 'react';
3
+ export declare const VirtualRow: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
4
+ virtualRow?: VirtualItem;
5
+ } & {
6
+ children?: import("react").ReactNode | undefined;
7
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,21 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
13
+ import { forwardRef } from 'react';
14
+ import styles from './styles.module.css';
15
+ export const VirtualRow = forwardRef((_a, ref) => {
16
+ var { virtualRow, children } = _a, attributes = __rest(_a, ["virtualRow", "children"]);
17
+ if (!virtualRow) {
18
+ return _jsx(_Fragment, { children: children });
19
+ }
20
+ return (_jsx("div", Object.assign({ ref: ref, className: styles.virtualRow, style: { '--virtual-row-deviation': `${virtualRow.start}px` } }, attributes, { children: children })));
21
+ });
@@ -85,4 +85,12 @@
85
85
  z-index:3;
86
86
  top:0;
87
87
  border:none;
88
+ }
89
+
90
+ .virtualRow{
91
+ position:absolute;
92
+ top:0;
93
+ left:0;
94
+ width:100%;
95
+ transform:translateY(var(--virtual-row-deviation));
88
96
  }
@@ -1,4 +1,5 @@
1
1
  import { CellContext, ColumnDef, ColumnMeta, HeaderContext, PaginationState, RowSelectionOptions, RowSelectionState, SortingState } from '@tanstack/react-table';
2
+ import { Virtualizer } from '@tanstack/react-virtual';
2
3
  import { ToolbarProps } from '@snack-uikit/toolbar';
3
4
  import { ValueOf } from '@snack-uikit/utils';
4
5
  import { COLUMN_ALIGN, COLUMN_PIN_POSITION } from './constants';
@@ -39,5 +40,6 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
39
40
  size: number;
40
41
  };
41
42
  export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
43
+ export type RowVirtualizer = Virtualizer<Element, Element> | null;
42
44
  export type { RowActionsColumnDefProps, StatusColumnDefinitionProps, RowInfo, RowClickHandler, ActionsGenerator, CopyCellProps, MapStatusToAppearanceFnType, } from './helperComponents';
43
45
  export type { ColumnPinPosition, PaginationState, SortingState, RowSelectionState, RowSelectionOptions, EmptyStateProps, ToolbarProps, HeaderContext, CellContext, };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Table",
7
- "version": "0.25.17",
7
+ "version": "0.25.18-preview-cfe0e263.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -52,14 +52,16 @@
52
52
  "@snack-uikit/utils": "3.6.0",
53
53
  "@tanstack/match-sorter-utils": "8.11.8",
54
54
  "@tanstack/react-table": "8.12.0",
55
+ "@tanstack/react-virtual": "3.10.9",
55
56
  "classnames": "2.5.1",
56
57
  "copy-to-clipboard": "3.3.3",
57
58
  "lodash.debounce": "4.0.8",
59
+ "merge-refs": "1.3.0",
58
60
  "uncontrollable": "8.0.4",
59
61
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
60
62
  },
61
63
  "peerDependencies": {
62
64
  "@snack-uikit/locale": "*"
63
65
  },
64
- "gitHead": "0e3ad2b84a59c2a008bfa95a48a29dbecd0b5a1c"
66
+ "gitHead": "d40c24c19cce11b5126931bc317bf88deb0c216a"
65
67
  }
@@ -1,5 +1,4 @@
1
1
  import {
2
- CellContext,
3
2
  ColumnPinningState,
4
3
  getCoreRowModel,
5
4
  getFilteredRowModel,
@@ -11,13 +10,13 @@ import {
11
10
  useReactTable,
12
11
  } from '@tanstack/react-table';
13
12
  import cn from 'classnames';
14
- import { RefObject, useCallback, useEffect, useMemo, useRef } from 'react';
13
+ import mergeRefs from 'merge-refs';
14
+ import { RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
 
16
16
  import { useLocale } from '@snack-uikit/locale';
17
17
  import { Scroll } from '@snack-uikit/scroll';
18
18
  import { SkeletonContextProvider } from '@snack-uikit/skeleton';
19
19
  import { Toolbar } from '@snack-uikit/toolbar';
20
- import { TruncateString } from '@snack-uikit/truncate-string';
21
20
  import { extractSupportProps } from '@snack-uikit/utils';
22
21
 
23
22
  import { DEFAULT_PAGE_SIZE } from '../../constants';
@@ -35,17 +34,21 @@ import {
35
34
  TablePagination,
36
35
  useEmptyState,
37
36
  } from '../../helperComponents';
37
+ import { VirtualRow } from '../../helperComponents/Rows/VirtualRow';
38
38
  import { ColumnDefinition } from '../../types';
39
39
  import { fuzzyFilter } from '../../utils';
40
40
  import { TableProps } from '../types';
41
41
  import { useLoadingTable, useStateControl } from './hooks';
42
42
  import { usePageReset } from './hooks/usePageReset';
43
+ import { useRowVirtualizer } from './hooks/useRowVirtualizer';
43
44
  import styles from './styles.module.scss';
44
45
  import {
45
46
  getColumnStyleVars,
46
47
  getCurrentlyConfiguredHeaderWidth,
47
48
  getInitColumnSizeFromLocalStorage,
49
+ isVirtualRow,
48
50
  saveStateToLocalStorage,
51
+ truncateCell,
49
52
  } from './utils';
50
53
 
51
54
  /** Компонент таблицы */
@@ -92,6 +95,11 @@ export function Table<TData extends object>({
92
95
  enableFuzzySearch,
93
96
  savedState,
94
97
 
98
+ onScroll,
99
+ enableRowVirtualization,
100
+ rowVirtualizerOptions,
101
+ rowVirtualizerInstanceRef,
102
+
95
103
  ...rest
96
104
  }: TableProps<TData>) {
97
105
  const { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl<string>(search, '');
@@ -114,6 +122,7 @@ export function Table<TData extends object>({
114
122
  defaultPaginationState,
115
123
  );
116
124
  const enableSelection = Boolean(rowSelectionProp?.enable);
125
+ const tableContainerRef = useRef<HTMLElement>(null);
117
126
 
118
127
  const tableColumns: ColumnDefinition<TData>[] = useMemo(() => {
119
128
  let cols: ColumnDefinition<TData>[] = columnDefinitions;
@@ -137,6 +146,7 @@ export function Table<TData extends object>({
137
146
  const table = useReactTable<TData>({
138
147
  data,
139
148
  columns: tableColumns,
149
+
140
150
  state: {
141
151
  columnPinning,
142
152
  globalFilter,
@@ -150,7 +160,7 @@ export function Table<TData extends object>({
150
160
  enableSorting: false,
151
161
  enableResizing: false,
152
162
  minSize: 40,
153
- cell: (cell: CellContext<TData, unknown>) => <TruncateString text={String(cell.getValue())} maxLines={1} />,
163
+ cell: truncateCell,
154
164
  },
155
165
 
156
166
  manualSorting,
@@ -177,6 +187,13 @@ export function Table<TData extends object>({
177
187
  getCoreRowModel: getCoreRowModel(),
178
188
  columnResizeMode: 'onEnd',
179
189
  keepPinnedRows,
190
+
191
+ meta: {
192
+ tableContainerRef,
193
+ rowVirtualizerInstanceRef,
194
+ rowVirtualizerOptions,
195
+ enableRowVirtualization,
196
+ },
180
197
  });
181
198
 
182
199
  const { loadingTable } = useLoadingTable({
@@ -224,9 +241,21 @@ export function Table<TData extends object>({
224
241
  const columnSizeVarsRef = useRef<Record<string, string>>();
225
242
  const headers = table.getFlatHeaders();
226
243
 
227
- const columnSizeVars = useMemo(() => {
244
+ const tableRows = table.getRowModel().rows;
245
+ const tableCenterRows = table.getCenterRows();
246
+ const tableFilteredRows = table.getFilteredRowModel().rows;
247
+ const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
248
+ const topRows = table.getTopRows();
249
+ const loadingTableRows = loadingTable.getRowModel().rows;
250
+ const tablePagination = table.getState().pagination;
251
+
252
+ const [isScrollBeenInitialized, setScrollInitialization] = useState(false);
253
+ const rowVirtualizer = useRowVirtualizer(table, isScrollBeenInitialized);
254
+ const overallVirtualHeight = rowVirtualizer?.getTotalSize() ?? 0;
255
+
256
+ const cssSizeVars = useMemo(() => {
228
257
  const originalColumnDefs = table._getColumnDefs();
229
- const colSizes: Record<string, string> = {};
258
+ const colSizes: Record<string, string> = overallVirtualHeight > 0 ? { height: `${overallVirtualHeight}px` } : {};
230
259
 
231
260
  for (let i = 0; i < headers.length; i++) {
232
261
  const header = headers[i];
@@ -288,19 +317,11 @@ export function Table<TData extends object>({
288
317
  table.getTotalSize() will trigger re-render after double-click size reset
289
318
  */
290
319
  // eslint-disable-next-line react-hooks/exhaustive-deps
291
- }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
320
+ }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize(), overallVirtualHeight]);
292
321
 
293
322
  useEffect(() => {
294
- columnSizeVarsRef.current = columnSizeVars;
295
- }, [columnSizeVars]);
296
-
297
- const tableRows = table.getRowModel().rows;
298
- const tableCenterRows = table.getCenterRows();
299
- const tableFilteredRows = table.getFilteredRowModel().rows;
300
- const tableFilteredRowsIds = tableFilteredRows.map(row => row.id);
301
- const topRows = table.getTopRows();
302
- const loadingTableRows = loadingTable.getRowModel().rows;
303
- const tablePagination = table.getState().pagination;
323
+ columnSizeVarsRef.current = cssSizeVars;
324
+ }, [cssSizeVars]);
304
325
 
305
326
  const filteredTopRows = table.getState().globalFilter
306
327
  ? topRows.filter(tr => tableFilteredRowsIds.includes(tr.id))
@@ -316,6 +337,12 @@ export function Table<TData extends object>({
316
337
  return !tableRows.length ? Math.min(Math.max(tempPageSize, 5), DEFAULT_PAGE_SIZE) : tempPageSize;
317
338
  }, [filteredTopRows.length, pageSize, suppressPagination, tablePagination?.pageSize, tableRows.length]);
318
339
 
340
+ const tableCtx = useMemo(() => ({ table }), [table]);
341
+
342
+ const onInitializeScroll = useCallback(() => {
343
+ setScrollInitialization(true);
344
+ }, []);
345
+
319
346
  usePageReset({
320
347
  manualPagination,
321
348
  maximumAvailablePage: pageCount || data.length / pagination.pageSize,
@@ -375,9 +402,15 @@ export function Table<TData extends object>({
375
402
  )}
376
403
 
377
404
  <div className={styles.scrollWrapper} data-outline={outline || undefined}>
378
- <Scroll size='s' className={styles.table} ref={scrollContainerRef}>
379
- <div className={styles.tableContent} style={columnSizeVars}>
380
- <TableContext.Provider value={{ table }}>
405
+ <Scroll
406
+ size='s'
407
+ ref={mergeRefs(tableContainerRef, scrollContainerRef)}
408
+ className={styles.table}
409
+ onScroll={onScroll}
410
+ onInitialized={onInitializeScroll}
411
+ >
412
+ <div className={styles.tableContent} style={cssSizeVars}>
413
+ <TableContext.Provider value={tableCtx}>
381
414
  {loading ? (
382
415
  <SkeletonContextProvider loading>
383
416
  <HeaderRow />
@@ -396,9 +429,20 @@ export function Table<TData extends object>({
396
429
  </div>
397
430
  ) : null}
398
431
 
399
- {centerRows.map(row => (
400
- <BodyRow key={row.id} row={row} onRowClick={onRowClick} />
401
- ))}
432
+ {(rowVirtualizer?.getVirtualItems() ?? centerRows).map((rowOrVirtualRow, index) => {
433
+ const row = isVirtualRow(rowOrVirtualRow) ? centerRows[rowOrVirtualRow.index] : centerRows[index];
434
+
435
+ return (
436
+ <VirtualRow
437
+ key={row.id}
438
+ ref={rowVirtualizer?.measureElement}
439
+ data-index={row.index}
440
+ virtualRow={isVirtualRow(rowOrVirtualRow) ? rowOrVirtualRow : undefined}
441
+ >
442
+ <BodyRow row={row} onRowClick={onRowClick} />
443
+ </VirtualRow>
444
+ );
445
+ })}
402
446
 
403
447
  <TableEmptyState
404
448
  emptyStates={emptyStates}
@@ -0,0 +1,43 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ import { useVirtualizer } from '@tanstack/react-virtual';
3
+ import { MutableRefObject } from 'react';
4
+
5
+ import { TableProps } from '../../types';
6
+
7
+ type MetaParams = Pick<
8
+ TableProps<never>,
9
+ 'rowVirtualizerInstanceRef' | 'rowVirtualizerOptions' | 'enableRowVirtualization'
10
+ > & {
11
+ tableContainerRef: MutableRefObject<HTMLElement | null>;
12
+ };
13
+
14
+ export function useRowVirtualizer<T extends object>(table: Table<T>, isScrollInitialized: boolean) {
15
+ const {
16
+ getRowModel,
17
+ options: { meta = {} },
18
+ } = table;
19
+
20
+ const { rowVirtualizerInstanceRef, rowVirtualizerOptions, tableContainerRef, enableRowVirtualization } =
21
+ meta as MetaParams;
22
+
23
+ const normalRowHeight = 40;
24
+
25
+ const rowVirtualizer = useVirtualizer({
26
+ count: getRowModel().rows.length,
27
+ estimateSize: () => normalRowHeight,
28
+ getScrollElement: () => (isScrollInitialized ? tableContainerRef.current : null),
29
+ measureElement:
30
+ typeof window !== 'undefined' && navigator.userAgent.indexOf('Firefox') === -1
31
+ ? element => element?.getBoundingClientRect().height
32
+ : undefined,
33
+ overscan: 28,
34
+ enabled: enableRowVirtualization,
35
+ ...rowVirtualizerOptions,
36
+ });
37
+
38
+ if (rowVirtualizerInstanceRef && enableRowVirtualization) {
39
+ rowVirtualizerInstanceRef.current = rowVirtualizer;
40
+ }
41
+
42
+ return enableRowVirtualization ? rowVirtualizer : undefined;
43
+ }
@@ -3,10 +3,15 @@
3
3
  .table {
4
4
  @include styles-tokens-table.composite-var(styles-tokens-table.$table-table-container);
5
5
 
6
+ will-change: transform;
7
+ overflow-anchor: none;
8
+
6
9
  position: relative;
7
10
  /* stylelint-disable-next-line declaration-property-value-allowed-list */
8
11
  z-index: 0;
9
12
 
13
+ contain: paint;
14
+
10
15
  /* stylelint-disable-next-line declaration-no-important */
11
16
  overflow: hidden !important;
12
17
  display: flex;
@@ -39,6 +44,7 @@
39
44
  }
40
45
 
41
46
  .tableContent {
47
+ content-visibility: auto;
42
48
  min-width: max-content;
43
49
  }
44
50
 
@@ -1,3 +1,7 @@
1
+ import { CellContext } from '@tanstack/react-table';
2
+ import { VirtualItem } from '@tanstack/react-virtual';
3
+
4
+ import { TruncateString } from '@snack-uikit/truncate-string';
1
5
  import { isBrowser } from '@snack-uikit/utils';
2
6
 
3
7
  export function getCurrentlyConfiguredHeaderWidth(id: string): number {
@@ -58,3 +62,17 @@ export function saveStateToLocalStorage({ id, columnId, size }: SaveStateToLocal
58
62
 
59
63
  localStorage.setItem(id, JSON.stringify({ ...(savedStateFromStorage || {}), resizeState: newResizeState }));
60
64
  }
65
+
66
+ /**
67
+ * check the key, because index is contained in a common table row
68
+ */
69
+ export function isVirtualRow(row: unknown): row is VirtualItem {
70
+ return typeof row === 'object' && row != null && 'key' in row;
71
+ }
72
+
73
+ /**
74
+ * @description prevent permanent recreation of the function on rerender
75
+ */
76
+ export function truncateCell<TData>(cell: CellContext<TData, unknown>) {
77
+ return <TruncateString text={String(cell.getValue())} maxLines={1} />;
78
+ }
@@ -6,13 +6,14 @@ import {
6
6
  RowSelectionState,
7
7
  SortingState,
8
8
  } from '@tanstack/react-table';
9
- import { ReactNode, RefObject } from 'react';
9
+ import { useVirtualizer } from '@tanstack/react-virtual';
10
+ import { MutableRefObject, ReactNode, RefObject } from 'react';
10
11
 
11
12
  import { ToolbarProps } from '@snack-uikit/toolbar';
12
13
  import { WithSupportProps } from '@snack-uikit/utils';
13
14
 
14
15
  import { EmptyStateProps, ExportButtonProps, RowClickHandler } from '../helperComponents';
15
- import { ColumnDefinition } from '../types';
16
+ import { ColumnDefinition, RowVirtualizer } from '../types';
16
17
 
17
18
  export type TableProps<TData extends object> = WithSupportProps<{
18
19
  /** Данные для отрисовки */
@@ -161,6 +162,14 @@ export type TableProps<TData extends object> = WithSupportProps<{
161
162
  id: string;
162
163
  resize?: boolean;
163
164
  };
165
+
166
+ onScroll?: (event?: Event) => void;
167
+ // Включение виртуализации для строк
168
+ enableRowVirtualization?: boolean;
169
+ // Параметры для переопределения настроек виртуализации строк
170
+ rowVirtualizerOptions?: Parameters<typeof useVirtualizer>[0];
171
+ // Ссылка на экземпляр useRowVirtualizer
172
+ rowVirtualizerInstanceRef?: MutableRefObject<RowVirtualizer>;
164
173
  }>;
165
174
 
166
175
  export type ServerTableProps<TData extends object> = Omit<
@@ -0,0 +1,24 @@
1
+ import { VirtualItem } from '@tanstack/react-virtual';
2
+ import { forwardRef, HTMLAttributes, PropsWithChildren } from 'react';
3
+
4
+ import styles from './styles.module.scss';
5
+
6
+ export const VirtualRow = forwardRef<
7
+ HTMLDivElement,
8
+ PropsWithChildren<HTMLAttributes<HTMLDivElement> & { virtualRow?: VirtualItem }>
9
+ >(({ virtualRow, children, ...attributes }, ref) => {
10
+ if (!virtualRow) {
11
+ return <>{children}</>;
12
+ }
13
+
14
+ return (
15
+ <div
16
+ ref={ref}
17
+ className={styles.virtualRow}
18
+ style={{ '--virtual-row-deviation': `${virtualRow.start}px` }}
19
+ {...attributes}
20
+ >
21
+ {children}
22
+ </div>
23
+ );
24
+ });
@@ -127,3 +127,11 @@ $snack-ui-table-row-background: var(--snack-ui-table-row-background);
127
127
  top: 0;
128
128
  border: none;
129
129
  }
130
+
131
+ .virtualRow {
132
+ position: absolute;
133
+ top: 0;
134
+ left: 0;
135
+ width: 100%;
136
+ transform: translateY(var(--virtual-row-deviation));
137
+ }
package/src/types.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  RowSelectionState,
9
9
  SortingState,
10
10
  } from '@tanstack/react-table';
11
+ import { Virtualizer } from '@tanstack/react-virtual';
11
12
 
12
13
  import { ToolbarProps } from '@snack-uikit/toolbar';
13
14
  import { ValueOf } from '@snack-uikit/utils';
@@ -65,6 +66,8 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
65
66
 
66
67
  export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
67
68
 
69
+ export type RowVirtualizer = Virtualizer<Element, Element> | null;
70
+
68
71
  export type {
69
72
  RowActionsColumnDefProps,
70
73
  StatusColumnDefinitionProps,