Skip to content

Commit 7091789

Browse files
authored
Add support for inclusive selection mode (#1102)
* Add support for inclusive selection mode * Rename to additive * Use markdown * Add unit tests
1 parent 84074af commit 7091789

File tree

4 files changed

+133
-19
lines changed

4 files changed

+133
-19
lines changed

packages/core/API.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -993,12 +993,12 @@ When range select is set to cell, only one cell may be selected at a time. When
993993
## rangeSelectionBlending
994994

995995
```ts
996-
rangeSelectionBlending?: "exclusive" | "mixed"; // default exclusive
997-
columnSelectionBlending?: "exclusive" | "mixed"; // default exclusive
998-
rowSelectionBlending?: "exclusive" | "mixed"; // default exclusive
996+
rangeSelectionBlending?: "exclusive" | "mixed" | "additive"; // default exclusive
997+
columnSelectionBlending?: "exclusive" | "mixed" | "additive"; // default exclusive
998+
rowSelectionBlending?: "exclusive" | "mixed" | "additive"; // default exclusive
999999
```
10001000

1001-
Controls which types of selections can exist at the same time in the grid. If selection blending is set to exclusive, the grid will clear other types of selections when the exclusive selection is made. By default row, column, and range selections are exclusive.
1001+
Controls which types of selections can exist at the same time in the grid. If selection blending is set to `exclusive`, the grid will clear other types of selections when the exclusive selection is made. By default row, column, and range selections are exclusive. If `mixed` is set, other types of selections are kept only when a multi-key (e.g., Cmd/Ctrl) is held. If `additive` is set, other types of selections are always kept; selections accumulate without a modifier.
10021002

10031003
---
10041004

packages/core/src/docs/examples/input-blending.stories.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export default {
2525
};
2626

2727
interface InputBlendingGridProps {
28-
rangeBlending: "mixed" | "exclusive";
29-
columnBlending: "mixed" | "exclusive";
30-
rowBlending: "mixed" | "exclusive";
28+
rangeBlending: "mixed" | "exclusive" | "additive";
29+
columnBlending: "mixed" | "exclusive" | "additive";
30+
rowBlending: "mixed" | "exclusive" | "additive";
3131
rangeMultiSelect: "none" | "cell" | "rect" | "multi-cell" | "multi-rect";
3232
columnMultiSelect: "none" | "single" | "multi";
3333
rowMultiSelect: "none" | "single" | "multi";
@@ -77,15 +77,15 @@ export const InputBlending: React.FC<InputBlendingGridProps> = p => {
7777
(InputBlending as any).argTypes = {
7878
rangeBlending: {
7979
control: { type: "select" },
80-
options: ["mixed", "exclusive"],
80+
options: ["mixed", "exclusive", "additive"],
8181
},
8282
columnBlending: {
8383
control: { type: "select" },
84-
options: ["mixed", "exclusive"],
84+
options: ["mixed", "exclusive", "additive"],
8585
},
8686
rowBlending: {
8787
control: { type: "select" },
88-
options: ["mixed", "exclusive"],
88+
options: ["mixed", "exclusive", "additive"],
8989
},
9090
rangeMultiSelect: {
9191
control: { type: "select" },

packages/core/src/internal/data-grid/use-selection-behavior.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { CompactSelection, type GridSelection, type Slice } from "./data-grid-ty
33

44
type SetCallback = (newVal: GridSelection, expand: boolean) => void;
55

6-
export type SelectionBlending = "exclusive" | "mixed";
6+
/**
7+
* The type of selection blending to use:
8+
* - `exclusive`: Only one type of selection can be made at a time.
9+
* - `mixed`: Multiple types of selection can be made at a time, but only when a multi-key (e.g., Cmd/Ctrl) is held.
10+
* - `additive`: Multiple types of selection can be made at a time, and selections accumulate without a modifier.
11+
*/
12+
export type SelectionBlending = "exclusive" | "mixed" | "additive";
713

814
type SelectionTrigger = "click" | "drag" | "keyboard-nav" | "keyboard-select" | "edit";
915

@@ -47,16 +53,17 @@ export function useSelectionBehavior(
4753
};
4854
}
4955

50-
const rangeMixable = rangeBehavior === "mixed" && (append || trigger === "drag");
51-
const allowColumnCoSelect = columnBehavior === "mixed" && rangeMixable;
52-
const allowRowCoSelect = rowBehavior === "mixed" && rangeMixable;
56+
const rangeMixable =
57+
(rangeBehavior === "mixed" || rangeBehavior === "additive") && (append || trigger === "drag");
58+
const allowColumnCoSelect = (columnBehavior === "mixed" || columnBehavior === "additive") && rangeMixable;
59+
const allowRowCoSelect = (rowBehavior === "mixed" || rowBehavior === "additive") && rangeMixable;
5360
let newVal: GridSelection = {
5461
current:
5562
value === undefined
5663
? undefined
5764
: {
5865
...value,
59-
rangeStack: trigger === "drag" ? gridSelection.current?.rangeStack ?? [] : [],
66+
rangeStack: trigger === "drag" ? (gridSelection.current?.rangeStack ?? []) : [],
6067
},
6168
columns: allowColumnCoSelect ? gridSelection.columns : CompactSelection.empty(),
6269
rows: allowRowCoSelect ? gridSelection.rows : CompactSelection.empty(),
@@ -99,8 +106,8 @@ export function useSelectionBehavior(
99106
rows: newRows,
100107
};
101108
} else {
102-
const rangeMixed = allowMixed && rangeBehavior === "mixed";
103-
const columnMixed = allowMixed && columnBehavior === "mixed";
109+
const rangeMixed = (allowMixed && rangeBehavior === "mixed") || rangeBehavior === "additive";
110+
const columnMixed = (allowMixed && columnBehavior === "mixed") || columnBehavior === "additive";
104111
const current = !rangeMixed ? undefined : gridSelection.current;
105112
newVal = {
106113
current,
@@ -127,8 +134,8 @@ export function useSelectionBehavior(
127134
columns: newCols,
128135
};
129136
} else {
130-
const rangeMixed = allowMixed && rangeBehavior === "mixed";
131-
const rowMixed = allowMixed && rowBehavior === "mixed";
137+
const rangeMixed = (allowMixed && rangeBehavior === "mixed") || rangeBehavior === "additive";
138+
const rowMixed = (allowMixed && rowBehavior === "mixed") || rowBehavior === "additive";
132139
const current = !rangeMixed ? undefined : gridSelection.current;
133140
newVal = {
134141
current,

packages/core/test/data-editor-input.test.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,111 @@ a new line char ""more quotes"" plus a tab ." https://google.com`)
573573
rows: CompactSelection.empty(),
574574
});
575575
});
576+
577+
test("Select row - single-cell/single-row with additive row blending (no modifiers)", async () => {
578+
const spy = vi.fn();
579+
vi.useFakeTimers();
580+
render(
581+
<EventedDataEditor
582+
{...basicProps}
583+
gridSelection={{
584+
current: {
585+
cell: [1, 2],
586+
range: { height: 1, width: 1, x: 1, y: 2 },
587+
rangeStack: [],
588+
},
589+
columns: CompactSelection.empty(),
590+
rows: CompactSelection.empty(),
591+
}}
592+
rangeSelect="cell"
593+
rowSelect="single"
594+
rowSelectionBlending="additive"
595+
rangeSelectionBlending="additive"
596+
onGridSelectionChange={spy}
597+
/>,
598+
{
599+
wrapper: Context,
600+
}
601+
);
602+
prep();
603+
const canvas = screen.getByTestId("data-grid-canvas");
604+
605+
fireEvent.pointerDown(canvas, {
606+
clientX: 20,
607+
clientY: 36 + 32 * 3 + 16,
608+
});
609+
610+
fireEvent.pointerMove(canvas, {
611+
clientX: 20,
612+
clientY: 36 + 32 * 3 + 16,
613+
});
614+
615+
fireEvent.pointerUp(canvas, {
616+
clientX: 20,
617+
clientY: 36 + 32 * 3 + 16,
618+
});
619+
620+
expect(spy).toBeCalledWith({
621+
current: {
622+
cell: [1, 2],
623+
range: { height: 1, width: 1, x: 1, y: 2 },
624+
rangeStack: [],
625+
},
626+
columns: CompactSelection.empty(),
627+
rows: CompactSelection.fromSingleSelection(3),
628+
});
629+
});
630+
631+
test("Select col - single-cell with additive col blending (no modifiers)", async () => {
632+
const spy = vi.fn();
633+
vi.useFakeTimers();
634+
render(
635+
<EventedDataEditor
636+
{...basicProps}
637+
gridSelection={{
638+
current: {
639+
cell: [1, 2],
640+
range: { height: 1, width: 1, x: 1, y: 2 },
641+
rangeStack: [],
642+
},
643+
columns: CompactSelection.empty(),
644+
rows: CompactSelection.empty(),
645+
}}
646+
rangeSelect="cell"
647+
columnSelectionBlending="additive"
648+
rangeSelectionBlending="additive"
649+
onGridSelectionChange={spy}
650+
/>,
651+
{
652+
wrapper: Context,
653+
}
654+
);
655+
prep();
656+
const canvas = screen.getByTestId("data-grid-canvas");
657+
658+
fireEvent.pointerDown(canvas, {
659+
clientX: 220,
660+
clientY: 16,
661+
});
662+
663+
fireEvent.pointerMove(canvas, {
664+
clientX: 220,
665+
clientY: 16,
666+
});
667+
668+
fireEvent.pointerUp(canvas, {
669+
clientX: 220,
670+
clientY: 16,
671+
});
672+
673+
expect(spy).toBeCalledWith({
674+
current: {
675+
cell: [1, 2],
676+
range: { height: 1, width: 1, x: 1, y: 2 },
677+
rangeStack: [],
678+
},
679+
columns: CompactSelection.fromSingleSelection(1),
680+
rows: CompactSelection.empty(),
681+
});
682+
});
576683
});

0 commit comments

Comments
 (0)