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
Remove InteractionMask #2061
Conversation
src/DataGrid.tsx
Outdated
<div style={{ height: Math.max(rows.length * rowHeight, clientHeight) }} /> | ||
{getViewportRows()} | ||
{viewportWidth > 0 && getViewportRows()} |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
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); |
There was a problem hiding this comment.
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' }); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com>
src/Cell.tsx
Outdated
@@ -72,6 +111,15 @@ function Cell<R, SR>({ | |||
isRowSelected={isRowSelected} | |||
onRowSelectionChange={onRowSelectionChange} | |||
/> | |||
{enableCellDragAndDrop && isSelected && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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
@@ -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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@@ -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} |
There was a problem hiding this comment.
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;
...
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
@nstepien is it worth adding a new |
src/DataGrid.tsx
Outdated
}); | ||
|
||
useEffect(() => { | ||
latestDraggedOverRowIdx.current = draggedOverRowIdx; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
@@ -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} |
There was a problem hiding this comment.
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/Cell.tsx
Outdated
'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 |
There was a problem hiding this comment.
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
🤔
There was a problem hiding this comment.
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
Select cell only on update
Co-authored-by: Nicolas Stepien <567105+nstepien@users.noreply.github.com>
src/common/types.ts
Outdated
onCommitCancel: () => void; | ||
} | ||
|
||
export type SelectedCellProps = { |
There was a problem hiding this comment.
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} /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
return isDraggedOver ? selectedPosition.idx : undefined; | ||
} | ||
|
||
function getSelectedCellProps(rowIdx: number): SelectedCellProps | undefined { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢
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': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style/cell.less
Outdated
border-left: 1px dashed black; | ||
border-right: 1px dashed black; | ||
padding-left: 7px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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)
react-data-grid/style/cell.less
Line 5 in 6ffb06a
padding: 0 8px; |
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 flagisCellActive = 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 |
There was a problem hiding this comment.
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
Scrolling profile
Before
After
It is more or less the same.