DevTools: merge element fields in TreeStateContext (#31956)
Stacked on https://github.com/facebook/react/pull/31892, see commit on top. For some reason, there were 2 fields different fields for essentially same thing: `selectedElementID` and `inspectedElementID`. Basically, the change is: ``` selectedElementID -> inspectedElementID selectedElementIndex -> inspectedElementIndex ``` I have a theory that it was due to previously used async approach around element inspection, and the whole `InspectedElementView` was wrapped in `Suspense`.
Ruslan Lesiutin committed
Jan 9, 2025 at 18:13 UTC
d6345482430952306fc83e62d4c14e2622fb1752
12 files changed
+265
-278
packages/react-devtools-inline/__tests__/__e2e__/components.test.js
+10
-10
@@ -119,7 +119,7 @@ test.describe('Components', () => {
119
runOnlyForReactRange('>=16.8');
120
121
// Select the first list item in DevTools.
122
- await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp');
122
+ await devToolsUtils.selectElement(page, 'ListItem', 'List\nApp', true);
123
124
// Then read the inspected values.
125
const sourceText = await page.evaluate(() => {
@@ -127,7 +127,7 @@ test.describe('Components', () => {
127
const container = document.getElementById('devtools');
128
129
const source = findAllNodes(container, [
130
- createTestNameSelector('InspectedElementView-Source'),
130
+ createTestNameSelector('InspectedElementView-FormattedSourceString'),
131
])[0];
132
133
return source.innerText;
@@ -237,35 +237,35 @@ test.describe('Components', () => {
237
}
238
239
await focusComponentSearch();
240
- page.keyboard.insertText('List');
240
+ await page.keyboard.insertText('List');
241
let count = await getComponentSearchResultsCount();
242
expect(count).toBe('1 | 4');
243
244
- page.keyboard.insertText('Item');
244
+ await page.keyboard.insertText('Item');
245
count = await getComponentSearchResultsCount();
246
expect(count).toBe('1 | 3');
247
248
- page.keyboard.press('Enter');
248
+ await page.keyboard.press('Enter');
249
count = await getComponentSearchResultsCount();
250
expect(count).toBe('2 | 3');
251
252
- page.keyboard.press('Enter');
252
+ await page.keyboard.press('Enter');
253
count = await getComponentSearchResultsCount();
254
expect(count).toBe('3 | 3');
255
256
- page.keyboard.press('Enter');
256
+ await page.keyboard.press('Enter');
257
count = await getComponentSearchResultsCount();
258
expect(count).toBe('1 | 3');
259
260
- page.keyboard.press('Shift+Enter');
260
+ await page.keyboard.press('Shift+Enter');
261
count = await getComponentSearchResultsCount();
262
expect(count).toBe('3 | 3');
263
264
- page.keyboard.press('Shift+Enter');
264
+ await page.keyboard.press('Shift+Enter');
265
count = await getComponentSearchResultsCount();
266
expect(count).toBe('2 | 3');
267
268
- page.keyboard.press('Shift+Enter');
268
+ await page.keyboard.press('Shift+Enter');
269
count = await getComponentSearchResultsCount();
270
expect(count).toBe('1 | 3');
271
});
packages/react-devtools-inline/__tests__/__e2e__/devtools-utils.js
+20
-1
@@ -27,7 +27,12 @@ async function getElementCount(page, displayName) {
27
}, displayName);
28
}
29
30
-async function selectElement(page, displayName, waitForOwnersText) {
30
+async function selectElement(
31
+ page,
32
+ displayName,
33
+ waitForOwnersText,
34
+ waitForSourceLoaded = false
35
+) {
36
await page.evaluate(listItemText => {
37
const {createTestNameSelector, createTextSelector, findAllNodes} =
38
window.REACT_DOM_DEVTOOLS;
@@ -69,6 +74,20 @@ async function selectElement(page, displayName, waitForOwnersText) {
74
{titleText: displayName, ownersListText: waitForOwnersText}
75
);
76
}
77
+
78
+ if (waitForSourceLoaded) {
79
+ await page.waitForFunction(() => {
80
+ const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
81
+ const container = document.getElementById('devtools');
82
+
83
+ const sourceStringBlock = findAllNodes(container, [
84
+ createTestNameSelector('InspectedElementView-FormattedSourceString'),
85
+ ])[0];
86
+
87
+ // Wait for a new source line to be fetched
88
+ return sourceStringBlock != null && sourceStringBlock.innerText != null;
89
+ });
90
+ }
91
}
92
93
module.exports = {
packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
+13
-12
@@ -115,16 +115,15 @@ describe('InspectedElement', () => {
115
116
const Contexts = ({
117
children,
118
- defaultSelectedElementID = null,
119
- defaultSelectedElementIndex = null,
118
+ defaultInspectedElementID = null,
119
+ defaultInspectedElementIndex = null,
120
}) => (
121
<BridgeContext.Provider value={bridge}>
122
<StoreContext.Provider value={store}>
123
<SettingsContextController>
124
<TreeContextController
125
- defaultSelectedElementID={defaultSelectedElementID}
126
- defaultSelectedElementIndex={defaultSelectedElementIndex}
127
- defaultInspectedElementID={defaultSelectedElementID}>
125
+ defaultInspectedElementID={defaultInspectedElementID}
126
+ defaultInspectedElementIndex={defaultInspectedElementIndex}>
127
<InspectedElementContextController>
128
{children}
129
</InspectedElementContextController>
@@ -167,8 +166,8 @@ describe('InspectedElement', () => {
166
testRendererInstance.update(
167
<ErrorBoundary>
168
<Contexts
170
- defaultSelectedElementID={id}
171
- defaultSelectedElementIndex={index}>
169
+ defaultInspectedElementID={id}
170
+ defaultInspectedElementIndex={index}>
171
<React.Suspense fallback={null}>
172
<Suspender id={id} index={index} />
173
</React.Suspense>
@@ -355,7 +354,7 @@ describe('InspectedElement', () => {
354
const {index, shouldHaveLegacyContext} = cases[i];
355
356
// HACK: Recreate TestRenderer instance because we rely on default state values
358
- // from props like defaultSelectedElementID and it's easier to reset here than
357
+ // from props like defaultInspectedElementID and it's easier to reset here than
358
// to read the TreeDispatcherContext and update the selected ID that way.
359
// We're testing the inspected values here, not the context wiring, so that's ok.
360
withErrorsOrWarningsIgnored(
@@ -2069,7 +2068,7 @@ describe('InspectedElement', () => {
2068
}, false);
2069
2070
// HACK: Recreate TestRenderer instance because we rely on default state values
2072
- // from props like defaultSelectedElementID and it's easier to reset here than
2071
+ // from props like defaultInspectedElementID and it's easier to reset here than
2072
// to read the TreeDispatcherContext and update the selected ID that way.
2073
// We're testing the inspected values here, not the context wiring, so that's ok.
2074
withErrorsOrWarningsIgnored(
@@ -2129,7 +2128,7 @@ describe('InspectedElement', () => {
2128
}, false);
2129
2130
// HACK: Recreate TestRenderer instance because we rely on default state values
2132
- // from props like defaultSelectedElementID and it's easier to reset here than
2131
+ // from props like defaultInspectedElementID and it's easier to reset here than
2132
// to read the TreeDispatcherContext and update the selected ID that way.
2133
// We're testing the inspected values here, not the context wiring, so that's ok.
2134
withErrorsOrWarningsIgnored(
@@ -2408,8 +2407,8 @@ describe('InspectedElement', () => {
2407
await utils.actAsync(() => {
2408
root = TestRenderer.create(
2409
<Contexts
2411
- defaultSelectedElementID={id}
2412
- defaultSelectedElementIndex={index}>
2410
+ defaultInspectedElementID={id}
2411
+ defaultInspectedElementIndex={index}>
2412
<React.Suspense fallback={null}>
2413
<Suspender target={id} />
2414
</React.Suspense>
@@ -3101,6 +3100,7 @@ describe('InspectedElement', () => {
3100
3101
await utils.actAsync(() => {
3102
store.componentFilters = [utils.createDisplayNameFilter('Wrapper')];
3103
+ jest.runOnlyPendingTimers();
3104
}, false);
3105
3106
expect(state).toMatchInlineSnapshot(`
@@ -3120,6 +3120,7 @@ describe('InspectedElement', () => {
3120
3121
await utils.actAsync(() => {
3122
store.componentFilters = [];
3123
+ jest.runOnlyPendingTimers();
3124
}, false);
3125
expect(state).toMatchInlineSnapshot(`
3126
✕ 0, ⚠ 2
packages/react-devtools-shared/src/__tests__/profilerContext-test.js
+34
-27
@@ -69,14 +69,14 @@ describe('ProfilerContext', () => {
69
70
const Contexts = ({
71
children = null,
72
- defaultSelectedElementID = null,
73
- defaultSelectedElementIndex = null,
72
+ defaultInspectedElementID = null,
73
+ defaultInspectedElementIndex = null,
74
}: any) => (
75
<BridgeContext.Provider value={bridge}>
76
<StoreContext.Provider value={store}>
77
<TreeContextController
78
- defaultSelectedElementID={defaultSelectedElementID}
79
- defaultSelectedElementIndex={defaultSelectedElementIndex}>
78
+ defaultInspectedElementID={defaultInspectedElementID}
79
+ defaultInspectedElementIndex={defaultInspectedElementIndex}>
80
<ProfilerContextController>{children}</ProfilerContextController>
81
</TreeContextController>
82
</StoreContext.Provider>
@@ -225,8 +225,8 @@ describe('ProfilerContext', () => {
225
await utils.actAsync(() =>
226
TestRenderer.create(
227
<Contexts
228
- defaultSelectedElementID={store.getElementIDAtIndex(3)}
229
- defaultSelectedElementIndex={3}>
228
+ defaultInspectedElementID={store.getElementIDAtIndex(3)}
229
+ defaultInspectedElementIndex={3}>
230
<ContextReader />
231
</Contexts>,
232
),
@@ -276,8 +276,8 @@ describe('ProfilerContext', () => {
276
await utils.actAsync(() =>
277
TestRenderer.create(
278
<Contexts
279
- defaultSelectedElementID={store.getElementIDAtIndex(3)}
280
- defaultSelectedElementIndex={3}>
279
+ defaultInspectedElementID={store.getElementIDAtIndex(3)}
280
+ defaultInspectedElementIndex={3}>
281
<ContextReader />
282
</Contexts>,
283
),
@@ -323,8 +323,8 @@ describe('ProfilerContext', () => {
323
await utils.actAsync(() =>
324
TestRenderer.create(
325
<Contexts
326
- defaultSelectedElementID={store.getElementIDAtIndex(3)}
327
- defaultSelectedElementIndex={3}>
326
+ defaultInspectedElementID={store.getElementIDAtIndex(3)}
327
+ defaultInspectedElementIndex={3}>
328
<ContextReader />
329
</Contexts>,
330
),
@@ -374,8 +374,8 @@ describe('ProfilerContext', () => {
374
await utils.actAsync(() =>
375
TestRenderer.create(
376
<Contexts
377
- defaultSelectedElementID={store.getElementIDAtIndex(3)}
378
- defaultSelectedElementIndex={3}>
377
+ defaultInspectedElementID={store.getElementIDAtIndex(3)}
378
+ defaultInspectedElementIndex={3}>
379
<ContextReader />
380
</Contexts>,
381
),
@@ -415,11 +415,12 @@ describe('ProfilerContext', () => {
415
416
let context: Context = ((null: any): Context);
417
let dispatch: DispatcherContext = ((null: any): DispatcherContext);
418
- let selectedElementID = null;
418
+ let inspectedElementID = null;
419
function ContextReader() {
420
context = React.useContext(ProfilerContext);
421
dispatch = React.useContext(TreeDispatcherContext);
422
- selectedElementID = React.useContext(TreeStateContext).selectedElementID;
422
+ inspectedElementID =
423
+ React.useContext(TreeStateContext).inspectedElementID;
424
return null;
425
}
426
@@ -428,13 +429,15 @@ describe('ProfilerContext', () => {
429
// Select an element within the second root.
430
await utils.actAsync(() =>
431
TestRenderer.create(
431
- <Contexts defaultSelectedElementID={id} defaultSelectedElementIndex={3}>
432
+ <Contexts
433
+ defaultInspectedElementID={id}
434
+ defaultInspectedElementIndex={3}>
435
<ContextReader />
436
</Contexts>,
437
),
438
);
439
437
- expect(selectedElementID).toBe(id);
440
+ expect(inspectedElementID).toBe(id);
441
442
// Profile and record more updates to both roots
443
await utils.actAsync(() => store.profilerStore.startProfiling());
@@ -448,7 +451,7 @@ describe('ProfilerContext', () => {
451
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
452
453
// Verify that the initial Profiler root selection is maintained.
451
- expect(selectedElementID).toBe(otherID);
454
+ expect(inspectedElementID).toBe(otherID);
455
expect(context).not.toBeNull();
456
expect(context.rootID).toBe(store.getRootIDForElement(id));
457
});
@@ -484,11 +487,12 @@ describe('ProfilerContext', () => {
487
488
let context: Context = ((null: any): Context);
489
let dispatch: DispatcherContext = ((null: any): DispatcherContext);
487
- let selectedElementID = null;
490
+ let inspectedElementID = null;
491
function ContextReader() {
492
context = React.useContext(ProfilerContext);
493
dispatch = React.useContext(TreeDispatcherContext);
491
- selectedElementID = React.useContext(TreeStateContext).selectedElementID;
494
+ inspectedElementID =
495
+ React.useContext(TreeStateContext).inspectedElementID;
496
return null;
497
}
498
@@ -497,13 +501,15 @@ describe('ProfilerContext', () => {
501
// Select an element within the second root.
502
await utils.actAsync(() =>
503
TestRenderer.create(
500
- <Contexts defaultSelectedElementID={id} defaultSelectedElementIndex={3}>
504
+ <Contexts
505
+ defaultInspectedElementID={id}
506
+ defaultInspectedElementIndex={3}>
507
<ContextReader />
508
</Contexts>,
509
),
510
);
511
506
- expect(selectedElementID).toBe(id);
512
+ expect(inspectedElementID).toBe(id);
513
514
// Profile and record more updates to both roots
515
await utils.actAsync(() => store.profilerStore.startProfiling());
@@ -517,7 +523,7 @@ describe('ProfilerContext', () => {
523
utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
524
525
// Verify that the initial Profiler root selection is maintained.
520
- expect(selectedElementID).toBe(otherID);
526
+ expect(inspectedElementID).toBe(otherID);
527
expect(context).not.toBeNull();
528
expect(context.rootID).toBe(store.getRootIDForElement(id));
529
});
@@ -553,10 +559,11 @@ describe('ProfilerContext', () => {
559
`);
560
561
let context: Context = ((null: any): Context);
556
- let selectedElementID = null;
562
+ let inspectedElementID = null;
563
function ContextReader() {
564
context = React.useContext(ProfilerContext);
559
- selectedElementID = React.useContext(TreeStateContext).selectedElementID;
565
+ inspectedElementID =
566
+ React.useContext(TreeStateContext).inspectedElementID;
567
return null;
568
}
569
@@ -567,14 +574,14 @@ describe('ProfilerContext', () => {
574
</Contexts>,
575
),
576
);
570
- expect(selectedElementID).toBeNull();
577
+ expect(inspectedElementID).toBeNull();
578
579
// Select an element in the Profiler tab and verify that the selection is synced to the Components tab.
580
await utils.actAsync(() => context.selectFiber(parentID, 'Parent'));
574
- expect(selectedElementID).toBe(parentID);
581
+ expect(inspectedElementID).toBe(parentID);
582
583
// Select an unmounted element and verify no Components tab selection doesn't change.
584
await utils.actAsync(() => context.selectFiber(childID, 'Child'));
578
- expect(selectedElementID).toBe(parentID);
585
+ expect(inspectedElementID).toBe(parentID);
586
});
587
});
packages/react-devtools-shared/src/devtools/utils.js
+1
-1
@@ -67,7 +67,7 @@ export function printStore(
67
if (state === null) {
68
return '';
69
}
70
- return state.selectedElementIndex === index ? `→` : ' ';
70
+ return state.inspectedElementIndex === index ? `→` : ' ';
71
}
72
73
function printErrorsAndWarnings(element: Element): string {
packages/react-devtools-shared/src/devtools/views/Components/Element.js
+2
-2
@@ -33,7 +33,7 @@ type Props = {
33
34
export default function Element({data, index, style}: Props): React.Node {
35
const store = useContext(StoreContext);
36
- const {ownerFlatTree, ownerID, selectedElementID} =
36
+ const {ownerFlatTree, ownerID, inspectedElementID} =
37
useContext(TreeStateContext);
38
const dispatch = useContext(TreeDispatcherContext);
39
@@ -46,7 +46,7 @@ export default function Element({data, index, style}: Props): React.Node {
46
47
const {isNavigatingWithKeyboard, onElementMouseEnter, treeFocused} = data;
48
const id = element === null ? null : element.id;
49
- const isSelected = selectedElementID === id;
49
+ const isSelected = inspectedElementID === id;
50
51
const errorsAndWarningsSubscription = useMemo(
52
() => ({
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js
+7
-3
@@ -28,7 +28,7 @@ function InspectedElementSourcePanel({
28
symbolicatedSourcePromise,
29
}: Props): React.Node {
30
return (
31
- <div data-testname="InspectedElementView-Source">
31
+ <div>
32
<div className={styles.SourceHeaderRow}>
33
<div className={styles.SourceHeader}>source</div>
34
@@ -84,7 +84,9 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
84
const {sourceURL, line} = source;
85
86
return (
87
- <div className={styles.SourceOneLiner}>
87
+ <div
88
+ className={styles.SourceOneLiner}
89
+ data-testname="InspectedElementView-FormattedSourceString">
90
{formatSourceForDisplay(sourceURL, line)}
91
</div>
92
);
@@ -93,7 +95,9 @@ function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
95
const {sourceURL, line} = symbolicatedSource;
96
97
return (
96
- <div className={styles.SourceOneLiner}>
98
+ <div
99
+ className={styles.SourceOneLiner}
100
+ data-testname="InspectedElementView-FormattedSourceString">
101
{formatSourceForDisplay(sourceURL, line)}
102
</div>
103
);
packages/react-devtools-shared/src/devtools/views/Components/NativeStyleEditor/context.js
+9
-9
@@ -100,10 +100,10 @@ function NativeStyleContextController({children}: Props): React.Node {
100
[store],
101
);
102
103
- // It's very important that this context consumes selectedElementID and not NativeStyleID.
103
+ // It's very important that this context consumes inspectedElementID and not NativeStyleID.
104
// Otherwise the effect that sends the "inspect" message across the bridge-
105
// would itself be blocked by the same render that suspends (waiting for the data).
106
- const {selectedElementID} = useContext<StateContext>(TreeStateContext);
106
+ const {inspectedElementID} = useContext<StateContext>(TreeStateContext);
107
108
const [currentStyleAndLayout, setCurrentStyleAndLayout] =
109
useState<StyleAndLayoutFrontend | null>(null);
@@ -128,7 +128,7 @@ function NativeStyleContextController({children}: Props): React.Node {
128
resource.write(element, styleAndLayout);
129
130
// Schedule update with React if the currently-selected element has been invalidated.
131
- if (id === selectedElementID) {
131
+ if (id === inspectedElementID) {
132
setCurrentStyleAndLayout(styleAndLayout);
133
}
134
}
@@ -141,15 +141,15 @@ function NativeStyleContextController({children}: Props): React.Node {
141
'NativeStyleEditor_styleAndLayout',
142
onStyleAndLayout,
143
);
144
- }, [bridge, currentStyleAndLayout, selectedElementID, store]);
144
+ }, [bridge, currentStyleAndLayout, inspectedElementID, store]);
145
146
// This effect handler polls for updates on the currently selected element.
147
useEffect(() => {
148
- if (selectedElementID === null) {
148
+ if (inspectedElementID === null) {
149
return () => {};
150
}
151
152
- const rendererID = store.getRendererIDForElement(selectedElementID);
152
+ const rendererID = store.getRendererIDForElement(inspectedElementID);
153
154
let timeoutID: TimeoutID | null = null;
155
@@ -158,7 +158,7 @@ function NativeStyleContextController({children}: Props): React.Node {
158
159
if (rendererID !== null) {
160
bridge.send('NativeStyleEditor_measure', {
161
- id: selectedElementID,
161
+ id: inspectedElementID,
162
rendererID,
163
});
164
}
@@ -170,7 +170,7 @@ function NativeStyleContextController({children}: Props): React.Node {
170
171
const onStyleAndLayout = ({id}: StyleAndLayoutBackend) => {
172
// If this is the element we requested, wait a little bit and then ask for another update.
173
- if (id === selectedElementID) {
173
+ if (id === inspectedElementID) {
174
if (timeoutID !== null) {
175
clearTimeout(timeoutID);
176
}
@@ -190,7 +190,7 @@ function NativeStyleContextController({children}: Props): React.Node {
190
clearTimeout(timeoutID);
191
}
192
};
193
- }, [bridge, selectedElementID, store]);
193
+ }, [bridge, inspectedElementID, store]);
194
195
const value = useMemo(
196
() => ({getStyleAndLayout}),
packages/react-devtools-shared/src/devtools/views/Components/SelectedTreeHighlight.js
+5
-5
@@ -28,19 +28,19 @@ export default function SelectedTreeHighlight(_: {}): React.Node {
28
const {lineHeight} = useContext(SettingsContext);
29
const store = useContext(StoreContext);
30
const treeFocused = useContext(TreeFocusedContext);
31
- const {ownerID, selectedElementID} = useContext(TreeStateContext);
31
+ const {ownerID, inspectedElementID} = useContext(TreeStateContext);
32
33
const subscription = useMemo(
34
() => ({
35
getCurrentValue: () => {
36
if (
37
- selectedElementID === null ||
38
- store.isInsideCollapsedSubTree(selectedElementID)
37
+ inspectedElementID === null ||
38
+ store.isInsideCollapsedSubTree(inspectedElementID)
39
) {
40
return null;
41
}
42
43
- const element = store.getElementByID(selectedElementID);
43
+ const element = store.getElementByID(inspectedElementID);
44
if (
45
element === null ||
46
element.isCollapsed ||
@@ -83,7 +83,7 @@ export default function SelectedTreeHighlight(_: {}): React.Node {
83
};
84
},
85
}),
86
- [selectedElementID, store],
86
+ [inspectedElementID, store],
87
);
88
const data = useSubscription<Data | null>(subscription);
89
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+16
-16
@@ -54,8 +54,8 @@ export default function Tree(): React.Node {
54
ownerID,
55
searchIndex,
56
searchResults,
57
- selectedElementID,
58
- selectedElementIndex,
57
+ inspectedElementID,
58
+ inspectedElementIndex,
59
} = useContext(TreeStateContext);
60
const bridge = useContext(BridgeContext);
61
const store = useContext(StoreContext);
@@ -84,11 +84,11 @@ export default function Tree(): React.Node {
84
// Using a callback ref accounts for this case...
85
const listCallbackRef = useCallback(
86
(list: $FlowFixMe) => {
87
- if (list != null && selectedElementIndex !== null) {
88
- list.scrollToItem(selectedElementIndex, 'smart');
87
+ if (list != null && inspectedElementIndex !== null) {
88
+ list.scrollToItem(inspectedElementIndex, 'smart');
89
}
90
},
91
- [selectedElementIndex],
91
+ [inspectedElementIndex],
92
);
93
94
// Picking an element in the inspector should put focus into the tree.
@@ -133,8 +133,8 @@ export default function Tree(): React.Node {
133
case 'ArrowLeft':
134
event.preventDefault();
135
element =
136
- selectedElementID !== null
137
- ? store.getElementByID(selectedElementID)
136
+ inspectedElementID !== null
137
+ ? store.getElementByID(inspectedElementID)
138
: null;
139
if (element !== null) {
140
if (event.altKey) {
@@ -153,8 +153,8 @@ export default function Tree(): React.Node {
153
case 'ArrowRight':
154
event.preventDefault();
155
element =
156
- selectedElementID !== null
157
- ? store.getElementByID(selectedElementID)
156
+ inspectedElementID !== null
157
+ ? store.getElementByID(inspectedElementID)
158
: null;
159
if (element !== null) {
160
if (event.altKey) {
@@ -202,7 +202,7 @@ export default function Tree(): React.Node {
202
return () => {
203
container.removeEventListener('keydown', handleKeyDown);
204
};
205
- }, [dispatch, selectedElementID, store]);
205
+ }, [dispatch, inspectedElementID, store]);
206
207
// Focus management.
208
const handleBlur = useCallback(() => setTreeFocused(false), []);
@@ -213,15 +213,15 @@ export default function Tree(): React.Node {
213
switch (event.key) {
214
case 'Enter':
215
case ' ':
216
- if (selectedElementID !== null) {
217
- dispatch({type: 'SELECT_OWNER', payload: selectedElementID});
216
+ if (inspectedElementID !== null) {
217
+ dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
218
}
219
break;
220
default:
221
break;
222
}
223
},
224
- [dispatch, selectedElementID],
224
+ [dispatch, inspectedElementID],
225
);
226
227
// If we switch the selected element while using the keyboard,
@@ -238,8 +238,8 @@ export default function Tree(): React.Node {
238
didSelectNewSearchResult = true;
239
}
240
if (isNavigatingWithKeyboard || didSelectNewSearchResult) {
241
- if (selectedElementID !== null) {
242
- highlightHostInstance(selectedElementID);
241
+ if (inspectedElementID !== null) {
242
+ highlightHostInstance(inspectedElementID);
243
} else {
244
clearHighlightHostInstance();
245
}
@@ -250,7 +250,7 @@ export default function Tree(): React.Node {
250
highlightHostInstance,
251
searchIndex,
252
searchResults,
253
- selectedElementID,
253
+ inspectedElementID,
254
]);
255
256
// Highlight last hovered element.
packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js
+145
-189
@@ -29,14 +29,12 @@ import type {ReactContext} from 'shared/ReactTypes';
29
import * as React from 'react';
30
import {
31
createContext,
32
- useCallback,
32
useContext,
33
useEffect,
34
useLayoutEffect,
35
useMemo,
36
useReducer,
37
useRef,
39
- startTransition,
38
} from 'react';
39
import {createRegExp} from '../utils';
40
import {StoreContext} from '../context';
@@ -48,8 +46,6 @@ export type StateContext = {
46
// Tree
47
numElements: number,
48
ownerSubtreeLeafElementID: number | null,
51
- selectedElementID: number | null,
52
- selectedElementIndex: number | null,
49
50
// Search
51
searchIndex: number | null,
@@ -62,6 +58,7 @@ export type StateContext = {
58
59
// Inspection element panel
60
inspectedElementID: number | null,
61
+ inspectedElementIndex: number | null,
62
};
63
64
type ACTION_GO_TO_NEXT_SEARCH_RESULT = {
@@ -123,9 +120,6 @@ type ACTION_SET_SEARCH_TEXT = {
120
type: 'SET_SEARCH_TEXT',
121
payload: string,
122
};
126
-type ACTION_UPDATE_INSPECTED_ELEMENT_ID = {
127
- type: 'UPDATE_INSPECTED_ELEMENT_ID',
128
-};
123
124
type Action =
125
| ACTION_GO_TO_NEXT_SEARCH_RESULT
@@ -145,8 +139,7 @@ type Action =
139
| ACTION_SELECT_PREVIOUS_SIBLING_IN_TREE
140
| ACTION_SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE
141
| ACTION_SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE
148
- | ACTION_SET_SEARCH_TEXT
149
- | ACTION_UPDATE_INSPECTED_ELEMENT_ID;
142
+ | ACTION_SET_SEARCH_TEXT;
143
144
export type DispatcherContext = (action: Action) => void;
145
@@ -162,8 +155,6 @@ type State = {
155
// Tree
156
numElements: number,
157
ownerSubtreeLeafElementID: number | null,
165
- selectedElementID: number | null,
166
- selectedElementIndex: number | null,
158
159
// Search
160
searchIndex: number | null,
@@ -176,14 +167,15 @@ type State = {
167
168
// Inspection element panel
169
inspectedElementID: number | null,
170
+ inspectedElementIndex: number | null,
171
};
172
173
function reduceTreeState(store: Store, state: State, action: Action): State {
174
let {
175
numElements,
176
ownerSubtreeLeafElementID,
185
- selectedElementIndex,
186
- selectedElementID,
177
+ inspectedElementID,
178
+ inspectedElementIndex,
179
} = state;
180
const ownerID = state.ownerID;
181
@@ -201,34 +193,33 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
193
// We deduce the parent-child mapping from removedIDs (id -> parentID)
194
// because by now it's too late to read them from the store.
195
while (
204
- selectedElementID !== null &&
205
- removedIDs.has(selectedElementID)
196
+ inspectedElementID !== null &&
197
+ removedIDs.has(inspectedElementID)
198
) {
207
- selectedElementID = ((removedIDs.get(
208
- selectedElementID,
209
- ): any): number);
199
+ // $FlowExpectedError[incompatible-type]
200
+ inspectedElementID = removedIDs.get(inspectedElementID);
201
}
211
- if (selectedElementID === 0) {
202
+ if (inspectedElementID === 0) {
203
// The whole root was removed.
213
- selectedElementIndex = null;
204
+ inspectedElementIndex = null;
205
}
206
break;
207
case 'SELECT_CHILD_ELEMENT_IN_TREE':
208
ownerSubtreeLeafElementID = null;
209
219
- if (selectedElementIndex !== null) {
220
- const selectedElement = store.getElementAtIndex(
221
- ((selectedElementIndex: any): number),
210
+ if (inspectedElementIndex !== null) {
211
+ const inspectedElement = store.getElementAtIndex(
212
+ inspectedElementIndex,
213
);
214
if (
224
- selectedElement !== null &&
225
- selectedElement.children.length > 0 &&
226
- !selectedElement.isCollapsed
215
+ inspectedElement !== null &&
216
+ inspectedElement.children.length > 0 &&
217
+ !inspectedElement.isCollapsed
218
) {
228
- const firstChildID = selectedElement.children[0];
219
+ const firstChildID = inspectedElement.children[0];
220
const firstChildIndex = store.getIndexOfElementID(firstChildID);
221
if (firstChildIndex !== null) {
231
- selectedElementIndex = firstChildIndex;
222
+ inspectedElementIndex = firstChildIndex;
223
}
224
}
225
}
@@ -236,7 +227,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
227
case 'SELECT_ELEMENT_AT_INDEX':
228
ownerSubtreeLeafElementID = null;
229
239
- selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
230
+ inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX)
231
+ .payload;
232
break;
233
case 'SELECT_ELEMENT_BY_ID':
234
ownerSubtreeLeafElementID = null;
@@ -245,30 +237,30 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
237
// It might also cause problems if the specified element was inside of a (not yet expanded) subtree.
238
lookupIDForIndex = false;
239
248
- selectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
249
- selectedElementIndex =
250
- selectedElementID === null
240
+ inspectedElementID = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
241
+ inspectedElementIndex =
242
+ inspectedElementID === null
243
? null
252
- : store.getIndexOfElementID(selectedElementID);
244
+ : store.getIndexOfElementID(inspectedElementID);
245
break;
246
case 'SELECT_NEXT_ELEMENT_IN_TREE':
247
ownerSubtreeLeafElementID = null;
248
249
if (
258
- selectedElementIndex === null ||
259
- selectedElementIndex + 1 >= numElements
250
+ inspectedElementIndex === null ||
251
+ inspectedElementIndex + 1 >= numElements
252
) {
261
- selectedElementIndex = 0;
253
+ inspectedElementIndex = 0;
254
} else {
263
- selectedElementIndex++;
255
+ inspectedElementIndex++;
256
}
257
break;
258
case 'SELECT_NEXT_SIBLING_IN_TREE':
259
ownerSubtreeLeafElementID = null;
260
269
- if (selectedElementIndex !== null) {
261
+ if (inspectedElementIndex !== null) {
262
const selectedElement = store.getElementAtIndex(
271
- ((selectedElementIndex: any): number),
263
+ ((inspectedElementIndex: any): number),
264
);
265
if (selectedElement !== null && selectedElement.parentID !== 0) {
266
const parent = store.getElementByID(selectedElement.parentID);
@@ -279,23 +271,23 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
271
selectedChildIndex < children.length - 1
272
? children[selectedChildIndex + 1]
273
: children[0];
282
- selectedElementIndex = store.getIndexOfElementID(nextChildID);
274
+ inspectedElementIndex = store.getIndexOfElementID(nextChildID);
275
}
276
}
277
}
278
break;
279
case 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE':
288
- if (selectedElementIndex !== null) {
280
+ if (inspectedElementIndex !== null) {
281
if (
282
ownerSubtreeLeafElementID !== null &&
291
- ownerSubtreeLeafElementID !== selectedElementID
283
+ ownerSubtreeLeafElementID !== inspectedElementID
284
) {
285
const leafElement = store.getElementByID(ownerSubtreeLeafElementID);
286
if (leafElement !== null) {
287
let currentElement: null | Element = leafElement;
288
while (currentElement !== null) {
297
- if (currentElement.ownerID === selectedElementID) {
298
- selectedElementIndex = store.getIndexOfElementID(
289
+ if (currentElement.ownerID === inspectedElementID) {
290
+ inspectedElementIndex = store.getIndexOfElementID(
291
currentElement.id,
292
);
293
break;
@@ -308,23 +300,23 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
300
}
301
break;
302
case 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE':
311
- if (selectedElementIndex !== null) {
303
+ if (inspectedElementIndex !== null) {
304
if (ownerSubtreeLeafElementID === null) {
305
// If this is the first time we're stepping through the owners tree,
306
// pin the current component as the owners list leaf.
307
// This will enable us to step back down to this component.
316
- ownerSubtreeLeafElementID = selectedElementID;
308
+ ownerSubtreeLeafElementID = inspectedElementID;
309
}
310
311
const selectedElement = store.getElementAtIndex(
320
- ((selectedElementIndex: any): number),
312
+ ((inspectedElementIndex: any): number),
313
);
314
if (selectedElement !== null && selectedElement.ownerID !== 0) {
315
const ownerIndex = store.getIndexOfElementID(
316
selectedElement.ownerID,
317
);
318
if (ownerIndex !== null) {
327
- selectedElementIndex = ownerIndex;
319
+ inspectedElementIndex = ownerIndex;
320
}
321
}
322
}
@@ -332,16 +324,16 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
324
case 'SELECT_PARENT_ELEMENT_IN_TREE':
325
ownerSubtreeLeafElementID = null;
326
335
- if (selectedElementIndex !== null) {
327
+ if (inspectedElementIndex !== null) {
328
const selectedElement = store.getElementAtIndex(
337
- ((selectedElementIndex: any): number),
329
+ ((inspectedElementIndex: any): number),
330
);
331
if (selectedElement !== null && selectedElement.parentID !== 0) {
332
const parentIndex = store.getIndexOfElementID(
333
selectedElement.parentID,
334
);
335
if (parentIndex !== null) {
344
- selectedElementIndex = parentIndex;
336
+ inspectedElementIndex = parentIndex;
337
}
338
}
339
}
@@ -349,18 +341,18 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
341
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
342
ownerSubtreeLeafElementID = null;
343
352
- if (selectedElementIndex === null || selectedElementIndex === 0) {
353
- selectedElementIndex = numElements - 1;
344
+ if (inspectedElementIndex === null || inspectedElementIndex === 0) {
345
+ inspectedElementIndex = numElements - 1;
346
} else {
355
- selectedElementIndex--;
347
+ inspectedElementIndex--;
348
}
349
break;
350
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
351
ownerSubtreeLeafElementID = null;
352
361
- if (selectedElementIndex !== null) {
353
+ if (inspectedElementIndex !== null) {
354
const selectedElement = store.getElementAtIndex(
363
- ((selectedElementIndex: any): number),
355
+ ((inspectedElementIndex: any): number),
356
);
357
if (selectedElement !== null && selectedElement.parentID !== 0) {
358
const parent = store.getElementByID(selectedElement.parentID);
@@ -371,7 +363,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
363
selectedChildIndex > 0
364
? children[selectedChildIndex - 1]
365
: children[children.length - 1];
374
- selectedElementIndex = store.getIndexOfElementID(nextChildID);
366
+ inspectedElementIndex = store.getIndexOfElementID(nextChildID);
367
}
368
}
369
}
@@ -384,7 +376,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
376
}
377
378
let flatIndex = 0;
387
- if (selectedElementIndex !== null) {
379
+ if (inspectedElementIndex !== null) {
380
// Resume from the current position in the list.
381
// Otherwise step to the previous item, relative to the current selection.
382
for (
@@ -393,7 +385,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
385
i--
386
) {
387
const {index} = elementIndicesWithErrorsOrWarnings[i];
396
- if (index >= selectedElementIndex) {
388
+ if (index >= inspectedElementIndex) {
389
flatIndex = i;
390
} else {
391
break;
@@ -407,12 +399,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
399
elementIndicesWithErrorsOrWarnings[
400
elementIndicesWithErrorsOrWarnings.length - 1
401
];
410
- selectedElementID = prevEntry.id;
411
- selectedElementIndex = prevEntry.index;
402
+ inspectedElementID = prevEntry.id;
403
+ inspectedElementIndex = prevEntry.index;
404
} else {
405
prevEntry = elementIndicesWithErrorsOrWarnings[flatIndex - 1];
414
- selectedElementID = prevEntry.id;
415
- selectedElementIndex = prevEntry.index;
406
+ inspectedElementID = prevEntry.id;
407
+ inspectedElementIndex = prevEntry.index;
408
}
409
410
lookupIDForIndex = false;
@@ -426,12 +418,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
418
}
419
420
let flatIndex = -1;
429
- if (selectedElementIndex !== null) {
421
+ if (inspectedElementIndex !== null) {
422
// Resume from the current position in the list.
423
// Otherwise step to the next item, relative to the current selection.
424
for (let i = 0; i < elementIndicesWithErrorsOrWarnings.length; i++) {
425
const {index} = elementIndicesWithErrorsOrWarnings[i];
434
- if (index <= selectedElementIndex) {
426
+ if (index <= inspectedElementIndex) {
427
flatIndex = i;
428
} else {
429
break;
@@ -442,12 +434,12 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
434
let nextEntry;
435
if (flatIndex >= elementIndicesWithErrorsOrWarnings.length - 1) {
436
nextEntry = elementIndicesWithErrorsOrWarnings[0];
445
- selectedElementID = nextEntry.id;
446
- selectedElementIndex = nextEntry.index;
437
+ inspectedElementID = nextEntry.id;
438
+ inspectedElementIndex = nextEntry.index;
439
} else {
440
nextEntry = elementIndicesWithErrorsOrWarnings[flatIndex + 1];
449
- selectedElementID = nextEntry.id;
450
- selectedElementIndex = nextEntry.index;
441
+ inspectedElementID = nextEntry.id;
442
+ inspectedElementIndex = nextEntry.index;
443
}
444
445
lookupIDForIndex = false;
@@ -460,12 +452,15 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
452
}
453
454
// Keep selected item ID and index in sync.
463
- if (lookupIDForIndex && selectedElementIndex !== state.selectedElementIndex) {
464
- if (selectedElementIndex === null) {
465
- selectedElementID = null;
455
+ if (
456
+ lookupIDForIndex &&
457
+ inspectedElementIndex !== state.inspectedElementIndex
458
+ ) {
459
+ if (inspectedElementIndex === null) {
460
+ inspectedElementID = null;
461
} else {
467
- selectedElementID = store.getElementIDAtIndex(
468
- ((selectedElementIndex: any): number),
462
+ inspectedElementID = store.getElementIDAtIndex(
463
+ ((inspectedElementIndex: any): number),
464
);
465
}
466
}
@@ -475,8 +470,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
470
471
numElements,
472
ownerSubtreeLeafElementID,
478
- selectedElementIndex,
479
- selectedElementID,
473
+ inspectedElementIndex,
474
+ inspectedElementID,
475
};
476
}
477
@@ -485,8 +480,8 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
480
searchIndex,
481
searchResults,
482
searchText,
488
- selectedElementID,
489
- selectedElementIndex,
483
+ inspectedElementID,
484
+ inspectedElementIndex,
485
} = state;
486
const ownerID = state.ownerID;
487
@@ -594,11 +589,11 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
589
});
590
if (searchResults.length > 0) {
591
if (prevSearchIndex === null) {
597
- if (selectedElementIndex !== null) {
592
+ if (inspectedElementIndex !== null) {
593
searchIndex = getNearestResultIndex(
594
store,
595
searchResults,
601
- selectedElementIndex,
596
+ inspectedElementIndex,
597
);
598
} else {
599
searchIndex = 0;
@@ -619,7 +614,7 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
614
}
615
616
if (searchText !== prevSearchText) {
622
- const newSearchIndex = searchResults.indexOf(selectedElementID);
617
+ const newSearchIndex = searchResults.indexOf(inspectedElementID);
618
if (newSearchIndex === -1) {
619
// Only move the selection if the new query
620
// doesn't match the current selection anymore.
@@ -631,17 +626,17 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
626
}
627
}
628
if (didRequestSearch && searchIndex !== null) {
634
- selectedElementID = ((searchResults[searchIndex]: any): number);
635
- selectedElementIndex = store.getIndexOfElementID(
636
- ((selectedElementID: any): number),
629
+ inspectedElementID = ((searchResults[searchIndex]: any): number);
630
+ inspectedElementIndex = store.getIndexOfElementID(
631
+ ((inspectedElementID: any): number),
632
);
633
}
634
635
return {
636
...state,
637
643
- selectedElementID,
644
- selectedElementIndex,
638
+ inspectedElementID,
639
+ inspectedElementIndex,
640
641
searchIndex,
642
searchResults,
@@ -652,14 +647,14 @@ function reduceSearchState(store: Store, state: State, action: Action): State {
647
function reduceOwnersState(store: Store, state: State, action: Action): State {
648
let {
649
numElements,
655
- selectedElementID,
656
- selectedElementIndex,
650
ownerID,
651
ownerFlatTree,
652
+ inspectedElementID,
653
+ inspectedElementIndex,
654
} = state;
655
const {searchIndex, searchResults, searchText} = state;
656
662
- let prevSelectedElementIndex = selectedElementIndex;
657
+ let prevInspectedElementIndex = inspectedElementIndex;
658
659
switch (action.type) {
660
case 'HANDLE_STORE_MUTATION':
@@ -667,75 +662,76 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
662
if (!store.containsElement(ownerID)) {
663
ownerID = null;
664
ownerFlatTree = null;
670
- selectedElementID = null;
665
+ prevInspectedElementIndex = null;
666
} else {
667
ownerFlatTree = store.getOwnersListForElement(ownerID);
673
- if (selectedElementID !== null) {
668
+ if (inspectedElementID !== null) {
669
// Mutation might have caused the index of this ID to shift.
675
- selectedElementIndex = ownerFlatTree.findIndex(
676
- element => element.id === selectedElementID,
670
+ prevInspectedElementIndex = ownerFlatTree.findIndex(
671
+ element => element.id === inspectedElementID,
672
);
673
}
674
}
675
} else {
681
- if (selectedElementID !== null) {
676
+ if (inspectedElementID !== null) {
677
// Mutation might have caused the index of this ID to shift.
683
- selectedElementIndex = store.getIndexOfElementID(selectedElementID);
678
+ inspectedElementIndex = store.getIndexOfElementID(inspectedElementID);
679
}
680
}
686
- if (selectedElementIndex === -1) {
681
+ if (inspectedElementIndex === -1) {
682
// If we couldn't find this ID after mutation, unselect it.
688
- selectedElementIndex = null;
689
- selectedElementID = null;
683
+ inspectedElementIndex = null;
684
+ inspectedElementID = null;
685
}
686
break;
687
case 'RESET_OWNER_STACK':
688
ownerID = null;
689
ownerFlatTree = null;
695
- selectedElementIndex =
696
- selectedElementID !== null
697
- ? store.getIndexOfElementID(selectedElementID)
690
+ inspectedElementIndex =
691
+ inspectedElementID !== null
692
+ ? store.getIndexOfElementID(inspectedElementID)
693
: null;
694
break;
695
case 'SELECT_ELEMENT_AT_INDEX':
696
if (ownerFlatTree !== null) {
702
- selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
697
+ inspectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX)
698
+ .payload;
699
}
700
break;
701
case 'SELECT_ELEMENT_BY_ID':
702
if (ownerFlatTree !== null) {
703
const payload = (action: ACTION_SELECT_ELEMENT_BY_ID).payload;
704
if (payload === null) {
709
- selectedElementIndex = null;
705
+ inspectedElementIndex = null;
706
} else {
711
- selectedElementIndex = ownerFlatTree.findIndex(
707
+ inspectedElementIndex = ownerFlatTree.findIndex(
708
element => element.id === payload,
709
);
710
711
// If the selected element is outside of the current owners list,
712
// exit the list and select the element in the main tree.
713
// This supports features like toggling Suspense.
718
- if (selectedElementIndex !== null && selectedElementIndex < 0) {
714
+ if (inspectedElementIndex !== null && inspectedElementIndex < 0) {
715
ownerID = null;
716
ownerFlatTree = null;
721
- selectedElementIndex = store.getIndexOfElementID(payload);
717
+ inspectedElementIndex = store.getIndexOfElementID(payload);
718
}
719
}
720
}
721
break;
722
case 'SELECT_NEXT_ELEMENT_IN_TREE':
723
if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
728
- if (selectedElementIndex === null) {
729
- selectedElementIndex = 0;
730
- } else if (selectedElementIndex + 1 < ownerFlatTree.length) {
731
- selectedElementIndex++;
724
+ if (inspectedElementIndex === null) {
725
+ inspectedElementIndex = 0;
726
+ } else if (inspectedElementIndex + 1 < ownerFlatTree.length) {
727
+ inspectedElementIndex++;
728
}
729
}
730
break;
731
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
732
if (ownerFlatTree !== null && ownerFlatTree.length > 0) {
737
- if (selectedElementIndex !== null && selectedElementIndex > 0) {
738
- selectedElementIndex--;
733
+ if (inspectedElementIndex !== null && inspectedElementIndex > 0) {
734
+ inspectedElementIndex--;
735
}
736
}
737
break;
@@ -747,8 +743,8 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
743
ownerFlatTree = store.getOwnersListForElement(ownerID);
744
745
// Always force reset selection to be the top of the new owner tree.
750
- selectedElementIndex = 0;
751
- prevSelectedElementIndex = null;
746
+ inspectedElementIndex = 0;
747
+ prevInspectedElementIndex = null;
748
}
749
break;
750
default:
@@ -769,12 +765,12 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
765
}
766
767
// Keep selected item ID and index in sync.
772
- if (selectedElementIndex !== prevSelectedElementIndex) {
773
- if (selectedElementIndex === null) {
774
- selectedElementID = null;
768
+ if (inspectedElementIndex !== prevInspectedElementIndex) {
769
+ if (inspectedElementIndex === null) {
770
+ inspectedElementID = null;
771
} else {
772
if (ownerFlatTree !== null) {
777
- selectedElementID = ownerFlatTree[selectedElementIndex].id;
773
+ inspectedElementID = ownerFlatTree[inspectedElementIndex].id;
774
}
775
}
776
}
@@ -783,8 +779,6 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
779
...state,
780
781
numElements,
786
- selectedElementID,
787
- selectedElementIndex,
782
783
searchIndex,
784
searchResults,
@@ -792,49 +786,27 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
786
787
ownerID,
788
ownerFlatTree,
795
- };
796
-}
789
798
-function reduceSuspenseState(
799
- store: Store,
800
- state: State,
801
- action: Action,
802
-): State {
803
- const {type} = action;
804
- switch (type) {
805
- case 'UPDATE_INSPECTED_ELEMENT_ID':
806
- if (state.inspectedElementID !== state.selectedElementID) {
807
- return {
808
- ...state,
809
- inspectedElementID: state.selectedElementID,
810
- };
811
- }
812
- break;
813
- default:
814
- break;
815
- }
816
-
817
- // React can bailout of no-op updates.
818
- return state;
790
+ inspectedElementID,
791
+ inspectedElementIndex,
792
+ };
793
}
794
795
type Props = {
796
children: React$Node,
797
798
// Used for automated testing
825
- defaultInspectedElementID?: ?number,
799
defaultOwnerID?: ?number,
827
- defaultSelectedElementID?: ?number,
828
- defaultSelectedElementIndex?: ?number,
800
+ defaultInspectedElementID?: ?number,
801
+ defaultInspectedElementIndex?: ?number,
802
};
803
804
// TODO Remove TreeContextController wrapper element once global Context.write API exists.
805
function TreeContextController({
806
children,
834
- defaultInspectedElementID,
807
defaultOwnerID,
836
- defaultSelectedElementID,
837
- defaultSelectedElementIndex,
808
+ defaultInspectedElementID,
809
+ defaultInspectedElementIndex,
810
}: Props): React.Node {
811
const store = useContext(StoreContext);
812
@@ -865,23 +837,22 @@ function TreeContextController({
837
case 'SELECT_PREVIOUS_ELEMENT_WITH_ERROR_OR_WARNING_IN_TREE':
838
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
839
case 'SELECT_OWNER':
868
- case 'UPDATE_INSPECTED_ELEMENT_ID':
840
case 'SET_SEARCH_TEXT':
841
state = reduceTreeState(store, state, action);
842
state = reduceSearchState(store, state, action);
843
state = reduceOwnersState(store, state, action);
873
- state = reduceSuspenseState(store, state, action);
844
845
+ // TODO(hoxyq): review
846
// If the selected ID is in a collapsed subtree, reset the selected index to null.
847
// We'll know the correct index after the layout effect will toggle the tree,
848
// and the store tree is mutated to account for that.
849
if (
879
- state.selectedElementID !== null &&
880
- store.isInsideCollapsedSubTree(state.selectedElementID)
850
+ state.inspectedElementID !== null &&
851
+ store.isInsideCollapsedSubTree(state.inspectedElementID)
852
) {
853
return {
854
...state,
884
- selectedElementIndex: null,
855
+ inspectedElementIndex: null,
856
};
857
}
858
@@ -897,16 +868,6 @@ function TreeContextController({
868
// Tree
869
numElements: store.numElements,
870
ownerSubtreeLeafElementID: null,
900
- selectedElementID:
901
- defaultSelectedElementID != null
902
- ? defaultSelectedElementID
903
- : store.lastSelectedHostInstanceElementId,
904
- selectedElementIndex:
905
- defaultSelectedElementIndex != null
906
- ? defaultSelectedElementIndex
907
- : store.lastSelectedHostInstanceElementId
908
- ? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
909
- : null,
871
872
// Search
873
searchIndex: null,
@@ -922,42 +883,38 @@ function TreeContextController({
883
defaultInspectedElementID != null
884
? defaultInspectedElementID
885
: store.lastSelectedHostInstanceElementId,
886
+ inspectedElementIndex:
887
+ defaultInspectedElementIndex != null
888
+ ? defaultInspectedElementIndex
889
+ : store.lastSelectedHostInstanceElementId
890
+ ? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
891
+ : null,
892
});
893
927
- const dispatchWrapper = useCallback(
928
- (action: Action) => {
929
- dispatch(action);
930
- startTransition(() => {
931
- dispatch({type: 'UPDATE_INSPECTED_ELEMENT_ID'});
932
- });
933
- },
934
- [dispatch],
935
- );
936
-
894
// Listen for host element selections.
895
useEffect(() => {
896
const handler = (id: Element['id']) =>
940
- dispatchWrapper({type: 'SELECT_ELEMENT_BY_ID', payload: id});
897
+ dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
898
899
store.addListener('hostInstanceSelected', handler);
900
return () => store.removeListener('hostInstanceSelected', handler);
944
- }, [store, dispatchWrapper]);
901
+ }, [store, dispatch]);
902
903
// If a newly-selected search result or inspection selection is inside of a collapsed subtree, auto expand it.
904
// This needs to be a layout effect to avoid temporarily flashing an incorrect selection.
948
- const prevSelectedElementID = useRef<number | null>(null);
905
+ const prevInspectedElementID = useRef<number | null>(null);
906
useLayoutEffect(() => {
950
- if (state.selectedElementID !== prevSelectedElementID.current) {
951
- prevSelectedElementID.current = state.selectedElementID;
907
+ if (state.inspectedElementID !== prevInspectedElementID.current) {
908
+ prevInspectedElementID.current = state.inspectedElementID;
909
953
- if (state.selectedElementID !== null) {
954
- const element = store.getElementByID(state.selectedElementID);
910
+ if (state.inspectedElementID !== null) {
911
+ const element = store.getElementByID(state.inspectedElementID);
912
if (element !== null && element.parentID > 0) {
913
store.toggleIsCollapsed(element.parentID, false);
914
}
915
}
916
}
960
- }, [state.selectedElementID, store]);
917
+ }, [state.inspectedElementID, store]);
918
919
// Mutations to the underlying tree may impact this context (e.g. search results, selection state).
920
useEffect(() => {
@@ -965,7 +922,7 @@ function TreeContextController({
922
Array<number>,
923
Map<number, number>,
924
]) => {
968
- dispatchWrapper({
925
+ dispatch({
926
type: 'HANDLE_STORE_MUTATION',
927
payload: [addedElementIDs, removedElementIDs],
928
});
@@ -976,20 +933,19 @@ function TreeContextController({
933
// At the moment, we can treat this as a mutation.
934
// We don't know which Elements were newly added/removed, but that should be okay in this case.
935
// It would only impact the search state, which is unlikely to exist yet at this point.
979
- dispatchWrapper({
936
+ dispatch({
937
type: 'HANDLE_STORE_MUTATION',
938
payload: [[], new Map()],
939
});
940
}
941
942
store.addListener('mutated', handleStoreMutated);
986
-
943
return () => store.removeListener('mutated', handleStoreMutated);
988
- }, [dispatchWrapper, initialRevision, store]);
944
+ }, [dispatch, initialRevision, store]);
945
946
return (
947
<TreeStateContext.Provider value={state}>
992
- <TreeDispatcherContext.Provider value={dispatchWrapper}>
948
+ <TreeDispatcherContext.Provider value={dispatch}>
949
{children}
950
</TreeDispatcherContext.Provider>
951
</TreeStateContext.Provider>
@@ -1028,11 +984,11 @@ function recursivelySearchTree(
984
function getNearestResultIndex(
985
store: Store,
986
searchResults: Array<number>,
1031
- selectedElementIndex: number,
987
+ inspectedElementIndex: number,
988
): number {
989
const index = searchResults.findIndex(id => {
990
const innerIndex = store.getIndexOfElementID(id);
1035
- return innerIndex !== null && innerIndex >= selectedElementIndex;
991
+ return innerIndex !== null && innerIndex >= inspectedElementIndex;
992
});
993
994
return index === -1 ? 0 : index;
packages/react-devtools-shared/src/devtools/views/Profiler/ProfilerContext.js
+3
-3
@@ -88,7 +88,7 @@ type Props = {
88
89
function ProfilerContextController({children}: Props): React.Node {
90
const store = useContext(StoreContext);
91
- const {selectedElementID} = useContext(TreeStateContext);
91
+ const {inspectedElementID} = useContext(TreeStateContext);
92
const dispatch = useContext(TreeDispatcherContext);
93
94
const {profilerStore} = store;
@@ -176,9 +176,9 @@ function ProfilerContextController({children}: Props): React.Node {
176
177
if (rootID === null || !dataForRoots.has(rootID)) {
178
let selectedElementRootID = null;
179
- if (selectedElementID !== null) {
179
+ if (inspectedElementID !== null) {
180
selectedElementRootID =
181
- store.getRootIDForElement(selectedElementID);
181
+ store.getRootIDForElement(inspectedElementID);
182
}
183
if (
184
selectedElementRootID !== null &&