Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Fix scroll to cell logic for keyboard navigating cells and drag selection with pinned columns #14550

Merged
merged 11 commits into from
Sep 23, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ export const useGridCellSelection = (
}

const { x: mouseX, y: mouseY } = mousePosition.current;
const { height, width } = dimensions.viewportInnerSize;
const { width, height: viewportOuterHeight } = dimensions.viewportOuterSize;
const height = viewportOuterHeight - totalHeaderHeight;

let deltaX = 0;
let deltaY = 0;
Expand Down Expand Up @@ -330,7 +331,7 @@ export const useGridCellSelection = (
}

autoScroll();
}, [apiRef, dimensions]);
}, [apiRef, dimensions, totalHeaderHeight]);

const handleCellMouseOver = React.useCallback<GridEventListener<'cellMouseOver'>>(
(params, event) => {
Expand All @@ -354,7 +355,8 @@ export const useGridCellSelection = (
}

const { x, y } = virtualScrollerRect;
const { height, width } = dimensions.viewportInnerSize;
const { width, height: viewportOuterHeight } = dimensions.viewportOuterSize;
const height = viewportOuterHeight - totalHeaderHeight;
const mouseX = event.clientX - x;
const mouseY = event.clientY - y - totalHeaderHeight;
mousePosition.current = { x: mouseX, y: mouseY };
Expand Down
83 changes: 63 additions & 20 deletions packages/x-data-grid/src/components/containers/GridRootStyles.ts
cherniavskii marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,38 @@ const columnHeaderStyles = {
},
};

function getPinnedBackgroundStyles({
backgroundColor,
selectedBackground,
hoverOpacity,
selectedOpacity,
}: {
backgroundColor: string;
selectedBackground: string;
hoverOpacity: number;
selectedOpacity: number;
}) {
const selectedBackgroundColor = blend(backgroundColor, selectedBackground, selectedOpacity);

const selectedHoverBackgroundColor = blend(
backgroundColor,
selectedBackground,
hoverOpacity + selectedOpacity,
);

return {
[`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: {
backgroundColor,
'&.Mui-selected': {
backgroundColor: selectedBackgroundColor,
'&:hover': {
backgroundColor: selectedHoverBackgroundColor,
},
},
},
};
}

const columnSeparatorTargetSize = 10;
const columnSeparatorOffset = -5;

Expand Down Expand Up @@ -171,15 +203,35 @@ export const GridRootStyles = styled('div', {
t.palette.action.selectedOpacity + t.palette.action.hoverOpacity,
);

const pinnedHoverBackground = t.vars
const pinnedBackgroundColor = t.vars
? hoverColor
: blend(pinnedBackground, hoverColor, hoverOpacity);
const pinnedSelectedBackground = t.vars
const pinnedHoverStyles = getPinnedBackgroundStyles({
hoverOpacity,
selectedOpacity,
selectedBackground,
backgroundColor: pinnedBackgroundColor,
});

const pinnedSelectedBackgroundColor = t.vars
? selectedBackground
: blend(pinnedBackground, selectedBackground, selectedOpacity);
const pinnedSelectedHoverBackground = t.vars
? hoverColor
: blend(pinnedSelectedBackground, hoverColor, hoverOpacity);
const pinnedSelectedStyles = getPinnedBackgroundStyles({
hoverOpacity,
selectedOpacity,
selectedBackground,
backgroundColor: pinnedSelectedBackgroundColor,
Copy link
Member

@cherniavskii cherniavskii Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pinnedSelectedBackgroundColor here can be either a CSS color value or a CSS variable.
getPinnedBackgroundStyles assumes it's a CSS color value and uses the blend function, which won't work with CSS variables.
This made me realize that we missed a test for the DataGrid rendered inside of the CssVarsProvider, so I opened #14662 to add one.

We have to make sure we don't try to blend CSS variables.
This also reminds me about this PR: #12901.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cherniavskii I am not sure of the best way to handle this.

color-mix would work well besides the versions of Safari that do not support it. It doesn't look like there is another way to blend colors with CSS variables otherwise.

I don't know if we can add an exception to our browser support specifically for cases where CssVarsProvider is used? 😅

The only other way I can think to achieve this effect with CSS variables is by having an inner element in pinned cells to apply the transparent hover/selected background-color to. Not sure this is viable though, considering the performance implications of having an extra element for every pinned cell. https://codepen.io/KenanYusuf/pen/WNqVXqM

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KenanYusuf, not sure it helps, but we ran into similar problems when migrating to DataGrid v7 due to our custom styles. Some of our pinned cells had backgrounds with alpha values. Therefore, the non-pinned cells were visible behind them.

We attempted to solve this using color-mix. The result worked but was very hard to read and extend in our case.

We opted for the two-element approach you described, but instead of adding one additional element per cell, we added one sticky background element with the width of the pinned cells.

Details

grafik

We added the sticky background to the row using DataGrid slots. We had to place it between row and pinned cells using z-index, but you could probably just make it a container element of the pinned cells.

The additional sticky background always has the same background color as the row. If the row is selected using row selection, the color of the additional sticky background changes too. If only a pinned cell is selected using cell selection, only the background color of the pinned cell changes.

We didn't run into noticeable performance problems. If you consider pinned left and pinned right, it's only two more divs in each row.

});

const pinnedSelectedHoverBackgroundColor = t.vars
? selectedHoverBackground
: blend(pinnedBackground, selectedHoverBackground, hoverOpacity + selectedOpacity);
const pinnedSelectedHoverStyles = getPinnedBackgroundStyles({
hoverOpacity,
selectedOpacity,
selectedBackground,
backgroundColor: pinnedSelectedHoverBackgroundColor,
});

const selectedStyles = {
backgroundColor: selectedBackground,
Expand Down Expand Up @@ -636,23 +688,14 @@ export const GridRootStyles = styled('div', {
position: 'sticky',
zIndex: 3,
background: 'var(--DataGrid-pinnedBackground)',
'&.Mui-selected': {
backgroundColor: pinnedSelectedBackgroundColor,
},
},
[`& .${c.virtualScrollerContent} .${c.row}`]: {
'&:hover': {
[`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: {
backgroundColor: pinnedHoverBackground,
},
},
[`&.Mui-selected`]: {
[`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: {
backgroundColor: pinnedSelectedBackground,
},
'&:hover': {
[`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: {
backgroundColor: pinnedSelectedHoverBackground,
},
},
},
'&:hover': pinnedHoverStyles,
'&.Mui-selected': pinnedSelectedStyles,
'&.Mui-selected:hover': pinnedSelectedHoverStyles,
},
[`& .${c.cellOffsetLeft}`]: {
flex: '0 0 auto',
Expand Down
40 changes: 20 additions & 20 deletions packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ import { gridDimensionsSelector } from '../dimensions';
// Logic copied from https://www.w3.org/TR/wai-aria-practices/examples/listbox/js/listbox.js
// Similar to https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
function scrollIntoView(dimensions: {
clientHeight: number;
scrollTop: number;
offsetHeight: number;
offsetTop: number;
containerSize: number;
scrollPosition: number;
elementSize: number;
elementOffset: number;
Copy link
Member Author

@KenanYusuf KenanYusuf Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the param names to be direction agnostic, since this is used for horizontal and vertical scrolling

}) {
const { clientHeight, scrollTop, offsetHeight, offsetTop } = dimensions;
const { containerSize, scrollPosition, elementSize, elementOffset } = dimensions;

const elementBottom = offsetTop + offsetHeight;
const elementEnd = elementOffset + elementSize;
// Always scroll to top when cell is higher than viewport to avoid scroll jump
// See https://github.com/mui/mui-x/issues/4513 and https://github.com/mui/mui-x/issues/4514
if (offsetHeight > clientHeight) {
return offsetTop;
if (elementSize > containerSize) {
return elementOffset;
}
if (elementBottom - clientHeight > scrollTop) {
return elementBottom - clientHeight;
if (elementEnd - containerSize > scrollPosition) {
return elementEnd - containerSize;
}
if (offsetTop < scrollTop) {
return offsetTop;
if (elementOffset < scrollPosition) {
return elementOffset;
}
return undefined;
}
Expand Down Expand Up @@ -96,10 +96,10 @@ export const useGridScroll = (
}
// When using RTL, `scrollLeft` becomes negative, so we must ensure that we only compare values.
scrollCoordinates.left = scrollIntoView({
clientHeight: dimensions.viewportInnerSize.width,
scrollTop: Math.abs(virtualScrollerRef.current!.scrollLeft),
offsetHeight: cellWidth,
offsetTop: columnPositions[params.colIndex],
containerSize: dimensions.viewportOuterSize.width,
scrollPosition: Math.abs(virtualScrollerRef.current!.scrollLeft),
elementSize: cellWidth,
elementOffset: columnPositions[params.colIndex],
});
}
if (params.rowIndex !== undefined) {
Expand All @@ -116,10 +116,10 @@ export const useGridScroll = (
: rowsMeta.currentPageTotalHeight - rowsMeta.positions[elementIndex];

scrollCoordinates.top = scrollIntoView({
clientHeight: dimensions.viewportInnerSize.height,
scrollTop: virtualScrollerRef.current!.scrollTop,
offsetHeight: targetOffsetHeight,
offsetTop: rowsMeta.positions[elementIndex],
containerSize: dimensions.viewportInnerSize.height,
scrollPosition: virtualScrollerRef.current!.scrollTop,
elementSize: targetOffsetHeight,
elementOffset: rowsMeta.positions[elementIndex],
});
}

Expand Down