Track search navigation in DOM

This commit is contained in:
Dan Abramov
2019-04-12 18:14:46 -07:00
committed by Brian Vaughn
parent 7145c78325
commit 097d0386b1
3 changed files with 57 additions and 24 deletions
+2 -2
View File
@@ -44,7 +44,7 @@ export default function ElementView({ data, index, style }: Props) {
const {
lastScrolledIDRef,
treeFocused,
isNavigatingWithKeyboard,
isUsingKeyboardOrSearch,
onElementMouseEnter,
} = data;
const id = element === null ? null : element.id;
@@ -122,7 +122,7 @@ export default function ElementView({ data, index, style }: Props) {
className = treeFocused
? styles.SelectedElement
: styles.InactiveSelectedElement;
} else if (isHovered && !isNavigatingWithKeyboard) {
} else if (isHovered && !isUsingKeyboardOrSearch) {
className = styles.HoveredElement;
}
+24 -8
View File
@@ -8,9 +8,12 @@ import Icon from '../Icon';
import styles from './SearchInput.css';
type Props = {||};
type Props = {|
onSearchInteraction: () => void,
|};
export default function SearchInput(props: Props) {
const { onSearchInteraction } = props;
const {
goToNextSearchResult,
goToPreviousSearchResult,
@@ -25,13 +28,17 @@ export default function SearchInput(props: Props) {
const inputRef = useRef<HTMLInputElement | null>(null);
const handleTextChange = useCallback(
({ currentTarget }) => setSearchText(currentTarget.value),
[setSearchText]
({ currentTarget }) => {
setSearchText(currentTarget.value);
onSearchInteraction();
},
[setSearchText, onSearchInteraction]
);
const resetSearch = useCallback(() => {
setSearchText('');
}, [setSearchText]);
onSearchInteraction();
}, [setSearchText, onSearchInteraction]);
const handleKeyDown = useCallback(
event => {
@@ -39,26 +46,29 @@ export default function SearchInput(props: Props) {
switch (event.key) {
case 'ArrowDown':
selectNextElementInTree();
onSearchInteraction();
event.preventDefault();
break;
case 'ArrowUp':
selectPreviousElementInTree();
onSearchInteraction();
event.preventDefault();
break;
default:
break;
}
},
[selectNextElementInTree, selectPreviousElementInTree]
[selectNextElementInTree, selectPreviousElementInTree, onSearchInteraction]
);
const handleInputKeyPress = useCallback(
({ key }) => {
if (key === 'Enter') {
goToNextSearchResult();
onSearchInteraction();
}
},
[goToNextSearchResult]
[goToNextSearchResult, onSearchInteraction]
);
// Auto-focus search input
@@ -109,7 +119,10 @@ export default function SearchInput(props: Props) {
<Button
className={styles.IconButton}
disabled={!searchText}
onClick={goToPreviousSearchResult}
onClick={() => {
goToPreviousSearchResult();
onSearchInteraction();
}}
title="Scroll to previous search result"
>
<ButtonIcon type="up" />
@@ -117,7 +130,10 @@ export default function SearchInput(props: Props) {
<Button
className={styles.IconButton}
disabled={!searchText}
onClick={goToNextSearchResult}
onClick={() => {
goToNextSearchResult();
onSearchInteraction();
}}
title="Scroll to next search result"
>
<ButtonIcon type="down" />
+31 -14
View File
@@ -27,7 +27,7 @@ export type ItemData = {|
baseDepth: number,
numElements: number,
getElementAtIndex: (index: number) => Element | null,
isNavigatingWithKeyboard: boolean,
isUsingKeyboardOrSearch: boolean,
lastScrolledIDRef: { current: number | null },
onElementMouseEnter: (id: number) => void,
treeFocused: boolean,
@@ -52,9 +52,7 @@ export default function Tree(props: Props) {
} = useContext(TreeContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
false
);
const [isUsingKeyboardOrSearch, setIsUsingKeyboardOrSearch] = useState(false);
// $FlowFixMe https://github.com/facebook/flow/issues/7341
const listRef = useRef<FixedSizeList<ItemData> | null>(null);
const treeRef = useRef<HTMLDivElement | null>(null);
@@ -146,7 +144,7 @@ export default function Tree(props: Props) {
default:
return;
}
setIsNavigatingWithKeyboard(true);
setIsUsingKeyboardOrSearch(true);
};
// It's important to listen to the ownerDocument to support the browser extension.
@@ -213,33 +211,48 @@ export default function Tree(props: Props) {
// If we switch the selected element while using the keyboard,
// start highlighting it in the DOM instead of the last hovered node.
useEffect(() => {
if (isNavigatingWithKeyboard && selectedElementID !== null) {
highlightElementInDOM(selectedElementID);
if (isUsingKeyboardOrSearch) {
if (selectedElementID !== null) {
highlightElementInDOM(selectedElementID);
} else {
bridge.send('clearHighlightedElementInDOM');
}
}
}, [isNavigatingWithKeyboard, highlightElementInDOM, selectedElementID]);
}, [
bridge,
isUsingKeyboardOrSearch,
highlightElementInDOM,
selectedElementID,
]);
// Highlight last hovered element.
const handleElementMouseEnter = useCallback(
id => {
// Ignore hover while we're navigating with keyboard.
// This avoids flicker from the hovered nodes under the mouse.
if (!isNavigatingWithKeyboard) {
if (!isUsingKeyboardOrSearch) {
highlightElementInDOM(id);
}
},
[isNavigatingWithKeyboard, highlightElementInDOM]
[isUsingKeyboardOrSearch, highlightElementInDOM]
);
const handleMouseMove = useCallback(() => {
// We started using the mouse again.
// This will enable hover styles in individual rows.
setIsNavigatingWithKeyboard(false);
setIsUsingKeyboardOrSearch(false);
}, []);
const handleMouseLeave = useCallback(() => {
bridge.send('clearHighlightedElementInDOM');
}, [bridge]);
const handleSearchInteraction = useCallback(() => {
// We started navigating using search.
// This will disable hover styles and start highlighting DOM elements.
setIsUsingKeyboardOrSearch(true);
}, []);
// Let react-window know to re-render any time the underlying tree data changes.
// This includes the owner context, since it controls a filtered view of the tree.
const itemData = useMemo<ItemData>(
@@ -247,7 +260,7 @@ export default function Tree(props: Props) {
baseDepth,
numElements,
getElementAtIndex,
isNavigatingWithKeyboard,
isUsingKeyboardOrSearch,
onElementMouseEnter: handleElementMouseEnter,
lastScrolledIDRef,
treeFocused,
@@ -256,7 +269,7 @@ export default function Tree(props: Props) {
baseDepth,
numElements,
getElementAtIndex,
isNavigatingWithKeyboard,
isUsingKeyboardOrSearch,
handleElementMouseEnter,
lastScrolledIDRef,
treeFocused,
@@ -266,7 +279,11 @@ export default function Tree(props: Props) {
return (
<div className={styles.Tree} ref={treeRef}>
<div className={styles.SearchInput}>
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
{ownerStack.length > 0 ? (
<OwnersStack />
) : (
<SearchInput onSearchInteraction={handleSearchInteraction} />
)}
<InspectHostNodesToggle />
</div>
<div