Track search navigation in DOM
Dan Abramov committed
Apr 12, 2019 at 18:59 UTC
097d0386b133f779f4c3622d2a745d64e1e32fc8
3 files changed
+57
-24
src/devtools/views/Components/Element.js
+2
-2
@@ -44,7 +44,7 @@ export default function ElementView({ data, index, style }: Props) {
44
const {
45
lastScrolledIDRef,
46
treeFocused,
47
- isNavigatingWithKeyboard,
47
+ isUsingKeyboardOrSearch,
48
onElementMouseEnter,
49
} = data;
50
const id = element === null ? null : element.id;
@@ -122,7 +122,7 @@ export default function ElementView({ data, index, style }: Props) {
122
className = treeFocused
123
? styles.SelectedElement
124
: styles.InactiveSelectedElement;
125
- } else if (isHovered && !isNavigatingWithKeyboard) {
125
+ } else if (isHovered && !isUsingKeyboardOrSearch) {
126
className = styles.HoveredElement;
127
}
128
src/devtools/views/Components/SearchInput.js
+24
-8
@@ -8,9 +8,12 @@ import Icon from '../Icon';
8
9
import styles from './SearchInput.css';
10
11
-type Props = {||};
11
+type Props = {|
12
+ onSearchInteraction: () => void,
13
+|};
14
15
export default function SearchInput(props: Props) {
16
+ const { onSearchInteraction } = props;
17
const {
18
goToNextSearchResult,
19
goToPreviousSearchResult,
@@ -25,13 +28,17 @@ export default function SearchInput(props: Props) {
28
const inputRef = useRef<HTMLInputElement | null>(null);
29
30
const handleTextChange = useCallback(
28
- ({ currentTarget }) => setSearchText(currentTarget.value),
29
- [setSearchText]
31
+ ({ currentTarget }) => {
32
+ setSearchText(currentTarget.value);
33
+ onSearchInteraction();
34
+ },
35
+ [setSearchText, onSearchInteraction]
36
);
37
38
const resetSearch = useCallback(() => {
39
setSearchText('');
34
- }, [setSearchText]);
40
+ onSearchInteraction();
41
+ }, [setSearchText, onSearchInteraction]);
42
43
const handleKeyDown = useCallback(
44
event => {
@@ -39,26 +46,29 @@ export default function SearchInput(props: Props) {
46
switch (event.key) {
47
case 'ArrowDown':
48
selectNextElementInTree();
49
+ onSearchInteraction();
50
event.preventDefault();
51
break;
52
case 'ArrowUp':
53
selectPreviousElementInTree();
54
+ onSearchInteraction();
55
event.preventDefault();
56
break;
57
default:
58
break;
59
}
60
},
52
- [selectNextElementInTree, selectPreviousElementInTree]
61
+ [selectNextElementInTree, selectPreviousElementInTree, onSearchInteraction]
62
);
63
64
const handleInputKeyPress = useCallback(
65
({ key }) => {
66
if (key === 'Enter') {
67
goToNextSearchResult();
68
+ onSearchInteraction();
69
}
70
},
61
- [goToNextSearchResult]
71
+ [goToNextSearchResult, onSearchInteraction]
72
);
73
74
// Auto-focus search input
@@ -109,7 +119,10 @@ export default function SearchInput(props: Props) {
119
<Button
120
className={styles.IconButton}
121
disabled={!searchText}
112
- onClick={goToPreviousSearchResult}
122
+ onClick={() => {
123
+ goToPreviousSearchResult();
124
+ onSearchInteraction();
125
+ }}
126
title="Scroll to previous search result"
127
>
128
<ButtonIcon type="up" />
@@ -117,7 +130,10 @@ export default function SearchInput(props: Props) {
130
<Button
131
className={styles.IconButton}
132
disabled={!searchText}
120
- onClick={goToNextSearchResult}
133
+ onClick={() => {
134
+ goToNextSearchResult();
135
+ onSearchInteraction();
136
+ }}
137
title="Scroll to next search result"
138
>
139
<ButtonIcon type="down" />
src/devtools/views/Components/Tree.js
+31
-14
@@ -27,7 +27,7 @@ export type ItemData = {|
27
baseDepth: number,
28
numElements: number,
29
getElementAtIndex: (index: number) => Element | null,
30
- isNavigatingWithKeyboard: boolean,
30
+ isUsingKeyboardOrSearch: boolean,
31
lastScrolledIDRef: { current: number | null },
32
onElementMouseEnter: (id: number) => void,
33
treeFocused: boolean,
@@ -52,9 +52,7 @@ export default function Tree(props: Props) {
52
} = useContext(TreeContext);
53
const bridge = useContext(BridgeContext);
54
const store = useContext(StoreContext);
55
- const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
56
- false
57
- );
55
+ const [isUsingKeyboardOrSearch, setIsUsingKeyboardOrSearch] = useState(false);
56
// $FlowFixMe https://github.com/facebook/flow/issues/7341
57
const listRef = useRef<FixedSizeList<ItemData> | null>(null);
58
const treeRef = useRef<HTMLDivElement | null>(null);
@@ -146,7 +144,7 @@ export default function Tree(props: Props) {
144
default:
145
return;
146
}
149
- setIsNavigatingWithKeyboard(true);
147
+ setIsUsingKeyboardOrSearch(true);
148
};
149
150
// It's important to listen to the ownerDocument to support the browser extension.
@@ -213,33 +211,48 @@ export default function Tree(props: Props) {
211
// If we switch the selected element while using the keyboard,
212
// start highlighting it in the DOM instead of the last hovered node.
213
useEffect(() => {
216
- if (isNavigatingWithKeyboard && selectedElementID !== null) {
217
- highlightElementInDOM(selectedElementID);
214
+ if (isUsingKeyboardOrSearch) {
215
+ if (selectedElementID !== null) {
216
+ highlightElementInDOM(selectedElementID);
217
+ } else {
218
+ bridge.send('clearHighlightedElementInDOM');
219
+ }
220
}
219
- }, [isNavigatingWithKeyboard, highlightElementInDOM, selectedElementID]);
221
+ }, [
222
+ bridge,
223
+ isUsingKeyboardOrSearch,
224
+ highlightElementInDOM,
225
+ selectedElementID,
226
+ ]);
227
228
// Highlight last hovered element.
229
const handleElementMouseEnter = useCallback(
230
id => {
231
// Ignore hover while we're navigating with keyboard.
232
// This avoids flicker from the hovered nodes under the mouse.
226
- if (!isNavigatingWithKeyboard) {
233
+ if (!isUsingKeyboardOrSearch) {
234
highlightElementInDOM(id);
235
}
236
},
230
- [isNavigatingWithKeyboard, highlightElementInDOM]
237
+ [isUsingKeyboardOrSearch, highlightElementInDOM]
238
);
239
240
const handleMouseMove = useCallback(() => {
241
// We started using the mouse again.
242
// This will enable hover styles in individual rows.
236
- setIsNavigatingWithKeyboard(false);
243
+ setIsUsingKeyboardOrSearch(false);
244
}, []);
245
246
const handleMouseLeave = useCallback(() => {
247
bridge.send('clearHighlightedElementInDOM');
248
}, [bridge]);
249
250
+ const handleSearchInteraction = useCallback(() => {
251
+ // We started navigating using search.
252
+ // This will disable hover styles and start highlighting DOM elements.
253
+ setIsUsingKeyboardOrSearch(true);
254
+ }, []);
255
+
256
// Let react-window know to re-render any time the underlying tree data changes.
257
// This includes the owner context, since it controls a filtered view of the tree.
258
const itemData = useMemo<ItemData>(
@@ -247,7 +260,7 @@ export default function Tree(props: Props) {
260
baseDepth,
261
numElements,
262
getElementAtIndex,
250
- isNavigatingWithKeyboard,
263
+ isUsingKeyboardOrSearch,
264
onElementMouseEnter: handleElementMouseEnter,
265
lastScrolledIDRef,
266
treeFocused,
@@ -256,7 +269,7 @@ export default function Tree(props: Props) {
269
baseDepth,
270
numElements,
271
getElementAtIndex,
259
- isNavigatingWithKeyboard,
272
+ isUsingKeyboardOrSearch,
273
handleElementMouseEnter,
274
lastScrolledIDRef,
275
treeFocused,
@@ -266,7 +279,11 @@ export default function Tree(props: Props) {
279
return (
280
<div className={styles.Tree} ref={treeRef}>
281
<div className={styles.SearchInput}>
269
- {ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
282
+ {ownerStack.length > 0 ? (
283
+ <OwnersStack />
284
+ ) : (
285
+ <SearchInput onSearchInteraction={handleSearchInteraction} />
286
+ )}
287
<InspectHostNodesToggle />
288
</div>
289
<div