@samitouri / QOS-React-2 / commits / 17b04c321c

Cleaned up how we measure children and adjust indentation size

Brian Vaughn committed Jun 2, 2019 at 10:02 UTC 17b04c321c5b494ab2418197c5001603453c6aa3
5 files changed +93 -66
src/devtools/views/Components/Badge.js
+1 -1
@@ -40,7 +40,7 @@ export default function Badge({ className, hocDisplayNames, type }: Props) {
40 {hocDisplayName || typeLabel}
41 </div>
42 {totalBadgeCount > 1 && (
43 - <div className={styles.ExtraLabel}>+{totalBadgeCount}</div>
43 + <div className={styles.ExtraLabel}>+{totalBadgeCount - 1}</div>
44 )}
45 </Fragment>
46 );
src/devtools/views/Components/Element.css
+11 -11
@@ -3,12 +3,6 @@
3 .SelectedElement,
4 .HoveredElement {
5 border-radius: 0.25em;
6 - white-space: nowrap;
7 - line-height: var(--line-height-data);
8 - display: flex;
9 - align-items: center;
10 - cursor: default;
11 - user-select: none;
6 color: var(--color-component-name);
7 }
8 .HoveredElement {
@@ -18,6 +12,17 @@
12 background-color: var(--color-background-inactive);
13 }
14
15 +.Wrapper {
16 + padding: 0 0.25rem;
17 + white-space: nowrap;
18 + height: var(--line-height-data);
19 + line-height: var(--line-height-data);
20 + display: inline-flex;
21 + align-items: center;
22 + cursor: default;
23 + user-select: none;
24 +}
25 +
26 .Bracket {
27 color: var(--color-jsx-arrow-brackets);
28 }
@@ -67,11 +72,6 @@
72 color: var(--color-expand-collapse-toggle);
73 }
74
70 -.Padding {
71 - width: 0.25rem;
72 - min-width: 0.25rem; /* Keep padding from shrinking when it's near the right */
73 -}
74 -
75 .Badge {
76 margin-left: 0.25rem;
77 }
src/devtools/views/Components/Element.js
+30 -28
@@ -104,36 +104,38 @@ export default function ElementView({ data, index, style }: Props) {
104 onMouseLeave={handleMouseLeave}
105 onMouseDown={handleMouseDown}
106 onDoubleClick={handleDoubleClick}
107 - style={{
108 - ...style, // "style" comes from react-window
109 -
110 - // Left padding presents the appearance of a nested tree structure.
111 - // We must use padding rather than margin/left because of the selected background color.
112 - paddingLeft: `calc(${depth} * var(--indentation-size))`,
113 - }}
107 + style={style}
108 data-depth={depth}
109 >
116 - <span className={styles.Padding} />
117 - {ownerID === null ? (
118 - <ExpandCollapseToggle element={element} store={store} />
119 - ) : null}
120 - <span className={styles.Bracket}>&lt;</span>
121 - <DisplayName displayName={displayName} id={((id: any): number)} />
122 - {key && (
123 - <Fragment>
124 - &nbsp;<span className={styles.AttributeName}>key</span>=
125 - <span className={styles.AttributeValue} title={key}>
126 - "{truncateText(`${key}`, 10)}"
127 - </span>
128 - </Fragment>
129 - )}
130 - <span className={styles.Bracket}>&gt;</span>
131 - <Badge
132 - className={styles.Badge}
133 - hocDisplayNames={hocDisplayNames}
134 - type={type}
135 - />
136 - <span className={styles.Padding} />
110 + {/* This wrapper is used by Tree for measurement purposes. */}
111 + <div
112 + className={styles.Wrapper}
113 + style={{
114 + // Left offset presents the appearance of a nested tree structure.
115 + // We must use padding rather than margin/left because of the selected background color.
116 + transform: `translateX(calc(${depth} * var(--indentation-size)))`,
117 + }}
118 + >
119 + {ownerID === null ? (
120 + <ExpandCollapseToggle element={element} store={store} />
121 + ) : null}
122 + <span className={styles.Bracket}>&lt;</span>
123 + <DisplayName displayName={displayName} id={((id: any): number)} />
124 + {key && (
125 + <Fragment>
126 + &nbsp;<span className={styles.AttributeName}>key</span>=
127 + <span className={styles.AttributeValue} title={key}>
128 + "{truncateText(`${key}`, 10)}"
129 + </span>
130 + </Fragment>
131 + )}
132 + <span className={styles.Bracket}>&gt;</span>
133 + <Badge
134 + className={styles.Badge}
135 + hocDisplayNames={hocDisplayNames}
136 + type={type}
137 + />
138 + </div>
139 </div>
140 );
141 }
src/devtools/views/Components/HocBadges.js
-1
@@ -2,7 +2,6 @@
2
3 import React from 'react';
4 import { ElementTypeForwardRef, ElementTypeMemo } from 'src/types';
5 -import Badge from './Badge';
5 import styles from './HocBadges.css';
6
7 import type { Element } from './types';
src/devtools/views/Components/Tree.js
+51 -25
@@ -322,14 +322,55 @@ export default function Tree(props: Props) {
322 );
323 }
324
325 +// Indentation size can be adjusted but child width is fixed.
326 +// We need to adjust indentations so the widest child can fit without overflowing.
327 +// Sometimes the widest child is also the deepest in the tree:
328 +// ┏━━━━━━━━━━━━┓
329 +// ┃ <Foo> ┃
330 +// ┃ ••••<Foobar> ┃
331 +// ┃ ••••••••<Baz> ┃
332 +// ┗━━━━━━━━━━━━┛
333 +//
334 +// But this is not always the case.
335 +// Even with the above example, a change in indentation may change the overall widest child:
336 +// ┏━━━━━━━━━━━━┓
337 +// ┃ <Foo> ┃
338 +// ┃ ••<Foobar> ┃
339 +// ┃ ••••<Baz> ┃
340 +// ┗━━━━━━━━━━━━┛
341 +//
342 +// In extreme cases this difference can be important:
343 +// ┏━━━━━━━━━━━━┓
344 +// ┃ <ReallyLongName> ┃
345 +// ┃ ••<Foo> ┃
346 +// ┃ ••••<Bar> ┃
347 +// ┃ ••••••<Baz> ┃
348 +// ┃ ••••••••<Qux> ┃
349 +// ┗━━━━━━━━━━━━┛
350 +//
351 +// In the above example, the current indentation is fine,
352 +// but if we naively assumed that the widest element is also the deepest element,
353 +// we would end up compressing the indentation unnecessarily:
354 +// ┏━━━━━━━━━━━━┓
355 +// ┃ <ReallyLongName> ┃
356 +// ┃ •<Foo> ┃
357 +// ┃ ••<Bar> ┃
358 +// ┃ •••<Baz> ┃
359 +// ┃ ••••<Qux> ┃
360 +// ┗━━━━━━━━━━━━┛
361 +//
362 +// The way we deal with this is to compute the max indentation size that can fit each child,
363 +// given the child's fixed width and depth within the tree.
364 +// Then we take the smallest of these indentation sizes...
365 function updateIndentationSizeVar(
366 innerDiv: HTMLDivElement,
327 - indentationSizeRef: {| current: number |},
367 cachedChildWidths: WeakMap<HTMLElement, number>
368 ): void {
369 const list = ((innerDiv.parentElement: any): HTMLDivElement);
370 + const listWidth = list.clientWidth;
371 +
372 + let maxIndentationSize: number = 12;
373
332 - let maxChildWidth = 0;
374 for (let child of innerDiv.children) {
375 const depth = parseInt(child.getAttribute('data-depth'), 10) || 0;
376
@@ -339,37 +380,26 @@ function updateIndentationSizeVar(
380 if (cachedChildWidth != null) {
381 childWidth = cachedChildWidth;
382 } else {
342 - const { firstElementChild, lastElementChild } = child;
383 + const { firstElementChild } = child;
384
385 // Skip over e.g. the guideline element
345 - if (firstElementChild != null && lastElementChild != null) {
346 - const firstBounds = ((firstElementChild.getBoundingClientRect(): any): DOMRect);
347 - const lastBounds = ((lastElementChild.getBoundingClientRect(): any): DOMRect);
348 -
349 - childWidth = lastBounds.x + lastBounds.width - firstBounds.x;
386 + if (firstElementChild != null) {
387 + childWidth = firstElementChild.clientWidth;
388 cachedChildWidths.set(child, childWidth);
389 }
390 }
391
354 - const childWidthIncludingIndentation =
355 - indentationSizeRef.current * depth + childWidth;
356 - maxChildWidth = Math.max(maxChildWidth, childWidthIncludingIndentation);
357 - }
358 -
359 - const indentationSize = Math.min(
360 - 12,
361 - (list.clientWidth / maxChildWidth) * indentationSizeRef.current
362 - );
392 + const remainingWidth = Math.max(0, listWidth - childWidth);
393
364 - indentationSizeRef.current = indentationSize;
394 + maxIndentationSize = Math.min(maxIndentationSize, remainingWidth / depth);
395 + }
396
366 - list.style.setProperty('--indentation-size', `${indentationSize}px`);
397 + list.style.setProperty('--indentation-size', `${maxIndentationSize}px`);
398 }
399
400 function InnerElementType({ children, style, ...rest }) {
401 const { ownerID } = useContext(TreeStateContext);
402
372 - const indentationSizeRef = useRef<number>(12);
403 const cachedChildWidths = useMemo<WeakMap<HTMLElement, number>>(
404 () => new WeakMap(),
405 []
@@ -386,11 +416,7 @@ function InnerElementType({ children, style, ...rest }) {
416 // eslint-disable-next-line react-hooks/exhaustive-deps
417 useEffect(() => {
418 if (divRef.current !== null) {
389 - updateIndentationSizeVar(
390 - divRef.current,
391 - indentationSizeRef,
392 - cachedChildWidths
393 - );
419 + updateIndentationSizeVar(divRef.current, cachedChildWidths);
420 }
421 });
422