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

SAP / ui5-webcomponents-react / 6852590071

13 Nov 2023 04:07PM CUT coverage: 87.805% (+0.06%) from 87.743%
6852590071

Pull #5230

github

web-flow
Merge 9a7a117e3 into 6710b1896
Pull Request #5230: fix(withWebcomponent): correctly propagate/set `key` for `slot` components

2802 of 3755 branches covered (0.0%)

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

17 existing lines in 2 files now uncovered.

5112 of 5822 relevant lines covered (87.8%)

31884.0 hits per line

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

87.5
/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
} from '@ui5/webcomponents-react-base';
13
import { clsx } from 'clsx';
14
import type { CSSProperties, MutableRefObject } from 'react';
15
import React, { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
16
import { createUseStyles } from 'react-jss';
17
import {
18
  useColumnOrder,
19
  useExpanded,
20
  useFilters,
21
  useGlobalFilter,
22
  useGroupBy,
23
  useResizeColumns,
24
  useRowSelect,
25
  useSortBy,
26
  useTable
27
} from 'react-table';
28
import {
29
  AnalyticalTableScaleWidthMode,
30
  AnalyticalTableSelectionBehavior,
31
  AnalyticalTableSelectionMode,
32
  AnalyticalTableSubComponentsBehavior,
33
  AnalyticalTableVisibleRowCountMode,
34
  GlobalStyleClasses
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
  SELECT_ALL,
46
  SELECT_PRESS_SPACE,
47
  UNSELECT_PRESS_SPACE
48
} from '../../i18n/i18n-defaults.js';
49
import { FlexBox } from '../FlexBox/index.js';
50
import { Text } from '../Text/index.js';
51
import styles from './AnayticalTable.jss.js';
52
import { ColumnHeaderContainer } from './ColumnHeader/ColumnHeaderContainer.js';
53
import { DefaultColumn } from './defaults/Column/index.js';
54
import { DefaultLoadingComponent } from './defaults/LoadingComponent/index.js';
55
import { TablePlaceholder } from './defaults/LoadingComponent/TablePlaceholder.js';
56
import { DefaultNoDataComponent } from './defaults/NoDataComponent/index.js';
57
import { useA11y } from './hooks/useA11y.js';
58
import { useColumnDragAndDrop } from './hooks/useDragAndDrop.js';
59
import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths.js';
60
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation.js';
61
import { usePopIn } from './hooks/usePopIn.js';
62
import { useResizeColumnsConfig } from './hooks/useResizeColumnsConfig.js';
63
import { useRowHighlight } from './hooks/useRowHighlight.js';
64
import { useRowNavigationIndicators } from './hooks/useRowNavigationIndicator.js';
65
import { useRowSelectionColumn } from './hooks/useRowSelectionColumn.js';
66
import { useSelectionChangeCallback } from './hooks/useSelectionChangeCallback.js';
67
import { useSingleRowStateSelection } from './hooks/useSingleRowStateSelection.js';
68
import { useStyling } from './hooks/useStyling.js';
69
import { useTableScrollHandles } from './hooks/useTableScrollHandles.js';
70
import { useToggleRowExpand } from './hooks/useToggleRowExpand.js';
71
import { useVisibleColumnsWidth } from './hooks/useVisibleColumnsWidth.js';
72
import { VerticalScrollbar } from './scrollbars/VerticalScrollbar.js';
73
import { VirtualTableBody } from './TableBody/VirtualTableBody.js';
74
import { VirtualTableBodyContainer } from './TableBody/VirtualTableBodyContainer.js';
75
import { stateReducer } from './tableReducer/stateReducer.js';
76
import { TitleBar } from './TitleBar/index.js';
77
import type {
78
  AnalyticalTableColumnDefinition,
79
  AnalyticalTableDomRef,
80
  AnalyticalTablePropTypes,
81
  AnalyticalTableState,
82
  DivWithCustomScrollProp
83
} from './types/index.js';
84
import { getRowHeight, getSubRowsByString, tagNamesWhichShouldNotSelectARow } from './util/index.js';
85
import { VerticalResizer } from './VerticalResizer.js';
86

87
// 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`.
88
const sortTypesFallback = {
411✔
89
  undefined: () => undefined
×
90
};
91

92
const useStyles = createUseStyles(styles, { name: 'AnalyticalTable' });
411✔
93
/**
94
 * 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.
95
 * It also provides several possibilities for working with the data, including sorting, filtering, grouping and aggregation.
96
 */
97
const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTypes>((props, ref) => {
411✔
98
  const {
99
    alternateRowColor,
100
    adjustTableHeightOnPopIn,
101
    className,
102
    columnOrder,
103
    columns,
104
    data: rawData,
105
    extension,
106
    filterable,
107
    globalFilterValue,
108
    groupBy,
109
    groupable,
110
    header,
111
    headerRowHeight,
112
    highlightField,
113
    infiniteScroll,
114
    infiniteScrollThreshold,
115
    isTreeTable,
116
    loading,
117
    markNavigatedRow,
118
    minRows,
119
    noDataText,
120
    overscanCount,
121
    overscanCountHorizontal,
122
    portalContainer,
123
    retainColumnWidth,
124
    reactTableOptions,
125
    renderRowSubComponent,
126
    rowHeight,
127
    scaleWidthMode,
128
    scaleXFactor,
129
    selectedRowIds,
130
    selectionBehavior,
131
    selectionMode,
132
    showOverlay,
133
    sortable,
134
    style,
135
    subComponentsBehavior,
136
    subRowsKey,
137
    tableHooks,
138
    tableInstance,
139
    visibleRowCountMode,
140
    visibleRows,
141
    withNavigationHighlight,
142
    withRowHighlight,
143
    onColumnsReorder,
144
    onGroup,
145
    onLoadMore,
146
    onRowClick,
147
    onRowExpandChange,
148
    onRowSelect,
149
    onSort,
150
    onTableScroll,
151
    LoadingComponent,
152
    NoDataComponent,
153
    alwaysShowSubComponent: _omit,
154
    ...rest
155
  } = props;
35,365✔
156

157
  useEffect(() => {
35,330✔
158
    if (props.alwaysShowSubComponent != undefined) {
1,861!
UNCOV
159
      deprecationNotice(
×
160
        'alwaysShowSubComponent',
161
        '`alwaysShowSubComponent` is deprecated. Please use `subComponentsBehavior` instead!'
162
      );
163
    }
164
  }, [props.alwaysShowSubComponent]);
165

166
  const alwaysShowSubComponent =
167
    subComponentsBehavior === AnalyticalTableSubComponentsBehavior.Visible ||
35,330✔
168
    subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight ||
169
    props.alwaysShowSubComponent;
170

171
  const uniqueId = useIsomorphicId();
35,330✔
172
  const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
35,330✔
173
  const titleBarId = useRef(`titlebar-${uniqueId}`).current;
35,330✔
174
  const invalidTableTextId = useRef(`invalidTableText-${uniqueId}`).current;
35,330✔
175

176
  const classes = useStyles();
35,330✔
177

178
  const [analyticalTableRef, scrollToRef] = useTableScrollHandles(ref);
35,330✔
179
  const tableRef = useRef<DivWithCustomScrollProp>(null);
35,330✔
180

181
  const isRtl = useIsRTL(analyticalTableRef);
35,330✔
182

183
  const getSubRows = useCallback((row) => getSubRowsByString(subRowsKey, row) || [], [subRowsKey]);
366,713✔
184

185
  const invalidTableA11yText = i18nBundle.getText(INVALID_TABLE);
35,330✔
186
  const tableInstanceRef = useRef<Record<string, any>>(null);
35,330✔
187
  const scrollContainerRef = useRef<HTMLDivElement>(null);
35,330✔
188
  tableInstanceRef.current = useTable(
35,330✔
189
    {
190
      columns,
191
      data: rawData,
192
      defaultColumn: DefaultColumn,
193
      getSubRows,
194
      stateReducer,
195
      disableFilters: !filterable,
196
      disableSortBy: !sortable,
197
      disableGroupBy: isTreeTable || (!alwaysShowSubComponent && renderRowSubComponent) ? true : !groupable,
126,593✔
198
      selectSubRows: false,
199
      sortTypes: sortTypesFallback,
200
      webComponentsReactProperties: {
201
        translatableTexts: {
202
          selectAllText: i18nBundle.getText(SELECT_ALL),
203
          deselectAllText: i18nBundle.getText(DESELECT_ALL),
204
          expandA11yText: i18nBundle.getText(EXPAND_PRESS_SPACE),
205
          collapseA11yText: i18nBundle.getText(COLLAPSE_PRESS_SPACE),
206
          selectA11yText: i18nBundle.getText(SELECT_PRESS_SPACE),
207
          unselectA11yText: i18nBundle.getText(UNSELECT_PRESS_SPACE),
208
          expandNodeA11yText: i18nBundle.getText(EXPAND_NODE),
209
          collapseNodeA11yText: i18nBundle.getText(COLLAPSE_NODE),
210
          filteredA11yText: i18nBundle.getText(FILTERED),
211
          groupedA11yText: i18nBundle.getText(GROUPED)
212
        },
213
        tagNamesWhichShouldNotSelectARow,
214
        tableRef,
215
        selectionMode,
216
        selectionBehavior,
217
        classes,
218
        onRowSelect: onRowSelect,
219
        onRowClick,
220
        onRowExpandChange,
221
        isTreeTable,
222
        alternateRowColor,
223
        scaleWidthMode,
224
        loading,
225
        withRowHighlight,
226
        highlightField,
227
        withNavigationHighlight,
228
        markNavigatedRow,
229
        renderRowSubComponent,
230
        alwaysShowSubComponent,
231
        scrollToRef,
232
        showOverlay,
233
        uniqueId,
234
        subRowsKey,
235
        onColumnsReorder
236
      },
237
      ...reactTableOptions
238
    },
239
    useFilters,
240
    useGlobalFilter,
241
    useColumnOrder,
242
    useGroupBy,
243
    useSortBy,
244
    useExpanded,
245
    useRowSelect,
246
    useResizeColumns,
247
    useResizeColumnsConfig,
248
    useRowSelectionColumn,
249
    useSingleRowStateSelection,
250
    useSelectionChangeCallback,
251
    useRowHighlight,
252
    useRowNavigationIndicators,
253
    useDynamicColumnWidths,
254
    useStyling,
255
    useToggleRowExpand,
256
    useA11y,
257
    usePopIn,
258
    useVisibleColumnsWidth,
259
    useKeyboardNavigation,
260
    useColumnDragAndDrop,
261
    ...tableHooks
262
  );
263

264
  const {
265
    getTableProps,
266
    headerGroups,
267
    rows,
268
    prepareRow,
269
    setColumnOrder,
270
    dispatch,
271
    totalColumnsWidth,
272
    visibleColumns,
273
    visibleColumnsWidth,
274
    setGroupBy,
275
    setGlobalFilter
276
  } = tableInstanceRef.current;
35,330✔
277
  const tableState: AnalyticalTableState = tableInstanceRef.current.state;
35,330✔
278

279
  const includeSubCompRowHeight =
280
    !!renderRowSubComponent &&
35,330✔
281
    subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight &&
282
    !!tableState.subComponentsHeight &&
283
    !!Object.keys(tableState.subComponentsHeight);
284

285
  if (tableInstance && {}.hasOwnProperty.call(tableInstance, 'current')) {
35,330✔
286
    (tableInstance as MutableRefObject<Record<string, any>>).current = tableInstanceRef.current;
1,855✔
287
  }
288
  if (typeof tableInstance === 'function') {
35,330!
UNCOV
289
    tableInstance(tableInstanceRef.current);
×
290
  }
291

292
  const titleBarRef = useRef(null);
35,330✔
293
  const extensionRef = useRef(null);
35,330✔
294
  const headerRef = useRef(null);
35,330✔
295

296
  const extensionsHeight =
35,330✔
297
    (titleBarRef.current?.offsetHeight ?? 0) +
68,250✔
298
    (extensionRef.current?.offsetHeight ?? 0) +
70,660✔
299
    (headerRef.current?.offsetHeight ?? 0);
44,888✔
300

301
  const internalRowHeight = getRowHeight(rowHeight, tableRef);
35,330✔
302
  const internalHeaderRowHeight = headerRowHeight ?? internalRowHeight;
35,330✔
303
  const popInRowHeight =
304
    tableState?.popInColumns?.length > 0
35,330✔
305
      ? internalRowHeight + tableState.popInColumns.length * (internalRowHeight + 16)
306
      : internalRowHeight;
307

308
  const internalVisibleRowCount = tableState.visibleRows ?? visibleRows;
35,330✔
309

310
  const updateTableClientWidth = useCallback(() => {
35,330✔
311
    if (tableRef.current) {
5,975✔
312
      dispatch({ type: 'TABLE_RESIZE', payload: { tableClientWidth: tableRef.current.clientWidth } });
5,975✔
313
    }
314
  }, [tableRef.current]);
315

316
  const updateRowsCount = useCallback(() => {
35,330✔
317
    if (visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto && analyticalTableRef.current?.parentElement) {
10,519✔
318
      const parentElement = analyticalTableRef.current?.parentElement;
546✔
319
      const tableYPosition =
320
        parentElement &&
546!
321
        getComputedStyle(parentElement).position === 'relative' &&
322
        analyticalTableRef.current?.offsetTop
323
          ? analyticalTableRef.current?.offsetTop
324
          : 0;
325
      const parentHeight = parentElement?.getBoundingClientRect().height;
546✔
326
      const tableHeight = parentHeight ? parentHeight - tableYPosition : 0;
546!
327
      const bodyHeight = tableHeight - extensionsHeight;
546✔
328
      let subCompsRowCount = 0;
546✔
329
      if (includeSubCompRowHeight) {
546!
330
        let localBodyHeight = 0;
×
331
        let i = 0;
×
332
        while (localBodyHeight < bodyHeight) {
×
333
          if (tableState.subComponentsHeight[i]) {
×
334
            localBodyHeight += tableState.subComponentsHeight[i].subComponentHeight + popInRowHeight;
×
335
          } else if (rows[i]) {
×
UNCOV
336
            localBodyHeight += popInRowHeight;
×
337
          } else {
UNCOV
338
            break;
×
339
          }
340
          if (localBodyHeight >= bodyHeight) {
×
UNCOV
341
            break;
×
342
          }
343
          subCompsRowCount++;
×
UNCOV
344
          i++;
×
345
        }
UNCOV
346
        dispatch({
×
347
          type: 'VISIBLE_ROWS',
348
          payload: { visibleRows: Math.max(1, subCompsRowCount) }
349
        });
350
      } else {
351
        const rowCount = Math.max(1, Math.floor(bodyHeight / popInRowHeight));
546✔
352
        dispatch({
546✔
353
          type: 'VISIBLE_ROWS',
354
          payload: { visibleRows: rowCount }
355
        });
356
      }
357
    }
358
  }, [
359
    analyticalTableRef.current?.parentElement?.getBoundingClientRect().height,
360
    analyticalTableRef.current?.getBoundingClientRect().y,
361
    extensionsHeight,
362
    popInRowHeight,
363
    visibleRowCountMode,
364
    includeSubCompRowHeight,
365
    tableState.subComponentsHeight
366
  ]);
367

368
  useEffect(() => {
35,330✔
369
    setGlobalFilter(globalFilterValue);
2,445✔
370
  }, [globalFilterValue, setGlobalFilter]);
371

372
  useEffect(() => {
35,330✔
373
    const debouncedWidthObserverFn = debounce(updateTableClientWidth, 60);
8,309✔
374
    const tableWidthObserver = new ResizeObserver(debouncedWidthObserverFn);
8,309✔
375
    tableWidthObserver.observe(tableRef.current);
8,309✔
376

377
    const debouncedHeightObserverFn = debounce(updateRowsCount, 60);
8,309✔
378
    const parentHeightObserver = new ResizeObserver(debouncedHeightObserverFn);
8,309✔
379
    if (analyticalTableRef.current?.parentElement) {
8,309✔
380
      parentHeightObserver.observe(analyticalTableRef.current?.parentElement);
8,309✔
381
    }
382
    return () => {
8,309✔
383
      debouncedHeightObserverFn.cancel();
8,266✔
384
      debouncedWidthObserverFn.cancel();
8,266✔
385
      tableWidthObserver.disconnect();
8,266✔
386
      parentHeightObserver.disconnect();
8,266✔
387
    };
388
  }, [updateTableClientWidth, updateRowsCount]);
389

390
  useIsomorphicLayoutEffect(() => {
35,330✔
391
    dispatch({ type: 'IS_RTL', payload: { isRtl } });
1,965✔
392
  }, [isRtl]);
393

394
  useIsomorphicLayoutEffect(() => {
35,330✔
395
    updateTableClientWidth();
3,722✔
396
  }, [updateTableClientWidth]);
397

398
  useIsomorphicLayoutEffect(() => {
35,330✔
399
    updateRowsCount();
8,309✔
400
  }, [updateRowsCount]);
401

402
  useEffect(() => {
35,330✔
403
    if (tableState.visibleRows !== undefined && visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Fixed) {
2,281!
UNCOV
404
      dispatch({
×
405
        type: 'VISIBLE_ROWS',
406
        payload: { visibleRows: undefined }
407
      });
408
    }
409
  }, [visibleRowCountMode, tableState.visibleRows]);
410

411
  useEffect(() => {
35,330✔
412
    setGroupBy(groupBy);
1,861✔
413
  }, [groupBy, setGroupBy]);
414

415
  useEffect(() => {
35,330✔
416
    dispatch({ type: 'SET_SELECTED_ROW_IDS', payload: { selectedRowIds } });
1,899✔
417
  }, [selectedRowIds]);
418

419
  useEffect(() => {
35,330✔
420
    if (tableState?.interactiveRowsHavePopIn && (!tableState?.popInColumns || tableState?.popInColumns?.length === 0)) {
1,993!
UNCOV
421
      dispatch({ type: 'WITH_POPIN', payload: false });
×
422
    }
423
  }, [tableState?.interactiveRowsHavePopIn, tableState?.popInColumns?.length]);
424

425
  const tableBodyHeight = useMemo(() => {
35,330✔
426
    if (typeof tableState.bodyHeight === 'number') {
4,070✔
427
      return tableState.bodyHeight;
3✔
428
    }
429
    const rowNum = rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount;
4,067✔
430

431
    const rowHeight =
432
      visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto ||
4,067✔
433
      tableState.interactiveRowsHavePopIn ||
434
      adjustTableHeightOnPopIn
435
        ? popInRowHeight
436
        : internalRowHeight;
437

438
    if (includeSubCompRowHeight) {
4,067✔
439
      let initialBodyHeightWithSubComps = 0;
64✔
440
      for (let i = 0; i < rowNum; i++) {
64✔
441
        if (tableState.subComponentsHeight[i]) {
192✔
442
          initialBodyHeightWithSubComps += tableState.subComponentsHeight[i].subComponentHeight + rowHeight;
96✔
443
        } else if (rows[i]) {
96✔
444
          initialBodyHeightWithSubComps += rowHeight;
96✔
445
        }
446
      }
447
      return initialBodyHeightWithSubComps;
64✔
448
    }
449
    return rowHeight * rowNum;
4,003✔
450
  }, [
451
    internalRowHeight,
452
    rows.length,
453
    internalVisibleRowCount,
454
    minRows,
455
    popInRowHeight,
456
    visibleRowCountMode,
457
    tableState.interactiveRowsHavePopIn,
458
    adjustTableHeightOnPopIn,
459
    includeSubCompRowHeight,
460
    tableState.subComponentsHeight,
461
    tableState.bodyHeight
462
  ]);
463

464
  // scroll bar detection
465
  useEffect(() => {
35,330✔
466
    const visibleRowCount =
467
      rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount;
3,824✔
468
    if (popInRowHeight !== internalRowHeight) {
3,824✔
469
      dispatch({
88✔
470
        type: 'TABLE_SCROLLING_ENABLED',
471
        payload: { isScrollable: visibleRowCount * popInRowHeight > tableBodyHeight || rows.length > visibleRowCount }
116✔
472
      });
473
    } else {
474
      dispatch({ type: 'TABLE_SCROLLING_ENABLED', payload: { isScrollable: rows.length > visibleRowCount } });
3,736✔
475
    }
476
  }, [rows.length, minRows, internalVisibleRowCount, popInRowHeight, tableBodyHeight]);
477

478
  const noDataStyles = {
35,330✔
479
    height: `${tableBodyHeight}px`,
480
    width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%'
35,330✔
481
  };
482

483
  const onGroupByChanged = useCallback(
35,330✔
484
    (e) => {
485
      const { column, isGrouped } = e.detail;
71✔
486
      let groupedColumns;
487
      if (isGrouped) {
71✔
488
        groupedColumns = [...tableState.groupBy, column.id];
61✔
489
      } else {
490
        groupedColumns = tableState.groupBy.filter((group) => group !== column.id);
10✔
491
      }
492
      setGroupBy(groupedColumns);
71✔
493
      onGroup(
71✔
494
        enrichEventWithDetails(e, {
495
          column,
496
          groupedColumns
497
        })
498
      );
499
    },
500
    [tableState.groupBy, onGroup, setGroupBy]
501
  );
502

503
  useEffect(() => {
35,330✔
504
    if (columnOrder?.length > 0) {
1,861✔
505
      setColumnOrder(columnOrder);
50✔
506
    }
507
  }, [columnOrder]);
508

509
  const inlineStyle = useMemo(() => {
35,330✔
510
    const tableStyles = {
5,395✔
511
      maxWidth: '100%',
512
      overflowX: 'auto',
513
      display: 'flex',
514
      flexDirection: 'column'
515
    };
516
    if (!!rowHeight) {
5,395✔
517
      tableStyles['--_ui5wcr-AnalyticalTableRowHeight'] = `${rowHeight}px`;
259✔
518
      tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${rowHeight}px`;
259✔
519
    }
520
    if (!!headerRowHeight) {
5,395✔
521
      tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${headerRowHeight}px`;
111✔
522
    }
523

524
    if (tableState.tableClientWidth > 0) {
5,395✔
525
      return {
3,534✔
526
        ...tableStyles,
527
        ...style
528
      } as CSSProperties;
529
    }
530
    return {
1,861✔
531
      ...tableStyles,
532
      ...style,
533
      visibility: 'hidden'
534
    } as CSSProperties;
535
  }, [tableState.tableClientWidth, style, rowHeight, headerRowHeight]);
536

537
  useEffect(() => {
35,330✔
538
    if (retainColumnWidth && tableState.columnResizing?.isResizingColumn && tableState.tableColResized == null) {
7,465!
UNCOV
539
      dispatch({ type: 'TABLE_COL_RESIZED', payload: true });
×
540
    }
541
    if (tableState.tableColResized && !retainColumnWidth) {
7,465!
UNCOV
542
      dispatch({ type: 'TABLE_COL_RESIZED', payload: undefined });
×
543
    }
544
  }, [tableState.columnResizing, retainColumnWidth, tableState.tableColResized]);
545

546
  const parentRef = useRef<DivWithCustomScrollProp>(null);
35,330✔
547
  const verticalScrollBarRef = useRef<DivWithCustomScrollProp>(null);
35,330✔
548

549
  const handleBodyScroll = (e) => {
35,330✔
550
    if (typeof onTableScroll === 'function') {
437✔
551
      onTableScroll(e);
82✔
552
    }
553
    if (verticalScrollBarRef.current && verticalScrollBarRef.current.scrollTop !== parentRef.current.scrollTop) {
437✔
554
      if (!parentRef.current.isExternalVerticalScroll) {
428✔
555
        verticalScrollBarRef.current.scrollTop = parentRef.current.scrollTop;
428✔
556
        verticalScrollBarRef.current.isExternalVerticalScroll = true;
428✔
557
      }
558
      parentRef.current.isExternalVerticalScroll = false;
428✔
559
    }
560
  };
561

562
  const handleVerticalScrollBarScroll = () => {
35,330✔
563
    if (parentRef.current && !verticalScrollBarRef.current.isExternalVerticalScroll) {
428!
564
      parentRef.current.scrollTop = verticalScrollBarRef.current.scrollTop;
×
UNCOV
565
      parentRef.current.isExternalVerticalScroll = true;
×
566
    }
567
    verticalScrollBarRef.current.isExternalVerticalScroll = false;
428✔
568
  };
569

570
  const columnVirtualizer = useVirtualizer({
35,330✔
571
    count: visibleColumnsWidth.length,
572
    getScrollElement: () => tableRef.current,
25,017✔
573
    estimateSize: useCallback((index) => visibleColumnsWidth[index], [visibleColumnsWidth]),
48,378✔
574
    horizontal: true,
575
    overscan: isRtl ? Infinity : overscanCountHorizontal,
35,330✔
576
    indexAttribute: 'data-column-index',
577
    // necessary as otherwise values are rounded which leads to wrong total width calculation leading to unnecessary scrollbar
578
    measureElement: !scaleXFactor || scaleXFactor === 1 ? (el) => el.getBoundingClientRect().width : undefined
28,395!
579
  });
580

581
  useEffect(() => {
35,330✔
582
    columnVirtualizer.measure();
7,513✔
583
  }, [columnVirtualizer, tableState.columnOrder, tableState.columnResizing]);
584

585
  const totalSize = columnVirtualizer.getTotalSize();
35,330✔
586
  const showVerticalEndBorder = tableState.tableClientWidth > totalSize;
35,330✔
587

588
  const tableClasses = clsx(
35,330✔
589
    classes.table,
590
    GlobalStyleClasses.sapScrollBar,
591
    withNavigationHighlight && classes.hasNavigationIndicator,
37,496✔
592
    showVerticalEndBorder && classes.showVerticalEndBorder
41,290✔
593
  );
594

595
  scrollToRef.current = {
35,330✔
596
    ...scrollToRef.current,
597
    horizontalScrollToOffset: columnVirtualizer.scrollToOffset,
598
    horizontalScrollToIndex: columnVirtualizer.scrollToIndex
599
  };
600

601
  const handleOnLoadMore = (e) => {
35,330✔
602
    const rootNodes = rows.filter((row) => row.depth === 0);
15,427✔
603
    onLoadMore(
177✔
604
      enrichEventWithDetails(e, {
605
        rowCount: rootNodes.length,
606
        totalRowCount: rows.length
607
      })
608
    );
609
  };
610

611
  return (
35,330✔
612
    <>
613
      <div className={className} style={inlineStyle} ref={analyticalTableRef} {...rest}>
614
        {header && (
38,466✔
615
          <TitleBar ref={titleBarRef} titleBarId={titleBarId}>
616
            {header}
617
          </TitleBar>
618
        )}
619
        {extension && <div ref={extensionRef}>{extension}</div>}
35,330!
620
        <FlexBox
621
          className={classes.tableContainerWithScrollBar}
622
          data-component-name="AnalyticalTableContainerWithScrollbar"
623
        >
624
          {showOverlay && (
35,551✔
625
            <>
626
              <span id={invalidTableTextId} className={classes.hiddenA11yText} aria-hidden>
627
                {invalidTableA11yText}
628
              </span>
629
              <div
630
                tabIndex={0}
631
                aria-labelledby={`${titleBarId} ${invalidTableTextId}`}
632
                role="region"
633
                data-component-name="AnalyticalTableOverlay"
634
                className={classes.overlay}
635
              />
636
            </>
637
          )}
638
          <div
639
            aria-labelledby={titleBarId}
640
            {...getTableProps()}
641
            tabIndex={showOverlay ? -1 : 0}
35,330✔
642
            role="grid"
643
            aria-rowcount={rows.length}
644
            aria-colcount={visibleColumns.length}
645
            data-per-page={internalVisibleRowCount}
646
            data-component-name="AnalyticalTableContainer"
647
            ref={tableRef}
648
            data-native-scrollbar={props['data-native-scrollbar']}
649
            className={tableClasses}
650
          >
651
            <div className={classes.tableHeaderBackgroundElement} />
652
            <div className={classes.tableBodyBackgroundElement} />
653
            {headerGroups.map((headerGroup) => {
654
              let headerProps: Record<string, unknown> = {};
35,042✔
655
              if (headerGroup.getHeaderGroupProps) {
35,042✔
656
                headerProps = headerGroup.getHeaderGroupProps();
35,042✔
657
              }
658
              return (
35,042✔
659
                tableRef.current && (
64,597✔
660
                  <ColumnHeaderContainer
661
                    ref={headerRef}
662
                    key={headerProps.key as string}
663
                    resizeInfo={tableState.columnResizing}
664
                    headerProps={headerProps}
665
                    headerGroup={headerGroup}
666
                    onSort={onSort}
667
                    onGroupByChanged={onGroupByChanged}
668
                    isRtl={isRtl}
669
                    portalContainer={portalContainer}
670
                    columnVirtualizer={columnVirtualizer}
671
                    uniqueId={uniqueId}
672
                    showVerticalEndBorder={showVerticalEndBorder}
673
                  />
674
                )
675
              );
676
            })}
677
            {loading && rawData?.length > 0 && <LoadingComponent style={{ width: `${totalColumnsWidth}px` }} />}
36,194✔
678
            {loading && rawData?.length === 0 && (
36,005✔
679
              <TablePlaceholder columns={visibleColumns} rows={minRows} style={noDataStyles} />
680
            )}
681
            {!loading && rawData?.length === 0 && (
70,560✔
682
              <NoDataComponent noDataText={noDataText} className={classes.noDataContainer} style={noDataStyles} />
683
            )}
684
            {rawData?.length > 0 && tableRef.current && (
99,452✔
685
              <VirtualTableBodyContainer
686
                rowCollapsedFlag={tableState.rowCollapsed}
687
                dispatch={dispatch}
688
                tableBodyHeight={tableBodyHeight}
689
                totalColumnsWidth={columnVirtualizer.getTotalSize()}
690
                parentRef={parentRef}
691
                classes={classes}
692
                infiniteScroll={infiniteScroll}
693
                infiniteScrollThreshold={infiniteScrollThreshold}
694
                onLoadMore={handleOnLoadMore}
695
                internalRowHeight={internalRowHeight}
696
                popInRowHeight={popInRowHeight}
697
                rows={rows}
698
                handleExternalScroll={handleBodyScroll}
699
                visibleRows={internalVisibleRowCount}
700
              >
701
                <VirtualTableBody
702
                  scrollContainerRef={scrollContainerRef}
703
                  classes={classes}
704
                  prepareRow={prepareRow}
705
                  rows={rows}
706
                  minRows={minRows}
707
                  scrollToRef={scrollToRef}
708
                  isTreeTable={isTreeTable}
709
                  internalRowHeight={internalRowHeight}
710
                  popInRowHeight={popInRowHeight}
711
                  visibleRows={internalVisibleRowCount}
712
                  alternateRowColor={alternateRowColor}
713
                  overscanCount={overscanCount}
714
                  parentRef={parentRef}
715
                  visibleColumns={visibleColumns}
716
                  renderRowSubComponent={renderRowSubComponent}
717
                  alwaysShowSubComponent={alwaysShowSubComponent}
718
                  markNavigatedRow={markNavigatedRow}
719
                  isRtl={isRtl}
720
                  subComponentsHeight={tableState.subComponentsHeight}
721
                  dispatch={dispatch}
722
                  columnVirtualizer={columnVirtualizer}
723
                  manualGroupBy={reactTableOptions?.manualGroupBy as boolean | undefined}
724
                  subRowsKey={subRowsKey}
725
                />
726
              </VirtualTableBodyContainer>
727
            )}
728
          </div>
76,863✔
729
          {(tableState.isScrollable === undefined || tableState.isScrollable) && (
730
            <VerticalScrollbar
731
              tableBodyHeight={tableBodyHeight}
732
              internalRowHeight={internalHeaderRowHeight}
733
              tableRef={tableRef}
734
              handleVerticalScrollBarScroll={handleVerticalScrollBarScroll}
735
              ref={verticalScrollBarRef}
736
              data-native-scrollbar={props['data-native-scrollbar']}
737
              scrollContainerRef={scrollContainerRef}
738
            />
739
          )}
740
        </FlexBox>
741
        {visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Interactive && (
35,917✔
742
          <VerticalResizer
743
            popInRowHeight={popInRowHeight}
744
            hasPopInColumns={tableState?.popInColumns?.length > 0}
745
            analyticalTableRef={analyticalTableRef}
746
            dispatch={dispatch}
747
            extensionsHeight={extensionsHeight}
748
            internalRowHeight={internalRowHeight}
749
            portalContainer={portalContainer}
750
            rowsLength={rows.length}
751
            visibleRows={internalVisibleRowCount}
752
            handleOnLoadMore={handleOnLoadMore}
753
          />
754
        )}
755
      </div>
756
      <Text
757
        aria-hidden="true"
758
        id={`scaleModeHelper-${uniqueId}`}
759
        className={classes.hiddenSmartColMeasure}
760
        data-component-name="AnalyticalTableScaleModeHelper"
761
      >
762
        {''}
763
      </Text>
764
      <Text
765
        aria-hidden="true"
766
        id={`scaleModeHelperHeader-${uniqueId}`}
767
        className={clsx(classes.hiddenSmartColMeasure, classes.hiddenSmartColMeasureHeader)}
768
        data-component-name="AnalyticalTableScaleModeHelperHeader"
769
      >
770
        {''}
771
      </Text>
772
    </>
773
  );
774
});
775

776
AnalyticalTable.displayName = 'AnalyticalTable';
411✔
777
AnalyticalTable.defaultProps = {
411✔
778
  infiniteScrollThreshold: 20,
779
  loading: false,
780
  sortable: true,
781
  filterable: false,
782
  groupable: false,
783
  selectionMode: AnalyticalTableSelectionMode.None,
784
  selectionBehavior: AnalyticalTableSelectionBehavior.Row,
785
  scaleWidthMode: AnalyticalTableScaleWidthMode.Default,
786
  subComponentsBehavior: AnalyticalTableSubComponentsBehavior.Expandable,
787
  data: [],
788
  columns: [],
789
  minRows: 5,
790
  groupBy: [],
791
  NoDataComponent: DefaultNoDataComponent,
792
  LoadingComponent: DefaultLoadingComponent,
793
  noDataText: 'No Data',
794
  reactTableOptions: {},
795
  tableHooks: [],
796
  visibleRows: 15,
797
  subRowsKey: 'subRows',
798
  highlightField: 'status',
799
  markNavigatedRow: () => false,
173,821✔
800
  selectedRowIds: {},
801
  onGroup: () => {},
802
  onRowExpandChange: () => {},
803
  isTreeTable: false,
804
  alternateRowColor: false,
805
  overscanCountHorizontal: 5,
806
  visibleRowCountMode: AnalyticalTableVisibleRowCountMode.Fixed
807
};
808

809
export { AnalyticalTable };
810
export type {
811
  AnalyticalTableColumnDefinition,
812
  AnalyticalTableDomRef,
813
  AnalyticalTablePropTypes,
814
  DivWithCustomScrollProp
815
};
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