Skip to content

Commit 3f05a1a

Browse files
committed
Refactored code into useAutoResize hook. Changed pattern to resemble useDynamicColumnWidths. Detecting expandable rows and adjusting for the icon/button space. Removed support for isTreeTable and tables with strings in the groupBy array
1 parent 18f3180 commit 3f05a1a

File tree

8 files changed

+147
-76
lines changed

8 files changed

+147
-76
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.cy.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ describe('AnalyticalTable', () => {
245245
});
246246

247247
let dataFixed = data.map((el, i) => {
248-
if (i === 2) return { ...el, name: 'Much Longer Name To Resize Larger For Testing A Larger Auto Resize' };
248+
if (i === 2) return { ...el, name: 'Longer Name Too' };
249249
return el;
250250
});
251251

@@ -261,7 +261,7 @@ describe('AnalyticalTable', () => {
261261
cy.get('[data-cy="data-resizer-1"]').should('be.visible').dblclick();
262262
cy.get('[data-column-id="age"]').invoke('outerWidth').should('equal', 60);
263263
cy.get('[data-cy="data-resizer-0"]').should('be.visible').dblclick();
264-
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 451);
264+
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 127);
265265

266266
dataFixed = generateMoreData(200);
267267

@@ -284,7 +284,7 @@ describe('AnalyticalTable', () => {
284284

285285
cy.get('[data-component-name="AnalyticalTableBody"]').scrollTo('bottom');
286286
cy.get('[data-cy="data-resizer-0"]').should('be.visible').dblclick();
287-
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 94);
287+
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 91);
288288

289289
resizeColumns = columns.map((el) => {
290290
return { ...el, autoResizable: false };
@@ -296,6 +296,26 @@ describe('AnalyticalTable', () => {
296296
cy.get('[data-column-id="age"]').invoke('outerWidth').should('equal', 472.75);
297297
cy.get('[data-cy="data-resizer-0"]').should('be.visible').dblclick();
298298
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 472.75);
299+
300+
const dataSub = data.map((el, i) => {
301+
if (i === 2) return { ...el, name: 'Longer Name Too' };
302+
return el;
303+
});
304+
305+
resizeColumns = columns.map((el) => {
306+
return { ...el, autoResizable: true };
307+
});
308+
309+
const renderRowSubComponent = () => {
310+
return <div title="subcomponent">SubComponent</div>;
311+
};
312+
313+
cy.mount(<AnalyticalTable data={dataSub} columns={resizeColumns} renderRowSubComponent={renderRowSubComponent} />);
314+
cy.wait(200);
315+
cy.get('[data-cy="data-resizer-1"]').should('be.visible').dblclick();
316+
cy.get('[data-column-id="age"]').invoke('outerWidth').should('equal', 60);
317+
cy.get('[data-cy="data-resizer-0"]').should('be.visible').dblclick();
318+
cy.get('[data-column-id="name"]').invoke('outerWidth').should('equal', 165);
299319
});
300320

301321
it('scrollTo', () => {

packages/main/src/components/AnalyticalTable/ColumnHeader/ColumnHeaderContainer.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ interface ColumnHeaderContainerProps {
1717
columnVirtualizer: Virtualizer<DivWithCustomScrollProp, Element>;
1818
uniqueId: string;
1919
showVerticalEndBorder: boolean;
20-
onAutoResize: (e: React.MouseEvent<HTMLDivElement, globalThis.MouseEvent>, accessor: string) => void;
20+
isTreeTable: boolean;
21+
rowVirtualizer: Virtualizer<DivWithCustomScrollProp, HTMLElement>;
22+
onAutoResize: (e?: CustomEvent<{ accessor: string; width: number }>) => void;
23+
groupBy: string[];
2124
}
2225

2326
export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderContainerProps>((props, ref) => {
@@ -32,7 +35,10 @@ export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderCont
3235
columnVirtualizer,
3336
uniqueId,
3437
showVerticalEndBorder,
35-
onAutoResize
38+
onAutoResize,
39+
rowVirtualizer,
40+
isTreeTable,
41+
groupBy
3642
} = props;
3743

3844
useStylesheet(styleData, 'Resizer');
@@ -71,8 +77,10 @@ export const ColumnHeaderContainer = forwardRef<HTMLDivElement, ColumnHeaderCont
7177
className={classNames.resizer}
7278
style={resizerDirectionStyle}
7379
onDoubleClick={(e) => {
74-
if (column.autoResizable) {
75-
onAutoResize(e, rest.id);
80+
if (column.autoResizable && !isTreeTable && !groupBy.length) {
81+
const items = rowVirtualizer.getVirtualItems();
82+
const [start, end] = [items[0].index, items[items.length - 1].index];
83+
column.getResizerProps().onDoubleClick(e, start, end, rest.id, onAutoResize);
7684
}
7785
}}
7886
/>

packages/main/src/components/AnalyticalTable/defaults/Column/Expandable.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import iconNavDownArrow from '@ui5/webcomponents-icons/dist/navigation-down-arrow.js';
22
import iconNavRightArrow from '@ui5/webcomponents-icons/dist/navigation-right-arrow.js';
3-
import { CssSizeVariables, useCurrentTheme, useStylesheet } from '@ui5/webcomponents-react-base';
3+
import { CssSizeVariables, useCurrentTheme, useStylesheet, useIsomorphicId } from '@ui5/webcomponents-react-base';
44
import { clsx } from 'clsx';
55
import React from 'react';
66
import { ButtonDesign } from '../../../../enums/index.js';
@@ -24,7 +24,7 @@ const getPadding = (level) => {
2424

2525
export const Expandable = (props) => {
2626
const { cell, row, column, visibleColumns: columns, webComponentsReactProperties } = props;
27-
const { renderRowSubComponent, alwaysShowSubComponent, translatableTexts } = webComponentsReactProperties;
27+
const { renderRowSubComponent, alwaysShowSubComponent, translatableTexts, uniqueId } = webComponentsReactProperties;
2828
const currentTheme = useCurrentTheme();
2929
useStylesheet(styleData, Expandable.displayName);
3030
const shouldRenderButton = currentTheme === 'sap_horizon' || currentTheme === 'sap_horizon_dark';
@@ -41,6 +41,8 @@ export const Expandable = (props) => {
4141
const subComponentExpandable =
4242
typeof renderRowSubComponent === 'function' && !!renderRowSubComponent(row) && !alwaysShowSubComponent;
4343

44+
const expandId = useIsomorphicId();
45+
4446
return (
4547
<>
4648
{columnIndex === 0 && (
@@ -53,6 +55,7 @@ export const Expandable = (props) => {
5355
className={classNames.container}
5456
aria-expanded={row.isExpanded}
5557
aria-label={row.isExpanded ? translatableTexts.collapseA11yText : translatableTexts.expandA11yText}
58+
id={`scaleModeHelperExpand-${uniqueId}-${expandId}`}
5659
>
5760
{shouldRenderButton ? (
5861
<Button
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { enrichEventWithDetails } from '@ui5/webcomponents-react-base';
2+
import { DEFAULT_COLUMN_WIDTH } from '../defaults/Column/index.js';
3+
import type { ReactTableHooks } from '../types/index.js';
4+
import { CELL_PADDING_PX } from './useDynamicColumnWidths.js';
5+
6+
const handleAutoResize = (props, { instance }) => {
7+
return {
8+
...props,
9+
onDoubleClick: (e, start, end, accessor, onAutoResize) => {
10+
// Find first in column to compare to accessor
11+
let columnFirst;
12+
const { columnOrder } = instance.state;
13+
let colOrder = [];
14+
if (columnOrder.length) {
15+
colOrder = columnOrder.filter(
16+
(col) =>
17+
col !== '__ui5wcr__internal_selection_column' &&
18+
col !== '__ui5wcr__internal_highlight_column' &&
19+
col !== '__ui5wcr__internal_navigation_column'
20+
);
21+
columnFirst = colOrder[0];
22+
}
23+
if (!columnFirst) {
24+
colOrder = instance.visibleColumns?.filter(
25+
(col) =>
26+
col.id !== '__ui5wcr__internal_selection_column' &&
27+
col.id !== '__ui5wcr__internal_highlight_column' &&
28+
col.id !== '__ui5wcr__internal_navigation_column'
29+
);
30+
columnFirst = colOrder[0].id;
31+
}
32+
33+
// Undefined accessor should not occur but checking to be safe
34+
columnFirst = columnFirst ? columnFirst === accessor : false;
35+
36+
const rows = instance.rows;
37+
const dispatch = instance.dispatch;
38+
const rowSlice = rows.slice(start, end);
39+
let largest = getContentPxMax(rowSlice, accessor, instance?.webComponentsReactProperties.uniqueId, columnFirst);
40+
largest = largest > DEFAULT_COLUMN_WIDTH ? largest : DEFAULT_COLUMN_WIDTH;
41+
onAutoResize(
42+
enrichEventWithDetails(e, {
43+
accessor,
44+
width: largest
45+
})
46+
);
47+
if (e.defaultPrevented) {
48+
return;
49+
}
50+
dispatch({
51+
type: 'AUTO_RESIZE',
52+
payload: { [accessor]: largest }
53+
});
54+
}
55+
};
56+
};
57+
58+
function getContentPxMax(rowSlice, accessor, uniqueId, isFirstColumn) {
59+
let high = 0,
60+
current,
61+
ruler,
62+
expand,
63+
expandPx = 0,
64+
style,
65+
margin = 0;
66+
67+
// Check for Expandable Row
68+
// If found in range: All rows treated as expandable
69+
if (isFirstColumn) {
70+
expand = document.querySelector(`[id^="scaleModeHelperExpand-${uniqueId}"]`);
71+
if (expand) {
72+
style = getComputedStyle(expand, null);
73+
margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight);
74+
margin = margin > 0 ? margin : 0;
75+
}
76+
console.log(margin);
77+
expandPx = expand ? expand.offsetWidth + margin : 0 + margin;
78+
}
79+
80+
for (let i = 0; i < rowSlice.length; i++) {
81+
const dataPoint = rowSlice[i]?.values?.[accessor];
82+
if (dataPoint) {
83+
ruler = ruler ? ruler : document.getElementById(`scaleModeHelper-${uniqueId}`);
84+
if (ruler) {
85+
ruler.innerHTML = `${dataPoint}`;
86+
current = ruler.scrollWidth + CELL_PADDING_PX + expandPx;
87+
} else current = 0;
88+
high = high > current ? high : current;
89+
}
90+
}
91+
return high ?? 0;
92+
}
93+
94+
export const useAutoResize = (hooks: ReactTableHooks) => {
95+
hooks.getResizerProps.push(handleAutoResize);
96+
};
97+
98+
useAutoResize.pluginName = 'useAutoResize';

packages/main/src/components/AnalyticalTable/hooks/useDynamicColumnWidths.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { AnalyticalTableColumnDefinition, ReactTableHooks } from '../types/
55

66
const ROW_SAMPLE_SIZE = 20;
77
const MAX_WIDTH = 700;
8-
const CELL_PADDING_PX = 18; /* padding left and right 0.5rem each (16px) + borders (1px) + buffer (1px) */
8+
export const CELL_PADDING_PX = 18; /* padding left and right 0.5rem each (16px) + borders (1px) + buffer (1px) */
99

1010
function findLongestString(str1, str2) {
1111
if (typeof str1 !== 'string' || typeof str2 !== 'string') {

packages/main/src/components/AnalyticalTable/index.tsx

Lines changed: 6 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { DefaultLoadingComponent } from './defaults/LoadingComponent/index.js';
5858
import { TablePlaceholder } from './defaults/LoadingComponent/TablePlaceholder.js';
5959
import { DefaultNoDataComponent } from './defaults/NoDataComponent/index.js';
6060
import { useA11y } from './hooks/useA11y.js';
61+
import { useAutoResize } from './hooks/useAutoResize.js';
6162
import { useColumnDragAndDrop } from './hooks/useDragAndDrop.js';
6263
import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths.js';
6364
import { useKeyboardNavigation } from './hooks/useKeyboardNavigation.js';
@@ -248,6 +249,7 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
248249
useResizeColumns,
249250
useResizeColumnsConfig,
250251
useRowSelectionColumn,
252+
useAutoResize,
251253
useSingleRowStateSelection,
252254
useSelectionChangeCallback,
253255
useRowHighlight,
@@ -656,70 +658,6 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
656658
);
657659
};
658660

659-
const handleOnAutoResize = (e, accessor) => {
660-
// Helpers
661-
interface canvasHolderProps {
662-
canvas?: HTMLCanvasElement;
663-
}
664-
const canvasHolder: canvasHolderProps = { canvas: undefined };
665-
// Text Width Analysis
666-
function getTextWidth(text: string, font: string) {
667-
// Reusing the canvas is more efficient
668-
const canvas = canvasHolder.canvas || (canvasHolder.canvas = document.createElement('canvas'));
669-
const context = canvas.getContext('2d');
670-
context.font = font;
671-
const metrics = context.measureText(text);
672-
return metrics.width;
673-
}
674-
675-
function getCssStyle(element: Element, prop: string) {
676-
return window.getComputedStyle(element, null).getPropertyValue(prop);
677-
}
678-
679-
function getCanvasFont(el: Element = document.body) {
680-
const fontWeight = getCssStyle(el, 'font-weight') || 'normal';
681-
const fontSize = getCssStyle(el, 'font-size') || '12px';
682-
const fontFamily = getCssStyle(el, 'font-family') || 'Times New Roman';
683-
684-
return `${fontWeight} ${fontSize} ${fontFamily}`;
685-
}
686-
687-
const findWidth = (text: string, el: Element) => {
688-
let font: string | undefined;
689-
if (!font) font = getCanvasFont(el);
690-
return getTextWidth(text, font);
691-
};
692-
// End Helpers
693-
let largest = 0;
694-
// Currently Including Overscan
695-
const items = rowVirtualizer.getVirtualItems();
696-
const [start, end] = [items[0].index, items[items.length - 1].index];
697-
698-
for (let i = start; i < end; i++) {
699-
// Use the classname for the span where the text lives AnalyticalTable.module.css.js
700-
const collection = document.getElementsByClassName(clsx(classNames.tableText));
701-
const current = findWidth(rows[i].values[accessor], collection[0]);
702-
largest = current > largest ? current : largest;
703-
}
704-
// Assign padding
705-
largest = Math.ceil(largest + 20);
706-
// Smallest column allowed is 60px
707-
largest = largest < 60 ? 60 : largest;
708-
onAutoResize(
709-
enrichEventWithDetails(e, {
710-
accessor,
711-
width: largest
712-
})
713-
);
714-
if (e.defaultPrevented) {
715-
return;
716-
}
717-
dispatch({
718-
type: 'DOUBLE_CLICK_RESIZE',
719-
payload: { [accessor]: largest }
720-
});
721-
};
722-
723661
const measureElement = (el: HTMLElement) => {
724662
return el.offsetHeight;
725663
};
@@ -821,9 +759,12 @@ const AnalyticalTable = forwardRef<AnalyticalTableDomRef, AnalyticalTablePropTyp
821759
isRtl={isRtl}
822760
portalContainer={portalContainer}
823761
columnVirtualizer={columnVirtualizer}
762+
isTreeTable={isTreeTable}
763+
rowVirtualizer={rowVirtualizer}
824764
uniqueId={uniqueId}
825765
showVerticalEndBorder={showVerticalEndBorder}
826-
onAutoResize={handleOnAutoResize}
766+
onAutoResize={onAutoResize}
767+
groupBy={groupBy}
827768
/>
828769
)
829770
);

packages/main/src/components/AnalyticalTable/tableReducer/stateReducer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const stateReducer = (state, action, _prevState, instance) => {
6767
// fallback if the component wasn't ready yet for scrolling (elements are not initialized), e.g. when calling `.scrollToItem` on mount
6868
case 'TRIGGER_PROG_SCROLL':
6969
return { ...state, triggerScroll: payload };
70-
case 'DOUBLE_CLICK_RESIZE':
70+
case 'AUTO_RESIZE':
7171
return {
7272
...state,
7373
columnResizing: {

packages/main/src/components/AnalyticalTable/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ export interface AnalyticalTableColumnDefinition {
261261
* Defines whether double clicking a columns data-resizer will automatically resize the column.
262262
*
263263
* Available on text columns
264+
* Disabled if isTreeTable is true or groupBy is used
264265
*/
265266
autoResizable?: boolean;
266267
// ui5 web components react properties

0 commit comments

Comments
 (0)