Skip to content

Commit b66481c

Browse files
committed
fix(datagrid-web): fix columnr resize
1 parent 6f58165 commit b66481c

File tree

9 files changed

+102
-23
lines changed

9 files changed

+102
-23
lines changed

packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ export function preview(props: DatagridPreviewProps): ReactElement {
111111
resize: noop,
112112
setFilter: noop,
113113
sortBy: noop,
114-
swap: noop
114+
swap: noop,
115+
setSize: noop,
116+
createSizeSnapshot: noop
115117
}}
116118
data={data}
117119
emptyPlaceholderRenderer={useCallback(

packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function Container(props: Props): ReactElement {
4444
const multipleFilteringState = useMultipleFiltering();
4545
const { FilterContext } = useFilterContext();
4646

47-
const [state, actions] = useGridState(props.initParams, props.mappedColumns, props.onStateChange);
47+
const [state, actions, { useHeaderRef }] = useGridState(props.initParams, props.mappedColumns, props.onStateChange);
4848

4949
const [{ items, exporting, processedRows }, { abort }] = useDG2ExportApi({
5050
columns: useMemo(
@@ -260,6 +260,7 @@ function Container(props: Props): ReactElement {
260260
cellEventsController={cellEventsController}
261261
checkboxEventsController={checkboxEventsController}
262262
focusController={focusController}
263+
useHeaderRef={useHeaderRef}
263264
/>
264265
);
265266
}

packages/pluggableWidgets/datagrid-web/src/components/ColumnResizer.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,34 @@ export interface ColumnResizerProps {
55
minWidth?: number;
66
setColumnWidth: (width: number) => void;
77
onResizeEnds?: () => void;
8+
onResizeStart?: () => void;
89
}
910

10-
export function ColumnResizer({ minWidth = 50, setColumnWidth, onResizeEnds }: ColumnResizerProps): ReactElement {
11+
export function ColumnResizer({
12+
minWidth = 50,
13+
setColumnWidth,
14+
onResizeEnds,
15+
onResizeStart
16+
}: ColumnResizerProps): ReactElement {
1117
const [isResizing, setIsResizing] = useState(false);
1218
const [startPosition, setStartPosition] = useState(0);
1319
const [currentWidth, setCurrentWidth] = useState(0);
1420
const resizerReference = useRef<HTMLDivElement>(null);
21+
const onStart = useEventCallback(onResizeStart);
1522

16-
const onStartDrag = useCallback((e: TouchEvent<HTMLDivElement> & MouseEvent<HTMLDivElement>): void => {
17-
const mouseX = e.touches ? e.touches[0].screenX : e.screenX;
18-
setStartPosition(mouseX);
19-
setIsResizing(true);
20-
if (resizerReference.current) {
21-
const column = resizerReference.current.parentElement!;
22-
setCurrentWidth(column.clientWidth);
23-
}
24-
}, []);
23+
const onStartDrag = useCallback(
24+
(e: TouchEvent<HTMLDivElement> & MouseEvent<HTMLDivElement>): void => {
25+
const mouseX = e.touches ? e.touches[0].screenX : e.screenX;
26+
setStartPosition(mouseX);
27+
setIsResizing(true);
28+
if (resizerReference.current) {
29+
const column = resizerReference.current.parentElement!;
30+
setCurrentWidth(column.clientWidth);
31+
}
32+
onStart();
33+
},
34+
[onStart]
35+
);
2536
const onEndDrag = useCallback((): void => {
2637
if (!isResizing) {
2738
return;

packages/pluggableWidgets/datagrid-web/src/components/Header.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { FaArrowsAltV } from "./icons/FaArrowsAltV";
1818
import { ColumnResizerProps } from "./ColumnResizer";
1919
import * as Grid from "../typings/GridModel";
2020
import { ColumnId, GridColumn } from "../typings/GridColumn";
21+
import { HeaderRefHook } from "../features/model/resizing";
2122

2223
export interface HeaderProps {
2324
className?: string;
@@ -39,6 +40,7 @@ export interface HeaderProps {
3940
setSortBy: Grid.Actions["sortBy"];
4041
sortRule?: Grid.SortRule;
4142
visibleColumns: GridColumn[];
43+
useHeaderRef?: HeaderRefHook<HTMLDivElement>;
4244
}
4345

4446
export function Header(props: HeaderProps): ReactElement {
@@ -89,6 +91,7 @@ export function Header(props: HeaderProps): ReactElement {
8991
role="columnheader"
9092
style={!props.sortable || !props.column.canSort ? { cursor: "unset" } : undefined}
9193
title={caption}
94+
ref={props.useHeaderRef?.(props.column.columnId)}
9295
>
9396
<div
9497
className={classNames("column-container", {

packages/pluggableWidgets/datagrid-web/src/components/Widget.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { KeyNavProvider } from "../features/keyboard-navigation/context";
2525
import * as GridModel from "../typings/GridModel";
2626
import { SelectActionHelper } from "../helpers/SelectActionHelper";
2727
import { FocusTargetController } from "../features/keyboard-navigation/FocusTargetController";
28+
import { HeaderRefHook } from "../features/model/resizing";
2829

2930
export interface WidgetProps<C extends GridColumn, T extends ObjectItem = ObjectItem> {
3031
CellComponent: CellComponent<C>;
@@ -70,6 +71,7 @@ export interface WidgetProps<C extends GridColumn, T extends ObjectItem = Object
7071
checkboxEventsController: EventsController;
7172
selectActionHelper: SelectActionHelper;
7273
focusController: FocusTargetController;
74+
useHeaderRef?: HeaderRefHook<HTMLDivElement>;
7375
}
7476

7577
export function Widget<C extends GridColumn>(props: WidgetProps<C>): ReactElement {
@@ -182,9 +184,10 @@ export function Widget<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
182184
hidable={columnsHidable}
183185
isDragging={isDragging}
184186
preview={preview}
185-
resizable={columnsResizable}
187+
resizable={columnsResizable && columnsToShow.at(-1) !== column}
186188
resizer={
187189
<ColumnResizer
190+
onResizeStart={actions.createSizeSnapshot}
188191
setColumnWidth={(width: number) =>
189192
actions.resize([column.columnId, width])
190193
}
@@ -197,6 +200,7 @@ export function Widget<C extends GridColumn>(props: WidgetProps<C>): ReactElemen
197200
sortable={columnsSortable}
198201
sortRule={state.sortOrder.find(([id]) => column.columnId === id)}
199202
visibleColumns={state.visibleColumns}
203+
useHeaderRef={props.useHeaderRef}
200204
/>
201205
)
202206
)}
@@ -265,9 +269,10 @@ function gridStyle(
265269
optional: OptionalColumns
266270
): CSSProperties {
267271
const columnSizes = columns.map(c => {
272+
const isLast = columns.at(-1) === c;
268273
const columnResizedSize = resizeMap[c.columnId];
269274
if (columnResizedSize) {
270-
return `${columnResizedSize}px`;
275+
return isLast ? "minmax(min-content, auto)" : `${columnResizedSize}px`;
271276
}
272277
switch (c.width) {
273278
case "autoFit": {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useRef, useEffect } from "react";
2+
import { ColumnId } from "../../typings/GridColumn";
3+
4+
export type ColumnElementsMap<T> = Map<ColumnId, T>;
5+
6+
export type HeaderRefHook<T> = (id: ColumnId) => React.RefObject<T>;
7+
8+
export type GetColumnElementsMapFn<T> = (callback: (map: ColumnElementsMap<T>) => void) => void;
9+
10+
export function useColumnsElementsMap<T>(): [GetColumnElementsMapFn<T>, HeaderRefHook<T>] {
11+
const mapRef = useRef<ColumnElementsMap<T>>(new Map());
12+
// eslint-disable-next-line prefer-arrow-callback
13+
const { current: hook } = useRef<HeaderRefHook<T>>(function useHeaderRef(id: ColumnId): React.RefObject<T> {
14+
const headerRef = useRef<T>(null);
15+
16+
useEffect(() => {
17+
const map = mapRef.current;
18+
map.set(id, headerRef.current!);
19+
return () => {
20+
map.delete(id);
21+
};
22+
}, [id]);
23+
24+
return headerRef;
25+
});
26+
27+
const { current: getColumnElementsMap } = useRef<GetColumnElementsMapFn<T>>(callback => callback(mapRef.current));
28+
29+
return [getColumnElementsMap, hook];
30+
}

packages/pluggableWidgets/datagrid-web/src/features/model/use-grid-state.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useWatchValues } from "@mendix/widget-plugin-hooks/useWatchValues";
33
import { ColumnId } from "../../typings/GridColumn";
44
import * as Grid from "../../typings/GridModel";
55
import { sortByOrder } from "./utils";
6+
import { HeaderRefHook, useColumnsElementsMap } from "./resizing";
67

78
type NamedAction<Name, Fn> = Fn extends (arg: infer Payload) => void
89
? {
@@ -27,27 +28,44 @@ type InitArg = {
2728

2829
export type StateChangeFx = (prev: Grid.State, next: Grid.State) => void;
2930

31+
export type GridProps = {
32+
useHeaderRef: HeaderRefHook<HTMLDivElement>;
33+
};
34+
3035
export function useGridState(
3136
initParams: Grid.InitParams,
3237
columns: Grid.Columns,
3338
onStateChange: StateChangeFx
34-
): [Grid.State, Grid.Actions] {
39+
): [Grid.State, Grid.Actions, GridProps] {
3540
const [state, dispatch] = useReducer(gridStateReducer, { params: initParams, columns }, initGridState);
41+
const [getColumnElementsMap, useHeaderRef] = useColumnsElementsMap<HTMLDivElement>();
3642

3743
const memoizedGridActions = useMemo<Grid.Actions>(() => {
38-
return {
44+
const actions: Grid.Actions = {
3945
resize: payload => dispatch({ type: "resize", payload }),
4046
setFilter: payload => dispatch({ type: "setFilter", payload }),
4147
sortBy: payload => dispatch({ type: "sortBy", payload }),
4248
swap: payload => dispatch({ type: "swap", payload }),
43-
toggleHidden: payload => dispatch({ type: "toggleHidden", payload })
49+
toggleHidden: payload => dispatch({ type: "toggleHidden", payload }),
50+
setSize: payload => dispatch({ type: "setSize", payload }),
51+
createSizeSnapshot: () =>
52+
getColumnElementsMap(elementsMap =>
53+
actions.setSize(
54+
[...elementsMap.entries()].reduce<Grid.ColumnWidthConfig>((config, [id, element]) => {
55+
config[id] = element.clientWidth;
56+
return config;
57+
}, {})
58+
)
59+
)
4460
};
45-
}, [dispatch]);
61+
62+
return actions;
63+
}, [dispatch, getColumnElementsMap]);
4664

4765
useEffect(() => dispatch({ type: "setColumns", payload: columns }), [columns]);
4866
useWatchValues(([prevState], [newState]) => onStateChange(prevState, newState), [state]);
4967

50-
return [state, memoizedGridActions];
68+
return [state, memoizedGridActions, { useHeaderRef }];
5169
}
5270

5371
function gridStateReducer(state: Grid.State, action: Action): Grid.State {
@@ -99,6 +117,11 @@ function gridStateReducer(state: Grid.State, action: Action): Grid.State {
99117
visibleColumns: computeVisible(state.availableColumns, newHidden)
100118
};
101119
}
120+
case "setSize": {
121+
return { ...state, size: action.payload };
122+
}
123+
default:
124+
return state;
102125
}
103126
}
104127

packages/pluggableWidgets/datagrid-web/src/typings/GridModel.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ export interface State extends BaseState {
4747
}
4848

4949
export type Actions = {
50-
toggleHidden: (id: ColumnId) => void;
51-
sortBy: (id: ColumnId) => void;
52-
swap: (arg: [a: ColumnId, b: ColumnId]) => void;
50+
createSizeSnapshot: () => void;
5351
resize: (arg: [id: ColumnId, size: number]) => void;
5452
setFilter: (arg: Filter) => void;
53+
setSize: (arg: ColumnWidthConfig) => void;
54+
sortBy: (id: ColumnId) => void;
55+
swap: (arg: [a: ColumnId, b: ColumnId]) => void;
56+
toggleHidden: (id: ColumnId) => void;
5557
};

packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ export function mockWidgetProps(): WidgetProps<GridColumn, ObjectItem> {
8888
resize: jest.fn(),
8989
setFilter: jest.fn(),
9090
sortBy: jest.fn(),
91-
swap: jest.fn()
91+
swap: jest.fn(),
92+
setSize: jest.fn(),
93+
createSizeSnapshot: jest.fn()
9294
},
9395
valueForSort: () => "dummy",
9496
selectionStatus: "unknown",

0 commit comments

Comments
 (0)