/
githubmr
/
facebook-react
Обзор
Документация
Войти
/
githubmr
/
facebook-react
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-devtools-shared/src/devtools/views/Components/Element.js
289 строк
8 KB
Sophie Alpert
devtools: fix ellipsis truncation for key values (#34796)
10 окт 2025, 21:05
Не верифицирован
10 окт 2025, 21:05
8454a32
Код
Авторство
О чём код?
/** * 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 * as React from 'react'; import {Fragment, useContext, useMemo, useState} from 'react'; import Store from 'react-devtools-shared/src/devtools/store'; import ButtonIcon from '../ButtonIcon'; import {TreeDispatcherContext, TreeStateContext} from './TreeContext'; import {StoreContext} from '../context'; import {useSubscription} from '../hooks'; import {logEvent} from 'react-devtools-shared/src/Logger'; import IndexableElementBadges from './IndexableElementBadges'; import IndexableDisplayName from './IndexableDisplayName'; import type {ItemData} from './Tree'; import type {Element as ElementType} from 'react-devtools-shared/src/frontend/types'; import styles from './Element.css'; import Icon from '../Icon'; import {useChangeOwnerAction} from './OwnersListContext'; import Tooltip from './reach-ui/tooltip'; type Props = { data: ItemData, index: number, style: Object, ... }; export default function Element({data, index, style}: Props): React.Node { const store = useContext(StoreContext); const {ownerFlatTree, ownerID, inspectedElementID} = useContext(TreeStateContext); const dispatch = useContext(TreeDispatcherContext); const element = ownerFlatTree !== null ? ownerFlatTree[index] : store.getElementAtIndex(index); const [isHovered, setIsHovered] = useState(false); const errorsAndWarningsSubscription = useMemo( () => ({ getCurrentValue: () => element === null ? {errorCount: 0, warningCount: 0} : store.getErrorAndWarningCountForElementID(element.id), subscribe: (callback: Function) => { store.addListener('mutated', callback); return () => store.removeListener('mutated', callback); }, }), [store, element], ); const {errorCount, warningCount} = useSubscription<{ errorCount: number, warningCount: number, }>(errorsAndWarningsSubscription); const changeOwnerAction = useChangeOwnerAction(); // Handle elements that are removed from the tree while an async render is in progress. if (element == null) { console.warn(`<Element> Could not find element at index ${index}`); // This return needs to happen after hooks, since hooks can't be conditional. return null; } const handleDoubleClick = () => { if (id !== null) { changeOwnerAction(id); } }; // $FlowFixMe[missing-local-annot] const handleClick = ({metaKey, button}) => { if (id !== null && button === 0) { logEvent({ event_name: 'select-element', metadata: {source: 'click-element'}, }); dispatch({ type: 'SELECT_ELEMENT_BY_ID', payload: metaKey ? null : id, }); } }; const handleMouseEnter = () => { setIsHovered(true); if (id !== null) { onElementMouseEnter(id); } }; const handleMouseLeave = () => { setIsHovered(false); }; // $FlowFixMe[missing-local-annot] const handleKeyDoubleClick = event => { // Double clicks on key value are used for text selection (if the text has been truncated). // They should not enter the owners tree view. event.stopPropagation(); event.preventDefault(); }; const { id, depth, displayName, hocDisplayNames, isStrictModeNonCompliant, key, nameProp, compiledWithForget, } = element; const { isNavigatingWithKeyboard, onElementMouseEnter, treeFocused, calculateElementOffset, } = data; const isSelected = inspectedElementID === id; const isDescendantOfSelected = inspectedElementID !== null && !isSelected && store.isDescendantOf(inspectedElementID, id); const elementOffset = calculateElementOffset(depth); // Only show strict mode non-compliance badges for top level elements. // Showing an inline badge for every element in the tree would be noisy. const showStrictModeBadge = isStrictModeNonCompliant && depth === 0; let className = styles.Element; if (isSelected) { className = treeFocused ? styles.SelectedElement : styles.InactiveSelectedElement; } else if (isHovered && !isNavigatingWithKeyboard) { className = styles.HoveredElement; } else if (isDescendantOfSelected) { className = treeFocused ? styles.HighlightedElement : styles.InactiveHighlightedElement; } return ( <div className={className} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} onMouseDown={handleClick} onDoubleClick={handleDoubleClick} style={{ ...style, paddingLeft: elementOffset, }} data-testname="ComponentTreeListItem"> {/* This wrapper is used by Tree for measurement purposes. */} <div className={styles.Wrapper}> {ownerID === null && ( <ExpandCollapseToggle element={element} store={store} /> )} <IndexableDisplayName displayName={displayName} id={id} /> {key && ( <Fragment> <span className={styles.KeyName}>key</span>=" <span className={styles.KeyValue} title={key} onDoubleClick={handleKeyDoubleClick}> <IndexableDisplayName displayName={key} id={id} /> </span> " </Fragment> )} {nameProp && ( <Fragment> <span className={styles.KeyName}>name</span>=" <span className={styles.KeyValue} title={nameProp} onDoubleClick={handleKeyDoubleClick}> <IndexableDisplayName displayName={nameProp} id={id} /> </span> " </Fragment> )} <IndexableElementBadges hocDisplayNames={hocDisplayNames} compiledWithForget={compiledWithForget} elementID={id} className={styles.BadgesBlock} /> {errorCount > 0 && ( <Icon type="error" className={ isSelected && treeFocused ? styles.ErrorIconContrast : styles.ErrorIcon } /> )} {warningCount > 0 && ( <Icon type="warning" className={ isSelected && treeFocused ? styles.WarningIconContrast : styles.WarningIcon } /> )} {showStrictModeBadge && ( <Tooltip label="This component is not running in StrictMode."> <Icon className={ isSelected && treeFocused ? styles.StrictModeContrast : styles.StrictMode } type="strict-mode-non-compliant" /> </Tooltip> )} </div> </div> ); } // Prevent double clicks on toggle from drilling into the owner list. // $FlowFixMe[missing-local-annot] const swallowDoubleClick = event => { event.preventDefault(); event.stopPropagation(); }; type ExpandCollapseToggleProps = { element: ElementType, store: Store, }; function ExpandCollapseToggle({element, store}: ExpandCollapseToggleProps) { const {children, id, isCollapsed} = element; // $FlowFixMe[missing-local-annot] const toggleCollapsed = event => { event.preventDefault(); event.stopPropagation(); store.toggleIsCollapsed(id, !isCollapsed); }; // $FlowFixMe[missing-local-annot] const stopPropagation = event => { // Prevent the row from selecting event.stopPropagation(); }; if (children.length === 0) { return <div className={styles.ExpandCollapseToggle} />; } return ( <div className={styles.ExpandCollapseToggle} onMouseDown={stopPropagation} onClick={toggleCollapsed} onDoubleClick={swallowDoubleClick}> <ButtonIcon type={isCollapsed ? 'collapsed' : 'expanded'} /> </div> ); }