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

Use context for selection #2338

Merged
merged 15 commits into from
May 13, 2021
8 changes: 0 additions & 8 deletions src/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ function Cell<R, SR>({
isCellSelected,
isCopied,
isDraggedOver,
isRowSelected,
row,
rowIdx,
dragHandleProps,
Expand All @@ -57,7 +56,6 @@ function Cell<R, SR>({
onContextMenu,
onRowChange,
selectCell,
selectRow,
...props
}: CellRendererProps<R, SR>, ref: React.Ref<HTMLDivElement>) {
const { cellClass } = column;
Expand Down Expand Up @@ -96,10 +94,6 @@ function Cell<R, SR>({
onRowChange(rowIdx, newRow);
}

function onRowSelectionChange(checked: boolean, isShiftClick: boolean) {
selectRow({ rowIdx, checked, isShiftClick });
}

return (
<div
role="gridcell"
Expand All @@ -120,8 +114,6 @@ function Cell<R, SR>({
rowIdx={rowIdx}
row={row}
isCellSelected={isCellSelected}
isRowSelected={isRowSelected}
onRowSelectionChange={onRowSelectionChange}
onRowChange={handleRowChange}
/>
{dragHandleProps && (
Expand Down
64 changes: 38 additions & 26 deletions src/Columns.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
import { SelectCellFormatter } from './formatters';
import type { Column } from './types';
import { useRowSelection, useRowSelectionChange } from './hooks';
import type { Column, FormatterProps, GroupFormatterProps } from './types';
import { stopPropagation } from './utils/domUtils';

export const SELECT_COLUMN_KEY = 'select-row';

function SelectFormatter(props: FormatterProps) {
const isRowSelected = useRowSelection();
const onRowSelectionChange = useRowSelectionChange();

return (
<SelectCellFormatter
aria-label="Select"
tabIndex={-1}
isCellSelected={props.isCellSelected}
value={isRowSelected}
onClick={stopPropagation}
onChange={onRowSelectionChange}
/>
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function SelectGroupFormatter(props: GroupFormatterProps<any, any>) {
const isRowSelected = useRowSelection();
const onRowSelectionChange = useRowSelectionChange();

return (
<SelectCellFormatter
aria-label="Select Group"
tabIndex={-1}
isCellSelected={props.isCellSelected}
value={isRowSelected}
onChange={onRowSelectionChange}
// Stop propagation to prevent row selection
onClick={stopPropagation}
/>
);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const SelectColumn: Column<any, any> = {
key: SELECT_COLUMN_KEY,
Expand All @@ -22,29 +57,6 @@ export const SelectColumn: Column<any, any> = {
/>
);
},
formatter(props) {
return (
<SelectCellFormatter
aria-label="Select"
tabIndex={-1}
isCellSelected={props.isCellSelected}
value={props.isRowSelected}
onClick={stopPropagation}
onChange={props.onRowSelectionChange}
/>
);
},
groupFormatter(props) {
return (
<SelectCellFormatter
aria-label="Select Group"
tabIndex={-1}
isCellSelected={props.isCellSelected}
value={props.isRowSelected}
onChange={props.onRowSelectionChange}
// Stop propagation to prevent row selection
onClick={stopPropagation}
/>
);
}
formatter: SelectFormatter,
groupFormatter: SelectGroupFormatter
};
12 changes: 0 additions & 12 deletions src/GroupCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ import { cellSelectedClassname } from './style';

type SharedGroupRowRendererProps<R, SR> = Pick<GroupRowRendererProps<R, SR>,
| 'id'
| 'rowIdx'
| 'groupKey'
| 'childRows'
| 'isExpanded'
| 'isRowSelected'
| 'selectRow'
| 'toggleGroup'
>;

Expand All @@ -24,25 +21,18 @@ interface GroupCellProps<R, SR> extends SharedGroupRowRendererProps<R, SR> {

function GroupCell<R, SR>({
id,
rowIdx,
groupKey,
childRows,
isExpanded,
isCellSelected,
isRowSelected,
column,
groupColumnIndex,
selectRow,
toggleGroup: toggleGroupWrapper
}: GroupCellProps<R, SR>) {
function toggleGroup() {
toggleGroupWrapper(id);
}

function onRowSelectionChange(checked: boolean) {
selectRow({ rowIdx, checked, isShiftClick: false });
}

// Only make the cell clickable if the group level matches
const isLevelMatching = column.rowGroup && groupColumnIndex === column.idx;

Expand All @@ -67,8 +57,6 @@ function GroupCell<R, SR>({
column={column}
isExpanded={isExpanded}
isCellSelected={isCellSelected}
isRowSelected={isRowSelected}
onRowSelectionChange={onRowSelectionChange}
toggleGroup={toggleGroup}
/>
)}
Expand Down
36 changes: 21 additions & 15 deletions src/GroupRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { memo } from 'react';
import { memo, useCallback } from 'react';
import clsx from 'clsx';

import { groupRowClassname, groupRowSelectedClassname, rowClassname, rowSelectedClassname } from './style';
import { SELECT_COLUMN_KEY } from './Columns';
import GroupCell from './GroupCell';
import type { CalculatedColumn, Position, SelectRowEvent, Omit } from './types';
import { RowSelectionChangeContext, RowSelectionContext } from './hooks';

export interface GroupRowRendererProps<R, SR = unknown> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style' | 'children'> {
id: string;
Expand Down Expand Up @@ -38,6 +39,10 @@ function GroupedRow<R, SR>({
toggleGroup,
...props
}: GroupRowRendererProps<R, SR>) {
const onRowSelectionChange = useCallback((checked: boolean) => {
selectRow({ rowIdx, checked, isShiftClick: false });
}, [rowIdx, selectRow]);

// Select is always the first column
const idx = viewportColumns[0].key === SELECT_COLUMN_KEY ? level + 1 : level;

Expand All @@ -63,20 +68,21 @@ function GroupedRow<R, SR>({
{...props}
>
{viewportColumns.map(column => (
<GroupCell<R, SR>
key={column.key}
id={id}
rowIdx={rowIdx}
groupKey={groupKey}
childRows={childRows}
isExpanded={isExpanded}
isRowSelected={isRowSelected}
isCellSelected={selectedCellIdx === column.idx}
column={column}
groupColumnIndex={idx}
selectRow={selectRow}
toggleGroup={toggleGroup}
/>
<RowSelectionChangeContext.Provider value={onRowSelectionChange}>
<RowSelectionContext.Provider value={isRowSelected}>
<GroupCell<R, SR>
key={column.key}
id={id}
groupKey={groupKey}
childRows={childRows}
isExpanded={isExpanded}
isCellSelected={selectedCellIdx === column.idx}
column={column}
groupColumnIndex={idx}
toggleGroup={toggleGroup}
/>
</RowSelectionContext.Provider>
</RowSelectionChangeContext.Provider>
))}
</div>
);
Expand Down
43 changes: 25 additions & 18 deletions src/Row.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { memo, forwardRef } from 'react';
import { memo, forwardRef, useCallback } from 'react';
import type { RefAttributes } from 'react';
import clsx from 'clsx';

import { groupRowSelectedClassname, rowClassname, rowSelectedClassname } from './style';
import Cell from './Cell';
import EditCell from './EditCell';
import type { RowRendererProps, SelectedCellProps } from './types';
import { RowSelectionChangeContext, RowSelectionContext } from './hooks';

function Row<R, SR = unknown>({
cellRenderer: CellRenderer = Cell,
Expand All @@ -29,6 +30,10 @@ function Row<R, SR = unknown>({
'aria-selected': ariaSelected,
...props
}: RowRendererProps<R, SR>, ref: React.Ref<HTMLDivElement>) {
const onRowSelectionChange = useCallback((checked: boolean, isShiftClick: boolean) => {
selectRow({ rowIdx, checked, isShiftClick });
}, [rowIdx, selectRow]);

function handleDragEnter(event: React.MouseEvent<HTMLDivElement>) {
setDraggedOverRowIdx?.(rowIdx);
onMouseEnter?.(event);
Expand Down Expand Up @@ -71,23 +76,25 @@ function Row<R, SR = unknown>({
}

return (
<CellRenderer
key={column.key}
rowIdx={rowIdx}
column={column}
row={row}
isCopied={copiedCellIdx === column.idx}
isDraggedOver={draggedOverCellIdx === column.idx}
isCellSelected={isCellSelected}
isRowSelected={isRowSelected}
dragHandleProps={isCellSelected ? (selectedCellProps as SelectedCellProps).dragHandleProps : undefined}
onFocus={isCellSelected ? (selectedCellProps as SelectedCellProps).onFocus : undefined}
onKeyDown={isCellSelected ? selectedCellProps!.onKeyDown : undefined}
onRowClick={onRowClick}
onRowChange={onRowChange}
selectCell={selectCell}
selectRow={selectRow}
/>
<RowSelectionChangeContext.Provider value={onRowSelectionChange}>
<RowSelectionContext.Provider value={isRowSelected}>
amanmahajan7 marked this conversation as resolved.
Show resolved Hide resolved
<CellRenderer
key={column.key}
rowIdx={rowIdx}
column={column}
row={row}
isCopied={copiedCellIdx === column.idx}
isDraggedOver={draggedOverCellIdx === column.idx}
isCellSelected={isCellSelected}
dragHandleProps={isCellSelected ? (selectedCellProps as SelectedCellProps).dragHandleProps : undefined}
onFocus={isCellSelected ? (selectedCellProps as SelectedCellProps).onFocus : undefined}
onKeyDown={isCellSelected ? selectedCellProps!.onKeyDown : undefined}
onRowClick={onRowClick}
onRowChange={onRowChange}
selectCell={selectCell}
/>
</RowSelectionContext.Provider>
</RowSelectionChangeContext.Provider>
);
})}
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export * from './useViewportColumns';
export * from './useViewportRows';
export * from './useFocusRef';
export * from './useLatestFunc';
export * from './useRowSelection';
export * from './useRowSelectionChange';
12 changes: 12 additions & 0 deletions src/hooks/useRowSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContext, useContext } from 'react';

export const RowSelectionContext =
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 export the Provider directly here instead, that way we save a property access during render.

Also can we merge the two contexts into one?

createContext<boolean | undefined>(undefined);

export function useRowSelection() {
const context = useContext(RowSelectionContext);
if (context === undefined) {
throw new Error('useRowSelection must be used within a RowSelectionContext');
}
return context;
}
12 changes: 12 additions & 0 deletions src/hooks/useRowSelectionChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContext, useContext } from 'react';

export const RowSelectionChangeContext =
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

createContext<(((checked: boolean, isShiftClick: boolean) => void) | undefined)>(undefined);

export function useRowSelectionChange() {
const context = useContext(RowSelectionChangeContext);
if (context === undefined) {
throw new Error('useRowSelectionChange must be used within a RowSelectionChangeContext');
}
return context;
}
6 changes: 0 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ export interface FormatterProps<TRow = any, TSummaryRow = any> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
isCellSelected: boolean;
isRowSelected: boolean;
onRowSelectionChange: (checked: boolean, isShiftClick: boolean) => void;
onRowChange: (row: Readonly<TRow>) => void;
}

Expand All @@ -96,8 +94,6 @@ export interface GroupFormatterProps<TRow, TSummaryRow = unknown> {
childRows: readonly TRow[];
isExpanded: boolean;
isCellSelected: boolean;
isRowSelected: boolean;
onRowSelectionChange: (checked: boolean) => void;
toggleGroup: () => void;
}

Expand Down Expand Up @@ -148,12 +144,10 @@ export interface CellRendererProps<TRow, TSummaryRow = unknown> extends Omit<Rea
isCopied: boolean;
isDraggedOver: boolean;
isCellSelected: boolean;
isRowSelected: boolean;
dragHandleProps?: Pick<React.HTMLAttributes<HTMLDivElement>, 'onMouseDown' | 'onDoubleClick'>;
onRowChange: (rowIdx: number, newRow: TRow) => void;
onRowClick?: (rowIdx: number, row: TRow, column: CalculatedColumn<TRow, TSummaryRow>) => void;
selectCell: (position: Position, enableEditor?: boolean) => void;
selectRow: (selectRowEvent: SelectRowEvent) => void;
}

export interface RowRendererProps<TRow, TSummaryRow = unknown> extends Omit<React.HTMLAttributes<HTMLDivElement>, 'style' | 'children'> {
Expand Down