Skip to content

Commit 43dc232

Browse files
committed
feat: static Components panel layout
1 parent 602917c commit 43dc232

8 files changed

Lines changed: 209 additions & 305 deletions

File tree

packages/react-devtools-shared/src/devtools/store.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ export default class Store extends EventEmitter<{
195195
// Only used in browser extension for synchronization with built-in Elements panel.
196196
_lastSelectedHostInstanceElementId: Element['id'] | null = null;
197197

198+
// Maximum recorded node depth during the lifetime of this Store.
199+
// Can only increase: not guaranteed to return maximal value for currently recorded elements.
200+
_maximumRecordedDepth = 0;
201+
198202
constructor(bridge: FrontendBridge, config?: Config) {
199203
super();
200204

@@ -698,6 +702,46 @@ export default class Store extends EventEmitter<{
698702
return index;
699703
}
700704

705+
isDescendantOf(parentId: number, descendantId: number): boolean {
706+
if (descendantId === 0) {
707+
return false;
708+
}
709+
710+
const descendant = this.getElementByID(descendantId);
711+
if (descendant === null) {
712+
return false;
713+
}
714+
715+
if (descendant.parentID === parentId) {
716+
return true;
717+
}
718+
719+
const parent = this.getElementByID(parentId);
720+
if (!parent || parent.depth >= descendant.depth) {
721+
return false;
722+
}
723+
724+
return this.isDescendantOf(parentId, descendant.parentID);
725+
}
726+
727+
getIndexOfLowestDescendantElement(element: Element): number | null {
728+
let current: null | Element = element;
729+
while (current !== null) {
730+
if (current.isCollapsed || current.children.length === 0) {
731+
if (current === element) {
732+
return null;
733+
}
734+
735+
return this.getIndexOfElementID(current.id);
736+
} else {
737+
const lastChildID = current.children[current.children.length - 1];
738+
current = this.getElementByID(lastChildID);
739+
}
740+
}
741+
742+
return null;
743+
}
744+
701745
getOwnersListForElement(ownerID: number): Array<Element> {
702746
const list: Array<Element> = [];
703747
const element = this._idToElement.get(ownerID);
@@ -1089,9 +1133,15 @@ export default class Store extends EventEmitter<{
10891133
compiledWithForget,
10901134
} = parseElementDisplayNameFromBackend(displayName, type);
10911135

1136+
const elementDepth = parentElement.depth + 1;
1137+
this._maximumRecordedDepth = Math.max(
1138+
this._maximumRecordedDepth,
1139+
elementDepth,
1140+
);
1141+
10921142
const element: Element = {
10931143
children: [],
1094-
depth: parentElement.depth + 1,
1144+
depth: elementDepth,
10951145
displayName: displayNameWithoutHOCs,
10961146
hocDisplayNames,
10971147
id,
@@ -1536,6 +1586,14 @@ export default class Store extends EventEmitter<{
15361586
}
15371587
};
15381588

1589+
/**
1590+
* Maximum recorded node depth during the lifetime of this Store.
1591+
* Can only increase: not guaranteed to return maximal value for currently recorded elements.
1592+
*/
1593+
getMaximumRecordedDepth(): number {
1594+
return this._maximumRecordedDepth;
1595+
}
1596+
15391597
updateHookSettings: (settings: $ReadOnly<DevToolsHookSettings>) => void =
15401598
settings => {
15411599
this._hookSettings = settings;

packages/react-devtools-shared/src/devtools/views/Components/Components.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ function Components(_: {}) {
176176

177177
const LOCAL_STORAGE_KEY = 'React::DevTools::createResizeReducer';
178178
const VERTICAL_MODE_MAX_WIDTH = 600;
179-
const MINIMUM_SIZE = 50;
179+
const MINIMUM_SIZE = 100;
180180

181181
function initResizeState(): ResizeState {
182182
let horizontalPercentage = 0.65;

packages/react-devtools-shared/src/devtools/views/Components/Element.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
.Element,
2+
.HoveredElement,
23
.InactiveSelectedElement,
3-
.SelectedElement,
4-
.HoveredElement {
4+
.HighlightedElement,
5+
.InactiveHighlightedElement,
6+
.SelectedElement {
57
color: var(--color-component-name);
68
}
79
.HoveredElement {
@@ -10,8 +12,15 @@
1012
.InactiveSelectedElement {
1113
background-color: var(--color-background-inactive);
1214
}
15+
.HighlightedElement {
16+
background-color: var(--color-selected-tree-highlight-active);
17+
}
18+
.InactiveHighlightedElement {
19+
background-color: var(--color-selected-tree-highlight-inactive);
20+
}
1321

1422
.Wrapper {
23+
position: relative;
1524
padding: 0 0.25rem;
1625
white-space: pre;
1726
height: var(--line-height-data);

packages/react-devtools-shared/src/devtools/views/Components/Element.js

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ export default function Element({data, index, style}: Props): React.Node {
4545

4646
const [isHovered, setIsHovered] = useState(false);
4747

48-
const {isNavigatingWithKeyboard, onElementMouseEnter, treeFocused} = data;
49-
const id = element === null ? null : element.id;
50-
const isSelected = inspectedElementID === id;
51-
5248
const errorsAndWarningsSubscription = useMemo(
5349
() => ({
5450
getCurrentValue: () =>
@@ -68,6 +64,15 @@ export default function Element({data, index, style}: Props): React.Node {
6864
}>(errorsAndWarningsSubscription);
6965

7066
const changeOwnerAction = useChangeOwnerAction();
67+
68+
// Handle elements that are removed from the tree while an async render is in progress.
69+
if (element == null) {
70+
console.warn(`<Element> Could not find element at index ${index}`);
71+
72+
// This return needs to happen after hooks, since hooks can't be conditional.
73+
return null;
74+
}
75+
7176
const handleDoubleClick = () => {
7277
if (id !== null) {
7378
changeOwnerAction(id);
@@ -107,22 +112,28 @@ export default function Element({data, index, style}: Props): React.Node {
107112
event.preventDefault();
108113
};
109114

110-
// Handle elements that are removed from the tree while an async render is in progress.
111-
if (element == null) {
112-
console.warn(`<Element> Could not find element at index ${index}`);
113-
114-
// This return needs to happen after hooks, since hooks can't be conditional.
115-
return null;
116-
}
117-
118115
const {
116+
id,
119117
depth,
120118
displayName,
121119
hocDisplayNames,
122120
isStrictModeNonCompliant,
123121
key,
124122
compiledWithForget,
125123
} = element;
124+
const {
125+
isNavigatingWithKeyboard,
126+
onElementMouseEnter,
127+
treeFocused,
128+
calculateElementOffset,
129+
} = data;
130+
131+
const isSelected = inspectedElementID === id;
132+
const isDescendantOfSelected =
133+
inspectedElementID !== null &&
134+
!isSelected &&
135+
store.isDescendantOf(inspectedElementID, id);
136+
const elementOffset = calculateElementOffset(depth);
126137

127138
// Only show strict mode non-compliance badges for top level elements.
128139
// Showing an inline badge for every element in the tree would be noisy.
@@ -135,6 +146,10 @@ export default function Element({data, index, style}: Props): React.Node {
135146
: styles.InactiveSelectedElement;
136147
} else if (isHovered && !isNavigatingWithKeyboard) {
137148
className = styles.HoveredElement;
149+
} else if (isDescendantOfSelected) {
150+
className = treeFocused
151+
? styles.HighlightedElement
152+
: styles.InactiveHighlightedElement;
138153
}
139154

140155
return (
@@ -144,17 +159,13 @@ export default function Element({data, index, style}: Props): React.Node {
144159
onMouseLeave={handleMouseLeave}
145160
onMouseDown={handleClick}
146161
onDoubleClick={handleDoubleClick}
147-
style={style}
148-
data-testname="ComponentTreeListItem"
149-
data-depth={depth}>
162+
style={{
163+
...style,
164+
paddingLeft: elementOffset,
165+
}}
166+
data-testname="ComponentTreeListItem">
150167
{/* This wrapper is used by Tree for measurement purposes. */}
151-
<div
152-
className={styles.Wrapper}
153-
style={{
154-
// Left offset presents the appearance of a nested tree structure.
155-
// We must use padding rather than margin/left because of the selected background color.
156-
transform: `translateX(calc(${depth} * var(--indentation-size)))`,
157-
}}>
168+
<div className={styles.Wrapper}>
158169
{ownerID === null && (
159170
<ExpandCollapseToggle element={element} store={store} />
160171
)}

packages/react-devtools-shared/src/devtools/views/Components/SelectedTreeHighlight.css

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/react-devtools-shared/src/devtools/views/Components/SelectedTreeHighlight.js

Lines changed: 0 additions & 110 deletions
This file was deleted.

packages/react-devtools-shared/src/devtools/views/Components/Tree.css

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
display: flex;
66
flex-direction: column;
77
border-top: 1px solid var(--color-border);
8-
9-
/* Default size will be adjusted by Tree after scrolling */
10-
--indentation-size: 12px;
118
}
129

13-
.List {
14-
overflow-x: hidden !important;
10+
.InnerElementType {
11+
position: relative;
1512
}
1613

17-
.InnerElementType {
18-
overflow-x: hidden;
14+
.VerticalDelimiter {
15+
position: absolute;
16+
width: 0.025rem;
17+
background: #b0b0b0;
1918
}
2019

2120
.SearchInput {
@@ -97,4 +96,4 @@
9796

9897
.Link {
9998
color: var(--color-button-active);
100-
}
99+
}

0 commit comments

Comments
 (0)