72
type ACTION_GO_TO_PREVIOUS_SEARCH_RESULT = {
73
type: 'GO_TO_PREVIOUS_SEARCH_RESULT',
74
};
75
+type ACTION_GO_TO_SEARCH_RESULT = {
76
+ type: 'GO_TO_SEARCH_RESULT',
77
+ payload: number,
78
+};
79
type ACTION_HANDLE_STORE_MUTATION = {
80
type: 'HANDLE_STORE_MUTATION',
81
payload: [Array<number>, Map<number, number>, null | Element['id']],
133
type Action =
134
| ACTION_GO_TO_NEXT_SEARCH_RESULT
135
| ACTION_GO_TO_PREVIOUS_SEARCH_RESULT
136
+ | ACTION_GO_TO_SEARCH_RESULT
137
| ACTION_HANDLE_STORE_MUTATION
138
| ACTION_RESET_OWNER_STACK
139
| ACTION_SELECT_CHILD_ELEMENT_IN_TREE
530
: numPrevSearchResults - 1;
531
}
532
break;
533
+ case 'GO_TO_SEARCH_RESULT':
534
+ if (numPrevSearchResults > 0) {
535
+ didRequestSearch = true;
536
+ // Jump directly to a specific result (0-based), clamped to range.
537
+ // This lets users skip past large virtualized lists instead of
538
+ // stepping through results one at a time.
539
+ const targetIndex = (action: ACTION_GO_TO_SEARCH_RESULT).payload;
540
+ searchIndex = Math.max(
541
+ 0,
542
+ Math.min(targetIndex, numPrevSearchResults - 1),
543
+ );
544
+ }
545
+ break;
546
case 'HANDLE_STORE_MUTATION':
547
if (searchText !== '') {
548
const [addedElementIDs, removedElementIDs] = (
648
if (searchText !== prevSearchText) {
649
// $FlowFixMe[incompatible-type]
650
const newSearchIndex = searchResults.indexOf(inspectedElementID);
633
- if (newSearchIndex === -1) {
634
- // Only move the selection if the new query
635
- // doesn't match the current selection anymore.
651
+ if (prevSearchText === '') {
652
+ // Starting a fresh search (e.g. after clearing the box). Honor the index
653
+ // computed above, which uses "find next" semantics so that retyping the
654
+ // same query advances past the still-selected result instead of snapping
655
+ // back to it.
656
+ if (searchIndex !== null) {
657
+ didRequestSearch = true;
658
+ }
659
+ } else if (newSearchIndex === -1) {
660
+ // Refining an existing query and the current selection no longer matches,
661
+ // so move the selection to the nearest result.
662
didRequestSearch = true;
663
} else {
638
- // Selected item still matches the new search query.
639
- // Adjust the index to reflect its position in new results.
664
+ // Refining an existing query and the current selection still matches.
665
+ // Keep it selected and adjust the index to its position in new results.
666
searchIndex = newSearchIndex;
667
}
668
}
936
switch (type) {
937
case 'GO_TO_NEXT_SEARCH_RESULT':
938
case 'GO_TO_PREVIOUS_SEARCH_RESULT':
939
+ case 'GO_TO_SEARCH_RESULT':
940
case 'HANDLE_STORE_MUTATION':
941
case 'RESET_OWNER_STACK':
942
case 'SELECT_ELEMENT_AT_INDEX':
1106
searchResults: Array<number>,
1107
inspectedElementIndex: number,
1108
): number {
1109
+ // When the currently selected element is itself a match for the new query
1110
+ // (e.g. you cleared the search and retyped the same text while a result was
1111
+ // still selected), advance to the *next* match instead of snapping back to
1112
+ // the same component. This mirrors "find next" semantics in browsers/editors
1113
+ // and avoids the search feeling stuck on the same result.
1114
+ const selectedIsResult = searchResults.some(
1115
+ id => store.getIndexOfElementID(id) === inspectedElementIndex,
1116
+ );
1117
+
1118
const index = searchResults.findIndex(id => {
1119
const innerIndex = store.getIndexOfElementID(id);
1084
- return innerIndex !== null && innerIndex >= inspectedElementIndex;
1120
+ if (innerIndex === null) {
1121
+ return false;
1122
+ }
1123
+ return selectedIsResult
1124
+ ? innerIndex > inspectedElementIndex
1125
+ : innerIndex >= inspectedElementIndex;
1126
});
1127
1128
return index === -1 ? 0 : index;