diff --git a/.changeset/icy-states-chew.md b/.changeset/icy-states-chew.md
new file mode 100644
index 00000000..5e445e0c
--- /dev/null
+++ b/.changeset/icy-states-chew.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/devtools': minor
+---
+
+Adds copy path feature and config to devtools source inspector
diff --git a/docs/source-inspector.md b/docs/source-inspector.md
index e49535db..44b26946 100644
--- a/docs/source-inspector.md
+++ b/docs/source-inspector.md
@@ -67,6 +67,25 @@ export default {
Both `files` and `components` accept arrays of strings (exact match) or RegExp patterns.
+## Click Action
+
+By default, clicking an inspected element opens the file in your editor. You can change this to copy the source path to the clipboard instead using the `sourceAction` setting:
+
+```ts
+
+```
+
+| Value | Behavior |
+| --- | --- |
+| `"ide-warp"` | Opens the file in your editor at the exact line (default) |
+| `"copy-path"` | Copies the `filepath:line:column` string to the clipboard |
+
+This is useful in environments where the Vite dev server cannot reach your editor, or when you want to paste the path elsewhere.
+
## Editor Configuration
Most popular editors work out of the box via the `launch-editor` package. Supported editors include VS Code, WebStorm, Sublime Text, Atom, and more ([full list](https://github.com/yyx990803/launch-editor?tab=readme-ov-file#supported-editors)).
diff --git a/examples/react/basic/src/setup.tsx b/examples/react/basic/src/setup.tsx
index a2839d99..d164bd3b 100644
--- a/examples/react/basic/src/setup.tsx
+++ b/examples/react/basic/src/setup.tsx
@@ -59,9 +59,7 @@ export default function DevtoolsExample() {
return (
<>
{
const downList = useKeyDownList()
+ const [disabledAfterClick, setDisabledAfterClick] = createSignal(false)
+
const isHighlightingKeysHeld = createMemo(() => {
return isHotkeyCombinationPressed(downList(), settings().inspectHotkey)
})
createEffect(() => {
if (!isHighlightingKeysHeld()) {
+ setDisabledAfterClick(false)
+ }
+ })
+
+ const isActive = createMemo(
+ () => isHighlightingKeysHeld() && !disabledAfterClick(),
+ )
+
+ createEffect(() => {
+ if (isActive()) {
+ document.body.style.cursor = 'pointer'
+ } else {
+ document.body.style.cursor = ''
+ }
+ })
+
+ createEffect(() => {
+ if (!isActive()) {
resetHighlight()
return
}
@@ -78,6 +98,12 @@ export const SourceInspector = () => {
window.getSelection()?.removeAllRanges()
e.preventDefault()
e.stopPropagation()
+ setDisabledAfterClick(true)
+
+ if (settings().sourceAction === 'copy-path') {
+ navigator.clipboard.writeText(highlightState.dataSource).catch(() => {})
+ return
+ }
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const baseUrl = new URL(import.meta.env?.BASE_URL ?? '/', location.origin)
diff --git a/packages/devtools/src/context/devtools-store.ts b/packages/devtools/src/context/devtools-store.ts
index e4e908de..be9cdd87 100644
--- a/packages/devtools/src/context/devtools-store.ts
+++ b/packages/devtools/src/context/devtools-store.ts
@@ -74,6 +74,13 @@ export type DevtoolsStore = {
*/
theme: TanStackDevtoolsTheme
+ /**
+ * The action to perform when clicking a source-inspected element
+ * - "ide-warp": open the file in the IDE via the Vite middleware
+ * - "copy-path": copy the file path to the clipboard
+ * @default "ide-warp"
+ */
+ sourceAction: 'ide-warp' | 'copy-path'
/**
* Whether the trigger should be completely hidden or not (you can still open with the hotkey)
*/
@@ -110,6 +117,7 @@ export const initialState: DevtoolsStore = {
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
+ sourceAction: 'ide-warp',
triggerHidden: false,
customTrigger: undefined,
},