Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3580959
initial work to focus the row instead of the cell
LFDanLu Mar 11, 2022
e51bd3f
cleanup
LFDanLu Mar 30, 2022
928549a
tentative approach to always skip focusing the cell
LFDanLu Mar 30, 2022
2216e98
retaining focus on Listview focusable child when it is clicked
LFDanLu Mar 30, 2022
dac22a8
making ListView specific keyboard delegate
LFDanLu Mar 30, 2022
b540f23
fixing pageUp/Down operations when within the ListView row
LFDanLu Mar 31, 2022
2ebabe1
adding tests
LFDanLu Mar 31, 2022
2a92a70
fixing tests after rebase
LFDanLu Mar 31, 2022
312aaed
refactor to have outer div behave as both gridcell and grid row
LFDanLu Apr 1, 2022
dd9dac5
fixing Home/End
LFDanLu Apr 1, 2022
17a11bd
fixing test and getting rid of erroneous data id
LFDanLu Apr 1, 2022
d9eb366
cleanup
LFDanLu Apr 1, 2022
303304c
lint
LFDanLu Apr 1, 2022
0fe13e6
removing todo
LFDanLu Apr 4, 2022
b4a4f40
updating lint so we check that a dep is actually needed before throwing
LFDanLu Apr 4, 2022
11302a3
removing private
LFDanLu Apr 4, 2022
c9ba923
pulling triggerPress outside the act
LFDanLu Apr 5, 2022
db7fbe7
Merge branch 'main' into refactor_listview_row_focus_2
LFDanLu Apr 6, 2022
471139a
Merge branch 'main' into refactor_listview_row_focus_2
LFDanLu Apr 13, 2022
f2ef4ef
tentative approach to use ListLayout instead of making a ListGrid key…
LFDanLu Apr 21, 2022
0476f4f
fixing build
LFDanLu Apr 21, 2022
2ea0c5a
removing useGridRow to ensure the focused key will always be a row
LFDanLu Apr 26, 2022
b30d604
Merge branch 'main' of github.com:adobe/react-spectrum into refactor_…
LFDanLu Apr 26, 2022
53e8c7a
cleanup
LFDanLu Apr 26, 2022
e659966
Merge branch 'main' of github.com:adobe/react-spectrum into refactor_…
LFDanLu Apr 27, 2022
3f7f3d8
adding listview story decorator for before and after focusable elements
LFDanLu Apr 28, 2022
7cf09be
Merge branch 'main' of github.com:adobe/react-spectrum into refactor_…
LFDanLu Apr 29, 2022
6c02faf
making the story input fields only appear at certain screen widths
LFDanLu Apr 29, 2022
9eda1f9
Merge branch 'main' into refactor_listview_row_focus_2
LFDanLu May 3, 2022
e4086bf
addressing review comments
LFDanLu May 4, 2022
5ed6df6
Merge branch 'main' into refactor_listview_row_focus_2
LFDanLu May 4, 2022
71809a1
Merge branch 'main' into refactor_listview_row_focus_2
LFDanLu May 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/@react-aria/grid/src/GridKeyboardDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,3 @@ export class GridKeyboardDelegate<T, C extends GridCollection<T>> implements Key
return null;
}
}

5 changes: 5 additions & 0 deletions packages/@react-aria/grid/src/useGridCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
let focus = () => {
let treeWalker = getFocusableTreeWalker(ref.current);
if (focusMode === 'child') {
// If focus is already on a focusable child within the cell, early return so we don't shift focus
if (ref.current.contains(document.activeElement) && ref.current !== document.activeElement) {
return;
}

let focusable = state.selectionManager.childFocusStrategy === 'last'
? last(treeWalker)
: treeWalker.firstChild() as HTMLElement;
Expand Down
39 changes: 10 additions & 29 deletions packages/@react-spectrum/list/src/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {DragHooks} from '@react-spectrum/dnd';
import {DragPreview} from './DragPreview';
import {filterDOMProps} from '@react-aria/utils';
import {GridCollection, GridState, useGridState} from '@react-stately/grid';
import {GridKeyboardDelegate, useGrid} from '@react-aria/grid';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {ListLayout} from '@react-stately/layout';
Expand All @@ -36,14 +35,13 @@ import {ListViewItem} from './ListViewItem';
import {ProgressCircle} from '@react-spectrum/progress';
import React, {ReactElement, useContext, useMemo, useRef} from 'react';
import {useCollator, useLocale, useMessageFormatter} from '@react-aria/i18n';
import {useGrid} from '@react-aria/grid';
import {useProvider} from '@react-spectrum/provider';
import {Virtualizer} from '@react-aria/virtualizer';

interface ListViewContextValue<T> {
state: GridState<T, GridCollection<any>>,
keyboardDelegate: GridKeyboardDelegate<T, GridCollection<any>>,
dragState: DraggableCollectionState,
onAction:(key: string) => void,
isListDraggable: boolean,
layout: ListLayout<T>
}
Expand Down Expand Up @@ -74,7 +72,8 @@ export function useListLayout<T>(state: ListState<T>, density: ListViewProps<T>[
estimatedRowHeight: ROW_HEIGHTS[density][scale],
padding: 0,
collator,
loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale]
loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale],
allowDisabledKeyFocus: true
})
, [collator, scale, density, isEmpty]);

Expand Down Expand Up @@ -128,8 +127,7 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';

let {styleProps} = useStyleProps(props);
let {direction, locale} = useLocale();
let collator = useCollator({usage: 'search', sensitivity: 'base'});
let {locale} = useLocale();
let gridCollection = useMemo(() => new GridCollection({
columnCount: 1,
items: [...collection].map(item => ({
Expand All @@ -151,21 +149,10 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
let state = useGridState({
...props,
collection: gridCollection,
focusMode: 'cell',
focusMode: 'row',
selectionBehavior: props.selectionStyle === 'highlight' ? 'replace' : 'toggle'
});
let layout = useListLayout(state, props.density || 'regular');
let keyboardDelegate = useMemo(() => new GridKeyboardDelegate({
collection: state.collection,
disabledKeys: state.disabledKeys,
ref: domRef,
direction,
collator,
// Focus the ListView cell instead of the row so that focus doesn't change with left/right arrow keys when there aren't any
// focusable children in the cell.
focusMode: 'cell'
}), [state, domRef, direction, collator]);

let provider = useProvider();
let dragState: DraggableCollectionState;
if (isListDraggable) {
Expand All @@ -183,29 +170,23 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv

let {gridProps} = useGrid({
...props,
onCellAction: onAction,
isVirtualized: true,
keyboardDelegate
keyboardDelegate: layout
}, state, domRef);

// Sync loading state into the layout.
layout.isLoading = isLoading;

let focusedKey = state.selectionManager.focusedKey;
let focusedItem = gridCollection.getItem(state.selectionManager.focusedKey);
if (focusedItem?.parentKey != null) {
focusedKey = focusedItem.parentKey;
}

return (
<ListViewContext.Provider value={{state, keyboardDelegate, dragState, onAction, isListDraggable, layout}}>
<ListViewContext.Provider value={{state, dragState, isListDraggable, layout}}>
<Virtualizer
{...filterDOMProps(otherProps)}
{...gridProps}
{...styleProps}
isLoading={isLoading}
onLoadMore={onLoadMore}
ref={domRef}
focusedKey={focusedKey}
focusedKey={state.selectionManager.focusedKey}
scrollDirection="vertical"
className={
classNames(
Expand All @@ -227,7 +208,7 @@ function ListView<T extends object>(props: ListViewProps<T>, ref: DOMRef<HTMLDiv
{(type, item) => {
if (type === 'item') {
return (
<ListViewItem item={item} isEmphasized dragHooks={dragHooks} />
<ListViewItem item={item} isEmphasized dragHooks={dragHooks} hasActions={onAction} />
);
} else if (type === 'loader') {
return (
Expand Down
59 changes: 36 additions & 23 deletions packages/@react-spectrum/list/src/ListViewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {ListViewContext} from './ListView';
import {mergeProps} from '@react-aria/utils';
import React, {useContext, useRef} from 'react';
import {useButton} from '@react-aria/button';
import {useGridCell, useGridRow, useGridSelectionCheckbox} from '@react-aria/grid';
import {useGridCell, useGridSelectionCheckbox} from '@react-aria/grid';
import {useHover, usePress} from '@react-aria/interactions';
import {useLocale} from '@react-aria/i18n';
import {useVisuallyHidden} from '@react-aria/visually-hidden';
Expand All @@ -32,47 +32,42 @@ export function ListViewItem(props) {
let {
item,
isEmphasized,
dragHooks
dragHooks,
hasActions
} = props;
let cellNode = [...item.childNodes][0];
let {state, dragState, onAction, isListDraggable, layout} = useContext(ListViewContext);

let {state, dragState, isListDraggable, layout} = useContext(ListViewContext);
let {direction} = useLocale();
let rowRef = useRef<HTMLDivElement>();
let cellRef = useRef<HTMLDivElement>();
let {
isFocusVisible: isFocusVisibleWithin,
focusProps: focusWithinProps
} = useFocusRing({within: true});
let {isFocusVisible, focusProps} = useFocusRing();
let allowsInteraction = state.selectionManager.selectionMode !== 'none' || onAction;
let allowsInteraction = state.selectionManager.selectionMode !== 'none' || hasActions;
let isDisabled = !allowsInteraction || state.disabledKeys.has(item.key);
let isDraggable = dragState?.isDraggable(item.key) && !isDisabled;
let {hoverProps, isHovered} = useHover({isDisabled});
let {pressProps, isPressed} = usePress({isDisabled});
let {rowProps} = useGridRow({

// We only make use of useGridCell here to allow for keyboard navigation to the focusable children of the row.
// The actual grid cell of the ListView is inert since we don't want to ever focus it to decrease screenreader
// verbosity, so we pretend the row node is the cell for interaction purposes. useGridRow is never used since
// it would conflict with useGridCell if applied to the same node.
let {gridCellProps} = useGridCell({
node: item,
focusMode: 'cell',
isVirtualized: true,
onAction: onAction ? () => onAction(item.key) : undefined,
shouldSelectOnPressUp: isListDraggable
}, state, rowRef);
Comment thread
LFDanLu marked this conversation as resolved.
let {gridCellProps} = useGridCell({
node: cellNode,
focusMode: 'cell'
}, state, cellRef);
delete gridCellProps['aria-colindex'];

let draggableItem: DraggableItemResult;
if (isListDraggable) {
// eslint-disable-next-line react-hooks/rules-of-hooks
draggableItem = dragHooks.useDraggableItem({key: item.key}, dragState);
}
const mergedProps = mergeProps(
gridCellProps,
hoverProps,
focusWithinProps,
focusProps
);
let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);

let {checkboxProps} = useGridSelectionCheckbox({...props, key: item.key}, state);
let dragButtonRef = React.useRef();
let {buttonProps} = useButton({
...draggableItem?.dragButtonProps,
Expand All @@ -98,6 +93,23 @@ export function ListViewItem(props) {
let isSelected = state.selectionManager.isSelected(item.key);
let showDragHandle = isDraggable && isFocusVisibleWithin;
let {visuallyHiddenProps} = useVisuallyHidden();
let rowProps = {
role: 'row',
'aria-label': item.textValue,
'aria-selected': state.selectionManager.selectionMode !== 'none' ? isSelected : undefined,
'aria-rowindex': item.index + 1
};

const mergedProps = mergeProps(
gridCellProps,
rowProps,
pressProps,
isDraggable && draggableItem?.dragProps,
hoverProps,
focusWithinProps,
focusProps
);

let isFirstRow = item.prevKey == null;
let isLastRow = item.nextKey == null;
// Figure out if the ListView content is equal or greater in height to the container. If so, we'll need to round the bottom
Expand All @@ -112,7 +124,7 @@ export function ListViewItem(props) {

return (
<div
{...mergeProps(rowProps, pressProps, isDraggable && draggableItem?.dragProps)}
{...mergedProps}
className={
classNames(
listStyles,
Expand All @@ -124,6 +136,7 @@ export function ListViewItem(props) {
}
ref={rowRef}>
<div
// TODO: refactor the css here now that we are focusing the row?
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Will be looking at refactoring the css after a couple of other ListView PRs w/ heavy css updates are merged

className={
classNames(
listStyles,
Expand All @@ -143,8 +156,8 @@ export function ListViewItem(props) {
}
)
}
ref={cellRef}
{...mergedProps}>
role="gridcell"
aria-colindex={1}>
<Grid UNSAFE_className={listStyles['react-spectrum-ListViewItem-grid']}>
{isListDraggable &&
<div className={listStyles['react-spectrum-ListViewItem-draghandle-container']}>
Expand Down
16 changes: 16 additions & 0 deletions packages/@react-spectrum/list/stories/ListView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,23 @@ function renderEmptyState() {
);
}

let decorator = (storyFn, context) => {
let omittedStories = ['draggable rows', 'dynamic items + renderEmptyState'];
return window.screen.width <= 700 || omittedStories.some(omittedName => context.name.includes(omittedName)) ?
storyFn() :
(
<>
<label htmlFor="focus-before">Focus before</label>
<input id="focus-before" />
{storyFn()}
<label htmlFor="focus-after">Focus after</label>
<input id="focus-after" />
</>
);
};

storiesOf('ListView', module)
.addDecorator(decorator)
.add('default', () => (
<ListView width="250px">
<Item textValue="Adobe Photoshop">Adobe Photoshop</Item>
Expand Down
Loading