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

Remove InteractionMask #2061

Merged
merged 43 commits into from Jul 15, 2020
Merged

Remove InteractionMask #2061

merged 43 commits into from Jul 15, 2020

Conversation

amanmahajan7
Copy link
Contributor

@amanmahajan7 amanmahajan7 commented Jun 23, 2020

Scrolling profile

Before

image

After

image

It is more or less the same.

@amanmahajan7 amanmahajan7 self-assigned this Jun 23, 2020
src/utils/selectedCellUtils.ts Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated
<div style={{ height: Math.max(rows.length * rowHeight, clientHeight) }} />
{getViewportRows()}
{viewportWidth > 0 && getViewportRows()}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this change? Does something break when we have viewportWidth === 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it was causing the same issue where the grid is initially scrolled

src/DataGrid.tsx Outdated
Comment on lines 218 to 220
const [selectedPosition, setSelectedPosition] = useState<SelectCellState | EditCellState>({ idx: -1, rowIdx: -1, mode: 'SELECT' });
const [copiedPosition, setCopiedPosition] = useState<Position & { value: unknown } | null>(null);
const [draggedOverRowIdx, setDraggedOverRowIdx] = useState<number | undefined>(undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want we can move some state/effects into a non-reusable custom hook, so we can keep the logic in smaller units.

@@ -206,6 +215,9 @@ function DataGrid<R, K extends keyof R, SR>({
const [scrollTop, setScrollTop] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
const [columnWidths, setColumnWidths] = useState<ReadonlyMap<string, number>>(() => new Map());
const [selectedPosition, setSelectedPosition] = useState<SelectCellState | EditCellState>({ idx: -1, rowIdx: -1, mode: 'SELECT' });
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use null as the "nothing selected" state instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this makes sense to stand for defaultSelectionState. In addition, we can skip empty check for this usage.

https://github.com/adazzle/react-data-grid/pull/2061/files#diff-60532bd8286dc6c925b8ea6ee3e2573bR436

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is okay to use a new { idx: -1, rowIdx: -1 } for now. Let's revisit once we add hooks and cleanup code

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/Cell.tsx Show resolved Hide resolved
src/Cell.tsx Outdated
@@ -72,6 +111,15 @@ function Cell<R, SR>({
isRowSelected={isRowSelected}
onRowSelectionChange={onRowSelectionChange}
/>
{enableCellDragAndDrop && isSelected && (
Copy link
Contributor

@qili26 qili26 Jun 28, 2020

Choose a reason for hiding this comment

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

I am thinking instead of keeping this being rendered in the cell component, how about rendering the draggable square to a portal? I see this weird render result for the hover effect. The right bottom corner of the square is not displaying outside of the cell. (MacOS with Chrome)

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated
@@ -129,8 +145,6 @@ export interface DataGridProps<R, K extends keyof R, SR = unknown> {
*/
/** Toggles whether filters row is displayed or not */
enableFilters?: boolean;
/** Toggles whether cells should be autofocused */
enableCellAutoFocus?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

What was this for at first? Making (0,0) cell selected upon mounting the grid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. I think we should not steal the focus and let the user select the first cell using grid.selectCell

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated
@@ -670,6 +673,7 @@ function DataGrid<R, K extends keyof R, SR>({
rowClass={rowClass}
top={rowIdx * rowHeight + totalHeaderHeight}
setDraggedOverRowIdx={isDragging ? setDraggedOverRowIdx : undefined}
dragHandle={selectedPosition.rowIdx === rowIdx ? getDragHandle() : undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks weird combining this condition with the checks inside the getDragHandle function at ln#635. I think it will be better for maintenance to either add a checking outside like this

dragHandle={displayDragHandle() ? getDragHandle() : undefined} // remove the checks from getDragHandle

or completely move all conditions inside getDragHandle like this

function getDragHandle() {
    if (selectedPosition.rowIdx !== rowIdx || !enableCellDragAndDrop || !isCellEditable(selectedPosition) || selectedPosition.mode === 'EDIT') return null;
    ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is fine. We need to add dragHandle only on the row that has the selected position. I will have to change the signature otherwise getDragHandle(rowIdx). I am happy to revisit after code cleanup

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Qi. We can also do return instead of return null in getDragHandle.

@amanmahajan7 amanmahajan7 marked this pull request as ready for review July 1, 2020 02:40
@amanmahajan7
Copy link
Contributor Author

@nstepien is it worth adding a new Viewport component? Currently we render header and summary rows on each cell navigation. Header and SummaryRow are memoized so I am not sure if there will be any performance gain. It however does extract Viewport logic in a separate component which may be easier to maintain. Thoughts?

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated
});

useEffect(() => {
latestDraggedOverRowIdx.current = draggedOverRowIdx;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of doing this, let's have a setter to wrap both setDraggedOverRowIdx and latestDraggedOverRowIdx.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We are using setDraggedOverRowIdx={isDragging ? setDraggedOverRowIdx : undefined}. When the drag starts we rerender all the rows but on subsequent render we only render the row that fires onMouseEnter. If we add a setter then all the rows will be rerendered during dragging, We can use useCallback to prevent that. What do you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer useCallback over useEffect for this. I feel like useEffect could cause rare timing issues.

src/DataGrid.tsx Outdated
setDraggedOverRowIdx(undefined);
}

function handleMouseDown() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should check that we initiate dragging with the primary mouse button, so we can at least avoid a re-render.

src/DataGrid.tsx Outdated
@@ -371,6 +548,88 @@ function DataGrid<R, K extends keyof R, SR>({
}
}

function isDraggedOver(currentRowIdx: number): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want, we can reduce the size of the file by moving utils like this one into a separate module, we'd just need to pass more parameters.

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Outdated
@@ -670,6 +673,7 @@ function DataGrid<R, K extends keyof R, SR>({
rowClass={rowClass}
top={rowIdx * rowHeight + totalHeaderHeight}
setDraggedOverRowIdx={isDragging ? setDraggedOverRowIdx : undefined}
dragHandle={selectedPosition.rowIdx === rowIdx ? getDragHandle() : undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Qi. We can also do return instead of return null in getDragHandle.

src/DataGrid.tsx Outdated Show resolved Hide resolved
src/Cell.tsx Outdated Show resolved Hide resolved
src/Cell.tsx Outdated
Comment on lines 59 to 63
'rdg-cell-frozen': column.frozen,
'rdg-cell-frozen-last': column.idx === lastFrozenColumnIndex
'rdg-cell-frozen-last': column.idx === lastFrozenColumnIndex,
'rdg-cell-selected': isSelected,
'rdg-cell-copied': isCopied,
'rdg-cell-dragged-over': isDraggedOver
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should set all these classes and event handlers at the row level, that way we can pass fewer props down to the Cell. Though that might require some useCallback 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We probably can. Let's revisit in the next PR

src/DataGrid.tsx Outdated Show resolved Hide resolved
Mahajan and others added 4 commits July 6, 2020 10:57
Select cell only on update
Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com>
src/Cell.tsx Outdated Show resolved Hide resolved
src/common/types.ts Show resolved Hide resolved
onCommitCancel: () => void;
}

export type SelectedCellProps = {
Copy link
Contributor

Choose a reason for hiding this comment

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

This type feels strange, shouldn't we extract the same fields and only differentiate the diffs?

interface SelectedCellPropsBase {
  idx: number;
  onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
}

interface SelectedCellPropsEdit extends SelectedCellPropsBase {
  mode: 'EDIT';
  editorContainerProps: SharedEditorContainerProps;
}

interface SelectedCellPropsSelect extends SelectedCellPropsBase {
  mode: 'SELECT';
  dragHandleProps?: Pick<React.HTMLAttributes<HTMLDivElement>, 'onMouseDown' | 'onDoubleClick'>;
}

export type SelectedCellProps = SelectedCellPropsEdit | SelectedCellPropsSelect;

onRowSelectionChange={onRowSelectionChange}
/>
{selectedCellProps?.dragHandleProps && (
<div className="rdg-cell-drag-handle" {...selectedCellProps.dragHandleProps} />
Copy link
Contributor

Choose a reason for hiding this comment

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

TBH, I don't know if is a good UI. The drag handle looks weird to me when it is expending inside.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is difficult to style it from what I can tell, @nstepien is there a way to make it look like the old drag handle?

Copy link
Contributor

Choose a reason for hiding this comment

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

We can't make it overflow without degrading performance, we could use a portal but then it wouldn't be in perfect sync when scrolling.

import { CellNavigationMode, SortDirection, UpdateActions } from './common/enums';

interface SelectCellState extends Position {
mode: 'SELECT';
Copy link
Contributor

Choose a reason for hiding this comment

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

Combining with my type comment here. https://github.com/adazzle/react-data-grid/pull/2061/files#diff-6859ab7932058080e7f916f3bd133a80R108

I think we can do the following

interface SelectCellMode {
  mode: 'SELECT';
}

interface SelectCellState extends Position, SelectCellMode {};

Further, the SelectCellMode can be reused in the types.ts for the props definition

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it is fine as we only have 2 places where we define mode. It feels like we may be adding too many interfaces with this approach.

src/DataGrid.tsx Show resolved Hide resolved
src/DataGrid.tsx Outdated Show resolved Hide resolved
src/DataGrid.tsx Show resolved Hide resolved
return isDraggedOver ? selectedPosition.idx : undefined;
}

function getSelectedCellProps(rowIdx: number): SelectedCellProps | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is called on each render, and then passed the return result into the Row. I am wondering if we shall add memorization to the output to prevent unnecessary Row re-render.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is only passed to the row containing the selected cell so we only rerender 2 rows max. The performance is good and it is tricky to memoize this function. I think it is fine for now

qili26
qili26 previously approved these changes Jul 8, 2020
Copy link
Contributor

@qili26 qili26 left a comment

Choose a reason for hiding this comment

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

🚢

@amanmahajan7
Copy link
Contributor Author

I faced the focus issue again while profiling. I think React is not able to register the change in the focused element. I will try adding keydown on the root element

return shiftKey ? { idx: columns.length - 1, rowIdx: rows.length - 1 } : { idx: 0, rowIdx: 0 };
}
return { idx: idx + (shiftKey ? -1 : 1), rowIdx };
case 'Home':
Copy link
Contributor Author

Choose a reason for hiding this comment

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

style/cell.less Outdated
Comment on lines 53 to 55
border-left: 1px dashed black;
border-right: 1px dashed black;
padding-left: 7px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we remove these three lines?
I don't know what the padding does either.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Each cell only has the right and the bottom border and we need to set left and right border when dragged over. This shifts the cell content so I had to change the left-padding to 8px- 1px (border-left-width)

padding: 0 8px;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The last dragged over cell should have a bottom border but we will fix it later

src/DataGrid.tsx Outdated
prevSelectedPosition.current = selectedPosition;
scrollToCell(selectedPosition);
if (document.activeElement !== rowsContainerRef.current) {
rowsContainerRef.current!.focus({ preventScroll: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

TIL preventScroll
https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus
Looks like it may not be supported by Safari, dunno if that'll be an issue or not going forward.

Copy link
Contributor

@qili26 qili26 Jul 14, 2020

Choose a reason for hiding this comment

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

Sticky css is not working well with Safari either. Not sure if we discussed that as well. If we had, perhaps this wouldn't be an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I implemented focus in a different way and { preventScroll } is no longer needed as the focused element never needs to be scrolled. I think in the next PR we can push focusing logic to the next level

  • Scroll the the last selected cell on focus
  • Tabbing in an out of the grid can be further improved
  • We can set tabIndex=-1 on the checkbox selector and pass a flag isCellActive = isSelected && isFocused flag to formatters. Then the formatters can choose to focus the checkbox when the cell is active. This way keyboard selection would work. Same thing can be done for treeview example.

src/DataGrid.tsx Outdated
height: Math.max(rows.length * rowHeight, clientHeight),
outline: 0,
position: 'relative',
top: -totalHeaderHeight // Prevents scrolling when grid receives focus via keyboard
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move these to core.less

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants