Skip to content

Commit 4b3b282

Browse files
authored
Table single selection, disabled rows, and emphasized checkboxes (adobe#1077)
1 parent 404b94a commit 4b3b282

File tree

15 files changed

+638
-123
lines changed

15 files changed

+638
-123
lines changed

packages/@adobe/spectrum-css-temp/components/table/skin.css

+3-3
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ tbody.spectrum-Table-body {
112112
box-sizing: border-box;
113113

114114
.spectrum-Table-cellWrapper {
115-
/* Add an opaque background so that the transparent background
115+
/* Add an opaque background so that the transparent background
116116
* on the cell doesn't allow other cells to show through sticky cells. */
117117
background-color: var(--spectrum-table-background-color);
118118
}
@@ -134,7 +134,7 @@ tbody.spectrum-Table-body {
134134
}
135135
}
136136

137-
&:active .spectrum-Table-cell {
137+
&:active .spectrum-Table-cell:not(.is-disabled) {
138138
background-color: var(--spectrum-table-row-background-color-down);
139139
}
140140

@@ -200,7 +200,7 @@ tbody.spectrum-Table-body {
200200
background-color: var(--spectrum-table-quiet-row-background-color-hover);
201201
}
202202

203-
&:active .spectrum-Table-cell {
203+
&:active .spectrum-Table-cell:not(.is-disabled) {
204204
background-color: var(--spectrum-table-quiet-row-background-color-down);
205205
}
206206

packages/@react-aria/selection/src/useSelectableItem.ts

+1-17
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,7 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
6969
focus
7070
} = options;
7171

72-
let onSelect = (e: PressEvent | PointerEvent) => {
73-
if (manager.selectionMode === 'none') {
74-
return;
75-
}
76-
77-
if (manager.selectionMode === 'single') {
78-
if (manager.isSelected(key) && !manager.disallowEmptySelection) {
79-
manager.toggleSelection(key);
80-
} else {
81-
manager.replaceSelection(key);
82-
}
83-
} else if (e.shiftKey) {
84-
manager.extendSelection(key);
85-
} else if (manager) {
86-
manager.toggleSelection(key);
87-
}
88-
};
72+
let onSelect = (e: PressEvent | PointerEvent) => manager.select(key, e);
8973

9074
// Focus the associated DOM node when this item becomes the focusedKey
9175
let isFocused = key === manager.focusedKey;

packages/@react-aria/table/src/TableKeyboardDelegate.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
8989
return child.key;
9090
}
9191

92-
let firstItem = this.collection.getItem(this.collection.getFirstKey());
92+
let firstItem = this.collection.getItem(this.getFirstKey());
9393
return [...firstItem.childNodes][startItem.index].key;
9494
}
9595

@@ -98,8 +98,8 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
9898
key = startItem.parentKey;
9999
}
100100

101-
// Find the next enabled item
102-
key = this.findNextKey(item => item.type === 'item' && !this.disabledKeys.has(item.key), key);
101+
// Find the next item
102+
key = this.findNextKey(item => item.type === 'item', key);
103103
if (key != null) {
104104
// If focus was on a cell, focus the cell with the same index in the next row.
105105
if (this.isCell(startItem)) {
@@ -133,8 +133,8 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
133133
key = startItem.parentKey;
134134
}
135135

136-
// Find the previous enabled item
137-
key = this.findPreviousKey(item => item.type === 'item' && !this.disabledKeys.has(item.key), key);
136+
// Find the previous item
137+
key = this.findPreviousKey(item => item.type === 'item', key);
138138
if (key != null) {
139139
// If focus was on a cell, focus the cell with the same index in the previous row.
140140
if (this.isCell(startItem)) {
@@ -282,8 +282,8 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
282282
}
283283
}
284284

285-
// Find the first enabled row
286-
key = this.findNextKey(item => item.type === 'item' && !this.disabledKeys.has(item.key));
285+
// Find the first row
286+
key = this.findNextKey(item => item.type === 'item');
287287

288288
// If global flag is set, focus the first cell in the first row.
289289
if (key != null && item && this.isCell(item) && global) {
@@ -312,8 +312,8 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
312312
}
313313
}
314314

315-
// Find the last enabled row
316-
key = this.findPreviousKey(item => item.type === 'item' && !this.disabledKeys.has(item.key));
315+
// Find the last row
316+
key = this.findPreviousKey(item => item.type === 'item');
317317

318318
// If global flag is set, focus the last cell in the last row.
319319
if (key != null && item && this.isCell(item) && global) {
@@ -375,16 +375,23 @@ export class TableKeyboardDelegate<T> implements KeyboardDelegate {
375375

376376
getKeyPageBelow(key: Key) {
377377
let itemRect = this.getItemRect(key);
378+
378379
if (!itemRect) {
379380
return null;
380381
}
381382

382383
let pageHeight = this.getPageHeight();
383-
let pageY = Math.min(this.getContentHeight() - pageHeight, itemRect.y + pageHeight);
384+
let pageY = Math.min(this.getContentHeight(), itemRect.y + pageHeight);
384385

385386
while (itemRect && itemRect.maxY < pageY) {
386-
key = this.getKeyBelow(key);
387-
itemRect = this.getItemRect(key);
387+
let nextKey = this.getKeyBelow(key);
388+
itemRect = this.getItemRect(nextKey);
389+
390+
// Guard against case where maxY of the last key is barely less than pageY due to rounding
391+
// and thus it attempts to set key to null
392+
if (nextKey != null) {
393+
key = nextKey;
394+
}
388395
}
389396

390397
return key;

packages/@react-aria/table/src/useTableCell.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {useSelectableItem} from '@react-aria/selection';
2121
interface GridCellProps {
2222
node: Node<unknown>,
2323
ref: RefObject<HTMLElement>,
24-
isVirtualized?: boolean
24+
isVirtualized?: boolean,
25+
isDisabled?: boolean
2526
}
2627

2728
interface GridCellAria {
@@ -32,7 +33,8 @@ export function useTableCell<T>(props: GridCellProps, state: TableState<T>): Gri
3233
let {
3334
node,
3435
ref,
35-
isVirtualized
36+
isVirtualized,
37+
isDisabled
3638
} = props;
3739

3840
// Handles focusing the cell. If there is a focusable child,
@@ -56,7 +58,7 @@ export function useTableCell<T>(props: GridCellProps, state: TableState<T>): Gri
5658
});
5759

5860
// TODO: move into useSelectableItem?
59-
let {pressProps} = usePress(itemProps);
61+
let {pressProps} = usePress({...itemProps, isDisabled});
6062

6163
// Grid cells can have focusable elements inside them. In this case, focus should
6264
// be marshalled to that element rather than focusing the cell itself.

packages/@react-aria/table/src/useTableColumnHeader.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ interface ColumnHeaderProps {
2424
node: Node<unknown>,
2525
ref: RefObject<HTMLElement>,
2626
isVirtualized?: boolean,
27-
colspan?: number
27+
colspan?: number,
28+
isDisabled?: boolean
2829
}
2930

3031
interface ColumnHeaderAria {
3132
columnHeaderProps: HTMLAttributes<HTMLElement>
3233
}
3334

3435
export function useTableColumnHeader<T>(props: ColumnHeaderProps, state: TableState<T>): ColumnHeaderAria {
35-
let {node, colspan, ref} = props;
36+
let {node, colspan, ref, isDisabled} = props;
3637
let {gridCellProps} = useTableCell(props, state);
3738

3839
let {pressProps} = usePress({
39-
isDisabled: !node.props.allowsSorting,
40+
isDisabled: !node.props.allowsSorting || isDisabled,
4041
onPress() {
4142
state.sort(node.key);
4243
}

packages/@react-aria/table/src/useTableRow.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ interface RowProps {
2121
node: Node<unknown>,
2222
ref?: RefObject<HTMLElement>,
2323
isVirtualized?: boolean,
24-
isSelected?: boolean
24+
isSelected?: boolean,
25+
isDisabled?: boolean
2526
}
2627

2728
interface RowAria {
@@ -33,7 +34,8 @@ export function useTableRow<T>(props: RowProps, state: TableState<T>): RowAria {
3334
node,
3435
ref,
3536
isVirtualized,
36-
isSelected
37+
isSelected,
38+
isDisabled
3739
} = props;
3840

3941
let {itemProps} = useSelectableItem({
@@ -44,7 +46,7 @@ export function useTableRow<T>(props: RowProps, state: TableState<T>): RowAria {
4446
});
4547

4648
// TODO: move into useSelectableItem?
47-
let {pressProps} = usePress(itemProps);
49+
let {pressProps} = usePress({...itemProps, isDisabled});
4850

4951
let rowProps: HTMLAttributes<HTMLElement> = {
5052
role: 'row',

packages/@react-aria/table/src/useTableRowHeader.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {useTableCell} from './useTableCell';
1919
interface RowHeaderProps {
2020
node: TableNode<unknown>,
2121
ref: RefObject<HTMLElement>,
22-
isVirtualized?: boolean
22+
isVirtualized?: boolean,
23+
isDisabled?: boolean
2324
}
2425

2526
interface RowHeaderAria {

packages/@react-aria/table/src/useTableSelectionCheckbox.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {TableState} from '@react-stately/table';
1717
import {useId} from '@react-aria/utils';
1818

1919
interface SelectionCheckboxProps {
20-
key: Key
20+
key: Key,
21+
isDisabled?: boolean
2122
}
2223

2324
interface SelectionCheckboxAria {
@@ -26,19 +27,23 @@ interface SelectionCheckboxAria {
2627

2728
export function useTableSelectionCheckbox<T>(props: SelectionCheckboxProps, state: TableState<T>): SelectionCheckboxAria {
2829
let {
29-
key
30+
key,
31+
isDisabled
3032
} = props;
3133

34+
let manager = state.selectionManager;
3235
let checkboxId = useId();
33-
let isSelected = state.selectionManager.isSelected(key);
36+
let isSelected = state.selectionManager.isSelected(key) && !isDisabled;
37+
38+
let onChange = () => manager.select(key);
3439

3540
return {
3641
checkboxProps: {
3742
id: checkboxId,
3843
'aria-label': 'Select',
3944
'aria-labelledby': `${checkboxId} ${getRowLabelledBy(state, key)}`,
4045
isSelected,
41-
onChange: () => state.selectionManager.toggleSelection(key)
46+
onChange
4247
}
4348
};
4449
}

0 commit comments

Comments
 (0)