@samitouri / QOS-React-2 / commits / aa87b125e7

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}>&gt;</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 +}