Use heuristics to detect when rows obtained focus due to keyboard interactions

This commit is contained in:
Sergio Padrino 2023-08-08 09:49:10 +02:00
parent 490a9c00ea
commit ae2d1baeee
2 changed files with 46 additions and 0 deletions

View file

@ -54,6 +54,14 @@ interface IListRowProps {
e: React.KeyboardEvent<any>
) => void
/** called when the row (or any of its descendants) receives focus due to a
* keyboard event
*/
readonly onRowKeyboardFocus?: (
index: RowIndexPath,
e: React.KeyboardEvent<any>
) => void
/** called when the row (or any of its descendants) receives focus */
readonly onRowFocus?: (
index: RowIndexPath,
@ -93,6 +101,14 @@ interface IListRowProps {
}
export class ListRow extends React.Component<IListRowProps, {}> {
// Since there is no way of knowing when a row has been focused via keyboard
// or mouse interaction, we will use the keyDown and keyUp events to track
// what the user did to get the row in a focused state.
// The heuristic is that we should receive a focus event followed by a keyUp
// event, with no keyDown events (since that keyDown event should've happened
// in the component that previously had focus).
private keyboardFocusDetectionState: 'ready' | 'failed' | 'focused' = 'ready'
private onRef = (elem: HTMLDivElement | null) => {
this.props.onRowRef?.(this.props.rowIndex, elem)
}
@ -115,13 +131,27 @@ export class ListRow extends React.Component<IListRowProps, {}> {
private onRowKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
this.props.onRowKeyDown(this.props.rowIndex, e)
this.keyboardFocusDetectionState = 'failed'
}
private onRowKeyUp = (e: React.KeyboardEvent<HTMLDivElement>) => {
this.props.onRowKeyDown(this.props.rowIndex, e)
if (this.keyboardFocusDetectionState === 'focused') {
this.props.onRowKeyboardFocus?.(this.props.rowIndex, e)
}
this.keyboardFocusDetectionState = 'ready'
}
private onFocus = (e: React.FocusEvent<HTMLDivElement>) => {
this.props.onRowFocus?.(this.props.rowIndex, e)
if (this.keyboardFocusDetectionState === 'ready') {
this.keyboardFocusDetectionState = 'focused'
}
}
private onBlur = (e: React.FocusEvent<HTMLDivElement>) => {
this.keyboardFocusDetectionState = 'ready'
this.props.onRowBlur?.(this.props.rowIndex, e)
}
@ -187,6 +217,7 @@ export class ListRow extends React.Component<IListRowProps, {}> {
onClick={this.onRowClick}
onDoubleClick={this.onRowDoubleClick}
onKeyDown={this.onRowKeyDown}
onKeyUp={this.onRowKeyUp}
style={fullWidthStyle}
onFocus={this.onFocus}
onBlur={this.onBlur}

View file

@ -116,6 +116,7 @@ interface IListProps {
readonly onRowClick?: (row: number, source: ClickSource) => void
readonly onRowDoubleClick?: (row: number, source: IMouseClickSource) => void
readonly onRowFocus?: (
row: number,
event: React.FocusEvent<HTMLDivElement>
@ -125,6 +126,11 @@ interface IListProps {
event: React.FocusEvent<HTMLDivElement>
) => void
readonly onRowKeyboardFocus?: (
row: number,
e: React.KeyboardEvent<any>
) => void
/**
* This prop defines the behaviour of the selection of items within this list.
* - 'single' : (default) single list-item selection. [shift] and [ctrl] have
@ -671,6 +677,14 @@ export class List extends React.Component<IListProps, IListState> {
this.props.onRowFocus?.(indexPath.row, e)
}
private onRowKeyboardFocus = (
indexPath: RowIndexPath,
e: React.KeyboardEvent<HTMLDivElement>
) => {
this.focusRow = indexPath.row
this.props.onRowKeyboardFocus?.(indexPath.row, e)
}
private onRowBlur = (
indexPath: RowIndexPath,
e: React.FocusEvent<HTMLDivElement>
@ -987,6 +1001,7 @@ export class List extends React.Component<IListProps, IListState> {
onRowMouseDown={this.onRowMouseDown}
onRowMouseUp={this.onRowMouseUp}
onRowFocus={this.onRowFocus}
onRowKeyboardFocus={this.onRowKeyboardFocus}
onRowBlur={this.onRowBlur}
onContextMenu={this.onRowContextMenu}
style={params.style}