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}><</span>
121
- <DisplayName displayName={displayName} id={((id: any): number)} />
122
- {key && (
123
- <Fragment>
124
- <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}>></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}><</span>
123
+ <DisplayName displayName={displayName} id={((id: any): number)} />
124
+ {key && (
125
+ <Fragment>
126
+ <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}>></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