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

SAP / ui5-webcomponents-react / 9808059946

05 Jul 2024 12:09PM CUT coverage: 80.982% (+0.01%) from 80.971%
9808059946

Pull #6023

github

web-flow
Merge 57ccbc732 into 160aa88a5
Pull Request #6023: feat: update UI5 Web Components to 2.0.0

2631 of 3841 branches covered (68.5%)

4748 of 5863 relevant lines covered (80.98%)

64106.52 hits per line

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

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

24
export interface ColumnHeaderProps {
25
  visibleColumnIndex: number;
26
  onSort?: (e: CustomEvent<{ column: unknown; sortDirection: string }>) => void;
27
  onGroupBy?: (e: CustomEvent<{ column: unknown; isGrouped: boolean }>) => void;
28
  onDragStart: DragEventHandler<HTMLDivElement>;
29
  onDragOver: DragEventHandler<HTMLDivElement>;
30
  onDrop: DragEventHandler<HTMLDivElement>;
31
  onDragEnter: DragEventHandler<HTMLDivElement>;
32
  onDragEnd: DragEventHandler<HTMLDivElement>;
33
  dragOver: boolean;
34
  isDraggable: boolean;
35
  headerTooltip: string;
36
  virtualColumn: VirtualItem<HTMLDivElement>;
37
  columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
38
  isRtl: boolean;
39
  children: ReactNode | ReactNode[];
40
  portalContainer: Element;
41
  columnId?: string;
42
  showVerticalEndBorder: boolean;
43

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

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

92
  const columnIndex = virtualColumn.index;
139,864✔
93

94
  const [popoverOpen, setPopoverOpen] = useState(false);
139,864✔
95
  const columnHeaderRef = useRef<HTMLDivElement>(null);
139,864✔
96

97
  const childIsString = typeof children === 'string';
139,864✔
98
  const tooltip = (() => {
139,864✔
99
    if (headerTooltip) {
139,864✔
100
      return headerTooltip;
26,138✔
101
    }
102
    if (childIsString) {
113,726✔
103
      return children;
72,819✔
104
    }
105
    return null;
40,907✔
106
  })();
107

108
  const textStyle = (() => {
139,864✔
109
    let margin = 0;
139,864✔
110
    const style: CSSProperties = {};
139,864✔
111

112
    if (column.hAlign) {
139,864!
113
      style.textAlign = column.hAlign.toLowerCase() as any;
×
114
    }
115

116
    if (column.isSorted) margin++;
139,864✔
117
    if (column.isGrouped) margin++;
139,864✔
118
    if (isFiltered) margin++;
139,864✔
119

120
    if (margin === 0) {
139,864✔
121
      return style;
135,742✔
122
    }
123

124
    if (margin > 0) margin += 0.5;
4,122✔
125

126
    style.marginInlineEnd = `${margin}rem`;
4,122✔
127

128
    return style;
4,122✔
129
  })();
130

131
  const hasPopover = column.canGroupBy || column.canSort || column.canFilter;
139,864✔
132

133
  const handleHeaderCellClick = (e) => {
139,864✔
134
    onClick?.(e);
1,443✔
135
    if (hasPopover) {
1,443✔
136
      setPopoverOpen(true);
742✔
137
    }
138
  };
139

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

144
  const handleHeaderCellKeyDown = (e) => {
139,864✔
145
    onKeyDown?.(e);
78✔
146
    if (hasPopover && e.code === 'Enter') {
78!
147
      setPopoverOpen(true);
×
148
    }
149
    if (e.code === 'Space') {
78!
150
      e.preventDefault();
×
151
    }
152
  };
153

154
  const handleHeaderCellKeyUp = (e) => {
139,864✔
155
    if (hasPopover && e.code === 'Space' && !e.target.hasAttribute('ui5-li')) {
1,232✔
156
      setPopoverOpen(true);
43✔
157
    }
158
  };
159

160
  if (!column) return null;
139,864!
161
  return (
139,864✔
162
    <div
163
      ref={columnHeaderRef}
164
      className={clsx(classNames.thContainer, showVerticalEndBorder && classNames.verticalEndBorder)}
161,247✔
165
      style={{
166
        position: 'absolute',
167
        insetBlockStart: 0,
168
        width: `${virtualColumn.size}px`,
169
        ...directionStyles
170
      }}
171
    >
172
      <div
173
        ref={columnVirtualizer.measureElement}
174
        data-visible-column-index={visibleColumnIndex}
175
        data-visible-row-index={0}
176
        data-row-index={0}
177
        data-column-index={columnIndex}
178
        tabIndex={-1}
179
        id={id}
180
        className={className}
181
        style={{
182
          ...style,
183
          borderInlineStart: dragOver ? `3px solid ${ThemingParameters.sapSelectedColor}` : undefined
139,864!
184
        }}
185
        aria-haspopup={hasPopover ? 'menu' : undefined}
139,864✔
186
        role={role}
187
        draggable={isDraggable}
188
        onDragEnter={onDragEnter}
189
        onDragOver={onDragOver}
190
        onDragStart={onDragStart}
191
        onDrop={onDrop}
192
        onDragEnd={onDragEnd}
193
        data-column-id={columnId}
194
        onClick={handleHeaderCellClick}
195
        onKeyDown={handleHeaderCellKeyDown}
196
        onKeyUp={handleHeaderCellKeyUp}
197
        aria-label={ariaLabel}
198
        aria-sort={ariaSort}
199
        title={title}
200
      >
201
        <div
202
          className={clsx(
203
            classNames.header,
204
            columnId === '__ui5wcr__internal_selection_column' && classNames.selectAllCheckBoxContainer
151,517✔
205
          )}
206
          data-h-align={column.hAlign}
207
          data-component-name={`AnalyticalTableHeaderContentContainer-${columnId}`}
208
        >
209
          {childIsString ? (
139,864✔
210
            <Text
211
              title={tooltip}
212
              maxLines={1}
213
              style={textStyle}
214
              className={classNames.text}
215
              data-component-name={`AnalyticalTableHeaderTextContentContainer-${columnId}`}
216
            >
217
              {children}
218
            </Text>
219
          ) : (
220
            children
221
          )}
222
          <div
223
            className={classNames.iconContainer}
224
            data-component-name={`AnalyticalTableHeaderIconsContainer-${columnId}`}
225
          >
226
            {isFiltered && <Icon name={iconFilter} aria-hidden />}
141,957✔
227
            {column.isSorted && (
140,512✔
228
              <Icon name={column.isSortedDesc ? iconSortDescending : iconSortAscending} aria-hidden />
648✔
229
            )}
230
            {column.isGrouped && <Icon name={iconGroup} aria-hidden />}
141,455✔
231
          </div>
232
        </div>
233
        {hasPopover && popoverOpen && (
266,587✔
234
          <ColumnHeaderModal
235
            isRtl={isRtl}
236
            column={column}
237
            onSort={onSort}
238
            onGroupBy={onGroupBy}
239
            openerRef={columnHeaderRef}
240
            open={popoverOpen}
241
            setPopoverOpen={setPopoverOpen}
242
            portalContainer={portalContainer}
243
          />
244
        )}
245
      </div>
246
    </div>
247
  );
248
};
249

250
ColumnHeader.displayName = 'ColumnHeader';
398✔
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