@drivenets/design-system 0.18.0 → 0.19.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 +26 -0
- package/dist/index.cjs +15850 -10821
- package/dist/index.d.cts +785 -239
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +785 -239
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15835 -10822
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +13 -13
- package/src/components/ds-bot-button/ds-bot-button.figma.ts +38 -0
- package/src/components/ds-bot-button/ds-bot-button.module.scss +41 -0
- package/src/components/ds-bot-button/ds-bot-button.tsx +48 -0
- package/src/components/ds-bot-button/ds-bot-button.types.ts +33 -0
- package/src/components/ds-bot-button/index.ts +2 -0
- package/src/components/ds-bot-button/net-gen-logo.tsx +46 -0
- package/src/components/ds-bulk-actions/components/action-button.tsx +42 -0
- package/src/components/ds-bulk-actions/components/menu-entries.tsx +76 -0
- package/src/components/ds-bulk-actions/components/overflow.tsx +75 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.figma.ts +28 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.module.scss +199 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.tsx +220 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.types.ts +170 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.unit.test.ts +195 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.utils.ts +91 -0
- package/src/components/ds-bulk-actions/ds-bulk-actions.utils.unit.test.ts +134 -0
- package/src/components/ds-bulk-actions/hooks/compute-bulk-actions-overflow.ts +85 -0
- package/src/components/ds-bulk-actions/hooks/use-bulk-actions-overflow.ts +153 -0
- package/src/components/ds-bulk-actions/index.ts +18 -0
- package/src/components/ds-button-v3/ds-button-v3.module.scss +2 -2
- package/src/components/ds-catalog-layout/ds-catalog-layout.figma.ts +1 -1
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
- package/src/components/ds-empty-state/ds-empty-state.module.scss +16 -0
- package/src/components/ds-empty-state/ds-empty-state.tsx +45 -0
- package/src/components/ds-empty-state/ds-empty-state.types.ts +38 -0
- package/src/components/ds-empty-state/index.ts +3 -0
- package/src/components/ds-file-upload/ds-file-upload.tsx +4 -1
- package/src/components/ds-form-control/ds-form-control.tsx +18 -10
- package/src/components/ds-illustration/ds-illustration.figma.ts +34 -0
- package/src/components/ds-illustration/ds-illustration.module.scss +6 -0
- package/src/components/ds-illustration/ds-illustration.tsx +29 -0
- package/src/components/ds-illustration/ds-illustration.types.ts +45 -0
- package/src/components/ds-illustration/illustrations/configuration.tsx +51 -0
- package/src/components/ds-illustration/illustrations/device.tsx +133 -0
- package/src/components/ds-illustration/illustrations/done.tsx +107 -0
- package/src/components/{ds-catalog-layout/catalog-layout-empty-illustration.tsx → ds-illustration/illustrations/empty-inbox.tsx} +10 -14
- package/src/components/ds-illustration/illustrations/error.tsx +86 -0
- package/src/components/ds-illustration/illustrations/go-filter.tsx +51 -0
- package/src/components/ds-illustration/illustrations/illustration-clouds.tsx +10 -0
- package/src/components/ds-illustration/illustrations/index.ts +39 -0
- package/src/components/ds-illustration/illustrations/no-connection.tsx +54 -0
- package/src/components/ds-illustration/illustrations/no-credit-card.tsx +48 -0
- package/src/components/ds-illustration/illustrations/no-device.tsx +61 -0
- package/src/components/ds-illustration/illustrations/no-documents.tsx +52 -0
- package/src/components/ds-illustration/illustrations/no-gps.tsx +57 -0
- package/src/components/ds-illustration/illustrations/no-images.tsx +82 -0
- package/src/components/ds-illustration/illustrations/no-items-cart.tsx +86 -0
- package/src/components/ds-illustration/illustrations/no-messages.tsx +51 -0
- package/src/components/ds-illustration/illustrations/no-search-result.tsx +76 -0
- package/src/components/ds-illustration/illustrations/no-tasks.tsx +61 -0
- package/src/components/ds-illustration/illustrations/site.tsx +80 -0
- package/src/components/ds-illustration/index.ts +3 -0
- package/src/components/ds-main-menu/components/expanded-item/expanded-item.tsx +108 -0
- package/src/components/ds-main-menu/components/expanded-item/expanded-item.types.ts +8 -0
- package/src/components/ds-main-menu/components/expanded-item/index.ts +1 -0
- package/src/components/ds-main-menu/components/menu-graphic/index.ts +1 -0
- package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.tsx +16 -0
- package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.types.ts +7 -0
- package/src/components/ds-main-menu/components/tile-link/index.ts +1 -0
- package/src/components/ds-main-menu/components/tile-link/tile-link.tsx +85 -0
- package/src/components/ds-main-menu/components/tile-link/tile-link.types.ts +8 -0
- package/src/components/ds-main-menu/components/utility-link/index.ts +1 -0
- package/src/components/ds-main-menu/components/utility-link/utility-link.tsx +37 -0
- package/src/components/ds-main-menu/components/utility-link/utility-link.types.ts +7 -0
- package/src/components/ds-main-menu/ds-main-menu.figma.ts +17 -0
- package/src/components/ds-main-menu/ds-main-menu.module.scss +349 -0
- package/src/components/ds-main-menu/ds-main-menu.tsx +102 -0
- package/src/components/ds-main-menu/ds-main-menu.types.ts +86 -0
- package/src/components/ds-main-menu/ds-main-menu.utils.ts +40 -0
- package/src/components/ds-main-menu/index.ts +9 -0
- package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +1 -1
- package/src/components/ds-popover/ds-popover.tsx +2 -1
- package/src/components/ds-popover/ds-popover.types.ts +7 -0
- package/src/components/ds-site-node/ds-site-node.tsx +2 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +12 -2
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -3
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +1 -6
- package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +6 -3
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +18 -7
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +27 -21
- package/src/components/ds-table/components/ds-table-header-label.module.scss +12 -0
- package/src/components/ds-table/components/ds-table-header-label.tsx +28 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +0 -1
- package/src/components/ds-table/context/ds-table-context.tsx +8 -0
- package/src/components/ds-table/ds-table.module.scss +26 -16
- package/src/components/ds-table/ds-table.tsx +46 -14
- package/src/components/ds-table/ds-table.types.ts +33 -3
- package/src/components/ds-table/hooks/use-column-resize.ts +114 -11
- package/src/components/ds-table/hooks/use-scrollbar-spacer.ts +108 -0
- package/src/components/ds-table/hooks/use-scrollbar-spacer.unit.test.ts +26 -0
- package/src/components/ds-table/styles/_variables.scss +0 -2
- package/src/components/ds-table/utils/column-size.ts +274 -1
- package/src/components/ds-table/utils/column-size.unit.test.ts +157 -0
- package/src/components/ds-table/utils/constants.ts +13 -5
- package/src/components/ds-table/utils/table-columns.tsx +27 -13
- package/src/components/ds-table/utils/table-columns.unit.test.ts +44 -0
- package/src/components/ds-tag-filter/ds-tag-filter.module.scss +7 -35
- package/src/components/ds-tag-filter/ds-tag-filter.tsx +34 -57
- package/src/components/ds-tag-filter/ds-tag-filter.types.ts +2 -1
- package/src/components/ds-tag-filter/hooks/use-tag-overflow-calculation.ts +13 -18
- package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
- package/src/components/ds-toggle/ds-toggle.tsx +15 -13
- package/src/components/ds-tooltip/ds-tooltip.tsx +6 -2
- package/src/components/ds-tooltip/ds-tooltip.types.ts +17 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.figma.ts +35 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.module.scss +150 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.tsx +161 -0
- package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.types.ts +117 -0
- package/src/components/ds-top-bar-navigation/index.ts +2 -0
- package/src/index.ts +6 -0
- package/src/utils/use-is-horizontally-overflowing.ts +35 -0
- package/src/components/ds-site-node/hooks/use-is-overflowing.ts +0 -31
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +0 -112
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +0 -67
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +0 -41
- package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +0 -2
|
@@ -19,12 +19,19 @@ import {
|
|
|
19
19
|
collectMissingLeafSizing,
|
|
20
20
|
getColumnSizeCssVars,
|
|
21
21
|
getResizeOriginSize,
|
|
22
|
+
growFillLeavesToContainer,
|
|
23
|
+
isExplicitColumnWidth,
|
|
22
24
|
measureLeafHeaderWidths,
|
|
23
25
|
omitBuiltinColumnSizing,
|
|
26
|
+
shiftColumnTrack,
|
|
27
|
+
withUtilityColumnSizing,
|
|
24
28
|
type ColumnSizeBoundsSource,
|
|
25
29
|
} from '../utils/column-size';
|
|
26
|
-
import { RESIZE_MIN_COLUMN_WIDTH } from '../utils/constants';
|
|
30
|
+
import { RESIZE_DIVIDER_WIDTH, RESIZE_MIN_COLUMN_WIDTH } from '../utils/constants';
|
|
27
31
|
import type { DsTableResizePhase } from '../context/ds-table-context';
|
|
32
|
+
import { useScrollbarSpacer, type ScrollbarSpacerWidth } from './use-scrollbar-spacer';
|
|
33
|
+
|
|
34
|
+
const TRACK_MATCH_PX = 3;
|
|
28
35
|
|
|
29
36
|
const INITIAL_COLUMN_SIZING_INFO: ColumnSizingInfoState = {
|
|
30
37
|
startOffset: null,
|
|
@@ -35,6 +42,14 @@ const INITIAL_COLUMN_SIZING_INFO: ColumnSizingInfoState = {
|
|
|
35
42
|
columnSizingStart: [],
|
|
36
43
|
};
|
|
37
44
|
|
|
45
|
+
const clampResizeOverlayOffset = (offset: number, tableWidth: number): number => {
|
|
46
|
+
if (tableWidth <= RESIZE_DIVIDER_WIDTH) {
|
|
47
|
+
return offset;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Math.min(offset, tableWidth - RESIZE_DIVIDER_WIDTH);
|
|
51
|
+
};
|
|
52
|
+
|
|
38
53
|
export interface UseColumnResizeOptions {
|
|
39
54
|
enabled: boolean;
|
|
40
55
|
/**
|
|
@@ -49,6 +64,15 @@ export interface UseColumnResizeOptions {
|
|
|
49
64
|
*/
|
|
50
65
|
columns: unknown;
|
|
51
66
|
columnVisibility: VisibilityState;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidates overflow measurement (row count). Read as a dependency only.
|
|
69
|
+
*/
|
|
70
|
+
overflowKey: number;
|
|
71
|
+
/**
|
|
72
|
+
* Authored widths for injected utility leaves. Overlaid last so they win
|
|
73
|
+
* over persist seed and live internal state.
|
|
74
|
+
*/
|
|
75
|
+
utilityColumnSizing?: ColumnSizingState;
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
export interface DsTableResizeContextSlice {
|
|
@@ -64,6 +88,10 @@ export interface DsTableResizeContextSlice {
|
|
|
64
88
|
* the persist callback, mirroring drag end.
|
|
65
89
|
*/
|
|
66
90
|
onResizeKeyboardNudge: (columnId: string, delta: number) => void;
|
|
91
|
+
/**
|
|
92
|
+
* Overflow-only **Scrollbar spacer** width (0 or rest scrollbar size).
|
|
93
|
+
*/
|
|
94
|
+
scrollbarSpacerWidth: ScrollbarSpacerWidth;
|
|
67
95
|
}
|
|
68
96
|
|
|
69
97
|
export interface UseColumnResizeActiveResize {
|
|
@@ -92,6 +120,7 @@ export interface UseColumnResizeResult {
|
|
|
92
120
|
columnSizingInfo: ColumnSizingInfoState;
|
|
93
121
|
};
|
|
94
122
|
bind: <TData>(table: Table<TData>) => UseColumnResizeBindResult;
|
|
123
|
+
scrollbarSpacerWidth: ScrollbarSpacerWidth;
|
|
95
124
|
}
|
|
96
125
|
|
|
97
126
|
/**
|
|
@@ -108,6 +137,8 @@ export const useColumnResize = ({
|
|
|
108
137
|
onColumnSizingChange,
|
|
109
138
|
columns,
|
|
110
139
|
columnVisibility,
|
|
140
|
+
overflowKey,
|
|
141
|
+
utilityColumnSizing,
|
|
111
142
|
}: UseColumnResizeOptions): UseColumnResizeResult => {
|
|
112
143
|
const [columnResizeState, setColumnResizeState] = useState<{
|
|
113
144
|
columnSizing: ColumnSizingState;
|
|
@@ -125,10 +156,11 @@ export const useColumnResize = ({
|
|
|
125
156
|
// drag (or seed measurement) of a persisted column still takes effect. Passed
|
|
126
157
|
// to TanStack and read for CSS vars / persist so restore, drag, and persist
|
|
127
158
|
// all see the same widths.
|
|
128
|
-
const columnSizing = useMemo<ColumnSizingState>(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
159
|
+
const columnSizing = useMemo<ColumnSizingState>(() => {
|
|
160
|
+
const merged = columnSizingProp ? { ...columnSizingProp, ...internalColumnSizing } : internalColumnSizing;
|
|
161
|
+
|
|
162
|
+
return withUtilityColumnSizing(merged, utilityColumnSizing ?? {});
|
|
163
|
+
}, [columnSizingProp, internalColumnSizing, utilityColumnSizing]);
|
|
132
164
|
const [resizeHover, setResizeHover] = useState<{
|
|
133
165
|
columnId: string;
|
|
134
166
|
offset: number;
|
|
@@ -136,6 +168,10 @@ export const useColumnResize = ({
|
|
|
136
168
|
const dragStartOffsetRef = useRef<number | null>(null);
|
|
137
169
|
const resizeOriginSizingRef = useRef<ColumnSizingState>({});
|
|
138
170
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
171
|
+
const scrollbarSpacerWidth = useScrollbarSpacer(containerRef, overflowKey);
|
|
172
|
+
const spacerWidthRef = useRef(scrollbarSpacerWidth);
|
|
173
|
+
spacerWidthRef.current = scrollbarSpacerWidth;
|
|
174
|
+
const prevSpacerWidthRef = useRef<ScrollbarSpacerWidth | null>(null);
|
|
139
175
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bind is generic; the ref only calls sizing APIs
|
|
140
176
|
const tableRef = useRef<Table<any> | null>(null);
|
|
141
177
|
|
|
@@ -235,6 +271,8 @@ export const useColumnResize = ({
|
|
|
235
271
|
);
|
|
236
272
|
|
|
237
273
|
useLayoutEffect(() => {
|
|
274
|
+
prevSpacerWidthRef.current = null;
|
|
275
|
+
|
|
238
276
|
if (!enabled) {
|
|
239
277
|
return;
|
|
240
278
|
}
|
|
@@ -258,6 +296,11 @@ export const useColumnResize = ({
|
|
|
258
296
|
const leaves = table.getVisibleLeafColumns();
|
|
259
297
|
const leafIds = leaves.map((column) => column.id);
|
|
260
298
|
const measured = measureLeafHeaderWidths(tableEl, leafIds);
|
|
299
|
+
const fillableIds = leaves
|
|
300
|
+
.filter((column) => !isExplicitColumnWidth(column, {}))
|
|
301
|
+
.map((column) => column.id);
|
|
302
|
+
const trackWidth = container.clientWidth - spacerWidthRef.current;
|
|
303
|
+
const missing = collectMissingLeafSizing(measured, columnSizingRef.current, trackWidth, fillableIds);
|
|
261
304
|
|
|
262
305
|
for (const column of leaves) {
|
|
263
306
|
const measuredWidth = measured[column.id];
|
|
@@ -265,17 +308,31 @@ export const useColumnResize = ({
|
|
|
265
308
|
continue;
|
|
266
309
|
}
|
|
267
310
|
|
|
268
|
-
resizeOriginSizingRef.current[column.id] = getResizeOriginSize(
|
|
311
|
+
resizeOriginSizingRef.current[column.id] = getResizeOriginSize(
|
|
312
|
+
column,
|
|
313
|
+
missing[column.id] ?? Math.round(measuredWidth),
|
|
314
|
+
);
|
|
269
315
|
}
|
|
270
316
|
|
|
271
|
-
const missing = collectMissingLeafSizing(measured, columnSizingRef.current);
|
|
272
317
|
if (Object.keys(missing).length > 0) {
|
|
318
|
+
const bounds = leafSizeBoundsRef.current;
|
|
319
|
+
const clamped = clampColumnSizing(missing, bounds);
|
|
320
|
+
const grown = growFillLeavesToContainer(
|
|
321
|
+
clamped,
|
|
322
|
+
columnSizingRef.current,
|
|
323
|
+
Object.keys(measured),
|
|
324
|
+
trackWidth,
|
|
325
|
+
fillableIds,
|
|
326
|
+
bounds,
|
|
327
|
+
);
|
|
273
328
|
handleColumnSizingChange((prev) => ({
|
|
274
|
-
...
|
|
329
|
+
...grown,
|
|
275
330
|
...prev,
|
|
276
331
|
}));
|
|
277
332
|
}
|
|
278
333
|
|
|
334
|
+
prevSpacerWidthRef.current = spacerWidthRef.current;
|
|
335
|
+
|
|
279
336
|
return true;
|
|
280
337
|
};
|
|
281
338
|
|
|
@@ -293,6 +350,48 @@ export const useColumnResize = ({
|
|
|
293
350
|
return () => observer.disconnect();
|
|
294
351
|
}, [enabled, columns, columnVisibility, handleColumnSizingChange]);
|
|
295
352
|
|
|
353
|
+
// After seed, a vertical scrollbar can appear or disappear (Infinite Scroll,
|
|
354
|
+
// filter, shorter viewport). The **Scrollbar spacer** then toggles 0↔10px.
|
|
355
|
+
// Shrink or grow the column track by that delta so leaves + spacer still
|
|
356
|
+
// fill the container — internally only, never via onColumnSizingChange.
|
|
357
|
+
// Skip when the leaves were not filling the previous track (already
|
|
358
|
+
// overflowing horizontally) so authored-wide columns are left alone.
|
|
359
|
+
useLayoutEffect(() => {
|
|
360
|
+
if (!enabled) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const prevSpacerWidth = prevSpacerWidthRef.current;
|
|
365
|
+
if (prevSpacerWidth === null || prevSpacerWidth === scrollbarSpacerWidth) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const container = containerRef.current;
|
|
370
|
+
const table = tableRef.current;
|
|
371
|
+
if (!container || !table) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const leafIds = table.getVisibleLeafColumns().map((column) => column.id);
|
|
376
|
+
let leafTotal = 0;
|
|
377
|
+
|
|
378
|
+
for (const id of leafIds) {
|
|
379
|
+
leafTotal += columnSizingRef.current[id] ?? 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const prevTrack = container.clientWidth - prevSpacerWidth;
|
|
383
|
+
prevSpacerWidthRef.current = scrollbarSpacerWidth;
|
|
384
|
+
|
|
385
|
+
if (Math.abs(leafTotal - prevTrack) > TRACK_MATCH_PX) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const delta = prevSpacerWidth - scrollbarSpacerWidth;
|
|
390
|
+
handleColumnSizingChange(
|
|
391
|
+
shiftColumnTrack(columnSizingRef.current, leafIds, delta, leafSizeBoundsRef.current),
|
|
392
|
+
);
|
|
393
|
+
}, [enabled, scrollbarSpacerWidth, handleColumnSizingChange]);
|
|
394
|
+
|
|
296
395
|
const tableOptions = useMemo<UseColumnResizeTableOptions>(
|
|
297
396
|
() => ({
|
|
298
397
|
enableColumnResizing: enabled,
|
|
@@ -319,7 +418,7 @@ export const useColumnResize = ({
|
|
|
319
418
|
const columnSizeVars: CSSProperties | undefined = resizeSizingReady
|
|
320
419
|
? {
|
|
321
420
|
...getColumnSizeCssVars(table.getFlatHeaders()),
|
|
322
|
-
width: table.getTotalSize(),
|
|
421
|
+
width: table.getTotalSize() + scrollbarSpacerWidth,
|
|
323
422
|
}
|
|
324
423
|
: undefined;
|
|
325
424
|
|
|
@@ -335,6 +434,7 @@ export const useColumnResize = ({
|
|
|
335
434
|
onResizeDragStart: handleResizeDragStart,
|
|
336
435
|
onResizeReset: handleResizeReset,
|
|
337
436
|
onResizeKeyboardNudge: handleResizeKeyboardNudge,
|
|
437
|
+
scrollbarSpacerWidth,
|
|
338
438
|
},
|
|
339
439
|
};
|
|
340
440
|
}
|
|
@@ -349,14 +449,15 @@ export const useColumnResize = ({
|
|
|
349
449
|
resizingHeader && startSize !== null
|
|
350
450
|
? resizingHeader.getSize() - startSize
|
|
351
451
|
: (columnSizingInfo.deltaOffset ?? 0);
|
|
452
|
+
const tableWidth = table.getTotalSize();
|
|
352
453
|
const activeResize: UseColumnResizeActiveResize | null =
|
|
353
454
|
isResizingColumn && dragStartOffsetRef.current !== null
|
|
354
455
|
? {
|
|
355
|
-
offset: dragStartOffsetRef.current + clampedResizeDelta,
|
|
456
|
+
offset: clampResizeOverlayOffset(dragStartOffsetRef.current + clampedResizeDelta, tableWidth),
|
|
356
457
|
phase: 'dragging',
|
|
357
458
|
}
|
|
358
459
|
: resizeHover
|
|
359
|
-
? { offset: resizeHover.offset, phase: 'hover' }
|
|
460
|
+
? { offset: clampResizeOverlayOffset(resizeHover.offset, tableWidth), phase: 'hover' }
|
|
360
461
|
: null;
|
|
361
462
|
|
|
362
463
|
return {
|
|
@@ -370,6 +471,7 @@ export const useColumnResize = ({
|
|
|
370
471
|
onResizeDragStart: handleResizeDragStart,
|
|
371
472
|
onResizeReset: handleResizeReset,
|
|
372
473
|
onResizeKeyboardNudge: handleResizeKeyboardNudge,
|
|
474
|
+
scrollbarSpacerWidth,
|
|
373
475
|
},
|
|
374
476
|
};
|
|
375
477
|
};
|
|
@@ -381,5 +483,6 @@ export const useColumnResize = ({
|
|
|
381
483
|
columnSizingInfo,
|
|
382
484
|
},
|
|
383
485
|
bind,
|
|
486
|
+
scrollbarSpacerWidth,
|
|
384
487
|
};
|
|
385
488
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useLayoutEffect, useState, type RefObject } from 'react';
|
|
2
|
+
import { SCROLLBAR_SPACER_WIDTH } from '../utils/constants';
|
|
3
|
+
|
|
4
|
+
export type ScrollbarSpacerWidth = 0 | typeof SCROLLBAR_SPACER_WIDTH;
|
|
5
|
+
|
|
6
|
+
const BODY_SCROLLBAR_COMPENSATION_CSS_VAR = '--ds-table-body-scrollbar-compensation';
|
|
7
|
+
|
|
8
|
+
const bodyOverflowsVertically = (body: HTMLElement): boolean => body.scrollHeight > body.clientHeight;
|
|
9
|
+
|
|
10
|
+
const verticalScrollbarOccupancy = (body: HTMLElement): number =>
|
|
11
|
+
Math.max(0, body.offsetWidth - body.clientWidth);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extra `padding-inline-end` on body rows so Fill columns match the header
|
|
15
|
+
* **Scrollbar spacer** when the vertical scrollbar occupies no layout (overlay).
|
|
16
|
+
* Classic scrollbars already inset `clientWidth`; do not add that occupancy twice.
|
|
17
|
+
*/
|
|
18
|
+
export const getBodyScrollbarCompensation = (
|
|
19
|
+
occupancyPx: number,
|
|
20
|
+
spacerWidth: ScrollbarSpacerWidth,
|
|
21
|
+
): number => Math.max(0, spacerWidth - occupancyPx);
|
|
22
|
+
|
|
23
|
+
const syncBodyScrollbarCompensation = (
|
|
24
|
+
container: HTMLElement,
|
|
25
|
+
body: HTMLElement | null,
|
|
26
|
+
spacerWidth: ScrollbarSpacerWidth,
|
|
27
|
+
): void => {
|
|
28
|
+
if (!body) {
|
|
29
|
+
container.style.removeProperty(BODY_SCROLLBAR_COMPENSATION_CSS_VAR);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
container.style.setProperty(
|
|
34
|
+
BODY_SCROLLBAR_COMPENSATION_CSS_VAR,
|
|
35
|
+
`${String(getBodyScrollbarCompensation(verticalScrollbarOccupancy(body), spacerWidth))}px`,
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Overflow-only **Scrollbar spacer** width: `SCROLLBAR_SPACER_WIDTH` while the
|
|
41
|
+
* body scroll container overflows vertically, otherwise 0.
|
|
42
|
+
*
|
|
43
|
+
* Observes container/tbody size and tbody children (rows added/removed). Does
|
|
44
|
+
* not watch `style` attributes — virtualized rows rewrite `transform` on scroll.
|
|
45
|
+
*/
|
|
46
|
+
export const useScrollbarSpacer = (
|
|
47
|
+
containerRef: RefObject<HTMLElement | null>,
|
|
48
|
+
overflowKey: number,
|
|
49
|
+
): ScrollbarSpacerWidth => {
|
|
50
|
+
const [width, setWidth] = useState<ScrollbarSpacerWidth>(0);
|
|
51
|
+
|
|
52
|
+
useLayoutEffect(() => {
|
|
53
|
+
const container = containerRef.current;
|
|
54
|
+
if (!container) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const read = () => {
|
|
59
|
+
const body = container.querySelector('tbody');
|
|
60
|
+
if (!(body instanceof HTMLElement)) {
|
|
61
|
+
setWidth(0);
|
|
62
|
+
syncBodyScrollbarCompensation(container, null, 0);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nextWidth = bodyOverflowsVertically(body) ? SCROLLBAR_SPACER_WIDTH : 0;
|
|
67
|
+
setWidth(nextWidth);
|
|
68
|
+
syncBodyScrollbarCompensation(container, body, nextWidth);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resizeObserver = new ResizeObserver(read);
|
|
72
|
+
const bodyChildObserver = new MutationObserver(read);
|
|
73
|
+
resizeObserver.observe(container);
|
|
74
|
+
|
|
75
|
+
let observedBody: HTMLElement | null = null;
|
|
76
|
+
|
|
77
|
+
const bindBody = () => {
|
|
78
|
+
const body = container.querySelector('tbody');
|
|
79
|
+
if (!(body instanceof HTMLElement) || body === observedBody) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
bodyChildObserver.disconnect();
|
|
84
|
+
observedBody = body;
|
|
85
|
+
resizeObserver.observe(body);
|
|
86
|
+
bodyChildObserver.observe(body, { childList: true });
|
|
87
|
+
read();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
bindBody();
|
|
91
|
+
read();
|
|
92
|
+
|
|
93
|
+
const table = container.querySelector('table');
|
|
94
|
+
const tableChildObserver = new MutationObserver(bindBody);
|
|
95
|
+
if (table) {
|
|
96
|
+
tableChildObserver.observe(table, { childList: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
resizeObserver.disconnect();
|
|
101
|
+
bodyChildObserver.disconnect();
|
|
102
|
+
tableChildObserver.disconnect();
|
|
103
|
+
container.style.removeProperty(BODY_SCROLLBAR_COMPENSATION_CSS_VAR);
|
|
104
|
+
};
|
|
105
|
+
}, [containerRef, overflowKey]);
|
|
106
|
+
|
|
107
|
+
return width;
|
|
108
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { SCROLLBAR_SPACER_WIDTH } from '../utils/constants';
|
|
3
|
+
import { getBodyScrollbarCompensation } from './use-scrollbar-spacer';
|
|
4
|
+
|
|
5
|
+
describe('getBodyScrollbarCompensation', () => {
|
|
6
|
+
it('reserves the full spacer when the scrollbar occupies no layout (overlay)', () => {
|
|
7
|
+
expect(getBodyScrollbarCompensation(0, SCROLLBAR_SPACER_WIDTH)).toBe(SCROLLBAR_SPACER_WIDTH);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('adds nothing when a classic scrollbar already occupies the spacer width', () => {
|
|
11
|
+
expect(getBodyScrollbarCompensation(SCROLLBAR_SPACER_WIDTH, SCROLLBAR_SPACER_WIDTH)).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('fills only the gap when a thinner scrollbar occupies part of the spacer', () => {
|
|
15
|
+
expect(getBodyScrollbarCompensation(8, SCROLLBAR_SPACER_WIDTH)).toBe(2);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('does not shrink the body when occupancy is wider than the spacer', () => {
|
|
19
|
+
expect(getBodyScrollbarCompensation(15, SCROLLBAR_SPACER_WIDTH)).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('adds nothing when the spacer is collapsed', () => {
|
|
23
|
+
expect(getBodyScrollbarCompensation(0, 0)).toBe(0);
|
|
24
|
+
expect(getBodyScrollbarCompensation(SCROLLBAR_SPACER_WIDTH, 0)).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
$border-radius: 4px;
|
|
2
2
|
$border-width: 1px;
|
|
3
3
|
$group-border-width: 2px;
|
|
4
|
-
$reorder-column-width: 60px;
|
|
5
4
|
$container-margin-vertical: 1rem;
|
|
6
5
|
$filter-input-gap: 0.5rem;
|
|
7
6
|
$filter-input-max-width: 24rem;
|
|
8
7
|
$header-sort-gap: 0.5rem;
|
|
9
|
-
$empty-state-height: 6rem;
|
|
10
8
|
$resize-handle-hit-width: 12px;
|
|
11
9
|
$resize-divider-width: 2px;
|
|
12
10
|
|
|
@@ -188,25 +188,257 @@ export const measureLeafHeaderWidths = (
|
|
|
188
188
|
return widths;
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
const CONTAINER_SIZE_SNAP_PX = 1;
|
|
192
|
+
|
|
193
|
+
const absorbSizingDrift = (sizing: ColumnSizingState, ids: string[], drift: number): void => {
|
|
194
|
+
for (const id of [...ids].reverse()) {
|
|
195
|
+
if (drift === 0) {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const current = sizing[id];
|
|
200
|
+
if (current === undefined) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const nextSize = Math.max(RESIZE_MIN_COLUMN_WIDTH, current - drift);
|
|
205
|
+
drift -= current - nextSize;
|
|
206
|
+
sizing[id] = nextSize;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const toIdSet = (ids: ReadonlySet<string> | readonly string[]): ReadonlySet<string> =>
|
|
211
|
+
ids instanceof Set ? ids : new Set(ids);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Grows `ids` so their combined increase equals `extra`. Shares follow each
|
|
215
|
+
* id's current width; leftover rounding is absorbed on the last id.
|
|
216
|
+
*/
|
|
217
|
+
const distributeExtraSpace = (
|
|
218
|
+
sizing: ColumnSizingState,
|
|
219
|
+
ids: string[],
|
|
220
|
+
extra: number,
|
|
221
|
+
maxById?: ReadonlyMap<string, number>,
|
|
222
|
+
): void => {
|
|
223
|
+
if (ids.length === 0 || extra <= 0) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let remaining = extra;
|
|
228
|
+
let open = ids.filter((id) => (sizing[id] ?? 0) < (maxById?.get(id) ?? DEFAULT_MAX_COLUMN_SIZE));
|
|
229
|
+
|
|
230
|
+
while (remaining > 0 && open.length > 0) {
|
|
231
|
+
let weightSum = 0;
|
|
232
|
+
|
|
233
|
+
for (const id of open) {
|
|
234
|
+
weightSum += sizing[id] ?? 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let assigned = 0;
|
|
238
|
+
const stillOpen: string[] = [];
|
|
239
|
+
|
|
240
|
+
for (const id of open) {
|
|
241
|
+
const current = sizing[id] ?? 0;
|
|
242
|
+
const max = maxById?.get(id) ?? DEFAULT_MAX_COLUMN_SIZE;
|
|
243
|
+
const room = max - current;
|
|
244
|
+
const rawShare = weightSum <= 0 ? remaining / open.length : remaining * (current / weightSum);
|
|
245
|
+
const share = Math.min(room, Math.round(rawShare));
|
|
246
|
+
sizing[id] = current + share;
|
|
247
|
+
assigned += share;
|
|
248
|
+
|
|
249
|
+
if (current + share < max) {
|
|
250
|
+
stillOpen.push(id);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let leftover = remaining - assigned;
|
|
255
|
+
|
|
256
|
+
for (const id of stillOpen) {
|
|
257
|
+
if (leftover <= 0) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const max = maxById?.get(id) ?? DEFAULT_MAX_COLUMN_SIZE;
|
|
262
|
+
const add = Math.min(leftover, max - (sizing[id] ?? 0));
|
|
263
|
+
sizing[id] = (sizing[id] ?? 0) + add;
|
|
264
|
+
leftover -= add;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (assigned === 0 && leftover === remaining) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
remaining = leftover;
|
|
272
|
+
open = stillOpen.filter((id) => (sizing[id] ?? 0) < (maxById?.get(id) ?? DEFAULT_MAX_COLUMN_SIZE));
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
191
276
|
/**
|
|
192
277
|
* Sizing entries for leaves that are not yet in `existing`. Does not overwrite
|
|
193
|
-
* user-resized (or previously snapshotted) ids.
|
|
278
|
+
* user-resized (or previously snapshotted) ids. Rounds each measurement, then
|
|
279
|
+
* shifts leftover pixels onto the last missing leaf so the visible total
|
|
280
|
+
* matches the measured width (or the container, when they differ by 1px).
|
|
281
|
+
* When the measured total is short of the container, leftover space is shared
|
|
282
|
+
* across missing fill leaves in `fillableIds`.
|
|
194
283
|
*/
|
|
195
284
|
export const collectMissingLeafSizing = (
|
|
196
285
|
measured: Record<string, number>,
|
|
197
286
|
existing: ColumnSizingState,
|
|
287
|
+
containerWidth?: number,
|
|
288
|
+
fillableIds?: ReadonlySet<string> | readonly string[],
|
|
198
289
|
): ColumnSizingState => {
|
|
199
290
|
const next: ColumnSizingState = {};
|
|
291
|
+
const missingIds: string[] = [];
|
|
200
292
|
|
|
201
293
|
for (const [id, width] of Object.entries(measured)) {
|
|
202
294
|
if (existing[id] === undefined) {
|
|
203
295
|
next[id] = Math.round(width);
|
|
296
|
+
missingIds.push(id);
|
|
204
297
|
}
|
|
205
298
|
}
|
|
206
299
|
|
|
300
|
+
if (missingIds.length === 0) {
|
|
301
|
+
return next;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let roundedMissingSum = 0;
|
|
305
|
+
let unroundedMissingSum = 0;
|
|
306
|
+
|
|
307
|
+
for (const id of missingIds) {
|
|
308
|
+
roundedMissingSum += next[id] ?? 0;
|
|
309
|
+
unroundedMissingSum += measured[id] ?? 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
absorbSizingDrift(next, missingIds, roundedMissingSum - Math.round(unroundedMissingSum));
|
|
313
|
+
|
|
314
|
+
if (containerWidth === undefined || !Number.isFinite(containerWidth) || containerWidth <= 0) {
|
|
315
|
+
return next;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let projected = 0;
|
|
319
|
+
|
|
320
|
+
for (const id of Object.keys(measured)) {
|
|
321
|
+
projected += existing[id] ?? next[id] ?? 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const delta = projected - containerWidth;
|
|
325
|
+
|
|
326
|
+
if (Math.abs(delta) <= CONTAINER_SIZE_SNAP_PX) {
|
|
327
|
+
absorbSizingDrift(next, missingIds, delta);
|
|
328
|
+
return next;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (delta < 0 && fillableIds) {
|
|
332
|
+
const fillable = toIdSet(fillableIds);
|
|
333
|
+
distributeExtraSpace(
|
|
334
|
+
next,
|
|
335
|
+
missingIds.filter((id) => fillable.has(id)),
|
|
336
|
+
-delta,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
207
340
|
return next;
|
|
208
341
|
};
|
|
209
342
|
|
|
343
|
+
const maxSizeOf = (id: string, boundsById: Map<string, ColumnSizeBoundsSource['columnDef']>): number =>
|
|
344
|
+
boundsById.get(id)?.maxSize ?? DEFAULT_MAX_COLUMN_SIZE;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* After clamp, grow fill leaves that still have room so the projected
|
|
348
|
+
* measured total matches the container. A leaf that hit `maxSize` must not
|
|
349
|
+
* leave a gap — leftover pixels go to siblings.
|
|
350
|
+
*/
|
|
351
|
+
export const growFillLeavesToContainer = (
|
|
352
|
+
sizing: ColumnSizingState,
|
|
353
|
+
existing: ColumnSizingState,
|
|
354
|
+
measuredIds: readonly string[],
|
|
355
|
+
containerWidth: number,
|
|
356
|
+
fillableIds: ReadonlySet<string> | readonly string[],
|
|
357
|
+
bounds: ReadonlyArray<ColumnSizeBoundsSource>,
|
|
358
|
+
): ColumnSizingState => {
|
|
359
|
+
if (!Number.isFinite(containerWidth) || containerWidth <= 0 || measuredIds.length === 0) {
|
|
360
|
+
return sizing;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
let projected = 0;
|
|
364
|
+
|
|
365
|
+
for (const id of measuredIds) {
|
|
366
|
+
projected += existing[id] ?? sizing[id] ?? 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const extra = containerWidth - projected;
|
|
370
|
+
if (extra <= 0) {
|
|
371
|
+
return sizing;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const fillable = toIdSet(fillableIds);
|
|
375
|
+
const growable = Object.keys(sizing).filter((id) => fillable.has(id));
|
|
376
|
+
if (growable.length === 0) {
|
|
377
|
+
return sizing;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const next = { ...sizing };
|
|
381
|
+
const boundsById = new Map(bounds.map((column) => [column.id, column.columnDef]));
|
|
382
|
+
distributeExtraSpace(next, growable, extra, new Map(growable.map((id) => [id, maxSizeOf(id, boundsById)])));
|
|
383
|
+
|
|
384
|
+
return next;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const minSizeOf = (id: string, boundsById: Map<string, ColumnSizeBoundsSource['columnDef']>): number =>
|
|
388
|
+
boundsById.get(id)?.minSize ?? RESIZE_MIN_COLUMN_WIDTH;
|
|
389
|
+
|
|
390
|
+
const shrinkColumnTrack = (
|
|
391
|
+
sizing: ColumnSizingState,
|
|
392
|
+
ids: readonly string[],
|
|
393
|
+
amount: number,
|
|
394
|
+
bounds: ReadonlyArray<ColumnSizeBoundsSource>,
|
|
395
|
+
): void => {
|
|
396
|
+
const boundsById = new Map(bounds.map((column) => [column.id, column.columnDef]));
|
|
397
|
+
let remaining = amount;
|
|
398
|
+
|
|
399
|
+
for (const id of [...ids].reverse()) {
|
|
400
|
+
if (remaining <= 0) {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const current = sizing[id];
|
|
405
|
+
if (current === undefined) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const nextSize = Math.max(minSizeOf(id, boundsById), current - remaining);
|
|
410
|
+
remaining -= current - nextSize;
|
|
411
|
+
sizing[id] = nextSize;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Grows or shrinks `ids` so the column track changes by `delta` (positive =
|
|
417
|
+
* grow). Used when the **Scrollbar spacer** toggles after seed; does not
|
|
418
|
+
* invent ids missing from `sizing`.
|
|
419
|
+
*/
|
|
420
|
+
export const shiftColumnTrack = (
|
|
421
|
+
sizing: ColumnSizingState,
|
|
422
|
+
ids: readonly string[],
|
|
423
|
+
delta: number,
|
|
424
|
+
bounds: ReadonlyArray<ColumnSizeBoundsSource>,
|
|
425
|
+
): ColumnSizingState => {
|
|
426
|
+
if (!Number.isFinite(delta) || delta === 0 || ids.length === 0) {
|
|
427
|
+
return sizing;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const next = { ...sizing };
|
|
431
|
+
|
|
432
|
+
if (delta > 0) {
|
|
433
|
+
const boundsById = new Map(bounds.map((column) => [column.id, column.columnDef]));
|
|
434
|
+
distributeExtraSpace(next, [...ids], delta, new Map(ids.map((id) => [id, maxSizeOf(id, boundsById)])));
|
|
435
|
+
} else {
|
|
436
|
+
shrinkColumnTrack(next, ids, -delta, bounds);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return clampColumnSizing(next, bounds);
|
|
440
|
+
};
|
|
441
|
+
|
|
210
442
|
/**
|
|
211
443
|
* Copy of `columnSizing` without injected utility leaves. Use at the public
|
|
212
444
|
* `onColumnSizingChange` boundary so consumers persist only their own columns.
|
|
@@ -222,3 +454,44 @@ export const omitBuiltinColumnSizing = (columnSizing: ColumnSizingState): Column
|
|
|
222
454
|
|
|
223
455
|
return next;
|
|
224
456
|
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Authored utility-column width, or `fallback` when the value is omitted,
|
|
460
|
+
* non-finite, or not positive.
|
|
461
|
+
*/
|
|
462
|
+
export const resolveUtilityColumnWidth = (width: number | undefined, fallback: number): number => {
|
|
463
|
+
if (width === undefined || !Number.isFinite(width) || width <= 0) {
|
|
464
|
+
return fallback;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return width;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Drops any utility ids from `columnSizing`, then applies `utilityColumnSizing`
|
|
472
|
+
* so those leaves always take the authored width.
|
|
473
|
+
*/
|
|
474
|
+
export const withUtilityColumnSizing = (
|
|
475
|
+
columnSizing: ColumnSizingState,
|
|
476
|
+
utilityColumnSizing: ColumnSizingState,
|
|
477
|
+
): ColumnSizingState => ({
|
|
478
|
+
...omitBuiltinColumnSizing(columnSizing),
|
|
479
|
+
...utilityColumnSizing,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Authored sizes for injected utility leaves, keyed by column id.
|
|
484
|
+
*/
|
|
485
|
+
export const getUtilityColumnSizing = (
|
|
486
|
+
columns: ReadonlyArray<{ id?: string; size?: number }>,
|
|
487
|
+
): ColumnSizingState => {
|
|
488
|
+
const next: ColumnSizingState = {};
|
|
489
|
+
|
|
490
|
+
for (const column of columns) {
|
|
491
|
+
if (column.id !== undefined && BUILTIN_COLUMN_IDS.has(column.id) && column.size !== undefined) {
|
|
492
|
+
next[column.id] = column.size;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return next;
|
|
497
|
+
};
|