Lots of tweaks Remove selected guideline in favor of background color for selected subtree. Add badges in grid format to selected elements prop panel. Show badges beside owners list.
Lots of tweaks Remove selected guideline in favor of background color for selected subtree. Add badges in grid format to selected elements prop panel. Show badges beside owners list.
Brian Vaughn committed
Jun 1, 2019 at 08:24 UTC
aa87b125e778709b880c43471466339b8b2feeeb
21 files changed
+364
-82
shells/dev/app/DeeplyNestedComponents/index.js
+2
-1
@@ -6,7 +6,8 @@ function wrapWithHoc(Component, index) {
6
function HOC() {
7
return <Component />;
8
}
9
- HOC.displayName = `HOC-${index}`;
9
+ HOC.displayName = `withHoc${index}(${Component.displayName ||
10
+ Component.name})`;
11
return HOC;
12
}
13
src/backend/agent.js
+1
-1
@@ -11,12 +11,12 @@ import {
11
import { hideOverlay, showOverlay } from './views/Highlighter';
12
13
import type {
14
+ OwnersList,
15
PathFrame,
16
PathMatch,
17
RendererID,
18
RendererInterface,
19
} from './types';
19
-import type { OwnersList } from 'src/devtools/views/Components/types';
20
import type { Bridge, ComponentFilter } from '../types';
21
22
const debug = (methodName, ...args) => {
src/backend/renderer.js
+6
-4
@@ -39,6 +39,8 @@ import type {
39
CommitDataBackend,
40
DevToolsHook,
41
Fiber,
42
+ InspectedElement,
43
+ Owner,
44
PathFrame,
45
PathMatch,
46
ProfilingDataBackend,
@@ -46,10 +48,6 @@ import type {
48
ReactRenderer,
49
RendererInterface,
50
} from './types';
49
-import type {
50
- InspectedElement,
51
- Owner,
52
-} from 'src/devtools/views/Components/types';
51
import type { Interaction } from 'src/devtools/views/Profiler/types';
52
import type { ComponentFilter, ElementType } from 'src/types';
53
@@ -1752,6 +1750,7 @@ export function attach(
1750
{
1751
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
1752
id,
1753
+ type: getElementTypeForFiber(fiber),
1754
},
1755
];
1756
@@ -1761,6 +1760,7 @@ export function attach(
1760
owners.unshift({
1761
displayName: getDisplayNameForFiber(owner) || 'Anonymous',
1762
id: getFiberID(getPrimaryFiber(owner)),
1763
+ type: getElementTypeForFiber(owner),
1764
});
1765
owner = owner._debugOwner || null;
1766
}
@@ -1858,6 +1858,7 @@ export function attach(
1858
owners.push({
1859
displayName: getDisplayNameForFiber(owner) || 'Anonymous',
1860
id: getFiberID(getPrimaryFiber(owner)),
1861
+ type: getElementTypeForFiber(owner),
1862
});
1863
owner = owner._debugOwner || null;
1864
}
@@ -1904,6 +1905,7 @@ export function attach(
1905
canViewSource,
1906
1907
displayName: getDisplayNameForFiber(fiber),
1908
+ type: getElementTypeForFiber(fiber),
1909
1910
// Inspectable properties.
1911
// TODO Review sanitization approach for the below inspectable values.
src/backend/types.js
+44
-4
@@ -1,10 +1,6 @@
1
// @flow
2
3
import type { ComponentFilter, ElementType } from 'src/types';
4
-import type {
5
- InspectedElement,
6
- Owner,
7
-} from 'src/devtools/views/Components/types';
4
import type { Interaction } from 'src/devtools/views/Profiler/types';
5
6
type BundleType =
@@ -156,6 +152,50 @@ export type PathMatch = {|
152
isFullMatch: boolean,
153
|};
154
155
+export type Owner = {|
156
+ displayName: string | null,
157
+ id: number,
158
+ type: ElementType,
159
+|};
160
+
161
+export type OwnersList = {|
162
+ id: number,
163
+ owners: Array<Owner> | null,
164
+|};
165
+
166
+export type InspectedElement = {|
167
+ id: number,
168
+
169
+ displayName: string | null,
170
+
171
+ // Does the current renderer support editable hooks?
172
+ canEditHooks: boolean,
173
+
174
+ // Does the current renderer support editable function props?
175
+ canEditFunctionProps: boolean,
176
+
177
+ // Is this Suspense, and can its value be overriden now?
178
+ canToggleSuspense: boolean,
179
+
180
+ // Can view component source location.
181
+ canViewSource: boolean,
182
+
183
+ // Inspectable properties.
184
+ context: Object | null,
185
+ events: Object | null,
186
+ hooks: Object | null,
187
+ props: Object | null,
188
+ state: Object | null,
189
+
190
+ // List of owners
191
+ owners: Array<Owner> | null,
192
+
193
+ // Location of component in source coude.
194
+ source: Object | null,
195
+
196
+ type: ElementType,
197
+|};
198
+
199
export type RendererInterface = {
200
cleanup: () => void,
201
findNativeNodesForFiberID: (id: number) => ?Array<NativeType>,
src/devtools/store.js
+9
-1
@@ -12,6 +12,7 @@ import { ElementTypeRoot } from '../types';
12
import {
13
getSavedComponentFilters,
14
saveComponentFilters,
15
+ separateDisplayNameAndHOCs,
16
utfDecodeString,
17
} from '../utils';
18
import { __DEBUG__ } from '../constants';
@@ -674,6 +675,7 @@ export default class Store extends EventEmitter {
675
children: [],
676
depth: -1,
677
displayName: null,
678
+ hocDisplayNames: null,
679
id,
680
isCollapsed: false, // Never collapse roots; it would hide the entire tree.
681
key: null,
@@ -717,10 +719,16 @@ export default class Store extends EventEmitter {
719
): any): Element);
720
parentElement.children.push(id);
721
722
+ const [
723
+ displayNameWithoutHOCs,
724
+ hocDisplayNames,
725
+ ] = separateDisplayNameAndHOCs(displayName, type);
726
+
727
const element: Element = {
728
children: [],
729
depth: parentElement.depth + 1,
723
- displayName,
730
+ displayName: displayNameWithoutHOCs,
731
+ hocDisplayNames,
732
id,
733
isCollapsed: this._collapseNodesByDefault,
734
key,
src/devtools/views/Components/Badge.css
new
+11
@@ -0,0 +1,11 @@
1
+.Badge {
2
+ display: inline-block;
3
+ background-color: var(--color-component-badge-background);
4
+ color: var(--color-text);
5
+ padding: 0.125rem 0.25rem;
6
+ line-height: normal;
7
+ border-radius: 0.125rem;
8
+ margin-right: 0.25rem;
9
+ font-family: var(--font-family-monospace);
10
+ font-size: var(--font-size-monospace-small);
11
+}
src/devtools/views/Components/Badge.js
new
+16
@@ -0,0 +1,16 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import styles from './Badge.css';
5
+
6
+type Props = {|
7
+ children: string | null,
8
+|};
9
+
10
+export default function Badge({ children }: Props) {
11
+ if (children === null) {
12
+ return null;
13
+ }
14
+
15
+ return <div className={styles.Badge}>{children}</div>;
16
+}
src/devtools/views/Components/Element.css
-11
@@ -64,14 +64,3 @@
64
flex: 0 0 1rem;
65
color: var(--color-expand-collapse-toggle);
66
}
67
-
68
-.Badge {
69
- color: var(--color-text);
70
- background-color: var(--color-component-badge-background);
71
- padding: 0.125rem 0.25rem;
72
- line-height: normal;
73
- border-radius: 0.125rem;
74
- margin-left: 0.25rem;
75
- font-family: var(--font-family-monospace);
76
- font-size: var(--font-size-monospace-small);
77
-}
src/devtools/views/Components/Element.js
+24
-10
@@ -9,6 +9,7 @@ import React, {
9
} from 'react';
10
import { ElementTypeMemo, ElementTypeForwardRef } from 'src/types';
11
import Store from 'src/devtools/store';
12
+import Badge from './Badge';
13
import ButtonIcon from '../ButtonIcon';
14
import { createRegExp, truncateText } from '../utils';
15
import { TreeDispatcherContext, TreeStateContext } from './TreeContext';
@@ -80,9 +81,23 @@ export default function ElementView({ data, index, style }: Props) {
81
return null;
82
}
83
83
- const { depth, displayName, key, type } = ((element: any): Element);
84
-
85
- const showBadge = type === ElementTypeMemo || type === ElementTypeForwardRef;
84
+ const {
85
+ depth,
86
+ displayName,
87
+ hocDisplayNames,
88
+ key,
89
+ type,
90
+ } = ((element: any): Element);
91
+
92
+ // TODO Maybe factor this into a shared util method?
93
+ let badge = null;
94
+ if (hocDisplayNames !== null) {
95
+ badge = hocDisplayNames.length === 1 ? hocDisplayNames[0] : '…';
96
+ } else if (type === ElementTypeMemo) {
97
+ badge = 'Memo';
98
+ } else if (type === ElementTypeForwardRef) {
99
+ badge = 'ForwardRef';
100
+ }
101
102
let className = styles.Element;
103
if (isSelected) {
@@ -105,8 +120,12 @@ export default function ElementView({ data, index, style }: Props) {
120
121
// Left padding presents the appearance of a nested tree structure.
122
// We must use padding rather than margin/left because of the selected background color.
108
- paddingLeft: `calc(${depth} * var(--indentation-size))`,
123
+ paddingLeft: `calc(${depth} * var(--indentation-size) + 0.25rem)`,
124
+
125
+ // TODO Animate horizontal axis compression so it's smoother.
126
+ // transition: 'padding-left 250ms',
127
}}
128
+ data-depth={depth}
129
>
130
{ownerID === null ? (
131
<ExpandCollapseToggle element={element} store={store} />
@@ -123,12 +142,7 @@ export default function ElementView({ data, index, style }: Props) {
142
</Fragment>
143
)}
144
<span className={styles.Bracket}>></span>
126
-
127
- {showBadge && (
128
- <span className={styles.Badge}>
129
- {type === ElementTypeMemo ? 'Memo' : 'ForwardRef'}
130
- </span>
131
- )}
145
+ <Badge>{badge}</Badge>
146
</div>
147
);
148
}
src/devtools/views/Components/Guideline.css
+6
-12
@@ -1,14 +1,8 @@
1
-.GuidelineActive,
2
-.GuidelineInactive {
3
- width: 1px;
4
- z-index: 2;
1
+.Guideline {
2
+ position: absolute;
3
+ left: 0;
4
+ width: 100%;
5
+ z-index: 0;
6
pointer-events: none;
6
-}
7
-
8
-.GuidelineActive {
9
- background-color: var(--color-background-selected);
10
-}
11
-
12
-.GuidelineInactive {
13
- background-color: var(--color-background-inactive);
7
+ background-color: var(--color-guideline);
8
}
src/devtools/views/Components/Guideline.js
+5
-11
@@ -2,7 +2,6 @@
2
3
import React, { useContext, useMemo } from 'react';
4
import { TreeStateContext } from './TreeContext';
5
-import TreeFocusedContext from './TreeFocusedContext';
5
import { SettingsContext } from '../Settings/SettingsContext';
6
import { StoreContext } from '../context';
7
import { useSubscription } from '../hooks';
@@ -11,7 +10,6 @@ import Store from '../../store';
10
import styles from './Guideline.css';
11
12
type Data = {|
14
- depth: number,
13
startIndex: number,
14
stopIndex: number,
15
|};
@@ -20,7 +18,6 @@ export default function Guideline(_: {||}) {
18
const { lineHeight } = useContext(SettingsContext);
19
const store = useContext(StoreContext);
20
const { selectedElementID } = useContext(TreeStateContext);
23
- const treeFocused = useContext(TreeFocusedContext);
21
22
const subscription = useMemo(
23
() => ({
@@ -61,7 +58,6 @@ export default function Guideline(_: {||}) {
58
}
59
60
return {
64
- depth: element.depth,
61
startIndex,
62
stopIndex,
63
};
@@ -81,18 +77,16 @@ export default function Guideline(_: {||}) {
77
return null;
78
}
79
84
- const { depth, startIndex, stopIndex } = data;
80
+ const { startIndex, stopIndex } = data;
81
82
return (
83
<div
88
- className={
89
- treeFocused ? styles.GuidelineActive : styles.GuidelineInactive
90
- }
84
+ className={styles.Guideline}
85
style={{
86
position: 'absolute',
93
- top: `${startIndex * lineHeight}px`,
94
- left: `calc(${depth} * var(--indentation-size) + 0.5rem)`,
95
- height: `${(stopIndex + 1 - startIndex) * lineHeight}px`,
87
+ top: `${startIndex * lineHeight - lineHeight / 2}px`,
88
+ height: `${(stopIndex + 1 - startIndex) * lineHeight +
89
+ lineHeight / 2}px`,
90
}}
91
/>
92
);
src/devtools/views/Components/HocBadges.css
new
+9
@@ -0,0 +1,9 @@
1
+.HocBadges {
2
+ padding: 0.125rem 0.25rem;
3
+ background-color: var(--color-background-selected);
4
+ color: var(--color-text-selected);
5
+ --color-component-badge-background: var(
6
+ --color-component-badge-background-inverted
7
+ );
8
+ user-select: none;
9
+}
src/devtools/views/Components/HocBadges.js
new
+37
@@ -0,0 +1,37 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { ElementTypeForwardRef, ElementTypeMemo } from 'src/types';
5
+import Badge from './Badge';
6
+import styles from './HocBadges.css';
7
+
8
+import type { Element } from './types';
9
+
10
+type Props = {|
11
+ element: Element,
12
+|};
13
+
14
+export default function HocBadges({ element }: Props) {
15
+ const { hocDisplayNames, type } = ((element: any): Element);
16
+
17
+ let typeBadge = null;
18
+ if (type === ElementTypeMemo) {
19
+ typeBadge = 'Memo';
20
+ } else if (type === ElementTypeForwardRef) {
21
+ typeBadge = 'ForwardRef';
22
+ }
23
+
24
+ if (hocDisplayNames === null && typeBadge === null) {
25
+ return null;
26
+ }
27
+
28
+ return (
29
+ <div className={styles.HocBadges}>
30
+ <Badge>{typeBadge}</Badge>
31
+ {hocDisplayNames !== null &&
32
+ hocDisplayNames.map(hocDisplayName => (
33
+ <Badge key={hocDisplayName}>{hocDisplayName}</Badge>
34
+ ))}
35
+ </div>
36
+ );
37
+}
src/devtools/views/Components/InspectedElementContext.js
+24
@@ -12,6 +12,7 @@ import { createResource } from '../../cache';
12
import { BridgeContext, StoreContext } from '../context';
13
import { hydrate } from 'src/hydration';
14
import { TreeStateContext } from './TreeContext';
15
+import { separateDisplayNameAndHOCs } from 'src/utils';
16
17
import type {
18
DehydratedData,
@@ -95,8 +96,31 @@ function InspectedElementContextController({ children }: Props) {
96
if (inspectedElement !== null && typeof inspectedElement === 'object') {
97
const id = inspectedElement.id;
98
99
+ const { displayName, type } = inspectedElement;
100
+
101
+ const [
102
+ displayNameWithoutHOCs,
103
+ hocDisplayNames,
104
+ ] = separateDisplayNameAndHOCs(displayName, type);
105
+
106
inspectedElement = (({
107
...inspectedElement,
108
+ displayName: displayNameWithoutHOCs,
109
+ owners:
110
+ inspectedElement.owners === null
111
+ ? null
112
+ : inspectedElement.owners.map(owner => {
113
+ const [
114
+ displayName,
115
+ hocDisplayNames,
116
+ ] = separateDisplayNameAndHOCs(owner.displayName, owner.type);
117
+ return {
118
+ ...owner,
119
+ displayName,
120
+ hocDisplayNames,
121
+ };
122
+ }),
123
+ hocDisplayNames,
124
context: hydrateHelper(inspectedElement.context),
125
events: hydrateHelper(inspectedElement.events),
126
hooks: hydrateHelper(inspectedElement.hooks),
src/devtools/views/Components/OwnersListContext.js
+18
-1
@@ -9,6 +9,7 @@ import React, {
9
import { createResource } from '../../cache';
10
import { BridgeContext, StoreContext } from '../context';
11
import { TreeStateContext } from './TreeContext';
12
+import { separateDisplayNameAndHOCs } from 'src/utils';
13
14
import type {
15
Element,
@@ -79,7 +80,23 @@ function OwnersListContextController({ children }: Props) {
80
const request = inProgressRequests.get(element);
81
if (request != null) {
82
inProgressRequests.delete(element);
82
- request.resolveFn(ownersList.owners);
83
+
84
+ request.resolveFn(
85
+ ownersList.owners === null
86
+ ? null
87
+ : ownersList.owners.map(owner => {
88
+ const [
89
+ displayNameWithoutHOCs,
90
+ hocDisplayNames,
91
+ ] = separateDisplayNameAndHOCs(owner.displayName, owner.type);
92
+
93
+ return {
94
+ ...owner,
95
+ displayName: displayNameWithoutHOCs,
96
+ hocDisplayNames,
97
+ };
98
+ })
99
+ );
100
}
101
}
102
};
src/devtools/views/Components/SelectedElement.css
+6
-1
@@ -69,7 +69,6 @@
69
.Owner {
70
border-radius: 0.25rem;
71
padding: 0.125rem 0.25rem;
72
- margin-left: 0.5rem;
72
cursor: pointer;
73
background: none;
74
border: none;
@@ -94,3 +93,9 @@
93
color: var(--color-dim);
94
cursor: default;
95
}
96
+
97
+.OwnerButton {
98
+ display: flex;
99
+ align-items: center;
100
+ margin-left: 0.5rem;
101
+}
src/devtools/views/Components/SelectedElement.js
+39
-7
@@ -8,10 +8,12 @@ import ButtonIcon from '../ButtonIcon';
8
import HooksTree from './HooksTree';
9
import EventsTree from './EventsTree';
10
import { ModalDialogContext } from '../ModalDialog';
11
+import HocBadges from './HocBadges';
12
import InspectedElementTree from './InspectedElementTree';
13
import { InspectedElementContext } from './InspectedElementContext';
14
import ViewElementSourceContext from './ViewElementSourceContext';
15
import Toggle from '../Toggle';
16
+import Badge from './Badge';
17
import {
18
ComponentFilterElementType,
19
ElementTypeClass,
@@ -24,6 +26,7 @@ import {
26
import styles from './SelectedElement.css';
27
28
import type { Element, InspectedElement } from './types';
29
+import type { ElementType } from 'src/types';
30
31
export type Props = {||};
32
@@ -272,6 +275,7 @@ function InspectedElementView({
275
276
return (
277
<div className={styles.InspectedElement}>
278
+ <HocBadges element={element} />
279
<InspectedElementTree
280
label="props"
281
data={props}
@@ -308,8 +312,10 @@ function InspectedElementView({
312
<OwnerView
313
key={owner.id}
314
displayName={owner.displayName || 'Anonymous'}
315
+ hocDisplayNames={owner.hocDisplayNames}
316
id={owner.id}
317
isInStore={store.containsElement(owner.id)}
318
+ type={owner.type}
319
/>
320
))}
321
</div>
@@ -318,9 +324,21 @@ function InspectedElementView({
324
);
325
}
326
321
-type OwnerViewProps = {| displayName: string, id: number, isInStore: boolean |};
327
+type OwnerViewProps = {|
328
+ displayName: string,
329
+ hocDisplayNames: Array<string> | null,
330
+ id: number,
331
+ isInStore: boolean,
332
+ type: ElementType,
333
+|};
334
323
-function OwnerView({ displayName, id, isInStore }: OwnerViewProps) {
335
+function OwnerView({
336
+ displayName,
337
+ hocDisplayNames,
338
+ id,
339
+ isInStore,
340
+ type,
341
+}: OwnerViewProps) {
342
const dispatch = useContext(TreeDispatcherContext);
343
344
const handleClick = useCallback(
@@ -332,16 +350,30 @@ function OwnerView({ displayName, id, isInStore }: OwnerViewProps) {
350
[dispatch, id]
351
);
352
353
+ let badge = null;
354
+ if (hocDisplayNames !== null) {
355
+ badge = hocDisplayNames.length === 1 ? hocDisplayNames[0] : '…';
356
+ } else if (type === ElementTypeMemo) {
357
+ badge = 'Memo';
358
+ } else if (type === ElementTypeForwardRef) {
359
+ badge = 'ForwardRef';
360
+ }
361
+
362
return (
336
- <button
363
+ <Button
364
key={id}
338
- className={`${styles.Owner} ${isInStore ? '' : styles.NotInStore}`}
365
+ className={styles.OwnerButton}
366
disabled={!isInStore}
367
onClick={handleClick}
341
- title={displayName}
368
>
343
- {displayName}
344
- </button>
369
+ <span
370
+ className={`${styles.Owner} ${isInStore ? '' : styles.NotInStore}`}
371
+ title={displayName}
372
+ >
373
+ {displayName}
374
+ </span>
375
+ <Badge>{badge}</Badge>
376
+ </Button>
377
);
378
}
379
src/devtools/views/Components/Tree.js
+62
-15
@@ -322,34 +322,73 @@ export default function Tree(props: Props) {
322
);
323
}
324
325
-function updateIndentationSizeVar(innerDiv: HTMLDivElement): void {
326
- const list = ((innerDiv.parentElement: any): HTMLDivElement);
325
+/* TODO Debounce so newly added rows animate with pre-existing ones
326
+let debounceTimeoutID: TimeoutID | null = null;
327
+function debounce(callback: () => void, delay: number) {
328
+ if (debounceTimeoutID !== null) {
329
+ clearTimeout(debounceTimeoutID);
330
+ }
331
+ debounceTimeoutID = setTimeout(callback, delay);
332
+}
333
+*/
334
328
- let indentationSize =
329
- parseFloat(getComputedStyle(list).getPropertyValue('--indentation-size')) ||
330
- 12;
335
+function updateIndentationSizeVar(
336
+ innerDiv: HTMLDivElement,
337
+ indentationSizeRef: {| current: number |},
338
+ cachedChildWidths: WeakMap<HTMLElement, number>
339
+): void {
340
+ const list = ((innerDiv.parentElement: any): HTMLDivElement);
341
342
let maxChildWidth = 0;
343
for (let child of innerDiv.children) {
334
- const { lastElementChild } = child;
335
- // Skip over e.g. the guideline element
336
- if (lastElementChild != null) {
337
- const bounds = ((lastElementChild.getBoundingClientRect(): any): DOMRect);
338
- maxChildWidth = Math.max(maxChildWidth, bounds.x + bounds.width);
344
+ const depth = parseInt(child.getAttribute('data-depth'), 10) || 0;
345
+
346
+ const cachedChildWidth = cachedChildWidths.get(child);
347
+ if (cachedChildWidth != null) {
348
+ maxChildWidth = Math.max(
349
+ maxChildWidth,
350
+ indentationSizeRef.current * depth + cachedChildWidth
351
+ );
352
+ } else {
353
+ const { firstElementChild, lastElementChild } = child;
354
+
355
+ // Skip over e.g. the guideline element
356
+ if (firstElementChild != null && lastElementChild != null) {
357
+ const firstBounds = ((firstElementChild.getBoundingClientRect(): any): DOMRect);
358
+ const lastBounds = ((lastElementChild.getBoundingClientRect(): any): DOMRect);
359
+ const childWidth = lastBounds.x + lastBounds.width - firstBounds.x;
360
+
361
+ cachedChildWidths.set(child, childWidth);
362
+
363
+ maxChildWidth = Math.max(
364
+ maxChildWidth,
365
+ indentationSizeRef.current * depth + childWidth
366
+ );
367
+ }
368
}
369
}
370
342
- indentationSize = Math.min(
371
+ const indentationSize = Math.min(
372
12,
344
- (list.clientWidth / maxChildWidth) * indentationSize
373
+ (list.clientWidth / maxChildWidth) * indentationSizeRef.current
374
);
375
376
+ // TODO Debounce so newly added rows animate with pre-existing ones
377
+ // debounce(() => {
378
list.style.setProperty('--indentation-size', `${indentationSize}px`);
379
+ indentationSizeRef.current = indentationSize;
380
+ // }, 50);
381
}
382
383
function InnerElementType({ children, style, ...rest }) {
384
const { ownerID } = useContext(TreeStateContext);
385
386
+ const indentationSizeRef = useRef<number>(12);
387
+ const cachedChildWidths = useMemo<WeakMap<HTMLElement, number>>(
388
+ () => new WeakMap(),
389
+ []
390
+ );
391
+
392
// The list may need to scroll horizontally due to deeply nested elements.
393
// We don't know the maximum scroll width up front, because we're windowing.
394
// What we can do instead, is passively measure the width of the current rows,
@@ -361,7 +400,11 @@ function InnerElementType({ children, style, ...rest }) {
400
// eslint-disable-next-line react-hooks/exhaustive-deps
401
useEffect(() => {
402
if (divRef.current !== null) {
364
- updateIndentationSizeVar(divRef.current);
403
+ updateIndentationSizeVar(
404
+ divRef.current,
405
+ indentationSizeRef,
406
+ cachedChildWidths
407
+ );
408
}
409
});
410
@@ -371,12 +414,16 @@ function InnerElementType({ children, style, ...rest }) {
414
useEffect(() => {
415
const invalidateMinWidth = () => {
416
if (divRef.current !== null) {
374
- updateIndentationSizeVar(divRef.current);
417
+ updateIndentationSizeVar(
418
+ divRef.current,
419
+ indentationSizeRef,
420
+ cachedChildWidths
421
+ );
422
}
423
};
424
window.addEventListener('resize', invalidateMinWidth);
425
return () => window.removeEventListener('resize', invalidateMinWidth);
379
- }, []);
426
+ }, [cachedChildWidths]);
427
428
// We shouldn't retain this width across different conceptual trees though,
429
// so when the user opens the "owners tree" view, we should discard the previous width.
src/devtools/views/Components/types.js
+6
@@ -14,6 +14,8 @@ export type Element = {|
14
displayName: string | null,
15
key: number | string | null,
16
17
+ hocDisplayNames: null | Array<string>,
18
+
19
// Should the elements children be visible in the tree?
20
isCollapsed: boolean,
21
@@ -33,6 +35,8 @@ export type Element = {|
35
export type Owner = {|
36
displayName: string | null,
37
id: number,
38
+ hocDisplayNames: Array<string> | null,
39
+ type: ElementType,
40
|};
41
42
export type OwnersList = {|
@@ -69,6 +73,8 @@ export type InspectedElement = {|
73
74
// Location of component in source coude.
75
source: Object | null,
76
+
77
+ type: ElementType,
78
|};
79
80
// TODO: Add profiling type
src/devtools/views/root.css
+2
-2
@@ -42,7 +42,7 @@
42
--light-color-dimmer: #cfd1d5;
43
--light-color-dimmest: #eff0f1;
44
--light-color-expand-collapse-toggle: #777d88;
45
- --light-color-guideline: rgba(0, 0, 0, 0.15);
45
+ --light-color-guideline: rgba(0, 0, 0, 0.05);
46
--light-color-jsx-arrow-brackets: #333333;
47
--light-color-jsx-arrow-brackets-inverted: rgba(255, 255, 255, 0.7);
48
--light-color-modal-background: rgba(255, 255, 255, 0.75);
@@ -100,7 +100,7 @@
100
--dark-color-dimmer: #777d88;
101
--dark-color-dimmest: #4f5766;
102
--dark-color-expand-collapse-toggle: #8f949d;
103
- --dark-color-guideline: rgba(255, 255, 255, 0.15);
103
+ --dark-color-guideline: rgba(255, 255, 255, 0.05);
104
--dark-color-jsx-arrow-brackets: #777d88;
105
--dark-color-jsx-arrow-brackets-inverted: rgba(255, 255, 255, 0.7);
106
--dark-color-modal-background: rgba(0, 0, 0, 0.75);
src/utils.js
+37
-1
@@ -3,8 +3,14 @@
3
import LRU from 'lru-cache';
4
import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
5
import { ComponentFilterElementType, ElementTypeHostComponent } from './types';
6
+import {
7
+ ElementTypeClass,
8
+ ElementTypeForwardRef,
9
+ ElementTypeFunction,
10
+ ElementTypeMemo,
11
+} from 'src/types';
12
7
-import type { ComponentFilter } from './types';
13
+import type { ComponentFilter, ElementType } from './types';
14
15
const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
16
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
@@ -109,3 +115,33 @@ export function saveComponentFilters(
115
JSON.stringify(componentFilters)
116
);
117
}
118
+
119
+export function separateDisplayNameAndHOCs(
120
+ displayName: string | null,
121
+ type: ElementType
122
+): [string | null, Array<string> | null] {
123
+ if (displayName === null) {
124
+ return [null, null];
125
+ }
126
+
127
+ let hocDisplayNames = null;
128
+
129
+ switch (type) {
130
+ case ElementTypeClass:
131
+ case ElementTypeForwardRef:
132
+ case ElementTypeFunction:
133
+ case ElementTypeMemo:
134
+ if (displayName.indexOf('(') >= 0) {
135
+ const matches = displayName.match(/[^()]+/g);
136
+ if (matches !== null) {
137
+ displayName = matches.pop();
138
+ hocDisplayNames = matches;
139
+ }
140
+ }
141
+ break;
142
+ default:
143
+ break;
144
+ }
145
+
146
+ return [displayName, hocDisplayNames];
147
+}