• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / ui5-webcomponents-react / 9808117599

05 Jul 2024 12:13PM CUT coverage: 81.144% (+0.2%) from 80.965%
9808117599

Pull #6021

github

web-flow
Merge 9da11f45b into 160aa88a5
Pull Request #6021: refactor(AnalyticalTable): remove deprecated props & enums

2631 of 4021 branches covered (65.43%)

1 of 1 new or added line in 1 file covered. (100.0%)

5 existing lines in 1 file now uncovered.

4751 of 5855 relevant lines covered (81.14%)

65205.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.43
/packages/main/src/components/AnalyticalTable/index.tsx
1
'use client';
2

3
import { useVirtualizer } from '@tanstack/react-virtual';
4
import {
5
  debounce,
6
  deprecationNotice,
7
  enrichEventWithDetails,
8
  useI18nBundle,
9
  useIsomorphicId,
10
  useIsomorphicLayoutEffect,
11
  useIsRTL,
12
  useStylesheet,
13
  useSyncRef
14
} from '@ui5/webcomponents-react-base';
15
import { clsx } from 'clsx';
16
import type { CSSProperties, MutableRefObject } from 'react';
17
import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
18
import {
19
  useColumnOrder,
20
  useExpanded,
21
  useFilters,
22
  useGlobalFilter,
23
  useGroupBy,
24
  useResizeColumns,
25
  useRowSelect,
26
  useSortBy,
27
  useTable
28
} from 'react-table';
29
import {
30
  AnalyticalTableScaleWidthMode,
31
  AnalyticalTableSelectionBehavior,
32
  AnalyticalTableSelectionMode,
33
  AnalyticalTableSubComponentsBehavior,
34
  AnalyticalTableVisibleRowCountMode
35
} from '../../enums/index.js';
36
import {
37
  COLLAPSE_NODE,
38
  COLLAPSE_PRESS_SPACE,
39
  DESELECT_ALL,
40
  EXPAND_NODE,
41
  EXPAND_PRESS_SPACE,
42
  FILTERED,
43
  GROUPED,
44
  INVALID_TABLE,
45
  LIST_NO_DATA,
46
  NO_DATA_FILTERED,
47
  SELECT_ALL,
48
  SELECT_PRESS_SPACE,
49
  UNSELECT_PRESS_SPACE
50
} from '../../i18n/i18n-defaults.js';
51
import { Text } from '../../webComponents/Text/index.js';
52
import { FlexBox } from '../FlexBox/index.js';
53
import { classNames, styleData } from './AnalyticalTable.module.css.js';
54
import { ColumnHeaderContainer } from './ColumnHeader/ColumnHeaderContainer.js';
55
import { DefaultColumn } from './defaults/Column/index.js';
56
import { DefaultLoadingComponent } from './defaults/LoadingComponent/index.js';
57
import { TablePlaceholder } from './defaults/LoadingComponent/TablePlaceholder.js';
58
import { DefaultNoDataComponent } from './defaults/NoDataComponent/index.js';
59
import { useA11y } from './hooks/useA11y.js';
60
import { useAutoResize } from './hooks/useAutoResize.js';
61
import { useColumnDragAndDrop } from './hooks/useDragAndDrop.js';
62
import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths.js';
63
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation.js';
64
import { usePopIn } from './hooks/usePopIn.js';
65
import { useResizeColumnsConfig } from './hooks/useResizeColumnsConfig.js';
66
import { useRowHighlight } from './hooks/useRowHighlight.js';
67
import { useRowNavigationIndicators } from './hooks/useRowNavigationIndicator.js';
68
import { useRowSelectionColumn } from './hooks/useRowSelectionColumn.js';
69
import { useSelectionChangeCallback } from './hooks/useSelectionChangeCallback.js';
70
import { useSingleRowStateSelection } from './hooks/useSingleRowStateSelection.js';
71
import { useStyling } from './hooks/useStyling.js';
72
import { useTableScrollHandles } from './hooks/useTableScrollHandles.js';
73
import { useToggleRowExpand } from './hooks/useToggleRowExpand.js';
74
import { useVisibleColumnsWidth } from './hooks/useVisibleColumnsWidth.js';
75
import { VerticalScrollbar } from './scrollbars/VerticalScrollbar.js';
76
import { VirtualTableBody } from './TableBody/VirtualTableBody.js';
77
import { VirtualTableBodyContainer } from './TableBody/VirtualTableBodyContainer.js';
78
import { stateReducer } from './tableReducer/stateReducer.js';
79
import { TitleBar } from './TitleBar/index.js';
80
import type {
81
  AnalyticalTableColumnDefinition,
82
  AnalyticalTableDomRef,
83
  AnalyticalTablePropTypes,
84
  AnalyticalTableState,
85
  DivWithCustomScrollProp
86
} from './types/index.js';
87
import { getRowHeight, getSubRowsByString, tagNamesWhichShouldNotSelectARow } from './util/index.js';
88
import { VerticalResizer } from './VerticalResizer.js';
89

90
// When a sorted column is removed from the visible columns array (e.g. when "popped-in"), it doesn't clean up the sorted columns leading to an undefined `sortType`.
136✔
91
const sortTypesFallback = {
262✔
92
  undefined: () => undefined
93
};
94

136✔
95
const measureElement = (el: HTMLElement) => {
262✔
96
  return el.offsetHeight;
99,219✔
97
};
98

99
/**
100
 * The `AnalyticalTable` provides a set of convenient functions for responsive table design, including virtualization of rows and columns, infinite scrolling and customizable columns that will, unless otherwise defined, distribute the available space equally among themselves.
101
 * It also provides several possibilities for working with the data, including sorting, filtering, grouping and aggregation.
102
 */
136✔
103
const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTypes>((props, ref) => {
262✔
104
  const {
105
    alternateRowColor,
106
    adjustTableHeightOnPopIn,
107
    className,
108
    columnOrder,
109
    columns,
110
    data: rawData,
111
    extension,
112
    filterable,
113
    globalFilterValue,
114
    groupBy,
115
    groupable,
116
    header,
117
    headerRowHeight,
×
118
    highlightField = 'status',
44,508✔
119
    infiniteScroll,
×
120
    infiniteScrollThreshold = 20,
41,146✔
121
    isTreeTable,
122
    loading,
123
    markNavigatedRow,
×
124
    minRows = 5,
41,165✔
125
    noDataText,
126
    overscanCount,
×
127
    overscanCountHorizontal = 5,
44,921✔
128
    portalContainer,
129
    retainColumnWidth,
130
    reactTableOptions,
131
    renderRowSubComponent,
132
    rowHeight,
×
133
    scaleWidthMode = AnalyticalTableScaleWidthMode.Default,
44,478✔
134
    scaleXFactor,
135
    selectedRowIds,
×
136
    selectionBehavior = AnalyticalTableSelectionBehavior.Row,
43,697!
137
    selectionMode = AnalyticalTableSelectionMode.None,
29,704✔
138
    showOverlay,
139
    sortable = true,
44,177✔
140
    style,
×
141
    subComponentsBehavior = AnalyticalTableSubComponentsBehavior.Expandable,
43,298✔
142
    subRowsKey = 'subRows',
44,751✔
143
    tableHooks = [],
41,065✔
144
    tableInstance,
×
145
    visibleRowCountMode = AnalyticalTableVisibleRowCountMode.Fixed,
40,458✔
146
    visibleRows = 15,
40,276✔
147
    withNavigationHighlight,
148
    withRowHighlight,
149
    onColumnsReorder,
150
    onGroup,
151
    onLoadMore,
152
    onRowClick,
153
    onRowExpandChange,
154
    onRowSelect,
155
    onSort,
156
    onTableScroll,
157
    onAutoResize,
×
158
    LoadingComponent = DefaultLoadingComponent,
44,921✔
159
    NoDataComponent = DefaultNoDataComponent,
44,921✔
160
    additionalEmptyRowsCount = 0,
44,236✔
161
    alwaysShowSubComponent: _omit,
162
    ...rest
163
  } = props;
44,960✔
164

165
  useStylesheet(styleData, AnalyticalTable.displayName);
44,921✔
166

×
167
  useEffect(() => {
44,921✔
168
    if (props.alwaysShowSubComponent != undefined) {
2,917!
UNCOV
169
      deprecationNotice(
×
170
        'alwaysShowSubComponent',
171
        '`alwaysShowSubComponent` is deprecated. Please use `subComponentsBehavior` instead!'
172
      );
173
    }
174
  }, [props.alwaysShowSubComponent]);
175

176
  const alwaysShowSubComponent =
177
    subComponentsBehavior === AnalyticalTableSubComponentsBehavior.Visible ||
44,921✔
178
    subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight ||
×
179
    props.alwaysShowSubComponent;
180

181
  const uniqueId = useIsomorphicId();
44,921✔
182
  const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
44,921✔
183
  const titleBarId = useRef(`titlebar-${uniqueId}`).current;
44,921✔
184
  const invalidTableTextId = useRef(`invalidTableText-${uniqueId}`).current;
44,921✔
185

186
  const tableRef = useRef<DivWithCustomScrollProp>(null);
44,921✔
187
  const parentRef = useRef<DivWithCustomScrollProp>(null);
44,921✔
188
  const verticalScrollBarRef = useRef<DivWithCustomScrollProp>(null);
44,921✔
189

190
  const getSubRows = useCallback((row) => getSubRowsByString(subRowsKey, row) || [], [subRowsKey]);
678,082✔
191

192
  const invalidTableA11yText = i18nBundle.getText(INVALID_TABLE);
44,921✔
193
  const tableInstanceRef = useRef<Record<string, any>>(null);
44,921!
194
  const scrollContainerRef = useRef<HTMLDivElement>(null);
44,921✔
195

196
  tableInstanceRef.current = useTable(
44,921✔
197
    {
198
      columns,
199
      data: rawData,
200
      defaultColumn: DefaultColumn,
201
      getSubRows,
202
      stateReducer,
203
      disableFilters: !filterable,
204
      disableSortBy: !sortable,
205
      disableGroupBy: isTreeTable || (!alwaysShowSubComponent && renderRowSubComponent) ? true : !groupable,
159,814✔
206
      selectSubRows: false,
207
      sortTypes: sortTypesFallback,
208
      webComponentsReactProperties: {
209
        translatableTexts: {
210
          selectAllText: i18nBundle.getText(SELECT_ALL),
211
          deselectAllText: i18nBundle.getText(DESELECT_ALL),
212
          expandA11yText: i18nBundle.getText(EXPAND_PRESS_SPACE),
213
          collapseA11yText: i18nBundle.getText(COLLAPSE_PRESS_SPACE),
214
          selectA11yText: i18nBundle.getText(SELECT_PRESS_SPACE),
215
          unselectA11yText: i18nBundle.getText(UNSELECT_PRESS_SPACE),
216
          expandNodeA11yText: i18nBundle.getText(EXPAND_NODE),
217
          collapseNodeA11yText: i18nBundle.getText(COLLAPSE_NODE),
218
          filteredA11yText: i18nBundle.getText(FILTERED),
219
          groupedA11yText: i18nBundle.getText(GROUPED)
220
        },
221
        tagNamesWhichShouldNotSelectARow,
222
        tableRef,
223
        selectionMode,
224
        selectionBehavior,
225
        classes: classNames,
226
        onAutoResize,
227
        onRowSelect: onRowSelect,
228
        onRowClick,
229
        onRowExpandChange,
230
        isTreeTable,
231
        alternateRowColor,
232
        scaleWidthMode,
233
        loading,
234
        withRowHighlight,
235
        highlightField,
236
        withNavigationHighlight,
237
        markNavigatedRow,
238
        renderRowSubComponent,
239
        alwaysShowSubComponent,
240
        showOverlay,
241
        uniqueId,
242
        subRowsKey,
243
        onColumnsReorder
244
      },
245
      ...reactTableOptions
246
    },
247
    useFilters,
248
    useGlobalFilter,
249
    useColumnOrder,
250
    useGroupBy,
251
    useSortBy,
252
    useExpanded,
253
    useRowSelect,
254
    useResizeColumns,
255
    useResizeColumnsConfig,
256
    useRowSelectionColumn,
257
    useAutoResize,
258
    useSingleRowStateSelection,
259
    useSelectionChangeCallback,
260
    useRowHighlight,
261
    useRowNavigationIndicators,
262
    useDynamicColumnWidths,
263
    useStyling,
264
    useToggleRowExpand,
265
    useA11y,
266
    usePopIn,
267
    useVisibleColumnsWidth,
268
    useKeyboardNavigation,
269
    useColumnDragAndDrop,
270
    ...tableHooks
271
  );
272

273
  const {
274
    getTableProps,
275
    headerGroups,
276
    rows,
277
    prepareRow,
278
    setColumnOrder,
279
    dispatch,
280
    totalColumnsWidth,
281
    visibleColumns,
×
282
    visibleColumnsWidth,
283
    setGroupBy,
284
    setGlobalFilter
285
  } = tableInstanceRef.current;
44,921✔
286

287
  const tableState: AnalyticalTableState = tableInstanceRef.current.state;
44,921✔
288
  const { triggerScroll } = tableState;
44,921✔
289

290
  const noDataTextI18n = i18nBundle.getText(LIST_NO_DATA);
44,921✔
291
  const noDataTextFiltered = i18nBundle.getText(NO_DATA_FILTERED);
44,921✔
292
  const noDataTextLocal =
×
293
    noDataText ?? (tableState.filters?.length > 0 || tableState.globalFilter ? noDataTextFiltered : noDataTextI18n);
44,921✔
294

295
  const [componentRef, updatedRef] = useSyncRef<AnalyticalTableDomRef>(ref);
44,921!
296
  //@ts-expect-error: types are compatible
297
  const isRtl = useIsRTL(updatedRef);
44,921✔
298

299
  const columnVirtualizer = useVirtualizer({
44,921!
300
    count: visibleColumnsWidth.length,
301
    getScrollElement: () => tableRef.current,
31,540✔
302
    estimateSize: useCallback((index) => visibleColumnsWidth[index], [visibleColumnsWidth]),
44,824✔
303
    horizontal: true,
304
    overscan: isRtl ? Infinity : overscanCountHorizontal,
44,921✔
305
    indexAttribute: 'data-column-index',
306
    // necessary as otherwise values are rounded which leads to wrong total width calculation leading to unnecessary scrollbar
307
    measureElement: !scaleXFactor || scaleXFactor === 1 ? (el) => el.getBoundingClientRect().width : undefined
35,726!
308
  });
×
309
  const [analyticalTableRef, scrollToRef] = useTableScrollHandles(updatedRef, dispatch);
44,921✔
310

311
  if (parentRef.current) {
44,921✔
312
    scrollToRef.current = {
29,382✔
313
      ...scrollToRef.current,
314
      horizontalScrollToOffset: columnVirtualizer.scrollToOffset,
315
      horizontalScrollToIndex: columnVirtualizer.scrollToIndex
316
    };
317
  }
×
318
  useEffect(() => {
44,921✔
319
    if (triggerScroll && triggerScroll.direction === 'horizontal') {
2,941✔
320
      if (triggerScroll.type === 'offset') {
12✔
321
        columnVirtualizer.scrollToOffset(...triggerScroll.args);
6✔
322
      } else {
323
        columnVirtualizer.scrollToIndex(...triggerScroll.args);
6!
324
      }
325
    }
326
  }, [triggerScroll]);
×
327

328
  const includeSubCompRowHeight =
329
    !!renderRowSubComponent &&
44,921✔
330
    (subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight ||
331
      subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeightExpandable) &&
332
    !!tableState.subComponentsHeight &&
333
    !!Object.keys(tableState.subComponentsHeight);
334

335
  if (tableInstance && {}.hasOwnProperty.call(tableInstance, 'current')) {
44,921✔
336
    (tableInstance as MutableRefObject<Record<string, any>>).current = tableInstanceRef.current;
2,107!
337
  }
×
338
  if (typeof tableInstance === 'function') {
44,921!
UNCOV
339
    tableInstance(tableInstanceRef.current);
×
340
  }
×
341

342
  const titleBarRef = useRef(null);
44,921!
343
  const extensionRef = useRef(null);
44,921✔
344
  const headerRef = useRef(null);
44,921✔
345

346
  const extensionsHeight =
44,921!
347
    (titleBarRef.current?.offsetHeight ?? 0) +
87,600✔
348
    (extensionRef.current?.offsetHeight ?? 0) +
89,842✔
349
    (headerRef.current?.offsetHeight ?? 0);
59,809!
350

351
  const internalRowHeight = getRowHeight(rowHeight, tableRef);
44,921✔
352
  const internalHeaderRowHeight = headerRowHeight ?? internalRowHeight;
44,921✔
353
  const popInRowHeight =
354
    tableState?.popInColumns?.length > 0
44,921!
355
      ? internalRowHeight + tableState.popInColumns.length * (internalRowHeight + 16)
356
      : internalRowHeight;
357

358
  const internalVisibleRowCount = tableState.visibleRows ?? visibleRows;
44,921✔
359

360
  const updateTableClientWidth = useCallback(() => {
44,921✔
361
    if (tableRef.current) {
8,727✔
362
      dispatch({
8,727✔
363
        type: 'TABLE_RESIZE',
×
364
        payload: {
365
          tableClientWidth:
366
            !scaleXFactor || scaleXFactor === 1
17,454!
367
              ? tableRef.current.getBoundingClientRect().width
368
              : tableRef.current.clientWidth
369
        }
370
      });
×
371
    }
372
  }, [tableRef.current, scaleXFactor]);
373

374
  const updateRowsCount = useCallback(() => {
44,921✔
375
    if (
14,907✔
376
      (visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto ||
×
377
        visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows) &&
378
      analyticalTableRef.current?.parentElement
379
    ) {
×
380
      const parentElement = analyticalTableRef.current?.parentElement;
1,152✔
381
      const tableYPosition =
382
        parentElement &&
1,152!
383
        getComputedStyle(parentElement).position === 'relative' &&
×
384
        analyticalTableRef.current?.offsetTop
385
          ? analyticalTableRef.current?.offsetTop
×
386
          : 0;
387
      const parentHeight = parentElement?.getBoundingClientRect().height;
1,152✔
388
      const tableHeight = parentHeight ? parentHeight - tableYPosition : 0;
1,152!
389
      const bodyHeight = tableHeight - extensionsHeight;
1,152✔
390
      let subCompsRowCount = 0;
1,152!
391
      if (includeSubCompRowHeight) {
1,152!
392
        let localBodyHeight = 0;
393
        let i = 0;
×
394
        while (localBodyHeight < bodyHeight) {
×
395
          if (tableState.subComponentsHeight[i]) {
×
396
            localBodyHeight += tableState.subComponentsHeight[i].subComponentHeight + popInRowHeight;
×
397
          } else if (rows[i]) {
×
398
            localBodyHeight += popInRowHeight;
399
          } else {
400
            break;
401
          }
UNCOV
402
          if (localBodyHeight >= bodyHeight) {
×
403
            break;
404
          }
405
          subCompsRowCount++;
406
          i++;
407
        }
408
        dispatch({
409
          type: 'VISIBLE_ROWS',
410
          payload: { visibleRows: Math.max(1, subCompsRowCount) }
411
        });
412
      } else {
413
        const rowCount = Math.max(1, Math.floor(bodyHeight / popInRowHeight));
1,152✔
414
        dispatch({
1,152✔
415
          type: 'VISIBLE_ROWS',
416
          payload: { visibleRows: rowCount }
417
        });
418
      }
419
    }
420
  }, [
421
    analyticalTableRef.current?.parentElement?.getBoundingClientRect().height,
422
    analyticalTableRef.current?.getBoundingClientRect().y,
423
    extensionsHeight,
424
    popInRowHeight,
425
    visibleRowCountMode,
426
    includeSubCompRowHeight,
427
    tableState.subComponentsHeight
428
  ]);
429

×
430
  useEffect(() => {
44,921✔
431
    setGlobalFilter(globalFilterValue);
3,561✔
432
  }, [globalFilterValue, setGlobalFilter]);
433

434
  useEffect(() => {
44,921✔
435
    const debouncedWidthObserverFn = debounce(updateTableClientWidth, 60);
12,111✔
436
    const tableWidthObserver = new ResizeObserver(debouncedWidthObserverFn);
12,111✔
437
    tableWidthObserver.observe(tableRef.current);
12,111✔
438

439
    const debouncedHeightObserverFn = debounce(updateRowsCount, 60);
12,111✔
440
    const parentHeightObserver = new ResizeObserver(debouncedHeightObserverFn);
12,111✔
441
    if (analyticalTableRef.current?.parentElement) {
12,111✔
442
      parentHeightObserver.observe(analyticalTableRef.current?.parentElement);
12,111✔
443
    }
444
    return () => {
12,111✔
445
      debouncedHeightObserverFn.cancel();
12,062✔
446
      debouncedWidthObserverFn.cancel();
12,062✔
447
      tableWidthObserver.disconnect();
12,062✔
448
      parentHeightObserver.disconnect();
12,062✔
449
    };
450
  }, [updateTableClientWidth, updateRowsCount]);
451

452
  useIsomorphicLayoutEffect(() => {
44,921✔
453
    dispatch({ type: 'IS_RTL', payload: { isRtl } });
3,038!
454
  }, [isRtl]);
455

456
  useIsomorphicLayoutEffect(() => {
44,921✔
457
    updateTableClientWidth();
5,834✔
458
  }, [updateTableClientWidth]);
459

460
  useIsomorphicLayoutEffect(() => {
44,921✔
461
    updateRowsCount();
12,111✔
462
  }, [updateRowsCount]);
×
463

464
  useEffect(() => {
44,921✔
465
    if (tableState.visibleRows !== undefined && visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Fixed) {
3,781!
466
      dispatch({
467
        type: 'VISIBLE_ROWS',
468
        payload: { visibleRows: undefined }
×
469
      });
470
    }
471
  }, [visibleRowCountMode, tableState.visibleRows]);
472

473
  useEffect(() => {
44,921✔
474
    if (groupBy) {
2,917!
475
      setGroupBy(groupBy);
×
476
    }
477
  }, [groupBy, setGroupBy]);
478

479
  useEffect(() => {
44,921✔
480
    if (selectedRowIds) {
2,959!
481
      dispatch({ type: 'SET_SELECTED_ROW_IDS', payload: { selectedRowIds } });
109✔
482
    }
483
  }, [selectedRowIds]);
484

×
485
  useEffect(() => {
44,921✔
486
    if (tableState?.interactiveRowsHavePopIn && (!tableState?.popInColumns || tableState?.popInColumns?.length === 0)) {
3,049!
UNCOV
487
      dispatch({ type: 'WITH_POPIN', payload: false });
×
488
    }
489
  }, [tableState?.interactiveRowsHavePopIn, tableState?.popInColumns?.length]);
490

491
  const tableBodyHeight = useMemo(() => {
44,921!
492
    if (typeof tableState.bodyHeight === 'number') {
6,232✔
493
      return tableState.bodyHeight;
7✔
494
    }
495
    let rowNum;
496
    if (visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows) {
6,225✔
497
      rowNum = internalVisibleRowCount;
576!
498
    } else {
499
      rowNum = rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount;
5,649✔
500
    }
×
501

502
    const rowHeight =
×
503
      visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto ||
6,225✔
504
      visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows ||
505
      tableState.interactiveRowsHavePopIn ||
506
      adjustTableHeightOnPopIn
507
        ? popInRowHeight
508
        : internalRowHeight;
509
    if (includeSubCompRowHeight) {
6,225✔
510
      let initialBodyHeightWithSubComps = 0;
164✔
511
      for (let i = 0; i < rowNum; i++) {
164✔
512
        if (tableState.subComponentsHeight[i]) {
656✔
513
          initialBodyHeightWithSubComps += tableState.subComponentsHeight[i].subComponentHeight + rowHeight;
312✔
514
        } else if (rows[i]) {
344✔
515
          initialBodyHeightWithSubComps += rowHeight;
344✔
516
        }
517
      }
518
      return initialBodyHeightWithSubComps;
164✔
519
    }
520
    return rowHeight * rowNum;
6,061✔
521
  }, [
522
    internalRowHeight,
523
    rows.length,
524
    internalVisibleRowCount,
525
    minRows,
526
    popInRowHeight,
×
527
    visibleRowCountMode,
×
528
    tableState.interactiveRowsHavePopIn,
529
    adjustTableHeightOnPopIn,
530
    includeSubCompRowHeight,
×
531
    tableState.subComponentsHeight,
532
    tableState.bodyHeight
533
  ]);
534

535
  // scroll bar detection
536
  useEffect(() => {
44,921✔
537
    const visibleRowCount =
538
      rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount;
5,827✔
539
    if (popInRowHeight !== internalRowHeight) {
5,827✔
540
      dispatch({
94✔
541
        type: 'TABLE_SCROLLING_ENABLED',
542
        payload: { isScrollable: visibleRowCount * popInRowHeight > tableBodyHeight || rows.length > visibleRowCount }
112✔
543
      });
544
    } else {
545
      dispatch({ type: 'TABLE_SCROLLING_ENABLED', payload: { isScrollable: rows.length > visibleRowCount } });
5,733✔
546
    }
×
547
  }, [rows.length, minRows, internalVisibleRowCount, popInRowHeight, tableBodyHeight]);
548

549
  const noDataStyles = {
44,921✔
550
    height: `${tableBodyHeight}px`,
551
    width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%'
44,921✔
552
  };
×
553

554
  const onGroupByChanged = useCallback(
44,921✔
555
    (e) => {
556
      const { column, isGrouped } = e.detail;
91✔
557
      let groupedColumns;
558
      if (isGrouped) {
91✔
559
        groupedColumns = [...tableState.groupBy, column.id];
77✔
560
      } else {
561
        groupedColumns = tableState.groupBy.filter((group) => group !== column.id);
14✔
562
      }
563
      setGroupBy(groupedColumns);
91✔
564
      if (typeof onGroup === 'function') {
91✔
565
        onGroup(
10!
566
          enrichEventWithDetails(e, {
567
            column,
568
            groupedColumns
569
          })
570
        );
571
      }
572
    },
573
    [tableState.groupBy, onGroup, setGroupBy]
574
  );
575

576
  useEffect(() => {
44,921✔
577
    if (columnOrder?.length > 0) {
2,917!
578
      setColumnOrder(columnOrder);
58✔
579
    }
580
  }, [columnOrder]);
581

×
582
  const inlineStyle = useMemo(() => {
44,921✔
583
    const tableStyles = {
8,704✔
584
      maxWidth: '100%',
585
      overflowX: 'auto',
×
586
      display: 'flex',
587
      flexDirection: 'column'
588
    };
589
    if (!!rowHeight) {
8,704✔
590
      tableStyles['--_ui5wcr-AnalyticalTableRowHeight'] = `${rowHeight}px`;
287✔
591
      tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${rowHeight}px`;
287✔
592
    }
593
    if (!!headerRowHeight) {
8,704✔
594
      tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${headerRowHeight}px`;
123✔
595
    }
596

597
    if (tableState.tableClientWidth > 0) {
8,704✔
598
      return {
5,787✔
599
        ...tableStyles,
×
600
        ...style
601
      } as CSSProperties;
602
    }
×
603
    return {
2,917✔
604
      ...tableStyles,
605
      ...style,
606
      visibility: 'hidden'
607
    } as CSSProperties;
608
  }, [tableState.tableClientWidth, style, rowHeight, headerRowHeight]);
×
609

610
  useEffect(() => {
44,921✔
611
    if (retainColumnWidth && tableState.columnResizing?.isResizingColumn && tableState.tableColResized == null) {
9,577!
612
      dispatch({ type: 'TABLE_COL_RESIZED', payload: true });
613
    }
×
614
    if (tableState.tableColResized && !retainColumnWidth) {
9,577!
UNCOV
615
      dispatch({ type: 'TABLE_COL_RESIZED', payload: undefined });
×
616
    }
617
  }, [tableState.columnResizing, retainColumnWidth, tableState.tableColResized]);
618

×
619
  const handleBodyScroll = (e) => {
44,921!
620
    if (typeof onTableScroll === 'function') {
673✔
621
      onTableScroll(e);
92✔
622
    }
623
    const targetScrollTop = e.currentTarget.scrollTop;
673✔
624

625
    if (verticalScrollBarRef.current) {
673✔
626
      const vertScrollbarScrollElement = verticalScrollBarRef.current.firstElementChild as HTMLDivElement;
673✔
627
      if (vertScrollbarScrollElement.offsetHeight !== scrollContainerRef.current?.offsetHeight) {
673✔
628
        vertScrollbarScrollElement.style.height = `${scrollContainerRef.current.offsetHeight}px`;
183✔
629
      }
×
630
      if (verticalScrollBarRef.current.scrollTop !== targetScrollTop) {
673✔
631
        if (!e.currentTarget.isExternalVerticalScroll) {
673✔
632
          verticalScrollBarRef.current.scrollTop = targetScrollTop;
639✔
633
          verticalScrollBarRef.current.isExternalVerticalScroll = true;
639✔
634
        }
635
        e.currentTarget.isExternalVerticalScroll = false;
673✔
636
      }
637
    }
638
  };
639

640
  const handleVerticalScrollBarScroll = useCallback((e) => {
44,921✔
641
    if (parentRef.current && !e.currentTarget.isExternalVerticalScroll) {
673✔
642
      parentRef.current.scrollTop = e.currentTarget.scrollTop;
34✔
643
      parentRef.current.isExternalVerticalScroll = true;
34✔
644
    }
645
    e.currentTarget.isExternalVerticalScroll = false;
673!
646
  }, []);
×
647

×
648
  useEffect(() => {
44,921✔
649
    columnVirtualizer.measure();
3,890✔
650
  }, [columnVirtualizer, tableState.columnOrder, tableState.columnResizing?.isResizingColumn, columns]);
651

652
  const totalSize = columnVirtualizer.getTotalSize();
44,921✔
653
  const showVerticalEndBorder = tableState.tableClientWidth > totalSize;
44,921✔
654

655
  const tableClasses = clsx(
44,921✔
656
    classNames.table,
657
    withNavigationHighlight && classNames.hasNavigationIndicator,
46,797✔
658
    showVerticalEndBorder && classNames.showVerticalEndBorder,
53,652✔
659
    className?.includes('ui5-content-native-scrollbars') && 'ui5-content-native-scrollbars'
44,921!
660
  );
×
661

×
662
  const handleOnLoadMore = (e) => {
44,921✔
663
    const rootNodes = rows.filter((row) => row.depth === 0);
17,939✔
664
    onLoadMore(
255✔
665
      enrichEventWithDetails(e, {
666
        rowCount: rootNodes.length,
667
        totalRowCount: rows.length
×
668
      })
×
669
    );
670
  };
671

672
  const overscan = overscanCount ? overscanCount : Math.floor(visibleRows / 2);
44,921✔
673
  const rHeight = popInRowHeight !== internalRowHeight ? popInRowHeight : internalRowHeight;
44,921✔
674

675
  const itemCount =
×
676
    Math.max(
44,921!
677
      minRows,
678
      rows.length,
679
      visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows ? internalVisibleRowCount : 0
44,921✔
680
    ) + (!tableState.isScrollable ? additionalEmptyRowsCount : 0);
44,921!
681

682
  const rowVirtualizer = useVirtualizer({
44,921✔
683
    count: itemCount,
684
    getScrollElement: () => parentRef.current,
31,540✔
685
    estimateSize: useCallback(
686
      (index) => {
687
        if (
157,509✔
688
          renderRowSubComponent &&
179,193✔
689
          (rows[index]?.isExpanded || alwaysShowSubComponent) &&
690
          tableState.subComponentsHeight?.[index]?.rowId === rows[index]?.id
691
        ) {
692
          return rHeight + (tableState.subComponentsHeight?.[index]?.subComponentHeight ?? 0);
824✔
693
        }
694
        return rHeight;
156,685✔
695
      },
696
      [rHeight, rows, renderRowSubComponent, alwaysShowSubComponent, tableState.subComponentsHeight]
697
    ),
698
    overscan,
699
    measureElement,
700
    indexAttribute: 'data-virtual-row-index'
701
  });
702
  // add range to instance for `useAutoResize` plugin hook
×
703
  tableInstanceRef.current.virtualRowsRange = rowVirtualizer.range;
44,921✔
704

705
  return (
44,921✔
706
    <>
707
      <div
×
708
        className={className}
709
        style={inlineStyle}
710
        //@ts-expect-error: types are compatible
711
        ref={componentRef}
712
        {...rest}
×
713
      >
714
        {header && (
48,066✔
715
          <TitleBar ref={titleBarRef} titleBarId={titleBarId}>
716
            {header}
717
          </TitleBar>
718
        )}
719
        {extension && <div ref={extensionRef}>{extension}</div>}
44,921!
720
        <FlexBox
721
          className={classNames.tableContainerWithScrollBar}
722
          data-component-name="AnalyticalTableContainerWithScrollbar"
723
        >
724
          {showOverlay && (
45,131✔
725
            <>
726
              <span id={invalidTableTextId} className={classNames.hiddenA11yText} aria-hidden>
727
                {invalidTableA11yText}
728
              </span>
729
              <div
×
730
                tabIndex={0}
731
                aria-labelledby={`${titleBarId} ${invalidTableTextId}`}
732
                role="region"
733
                data-component-name="AnalyticalTableOverlay"
734
                className={classNames.overlay}
735
              />
736
            </>
737
          )}
738
          <div
739
            aria-labelledby={titleBarId}
740
            {...getTableProps()}
741
            tabIndex={showOverlay ? -1 : 0}
44,921✔
742
            role="grid"
×
743
            aria-rowcount={rows.length}
744
            aria-colcount={visibleColumns.length}
745
            data-per-page={internalVisibleRowCount}
746
            data-component-name="AnalyticalTableContainer"
×
747
            ref={tableRef}
748
            className={tableClasses}
749
          >
750
            <div className={classNames.tableHeaderBackgroundElement} />
751
            <div className={classNames.tableBodyBackgroundElement} />
752
            {headerGroups.map((headerGroup) => {
753
              let headerProps: Record<string, unknown> = {};
44,633✔
754
              if (headerGroup.getHeaderGroupProps) {
44,633✔
755
                headerProps = headerGroup.getHeaderGroupProps();
44,633✔
756
              }
757
              return (
44,633✔
758
                tableRef.current && (
80,623✔
759
                  <ColumnHeaderContainer
760
                    ref={headerRef}
761
                    key={headerProps.key as string}
762
                    resizeInfo={tableState.columnResizing}
763
                    headerProps={headerProps}
764
                    headerGroup={headerGroup}
×
765
                    onSort={onSort}
×
766
                    onGroupByChanged={onGroupByChanged}
767
                    isRtl={isRtl}
768
                    portalContainer={portalContainer}
×
769
                    columnVirtualizer={columnVirtualizer}
770
                    uniqueId={uniqueId}
771
                    showVerticalEndBorder={showVerticalEndBorder}
772
                  />
773
                )
774
              );
775
            })}
×
776
            {loading && rows?.length > 0 && <LoadingComponent style={{ width: `${totalColumnsWidth}px` }} />}
45,820✔
777
            {loading && rows?.length === 0 && (
45,789✔
778
              <TablePlaceholder columns={visibleColumns} rows={minRows} style={noDataStyles} />
779
            )}
780
            {!loading && rows?.length === 0 && (
90,347✔
781
              <NoDataComponent
782
                noDataText={noDataTextLocal}
783
                className={classNames.noDataContainer}
784
                style={noDataStyles}
785
              />
786
            )}
787
            {rows?.length > 0 && tableRef.current && (
123,560✔
788
              <VirtualTableBodyContainer
789
                rowCollapsedFlag={tableState.rowCollapsed}
790
                dispatch={dispatch}
791
                tableBodyHeight={tableBodyHeight}
792
                totalColumnsWidth={columnVirtualizer.getTotalSize()}
793
                parentRef={parentRef}
794
                classes={classNames}
795
                infiniteScroll={infiniteScroll}
796
                infiniteScrollThreshold={infiniteScrollThreshold}
797
                onLoadMore={handleOnLoadMore}
798
                internalRowHeight={internalRowHeight}
799
                popInRowHeight={popInRowHeight}
800
                rows={rows}
801
                handleExternalScroll={handleBodyScroll}
802
                visibleRows={internalVisibleRowCount}
803
              >
804
                <VirtualTableBody
805
                  scrollContainerRef={scrollContainerRef}
806
                  classes={classNames}
807
                  prepareRow={prepareRow}
808
                  rows={rows}
809
                  scrollToRef={scrollToRef}
810
                  isTreeTable={isTreeTable}
811
                  internalRowHeight={internalRowHeight}
812
                  popInRowHeight={popInRowHeight}
813
                  alternateRowColor={alternateRowColor}
814
                  visibleColumns={visibleColumns}
815
                  renderRowSubComponent={renderRowSubComponent}
816
                  alwaysShowSubComponent={alwaysShowSubComponent}
817
                  markNavigatedRow={markNavigatedRow}
818
                  isRtl={isRtl}
×
819
                  subComponentsHeight={tableState.subComponentsHeight}
820
                  dispatch={dispatch}
821
                  columnVirtualizer={columnVirtualizer}
822
                  manualGroupBy={reactTableOptions?.manualGroupBy as boolean | undefined}
823
                  subRowsKey={subRowsKey}
824
                  subComponentsBehavior={subComponentsBehavior}
825
                  triggerScroll={tableState.triggerScroll}
826
                  rowVirtualizer={rowVirtualizer}
827
                />
828
              </VirtualTableBodyContainer>
829
            )}
830
          </div>
143,398✔
831
          {(additionalEmptyRowsCount || tableState.isScrollable === undefined || tableState.isScrollable) && (
832
            <VerticalScrollbar
×
833
              tableBodyHeight={tableBodyHeight}
834
              internalRowHeight={internalHeaderRowHeight}
835
              tableRef={tableRef}
836
              handleVerticalScrollBarScroll={handleVerticalScrollBarScroll}
837
              ref={verticalScrollBarRef}
838
              scrollContainerRef={scrollContainerRef}
839
              parentRef={parentRef}
840
              nativeScrollbar={className?.includes('ui5-content-native-scrollbars')}
841
            />
842
          )}
843
        </FlexBox>
844
        {visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Interactive && (
45,544✔
845
          <VerticalResizer
846
            popInRowHeight={popInRowHeight}
847
            hasPopInColumns={tableState?.popInColumns?.length > 0}
848
            analyticalTableRef={analyticalTableRef}
849
            dispatch={dispatch}
850
            extensionsHeight={extensionsHeight}
851
            internalRowHeight={internalRowHeight}
852
            portalContainer={portalContainer}
853
            rowsLength={rows.length}
854
            visibleRows={internalVisibleRowCount}
855
            handleOnLoadMore={handleOnLoadMore}
856
          />
857
        )}
858
      </div>
859
      <Text
860
        aria-hidden="true"
861
        id={`scaleModeHelper-${uniqueId}`}
862
        className={classNames.hiddenSmartColMeasure}
863
        data-component-name="AnalyticalTableScaleModeHelper"
864
      >
865
        {''}
866
      </Text>
867
      <Text
136✔
868
        aria-hidden="true"
869
        id={`scaleModeHelperHeader-${uniqueId}`}
870
        className={clsx(classNames.hiddenSmartColMeasure, classNames.hiddenSmartColMeasureHeader)}
871
        data-component-name="AnalyticalTableScaleModeHelperHeader"
872
      >
873
        {''}
874
      </Text>
875
    </>
876
  );
877
});
878

879
AnalyticalTable.displayName = 'AnalyticalTable';
262✔
880

881
export { AnalyticalTable };
882
export type {
883
  AnalyticalTableColumnDefinition,
884
  AnalyticalTableDomRef,
885
  AnalyticalTablePropTypes,
886
  DivWithCustomScrollProp
887
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc