[DevTools] Move "Back to full tree view" into Activity list item (#35164)
Sebastian "Sebbie" Silbermann committed
Nov 26, 2025 at 10:42 UTC
74fa1667a7355fd55591d82b0e1e992909b1dca0
1 file changed
+47
-38
packages/react-devtools-shared/src/devtools/views/SuspenseTab/ActivityList.js
+47
-38
@@ -30,8 +30,6 @@ import {
30
} from '../Components/TreeContext';
31
import {useHighlightHostInstance} from '../hooks';
32
import {StoreContext} from '../context';
33
-import ButtonIcon from '../ButtonIcon';
34
-import Button from '../Button';
33
34
export function useChangeActivitySliceAction(): (
35
id: Element['id'] | null,
@@ -118,6 +116,8 @@ export default function ActivityList({
116
useTransition();
117
const changeActivitySliceAction = useChangeActivitySliceAction();
118
119
+ const includeAllOption = activityID !== null;
120
+
121
function handleKeyDown(event: SyntheticKeyboardEvent) {
122
switch (event.key) {
123
case 'Escape':
@@ -128,15 +128,16 @@ export default function ActivityList({
128
break;
129
case 'Enter':
130
case ' ':
131
- if (inspectedElementID !== null) {
132
- startActivitySliceSelection(() => {
133
- changeActivitySliceAction(inspectedElementID);
134
- });
135
- }
131
+ startActivitySliceSelection(() => {
132
+ changeActivitySliceAction(inspectedElementID);
133
+ });
134
event.preventDefault();
135
break;
136
case 'Home':
139
- treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: activities[0].id});
137
+ treeDispatch({
138
+ type: 'SELECT_ELEMENT_BY_ID',
139
+ payload: includeAllOption ? null : activities[0].id,
140
+ });
141
event.preventDefault();
142
break;
143
case 'End':
@@ -150,15 +151,21 @@ export default function ActivityList({
151
const currentIndex = activities.findIndex(
152
activity => activity.id === selectedActivityID,
153
);
153
- if (currentIndex !== undefined) {
154
- const nextIndex =
155
- (currentIndex + activities.length - 1) % activities.length;
156
-
157
- treeDispatch({
158
- type: 'SELECT_ELEMENT_BY_ID',
159
- payload: activities[nextIndex].id,
160
- });
154
+ let nextIndex: number;
155
+ if (currentIndex === -1) {
156
+ // Currently selecting "All", wrap around to last Activity.
157
+ nextIndex = activities.length - 1;
158
+ } else {
159
+ nextIndex = currentIndex - 1;
160
+ if (!includeAllOption) {
161
+ nextIndex = (nextIndex + activities.length) % activities.length;
162
+ }
163
}
164
+
165
+ treeDispatch({
166
+ type: 'SELECT_ELEMENT_BY_ID',
167
+ payload: nextIndex === -1 ? null : activities[nextIndex].id,
168
+ });
169
event.preventDefault();
170
break;
171
}
@@ -166,14 +173,17 @@ export default function ActivityList({
173
const currentIndex = activities.findIndex(
174
activity => activity.id === selectedActivityID,
175
);
169
- if (currentIndex !== undefined) {
170
- const nextIndex = (currentIndex + 1) % activities.length;
171
-
172
- treeDispatch({
173
- type: 'SELECT_ELEMENT_BY_ID',
174
- payload: activities[nextIndex].id,
175
- });
176
+ let nextIndex: number;
177
+ if (includeAllOption && currentIndex === activities.length - 1) {
178
+ // Currently selecting last Activity, wrap around to "All".
179
+ nextIndex = -1;
180
+ } else {
181
+ nextIndex = (currentIndex + 1) % activities.length;
182
}
183
+ treeDispatch({
184
+ type: 'SELECT_ELEMENT_BY_ID',
185
+ payload: nextIndex === -1 ? null : activities[nextIndex].id,
186
+ });
187
event.preventDefault();
188
break;
189
}
@@ -182,7 +192,7 @@ export default function ActivityList({
192
}
193
}
194
185
- function handleClick(id: Element['id'], event: SyntheticMouseEvent) {
195
+ function handleClick(id: Element['id'] | null, event: SyntheticMouseEvent) {
196
event.preventDefault();
197
treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
198
}
@@ -195,25 +205,24 @@ export default function ActivityList({
205
206
return (
207
<div className={styles.ActivityListContaier}>
198
- <div className={styles.ActivityListHeader}>
199
- {activityID !== null && (
200
- // TODO: Obsolete once filtered Activities are included in this list.
201
- <Button
202
- onClick={startActivitySliceSelection.bind(
203
- null,
204
- changeActivitySliceAction.bind(null, null),
205
- )}
206
- title="Back to full tree view">
207
- <ButtonIcon type="previous" />
208
- </Button>
209
- )}
210
- </div>
208
+ <div className={styles.ActivityListHeader} />
209
<ol
210
role="listbox"
211
className={styles.ActivityListList}
212
data-pending-activity-slice-selection={isPendingActivitySliceSelection}
213
tabIndex={0}
214
onKeyDown={handleKeyDown}>
215
+ {includeAllOption && (
216
+ // TODO: Obsolete once filtered Activities are included in this list.
217
+ <li
218
+ role="option"
219
+ aria-selected={null === selectedActivityID ? 'true' : 'false'}
220
+ className={styles.ActivityListItem}
221
+ onClick={handleClick.bind(null, null)}
222
+ onDoubleClick={handleDoubleClick}>
223
+ All
224
+ </li>
225
+ )}
226
{activities.map(({id, depth}) => {
227
const activity = store.getElementByID(id);
228
if (activity === null) {
@@ -244,7 +253,7 @@ export default function ActivityList({
253
false,
254
)}
255
onPointerLeave={clearHighlightHostInstance}>
247
- {'\u00A0'.repeat(depth) + name}
256
+ {'\u00A0'.repeat(depth + (includeAllOption ? 1 : 0)) + name}
257
</li>
258
);
259
})}