@snack-uikit/table 0.29.0 → 0.31.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 0.31.0 (2025-02-27)
7
+
8
+
9
+ ### Features
10
+
11
+ * **FF-6236:** infinite scroll in table ([09afb22](https://github.com/cloud-ru-tech/snack-uikit/commit/09afb2234211802c86722e36e17b9161f912b9e8))
12
+
13
+
14
+
15
+
16
+
17
+ # 0.30.0 (2025-02-26)
18
+
19
+
20
+ ### Features
21
+
22
+ * **PDS-1487:** freeze columns size on resize ([f5f5694](https://github.com/cloud-ru-tech/snack-uikit/commit/f5f56948a5e0ba42f071a66e2de225465f67e980))
23
+
24
+
25
+
26
+
27
+
6
28
  # 0.29.0 (2025-02-25)
7
29
 
8
30
 
package/README.md CHANGED
@@ -120,9 +120,6 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
120
120
  | search | `{ initialState?: string; state?: string; placeholder?: string; loading?: boolean; onChange?(value: string): void; }` | 'Search'<br> <strong>loading</strong>: Состояние загрузки в строке поиска <br> <strong>onChange</strong>: Колбэк на изменение данных в строке поиска | Параметры отвечают за глобальный поиск в таблице <br> <strong>initialState</strong>: Начальное состояние строки поиска <br> <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br> <strong>placeholder</strong>: Placeholder строки поиска |
121
121
  | enableFuzzySearch | `boolean` | - | Включить нечеткий поиск |
122
122
  | pageSize | `number` | 10 | Максимальное кол-во строк на страницу |
123
- | pagination | `{ state?: PaginationState; options?: number[]; optionsLabel?: string; onChange?(state: PaginationState): void; optionsRender?(value: string \| number, idx: number): string \| number; }` | 'Rows volume: ' <br> <strong>onChange</strong>: Колбэк на изменение пагинации | Параметры отвечают за пагинацию в таблице <br> <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br> <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br> <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу |
124
- | autoResetPageIndex | `boolean` | - | Автоматический сброс пагинации к первой странице при изменении данных или состояния (e.g фильтры, сортировки, и т.д) |
125
- | pageCount | `number` | - | Кол-во страниц (используется для внешнего управления) |
126
123
  | onRowClick | `RowClickHandler<TData>` | - | Колбэк клика по строке |
127
124
  | className | `string` | - | CSS-класс |
128
125
  | loading | `boolean` | - | Состояние загрузки |
@@ -140,15 +137,19 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
140
137
  | moreActions | `Action[]` | - | Элементы выпадающего списка кнопки с действиями |
141
138
  | toolbarAfter | `ReactNode` | - | Дополнительный слот в `Toolbar` после строки поиска |
142
139
  | exportSettings | `{ fileName: string; filterData?: boolean; exportToCSV?(args: ExportProps<TData>): void; exportToXLSX?(args: ExportProps<TData>): void; }` | - | Настройки экспорта в тулбаре |
143
- | suppressPagination | `boolean` | - | Отключение пагинации |
144
140
  | manualSorting | `boolean` | - | |
145
- | manualPagination | `boolean` | - | |
146
141
  | manualFiltering | `boolean` | - | |
147
142
  | getRowId | `(originalRow: TData, index: number, parent?: Row<TData>) => string` | - | Дополнительная функция используется для получения уникального идентификатора для любой заданной строки |
148
143
  | scrollRef | `RefObject<HTMLElement>` | - | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
149
144
  | scrollContainerRef | `RefObject<HTMLElement>` | - | Ссылка на контейнер, который скроллится |
150
145
  | rowPinning | `Pick<RowPinningState, "top">` | { top: [], } | Определение какие строки должны быть закреплены в таблице |
151
146
  | savedState | `{ id: string; resize?: boolean; }` | - | Конфиг для сохранения состояния в localStorage. <br> Поле id должно быть уникальным для разных таблиц в рамках приложения. <br> Для корректной работы необходимо наличие id в конфиге columnDefinitions |
147
+ | pagination | `{ state?: PaginationState; options?: number[]; optionsLabel?: string; onChange?(state: PaginationState): void; optionsRender?(value: string \| number, idx: number): string \| number; }` | 'Rows volume: ' <br> <strong>onChange</strong>: Колбэк на изменение пагинации | Параметры отвечают за пагинацию в таблице <br> <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br> <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br> <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу |
148
+ | autoResetPageIndex | `boolean` | - | Автоматический сброс пагинации к первой странице при изменении данных или состояния (e.g фильтры, сортировки, и т.д) |
149
+ | pageCount | `number` | - | Кол-во страниц (используется для внешнего управления) |
150
+ | infiniteLoading | `boolean` | - | Режим работы "бесконечной" загрузки |
151
+ | suppressPagination | `boolean` | - | Отключение пагинации |
152
+ | manualPagination | `boolean` | - | |
152
153
  ## Table.getStatusColumnDef
153
154
  Вспомогательная функция для создания ячейки со статусом
154
155
  ### Props
@@ -176,6 +177,9 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
176
177
  |------|------|---------------|-------------|
177
178
  | onChangePage* | `(offset: number, limit: number) => void` | - | |
178
179
  | columnDefinitions* | `ColumnDefinition<TData>[]` | - | Определение внешнего вида и функционала колонок |
180
+ | onRefresh | `() => void` | - | Колбек обновления данных |
181
+ | moreActions | `Action[]` | - | Элементы выпадающего списка кнопки с действиями |
182
+ | bulkActions | `BulkAction[]` | - | Список действия для массовых операций |
179
183
  | keepPinnedRows | `boolean` | false | Параметр отвечает за отображение закрепленных строк на всех страницах таблицы |
180
184
  | copyPinnedRows | `boolean` | false | Параметр отвечает за сохранение закрепленных строк в теле таблицы |
181
185
  | enableSelectPinned | `boolean` | - | Параметр отвечает за чекбокс выбора закрепленных строк |
@@ -183,11 +187,9 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
183
187
  | expanding | `{ getSubRows: (element: TData) => TData[]; expandingColumnDefinition: TreeColumnDefinitionProps<TData>; }` | - | Параметр отвечает за общие настройки раскрывающихся строк |
184
188
  | rowSelection | `{ initialState?: RowSelectionState; state?: RowSelectionState; enable?: boolean \| ((row: Row<TData>) => boolean); multiRow?: boolean; onChange?(state: RowSelectionState): void; }` | - | Параметры отвечают за возможность выбора строк <br> <strong>initialState</strong>: Начальное состояние выбора строк <br> <strong>state</strong>: Состояние выбора строк, жестко устанавливаемое снаружи <br> <strong>enable</strong>: Колбэк определяющий можно ли выбрать строку <br> <strong>multiRow</strong>: Мульти-выбор строк (включен по-умолчанию, когда включается выбор) <br> <strong>onChange</strong>: Колбэк на выбор строк |
185
189
  | enableFuzzySearch | `boolean` | - | Включить нечеткий поиск |
186
- | autoResetPageIndex | `boolean` | - | Автоматический сброс пагинации к первой странице при изменении данных или состояния (e.g фильтры, сортировки, и т.д) |
187
190
  | onRowClick | `RowClickHandler<TData>` | - | Колбэк клика по строке |
188
191
  | className | `string` | - | CSS-класс |
189
192
  | loading | `boolean` | - | Состояние загрузки |
190
- | onRefresh | `() => void` | - | Колбек обновления данных |
191
193
  | outline | `boolean` | - | Внешний бордер для тулбара и таблицы |
192
194
  | columnFilters | `ChipChoiceRowProps<TFilters>` | - | Фильтры |
193
195
  | dataFiltered | `boolean` | - | Флаг, показывающий что данные были отфильтрованы при пустых данных |
@@ -197,19 +199,19 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
197
199
  | errorDataState | `EmptyStateProps` | - | Экран при ошибке запроса |
198
200
  | suppressToolbar | `boolean` | - | Отключение тулбара |
199
201
  | suppressSearch | `boolean` | - | Отключение поиска |
200
- | bulkActions | `BulkAction[]` | - | Список действия для массовых операций |
201
- | moreActions | `Action[]` | - | Элементы выпадающего списка кнопки с действиями |
202
202
  | toolbarAfter | `ReactNode` | - | Дополнительный слот в `Toolbar` после строки поиска |
203
203
  | exportSettings | `{ fileName: string; filterData?: boolean; exportToCSV?(args: ExportProps<TData>): void; exportToXLSX?(args: ExportProps<TData>): void; }` | - | Настройки экспорта в тулбаре |
204
- | suppressPagination | `boolean` | - | Отключение пагинации |
205
204
  | manualSorting | `boolean` | true | |
206
- | manualPagination | `boolean` | true | |
207
205
  | manualFiltering | `boolean` | true | |
208
206
  | getRowId | `(originalRow: TData, index: number, parent?: Row<TData>) => string` | - | Дополнительная функция используется для получения уникального идентификатора для любой заданной строки |
209
207
  | scrollRef | `RefObject<HTMLElement>` | - | Ссылка на элемент, обозначающий самый конец прокручиваемого списка |
210
208
  | scrollContainerRef | `RefObject<HTMLElement>` | - | Ссылка на контейнер, который скроллится |
211
209
  | rowPinning | `Pick<RowPinningState, "top">` | - | Определение какие строки должны быть закреплены в таблице |
212
210
  | savedState | `{ id: string; resize?: boolean; }` | - | Конфиг для сохранения состояния в localStorage. <br> Поле id должно быть уникальным для разных таблиц в рамках приложения. <br> Для корректной работы необходимо наличие id в конфиге columnDefinitions |
211
+ | autoResetPageIndex | `boolean` | - | Автоматический сброс пагинации к первой странице при изменении данных или состояния (e.g фильтры, сортировки, и т.д) |
212
+ | suppressPagination | `boolean` | - | Отключение пагинации |
213
+ | manualPagination | `boolean` | true | |
214
+ | infiniteLoading | `never` | - | |
213
215
  | items | `TData[]` | - | Данные для отрисовки |
214
216
  | total | `number` | 10 | Общее кол-во строк |
215
217
  | limit | `number` | 10 | Кол-во строк на страницу |
@@ -1,7 +1,7 @@
1
1
  import { FiltersState } from '@snack-uikit/chips';
2
2
  import { TableProps } from '../types';
3
3
  /** Компонент таблицы */
4
- export declare function Table<TData extends object, TFilters extends FiltersState = Record<string, unknown>>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, suppressSearch, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp, ...rest }: TableProps<TData, TFilters>): import("react/jsx-runtime").JSX.Element;
4
+ export declare function Table<TData extends object, TFilters extends FiltersState = Record<string, unknown>>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize, pageCount, loading, infiniteLoading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, suppressSearch, toolbarAfter, suppressPagination, manualSorting, manualPagination: manualPaginationProp, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp, ...rest }: TableProps<TData, TFilters>): import("react/jsx-runtime").JSX.Element;
5
5
  export declare namespace Table {
6
6
  var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
7
7
  var statusAppearances: Record<string, string>;
@@ -58,6 +58,7 @@ function Table(_a) {
58
58
  pageSize = constants_1.DEFAULT_PAGE_SIZE,
59
59
  pageCount,
60
60
  loading = false,
61
+ infiniteLoading = false,
61
62
  outline = false,
62
63
  moreActions,
63
64
  exportSettings,
@@ -71,7 +72,7 @@ function Table(_a) {
71
72
  toolbarAfter,
72
73
  suppressPagination = false,
73
74
  manualSorting = false,
74
- manualPagination = false,
75
+ manualPagination: manualPaginationProp = false,
75
76
  manualFiltering = false,
76
77
  autoResetPageIndex = false,
77
78
  scrollRef,
@@ -82,7 +83,7 @@ function Table(_a) {
82
83
  expanding,
83
84
  bulkActions: bulkActionsProp
84
85
  } = _a,
85
- rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "suppressSearch", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "expanding", "bulkActions"]);
86
+ rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "pageSize", "pageCount", "loading", "infiniteLoading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "suppressSearch", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "expanding", "bulkActions"]);
86
87
  const {
87
88
  state: globalFilter,
88
89
  onStateChange: onGlobalFilterChange
@@ -104,6 +105,7 @@ function Table(_a) {
104
105
  onStateChange: onPaginationChange
105
106
  } = (0, hooks_1.useStateControl)(paginationProp, defaultPaginationState);
106
107
  const enableSelection = Boolean(rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable);
108
+ const manualPagination = infiniteLoading || manualPaginationProp;
107
109
  const tableColumns = (0, react_1.useMemo)(() => {
108
110
  let cols = columnDefinitions;
109
111
  if (enableSelection && !expanding) {
@@ -229,10 +231,17 @@ function Table(_a) {
229
231
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
230
232
  const columnSizeVarsRef = (0, react_1.useRef)();
231
233
  const headers = table.getFlatHeaders();
232
- const columnSizeVars = (0, react_1.useMemo)(() => {
234
+ const columnSizes = (0, react_1.useMemo)(() => {
233
235
  var _a;
234
236
  const originalColumnDefs = table._getColumnDefs();
235
- const colSizes = {};
237
+ const vars = {};
238
+ const realSizes = {};
239
+ const resizedColumnIndex = headers.findIndex(_ref => {
240
+ let {
241
+ column
242
+ } = _ref;
243
+ return column.getIsResizing();
244
+ });
236
245
  for (let i = 0; i < headers.length; i++) {
237
246
  const header = headers[i];
238
247
  const {
@@ -241,8 +250,8 @@ function Table(_a) {
241
250
  } = (0, utils_3.getColumnStyleVars)(header.id);
242
251
  const originalColDef = originalColumnDefs.find(col => (0, helperComponents_1.getColumnId)(header) === col.id);
243
252
  if (header.id === 'snack_predefined_statusColumn' && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.header) && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.enableSorting)) {
244
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
245
- colSizes[flexKey] = '100%';
253
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
254
+ vars[flexKey] = '100%';
246
255
  } else {
247
256
  const originalColumnDefSize = originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.size;
248
257
  let initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
@@ -261,12 +270,9 @@ function Table(_a) {
261
270
  if (header.column.getCanResize()) {
262
271
  const currentSize = header.getSize();
263
272
  const colDefSize = header.column.columnDef.size;
264
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
265
- if (prevSize === '100%' && currentSize !== colDefSize) {
266
- const realSize = (0, utils_3.getCurrentlyConfiguredHeaderWidth)(header.id);
267
- table.setColumnSizing(old => Object.assign(Object.assign({}, old), {
268
- [header.id]: realSize
269
- }));
273
+ if (currentSize !== colDefSize || i < resizedColumnIndex && prevSize === '100%') {
274
+ const realSize = prevSize === '100%' ? (0, utils_3.getCurrentlyConfiguredHeaderWidth)(header.id) : currentSize;
275
+ realSizes[header.id] = realSize;
270
276
  size = `${realSize}px`;
271
277
  }
272
278
  }
@@ -277,11 +283,14 @@ function Table(_a) {
277
283
  size
278
284
  });
279
285
  }
280
- colSizes[sizeKey] = size;
281
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
286
+ vars[sizeKey] = size;
287
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
282
288
  }
283
289
  }
284
- return colSizes;
290
+ return {
291
+ vars,
292
+ realSizes
293
+ };
285
294
  /*
286
295
  effect must be called only on columnSizingInfo.isResizingColumn changes
287
296
  to reduce unnecessary recalculations
@@ -291,8 +300,11 @@ function Table(_a) {
291
300
  // eslint-disable-next-line react-hooks/exhaustive-deps
292
301
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
293
302
  (0, react_1.useEffect)(() => {
294
- columnSizeVarsRef.current = columnSizeVars;
295
- }, [columnSizeVars]);
303
+ if (Object.keys(columnSizes.realSizes).length) {
304
+ table.setColumnSizing(old => Object.assign(Object.assign({}, old), columnSizes.realSizes));
305
+ }
306
+ columnSizeVarsRef.current = columnSizes.vars;
307
+ }, [columnSizes, table]);
296
308
  const tableRows = table.getRowModel().rows;
297
309
  const tableCenterRows = table.getCenterRows();
298
310
  const tableFilteredRows = table.getFilteredRowModel().rows;
@@ -374,12 +386,12 @@ function Table(_a) {
374
386
  ref: scrollContainerRef,
375
387
  children: [(0, jsx_runtime_1.jsx)("div", {
376
388
  className: styles_module_scss_1.default.tableContent,
377
- style: columnSizeVars,
389
+ style: columnSizes.vars,
378
390
  children: (0, jsx_runtime_1.jsx)(helperComponents_1.TableContext.Provider, {
379
391
  value: {
380
392
  table
381
393
  },
382
- children: loading ? (0, jsx_runtime_1.jsxs)(skeleton_1.SkeletonContextProvider, {
394
+ children: (!infiniteLoading || !data.length) && loading ? (0, jsx_runtime_1.jsxs)(skeleton_1.SkeletonContextProvider, {
383
395
  loading: true,
384
396
  children: [(0, jsx_runtime_1.jsx)(helperComponents_1.HeaderRow, {}), loadingTableRows.map(row => (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
385
397
  row: row
@@ -394,7 +406,12 @@ function Table(_a) {
394
406
  }) : null, centerRows.map(row => (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
395
407
  row: row,
396
408
  onRowClick: onRowClick
397
- }, row.id)), (0, jsx_runtime_1.jsx)(helperComponents_1.TableEmptyState, {
409
+ }, row.id)), data.length > 0 && infiniteLoading && loading && (0, jsx_runtime_1.jsx)(skeleton_1.SkeletonContextProvider, {
410
+ loading: true,
411
+ children: loadingTableRows.slice(0, 3).map(row => (0, jsx_runtime_1.jsx)(helperComponents_1.BodyRow, {
412
+ row: row
413
+ }, row.id))
414
+ }), (0, jsx_runtime_1.jsx)(helperComponents_1.TableEmptyState, {
398
415
  emptyStates: emptyStates,
399
416
  dataError: dataError,
400
417
  dataFiltered: dataFiltered || Boolean(table.getState().globalFilter),
@@ -407,7 +424,7 @@ function Table(_a) {
407
424
  ref: scrollRef
408
425
  })]
409
426
  })
410
- }), !suppressPagination && (0, jsx_runtime_1.jsx)(helperComponents_1.TablePagination, {
427
+ }), !infiniteLoading && !suppressPagination && (0, jsx_runtime_1.jsx)(helperComponents_1.TablePagination, {
411
428
  table: table,
412
429
  options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options,
413
430
  optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel,
@@ -12,12 +12,15 @@ function getCurrentlyConfiguredHeaderWidth(id) {
12
12
  if ((0, utils_1.isBrowser)()) {
13
13
  const cell = document.querySelector(`[data-header-id="${id}"]`);
14
14
  const resizeHandler = cell === null || cell === void 0 ? void 0 : cell.querySelector('[data-test-id="table__header-cell-resize-handle-moving-part"]');
15
- if (cell && resizeHandler) {
15
+ if (cell) {
16
16
  const {
17
17
  width
18
18
  } = cell.getBoundingClientRect();
19
- const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
20
- return width + offset;
19
+ if (resizeHandler) {
20
+ const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
21
+ return width + offset;
22
+ }
23
+ return width;
21
24
  }
22
25
  }
23
26
  return 0;
@@ -9,7 +9,7 @@ import { ColumnDefinition } from '../types';
9
9
  type BulkAction = Omit<NonNullable<ToolbarProps<Record<string, string>>['bulkActions']>[number], 'onClick'> & {
10
10
  onClick?(selectionState: RowSelectionState, resetRowSelection: (defaultState?: boolean) => void): void;
11
11
  };
12
- export type TableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = WithSupportProps<{
12
+ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = WithSupportProps<{
13
13
  /** Данные для отрисовки */
14
14
  data: TData[];
15
15
  /** Определение внешнего вида и функционала колонок */
@@ -68,25 +68,6 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
68
68
  enableFuzzySearch?: boolean;
69
69
  /** Максимальное кол-во строк на страницу @default 10 */
70
70
  pageSize?: number;
71
- /** Параметры отвечают за пагинацию в таблице <br>
72
- * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
73
- * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
74
- * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
75
- * <strong>onChange</strong>: Колбэк на изменение пагинации
76
- * */
77
- pagination?: {
78
- state?: PaginationState;
79
- options?: number[];
80
- optionsLabel?: string;
81
- onChange?(state: PaginationState): void;
82
- optionsRender?(value: string | number, idx: number): string | number;
83
- };
84
- /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
85
- * (e.g фильтры, сортировки, и т.д)
86
- * */
87
- autoResetPageIndex?: boolean;
88
- /** Кол-во страниц (используется для внешнего управления) */
89
- pageCount?: number;
90
71
  /** Колбэк клика по строке */
91
72
  onRowClick?: RowClickHandler<TData>;
92
73
  /** CSS-класс */
@@ -121,10 +102,7 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
121
102
  toolbarAfter?: ReactNode;
122
103
  /** Настройки экспорта в тулбаре */
123
104
  exportSettings?: ExportButtonProps<TData>['settings'];
124
- /** Отключение пагинации */
125
- suppressPagination?: boolean;
126
105
  manualSorting?: boolean;
127
- manualPagination?: boolean;
128
106
  manualFiltering?: boolean;
129
107
  /** Дополнительная функция используется для получения уникального идентификатора для любой заданной строки */
130
108
  getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string;
@@ -143,7 +121,43 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
143
121
  resize?: boolean;
144
122
  };
145
123
  }>;
146
- export type ServerTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = Omit<TableProps<TData, TFilters>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
124
+ export type InfiniteTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = BaseTableProps<TData, TFilters> & {
125
+ pagination?: never;
126
+ autoResetPageIndex?: never;
127
+ pageCount?: never;
128
+ /** Режим работы "бесконечной" загрузки */
129
+ infiniteLoading?: boolean;
130
+ /** Отключение пагинации */
131
+ suppressPagination?: never;
132
+ manualPagination?: never;
133
+ };
134
+ export type ClientTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = BaseTableProps<TData, TFilters> & {
135
+ /** Параметры отвечают за пагинацию в таблице <br>
136
+ * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
137
+ * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
138
+ * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
139
+ * <strong>onChange</strong>: Колбэк на изменение пагинации
140
+ * */
141
+ pagination?: {
142
+ state?: PaginationState;
143
+ options?: number[];
144
+ optionsLabel?: string;
145
+ onChange?(state: PaginationState): void;
146
+ optionsRender?(value: string | number, idx: number): string | number;
147
+ };
148
+ /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
149
+ * (e.g фильтры, сортировки, и т.д)
150
+ * */
151
+ autoResetPageIndex?: boolean;
152
+ /** Кол-во страниц (используется для внешнего управления) */
153
+ pageCount?: number;
154
+ /** Отключение пагинации */
155
+ suppressPagination?: boolean;
156
+ manualPagination?: boolean;
157
+ infiniteLoading?: never;
158
+ };
159
+ export type TableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = InfiniteTableProps<TData, TFilters> | ClientTableProps<TData, TFilters>;
160
+ export type ServerTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = Omit<ClientTableProps<TData, TFilters>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
147
161
  /** Данные для отрисовки */
148
162
  items?: TData[];
149
163
  /**
@@ -1,7 +1,7 @@
1
1
  import { FiltersState } from '@snack-uikit/chips';
2
2
  import { TableProps } from '../types';
3
3
  /** Компонент таблицы */
4
- export declare function Table<TData extends object, TFilters extends FiltersState = Record<string, unknown>>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize, pageCount, loading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, suppressSearch, toolbarAfter, suppressPagination, manualSorting, manualPagination, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp, ...rest }: TableProps<TData, TFilters>): import("react/jsx-runtime").JSX.Element;
4
+ export declare function Table<TData extends object, TFilters extends FiltersState = Record<string, unknown>>({ data, rowPinning, columnDefinitions, keepPinnedRows, copyPinnedRows, enableSelectPinned, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize, pageCount, loading, infiniteLoading, outline, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar, suppressSearch, toolbarAfter, suppressPagination, manualSorting, manualPagination: manualPaginationProp, manualFiltering, autoResetPageIndex, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp, ...rest }: TableProps<TData, TFilters>): import("react/jsx-runtime").JSX.Element;
5
5
  export declare namespace Table {
6
6
  var getStatusColumnDef: typeof import("../../helperComponents").getStatusColumnDef;
7
7
  var statusAppearances: Record<string, string>;
@@ -32,7 +32,7 @@ import { getColumnStyleVars, getCurrentlyConfiguredHeaderWidth, getInitColumnSiz
32
32
  export function Table(_a) {
33
33
  var { data, rowPinning = {
34
34
  top: [],
35
- }, columnDefinitions, keepPinnedRows = false, copyPinnedRows = false, enableSelectPinned = false, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize = DEFAULT_PAGE_SIZE, pageCount, loading = false, outline = false, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar = false, suppressSearch = false, toolbarAfter, suppressPagination = false, manualSorting = false, manualPagination = false, manualFiltering = false, autoResetPageIndex = false, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp } = _a, rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "pageSize", "pageCount", "loading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "suppressSearch", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "expanding", "bulkActions"]);
35
+ }, columnDefinitions, keepPinnedRows = false, copyPinnedRows = false, enableSelectPinned = false, rowSelection: rowSelectionProp, search, sorting: sortingProp, columnFilters, pagination: paginationProp, className, onRowClick, onRefresh, pageSize = DEFAULT_PAGE_SIZE, pageCount, loading = false, infiniteLoading = false, outline = false, moreActions, exportSettings, dataFiltered, dataError, noDataState, noResultsState, errorDataState, suppressToolbar = false, suppressSearch = false, toolbarAfter, suppressPagination = false, manualSorting = false, manualPagination: manualPaginationProp = false, manualFiltering = false, autoResetPageIndex = false, scrollRef, scrollContainerRef, getRowId, enableFuzzySearch, savedState, expanding, bulkActions: bulkActionsProp } = _a, rest = __rest(_a, ["data", "rowPinning", "columnDefinitions", "keepPinnedRows", "copyPinnedRows", "enableSelectPinned", "rowSelection", "search", "sorting", "columnFilters", "pagination", "className", "onRowClick", "onRefresh", "pageSize", "pageCount", "loading", "infiniteLoading", "outline", "moreActions", "exportSettings", "dataFiltered", "dataError", "noDataState", "noResultsState", "errorDataState", "suppressToolbar", "suppressSearch", "toolbarAfter", "suppressPagination", "manualSorting", "manualPagination", "manualFiltering", "autoResetPageIndex", "scrollRef", "scrollContainerRef", "getRowId", "enableFuzzySearch", "savedState", "expanding", "bulkActions"]);
36
36
  const { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl(search, '');
37
37
  const { state: rowSelection, onStateChange: onRowSelectionChange } = useStateControl(rowSelectionProp, {});
38
38
  const defaultPaginationState = useMemo(() => ({
@@ -42,6 +42,7 @@ export function Table(_a) {
42
42
  const { state: sorting, onStateChange: onSortingChange } = useStateControl(sortingProp, []);
43
43
  const { state: pagination, onStateChange: onPaginationChange } = useStateControl(paginationProp, defaultPaginationState);
44
44
  const enableSelection = Boolean(rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable);
45
+ const manualPagination = infiniteLoading || manualPaginationProp;
45
46
  const tableColumns = useMemo(() => {
46
47
  let cols = columnDefinitions;
47
48
  if (enableSelection && !expanding) {
@@ -155,17 +156,19 @@ export function Table(_a) {
155
156
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
156
157
  const columnSizeVarsRef = useRef();
157
158
  const headers = table.getFlatHeaders();
158
- const columnSizeVars = useMemo(() => {
159
+ const columnSizes = useMemo(() => {
159
160
  var _a;
160
161
  const originalColumnDefs = table._getColumnDefs();
161
- const colSizes = {};
162
+ const vars = {};
163
+ const realSizes = {};
164
+ const resizedColumnIndex = headers.findIndex(({ column }) => column.getIsResizing());
162
165
  for (let i = 0; i < headers.length; i++) {
163
166
  const header = headers[i];
164
167
  const { sizeKey, flexKey } = getColumnStyleVars(header.id);
165
168
  const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
166
169
  if (header.id === 'snack_predefined_statusColumn' && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.header) && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.enableSorting)) {
167
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
168
- colSizes[flexKey] = '100%';
170
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
171
+ vars[flexKey] = '100%';
169
172
  }
170
173
  else {
171
174
  const originalColumnDefSize = originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.size;
@@ -182,21 +185,20 @@ export function Table(_a) {
182
185
  if (header.column.getCanResize()) {
183
186
  const currentSize = header.getSize();
184
187
  const colDefSize = header.column.columnDef.size;
185
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
186
- if (prevSize === '100%' && currentSize !== colDefSize) {
187
- const realSize = getCurrentlyConfiguredHeaderWidth(header.id);
188
- table.setColumnSizing(old => (Object.assign(Object.assign({}, old), { [header.id]: realSize })));
188
+ if (currentSize !== colDefSize || (i < resizedColumnIndex && prevSize === '100%')) {
189
+ const realSize = prevSize === '100%' ? getCurrentlyConfiguredHeaderWidth(header.id) : currentSize;
190
+ realSizes[header.id] = realSize;
189
191
  size = `${realSize}px`;
190
192
  }
191
193
  }
192
194
  if (isResizeSavedToStore) {
193
195
  saveStateToLocalStorage({ id: savedState.id, columnId: header.id, size });
194
196
  }
195
- colSizes[sizeKey] = size;
196
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
197
+ vars[sizeKey] = size;
198
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
197
199
  }
198
200
  }
199
- return colSizes;
201
+ return { vars, realSizes };
200
202
  /*
201
203
  effect must be called only on columnSizingInfo.isResizingColumn changes
202
204
  to reduce unnecessary recalculations
@@ -208,8 +210,11 @@ export function Table(_a) {
208
210
  // eslint-disable-next-line react-hooks/exhaustive-deps
209
211
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
210
212
  useEffect(() => {
211
- columnSizeVarsRef.current = columnSizeVars;
212
- }, [columnSizeVars]);
213
+ if (Object.keys(columnSizes.realSizes).length) {
214
+ table.setColumnSizing(old => (Object.assign(Object.assign({}, old), columnSizes.realSizes)));
215
+ }
216
+ columnSizeVarsRef.current = columnSizes.vars;
217
+ }, [columnSizes, table]);
213
218
  const tableRows = table.getRowModel().rows;
214
219
  const tableCenterRows = table.getCenterRows();
215
220
  const tableFilteredRows = table.getFilteredRowModel().rows;
@@ -245,7 +250,7 @@ export function Table(_a) {
245
250
  onChange: onGlobalFilterChange,
246
251
  loading: search === null || search === void 0 ? void 0 : search.loading,
247
252
  placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || t('searchPlaceholder'),
248
- }, className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, bulkActions: bulkActions, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), onCheck: enableSelection ? handleOnToolbarCheck : undefined, outline: outline, after: toolbarAfter || exportSettings ? (_jsxs(_Fragment, { children: [toolbarAfter, exportSettings && (_jsx(ExportButton, { settings: exportSettings, columnDefinitions: columnDefinitions, data: data, topRows: filteredTopRows, centerRows: centerRows }))] })) : undefined, moreActions: moreActions, filterRow: columnFilters, "data-test-id": TEST_IDS.toolbar }) })), _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 }))] })) }));
253
+ }, className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, bulkActions: bulkActions, selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), onCheck: enableSelection ? handleOnToolbarCheck : undefined, outline: outline, after: toolbarAfter || exportSettings ? (_jsxs(_Fragment, { children: [toolbarAfter, exportSettings && (_jsx(ExportButton, { settings: exportSettings, columnDefinitions: columnDefinitions, data: data, topRows: filteredTopRows, centerRows: centerRows }))] })) : undefined, moreActions: moreActions, filterRow: columnFilters, "data-test-id": TEST_IDS.toolbar }) })), _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: columnSizes.vars, children: _jsx(TableContext.Provider, { value: { table }, children: (!infiniteLoading || !data.length) && 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))), data.length > 0 && infiniteLoading && loading && (_jsx(SkeletonContextProvider, { loading: true, children: loadingTableRows.slice(0, 3).map(row => (_jsx(BodyRow, { row: row }, 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 })] }) }), !infiniteLoading && !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 }))] })) }));
249
254
  }
250
255
  Table.getStatusColumnDef = getStatusColumnDef;
251
256
  Table.statusAppearances = STATUS_APPEARANCE;
@@ -3,10 +3,13 @@ export function getCurrentlyConfiguredHeaderWidth(id) {
3
3
  if (isBrowser()) {
4
4
  const cell = document.querySelector(`[data-header-id="${id}"]`);
5
5
  const resizeHandler = cell === null || cell === void 0 ? void 0 : cell.querySelector('[data-test-id="table__header-cell-resize-handle-moving-part"]');
6
- if (cell && resizeHandler) {
6
+ if (cell) {
7
7
  const { width } = cell.getBoundingClientRect();
8
- const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
9
- return width + offset;
8
+ if (resizeHandler) {
9
+ const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
10
+ return width + offset;
11
+ }
12
+ return width;
10
13
  }
11
14
  }
12
15
  return 0;
@@ -9,7 +9,7 @@ import { ColumnDefinition } from '../types';
9
9
  type BulkAction = Omit<NonNullable<ToolbarProps<Record<string, string>>['bulkActions']>[number], 'onClick'> & {
10
10
  onClick?(selectionState: RowSelectionState, resetRowSelection: (defaultState?: boolean) => void): void;
11
11
  };
12
- export type TableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = WithSupportProps<{
12
+ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = WithSupportProps<{
13
13
  /** Данные для отрисовки */
14
14
  data: TData[];
15
15
  /** Определение внешнего вида и функционала колонок */
@@ -68,25 +68,6 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
68
68
  enableFuzzySearch?: boolean;
69
69
  /** Максимальное кол-во строк на страницу @default 10 */
70
70
  pageSize?: number;
71
- /** Параметры отвечают за пагинацию в таблице <br>
72
- * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
73
- * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
74
- * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
75
- * <strong>onChange</strong>: Колбэк на изменение пагинации
76
- * */
77
- pagination?: {
78
- state?: PaginationState;
79
- options?: number[];
80
- optionsLabel?: string;
81
- onChange?(state: PaginationState): void;
82
- optionsRender?(value: string | number, idx: number): string | number;
83
- };
84
- /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
85
- * (e.g фильтры, сортировки, и т.д)
86
- * */
87
- autoResetPageIndex?: boolean;
88
- /** Кол-во страниц (используется для внешнего управления) */
89
- pageCount?: number;
90
71
  /** Колбэк клика по строке */
91
72
  onRowClick?: RowClickHandler<TData>;
92
73
  /** CSS-класс */
@@ -121,10 +102,7 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
121
102
  toolbarAfter?: ReactNode;
122
103
  /** Настройки экспорта в тулбаре */
123
104
  exportSettings?: ExportButtonProps<TData>['settings'];
124
- /** Отключение пагинации */
125
- suppressPagination?: boolean;
126
105
  manualSorting?: boolean;
127
- manualPagination?: boolean;
128
106
  manualFiltering?: boolean;
129
107
  /** Дополнительная функция используется для получения уникального идентификатора для любой заданной строки */
130
108
  getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string;
@@ -143,7 +121,43 @@ export type TableProps<TData extends object, TFilters extends FiltersState = Rec
143
121
  resize?: boolean;
144
122
  };
145
123
  }>;
146
- export type ServerTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = Omit<TableProps<TData, TFilters>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
124
+ export type InfiniteTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = BaseTableProps<TData, TFilters> & {
125
+ pagination?: never;
126
+ autoResetPageIndex?: never;
127
+ pageCount?: never;
128
+ /** Режим работы "бесконечной" загрузки */
129
+ infiniteLoading?: boolean;
130
+ /** Отключение пагинации */
131
+ suppressPagination?: never;
132
+ manualPagination?: never;
133
+ };
134
+ export type ClientTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = BaseTableProps<TData, TFilters> & {
135
+ /** Параметры отвечают за пагинацию в таблице <br>
136
+ * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
137
+ * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
138
+ * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
139
+ * <strong>onChange</strong>: Колбэк на изменение пагинации
140
+ * */
141
+ pagination?: {
142
+ state?: PaginationState;
143
+ options?: number[];
144
+ optionsLabel?: string;
145
+ onChange?(state: PaginationState): void;
146
+ optionsRender?(value: string | number, idx: number): string | number;
147
+ };
148
+ /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
149
+ * (e.g фильтры, сортировки, и т.д)
150
+ * */
151
+ autoResetPageIndex?: boolean;
152
+ /** Кол-во страниц (используется для внешнего управления) */
153
+ pageCount?: number;
154
+ /** Отключение пагинации */
155
+ suppressPagination?: boolean;
156
+ manualPagination?: boolean;
157
+ infiniteLoading?: never;
158
+ };
159
+ export type TableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = InfiniteTableProps<TData, TFilters> | ClientTableProps<TData, TFilters>;
160
+ export type ServerTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = Omit<ClientTableProps<TData, TFilters>, 'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'> & {
147
161
  /** Данные для отрисовки */
148
162
  items?: TData[];
149
163
  /**
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Table",
7
- "version": "0.29.0",
7
+ "version": "0.31.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -61,5 +61,5 @@
61
61
  "peerDependencies": {
62
62
  "@snack-uikit/locale": "*"
63
63
  },
64
- "gitHead": "59426d0dd21ec9b22909d8350ca1d988665b4215"
64
+ "gitHead": "a430ef0cef6ce5141115be672073fca746f2d7fa"
65
65
  }
@@ -74,6 +74,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
74
74
  pageSize = DEFAULT_PAGE_SIZE,
75
75
  pageCount,
76
76
  loading = false,
77
+ infiniteLoading = false,
77
78
  outline = false,
78
79
  moreActions,
79
80
  exportSettings,
@@ -87,7 +88,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
87
88
  toolbarAfter,
88
89
  suppressPagination = false,
89
90
  manualSorting = false,
90
- manualPagination = false,
91
+ manualPagination: manualPaginationProp = false,
91
92
  manualFiltering = false,
92
93
  autoResetPageIndex = false,
93
94
  scrollRef,
@@ -119,6 +120,8 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
119
120
  );
120
121
  const enableSelection = Boolean(rowSelectionProp?.enable);
121
122
 
123
+ const manualPagination = infiniteLoading || manualPaginationProp;
124
+
122
125
  const tableColumns: ColumnDefinition<TData>[] = useMemo(() => {
123
126
  let cols: ColumnDefinition<TData>[] = columnDefinitions;
124
127
  if (enableSelection && !expanding) {
@@ -256,9 +259,11 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
256
259
  const columnSizeVarsRef = useRef<Record<string, string>>();
257
260
  const headers = table.getFlatHeaders();
258
261
 
259
- const columnSizeVars = useMemo(() => {
262
+ const columnSizes = useMemo(() => {
260
263
  const originalColumnDefs = table._getColumnDefs();
261
- const colSizes: Record<string, string> = {};
264
+ const vars: Record<string, string> = {};
265
+ const realSizes: Record<string, number> = {};
266
+ const resizedColumnIndex = headers.findIndex(({ column }) => column.getIsResizing());
262
267
 
263
268
  for (let i = 0; i < headers.length; i++) {
264
269
  const header = headers[i];
@@ -267,8 +272,8 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
267
272
  const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
268
273
 
269
274
  if (header.id === 'snack_predefined_statusColumn' && !originalColDef?.header && !originalColDef?.enableSorting) {
270
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
271
- colSizes[flexKey] = '100%';
275
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
276
+ vars[flexKey] = '100%';
272
277
  } else {
273
278
  const originalColumnDefSize = originalColDef?.size;
274
279
  let initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
@@ -289,12 +294,9 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
289
294
  const currentSize = header.getSize();
290
295
  const colDefSize = header.column.columnDef.size;
291
296
 
292
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
293
-
294
- if (prevSize === '100%' && currentSize !== colDefSize) {
295
- const realSize = getCurrentlyConfiguredHeaderWidth(header.id);
296
- table.setColumnSizing(old => ({ ...old, [header.id]: realSize }));
297
-
297
+ if (currentSize !== colDefSize || (i < resizedColumnIndex && prevSize === '100%')) {
298
+ const realSize = prevSize === '100%' ? getCurrentlyConfiguredHeaderWidth(header.id) : currentSize;
299
+ realSizes[header.id] = realSize;
298
300
  size = `${realSize}px`;
299
301
  }
300
302
  }
@@ -303,12 +305,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
303
305
  saveStateToLocalStorage({ id: savedState.id, columnId: header.id, size });
304
306
  }
305
307
 
306
- colSizes[sizeKey] = size;
307
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
308
+ vars[sizeKey] = size;
309
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
308
310
  }
309
311
  }
310
312
 
311
- return colSizes;
313
+ return { vars, realSizes };
312
314
  /*
313
315
  effect must be called only on columnSizingInfo.isResizingColumn changes
314
316
  to reduce unnecessary recalculations
@@ -321,8 +323,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
321
323
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
322
324
 
323
325
  useEffect(() => {
324
- columnSizeVarsRef.current = columnSizeVars;
325
- }, [columnSizeVars]);
326
+ if (Object.keys(columnSizes.realSizes).length) {
327
+ table.setColumnSizing(old => ({ ...old, ...columnSizes.realSizes }));
328
+ }
329
+
330
+ columnSizeVarsRef.current = columnSizes.vars;
331
+ }, [columnSizes, table]);
326
332
 
327
333
  const tableRows = table.getRowModel().rows;
328
334
  const tableCenterRows = table.getCenterRows();
@@ -413,9 +419,9 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
413
419
 
414
420
  <div className={styles.scrollWrapper} data-outline={outline || undefined}>
415
421
  <Scroll size='s' className={styles.table} ref={scrollContainerRef}>
416
- <div className={styles.tableContent} style={columnSizeVars}>
422
+ <div className={styles.tableContent} style={columnSizes.vars}>
417
423
  <TableContext.Provider value={{ table }}>
418
- {loading ? (
424
+ {(!infiniteLoading || !data.length) && loading ? (
419
425
  <SkeletonContextProvider loading>
420
426
  <HeaderRow />
421
427
  {loadingTableRows.map(row => (
@@ -438,6 +444,14 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
438
444
  <BodyRow key={row.id} row={row} onRowClick={onRowClick} />
439
445
  ))}
440
446
 
447
+ {data.length > 0 && infiniteLoading && loading && (
448
+ <SkeletonContextProvider loading>
449
+ {loadingTableRows.slice(0, 3).map(row => (
450
+ <BodyRow key={row.id} row={row} />
451
+ ))}
452
+ </SkeletonContextProvider>
453
+ )}
454
+
441
455
  <TableEmptyState
442
456
  emptyStates={emptyStates}
443
457
  dataError={dataError}
@@ -452,7 +466,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
452
466
  </Scroll>
453
467
  </div>
454
468
 
455
- {!suppressPagination && (
469
+ {!infiniteLoading && !suppressPagination && (
456
470
  <TablePagination
457
471
  table={table}
458
472
  options={paginationProp?.options}
@@ -7,10 +7,15 @@ export function getCurrentlyConfiguredHeaderWidth(id: string): number {
7
7
  '[data-test-id="table__header-cell-resize-handle-moving-part"]',
8
8
  );
9
9
 
10
- if (cell && resizeHandler) {
10
+ if (cell) {
11
11
  const { width } = cell.getBoundingClientRect();
12
- const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
13
- return width + offset;
12
+
13
+ if (resizeHandler) {
14
+ const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
15
+ return width + offset;
16
+ }
17
+
18
+ return width;
14
19
  }
15
20
  }
16
21
 
@@ -20,10 +20,7 @@ type BulkAction = Omit<NonNullable<ToolbarProps<Record<string, string>>['bulkAct
20
20
  onClick?(selectionState: RowSelectionState, resetRowSelection: (defaultState?: boolean) => void): void;
21
21
  };
22
22
 
23
- export type TableProps<
24
- TData extends object,
25
- TFilters extends FiltersState = Record<string, unknown>,
26
- > = WithSupportProps<{
23
+ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = WithSupportProps<{
27
24
  /** Данные для отрисовки */
28
25
  data: TData[];
29
26
  /** Определение внешнего вида и функционала колонок */
@@ -86,28 +83,6 @@ export type TableProps<
86
83
  /** Максимальное кол-во строк на страницу @default 10 */
87
84
  pageSize?: number;
88
85
 
89
- /** Параметры отвечают за пагинацию в таблице <br>
90
- * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
91
- * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
92
- * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
93
- * <strong>onChange</strong>: Колбэк на изменение пагинации
94
- * */
95
- pagination?: {
96
- state?: PaginationState;
97
- options?: number[];
98
- optionsLabel?: string;
99
- onChange?(state: PaginationState): void;
100
- optionsRender?(value: string | number, idx: number): string | number;
101
- };
102
-
103
- /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
104
- * (e.g фильтры, сортировки, и т.д)
105
- * */
106
- autoResetPageIndex?: boolean;
107
-
108
- /** Кол-во страниц (используется для внешнего управления) */
109
- pageCount?: number;
110
-
111
86
  /** Колбэк клика по строке */
112
87
  onRowClick?: RowClickHandler<TData>;
113
88
  /** CSS-класс */
@@ -150,12 +125,8 @@ export type TableProps<
150
125
  /** Настройки экспорта в тулбаре */
151
126
  exportSettings?: ExportButtonProps<TData>['settings'];
152
127
 
153
- /** Отключение пагинации */
154
- suppressPagination?: boolean;
155
-
156
128
  // Вынести это на пропсы
157
129
  manualSorting?: boolean;
158
- manualPagination?: boolean;
159
130
  manualFiltering?: boolean;
160
131
 
161
132
  /** Дополнительная функция используется для получения уникального идентификатора для любой заданной строки */
@@ -178,8 +149,63 @@ export type TableProps<
178
149
  };
179
150
  }>;
180
151
 
152
+ export type InfiniteTableProps<
153
+ TData extends object,
154
+ TFilters extends FiltersState = Record<string, unknown>,
155
+ > = BaseTableProps<TData, TFilters> & {
156
+ pagination?: never;
157
+ autoResetPageIndex?: never;
158
+ pageCount?: never;
159
+
160
+ /** Режим работы "бесконечной" загрузки */
161
+ infiniteLoading?: boolean;
162
+
163
+ /** Отключение пагинации */
164
+ suppressPagination?: never;
165
+
166
+ manualPagination?: never;
167
+ };
168
+
169
+ export type ClientTableProps<
170
+ TData extends object,
171
+ TFilters extends FiltersState = Record<string, unknown>,
172
+ > = BaseTableProps<TData, TFilters> & {
173
+ /** Параметры отвечают за пагинацию в таблице <br>
174
+ * <strong>state</strong>: Состояние строки поиска, жестко устанавливаемое снаружи <br>
175
+ * <strong>options</strong>: Варианты в выпадающем селекторе для установки кол-ва строк на страницу <br>
176
+ * <strong>optionsLabel</strong>: Текст для селектора кол-ва строк на страницу @default 'Rows volume: ' <br>
177
+ * <strong>onChange</strong>: Колбэк на изменение пагинации
178
+ * */
179
+ pagination?: {
180
+ state?: PaginationState;
181
+ options?: number[];
182
+ optionsLabel?: string;
183
+ onChange?(state: PaginationState): void;
184
+ optionsRender?(value: string | number, idx: number): string | number;
185
+ };
186
+
187
+ /** Автоматический сброс пагинации к первой странице при изменении данных или состояния
188
+ * (e.g фильтры, сортировки, и т.д)
189
+ * */
190
+ autoResetPageIndex?: boolean;
191
+
192
+ /** Кол-во страниц (используется для внешнего управления) */
193
+ pageCount?: number;
194
+
195
+ /** Отключение пагинации */
196
+ suppressPagination?: boolean;
197
+
198
+ manualPagination?: boolean;
199
+
200
+ infiniteLoading?: never;
201
+ };
202
+
203
+ export type TableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> =
204
+ | InfiniteTableProps<TData, TFilters>
205
+ | ClientTableProps<TData, TFilters>;
206
+
181
207
  export type ServerTableProps<TData extends object, TFilters extends FiltersState = Record<string, unknown>> = Omit<
182
- TableProps<TData, TFilters>,
208
+ ClientTableProps<TData, TFilters>,
183
209
  'pageSize' | 'pageCount' | 'pagination' | 'search' | 'data'
184
210
  > & {
185
211
  /** Данные для отрисовки */
@@ -58,7 +58,7 @@
58
58
  left: 0;
59
59
  transform: translateX(-50%);
60
60
 
61
- width: calc(100% + styles-tokens-table.$dimension-4m);
61
+ width: calc(100% + #{styles-tokens-table.$dimension-4m});
62
62
  height: 100%;
63
63
  }
64
64
  }
@@ -86,7 +86,7 @@
86
86
  left: 50%;
87
87
  transform: translateX(-50%);
88
88
 
89
- width: calc(100% - styles-tokens-table.$space-table-head-separator-padding * 2);
89
+ width: calc(100% - #{styles-tokens-table.$space-table-head-separator-padding} * 2);
90
90
  height: styles-tokens-table.$border-width-table;
91
91
 
92
92
  background-color: styles-tokens-table.$sys-neutral-decor-default;
@@ -106,7 +106,7 @@
106
106
  &::after {
107
107
  left: 0;
108
108
  transform: none;
109
- width: calc(100% - styles-tokens-table.$space-table-head-separator-padding);
109
+ width: calc(100% - #{styles-tokens-table.$space-table-head-separator-padding});
110
110
  }
111
111
  }
112
112
 
@@ -136,7 +136,7 @@
136
136
  }
137
137
 
138
138
  .tableHeaderResizeIndicator {
139
- right: calc(styles-tokens-table.$dimension-1m / 2);
139
+ right: calc(#{styles-tokens-table.$dimension-1m} / 2);
140
140
  }
141
141
  }
142
142
  }
@@ -42,8 +42,8 @@ $snack-ui-table-row-background: var(--snack-ui-table-row-background);
42
42
  content: '';
43
43
 
44
44
  position: absolute;
45
- top: calc(0px - styles-tokens-table.$border-width-table);
46
- bottom: calc(0px - styles-tokens-table.$border-width-table);
45
+ top: calc(0px - #{styles-tokens-table.$border-width-table});
46
+ bottom: calc(0px - #{styles-tokens-table.$border-width-table});
47
47
 
48
48
  box-sizing: border-box;
49
49
  width: styles-tokens-table.$border-width-table;