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

SAP / ui5-webcomponents-react / 14304969491

07 Apr 2025 08:53AM CUT coverage: 87.54% (+0.08%) from 87.46%
14304969491

Pull #7187

github

web-flow
Merge f9635f7ca into 8d652b4c7
Pull Request #7187: feat(ObjectPage): allow customizing `role` of `footerArea` container

2966 of 3932 branches covered (75.43%)

5185 of 5923 relevant lines covered (87.54%)

84562.43 hits per line

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

95.83
/packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx
1
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual';
2
import IconDesign from '@ui5/webcomponents/dist/types/IconDesign.js';
3
import IconMode from '@ui5/webcomponents/dist/types/IconMode.js';
4
import iconFilter from '@ui5/webcomponents-icons/dist/filter.js';
5
import iconGroup from '@ui5/webcomponents-icons/dist/group-2.js';
6
import iconSortAscending from '@ui5/webcomponents-icons/dist/sort-ascending.js';
7
import iconSortDescending from '@ui5/webcomponents-icons/dist/sort-descending.js';
8
import { ThemingParameters } from '@ui5/webcomponents-react-base';
9
import { clsx } from 'clsx';
10
import type {
11
  AriaAttributes,
12
  CSSProperties,
13
  DragEventHandler,
14
  KeyboardEventHandler,
15
  MouseEventHandler,
16
  ReactNode
17
} from 'react';
18
import { useRef, useState } from 'react';
19
import { Icon } from '../../../webComponents/Icon/index.js';
20
import { Text } from '../../../webComponents/Text/index.js';
21
import type { ClassNames, ColumnType, DivWithCustomScrollProp } from '../types/index.js';
22
import { RenderColumnTypes } from '../types/index.js';
23

24
export interface ColumnHeaderProps {
25
  visibleColumnIndex: number;
26
  onDragStart: DragEventHandler<HTMLDivElement>;
27
  onDragOver: DragEventHandler<HTMLDivElement>;
28
  onDrop: DragEventHandler<HTMLDivElement>;
29
  onDragEnter: DragEventHandler<HTMLDivElement>;
30
  onDragEnd: DragEventHandler<HTMLDivElement>;
31
  dragOver: boolean;
32
  isDraggable: boolean;
33
  headerTooltip: string;
34
  virtualColumn: VirtualItem;
35
  columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
36
  isRtl: boolean;
37
  children: ReactNode | ReactNode[];
38
  columnId?: string;
39
  showVerticalEndBorder: boolean;
40
  classNames: ClassNames;
41

42
  //getHeaderProps()
43
  id: string;
44
  onClick: MouseEventHandler<HTMLDivElement> | undefined;
45
  onKeyDown?: KeyboardEventHandler<HTMLDivElement> | undefined;
46
  onKeyUp?: KeyboardEventHandler<HTMLDivElement> | undefined;
47
  className: string;
48
  style: CSSProperties;
49
  column: ColumnType;
50
  role: string;
51
  isFiltered?: boolean;
52
  title?: string;
53
  ['aria-sort']?: AriaAttributes['aria-sort'];
54
  ['aria-label']?: AriaAttributes['aria-label'];
55
}
56

57
export const ColumnHeader = (props: ColumnHeaderProps) => {
427✔
58
  const {
59
    id,
60
    children,
61
    column,
62
    columnId,
63
    className,
64
    style,
65
    onDragEnter,
66
    onDragOver,
67
    onDragStart,
68
    onDrop,
69
    onDragEnd,
70
    headerTooltip,
71
    isDraggable,
72
    dragOver,
73
    role,
74
    virtualColumn,
75
    columnVirtualizer,
76
    isRtl,
77
    visibleColumnIndex,
78
    onClick,
79
    onKeyDown,
80
    onKeyUp,
81
    isFiltered,
82
    title,
83
    'aria-label': ariaLabel,
84
    'aria-sort': ariaSort,
85
    showVerticalEndBorder,
86
    classNames
87
  } = props;
300,138✔
88

89
  const columnIndex = virtualColumn.index;
300,138✔
90

91
  const [popoverOpen, setPopoverOpen] = useState(false);
300,138✔
92
  const columnHeaderRef = useRef<HTMLDivElement>(null);
300,138✔
93

94
  const childIsString = typeof children === 'string';
300,138✔
95
  const tooltip = (() => {
300,138✔
96
    if (headerTooltip) {
300,138✔
97
      return headerTooltip;
57,046✔
98
    }
99
    if (childIsString) {
243,092✔
100
      return children;
155,590✔
101
    }
102
    return null;
87,502✔
103
  })();
104

105
  const textStyle = (() => {
300,138✔
106
    let margin = 0;
300,138✔
107
    const style: CSSProperties = {};
300,138✔
108

109
    if (column.hAlign) {
300,138!
110
      style.textAlign = column.hAlign.toLowerCase();
×
111
    }
112

113
    if (column.isSorted) margin += 0.8125;
300,138✔
114
    if (column.isGrouped) margin += 0.8125;
300,138✔
115
    if (isFiltered) margin += 0.8125;
300,138✔
116

117
    if (margin === 0) {
300,138✔
118
      return style;
290,754✔
119
    }
120

121
    if (margin > 0) {
9,384✔
122
      margin += 0.75;
9,384✔
123
    }
124

125
    style.marginInlineEnd = `${margin}rem`;
9,384✔
126

127
    return style;
9,384✔
128
  })();
129

130
  const hasPopover = column.canGroupBy || column.canSort || column.canFilter;
300,138✔
131

132
  const handleHeaderCellClick = (e) => {
300,138✔
133
    if (typeof onClick === 'function') {
1,634✔
134
      onClick(e);
719✔
135
    }
136
    if (hasPopover) {
1,634✔
137
      setPopoverOpen(true);
782✔
138
    }
139
  };
140

141
  const directionStyles = isRtl
300,138✔
142
    ? { right: 0, transform: `translateX(-${virtualColumn.start}px)` }
143
    : { left: 0, transform: `translateX(${virtualColumn.start}px)` };
144

145
  const handleHeaderCellKeyDown = (e) => {
300,138✔
146
    if (typeof onKeyDown === 'function') {
241✔
147
      onKeyDown(e);
241✔
148
    }
149
    if (hasPopover && e.code === 'Enter') {
241!
150
      setPopoverOpen(true);
×
151
    }
152
    if (e.code === 'Space') {
241✔
153
      e.preventDefault();
44✔
154
    }
155
  };
156

157
  const handleHeaderCellKeyUp = (e) => {
300,138✔
158
    if (typeof onKeyUp === 'function') {
1,449✔
159
      onKeyUp(e);
132✔
160
    }
161
    if (hasPopover && e.code === 'Space' && !e.target.hasAttribute('ui5-li')) {
1,449✔
162
      setPopoverOpen(true);
45✔
163
    }
164
  };
165

166
  if (!column) return null;
300,138!
167
  return (
300,138✔
168
    <div
169
      ref={columnHeaderRef}
170
      className={clsx(classNames.thContainer, showVerticalEndBorder && classNames.verticalEndBorder)}
317,100✔
171
      style={{
172
        position: 'absolute',
173
        insetBlockStart: 0,
174
        width: `${virtualColumn.size}px`,
175
        ...directionStyles
176
      }}
177
    >
178
      <div
179
        ref={columnVirtualizer.measureElement}
180
        data-visible-column-index={visibleColumnIndex}
181
        data-visible-row-index={0}
182
        data-row-index={0}
183
        data-column-index={columnIndex}
184
        tabIndex={-1}
185
        id={id}
186
        className={className}
187
        style={{
188
          ...style,
189
          borderInlineStart: dragOver ? `3px solid ${ThemingParameters.sapSelectedColor}` : undefined
300,138!
190
        }}
191
        aria-haspopup={hasPopover ? 'menu' : undefined}
300,138✔
192
        role={role}
193
        draggable={isDraggable}
194
        onDragEnter={onDragEnter}
195
        onDragOver={onDragOver}
196
        onDragStart={onDragStart}
197
        onDrop={onDrop}
198
        onDragEnd={onDragEnd}
199
        data-column-id={columnId}
200
        onClick={handleHeaderCellClick}
201
        onKeyDown={handleHeaderCellKeyDown}
202
        onKeyUp={handleHeaderCellKeyUp}
203
        aria-label={ariaLabel}
204
        aria-sort={ariaSort}
205
        title={title}
206
      >
207
        <div
208
          className={clsx(
209
            classNames.header,
210
            columnId === '__ui5wcr__internal_selection_column' && classNames.selectAllCheckBoxContainer
325,314✔
211
          )}
212
          data-h-align={column.hAlign}
213
          data-component-name={`AnalyticalTableHeaderContentContainer-${columnId}`}
214
        >
215
          {childIsString ? (
300,138✔
216
            <Text
217
              title={tooltip}
218
              maxLines={1}
219
              style={textStyle}
220
              className={classNames.text}
221
              data-component-name={`AnalyticalTableHeaderTextContentContainer-${columnId}`}
222
            >
223
              {children}
224
            </Text>
225
          ) : (
226
            <span
227
              title={tooltip}
228
              style={textStyle}
229
              className={classNames.text}
230
              data-component-name={`AnalyticalTableHeaderContentContainer-${columnId}`}
231
            >
232
              {children}
233
            </span>
234
          )}
235
          <div
236
            className={classNames.iconContainer}
237
            data-component-name={`AnalyticalTableHeaderIconsContainer-${columnId}`}
238
          >
239
            {isFiltered && (
304,888✔
240
              <Icon
241
                design={IconDesign.NonInteractive}
242
                name={iconFilter}
243
                className={classNames.icon}
244
                mode={IconMode.Decorative}
245
              />
246
            )}
247
            {column.isSorted && (
302,280✔
248
              <Icon
249
                design={IconDesign.NonInteractive}
250
                name={column.isSortedDesc ? iconSortDescending : iconSortAscending}
2,142✔
251
                className={classNames.icon}
252
                mode={IconMode.Decorative}
253
              />
254
            )}
255
            {column.isGrouped && (
303,206✔
256
              <Icon
257
                design={IconDesign.NonInteractive}
258
                name={iconGroup}
259
                className={classNames.icon}
260
                mode={IconMode.Decorative}
261
              />
262
            )}
263
          </div>
264
        </div>
265
        {hasPopover &&
379,424✔
266
          popoverOpen &&
267
          // render the popover and add the props to the table instance
268
          column.render(RenderColumnTypes.Popover, {
269
            popoverProps: {
270
              openerRef: columnHeaderRef,
271
              setOpen: setPopoverOpen
272
            }
273
          })}
274
      </div>
275
    </div>
276
  );
277
};
278

279
ColumnHeader.displayName = 'ColumnHeader';
427✔
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