@snack-uikit/table 0.28.7 → 0.30.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.30.0 (2025-02-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * **PDS-1487:** freeze columns size on resize ([f5f5694](https://github.com/cloud-ru-tech/snack-uikit/commit/f5f56948a5e0ba42f071a66e2de225465f67e980))
12
+
13
+
14
+
15
+
16
+
17
+ # 0.29.0 (2025-02-25)
18
+
19
+
20
+ ### Features
21
+
22
+ * **PDS-569:** table bulk rows multiselect with shift key ([e5802e3](https://github.com/cloud-ru-tech/snack-uikit/commit/e5802e390c0beeb0d4cae20235f3e82fccb83b24))
23
+
24
+
25
+
26
+
27
+
6
28
  ## 0.28.7 (2025-02-25)
7
29
 
8
30
  ### Only dependencies have been changed
@@ -130,12 +130,12 @@ function Table(_a) {
130
130
  }, [tableColumns]);
131
131
  const enableRowSelection = (0, react_1.useCallback)(row => {
132
132
  const parent = row.getParentRow();
133
- const isParentEnable = parent ? parent.getCanSelect() : true;
134
- let isCurrentRowSelectionEnable = true;
133
+ const isParentSelected = parent ? parent.getCanSelect() : true;
134
+ let isCurrentRowSelected = true;
135
135
  if ((rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable) !== undefined) {
136
- isCurrentRowSelectionEnable = typeof rowSelectionProp.enable === 'boolean' ? rowSelectionProp.enable : rowSelectionProp.enable(row);
136
+ isCurrentRowSelected = typeof rowSelectionProp.enable === 'boolean' ? rowSelectionProp.enable : rowSelectionProp.enable(row);
137
137
  }
138
- return isParentEnable && isCurrentRowSelectionEnable;
138
+ return isParentSelected && isCurrentRowSelected;
139
139
  }, [rowSelectionProp]);
140
140
  const table = (0, react_table_1.useReactTable)({
141
141
  data,
@@ -167,6 +167,7 @@ function Table(_a) {
167
167
  onGlobalFilterChange,
168
168
  getRowId,
169
169
  onRowSelectionChange,
170
+ enableGrouping: true,
170
171
  enableRowSelection,
171
172
  enableMultiRowSelection: rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow,
172
173
  enableFilters: true,
@@ -209,7 +210,7 @@ function Table(_a) {
209
210
  return (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, table.getState().rowSelection, table.resetRowSelection);
210
211
  }
211
212
  })) : undefined, [bulkActionsProp, enableSelection, table]);
212
- const handleOnCheck = (0, react_1.useCallback)(() => {
213
+ const handleOnToolbarCheck = (0, react_1.useCallback)(() => {
213
214
  if (!loading && !enableSelectPinned && table.getTopRows().length) {
214
215
  const centerRows = table.getCenterRows();
215
216
  const isSomeRowsSelected = table.getIsSomePageRowsSelected();
@@ -228,10 +229,17 @@ function Table(_a) {
228
229
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
229
230
  const columnSizeVarsRef = (0, react_1.useRef)();
230
231
  const headers = table.getFlatHeaders();
231
- const columnSizeVars = (0, react_1.useMemo)(() => {
232
+ const columnSizes = (0, react_1.useMemo)(() => {
232
233
  var _a;
233
234
  const originalColumnDefs = table._getColumnDefs();
234
- const colSizes = {};
235
+ const vars = {};
236
+ const realSizes = {};
237
+ const resizedColumnIndex = headers.findIndex(_ref => {
238
+ let {
239
+ column
240
+ } = _ref;
241
+ return column.getIsResizing();
242
+ });
235
243
  for (let i = 0; i < headers.length; i++) {
236
244
  const header = headers[i];
237
245
  const {
@@ -240,8 +248,8 @@ function Table(_a) {
240
248
  } = (0, utils_3.getColumnStyleVars)(header.id);
241
249
  const originalColDef = originalColumnDefs.find(col => (0, helperComponents_1.getColumnId)(header) === col.id);
242
250
  if (header.id === 'snack_predefined_statusColumn' && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.header) && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.enableSorting)) {
243
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
244
- colSizes[flexKey] = '100%';
251
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
252
+ vars[flexKey] = '100%';
245
253
  } else {
246
254
  const originalColumnDefSize = originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.size;
247
255
  let initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
@@ -260,12 +268,9 @@ function Table(_a) {
260
268
  if (header.column.getCanResize()) {
261
269
  const currentSize = header.getSize();
262
270
  const colDefSize = header.column.columnDef.size;
263
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
264
- if (prevSize === '100%' && currentSize !== colDefSize) {
265
- const realSize = (0, utils_3.getCurrentlyConfiguredHeaderWidth)(header.id);
266
- table.setColumnSizing(old => Object.assign(Object.assign({}, old), {
267
- [header.id]: realSize
268
- }));
271
+ if (currentSize !== colDefSize || i < resizedColumnIndex && prevSize === '100%') {
272
+ const realSize = prevSize === '100%' ? (0, utils_3.getCurrentlyConfiguredHeaderWidth)(header.id) : currentSize;
273
+ realSizes[header.id] = realSize;
269
274
  size = `${realSize}px`;
270
275
  }
271
276
  }
@@ -276,11 +281,14 @@ function Table(_a) {
276
281
  size
277
282
  });
278
283
  }
279
- colSizes[sizeKey] = size;
280
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
284
+ vars[sizeKey] = size;
285
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
281
286
  }
282
287
  }
283
- return colSizes;
288
+ return {
289
+ vars,
290
+ realSizes
291
+ };
284
292
  /*
285
293
  effect must be called only on columnSizingInfo.isResizingColumn changes
286
294
  to reduce unnecessary recalculations
@@ -290,8 +298,11 @@ function Table(_a) {
290
298
  // eslint-disable-next-line react-hooks/exhaustive-deps
291
299
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
292
300
  (0, react_1.useEffect)(() => {
293
- columnSizeVarsRef.current = columnSizeVars;
294
- }, [columnSizeVars]);
301
+ if (Object.keys(columnSizes.realSizes).length) {
302
+ table.setColumnSizing(old => Object.assign(Object.assign({}, old), columnSizes.realSizes));
303
+ }
304
+ columnSizeVarsRef.current = columnSizes.vars;
305
+ }, [columnSizes, table]);
295
306
  const tableRows = table.getRowModel().rows;
296
307
  const tableCenterRows = table.getCenterRows();
297
308
  const tableFilteredRows = table.getFilteredRowModel().rows;
@@ -349,7 +360,7 @@ function Table(_a) {
349
360
  selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single',
350
361
  checked: table.getIsAllPageRowsSelected(),
351
362
  indeterminate: table.getIsSomePageRowsSelected(),
352
- onCheck: enableSelection ? handleOnCheck : undefined,
363
+ onCheck: enableSelection ? handleOnToolbarCheck : undefined,
353
364
  outline: outline,
354
365
  after: toolbarAfter || exportSettings ? (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, {
355
366
  children: [toolbarAfter, exportSettings && (0, jsx_runtime_1.jsx)(helperComponents_1.ExportButton, {
@@ -373,7 +384,7 @@ function Table(_a) {
373
384
  ref: scrollContainerRef,
374
385
  children: [(0, jsx_runtime_1.jsx)("div", {
375
386
  className: styles_module_scss_1.default.tableContent,
376
- style: columnSizeVars,
387
+ style: columnSizes.vars,
377
388
  children: (0, jsx_runtime_1.jsx)(helperComponents_1.TableContext.Provider, {
378
389
  value: {
379
390
  table
@@ -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;
@@ -1,13 +1,5 @@
1
1
  "use strict";
2
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
3
  var __importDefault = void 0 && (void 0).__importDefault || function (mod) {
12
4
  return mod && mod.__esModule ? mod : {
13
5
  "default": mod
@@ -21,31 +13,32 @@ const jsx_runtime_1 = require("react/jsx-runtime");
21
13
  const toggles_1 = require("@snack-uikit/toggles");
22
14
  const constants_1 = require("../../../constants");
23
15
  const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
24
- function SelectionCell(_a) {
25
- var {
26
- isMulti,
27
- onChange
28
- } = _a,
29
- props = __rest(_a, ["isMulti", "onChange"]);
30
- const handleCellClick = e => {
31
- e.stopPropagation();
32
- onChange(props.checked);
16
+ function getRowsToToggle(rows, clickedRowId, previousClickedRowId) {
17
+ const rowsToToggle = [];
18
+ const processedRowsMap = {
19
+ [clickedRowId]: false,
20
+ [previousClickedRowId]: false
33
21
  };
34
- return (
35
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
36
- (0, jsx_runtime_1.jsx)("div", {
37
- onClick: handleCellClick,
38
- className: styles_module_scss_1.default.selectionCell,
39
- "data-test-id": constants_1.TEST_IDS.rowSelect,
40
- children: isMulti ? (0, jsx_runtime_1.jsx)(toggles_1.Checkbox, Object.assign({}, props, {
41
- size: 's'
42
- })) : (0, jsx_runtime_1.jsx)(toggles_1.Radio, Object.assign({}, props, {
43
- size: 's'
44
- }))
45
- })
46
- );
22
+ const engagedRows = [clickedRowId, previousClickedRowId];
23
+ for (const row of rows) {
24
+ if (engagedRows.includes(row.id)) {
25
+ if (previousClickedRowId === '') {
26
+ rowsToToggle.push(row);
27
+ break;
28
+ }
29
+ processedRowsMap[row.id] = true;
30
+ }
31
+ if ((processedRowsMap[clickedRowId] || processedRowsMap[previousClickedRowId]) && !row.getIsGrouped()) {
32
+ rowsToToggle.push(row);
33
+ }
34
+ if (processedRowsMap[clickedRowId] && processedRowsMap[previousClickedRowId]) {
35
+ break;
36
+ }
37
+ }
38
+ return rowsToToggle;
47
39
  }
48
40
  function getSelectionCellColumnDef(enableSelectPinned) {
41
+ let previousClickedRowId = '';
49
42
  return {
50
43
  id: 'selectionCell',
51
44
  pinned: constants_1.COLUMN_PIN_POSITION.Left,
@@ -64,11 +57,39 @@ function getSelectionCellColumnDef(enableSelectPinned) {
64
57
  enableMultiRowSelection
65
58
  } = table.options;
66
59
  const isMulti = typeof enableMultiRowSelection === 'boolean' ? enableMultiRowSelection : true;
67
- return (0, jsx_runtime_1.jsx)(SelectionCell, {
68
- isMulti: isMulti,
69
- checked: row.getIsSelected(),
70
- onChange: row.getToggleSelectedHandler()
71
- });
60
+ const checked = row.getIsSelected();
61
+ const handleCellClick = e => {
62
+ var _a, _b;
63
+ e.stopPropagation();
64
+ (_a = globalThis.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
65
+ if (e.shiftKey) {
66
+ const {
67
+ rows,
68
+ rowsById
69
+ } = table.getRowModel();
70
+ const rowsToToggle = getRowsToToggle(rows, row.id, rows.map(r => r.id).includes(previousClickedRowId) ? previousClickedRowId : '');
71
+ const isSelected = !((_b = rowsById[row.id]) === null || _b === void 0 ? void 0 : _b.getIsSelected()) || false;
72
+ rowsToToggle.forEach(row => row.toggleSelected(isSelected));
73
+ } else {
74
+ row.toggleSelected(!checked);
75
+ }
76
+ previousClickedRowId = row.id;
77
+ };
78
+ return (
79
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
80
+ (0, jsx_runtime_1.jsx)("div", {
81
+ onClick: handleCellClick,
82
+ className: styles_module_scss_1.default.selectionCell,
83
+ "data-test-id": constants_1.TEST_IDS.rowSelect,
84
+ children: isMulti ? (0, jsx_runtime_1.jsx)(toggles_1.Checkbox, {
85
+ size: 's',
86
+ checked: checked
87
+ }) : (0, jsx_runtime_1.jsx)(toggles_1.Radio, {
88
+ size: 's',
89
+ checked: checked
90
+ })
91
+ })
92
+ );
72
93
  },
73
94
  meta: {
74
95
  skipOnExport: true
@@ -65,13 +65,13 @@ export function Table(_a) {
65
65
  }, [tableColumns]);
66
66
  const enableRowSelection = useCallback((row) => {
67
67
  const parent = row.getParentRow();
68
- const isParentEnable = parent ? parent.getCanSelect() : true;
69
- let isCurrentRowSelectionEnable = true;
68
+ const isParentSelected = parent ? parent.getCanSelect() : true;
69
+ let isCurrentRowSelected = true;
70
70
  if ((rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.enable) !== undefined) {
71
- isCurrentRowSelectionEnable =
71
+ isCurrentRowSelected =
72
72
  typeof rowSelectionProp.enable === 'boolean' ? rowSelectionProp.enable : rowSelectionProp.enable(row);
73
73
  }
74
- return isParentEnable && isCurrentRowSelectionEnable;
74
+ return isParentSelected && isCurrentRowSelected;
75
75
  }, [rowSelectionProp]);
76
76
  const table = useReactTable({
77
77
  data,
@@ -98,6 +98,7 @@ export function Table(_a) {
98
98
  onGlobalFilterChange,
99
99
  getRowId,
100
100
  onRowSelectionChange,
101
+ enableGrouping: true,
101
102
  enableRowSelection,
102
103
  enableMultiRowSelection: rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow,
103
104
  enableFilters: true,
@@ -135,7 +136,7 @@ export function Table(_a) {
135
136
  const bulkActions = useMemo(() => enableSelection
136
137
  ? bulkActionsProp === null || bulkActionsProp === void 0 ? void 0 : bulkActionsProp.map(action => (Object.assign(Object.assign({}, action), { onClick: () => { var _a; return (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action, table.getState().rowSelection, table.resetRowSelection); } })))
137
138
  : undefined, [bulkActionsProp, enableSelection, table]);
138
- const handleOnCheck = useCallback(() => {
139
+ const handleOnToolbarCheck = useCallback(() => {
139
140
  if (!loading && !enableSelectPinned && table.getTopRows().length) {
140
141
  const centerRows = table.getCenterRows();
141
142
  const isSomeRowsSelected = table.getIsSomePageRowsSelected();
@@ -154,17 +155,19 @@ export function Table(_a) {
154
155
  }, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table, enableSelectPinned]);
155
156
  const columnSizeVarsRef = useRef();
156
157
  const headers = table.getFlatHeaders();
157
- const columnSizeVars = useMemo(() => {
158
+ const columnSizes = useMemo(() => {
158
159
  var _a;
159
160
  const originalColumnDefs = table._getColumnDefs();
160
- const colSizes = {};
161
+ const vars = {};
162
+ const realSizes = {};
163
+ const resizedColumnIndex = headers.findIndex(({ column }) => column.getIsResizing());
161
164
  for (let i = 0; i < headers.length; i++) {
162
165
  const header = headers[i];
163
166
  const { sizeKey, flexKey } = getColumnStyleVars(header.id);
164
167
  const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
165
168
  if (header.id === 'snack_predefined_statusColumn' && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.header) && !(originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.enableSorting)) {
166
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
167
- colSizes[flexKey] = '100%';
169
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
170
+ vars[flexKey] = '100%';
168
171
  }
169
172
  else {
170
173
  const originalColumnDefSize = originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.size;
@@ -181,21 +184,20 @@ export function Table(_a) {
181
184
  if (header.column.getCanResize()) {
182
185
  const currentSize = header.getSize();
183
186
  const colDefSize = header.column.columnDef.size;
184
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
185
- if (prevSize === '100%' && currentSize !== colDefSize) {
186
- const realSize = getCurrentlyConfiguredHeaderWidth(header.id);
187
- table.setColumnSizing(old => (Object.assign(Object.assign({}, old), { [header.id]: realSize })));
187
+ if (currentSize !== colDefSize || (i < resizedColumnIndex && prevSize === '100%')) {
188
+ const realSize = prevSize === '100%' ? getCurrentlyConfiguredHeaderWidth(header.id) : currentSize;
189
+ realSizes[header.id] = realSize;
188
190
  size = `${realSize}px`;
189
191
  }
190
192
  }
191
193
  if (isResizeSavedToStore) {
192
194
  saveStateToLocalStorage({ id: savedState.id, columnId: header.id, size });
193
195
  }
194
- colSizes[sizeKey] = size;
195
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
196
+ vars[sizeKey] = size;
197
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
196
198
  }
197
199
  }
198
- return colSizes;
200
+ return { vars, realSizes };
199
201
  /*
200
202
  effect must be called only on columnSizingInfo.isResizingColumn changes
201
203
  to reduce unnecessary recalculations
@@ -207,8 +209,11 @@ export function Table(_a) {
207
209
  // eslint-disable-next-line react-hooks/exhaustive-deps
208
210
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
209
211
  useEffect(() => {
210
- columnSizeVarsRef.current = columnSizeVars;
211
- }, [columnSizeVars]);
212
+ if (Object.keys(columnSizes.realSizes).length) {
213
+ table.setColumnSizing(old => (Object.assign(Object.assign({}, old), columnSizes.realSizes)));
214
+ }
215
+ columnSizeVarsRef.current = columnSizes.vars;
216
+ }, [columnSizes, table]);
212
217
  const tableRows = table.getRowModel().rows;
213
218
  const tableCenterRows = table.getCenterRows();
214
219
  const tableFilteredRows = table.getFilteredRowModel().rows;
@@ -244,7 +249,7 @@ export function Table(_a) {
244
249
  onChange: onGlobalFilterChange,
245
250
  loading: search === null || search === void 0 ? void 0 : search.loading,
246
251
  placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || t('searchPlaceholder'),
247
- }, 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 ? handleOnCheck : 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 }))] })) }));
252
+ }, 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: 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 }))] })) }));
248
253
  }
249
254
  Table.getStatusColumnDef = getStatusColumnDef;
250
255
  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;
@@ -1,29 +1,33 @@
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
1
  import { jsx as _jsx } from "react/jsx-runtime";
13
2
  import { Checkbox, Radio } from '@snack-uikit/toggles';
14
3
  import { COLUMN_PIN_POSITION, TEST_IDS } from '../../../constants';
15
4
  import styles from './styles.module.css';
16
- function SelectionCell(_a) {
17
- var { isMulti, onChange } = _a, props = __rest(_a, ["isMulti", "onChange"]);
18
- const handleCellClick = (e) => {
19
- e.stopPropagation();
20
- onChange(props.checked);
5
+ function getRowsToToggle(rows, clickedRowId, previousClickedRowId) {
6
+ const rowsToToggle = [];
7
+ const processedRowsMap = {
8
+ [clickedRowId]: false,
9
+ [previousClickedRowId]: false,
21
10
  };
22
- return (
23
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
24
- _jsx("div", { onClick: handleCellClick, className: styles.selectionCell, "data-test-id": TEST_IDS.rowSelect, children: isMulti ? _jsx(Checkbox, Object.assign({}, props, { size: 's' })) : _jsx(Radio, Object.assign({}, props, { size: 's' })) }));
11
+ const engagedRows = [clickedRowId, previousClickedRowId];
12
+ for (const row of rows) {
13
+ if (engagedRows.includes(row.id)) {
14
+ if (previousClickedRowId === '') {
15
+ rowsToToggle.push(row);
16
+ break;
17
+ }
18
+ processedRowsMap[row.id] = true;
19
+ }
20
+ if ((processedRowsMap[clickedRowId] || processedRowsMap[previousClickedRowId]) && !row.getIsGrouped()) {
21
+ rowsToToggle.push(row);
22
+ }
23
+ if (processedRowsMap[clickedRowId] && processedRowsMap[previousClickedRowId]) {
24
+ break;
25
+ }
26
+ }
27
+ return rowsToToggle;
25
28
  }
26
29
  export function getSelectionCellColumnDef(enableSelectPinned) {
30
+ let previousClickedRowId = '';
27
31
  return {
28
32
  id: 'selectionCell',
29
33
  pinned: COLUMN_PIN_POSITION.Left,
@@ -37,7 +41,25 @@ export function getSelectionCellColumnDef(enableSelectPinned) {
37
41
  return null;
38
42
  const { enableMultiRowSelection } = table.options;
39
43
  const isMulti = typeof enableMultiRowSelection === 'boolean' ? enableMultiRowSelection : true;
40
- return (_jsx(SelectionCell, { isMulti: isMulti, checked: row.getIsSelected(), onChange: row.getToggleSelectedHandler() }));
44
+ const checked = row.getIsSelected();
45
+ const handleCellClick = (e) => {
46
+ var _a, _b;
47
+ e.stopPropagation();
48
+ (_a = globalThis.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
49
+ if (e.shiftKey) {
50
+ const { rows, rowsById } = table.getRowModel();
51
+ const rowsToToggle = getRowsToToggle(rows, row.id, rows.map(r => r.id).includes(previousClickedRowId) ? previousClickedRowId : '');
52
+ const isSelected = !((_b = rowsById[row.id]) === null || _b === void 0 ? void 0 : _b.getIsSelected()) || false;
53
+ rowsToToggle.forEach(row => row.toggleSelected(isSelected));
54
+ }
55
+ else {
56
+ row.toggleSelected(!checked);
57
+ }
58
+ previousClickedRowId = row.id;
59
+ };
60
+ return (
61
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
62
+ _jsx("div", { onClick: handleCellClick, className: styles.selectionCell, "data-test-id": TEST_IDS.rowSelect, children: isMulti ? _jsx(Checkbox, { size: 's', checked: checked }) : _jsx(Radio, { size: 's', checked: checked }) }));
41
63
  },
42
64
  meta: {
43
65
  skipOnExport: true,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Table",
7
- "version": "0.28.7",
7
+ "version": "0.30.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": "ad5274413320bf7880ba3c2d1124610281d0de81"
64
+ "gitHead": "afb93f86dbab6039f4c267a9d0e85350b59053c5"
65
65
  }
@@ -144,13 +144,13 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
144
144
  const enableRowSelection = useCallback(
145
145
  (row: Row<TData>) => {
146
146
  const parent = row.getParentRow();
147
- const isParentEnable = parent ? parent.getCanSelect() : true;
148
- let isCurrentRowSelectionEnable = true;
147
+ const isParentSelected = parent ? parent.getCanSelect() : true;
148
+ let isCurrentRowSelected = true;
149
149
  if (rowSelectionProp?.enable !== undefined) {
150
- isCurrentRowSelectionEnable =
150
+ isCurrentRowSelected =
151
151
  typeof rowSelectionProp.enable === 'boolean' ? rowSelectionProp.enable : rowSelectionProp.enable(row);
152
152
  }
153
- return isParentEnable && isCurrentRowSelectionEnable;
153
+ return isParentSelected && isCurrentRowSelected;
154
154
  },
155
155
  [rowSelectionProp],
156
156
  );
@@ -182,6 +182,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
182
182
 
183
183
  getRowId,
184
184
  onRowSelectionChange,
185
+ enableGrouping: true,
185
186
  enableRowSelection,
186
187
  enableMultiRowSelection: rowSelectionProp?.multiRow,
187
188
  enableFilters: true,
@@ -231,7 +232,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
231
232
  [bulkActionsProp, enableSelection, table],
232
233
  );
233
234
 
234
- const handleOnCheck = useCallback(() => {
235
+ const handleOnToolbarCheck = useCallback(() => {
235
236
  if (!loading && !enableSelectPinned && table.getTopRows().length) {
236
237
  const centerRows = table.getCenterRows();
237
238
  const isSomeRowsSelected = table.getIsSomePageRowsSelected();
@@ -255,9 +256,11 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
255
256
  const columnSizeVarsRef = useRef<Record<string, string>>();
256
257
  const headers = table.getFlatHeaders();
257
258
 
258
- const columnSizeVars = useMemo(() => {
259
+ const columnSizes = useMemo(() => {
259
260
  const originalColumnDefs = table._getColumnDefs();
260
- const colSizes: Record<string, string> = {};
261
+ const vars: Record<string, string> = {};
262
+ const realSizes: Record<string, number> = {};
263
+ const resizedColumnIndex = headers.findIndex(({ column }) => column.getIsResizing());
261
264
 
262
265
  for (let i = 0; i < headers.length; i++) {
263
266
  const header = headers[i];
@@ -266,8 +269,8 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
266
269
  const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
267
270
 
268
271
  if (header.id === 'snack_predefined_statusColumn' && !originalColDef?.header && !originalColDef?.enableSorting) {
269
- colSizes[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
270
- colSizes[flexKey] = '100%';
272
+ vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
273
+ vars[flexKey] = '100%';
271
274
  } else {
272
275
  const originalColumnDefSize = originalColDef?.size;
273
276
  let initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
@@ -288,12 +291,9 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
288
291
  const currentSize = header.getSize();
289
292
  const colDefSize = header.column.columnDef.size;
290
293
 
291
- size = currentSize === colDefSize ? initSize : `${currentSize}px`;
292
-
293
- if (prevSize === '100%' && currentSize !== colDefSize) {
294
- const realSize = getCurrentlyConfiguredHeaderWidth(header.id);
295
- table.setColumnSizing(old => ({ ...old, [header.id]: realSize }));
296
-
294
+ if (currentSize !== colDefSize || (i < resizedColumnIndex && prevSize === '100%')) {
295
+ const realSize = prevSize === '100%' ? getCurrentlyConfiguredHeaderWidth(header.id) : currentSize;
296
+ realSizes[header.id] = realSize;
297
297
  size = `${realSize}px`;
298
298
  }
299
299
  }
@@ -302,12 +302,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
302
302
  saveStateToLocalStorage({ id: savedState.id, columnId: header.id, size });
303
303
  }
304
304
 
305
- colSizes[sizeKey] = size;
306
- colSizes[flexKey] = size === '100%' ? 'unset' : '0';
305
+ vars[sizeKey] = size;
306
+ vars[flexKey] = size === '100%' ? 'unset' : '0';
307
307
  }
308
308
  }
309
309
 
310
- return colSizes;
310
+ return { vars, realSizes };
311
311
  /*
312
312
  effect must be called only on columnSizingInfo.isResizingColumn changes
313
313
  to reduce unnecessary recalculations
@@ -320,8 +320,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
320
320
  }, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
321
321
 
322
322
  useEffect(() => {
323
- columnSizeVarsRef.current = columnSizeVars;
324
- }, [columnSizeVars]);
323
+ if (Object.keys(columnSizes.realSizes).length) {
324
+ table.setColumnSizing(old => ({ ...old, ...columnSizes.realSizes }));
325
+ }
326
+
327
+ columnSizeVarsRef.current = columnSizes.vars;
328
+ }, [columnSizes, table]);
325
329
 
326
330
  const tableRows = table.getRowModel().rows;
327
331
  const tableCenterRows = table.getCenterRows();
@@ -385,7 +389,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
385
389
  selectionMode={rowSelectionProp?.multiRow ? 'multiple' : 'single'}
386
390
  checked={table.getIsAllPageRowsSelected()}
387
391
  indeterminate={table.getIsSomePageRowsSelected()}
388
- onCheck={enableSelection ? handleOnCheck : undefined}
392
+ onCheck={enableSelection ? handleOnToolbarCheck : undefined}
389
393
  outline={outline}
390
394
  after={
391
395
  toolbarAfter || exportSettings ? (
@@ -412,7 +416,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
412
416
 
413
417
  <div className={styles.scrollWrapper} data-outline={outline || undefined}>
414
418
  <Scroll size='s' className={styles.table} ref={scrollContainerRef}>
415
- <div className={styles.tableContent} style={columnSizeVars}>
419
+ <div className={styles.tableContent} style={columnSizes.vars}>
416
420
  <TableContext.Provider value={{ table }}>
417
421
  {loading ? (
418
422
  <SkeletonContextProvider loading>
@@ -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
 
@@ -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
  }
@@ -1,3 +1,4 @@
1
+ import { Row } from '@tanstack/react-table';
1
2
  import { MouseEvent } from 'react';
2
3
 
3
4
  import { Checkbox, Radio } from '@snack-uikit/toggles';
@@ -6,28 +7,40 @@ import { COLUMN_PIN_POSITION, TEST_IDS } from '../../../constants';
6
7
  import { ColumnDefinition } from '../../../types';
7
8
  import styles from './styles.module.scss';
8
9
 
9
- type SelectionCellProps = {
10
- checked: boolean;
11
- onChange(checked: boolean): void;
12
- isMulti?: boolean;
13
- };
10
+ function getRowsToToggle<TData>(rows: Row<TData>[], clickedRowId: string, previousClickedRowId: string) {
11
+ const rowsToToggle: Row<TData>[] = [];
12
+ const processedRowsMap: Record<string, boolean> = {
13
+ [clickedRowId]: false,
14
+ [previousClickedRowId]: false,
15
+ };
14
16
 
15
- function SelectionCell({ isMulti, onChange, ...props }: SelectionCellProps) {
16
- const handleCellClick = (e: MouseEvent<HTMLDivElement>) => {
17
- e.stopPropagation();
17
+ const engagedRows = [clickedRowId, previousClickedRowId];
18
18
 
19
- onChange(props.checked);
20
- };
19
+ for (const row of rows) {
20
+ if (engagedRows.includes(row.id)) {
21
+ if (previousClickedRowId === '') {
22
+ rowsToToggle.push(row);
23
+ break;
24
+ }
25
+
26
+ processedRowsMap[row.id] = true;
27
+ }
21
28
 
22
- return (
23
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
24
- <div onClick={handleCellClick} className={styles.selectionCell} data-test-id={TEST_IDS.rowSelect}>
25
- {isMulti ? <Checkbox {...props} size='s' /> : <Radio {...props} size='s' />}
26
- </div>
27
- );
29
+ if ((processedRowsMap[clickedRowId] || processedRowsMap[previousClickedRowId]) && !row.getIsGrouped()) {
30
+ rowsToToggle.push(row);
31
+ }
32
+
33
+ if (processedRowsMap[clickedRowId] && processedRowsMap[previousClickedRowId]) {
34
+ break;
35
+ }
36
+ }
37
+
38
+ return rowsToToggle;
28
39
  }
29
40
 
30
41
  export function getSelectionCellColumnDef<TData>(enableSelectPinned: boolean): ColumnDefinition<TData> {
42
+ let previousClickedRowId = '';
43
+
31
44
  return {
32
45
  id: 'selectionCell',
33
46
  pinned: COLUMN_PIN_POSITION.Left,
@@ -44,8 +57,34 @@ export function getSelectionCellColumnDef<TData>(enableSelectPinned: boolean): C
44
57
 
45
58
  const isMulti = typeof enableMultiRowSelection === 'boolean' ? enableMultiRowSelection : true;
46
59
 
60
+ const checked = row.getIsSelected();
61
+
62
+ const handleCellClick = (e: MouseEvent<HTMLDivElement>) => {
63
+ e.stopPropagation();
64
+
65
+ globalThis.getSelection()?.removeAllRanges();
66
+
67
+ if (e.shiftKey) {
68
+ const { rows, rowsById } = table.getRowModel();
69
+ const rowsToToggle = getRowsToToggle(
70
+ rows,
71
+ row.id,
72
+ rows.map(r => r.id).includes(previousClickedRowId) ? previousClickedRowId : '',
73
+ );
74
+ const isSelected = !rowsById[row.id]?.getIsSelected() || false;
75
+ rowsToToggle.forEach(row => row.toggleSelected(isSelected));
76
+ } else {
77
+ row.toggleSelected(!checked);
78
+ }
79
+
80
+ previousClickedRowId = row.id;
81
+ };
82
+
47
83
  return (
48
- <SelectionCell isMulti={isMulti} checked={row.getIsSelected()} onChange={row.getToggleSelectedHandler()} />
84
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
85
+ <div onClick={handleCellClick} className={styles.selectionCell} data-test-id={TEST_IDS.rowSelect}>
86
+ {isMulti ? <Checkbox size='s' checked={checked} /> : <Radio size='s' checked={checked} />}
87
+ </div>
49
88
  );
50
89
  },
51
90
  meta: {
@@ -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;