vite-plugin-source-locator 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,11 +42,16 @@ No `main.tsx` wiring required. The plugin auto-injects the client overlay in dev
42
42
 
43
43
  ## Pick Mode
44
44
 
45
- 1. Click the badge (bottom-right): **Source Locator (auto) — click to pick**
45
+ 1. Click the badge (bottom-right): **Locator**
46
46
  2. Hover elements — blue highlight + file paths in tooltip
47
47
  3. Click to open source — cycles **TSX → CSS → TSX** when both exist
48
48
  4. **Esc** — cancel pick mode
49
- 5. **Shift+L** — cycle IDE (session only, resets on refresh)
49
+
50
+ | Shortcut | Action |
51
+ |----------|--------|
52
+ | Click | Open TSX (or cycle TSX/CSS) |
53
+ | **Shift+C** | Open CSS/style directly |
54
+ | Esc | Cancel pick |
50
55
 
51
56
  ## Exports
52
57
 
@@ -89,7 +94,6 @@ import { initSourceLocator } from 'vite-plugin-source-locator/client'
89
94
  initSourceLocator({
90
95
  endpoint: '/__open-in-editor',
91
96
  attribute: 'data-source',
92
- ides: ['auto', 'cursor', 'vscode', 'webstorm'],
93
97
  theme: {
94
98
  background: '#ffffff',
95
99
  text: '#000000',
@@ -132,7 +136,6 @@ import { initSourceLocator } from 'vite-plugin-source-locator/client'
132
136
  initSourceLocator({
133
137
  endpoint: '/__open-in-editor',
134
138
  attribute: 'data-source',
135
- ides: ['auto', 'cursor', 'vscode', 'webstorm'],
136
139
  theme: 'blue',
137
140
  })
138
141
  ```
@@ -157,12 +160,9 @@ sourceLocator()
157
160
 
158
161
  // VS Code only
159
162
  sourceLocator({ ides: ['vscode'] })
160
-
161
- // auto + manual override with Shift+L (session only)
162
- sourceLocator({ ides: ['auto', 'vscode'] })
163
163
  ```
164
164
 
165
- The first entry in `ides` is the default. Shift+L cycles through the list in memory (resets on page refresh).
165
+ The `ides` option controls which editors the server may open. The client always uses auto-detection.
166
166
 
167
167
  ### Explicit editor
168
168
 
@@ -1,8 +1,7 @@
1
- import type { LocatorIde, LocatorThemeInput } from '../shared/index.js';
1
+ import type { LocatorThemeInput } from '../shared/index.js';
2
2
  export type ClientConfig = {
3
3
  endpoint: string;
4
4
  attribute: string;
5
- ides: LocatorIde[];
6
5
  theme?: LocatorThemeInput;
7
6
  };
8
7
  export declare function startPickController(root: ShadowRoot, host: Element, config: ClientConfig): () => void;
@@ -1,4 +1,4 @@
1
- import { nextClickTarget, nextIde, parseSourceLocation, resolveTheme } from '../shared/index.js';
1
+ import { DEFAULT_IDE, nextClickTarget, parseSourceLocation, resolveTheme } from '../shared/index.js';
2
2
  import { findCssSource } from './css-source.js';
3
3
  import { createLocatorOverlayUi } from './overlay.js';
4
4
  const EMPTY_SOURCES = Object.freeze({
@@ -12,13 +12,13 @@ function getSourceEl(target, attribute, host) {
12
12
  const el = target.closest(`[${attribute}]`);
13
13
  return el instanceof HTMLElement ? el : null;
14
14
  }
15
- async function openSourceInEditor(source, config, activeIde) {
15
+ async function openSourceInEditor(source, config) {
16
16
  const loc = parseSourceLocation(source);
17
17
  const params = new URLSearchParams({
18
18
  file: loc.file,
19
19
  line: loc.line,
20
20
  col: loc.col,
21
- ide: activeIde,
21
+ ide: DEFAULT_IDE,
22
22
  });
23
23
  await fetch(`${config.endpoint}?${params.toString()}`);
24
24
  }
@@ -33,8 +33,7 @@ function resolveOpenSource(sources) {
33
33
  export function startPickController(root, host, config) {
34
34
  let pickMode = false;
35
35
  let sources = EMPTY_SOURCES;
36
- let activeIde = config.ides[0] ?? 'auto';
37
- const ui = createLocatorOverlayUi(root, () => setPickMode(!pickMode), resolveTheme(config.theme), () => activeIde);
36
+ const ui = createLocatorOverlayUi(root, () => setPickMode(!pickMode), resolveTheme(config.theme));
38
37
  function setPickMode(active) {
39
38
  pickMode = active;
40
39
  ui.setPickActive(active);
@@ -52,7 +51,7 @@ export function startPickController(root, host, config) {
52
51
  return;
53
52
  }
54
53
  syncSources(el);
55
- ui.showSourceTooltip(el, sources.tsx, sources.css, sources.clickTarget, x, y);
54
+ ui.showSourceTooltip(el, sources.tsx, sources.css, x, y);
56
55
  };
57
56
  const onMouseMove = (e) => {
58
57
  if (!pickMode) {
@@ -61,17 +60,23 @@ export function startPickController(root, host, config) {
61
60
  }
62
61
  updateHover(e.target, e.clientX, e.clientY);
63
62
  };
64
- const onKeyDown = (e) => {
63
+ const onKeyDown = async (e) => {
65
64
  if (e.key === 'Escape' && pickMode) {
66
65
  setPickMode(false);
67
66
  return;
68
67
  }
69
- if (!e.shiftKey || e.key !== 'L')
68
+ if (e.shiftKey && e.key === 'C' && pickMode) {
69
+ const el = ui.getActiveEl();
70
+ if (!(el instanceof HTMLElement))
71
+ return;
72
+ syncSources(el);
73
+ if (!sources.css) {
74
+ ui.flashMessage('No CSS for this element');
75
+ return;
76
+ }
77
+ await openSourceInEditor(sources.css, config);
70
78
  return;
71
- activeIde = nextIde(activeIde, config.ides);
72
- if (!pickMode)
73
- ui.refreshBadgeLabel();
74
- ui.flashMessage(`IDE: ${activeIde}`);
79
+ }
75
80
  };
76
81
  const onClick = async (e) => {
77
82
  if (!pickMode || host.contains(e.target))
@@ -86,9 +91,9 @@ export function startPickController(root, host, config) {
86
91
  ui.flashMessage('No source for this element');
87
92
  return;
88
93
  }
89
- await openSourceInEditor(openSource, config, activeIde);
94
+ await openSourceInEditor(openSource, config);
90
95
  sources = { ...sources, clickTarget: nextClickTarget(sources.clickTarget, !!sources.css) };
91
- ui.showSourceTooltip(el, sources.tsx, sources.css, sources.clickTarget, e.clientX, e.clientY);
96
+ ui.showSourceTooltip(el, sources.tsx, sources.css, e.clientX, e.clientY);
92
97
  };
93
98
  document.addEventListener('keydown', onKeyDown);
94
99
  document.addEventListener('mousemove', onMouseMove);
@@ -1,10 +1,12 @@
1
1
  import { startPickController } from './controller.js';
2
+ import { HOST_LAYOUT } from './overlay-styles.js';
2
3
  const HOST_ID = 'source-locator-host';
3
4
  export function initSourceLocator(config) {
4
5
  if (window.__sourceLocator || document.getElementById(HOST_ID))
5
6
  return;
6
7
  const host = document.createElement('div');
7
8
  host.id = HOST_ID;
9
+ Object.assign(host.style, HOST_LAYOUT);
8
10
  const root = host.attachShadow({ mode: 'open' });
9
11
  document.body.appendChild(host);
10
12
  const disposeController = startPickController(root, host, config);
@@ -3,18 +3,25 @@ export declare const UI_IDS: {
3
3
  readonly tooltip: "source-locator-tooltip";
4
4
  readonly highlight: "source-locator-highlight";
5
5
  };
6
+ export declare const HOST_LAYOUT: {
7
+ readonly position: "fixed";
8
+ readonly inset: "0";
9
+ readonly zIndex: "99999";
10
+ readonly pointerEvents: "none";
11
+ };
6
12
  export declare const LAYOUT: {
7
13
  readonly badge: {
8
14
  readonly position: "fixed";
9
15
  readonly bottom: "12px";
10
16
  readonly right: "12px";
11
- readonly padding: "8px 12px";
12
- readonly borderRadius: "999px";
13
- readonly fontSize: "11px";
17
+ readonly padding: "10px 14px";
18
+ readonly borderRadius: "8px";
19
+ readonly fontSize: "12px";
14
20
  readonly fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace";
15
21
  readonly zIndex: "99999";
16
22
  readonly cursor: "pointer";
17
- readonly boxShadow: "0 4px 16px rgba(0, 0, 0, 0.35)";
23
+ readonly pointerEvents: "auto";
24
+ readonly boxShadow: "0 2px 12px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.08)";
18
25
  };
19
26
  readonly tooltip: {
20
27
  readonly position: "fixed";
@@ -3,18 +3,25 @@ export const UI_IDS = {
3
3
  tooltip: 'source-locator-tooltip',
4
4
  highlight: 'source-locator-highlight',
5
5
  };
6
+ export const HOST_LAYOUT = {
7
+ position: 'fixed',
8
+ inset: '0',
9
+ zIndex: '99999',
10
+ pointerEvents: 'none',
11
+ };
6
12
  export const LAYOUT = {
7
13
  badge: {
8
14
  position: 'fixed',
9
15
  bottom: '12px',
10
16
  right: '12px',
11
- padding: '8px 12px',
12
- borderRadius: '999px',
13
- fontSize: '11px',
17
+ padding: '10px 14px',
18
+ borderRadius: '8px',
19
+ fontSize: '12px',
14
20
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
15
21
  zIndex: '99999',
16
22
  cursor: 'pointer',
17
- boxShadow: '0 4px 16px rgba(0, 0, 0, 0.35)',
23
+ pointerEvents: 'auto',
24
+ boxShadow: '0 2px 12px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.08)',
18
25
  },
19
26
  tooltip: {
20
27
  position: 'fixed',
@@ -1,9 +1,8 @@
1
- import type { ClickTarget, LocatorIde, LocatorTheme } from '../shared/index.js';
2
- export declare function createLocatorOverlayUi(root: ShadowRoot, onTogglePick: () => void, theme: LocatorTheme, getActiveIde: () => LocatorIde): {
1
+ import type { LocatorTheme } from '../shared/index.js';
2
+ export declare function createLocatorOverlayUi(root: ShadowRoot, onTogglePick: () => void, theme: LocatorTheme): {
3
3
  mountBadge: () => void;
4
4
  setPickActive: (active: boolean) => void;
5
- refreshBadgeLabel: () => void;
6
- showSourceTooltip: (el: Element, tsxSource: string | undefined, cssSource: string | undefined, clickTarget: ClickTarget, x: number, y: number) => void;
5
+ showSourceTooltip: (el: Element, tsxSource: string | undefined, cssSource: string | undefined, x: number, y: number) => void;
7
6
  flashMessage: (text: string) => void;
8
7
  removeTooltip: () => void;
9
8
  getActiveEl: () => Element | null;
@@ -6,7 +6,7 @@ const TOOLTIP_CURSOR_OFFSET = 16;
6
6
  const FLASH_DURATION_MS = 1500;
7
7
  const FLASH_HORIZONTAL_OFFSET = 80;
8
8
  const FLASH_BOTTOM_OFFSET = 80;
9
- export function createLocatorOverlayUi(root, onTogglePick, theme, getActiveIde) {
9
+ export function createLocatorOverlayUi(root, onTogglePick, theme) {
10
10
  let activeEl = null;
11
11
  let flashTimeout = null;
12
12
  let badgeEl = null;
@@ -49,8 +49,8 @@ export function createLocatorOverlayUi(root, onTogglePick, theme, getActiveIde)
49
49
  if (el)
50
50
  showHighlight(el);
51
51
  };
52
- const showSourceTooltip = (el, tsxSource, cssSource, clickTarget, x, y) => {
53
- showTooltip(buildTooltipText(tsxSource, cssSource, clickTarget), el, x, y);
52
+ const showSourceTooltip = (el, tsxSource, cssSource, x, y) => {
53
+ showTooltip(buildTooltipText(tsxSource, cssSource), el, x, y);
54
54
  };
55
55
  const flashMessage = (text) => {
56
56
  if (flashTimeout)
@@ -65,25 +65,22 @@ export function createLocatorOverlayUi(root, onTogglePick, theme, getActiveIde)
65
65
  return;
66
66
  badgeEl.style.background = active ? theme.badgeActiveBackground : theme.badgeBackground;
67
67
  badgeEl.style.color = active ? theme.badgeActiveText : theme.badgeText;
68
+ badgeEl.style.border = `1px solid ${active ? theme.badgeActiveBorder : theme.badgeBorder}`;
68
69
  };
69
70
  const setPickActive = (active) => {
70
71
  document.body.style.cursor = active ? 'crosshair' : '';
71
72
  if (!badgeEl)
72
73
  return;
73
- badgeEl.textContent = badgeLabel(active, getActiveIde());
74
+ badgeEl.textContent = badgeLabel(active);
74
75
  applyBadgeColors(active);
75
76
  if (!active)
76
77
  removeTooltip();
77
78
  };
78
- const refreshBadgeLabel = () => {
79
- if (badgeEl)
80
- badgeEl.textContent = badgeLabel(false, getActiveIde());
81
- };
82
79
  const mountBadge = () => {
83
80
  badgeEl = document.createElement('button');
84
81
  badgeEl.id = UI_IDS.badge;
85
82
  badgeEl.type = 'button';
86
- badgeEl.textContent = badgeLabel(false, getActiveIde());
83
+ badgeEl.textContent = badgeLabel(false);
87
84
  Object.assign(badgeEl.style, LAYOUT.badge, {
88
85
  background: theme.badgeBackground,
89
86
  color: theme.badgeText,
@@ -98,7 +95,6 @@ export function createLocatorOverlayUi(root, onTogglePick, theme, getActiveIde)
98
95
  return {
99
96
  mountBadge,
100
97
  setPickActive,
101
- refreshBadgeLabel,
102
98
  showSourceTooltip,
103
99
  flashMessage,
104
100
  removeTooltip,
@@ -1,2 +1 @@
1
- import type { LocatorIde } from '../shared/index.js';
2
- export declare function badgeLabel(picking: boolean, activeIde: LocatorIde): string;
1
+ export declare function badgeLabel(picking: boolean): string;
@@ -1,7 +1,5 @@
1
- const BADGE_PICKING_HINT = 'Esc to cancel';
2
- const BADGE_IDLE_HINT = 'click to pick';
3
- export function badgeLabel(picking, activeIde) {
1
+ export function badgeLabel(picking) {
4
2
  if (picking)
5
- return `Pick element (${activeIde}) ${BADGE_PICKING_HINT}`;
6
- return `Source Locator (${activeIde}) — ${BADGE_IDLE_HINT}`;
3
+ return 'PickingEsc';
4
+ return 'Locator';
7
5
  }
@@ -1,2 +1 @@
1
- import type { ClickTarget } from '../shared/index.js';
2
- export declare function buildTooltipText(tsxSource: string | undefined, cssSource: string | undefined, clickTarget: ClickTarget): string;
1
+ export declare function buildTooltipText(tsxSource: string | undefined, cssSource: string | undefined): string;
@@ -1,9 +1,5 @@
1
1
  import { parseSourceLocation } from '../shared/index.js';
2
2
  const DEFAULT_LINE = '1';
3
- const CLICK_PROMPT_ORDER = {
4
- tsx: ['TSX', 'CSS'],
5
- css: ['CSS', 'TSX'],
6
- };
7
3
  function formatSourceLabel(source, prefix) {
8
4
  const { file, line } = parseSourceLocation(source);
9
5
  const name = file.split('/').pop() ?? file;
@@ -12,7 +8,7 @@ function formatSourceLabel(source, prefix) {
12
8
  return `${prefix}: ${label}`;
13
9
  return label;
14
10
  }
15
- export function buildTooltipText(tsxSource, cssSource, clickTarget) {
11
+ export function buildTooltipText(tsxSource, cssSource) {
16
12
  const lines = [];
17
13
  if (tsxSource)
18
14
  lines.push(formatSourceLabel(tsxSource, 'TSX'));
@@ -22,7 +18,6 @@ export function buildTooltipText(tsxSource, cssSource, clickTarget) {
22
18
  lines.push('Click → open TSX');
23
19
  return lines.join('\n');
24
20
  }
25
- const [first, second] = CLICK_PROMPT_ORDER[clickTarget];
26
- lines.push(`Click → open ${first}`, `Click again → open ${second}`);
21
+ lines.push('Click → open TSX', 'Shift+C CSS');
27
22
  return lines.join('\n');
28
23
  }
@@ -13,7 +13,6 @@ export declare const DEFAULT_IDE: LocatorIde;
13
13
  export declare const IDE_ORDER: LocatorIde[];
14
14
  export declare function isLocatorIde(value: string): value is LocatorIde;
15
15
  export declare function resolveIde(value: string, allowed: LocatorIde[]): LocatorIde;
16
- export declare function nextIde(current: LocatorIde, order?: LocatorIde[]): LocatorIde;
17
16
  export declare function parseSourceLocation(raw: string): SourceLocation;
18
17
  export declare function formatSourceLocation(loc: SourceLocation): string;
19
18
  export declare function nextClickTarget(current: ClickTarget, hasCss: boolean): ClickTarget;
@@ -14,11 +14,6 @@ export function resolveIde(value, allowed) {
14
14
  return fallback;
15
15
  return value;
16
16
  }
17
- export function nextIde(current, order = IDE_ORDER) {
18
- const index = order.indexOf(current);
19
- const nextIndex = (index + 1) % order.length;
20
- return order[nextIndex] ?? DEFAULT_IDE;
21
- }
22
17
  export function parseSourceLocation(raw) {
23
18
  const parts = raw.split(':');
24
19
  if (parts.length < 3)
@@ -11,6 +11,7 @@ export type LocatorTheme = {
11
11
  badgeActiveBackground: string;
12
12
  badgeActiveText: string;
13
13
  badgeBorder: string;
14
+ badgeActiveBorder: string;
14
15
  tooltipBackground: string;
15
16
  tooltipText: string;
16
17
  tooltipBorder: string;
@@ -1,9 +1,10 @@
1
1
  const PRESET_COLORS = {
2
2
  default: { background: '#0f172a', text: '#f8fafc', accent: '#38bdf8' },
3
- light: { background: '#ffffff', text: '#0f172a', accent: '#2563eb' },
3
+ light: { background: '#ffffff', text: '#111827', accent: '#2563eb' },
4
4
  dark: { background: '#000000', text: '#ffffff', accent: '#a3a3a3' },
5
5
  blue: { background: '#1e3a8a', text: '#eff6ff', accent: '#60a5fa' },
6
6
  };
7
+ const LIGHT_BADGE_BORDER = '#d1d5db';
7
8
  const HEX_COLOR_LENGTH = 7;
8
9
  const RGB_CHANNEL_MAX = 255;
9
10
  const HIGHLIGHT_ALPHA = 0.12;
@@ -16,14 +17,31 @@ function withAlpha(hex, alpha) {
16
17
  .padStart(2, '0');
17
18
  return `${hex}${channel}`;
18
19
  }
19
- function buildTheme(colors) {
20
+ function buildTheme(colors, preset) {
20
21
  const { background, text, accent } = colors;
22
+ if (preset === 'light') {
23
+ return {
24
+ badgeBackground: background,
25
+ badgeText: text,
26
+ badgeActiveBackground: background,
27
+ badgeActiveText: accent,
28
+ badgeBorder: LIGHT_BADGE_BORDER,
29
+ badgeActiveBorder: accent,
30
+ tooltipBackground: background,
31
+ tooltipText: text,
32
+ tooltipBorder: LIGHT_BADGE_BORDER,
33
+ highlightBorder: accent,
34
+ highlightBackground: withAlpha(accent, HIGHLIGHT_ALPHA),
35
+ highlightShadow: withAlpha(background, SHADOW_ALPHA),
36
+ };
37
+ }
21
38
  return {
22
39
  badgeBackground: background,
23
40
  badgeText: accent,
24
41
  badgeActiveBackground: accent,
25
42
  badgeActiveText: background,
26
43
  badgeBorder: accent,
44
+ badgeActiveBorder: accent,
27
45
  tooltipBackground: background,
28
46
  tooltipText: text,
29
47
  tooltipBorder: accent,
@@ -33,10 +51,10 @@ function buildTheme(colors) {
33
51
  };
34
52
  }
35
53
  export function resolveTheme(input) {
36
- const base = PRESET_COLORS.default;
54
+ const lightBase = PRESET_COLORS.light;
37
55
  if (!input)
38
- return buildTheme(base);
56
+ return buildTheme(lightBase, 'light');
39
57
  if (typeof input === 'string')
40
- return buildTheme(PRESET_COLORS[input] ?? base);
41
- return buildTheme({ ...base, ...input });
58
+ return buildTheme(PRESET_COLORS[input] ?? lightBase, input);
59
+ return buildTheme({ ...lightBase, ...input }, 'light');
42
60
  }
@@ -1,4 +1,5 @@
1
1
  import type { LocatorIde } from '../shared/index.js';
2
2
  export declare function toLaunchEditorName(ide: Exclude<LocatorIde, 'auto'>): string;
3
3
  export declare function resolveCliPath(command: string): string;
4
+ export declare function formatLaunchEditorCommand(command: string): string;
4
5
  export declare function resolveAutoEditor(): string | null;
@@ -39,6 +39,12 @@ export function resolveCliPath(command) {
39
39
  return bundlePath;
40
40
  return command;
41
41
  }
42
+ export function formatLaunchEditorCommand(command) {
43
+ if (command.includes(' ')) {
44
+ return `"${command}"`;
45
+ }
46
+ return command;
47
+ }
42
48
  export function resolveAutoEditor() {
43
49
  const [editor] = guessEditor();
44
50
  if (!editor)
@@ -1,18 +1,18 @@
1
1
  import launch from 'launch-editor';
2
2
  import { resolveIde } from '../shared/index.js';
3
- import { resolveAutoEditor, resolveCliPath, toLaunchEditorName } from './editor-cli.js';
3
+ import { formatLaunchEditorCommand, resolveAutoEditor, resolveCliPath, toLaunchEditorName } from './editor-cli.js';
4
4
  export function openInEditor(loc, ideParam, allowed) {
5
5
  const ide = resolveIde(ideParam, allowed);
6
6
  const spec = `${loc.file}:${loc.line}:${loc.col}`;
7
7
  if (ide === 'auto') {
8
8
  const resolved = resolveAutoEditor();
9
9
  if (resolved) {
10
- launch(spec, resolved);
10
+ launch(spec, formatLaunchEditorCommand(resolved));
11
11
  return;
12
12
  }
13
13
  launch(spec);
14
14
  return;
15
15
  }
16
16
  const command = resolveCliPath(toLaunchEditorName(ide));
17
- launch(spec, command);
17
+ launch(spec, formatLaunchEditorCommand(command));
18
18
  }
@@ -38,7 +38,6 @@ function sourceLocator(options = {}) {
38
38
  const clientConfig = {
39
39
  endpoint: config.endpoint,
40
40
  attribute: config.attribute,
41
- ides: config.ides,
42
41
  theme: config.theme,
43
42
  };
44
43
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-source-locator",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Dev-only Vite plugin: click UI elements to jump to source in your IDE",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,7 +32,7 @@
32
32
  "prepublishOnly": "npm run lint && npm run build && npm run test"
33
33
  },
34
34
  "peerDependencies": {
35
- "vite": "^5.0.0"
35
+ "vite": "^5.0.0 || ^6.0.0"
36
36
  },
37
37
  "dependencies": {
38
38
  "@babel/core": "^7.29.7",