### Run Next.js example Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Start the Next.js example in development mode. ```shell cd apps/next pnpm dev ``` -------------------------------- ### Run Create React App example Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Start the Create React App example. ```shell cd apps/cra pnpm start ``` -------------------------------- ### Install with npm Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Install the click-to-react-component package using npm. ```shell npm install click-to-react-component ``` -------------------------------- ### Install with pnpm Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Install the click-to-react-component package using pnpm. ```shell pnpm add click-to-react-component ``` -------------------------------- ### Install dependencies Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Install project dependencies using pnpm. ```shell pnpm install ``` -------------------------------- ### Install with yarn Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Install the click-to-react-component package using yarn. ```shell yarn add click-to-react-component ``` -------------------------------- ### Run the development server with npm or yarn Source: https://github.com/ericclemmons/click-to-component/blob/main/apps/next/README.md Start the Next.js development server using either npm run dev or yarn dev. After running, open http://localhost:3000 to view the app. ```bash npm run dev # or yarn dev ``` -------------------------------- ### Example: getSourceForInstance usage Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Example usage of getSourceForInstance with getReactInstancesForElement. Shows how to obtain the leaf instance and then the source object. The comment shows the expected result shape. ```js import { getReactInstancesForElement } from './getReactInstancesForElement.js' import { getSourceForInstance } from './getSourceForInstance.js' const [leafInstance] = getReactInstancesForElement(someElement) const source = getSourceForInstance(leafInstance) // source === { fileName: '/Users/me/app/src/App.js', lineNumber: 12, columnNumber: 34 } ``` -------------------------------- ### getPathToSource usage example Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Shows how to call `getPathToSource` with and without a `pathModifier`. The first call returns the full path; the second strips the `/app/` prefix. ```js import { getPathToSource } from './getPathToSource.js' const source = { fileName: '/app/src/Button.tsx', lineNumber: 7, columnNumber: 2 } getPathToSource(source) // '/app/src/Button.tsx:7:2' getPathToSource(source, (p) => p.replace('/app/', '')) // 'src/Button.tsx:7:2' ``` -------------------------------- ### getPropsForInstance usage example Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Usage example for getPropsForInstance. Imports the function and calls it with a fiber, producing a compact object of scalar props (e.g., { children: 'Submit', disabled: false }). The result is rendered by ContextMenu as {key} elements inside the menu item's . ```javascript import { getPropsForInstance } from './getPropsForInstance.js' const props = getPropsForInstance(fiber) // e.g. { children: 'Submit', disabled: false } ``` -------------------------------- ### Docusaurus Babel configuration Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Install the Babel plugin and configure it in babel.config.js to enable JSX source for development. ```shell npm install @babel/plugin-transform-react-jsx-source ``` ```js module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], plugins: [ ...(process.env.BABEL_ENV === 'development' ? ['@babel/plugin-transform-react-jsx-source'] : []), ], }; ``` -------------------------------- ### getUrl usage example Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Demonstrates `getUrl` with two editor schemes and path styles. The first uses a leading slash, the second does not. The result is meant to be passed to `window.location.assign(url)`; there is no validation of the scheme or path. ```js import { getUrl } from './getUrl.js' getUrl({ editor: 'vscode', pathToSource: '/app/src/App.tsx:9:3' }) // 'vscode://file/app/src/App.tsx:9:3' getUrl({ editor: 'cursor', pathToSource: 'src/App.tsx:9:3' }) // 'cursor://file/src/App.tsx:9:3' ``` -------------------------------- ### Usage example for getReactInstanceForElement Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Shows how to call `getReactInstanceForElement` with a DOM element and access fiber properties like `_debugOwner`, `_debugSource`, `tag`, and `memoizedProps` when a fiber is found. ```js import { getReactInstanceForElement } from './getReactInstanceForElement.js' const fiber = getReactInstanceForElement(document.querySelector('[data-foo]')) if (fiber) { // fiber._debugOwner, fiber._debugSource, fiber.tag, fiber.memoizedProps are available } ``` -------------------------------- ### getReactInstancesForElement usage example Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Shows how to call getReactInstancesForElement with a DOM element, such as a button, to obtain its fiber owner chain. ```js import { getReactInstancesForElement } from './getReactInstancesForElement.js' const chain = getReactInstancesForElement(document.querySelector('button')) ``` -------------------------------- ### getUrl signature Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Builds an `editor://` deep-link URL from an editor scheme and a `file:line:column` path. If `pathToSource` starts with `/`, it returns `${editor}://file${pathToSource}`; otherwise it inserts a slash after `file`. The leading-slash branch works around microsoft/vscode#197319. ```js export function getUrl({ editor, pathToSource }) { // Fix https://github.com/microsoft/vscode/issues/197319 if (pathToSource[0] === '/') { return `${editor}://file${pathToSource}` } return `${editor}://file/${pathToSource}` } ``` -------------------------------- ### getReactInstancesForElement function implementation Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Walks the fiber owner chain starting from the element's own fiber, collecting each _debugOwner until the chain ends. Returns a de-duplicated array in owner-chain order (own fiber first), or an empty array when the element resolves to no fiber. ```js export function getReactInstancesForElement( /** @type {HTMLElement} */ element ) { /** @type {Set} */ const instances = new Set() let instance = getReactInstanceForElement(element) while (instance) { instances.add(instance) instance = instance._debugOwner } return Array.from(instances) } ``` -------------------------------- ### Go to project directory Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Change into the project directory. ```shell cd click-to-component ``` -------------------------------- ### Clone the project Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Clone the click-to-component repository using GitHub CLI. ```shell gh repo clone ericclemmons/click-to-component ``` -------------------------------- ### getReactInstanceForElement implementation Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Resolves the nearest React fiber for a DOM element. Prefers the React DevTools hook, then `_reactRootContainer`, then element own properties starting with `__reactInternalInstance$` or `__reactFiber`. Returns `undefined` when nothing matches. ```js export function getReactInstanceForElement(element) { // Prefer React DevTools, which has direct access to `react-dom` for mapping `element` <=> Fiber if ('__REACT_DEVTOOLS_GLOBAL_HOOK__' in window) { // @ts-expect-error - TS2339 - Property '__REACT_DEVTOOLS_GLOBAL_HOOK__' does not exist on type 'Window & typeof globalThis'. const { renderers } = window.__REACT_DEVTOOLS_GLOBAL_HOOK__ for (const renderer of renderers.values()) { try { const fiber = renderer.findFiberByHostInstance(element) if (fiber) { return fiber } } catch (e) { // If React is mid-render, references to previous nodes may disappear during the click events // (This is especially true for interactive elements, like menus) } } } if ('_reactRootContainer' in element) { // @ts-expect-error - TS2339 - Property '_reactRootContainer' does not exist on type 'HTMLElement'. return element._reactRootContainer._internalRoot.current.child } // eslint-disable-next-line guard-for-in for (const key in element) { // Pre-Fiber access React internals if (key.startsWith('__reactInternalInstance$')) { return element[key] } // Fiber access to React internals if (key.startsWith('__reactFiber')) { return element[key] } } } ``` -------------------------------- ### Basic usage in Create React App Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md Basic usage in a Create React App, mirroring apps/cra/src/index.js. Renders ClickToComponent inside React.StrictMode alongside the App component. ```jsx import { ClickToComponent } from 'click-to-react-component'; import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( ); ``` -------------------------------- ### Use ClickToComponent with editor="vscode" Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ContextMenu.md Used implicitly through ClickToComponent, which mounts ContextMenu inside a FloatingPortal. Hold Alt and right-click an element to see the menu listing parent components with their props and line:column; click an entry to open it in the editor. ```jsx import { ClickToComponent } from 'click-to-react-component' function App() { return ( <> ) } ``` -------------------------------- ### CSS custom properties for ClickToComponent.js (click-to-component-style) Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/configuration.md Default styles injected by ClickToComponent.js (keyed click-to-component-style). The first rule restores pointer-events for descendants of [data-click-to-component]; the second sets cursor and outline on the target element using custom properties. The override example shows how to customize the cursor and outline by setting the custom properties on :root. ```css [data-click-to-component] * { pointer-events: auto !important; } [data-click-to-component-target] { cursor: var(--click-to-component-cursor, context-menu) !important; outline: auto 1px; outline: var( --click-to-component-outline, -webkit-focus-ring-color auto 1px ) !important; } ``` ```css :root { --click-to-component-cursor: crosshair; --click-to-component-outline: 2px solid rebeccapurple; } ``` -------------------------------- ### Click navigation URL construction Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md Builds the editor URL from the resolved source path and the configured editor and pathModifier. This is used during click navigation and when the context menu closes. ```javascript const path = getPathToSource(source, pathModifier) const url = getUrl({ editor, pathToSource: path }) ``` -------------------------------- ### getSourceForElement function source Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Exact source of getSourceForElement and its helper getFirstParentElementWithSource. The function resolves a source for an HTMLElement by getting its React instance and source, falling back to the nearest ancestor with a source. If no parent remains, it throws an Error('No parent found for element'). Note the recursion in the else branch re-passes the original element, not the parent, which may cause repeated inspection of the same parent and eventual throw. ```javascript export function getSourceForElement( /** * @type {HTMLElement} */ element ) { const instance = getReactInstanceForElement(element) const source = getSourceForInstance(instance) if (source) return source const fallbackSource = getFirstParentElementWithSource(element) return fallbackSource } function getFirstParentElementWithSource(element) { const parentElement = element.parentElement if (parentElement === null) { console.warn("Couldn't find a React instance for the element", element) throw new Error('No parent found for element') } const instance = getReactInstanceForElement(parentElement) const source = getSourceForInstance(instance) if (source) return source else return getFirstParentElementWithSource(element) } ``` -------------------------------- ### Compute menu item data for each React instance Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ContextMenu.md For each React instance of the target that has a source, these values are computed to render a menu item button. The path is used as the submit value (file:line:column string). ```javascript const name = getDisplayNameForInstance(instance) const source = getSourceForInstance(instance) const path = getPathToSource(source, pathModifier) const props = getPropsForInstance(instance) ``` -------------------------------- ### Docusaurus Root component Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Customize the Root component in src/theme/Root.js to include ClickToComponent. ```js import { ClickToComponent } from 'click-to-react-component'; import React from 'react'; // Default implementation, that you can customize export default function Root({ children }) { return ( <> {children} ); } ``` -------------------------------- ### Usage in Create React App Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Add the ClickToComponent component to the root render in a Create React App project. The diff shows adding the import and the component inside React.StrictMode. ```diff +import { ClickToComponent } from 'click-to-react-component'; import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; @@ -8,7 +7,6 @@ import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + ); ``` -------------------------------- ### Import ClickToComponent Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md Import the ClickToComponent component from the click-to-react-component package. This is the primary way to use the component in a React application. ```js import { ClickToComponent } from 'click-to-react-component' ``` -------------------------------- ### onContextMenu handler for Alt+contextmenu Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ContextMenu.md Document-level contextmenu listener registered via useEffect. Only Alt+contextmenu opens the menu; the anchor is a zero-size rect at e.clientX/e.clientY. The captured HTMLElement becomes the target whose React instances are listed. ```javascript function onContextMenu(e) { if (!e.altKey) { return } e.preventDefault() mergedReferenceRef({ getBoundingClientRect() { /* cursor point rect */ } }) setOpen(true) if (e.target instanceof HTMLElement) setTarget(e.target) } ``` -------------------------------- ### getSourceForInstance - extract source from fiber Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/source-helpers.md Exact signature of getSourceForInstance from packages/click-to-react-component/src/getSourceForInstance.js (line 9). Returns undefined if instance._debugSource is falsy; otherwise returns a normalized copy with columnNumber and lineNumber defaulting to 1. Uses @ts-ignore because columnNumber is absent from the published Source type. ```js /** * @param {Fiber} instance */ export function getSourceForInstance(instance) { if (!instance._debugSource) { return } const { // It _does_ exist! // @ts-ignore Property 'columnNumber' does not exist on type 'Source'.ts(2339) columnNumber = 1, fileName, lineNumber = 1, } = instance._debugSource return { columnNumber, fileName, lineNumber } } ``` -------------------------------- ### Set editor to vscode-insiders Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Change the editor prop to 'vscode-insiders' to use Visual Studio Code Insiders. ```diff - + ``` -------------------------------- ### Usage in Next.js Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Add the ClickToComponent component to the custom App component in a Next.js project. The diff shows adding the import and the component inside the fragment. ```diff +import { ClickToComponent } from 'click-to-react-component' import type { AppProps } from 'next/app' import '../styles/globals.css' function MyApp({ Component, pageProps }: AppProps) { return ( <> + ) ``` -------------------------------- ### getPropsForInstance implementation Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/react-instance-helpers.md Exact implementation of getPropsForInstance from packages/click-to-react-component/src/getPropsForInstance.js (line 9). It reads instance.memoizedProps and instance.type.defaultProps (guarded with optional chaining), dropping the 'key' prop and any prop equal to its default, and keeping only scalar primitives (string, number, boolean, symbol) and their wrapper instances. Returns a new object typed as Fiber['pendingProps']. ```javascript export function getPropsForInstance(instance) { /** * @type {Fiber['pendingProps']} */ const props = {} Object.entries(instance.memoizedProps).forEach(([key, value]) => { const type = typeof value // Ignore some values, even if they're scalar, because they're not unique enough if (['key'].includes(key) || value === instance.type.defaultProps?.[key]) { return } // Scalar values if ( ['string', 'number', 'boolean', 'symbol'].includes(type) || value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Symbol ) { props[key] = value } }) return props } ``` -------------------------------- ### ClickToComponent Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md The ClickToComponent component is the main export of the click-to-react-component package. It renders a development-only overlay that watches window-level mouse/keyboard events and navigates the browser to an editor URL pointing at the source of the React component responsible for the hovered element. In production builds, it renders nothing. ```APIDOC ## ClickToComponent ### Description Renders a development-only overlay that watches window-level mouse/keyboard events and navigates the browser to an editor URL pointing at the source of the React component responsible for the hovered element. ### Import ```js import { ClickToComponent } from 'click-to-react-component' ``` ### Behavior | Condition | Behavior | |---|---| | `process.env.NODE_ENV === 'development'` | Functional component that renders the overlay | | any other value (e.g. `production`) | No-op returning `null` | ### Production Behavior In production builds the export renders nothing, and because the package declares `"sideEffects": false`, bundlers can tree-shake the entire implementation. ### Source Files - Root re-export: `packages/click-to-react-component/src/index.js` (lines 1–4) - Implementation: `packages/click-to-react-component/src/ClickToComponent.js` (line 25) ``` -------------------------------- ### Basic usage in Next.js pages router Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md Basic usage in a Next.js pages router, mirroring apps/next/pages/_app.tsx. Passes the editor from the NEXT_PUBLIC_CTC_EDITOR environment variable. ```tsx import { ClickToComponent } from 'click-to-react-component' import type { AppProps } from 'next/app' function MyApp({ Component, pageProps }: AppProps) { return ( <> ) } ``` -------------------------------- ### Integrate pathModifier with editor="vscode-insiders" Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ContextMenu.md Integrates pathModifier to convert absolute paths to repo-relative ones. The modified string becomes the menu button value and is passed to onClose/navigation, so it must be a filesystem path acceptable to getUrl; the empty string would produce a malformed URL (no validation is performed). ```tsx import { ClickToComponent } from 'click-to-react-component' // Relative-ize paths coming from absolute monorepo roots. const toRepoRelative = (path: string) => path.replace(`${process.cwd()}/`, '') function App() { return } ``` -------------------------------- ### ContextMenu component signature (ContextMenu.js line 33) Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ContextMenu.md Exact source of the ContextMenu component, created with React.forwardRef. The ref is merged with the floating element's reference ref via react-merge-refs inside a useMemo and used to position the menu at the cursor on Alt+contextmenu. ```javascript // @ts-expect-error export const ContextMenu = React.forwardRef((props, ref) => { // @ts-expect-error const { onClose, pathModifier } = props ``` -------------------------------- ### Complete type-declaration file (types.d.ts) Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/types.md Complete type-declaration file for click-to-react-component. Defines Editor, PathModifier, ClickToComponentProps, Coords, Target, and ContextMenuProps, plus the ClickToComponent function overload. The Editor type includes 'vscode', 'vscode-insiders', 'cursor', and a string union widening for custom editors. ```ts import * as React from 'react' export type Editor = 'vscode' | 'vscode-insiders' | 'cursor' | (string & {}) export type PathModifier = (path: string) => string declare function ClickToComponent(props: ClickToComponentProps): React.ReactElement export type ClickToComponentProps = { editor?: Editor pathModifier?: PathModifier } export type Coords = [MouseEvent['pageX'], MouseEvent['pageY']] export type Target = HTMLElement export type ContextMenuProps = { onClose?: () => void pathModifier?: PathModifier } ``` -------------------------------- ### Usage in Vite Source: https://github.com/ericclemmons/click-to-component/blob/main/packages/click-to-react-component/README.md Add the ClickToComponent component to the root render in a Vite project. The diff shows adding the import and the component inside React.StrictMode. ```diff +import { ClickToComponent } from "click-to-react-component"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( + ); ``` -------------------------------- ### Non-trivial usage with explicit editor and pathModifier Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md Non-trivial usage with an explicit editor ("cursor") and a pathModifier that converts absolute paths to workspace-relative before opening the editor. ```tsx import { ClickToComponent } from 'click-to-react-component' // Convert absolute paths to workspace-relative before opening the editor. const stripWorkspacePrefix = (path: string) => path.replace(/^\/workspace\//, '') function App() { return ( <> ) } ``` -------------------------------- ### ClickToComponent return JSX (htm template) Source: https://github.com/ericclemmons/click-to-component/blob/main/_autodocs/api-reference/ClickToComponent.md The JSX structure returned by the component, using the htm template helper. It renders a style element and a FloatingPortal containing a ContextMenu component. ```javascript return html` <${FloatingPortal} key="click-to-component-portal"> ${html`<${ContextMenu} key="click-to-component-contextmenu" onClose=${onClose} pathModifier=${pathModifier} />`} ` ```