mirror of
https://github.com/facebook/react.git
synced 2026-02-27 03:07:57 +00:00
Add ⎇ + arrow key navigation to DevTools (#19741)
⎇ + left/right navigates between owners (similar to owners tree) and ⎇ + up/down navigations between siblings.
This commit is contained in:
@@ -130,7 +130,11 @@ export default function Tree(props: Props) {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'});
|
||||
if (event.altKey) {
|
||||
dispatch({type: 'SELECT_NEXT_SIBLING_IN_TREE'});
|
||||
} else {
|
||||
dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'});
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
event.preventDefault();
|
||||
@@ -139,10 +143,16 @@ export default function Tree(props: Props) {
|
||||
? store.getElementByID(selectedElementID)
|
||||
: null;
|
||||
if (element !== null) {
|
||||
if (element.children.length > 0 && !element.isCollapsed) {
|
||||
store.toggleIsCollapsed(element.id, true);
|
||||
if (event.altKey) {
|
||||
if (element.ownerID !== null) {
|
||||
dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'});
|
||||
}
|
||||
} else {
|
||||
dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'});
|
||||
if (element.children.length > 0 && !element.isCollapsed) {
|
||||
store.toggleIsCollapsed(element.id, true);
|
||||
} else {
|
||||
dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -153,16 +163,24 @@ export default function Tree(props: Props) {
|
||||
? store.getElementByID(selectedElementID)
|
||||
: null;
|
||||
if (element !== null) {
|
||||
if (element.children.length > 0 && element.isCollapsed) {
|
||||
store.toggleIsCollapsed(element.id, false);
|
||||
if (event.altKey) {
|
||||
dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'});
|
||||
} else {
|
||||
dispatch({type: 'SELECT_CHILD_ELEMENT_IN_TREE'});
|
||||
if (element.children.length > 0 && element.isCollapsed) {
|
||||
store.toggleIsCollapsed(element.id, false);
|
||||
} else {
|
||||
dispatch({type: 'SELECT_CHILD_ELEMENT_IN_TREE'});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'});
|
||||
if (event.altKey) {
|
||||
dispatch({type: 'SELECT_PREVIOUS_SIBLING_IN_TREE'});
|
||||
} else {
|
||||
dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
||||
@@ -49,6 +49,7 @@ import type {Element} from './types';
|
||||
export type StateContext = {|
|
||||
// Tree
|
||||
numElements: number,
|
||||
ownerSubtreeLeafElementID: number | null,
|
||||
selectedElementID: number | null,
|
||||
selectedElementIndex: number | null,
|
||||
|
||||
@@ -92,15 +93,27 @@ type ACTION_SELECT_ELEMENT_BY_ID = {|
|
||||
type ACTION_SELECT_NEXT_ELEMENT_IN_TREE = {|
|
||||
type: 'SELECT_NEXT_ELEMENT_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_NEXT_SIBLING_IN_TREE = {|
|
||||
type: 'SELECT_NEXT_SIBLING_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_OWNER = {|
|
||||
type: 'SELECT_OWNER',
|
||||
payload: number,
|
||||
|};
|
||||
type ACTION_SELECT_PARENT_ELEMENT_IN_TREE = {|
|
||||
type: 'SELECT_PARENT_ELEMENT_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_PREVIOUS_ELEMENT_IN_TREE = {|
|
||||
type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_OWNER = {|
|
||||
type: 'SELECT_OWNER',
|
||||
payload: number,
|
||||
type ACTION_SELECT_PREVIOUS_SIBLING_IN_TREE = {|
|
||||
type: 'SELECT_PREVIOUS_SIBLING_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE = {|
|
||||
type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE',
|
||||
|};
|
||||
type ACTION_SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE = {|
|
||||
type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE',
|
||||
|};
|
||||
type ACTION_SET_SEARCH_TEXT = {|
|
||||
type: 'SET_SEARCH_TEXT',
|
||||
@@ -119,9 +132,13 @@ type Action =
|
||||
| ACTION_SELECT_ELEMENT_AT_INDEX
|
||||
| ACTION_SELECT_ELEMENT_BY_ID
|
||||
| ACTION_SELECT_NEXT_ELEMENT_IN_TREE
|
||||
| ACTION_SELECT_NEXT_SIBLING_IN_TREE
|
||||
| ACTION_SELECT_OWNER
|
||||
| ACTION_SELECT_PARENT_ELEMENT_IN_TREE
|
||||
| ACTION_SELECT_PREVIOUS_ELEMENT_IN_TREE
|
||||
| ACTION_SELECT_OWNER
|
||||
| ACTION_SELECT_PREVIOUS_SIBLING_IN_TREE
|
||||
| ACTION_SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE
|
||||
| ACTION_SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE
|
||||
| ACTION_SET_SEARCH_TEXT
|
||||
| ACTION_UPDATE_INSPECTED_ELEMENT_ID;
|
||||
|
||||
@@ -140,6 +157,7 @@ TreeDispatcherContext.displayName = 'TreeDispatcherContext';
|
||||
type State = {|
|
||||
// Tree
|
||||
numElements: number,
|
||||
ownerSubtreeLeafElementID: number | null,
|
||||
selectedElementID: number | null,
|
||||
selectedElementIndex: number | null,
|
||||
|
||||
@@ -157,7 +175,12 @@ type State = {|
|
||||
|};
|
||||
|
||||
function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
let {numElements, selectedElementIndex, selectedElementID} = state;
|
||||
let {
|
||||
numElements,
|
||||
ownerSubtreeLeafElementID,
|
||||
selectedElementIndex,
|
||||
selectedElementID,
|
||||
} = state;
|
||||
const ownerID = state.ownerID;
|
||||
|
||||
let lookupIDForIndex = true;
|
||||
@@ -187,6 +210,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
break;
|
||||
case 'SELECT_CHILD_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
@@ -205,9 +230,13 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
break;
|
||||
case 'SELECT_ELEMENT_AT_INDEX':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
selectedElementIndex = (action: ACTION_SELECT_ELEMENT_AT_INDEX).payload;
|
||||
break;
|
||||
case 'SELECT_ELEMENT_BY_ID':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
// Skip lookup in this case; it would be redundant.
|
||||
// It might also cause problems if the specified element was inside of a (not yet expanded) subtree.
|
||||
lookupIDForIndex = false;
|
||||
@@ -219,6 +248,8 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
: store.getIndexOfElementID(selectedElementID);
|
||||
break;
|
||||
case 'SELECT_NEXT_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (
|
||||
selectedElementIndex === null ||
|
||||
selectedElementIndex + 1 >= numElements
|
||||
@@ -228,12 +259,80 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
selectedElementIndex++;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PARENT_ELEMENT_IN_TREE':
|
||||
case 'SELECT_NEXT_SIBLING_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== null) {
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parent = store.getElementByID(selectedElement.parentID);
|
||||
if (parent !== null) {
|
||||
const {children} = parent;
|
||||
const selectedChildIndex = children.indexOf(selectedElement.id);
|
||||
const nextChildID =
|
||||
selectedChildIndex < children.length - 1
|
||||
? children[selectedChildIndex + 1]
|
||||
: children[0];
|
||||
selectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE':
|
||||
if (selectedElementIndex !== null) {
|
||||
if (
|
||||
ownerSubtreeLeafElementID !== null &&
|
||||
ownerSubtreeLeafElementID !== selectedElementID
|
||||
) {
|
||||
const leafElement = store.getElementByID(ownerSubtreeLeafElementID);
|
||||
if (leafElement !== null) {
|
||||
let currentElement = leafElement;
|
||||
while (currentElement !== null) {
|
||||
if (currentElement.ownerID === selectedElementID) {
|
||||
selectedElementIndex = store.getIndexOfElementID(
|
||||
currentElement.id,
|
||||
);
|
||||
break;
|
||||
} else if (currentElement.ownerID !== 0) {
|
||||
currentElement = store.getElementByID(currentElement.ownerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE':
|
||||
if (selectedElementIndex !== null) {
|
||||
if (ownerSubtreeLeafElementID === null) {
|
||||
// If this is the first time we're stepping through the owners tree,
|
||||
// pin the current component as the owners list leaf.
|
||||
// This will enable us to step back down to this component.
|
||||
ownerSubtreeLeafElementID = selectedElementID;
|
||||
}
|
||||
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.ownerID !== 0) {
|
||||
const ownerIndex = store.getIndexOfElementID(
|
||||
selectedElement.ownerID,
|
||||
);
|
||||
if (ownerIndex !== null) {
|
||||
selectedElementIndex = ownerIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PARENT_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parentIndex = store.getIndexOfElementID(
|
||||
selectedElement.parentID,
|
||||
);
|
||||
@@ -244,12 +343,35 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex === null || selectedElementIndex === 0) {
|
||||
selectedElementIndex = numElements - 1;
|
||||
} else {
|
||||
selectedElementIndex--;
|
||||
}
|
||||
break;
|
||||
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
|
||||
ownerSubtreeLeafElementID = null;
|
||||
|
||||
if (selectedElementIndex !== null) {
|
||||
const selectedElement = store.getElementAtIndex(
|
||||
((selectedElementIndex: any): number),
|
||||
);
|
||||
if (selectedElement !== null && selectedElement.parentID !== 0) {
|
||||
const parent = store.getElementByID(selectedElement.parentID);
|
||||
if (parent !== null) {
|
||||
const {children} = parent;
|
||||
const selectedChildIndex = children.indexOf(selectedElement.id);
|
||||
const nextChildID =
|
||||
selectedChildIndex > 0
|
||||
? children[selectedChildIndex - 1]
|
||||
: children[children.length - 1];
|
||||
selectedElementIndex = store.getIndexOfElementID(nextChildID);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// React can bailout of no-op updates.
|
||||
return state;
|
||||
@@ -271,6 +393,7 @@ function reduceTreeState(store: Store, state: State, action: Action): State {
|
||||
...state,
|
||||
|
||||
numElements,
|
||||
ownerSubtreeLeafElementID,
|
||||
selectedElementIndex,
|
||||
selectedElementID,
|
||||
};
|
||||
@@ -653,8 +776,12 @@ function TreeContextController({
|
||||
case 'SELECT_ELEMENT_BY_ID':
|
||||
case 'SELECT_CHILD_ELEMENT_IN_TREE':
|
||||
case 'SELECT_NEXT_ELEMENT_IN_TREE':
|
||||
case 'SELECT_NEXT_SIBLING_IN_TREE':
|
||||
case 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE':
|
||||
case 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE':
|
||||
case 'SELECT_PARENT_ELEMENT_IN_TREE':
|
||||
case 'SELECT_PREVIOUS_ELEMENT_IN_TREE':
|
||||
case 'SELECT_PREVIOUS_SIBLING_IN_TREE':
|
||||
case 'SELECT_OWNER':
|
||||
case 'UPDATE_INSPECTED_ELEMENT_ID':
|
||||
case 'SET_SEARCH_TEXT':
|
||||
@@ -687,6 +814,7 @@ function TreeContextController({
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
// Tree
|
||||
numElements: store.numElements,
|
||||
ownerSubtreeLeafElementID: null,
|
||||
selectedElementID:
|
||||
defaultSelectedElementID == null ? null : defaultSelectedElementID,
|
||||
selectedElementIndex:
|
||||
|
||||
Reference in New Issue
Block a user