Skip to content

Commit

Permalink
fix[Tree]: only scroll to item when panel is visible
Browse files Browse the repository at this point in the history
  • Loading branch information
hoxyq committed Jan 8, 2025
1 parent ecb8df4 commit 1a2dd28
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 25 deletions.
8 changes: 6 additions & 2 deletions packages/react-devtools-extensions/src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,12 @@ function createComponentsPanel() {
}
});

// TODO: we should listen to createdPanel.onHidden to unmount some listeners
// and potentially stop highlighting
createdPanel.onShown.addListener(() => {
bridge.emit('extensionComponentsPanelShown');
});
createdPanel.onHidden.addListener(() => {
bridge.emit('extensionComponentsPanelHidden');
});
},
);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ type FrontendEvents = {
clearWarningsForElementID: [ElementAndRendererID],
copyElementPath: [CopyElementPathParams],
deletePath: [DeletePath],
extensionComponentsPanelShown: [],
extensionComponentsPanelHidden: [],
getBackendVersion: [],
getBridgeProtocol: [],
getIfHasUnsupportedRendererVersion: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import styles from './Tree.css';
import ButtonIcon from '../ButtonIcon';
import Button from '../Button';
import {logEvent} from 'react-devtools-shared/src/Logger';
import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';

// Never indent more than this number of pixels (even if we have the room).
const DEFAULT_INDENTATION_SIZE = 12;
Expand Down Expand Up @@ -78,36 +79,28 @@ export default function Tree(): React.Node {
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {hideSettings} = useContext(OptionsContext);
const {lineHeight} = useContext(SettingsContext);

const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] =
useState(false);
const {highlightHostInstance, clearHighlightHostInstance} =
useHighlightHostInstance();
const [treeFocused, setTreeFocused] = useState<boolean>(false);
const componentsPanelVisible = useExtensionComponentsPanelVisibility(bridge);

const treeRef = useRef<HTMLDivElement | null>(null);
const focusTargetRef = useRef<HTMLDivElement | null>(null);
const listRef = useRef(null);

const [treeFocused, setTreeFocused] = useState<boolean>(false);

const {lineHeight} = useContext(SettingsContext);
useEffect(() => {
if (!componentsPanelVisible) {
return;
}

// Make sure a newly selected element is visible in the list.
// This is helpful for things like the owners list and search.
//
// TRICKY:
// It's important to use a callback ref for this, rather than a ref object and an effect.
// As an optimization, the AutoSizer component does not render children when their size would be 0.
// This means that in some cases (if the browser panel size is initially really small),
// the Tree component might render without rendering an inner List.
// In this case, the list ref would be null on mount (when the scroll effect runs),
// meaning the scroll action would be skipped (since ref updates don't re-run effects).
// Using a callback ref accounts for this case...
const listCallbackRef = useCallback(
(list: $FlowFixMe) => {
if (list != null && inspectedElementIndex !== null) {
list.scrollToItem(inspectedElementIndex, 'smart');
}
},
[inspectedElementIndex],
);
if (listRef.current != null && inspectedElementIndex !== null) {
listRef.current.scrollToItem(inspectedElementIndex, 'smart');
}
}, [inspectedElementIndex, componentsPanelVisible]);

// Picking an element in the inspector should put focus into the tree.
// This ensures that keyboard navigation works right after picking a node.
Expand Down Expand Up @@ -449,7 +442,7 @@ export default function Tree(): React.Node {
itemData={itemData}
itemKey={itemKey}
itemSize={lineHeight}
ref={listCallbackRef}
ref={listRef}
width={width}>
{Element}
</FixedSizeList>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import {useState, useEffect} from 'react';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';

// Events that are prefixed with `extension` will only be emitted for the browser extension implementation.
// For other implementations, this hook will just return constant `true` value.
export function useExtensionComponentsPanelVisibility(
bridge: FrontendBridge,
): boolean {
const [isVisible, setIsVisible] = useState(true);

useEffect(() => {
function onPanelShown() {
setIsVisible(true);
}
function onPanelHidden() {
setIsVisible(false);
}

bridge.addListener('extensionComponentsPanelShown', onPanelShown);
bridge.addListener('extensionComponentsPanelHidden', onPanelHidden);

return () => {
bridge.removeListener('extensionComponentsPanelShown', onPanelShown);
bridge.removeListener('extensionComponentsPanelHidden', onPanelHidden);
};
}, [bridge]);

return isVisible;
}

0 comments on commit 1a2dd28

Please sign in to comment.