mirror of
https://github.com/desktop/desktop
synced 2024-09-13 21:31:32 +00:00
Merge pull request #18306 from desktop/jc-diff-search-voiceover
Add voiceover support for diff search
This commit is contained in:
commit
0a97e3d5f2
|
@ -71,6 +71,7 @@ import { DiffContentsWarning } from './diff-contents-warning'
|
|||
import { findDOMNode } from 'react-dom'
|
||||
import escapeRegExp from 'lodash/escapeRegExp'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { AriaLiveContainer } from '../accessibility/aria-live-container'
|
||||
|
||||
const DefaultRowHeight = 20
|
||||
|
||||
|
@ -84,6 +85,8 @@ export interface ISelection {
|
|||
readonly isSelected: boolean
|
||||
}
|
||||
|
||||
type SearchDirection = 'next' | 'previous'
|
||||
|
||||
type ModifiedLine = { line: DiffLine; diffLineNumber: number }
|
||||
|
||||
const isElement = (n: Node): n is Element => n.nodeType === Node.ELEMENT_NODE
|
||||
|
@ -210,6 +213,8 @@ interface ISideBySideDiffState {
|
|||
|
||||
readonly selectedSearchResult: number | undefined
|
||||
|
||||
readonly searchLiveMessage?: string
|
||||
|
||||
/** This tracks the last expanded hunk index so that we can refocus the expander after rerender */
|
||||
readonly lastExpandedHunk: {
|
||||
hunkIndex: number
|
||||
|
@ -562,7 +567,7 @@ export class SideBySideDiff extends React.Component<
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { diff } = this.state
|
||||
const { diff, searchLiveMessage, isSearching } = this.state
|
||||
|
||||
const rows = this.getCurrentDiffRows()
|
||||
const containerClassName = classNames('side-by-side-diff-container', {
|
||||
|
@ -581,7 +586,7 @@ export class SideBySideDiff extends React.Component<
|
|||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
<DiffContentsWarning diff={diff} />
|
||||
{this.state.isSearching && (
|
||||
{isSearching && (
|
||||
<DiffSearchInput
|
||||
onSearch={this.onSearch}
|
||||
onClose={this.onSearchCancel}
|
||||
|
@ -591,6 +596,9 @@ export class SideBySideDiff extends React.Component<
|
|||
className="side-by-side-diff cm-s-default"
|
||||
ref={this.onDiffContainerRef}
|
||||
>
|
||||
{isSearching && (
|
||||
<AriaLiveContainer message={searchLiveMessage || ''} />
|
||||
)}
|
||||
<AutoSizer onResize={this.clearListRowsHeightCache}>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
|
@ -606,7 +614,7 @@ export class SideBySideDiff extends React.Component<
|
|||
// The following properties are passed to the list
|
||||
// to make sure that it gets re-rendered when any of
|
||||
// them change.
|
||||
isSearching={this.state.isSearching}
|
||||
isSearching={isSearching}
|
||||
selectedSearchResult={this.state.selectedSearchResult}
|
||||
searchQuery={this.state.searchQuery}
|
||||
showSideBySideDiff={this.props.showSideBySideDiff}
|
||||
|
@ -1569,56 +1577,89 @@ export class SideBySideDiff extends React.Component<
|
|||
}
|
||||
}
|
||||
|
||||
private onSearch = (searchQuery: string, direction: 'next' | 'previous') => {
|
||||
let { selectedSearchResult, searchResults: searchResults } = this.state
|
||||
const { showSideBySideDiff } = this.props
|
||||
const { diff } = this.state
|
||||
private onSearch = (searchQuery: string, direction: SearchDirection) => {
|
||||
const { searchResults } = this.state
|
||||
|
||||
// If the query is unchanged and we've got tokens we'll continue, else we'll restart
|
||||
if (searchQuery === this.state.searchQuery && searchResults !== undefined) {
|
||||
if (selectedSearchResult === undefined) {
|
||||
selectedSearchResult = 0
|
||||
} else {
|
||||
const delta = direction === 'next' ? 1 : -1
|
||||
|
||||
// http://javascript.about.com/od/problemsolving/a/modulobug.htm
|
||||
selectedSearchResult =
|
||||
(selectedSearchResult + delta + searchResults.length) %
|
||||
searchResults.length
|
||||
}
|
||||
if (searchQuery?.trim() === '') {
|
||||
this.resetSearch(true, 'No results')
|
||||
} else if (searchQuery === this.state.searchQuery && searchResults) {
|
||||
this.continueSearch(searchResults, direction)
|
||||
} else {
|
||||
searchResults = calcSearchTokens(
|
||||
diff,
|
||||
showSideBySideDiff,
|
||||
this.startSearch(searchQuery, direction)
|
||||
}
|
||||
}
|
||||
|
||||
private startSearch = (searchQuery: string, direction: SearchDirection) => {
|
||||
const searchResults = calcSearchTokens(
|
||||
this.state.diff,
|
||||
this.props.showSideBySideDiff,
|
||||
searchQuery,
|
||||
this.canExpandDiff()
|
||||
)
|
||||
|
||||
if (searchResults === undefined || searchResults.length === 0) {
|
||||
this.resetSearch(true, `No results for "${searchQuery}"`)
|
||||
} else {
|
||||
const searchLiveMessage = `Result 1 of ${searchResults.length} for "${searchQuery}"`
|
||||
|
||||
this.scrollToSearchResult(0)
|
||||
|
||||
this.setState({
|
||||
searchQuery,
|
||||
this.canExpandDiff()
|
||||
)
|
||||
selectedSearchResult = 0
|
||||
|
||||
if (searchResults === undefined || searchResults.length === 0) {
|
||||
this.resetSearch(true)
|
||||
return
|
||||
}
|
||||
searchResults,
|
||||
selectedSearchResult: 0,
|
||||
searchLiveMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToRow = searchResults.get(selectedSearchResult)?.row
|
||||
private continueSearch = (
|
||||
searchResults: SearchResults,
|
||||
direction: SearchDirection
|
||||
) => {
|
||||
const { searchQuery } = this.state
|
||||
let { selectedSearchResult = 0 } = this.state
|
||||
|
||||
if (scrollToRow !== undefined) {
|
||||
this.virtualListRef.current?.scrollToRow(scrollToRow)
|
||||
}
|
||||
const delta = direction === 'next' ? 1 : -1
|
||||
|
||||
this.setState({ searchQuery, searchResults, selectedSearchResult })
|
||||
// https://web.archive.org/web/20090717035140if_/javascript.about.com/od/problemsolving/a/modulobug.htm
|
||||
selectedSearchResult =
|
||||
(selectedSearchResult + delta + searchResults.length) %
|
||||
searchResults.length
|
||||
|
||||
const searchLiveMessage = `Result ${selectedSearchResult + 1} of ${
|
||||
searchResults.length
|
||||
} for "${searchQuery}"`
|
||||
|
||||
this.scrollToSearchResult(selectedSearchResult)
|
||||
|
||||
this.setState({
|
||||
searchResults,
|
||||
selectedSearchResult,
|
||||
searchLiveMessage,
|
||||
})
|
||||
}
|
||||
|
||||
private onSearchCancel = () => {
|
||||
this.resetSearch(false)
|
||||
}
|
||||
|
||||
private resetSearch(isSearching: boolean) {
|
||||
private scrollToSearchResult = (index: number) => {
|
||||
const { searchResults } = this.state
|
||||
|
||||
const scrollToRow = searchResults?.get(index)?.row
|
||||
|
||||
if (scrollToRow !== undefined) {
|
||||
this.virtualListRef.current?.scrollToRow(scrollToRow)
|
||||
}
|
||||
}
|
||||
|
||||
private resetSearch(isSearching: boolean, searchLiveMessage?: string) {
|
||||
this.setState({
|
||||
selectedSearchResult: undefined,
|
||||
searchQuery: undefined,
|
||||
searchResults: undefined,
|
||||
searchLiveMessage,
|
||||
isSearching,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue