mirror of
https://github.com/facebook/react.git
synced 2026-02-22 03:42:05 +00:00
[DevTools] Separate breadcrumbs with » (#35705)
This commit is contained in:
committed by
GitHub
parent
8b276df415
commit
1c66ac740c
@@ -32,6 +32,14 @@
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.OwnerStackFlatListContainer {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.OwnerStackFlatListSeparator {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.VRule {
|
||||
flex: 0 0 auto;
|
||||
height: 20px;
|
||||
|
||||
@@ -77,6 +77,54 @@ function dialogReducer(state: State, action: Action) {
|
||||
}
|
||||
}
|
||||
|
||||
type OwnerStackFlatListProps = {
|
||||
owners: Array<SerializedElement>,
|
||||
selectedIndex: number,
|
||||
selectOwner: SelectOwner,
|
||||
setElementsTotalWidth: (width: number) => void,
|
||||
};
|
||||
|
||||
function OwnerStackFlatList({
|
||||
owners,
|
||||
selectedIndex,
|
||||
selectOwner,
|
||||
setElementsTotalWidth,
|
||||
}: OwnerStackFlatListProps): React.Node {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (container === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResizeObserver = container.ownerDocument.defaultView.ResizeObserver;
|
||||
const observer = new ResizeObserver(entries => {
|
||||
const entry = entries[0];
|
||||
setElementsTotalWidth(entry.contentRect.width);
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
return observer.disconnect.bind(observer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.OwnerStackFlatListContainer} ref={containerRef}>
|
||||
{owners.map((owner, index) => (
|
||||
<Fragment key={index}>
|
||||
<ElementView
|
||||
owner={owner}
|
||||
isSelected={index === selectedIndex}
|
||||
selectOwner={selectOwner}
|
||||
/>
|
||||
{index < owners.length - 1 && (
|
||||
<span className={styles.OwnerStackFlatListSeparator}>»</span>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function OwnerStack(): React.Node {
|
||||
const read = useContext(OwnersListContext);
|
||||
const {ownerID} = useContext(TreeStateContext);
|
||||
@@ -135,32 +183,10 @@ export default function OwnerStack(): React.Node {
|
||||
|
||||
const selectedOwner = owners[selectedIndex];
|
||||
|
||||
useLayoutEffect(() => {
|
||||
// If we're already overflowing, then we don't need to re-measure items.
|
||||
// That's because once the owners stack is open, it can only get larger (by drilling in).
|
||||
// A totally new stack can only be reached by exiting this mode and re-entering it.
|
||||
if (elementsBarRef.current === null || isOverflowing) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < owners.length; i++) {
|
||||
const element = elementsBarRef.current.children[i];
|
||||
const computedStyle = getComputedStyle(element);
|
||||
|
||||
totalWidth +=
|
||||
element.offsetWidth +
|
||||
parseInt(computedStyle.marginLeft, 10) +
|
||||
parseInt(computedStyle.marginRight, 10);
|
||||
}
|
||||
|
||||
setElementsTotalWidth(totalWidth);
|
||||
}, [elementsBarRef, isOverflowing, owners.length]);
|
||||
|
||||
return (
|
||||
<div className={styles.OwnerStack}>
|
||||
<div className={styles.Bar} ref={elementsBarRef}>
|
||||
{isOverflowing && (
|
||||
{isOverflowing ? (
|
||||
<Fragment>
|
||||
<ElementsDropdown
|
||||
owners={owners}
|
||||
@@ -180,16 +206,14 @@ export default function OwnerStack(): React.Node {
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
{!isOverflowing &&
|
||||
owners.map((owner, index) => (
|
||||
<ElementView
|
||||
key={index}
|
||||
owner={owner}
|
||||
isSelected={index === selectedIndex}
|
||||
) : (
|
||||
<OwnerStackFlatList
|
||||
owners={owners}
|
||||
selectedIndex={selectedIndex}
|
||||
selectOwner={selectOwner}
|
||||
setElementsTotalWidth={setElementsTotalWidth}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.VRule} />
|
||||
<Button onClick={() => selectOwner(null)} title="Back to tree view">
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.SuspenseBreadcrumbsListItemSeparator {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.SuspenseBreadcrumbsListItem[aria-current="true"] .SuspenseBreadcrumbsButton {
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {SuspenseNode} from 'react-devtools-shared/src/frontend/types';
|
||||
import typeof {SyntheticMouseEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
|
||||
|
||||
import * as React from 'react';
|
||||
import {useContext, useLayoutEffect, useRef, useState} from 'react';
|
||||
import {Fragment, useContext, useLayoutEffect, useRef, useState} from 'react';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Tooltip from '../Components/reach-ui/tooltip';
|
||||
@@ -40,20 +40,40 @@ type SuspenseBreadcrumbsFlatListProps = {
|
||||
scrollIntoView?: boolean,
|
||||
) => void,
|
||||
onItemPointerLeave: (event: SyntheticMouseEvent) => void,
|
||||
setElementsTotalWidth: (width: number) => void,
|
||||
};
|
||||
|
||||
function SuspenseBreadcrumbsFlatList({
|
||||
onItemClick,
|
||||
onItemPointerEnter,
|
||||
onItemPointerLeave,
|
||||
setElementsTotalWidth,
|
||||
}: SuspenseBreadcrumbsFlatListProps): React$Node {
|
||||
const store = useContext(StoreContext);
|
||||
const {activityID} = useContext(TreeStateContext);
|
||||
const {selectedSuspenseID, lineage, roots} = useContext(
|
||||
SuspenseTreeStateContext,
|
||||
);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (container === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResizeObserver = container.ownerDocument.defaultView.ResizeObserver;
|
||||
const observer = new ResizeObserver(entries => {
|
||||
const entry = entries[0];
|
||||
setElementsTotalWidth(entry.contentRect.width);
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
return observer.disconnect.bind(observer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ol className={styles.SuspenseBreadcrumbsList}>
|
||||
<ol className={styles.SuspenseBreadcrumbsList} ref={containerRef}>
|
||||
{lineage === null ? null : lineage.length === 0 ? (
|
||||
// We selected the root. This means that we're currently viewing the Transition
|
||||
// that rendered the whole screen. In laymans terms this is really "Initial Paint" .
|
||||
@@ -79,8 +99,8 @@ function SuspenseBreadcrumbsFlatList({
|
||||
const node = store.getSuspenseByID(id);
|
||||
|
||||
return (
|
||||
<Fragment key={id}>
|
||||
<li
|
||||
key={id}
|
||||
className={styles.SuspenseBreadcrumbsListItem}
|
||||
aria-current={selectedSuspenseID === id}
|
||||
onPointerEnter={onItemPointerEnter.bind(null, id, false)}
|
||||
@@ -92,6 +112,12 @@ function SuspenseBreadcrumbsFlatList({
|
||||
{node === null ? 'Unknown' : node.name || 'Unknown'}
|
||||
</button>
|
||||
</li>
|
||||
{index < lineage.length - 1 && (
|
||||
<span className={styles.SuspenseBreadcrumbsListItemSeparator}>
|
||||
»
|
||||
</span>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})
|
||||
)}
|
||||
@@ -271,37 +297,6 @@ export default function SuspenseBreadcrumbs(): React$Node {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const isOverflowing = useIsOverflowing(containerRef, elementsTotalWidth);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (
|
||||
container === null ||
|
||||
// We want to measure the size of the flat list only when it's being used.
|
||||
isOverflowing
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ResizeObserver = container.ownerDocument.defaultView.ResizeObserver;
|
||||
const observer = new ResizeObserver(() => {
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < container.children.length; i++) {
|
||||
const element = container.children[i];
|
||||
const computedStyle = getComputedStyle(element);
|
||||
|
||||
totalWidth +=
|
||||
element.offsetWidth +
|
||||
parseInt(computedStyle.marginLeft, 10) +
|
||||
parseInt(computedStyle.marginRight, 10);
|
||||
}
|
||||
setElementsTotalWidth(totalWidth);
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
|
||||
return observer.disconnect.bind(observer);
|
||||
}, [containerRef, isOverflowing]);
|
||||
|
||||
return (
|
||||
<div className={styles.SuspenseBreadcrumbsContainer} ref={containerRef}>
|
||||
{isOverflowing ? (
|
||||
@@ -315,6 +310,7 @@ export default function SuspenseBreadcrumbs(): React$Node {
|
||||
onItemClick={handleClick}
|
||||
onItemPointerEnter={highlightHostInstance}
|
||||
onItemPointerLeave={clearHighlightHostInstance}
|
||||
setElementsTotalWidth={setElementsTotalWidth}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user