semantic-inspector 0.1.0 → 0.2.0

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/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
- import { useState, useRef, useEffect } from 'react';
2
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { domToBlob } from 'modern-screenshot';
1
+ import { memo, useState, useRef, useMemo, useEffect } from 'react';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
3
 
5
4
  // src/SemanticInspector.tsx
6
5
  var Z = 2147483600;
@@ -63,7 +62,10 @@ function tipStyle(r) {
63
62
  whiteSpace: "nowrap"
64
63
  };
65
64
  }
66
- function Overlay({ target, toast }) {
65
+ var Overlay = memo(function Overlay2({
66
+ target,
67
+ toast
68
+ }) {
67
69
  return /* @__PURE__ */ jsxs(Fragment, { children: [
68
70
  /* @__PURE__ */ jsx("div", { style: badge, children: "\u2316 inspect \xB7 click=name \xB7 \u21E7click=shot \xB7 Esc=exit" }),
69
71
  target && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -78,12 +80,21 @@ function Overlay({ target, toast }) {
78
80
  ] }),
79
81
  toast && /* @__PURE__ */ jsx("div", { style: toastStyle, children: toast })
80
82
  ] });
81
- }
83
+ });
84
+
85
+ // src/clipboard.ts
82
86
  async function copyText(text) {
87
+ if (typeof navigator === "undefined" || !navigator.clipboard) {
88
+ throw new Error("Clipboard API unavailable (needs a secure context: https or localhost)");
89
+ }
83
90
  await navigator.clipboard.writeText(text);
84
91
  }
85
92
  async function copyElementShot(el) {
86
- const blob = await domToBlob(el);
93
+ if (typeof navigator === "undefined" || !navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
94
+ throw new Error("Clipboard image write unsupported (needs a secure context: https or localhost)");
95
+ }
96
+ const { domToBlob } = await import('modern-screenshot');
97
+ const blob = await domToBlob(el, { scale: 1 });
87
98
  if (!blob) throw new Error("screenshot produced empty blob");
88
99
  await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
89
100
  }
@@ -106,9 +117,10 @@ function fallbackName(el, loc) {
106
117
  return el.tagName.toLowerCase();
107
118
  }
108
119
  function fiberName(el) {
109
- const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
120
+ const host = el;
121
+ const key = Object.keys(host).find((k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
110
122
  if (!key) return null;
111
- let fiber = el[key] ?? null;
123
+ let fiber = host[key] ?? null;
112
124
  while (fiber) {
113
125
  const t = fiber.type;
114
126
  const name = t && typeof t !== "string" ? t.displayName ?? t.name : void 0;
@@ -123,15 +135,42 @@ var DEFAULT_HOTKEY = "Alt+Shift+S";
123
135
  function defaultFormat(t) {
124
136
  return t.loc ? `${t.comp} \u2014 ${t.loc}` : t.comp;
125
137
  }
126
- function matchHotkey(e, hotkey) {
138
+ function parseHotkey(hotkey) {
127
139
  const parts = hotkey.split("+").map((p) => p.trim().toLowerCase());
128
- const key = parts[parts.length - 1];
129
- const want = (m, alt) => parts.includes(m) || (alt ? parts.includes(alt) : false);
130
- if (e.altKey !== want("alt")) return false;
131
- if (e.shiftKey !== want("shift")) return false;
132
- if (e.ctrlKey !== want("ctrl", "control")) return false;
133
- if (e.metaKey !== want("meta", "cmd")) return false;
134
- return e.key.toLowerCase() === key || e.code.toLowerCase() === `key${key}`;
140
+ const has = (...names) => names.some((n) => parts.includes(n));
141
+ const last = parts[parts.length - 1];
142
+ return {
143
+ alt: has("alt"),
144
+ shift: has("shift"),
145
+ ctrl: has("ctrl", "control"),
146
+ meta: has("meta", "cmd"),
147
+ key: last === "" ? "+" : last
148
+ };
149
+ }
150
+ var CODE_TO_KEY = {
151
+ slash: "/",
152
+ backslash: "\\",
153
+ period: ".",
154
+ comma: ",",
155
+ semicolon: ";",
156
+ quote: "'",
157
+ backquote: "`",
158
+ bracketleft: "[",
159
+ bracketright: "]",
160
+ minus: "-",
161
+ equal: "="
162
+ };
163
+ function matchHotkey(e, hk) {
164
+ if (e.altKey !== hk.alt || e.shiftKey !== hk.shift || e.ctrlKey !== hk.ctrl || e.metaKey !== hk.meta) {
165
+ return false;
166
+ }
167
+ const rawCode = e.code.toLowerCase();
168
+ const code = CODE_TO_KEY[rawCode] ?? rawCode.replace(/^(key|digit)/, "");
169
+ return e.key.toLowerCase() === hk.key || code === hk.key;
170
+ }
171
+ function sameTarget(a, b) {
172
+ if (!a || !b) return a === b;
173
+ return a.el === b.el && a.rect.left === b.rect.left && a.rect.top === b.rect.top && a.rect.width === b.rect.width && a.rect.height === b.rect.height;
135
174
  }
136
175
  function useInspector(opts = {}) {
137
176
  const { hotkey = DEFAULT_HOTKEY } = opts;
@@ -139,68 +178,77 @@ function useInspector(opts = {}) {
139
178
  const [target, setTarget] = useState(null);
140
179
  const cbRef = useRef(opts);
141
180
  cbRef.current = opts;
181
+ const targetRef = useRef(null);
182
+ const hk = useMemo(() => parseHotkey(hotkey), [hotkey]);
142
183
  useEffect(() => {
143
184
  function onKey(e) {
144
- if (matchHotkey(e, hotkey)) {
185
+ if (matchHotkey(e, hk)) {
145
186
  e.preventDefault();
146
187
  setActive((a) => !a);
147
188
  } else if (e.key === "Escape") {
148
- setActive(false);
189
+ setActive((a) => a ? false : a);
149
190
  }
150
191
  }
151
192
  window.addEventListener("keydown", onKey);
152
- return () => {
153
- window.removeEventListener("keydown", onKey);
154
- };
155
- }, [hotkey]);
193
+ return () => window.removeEventListener("keydown", onKey);
194
+ }, [hk]);
156
195
  useEffect(() => {
157
196
  if (!active) {
197
+ targetRef.current = null;
158
198
  setTarget(null);
159
199
  return;
160
200
  }
201
+ let rafId = 0;
202
+ let lastX = 0;
203
+ let lastY = 0;
204
+ let shotInFlight = false;
161
205
  function onMove(e) {
162
- setTarget(resolveTarget(document.elementFromPoint(e.clientX, e.clientY)));
206
+ lastX = e.clientX;
207
+ lastY = e.clientY;
208
+ if (rafId) return;
209
+ rafId = requestAnimationFrame(() => {
210
+ rafId = 0;
211
+ const next = resolveTarget(document.elementFromPoint(lastX, lastY));
212
+ targetRef.current = next;
213
+ setTarget((prev) => sameTarget(prev, next) ? prev : next);
214
+ });
163
215
  }
164
216
  function onClick(e) {
165
- const t = resolveTarget(document.elementFromPoint(e.clientX, e.clientY));
217
+ const t = targetRef.current ?? resolveTarget(document.elementFromPoint(e.clientX, e.clientY));
166
218
  if (!t) return;
167
219
  e.preventDefault();
168
220
  e.stopPropagation();
169
221
  const { formatText = defaultFormat, onCopy, onError } = cbRef.current;
170
- const done = (kind, payload) => {
171
- onCopy?.(kind, payload);
172
- };
222
+ const done = (kind, payload) => onCopy?.(kind, payload);
173
223
  const fail = (kind, err) => {
174
- onError?.(kind, err);
224
+ if (onError) onError(kind, err);
225
+ else console.warn(`[semantic-inspector] ${kind} copy failed:`, err);
175
226
  };
176
227
  if (e.shiftKey) {
228
+ if (shotInFlight) return;
229
+ shotInFlight = true;
177
230
  copyElementShot(t.el).then(
178
- () => {
179
- done("screenshot", t.comp);
180
- },
181
- (err) => {
182
- fail("screenshot", err);
183
- }
184
- );
231
+ () => done("screenshot", t.comp),
232
+ (err) => fail("screenshot", err)
233
+ ).finally(() => {
234
+ shotInFlight = false;
235
+ });
185
236
  } else {
186
237
  const text = formatText({ comp: t.comp, loc: t.loc });
187
238
  copyText(text).then(
188
- () => {
189
- done("text", text);
190
- },
191
- (err) => {
192
- fail("text", err);
193
- }
239
+ () => done("text", text),
240
+ (err) => fail("text", err)
194
241
  );
195
242
  }
196
243
  }
197
- window.addEventListener("mousemove", onMove, true);
244
+ window.addEventListener("mousemove", onMove, { capture: true, passive: true });
198
245
  window.addEventListener("click", onClick, true);
199
246
  const prevCursor = document.body.style.cursor;
200
247
  document.body.style.cursor = "crosshair";
201
248
  return () => {
202
249
  window.removeEventListener("mousemove", onMove, true);
203
250
  window.removeEventListener("click", onClick, true);
251
+ if (rafId) cancelAnimationFrame(rafId);
204
252
  document.body.style.cursor = prevCursor;
205
253
  };
206
254
  }, [active]);
@@ -209,23 +257,20 @@ function useInspector(opts = {}) {
209
257
  var TOAST_MS = 1400;
210
258
  function SemanticInspector(props) {
211
259
  const [toast, setToast] = useState(null);
212
- const timer = useRef(void 0);
213
- const flash = (msg) => {
214
- setToast(msg);
215
- clearTimeout(timer.current);
216
- timer.current = setTimeout(() => {
217
- setToast(null);
218
- }, TOAST_MS);
219
- };
260
+ useEffect(() => {
261
+ if (toast == null) return;
262
+ const id = setTimeout(() => setToast(null), TOAST_MS);
263
+ return () => clearTimeout(id);
264
+ }, [toast]);
220
265
  const { active, target } = useInspector({
221
266
  hotkey: props.hotkey,
222
267
  formatText: props.formatText,
223
268
  onCopy: (kind, payload) => {
224
- flash(kind === "text" ? `\u2713 ${payload}` : "\u2713 screenshot copied");
269
+ setToast(kind === "text" ? `\u2713 ${payload}` : "\u2713 screenshot copied");
225
270
  props.onCopy?.(kind, payload);
226
271
  },
227
272
  onError: (kind, err) => {
228
- flash(`\u2717 ${kind} failed`);
273
+ setToast(`\u2717 ${kind} failed`);
229
274
  props.onError?.(kind, err);
230
275
  }
231
276
  });
@@ -233,6 +278,6 @@ function SemanticInspector(props) {
233
278
  return /* @__PURE__ */ jsx(Overlay, { target: active ? target : null, toast });
234
279
  }
235
280
 
236
- export { SemanticInspector, copyElementShot, copyText, resolveTarget, useInspector };
281
+ export { SemanticInspector, useInspector };
237
282
  //# sourceMappingURL=index.js.map
238
283
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Overlay.tsx","../src/clipboard.ts","../src/resolveTarget.ts","../src/useInspector.ts","../src/SemanticInspector.tsx"],"names":["useState","useRef","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { CSSProperties } from 'react';\nimport type { InspectTarget } from './types';\n\nconst Z = 2147483600;\n\nconst badge: CSSProperties = {\n position: 'fixed',\n bottom: 12,\n left: 12,\n zIndex: Z + 2,\n padding: '6px 10px',\n borderRadius: 6,\n font: '12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: 'rgba(17,17,17,0.92)',\n color: '#fff',\n pointerEvents: 'none',\n boxShadow: '0 2px 8px rgba(0,0,0,0.3)'\n};\n\nconst toastStyle: CSSProperties = {\n position: 'fixed',\n bottom: 12,\n right: 12,\n zIndex: Z + 2,\n padding: '6px 10px',\n borderRadius: 6,\n font: '12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: 'rgba(22,101,52,0.95)',\n color: '#fff',\n pointerEvents: 'none',\n maxWidth: '60vw',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis'\n};\n\nfunction boxStyle(r: DOMRect): CSSProperties {\n return {\n position: 'fixed',\n left: r.left,\n top: r.top,\n width: r.width,\n height: r.height,\n zIndex: Z,\n outline: '2px solid #6366f1',\n background: 'rgba(99,102,241,0.12)',\n pointerEvents: 'none',\n transition: 'all 60ms ease-out'\n };\n}\n\nfunction tipStyle(r: DOMRect): CSSProperties {\n const top = r.top > 26 ? r.top - 24 : r.bottom + 4;\n return {\n position: 'fixed',\n left: r.left,\n top,\n zIndex: Z + 1,\n padding: '2px 6px',\n borderRadius: 4,\n font: '11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: '#6366f1',\n color: '#fff',\n pointerEvents: 'none',\n whiteSpace: 'nowrap'\n };\n}\n\nexport function Overlay({ target, toast }: { target: InspectTarget | null; toast: string | null }) {\n return (\n <>\n <div style={badge}>⌖ inspect · click=name · ⇧click=shot · Esc=exit</div>\n {target && (\n <>\n <div style={boxStyle(target.rect)} />\n <div style={tipStyle(target.rect)}>\n {target.comp}\n <span style={{ opacity: 0.75 }}> · {target.loc ?? 'no source'}</span>\n </div>\n </>\n )}\n {toast && <div style={toastStyle}>{toast}</div>}\n </>\n );\n}\n","import { domToBlob } from 'modern-screenshot';\n\n/** Текст в буфер. */\nexport async function copyText(text: string): Promise<void> {\n await navigator.clipboard.writeText(text);\n}\n\n/**\n * PNG-скриншот ТОЛЬКО переданного элемента в буфер (image/png).\n * Должен вызываться из user-gesture (клик), иначе браузер блокит image-копию.\n */\nexport async function copyElementShot(el: Element): Promise<void> {\n const blob = await domToBlob(el as HTMLElement);\n if (!blob) throw new Error('screenshot produced empty blob');\n await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);\n}\n","import type { InspectTarget } from './types';\n\nconst LOC_ATTR = 'data-loc';\nconst COMP_ATTR = 'data-comp';\n\n/**\n * DOM-элемент под курсором → цель инспекции.\n * Идём к ближайшему предку с data-loc (заштампован babel-плагином). Если его нет\n * (prod-билд без штампов / сторонний узел) — best-effort: имя из React fiber,\n * затем имя файла из data-loc, затем имя тега.\n */\nexport function resolveTarget(el: Element | null): InspectTarget | null {\n if (!el) return null;\n const target = el.closest(`[${LOC_ATTR}]`) ?? el;\n const loc = target.getAttribute(LOC_ATTR);\n const comp = target.getAttribute(COMP_ATTR) ?? fiberName(target) ?? fallbackName(target, loc);\n return { comp, loc, el: target, rect: target.getBoundingClientRect() };\n}\n\nfunction fallbackName(el: Element, loc: string | null): string {\n if (loc) {\n const base = loc\n .split(':')[0]\n .split('/')\n .pop()\n ?.replace(/\\.[jt]sx?$/, '');\n if (base) return base;\n }\n return el.tagName.toLowerCase();\n}\n\n// React-internals fallback. _debugSource убран в React 19, но имя компонента из\n// fiber.type всё ещё доступно (в dev-билде не минифицировано).\ninterface FiberLike {\n type: { displayName?: string; name?: string } | string | null | undefined;\n return: FiberLike | null;\n}\n\nfunction fiberName(el: Element): string | null {\n const key = Object.keys(el).find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));\n if (!key) return null;\n let fiber = (el as unknown as Record<string, FiberLike | undefined>)[key] ?? null;\n while (fiber) {\n const t = fiber.type;\n const name = t && typeof t !== 'string' ? (t.displayName ?? t.name) : undefined;\n if (name && /^[A-Z]/.test(name)) return name;\n fiber = fiber.return;\n }\n return null;\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { copyElementShot, copyText } from './clipboard';\nimport { resolveTarget } from './resolveTarget';\nimport type { CopyKind, InspectTarget, SemanticInspectorProps } from './types';\n\nconst DEFAULT_HOTKEY = 'Alt+Shift+S';\n\nfunction defaultFormat(t: { comp: string; loc: string | null }): string {\n return t.loc ? `${t.comp} — ${t.loc}` : t.comp;\n}\n\n/** 'Alt+Shift+S' → совпадает ли событие keydown. Последний токен — клавиша. */\nfunction matchHotkey(e: KeyboardEvent, hotkey: string): boolean {\n const parts = hotkey.split('+').map((p) => p.trim().toLowerCase());\n const key = parts[parts.length - 1];\n const want = (m: string, alt?: string) => parts.includes(m) || (alt ? parts.includes(alt) : false);\n if (e.altKey !== want('alt')) return false;\n if (e.shiftKey !== want('shift')) return false;\n if (e.ctrlKey !== want('ctrl', 'control')) return false;\n if (e.metaKey !== want('meta', 'cmd')) return false;\n return e.key.toLowerCase() === key || e.code.toLowerCase() === `key${key}`;\n}\n\n/**\n * Состояние режима инспекции + слушатели.\n * - keydown: хоткей переключает active, Esc выключает.\n * - active: mousemove обновляет target; click (capture, preventDefault) копирует\n * текст, Shift+click — скриншот элемента.\n */\nexport function useInspector(opts: SemanticInspectorProps = {}) {\n const { hotkey = DEFAULT_HOTKEY } = opts;\n const [active, setActive] = useState(false);\n const [target, setTarget] = useState<InspectTarget | null>(null);\n\n // Свежие колбэки без переподписки слушателей.\n const cbRef = useRef<SemanticInspectorProps>(opts);\n cbRef.current = opts;\n\n useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (matchHotkey(e, hotkey)) {\n e.preventDefault();\n setActive((a) => !a);\n } else if (e.key === 'Escape') {\n setActive(false);\n }\n }\n window.addEventListener('keydown', onKey);\n return () => {\n window.removeEventListener('keydown', onKey);\n };\n }, [hotkey]);\n\n useEffect(() => {\n if (!active) {\n setTarget(null);\n return;\n }\n\n function onMove(e: MouseEvent) {\n setTarget(resolveTarget(document.elementFromPoint(e.clientX, e.clientY)));\n }\n\n function onClick(e: MouseEvent) {\n const t = resolveTarget(document.elementFromPoint(e.clientX, e.clientY));\n if (!t) return;\n e.preventDefault();\n e.stopPropagation();\n const { formatText = defaultFormat, onCopy, onError } = cbRef.current;\n const done = (kind: CopyKind, payload: string) => {\n onCopy?.(kind, payload);\n };\n const fail = (kind: CopyKind, err: unknown) => {\n onError?.(kind, err);\n };\n if (e.shiftKey) {\n copyElementShot(t.el).then(\n () => {\n done('screenshot', t.comp);\n },\n (err: unknown) => {\n fail('screenshot', err);\n }\n );\n } else {\n const text = formatText({ comp: t.comp, loc: t.loc });\n copyText(text).then(\n () => {\n done('text', text);\n },\n (err: unknown) => {\n fail('text', err);\n }\n );\n }\n }\n\n window.addEventListener('mousemove', onMove, true);\n window.addEventListener('click', onClick, true);\n const prevCursor = document.body.style.cursor;\n document.body.style.cursor = 'crosshair';\n return () => {\n window.removeEventListener('mousemove', onMove, true);\n window.removeEventListener('click', onClick, true);\n document.body.style.cursor = prevCursor;\n };\n }, [active]);\n\n return { active, target };\n}\n","import { useRef, useState } from 'react';\nimport { Overlay } from './Overlay';\nimport type { CopyKind, SemanticInspectorProps } from './types';\nimport { useInspector } from './useInspector';\n\nconst TOAST_MS = 1400;\n\n/**\n * Семантический инспектор. Сам по себе ничего не показывает, пока не включён\n * хоткеем. Гейтинг (где монтировать) — забота консьюмера: монтируй под своим\n * dev-флагом и желательно через React.lazy, чтобы не тянуть в prod-бандл.\n */\nexport function SemanticInspector(props: SemanticInspectorProps) {\n const [toast, setToast] = useState<string | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const flash = (msg: string) => {\n setToast(msg);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n setToast(null);\n }, TOAST_MS);\n };\n\n const { active, target } = useInspector({\n hotkey: props.hotkey,\n formatText: props.formatText,\n onCopy: (kind: CopyKind, payload: string) => {\n flash(kind === 'text' ? `✓ ${payload}` : '✓ screenshot copied');\n props.onCopy?.(kind, payload);\n },\n onError: (kind: CopyKind, err: unknown) => {\n flash(`✗ ${kind} failed`);\n props.onError?.(kind, err);\n }\n });\n\n if (!active && !toast) return null;\n return <Overlay target={active ? target : null} toast={toast} />;\n}\n"]}
1
+ {"version":3,"sources":["../src/Overlay.tsx","../src/clipboard.ts","../src/resolveTarget.ts","../src/useInspector.ts","../src/SemanticInspector.tsx"],"names":["Overlay","useState","useEffect","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { type CSSProperties, type JSX, type MemoExoticComponent, memo } from 'react';\nimport type { InspectTarget } from './types';\n\n// Just below the 32-bit max so the overlay wins stacking contests, with a small reserved band:\n// Z = highlight box, Z+1 = tip, Z+2 = badge/toast.\nconst Z = 2147483600;\n\nconst badge = {\n position: 'fixed',\n bottom: 12,\n left: 12,\n zIndex: Z + 2,\n padding: '6px 10px',\n borderRadius: 6,\n font: '12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: 'rgba(17,17,17,0.92)',\n color: '#fff',\n pointerEvents: 'none',\n boxShadow: '0 2px 8px rgba(0,0,0,0.3)'\n} satisfies CSSProperties;\n\nconst toastStyle = {\n position: 'fixed',\n bottom: 12,\n right: 12,\n zIndex: Z + 2,\n padding: '6px 10px',\n borderRadius: 6,\n font: '12px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: 'rgba(22,101,52,0.95)',\n color: '#fff',\n pointerEvents: 'none',\n maxWidth: '60vw',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis'\n} satisfies CSSProperties;\n\nfunction boxStyle(r: DOMRect): CSSProperties {\n return {\n position: 'fixed',\n left: r.left,\n top: r.top,\n width: r.width,\n height: r.height,\n zIndex: Z,\n outline: '2px solid #6366f1',\n background: 'rgba(99,102,241,0.12)',\n pointerEvents: 'none',\n transition: 'all 60ms ease-out'\n } satisfies CSSProperties;\n}\n\nfunction tipStyle(r: DOMRect): CSSProperties {\n const top = r.top > 26 ? r.top - 24 : r.bottom + 4;\n return {\n position: 'fixed',\n left: r.left,\n top,\n zIndex: Z + 1,\n padding: '2px 6px',\n borderRadius: 4,\n font: '11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace',\n background: '#6366f1',\n color: '#fff',\n pointerEvents: 'none',\n whiteSpace: 'nowrap'\n } satisfies CSSProperties;\n}\n\ninterface OverlayProps {\n target: InspectTarget | null;\n toast: string | null;\n}\n\nexport const Overlay: MemoExoticComponent<(props: OverlayProps) => JSX.Element> = memo(function Overlay({\n target,\n toast\n}: OverlayProps): JSX.Element {\n return (\n <>\n <div style={badge}>⌖ inspect · click=name · ⇧click=shot · Esc=exit</div>\n {target && (\n <>\n <div style={boxStyle(target.rect)} />\n <div style={tipStyle(target.rect)}>\n {target.comp}\n <span style={{ opacity: 0.75 }}> · {target.loc ?? 'no source'}</span>\n </div>\n </>\n )}\n {toast && <div style={toastStyle}>{toast}</div>}\n </>\n );\n});\n","/** Copy text to the clipboard. Requires a secure context (https or localhost). */\nexport async function copyText(text: string): Promise<void> {\n if (typeof navigator === 'undefined' || !navigator.clipboard) {\n throw new Error('Clipboard API unavailable (needs a secure context: https or localhost)');\n }\n await navigator.clipboard.writeText(text);\n}\n\n/**\n * Copy a PNG screenshot of ONLY the given element to the clipboard (image/png).\n * Must be called from a user gesture (click), or the browser blocks the image write.\n *\n * `modern-screenshot` is imported lazily so it is emitted as a separate chunk and never lands\n * in a consumer's production bundle through the runtime entry.\n */\nexport async function copyElementShot(el: Element): Promise<void> {\n if (typeof navigator === 'undefined' || !navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {\n throw new Error('Clipboard image write unsupported (needs a secure context: https or localhost)');\n }\n const { domToBlob } = await import('modern-screenshot');\n // scale: 1 avoids the 4x rasterization cost of devicePixelRatio on Retina displays.\n const blob = await domToBlob(el as HTMLElement, { scale: 1 });\n if (!blob) throw new Error('screenshot produced empty blob');\n await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);\n}\n","import type { InspectTarget } from './types';\n\nconst LOC_ATTR = 'data-loc';\nconst COMP_ATTR = 'data-comp';\n\n/**\n * DOM element under the cursor → inspection target.\n *\n * Walks to the nearest ancestor carrying data-loc (stamped by the Babel plugin). If none\n * exists (prod build without stamps / foreign node), it falls back best-effort to: the name\n * from the React fiber, then the file name from data-loc, then the tag name.\n */\nexport function resolveTarget(el: Element | null): InspectTarget | null {\n if (!el) return null;\n const target = el.closest(`[${LOC_ATTR}]`) ?? el;\n const loc = target.getAttribute(LOC_ATTR);\n const comp = target.getAttribute(COMP_ATTR) ?? fiberName(target) ?? fallbackName(target, loc);\n return { comp, loc, el: target, rect: target.getBoundingClientRect() };\n}\n\nfunction fallbackName(el: Element, loc: string | null): string {\n if (loc) {\n const base = loc\n .split(':')[0]\n .split('/')\n .pop()\n ?.replace(/\\.[jt]sx?$/, '');\n if (base) return base;\n }\n return el.tagName.toLowerCase();\n}\n\n// React-internals fallback. _debugSource was removed in React 19, but the component name from\n// fiber.type is still available (not minified in dev builds).\ninterface FiberLike {\n type: { displayName?: string; name?: string } | string | null | undefined;\n return: FiberLike | null;\n}\n\nfunction fiberName(el: Element): string | null {\n const host = el as Element & Record<string, FiberLike | undefined>;\n const key = Object.keys(host).find((k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));\n if (!key) return null;\n let fiber: FiberLike | null = host[key] ?? null;\n while (fiber) {\n const t = fiber.type;\n const name = t && typeof t !== 'string' ? (t.displayName ?? t.name) : undefined;\n if (name && /^[A-Z]/.test(name)) return name;\n fiber = fiber.return;\n }\n return null;\n}\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { copyElementShot, copyText } from './clipboard';\nimport { resolveTarget } from './resolveTarget';\nimport type { CopyKind, InspectTarget, LocInfo, SemanticInspectorProps, UseInspectorResult } from './types';\n\nconst DEFAULT_HOTKEY = 'Alt+Shift+S';\n\nfunction defaultFormat(t: LocInfo): string {\n return t.loc ? `${t.comp} — ${t.loc}` : t.comp;\n}\n\ninterface Hotkey {\n alt: boolean;\n shift: boolean;\n ctrl: boolean;\n meta: boolean;\n key: string;\n}\n\n/** Parse 'Alt+Shift+S' into a descriptor. The final token is the key (empty → literal '+'). */\nfunction parseHotkey(hotkey: string): Hotkey {\n const parts = hotkey.split('+').map((p) => p.trim().toLowerCase());\n const has = (...names: string[]): boolean => names.some((n) => parts.includes(n));\n const last = parts[parts.length - 1];\n return {\n alt: has('alt'),\n shift: has('shift'),\n ctrl: has('ctrl', 'control'),\n meta: has('meta', 'cmd'),\n key: last === '' ? '+' : last\n };\n}\n\n// Physical-key codes whose token differs from the produced character, so a hotkey written with\n// the unshifted glyph still matches when Shift transforms it (e.g. Ctrl+Shift+/ → e.key '?').\nconst CODE_TO_KEY: Record<string, string> = {\n slash: '/',\n backslash: '\\\\',\n period: '.',\n comma: ',',\n semicolon: ';',\n quote: \"'\",\n backquote: '`',\n bracketleft: '[',\n bracketright: ']',\n minus: '-',\n equal: '='\n};\n\n/** Whether a keydown event matches the parsed hotkey. */\nfunction matchHotkey(e: KeyboardEvent, hk: Hotkey): boolean {\n if (e.altKey !== hk.alt || e.shiftKey !== hk.shift || e.ctrlKey !== hk.ctrl || e.metaKey !== hk.meta) {\n return false;\n }\n // Normalize the physical code (KeyA → a, Digit1 → 1, Slash → /) so non-letter keys match too.\n const rawCode = e.code.toLowerCase();\n const code = CODE_TO_KEY[rawCode] ?? rawCode.replace(/^(key|digit)/, '');\n return e.key.toLowerCase() === hk.key || code === hk.key;\n}\n\nfunction sameTarget(a: InspectTarget | null, b: InspectTarget | null): boolean {\n if (!a || !b) return a === b;\n return (\n a.el === b.el &&\n a.rect.left === b.rect.left &&\n a.rect.top === b.rect.top &&\n a.rect.width === b.rect.width &&\n a.rect.height === b.rect.height\n );\n}\n\n/**\n * Inspection-mode state + listeners.\n * - keydown: the hotkey toggles `active`; Esc exits.\n * - active: mousemove (rAF-coalesced) updates `target`; click (capture, preventDefault) copies\n * text, Shift+click copies an element screenshot.\n */\nexport function useInspector(opts: SemanticInspectorProps = {}): UseInspectorResult {\n const { hotkey = DEFAULT_HOTKEY } = opts;\n const [active, setActive] = useState(false);\n const [target, setTarget] = useState<InspectTarget | null>(null);\n\n // Fresh callbacks without re-subscribing listeners.\n const cbRef = useRef<SemanticInspectorProps>(opts);\n cbRef.current = opts;\n\n // Latest hovered target, so the click handler copies exactly what is highlighted.\n const targetRef = useRef<InspectTarget | null>(null);\n\n const hk = useMemo(() => parseHotkey(hotkey), [hotkey]);\n\n useEffect(() => {\n function onKey(e: KeyboardEvent): void {\n if (matchHotkey(e, hk)) {\n e.preventDefault();\n setActive((a) => !a);\n } else if (e.key === 'Escape') {\n setActive((a) => (a ? false : a));\n }\n }\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [hk]);\n\n useEffect(() => {\n if (!active) {\n targetRef.current = null;\n setTarget(null);\n return;\n }\n\n let rafId = 0;\n let lastX = 0;\n let lastY = 0;\n let shotInFlight = false;\n\n function onMove(e: MouseEvent): void {\n lastX = e.clientX;\n lastY = e.clientY;\n if (rafId) return; // one update per frame, regardless of input rate\n rafId = requestAnimationFrame(() => {\n rafId = 0;\n const next = resolveTarget(document.elementFromPoint(lastX, lastY));\n targetRef.current = next;\n setTarget((prev) => (sameTarget(prev, next) ? prev : next));\n });\n }\n\n function onClick(e: MouseEvent): void {\n const t = targetRef.current ?? resolveTarget(document.elementFromPoint(e.clientX, e.clientY));\n if (!t) return;\n e.preventDefault();\n e.stopPropagation();\n const { formatText = defaultFormat, onCopy, onError } = cbRef.current;\n const done = (kind: CopyKind, payload: string): void => onCopy?.(kind, payload);\n const fail = (kind: CopyKind, err: unknown): void => {\n if (onError) onError(kind, err);\n // Surface failures even without an onError handler (console is the right channel for a dev tool).\n else console.warn(`[semantic-inspector] ${kind} copy failed:`, err);\n };\n\n if (e.shiftKey) {\n if (shotInFlight) return; // ignore overlapping captures\n shotInFlight = true;\n copyElementShot(t.el)\n .then(\n () => done('screenshot', t.comp),\n (err: unknown) => fail('screenshot', err)\n )\n .finally(() => {\n shotInFlight = false;\n });\n } else {\n const text = formatText({ comp: t.comp, loc: t.loc });\n copyText(text).then(\n () => done('text', text),\n (err: unknown) => fail('text', err)\n );\n }\n }\n\n window.addEventListener('mousemove', onMove, { capture: true, passive: true });\n window.addEventListener('click', onClick, true);\n const prevCursor = document.body.style.cursor;\n document.body.style.cursor = 'crosshair';\n return () => {\n window.removeEventListener('mousemove', onMove, true);\n window.removeEventListener('click', onClick, true);\n if (rafId) cancelAnimationFrame(rafId);\n document.body.style.cursor = prevCursor;\n };\n }, [active]);\n\n return { active, target };\n}\n","import { type JSX, useEffect, useState } from 'react';\nimport { Overlay } from './Overlay';\nimport type { CopyKind, SemanticInspectorProps } from './types';\nimport { useInspector } from './useInspector';\n\n// How long the copy toast stays visible (ms).\nconst TOAST_MS = 1400;\n\n/**\n * Semantic inspector. Renders nothing until toggled by the hotkey. Gating (where to mount) is\n * the consumer's responsibility: mount it under your dev flag, ideally via React.lazy, so it is\n * not pulled into the production bundle.\n */\nexport function SemanticInspector(props: SemanticInspectorProps): JSX.Element | null {\n const [toast, setToast] = useState<string | null>(null);\n\n // Auto-hide the toast; cleanup cancels the pending timer (including on unmount).\n // Note: two identical consecutive toasts share one window — acceptable for a dev tool.\n useEffect(() => {\n if (toast == null) return;\n const id = setTimeout(() => setToast(null), TOAST_MS);\n return () => clearTimeout(id);\n }, [toast]);\n\n const { active, target } = useInspector({\n hotkey: props.hotkey,\n formatText: props.formatText,\n onCopy: (kind: CopyKind, payload: string) => {\n setToast(kind === 'text' ? `✓ ${payload}` : '✓ screenshot copied');\n props.onCopy?.(kind, payload);\n },\n onError: (kind: CopyKind, err: unknown) => {\n setToast(`✗ ${kind} failed`);\n props.onError?.(kind, err);\n }\n });\n\n if (!active && !toast) return null;\n return <Overlay target={active ? target : null} toast={toast} />;\n}\n"]}
package/dist/vite.cjs CHANGED
@@ -1,69 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var chunkX6NMJJ2M_cjs = require('./chunk-X6NMJJ2M.cjs');
3
4
  var core = require('@babel/core');
4
5
 
5
- // src/vite.ts
6
-
7
- // src/stampLocBabel.ts
8
- function isHostElement(name) {
9
- return name.type === "JSXIdentifier" && /^[a-z]/.test(name.name);
10
- }
11
- function hasAttr(el, attrName) {
12
- return el.attributes.some(
13
- (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === attrName
14
- );
15
- }
16
- function nearestComponentName(path) {
17
- let p = path;
18
- while (p) {
19
- const node = p.node;
20
- if (node.type === "FunctionDeclaration" && node.id && /^[A-Z]/.test(node.id.name)) {
21
- return node.id.name;
22
- }
23
- if (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
24
- const parent = p.parentPath?.node;
25
- if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier" && /^[A-Z]/.test(parent.id.name)) {
26
- return parent.id.name;
27
- }
28
- }
29
- p = p.parentPath;
30
- }
31
- return null;
32
- }
33
- function stampLocBabel(babel, opts = {}) {
34
- const t = babel.types;
35
- const attrLoc = opts.attrLoc ?? "data-loc";
36
- const attrComp = opts.attrComp ?? "data-comp";
37
- const rootDir = opts.rootDir ?? process.cwd();
38
- const attr = (name, value) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));
39
- const toRel = (file) => {
40
- let root = rootDir;
41
- while (root.endsWith("/")) root = root.slice(0, -1);
42
- const rel = file.startsWith(root + "/") ? file.slice(root.length + 1) : file;
43
- return rel.split("\\").join("/");
44
- };
45
- return {
46
- name: "stamp-loc",
47
- visitor: {
48
- JSXOpeningElement(path, state) {
49
- const node = path.node;
50
- if (!isHostElement(node.name)) return;
51
- const filename = state.file.opts.filename;
52
- const loc = node.loc;
53
- if (!filename || !loc) return;
54
- if (!hasAttr(node, attrLoc)) {
55
- node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}`));
56
- }
57
- if (!hasAttr(node, attrComp)) {
58
- const comp = nearestComponentName(path);
59
- if (comp) node.attributes.push(attr(attrComp, comp));
60
- }
61
- }
62
- }
63
- };
64
- }
65
-
66
- // src/vite.ts
67
6
  function stampLocVite(opts = {}) {
68
7
  const include = opts.include ?? /\.[jt]sx$/;
69
8
  const babelOpts = {
@@ -74,9 +13,11 @@ function stampLocVite(opts = {}) {
74
13
  return {
75
14
  name: "semantic-inspector:stamp-loc",
76
15
  enforce: "pre",
16
+ apply: "serve",
77
17
  async transform(code, id) {
78
18
  const file = id.split("?")[0];
79
19
  if (!include.test(file) || file.includes("/node_modules/")) return null;
20
+ if (!code.includes("<")) return null;
80
21
  try {
81
22
  const result = await core.transformAsync(code, {
82
23
  filename: file,
@@ -84,10 +25,10 @@ function stampLocVite(opts = {}) {
84
25
  configFile: false,
85
26
  sourceMaps: true,
86
27
  parserOpts: { plugins: ["jsx", "typescript"] },
87
- plugins: [[stampLocBabel, babelOpts]]
28
+ plugins: [[chunkX6NMJJ2M_cjs.stampLocBabel, babelOpts]]
88
29
  });
89
30
  if (!result?.code) return null;
90
- return { code: result.code, map: result.map };
31
+ return { code: result.code, map: result.map ?? null };
91
32
  } catch (err) {
92
33
  this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);
93
34
  return null;
package/dist/vite.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stampLocBabel.ts","../src/vite.ts"],"names":["transformAsync"],"mappings":"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","file":"vite.cjs","sourcesContent":["import type { NodePath, PluginObj, types as BabelTypes } from '@babel/core';\n\nexport interface StampLocOptions {\n /** Имя атрибута пути. Default 'data-loc'. */\n attrLoc?: string;\n /** Имя атрибута компонента. Default 'data-comp'. */\n attrComp?: string;\n /** База для относительного пути в data-loc. Default process.cwd(). */\n rootDir?: string;\n}\n\nfunction isHostElement(name: BabelTypes.JSXOpeningElement['name']): boolean {\n return name.type === 'JSXIdentifier' && /^[a-z]/.test(name.name);\n}\n\nfunction hasAttr(el: BabelTypes.JSXOpeningElement, attrName: string): boolean {\n return el.attributes.some(\n (a) => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === attrName\n );\n}\n\n// Ближайшая функция-компонент с PascalCase-именем вверх по дереву:\n// function Foo() {} | const Foo = () => {} | const Foo = function () {}\nfunction nearestComponentName(path: NodePath): string | null {\n let p: NodePath | null = path;\n while (p) {\n const node = p.node;\n if (node.type === 'FunctionDeclaration' && node.id && /^[A-Z]/.test(node.id.name)) {\n return node.id.name;\n }\n if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {\n const parent = p.parentPath?.node;\n if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier' && /^[A-Z]/.test(parent.id.name)) {\n return parent.id.name;\n }\n }\n p = p.parentPath;\n }\n return null;\n}\n\n/**\n * Babel-плагин: вешает data-loc=\"<path>:<line>\" и data-comp=\"<Component>\" на\n * JSX host-элементы (div, section, ...). Рантайм-инспектор читает эти DOM-атрибуты\n * (не React-internals), поэтому устойчив к версии React. Компонентные теги\n * (PascalCase) пропускаем — они не дают собственного DOM-узла.\n */\nexport default function stampLocBabel(babel: { types: typeof BabelTypes }, opts: StampLocOptions = {}): PluginObj {\n const t = babel.types;\n const attrLoc = opts.attrLoc ?? 'data-loc';\n const attrComp = opts.attrComp ?? 'data-comp';\n const rootDir = opts.rootDir ?? process.cwd();\n\n const attr = (name: string, value: string) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));\n\n // path.relative без node:path, чтобы плагин не тянул узловые модули в чужих средах.\n const toRel = (file: string): string => {\n let root = rootDir;\n while (root.endsWith('/')) root = root.slice(0, -1);\n const rel = file.startsWith(root + '/') ? file.slice(root.length + 1) : file;\n return rel.split('\\\\').join('/');\n };\n\n return {\n name: 'stamp-loc',\n visitor: {\n JSXOpeningElement(path, state) {\n const node = path.node;\n if (!isHostElement(node.name)) return;\n\n const filename = state.file.opts.filename;\n const loc = node.loc;\n if (!filename || !loc) return;\n\n if (!hasAttr(node, attrLoc)) {\n node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}`));\n }\n if (!hasAttr(node, attrComp)) {\n const comp = nearestComponentName(path);\n if (comp) node.attributes.push(attr(attrComp, comp));\n }\n }\n }\n };\n}\n","import { transformAsync } from '@babel/core';\nimport type { Plugin } from 'vite';\nimport stampLocBabel, { type StampLocOptions } from './stampLocBabel';\n\nexport interface StampLocViteOptions extends StampLocOptions {\n /** Какие файлы штамповать. Default /\\.[jt]sx$/. */\n include?: RegExp;\n}\n\n/**\n * Vite-плагин: штампует data-loc/data-comp на JSX host-элементы.\n *\n * `@vitejs/plugin-react` v6 транспилит через oxc (без babel-хука), поэтому\n * атрибуты добавляем отдельным pre-проходом babel'а (только парсинг + наш\n * плагин, JSX/TS сохраняются), а oxc уже делает остальное.\n *\n * Парс-ошибка в отдельном файле не валит сборку — файл просто остаётся без\n * штампов (warn в консоль).\n */\nexport function stampLocVite(opts: StampLocViteOptions = {}): Plugin {\n const include = opts.include ?? /\\.[jt]sx$/;\n const babelOpts: StampLocOptions = {\n attrLoc: opts.attrLoc,\n attrComp: opts.attrComp,\n rootDir: opts.rootDir\n };\n\n return {\n name: 'semantic-inspector:stamp-loc',\n enforce: 'pre',\n async transform(code, id) {\n const file = id.split('?')[0];\n if (!include.test(file) || file.includes('/node_modules/')) return null;\n\n try {\n const result = await transformAsync(code, {\n filename: file,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['jsx', 'typescript'] },\n plugins: [[stampLocBabel, babelOpts]]\n });\n if (!result?.code) return null;\n return { code: result.code, map: result.map };\n } catch (err) {\n this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/vite.ts"],"names":["transformAsync","stampLocBabel"],"mappings":";;;;;AAmBO,SAAS,YAAA,CAAa,IAAA,GAA4B,EAAC,EAAW;AACnE,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,WAAA;AAChC,EAAA,MAAM,SAAA,GAA6B;AAAA,IACjC,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,SAAS,IAAA,CAAK;AAAA,GAChB;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,KAAA,EAAO,OAAA;AAAA,IACP,MAAM,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI;AACxB,MAAA,MAAM,IAAA,GAAO,EAAA,CAAG,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAC5B,MAAA,IAAI,CAAC,QAAQ,IAAA,CAAK,IAAI,KAAK,IAAA,CAAK,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,IAAA;AACnE,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,GAAG,GAAG,OAAO,IAAA;AAEhC,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAMA,mBAAA,CAAe,IAAA,EAAM;AAAA,UACxC,QAAA,EAAU,IAAA;AAAA,UACV,OAAA,EAAS,KAAA;AAAA,UACT,UAAA,EAAY,KAAA;AAAA,UACZ,UAAA,EAAY,IAAA;AAAA,UACZ,YAAY,EAAE,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY,CAAA,EAAE;AAAA,UAC7C,OAAA,EAAS,CAAC,CAACC,+BAAA,EAAe,SAAS,CAAC;AAAA,SACrC,CAAA;AACD,QAAA,IAAI,CAAC,MAAA,EAAQ,IAAA,EAAM,OAAO,IAAA;AAC1B,QAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,MAAM,GAAA,EAAK,MAAA,CAAO,OAAO,IAAA,EAAK;AAAA,MACtD,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,IAAA,CAAK,CAAA,kBAAA,EAAqB,IAAI,CAAA,EAAA,EAAK,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,CAAA;AAC1F,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAAA,GACF;AACF","file":"vite.cjs","sourcesContent":["import { transformAsync } from '@babel/core';\nimport type { Plugin } from 'vite';\nimport { type StampLocOptions, stampLocBabel } from './stampLocBabel';\n\nexport interface StampLocViteOptions extends StampLocOptions {\n /** Which files to stamp. Default /\\.[jt]sx$/. */\n include?: RegExp;\n}\n\n/**\n * Vite plugin: stamps data-loc/data-comp onto JSX host elements.\n *\n * `@vitejs/plugin-react` v6 transpiles via oxc (no Babel hook), so the attributes are added in\n * a separate pre-pass (Babel parse + our plugin only; JSX/TS are preserved) and oxc does the\n * rest. It runs only on the dev server (`apply: 'serve'`) so stamps never reach a prod build.\n *\n * A parse error in a single file does not fail the build that file is simply left unstamped\n * (warning in the console).\n */\nexport function stampLocVite(opts: StampLocViteOptions = {}): Plugin {\n const include = opts.include ?? /\\.[jt]sx$/;\n const babelOpts: StampLocOptions = {\n attrLoc: opts.attrLoc,\n attrComp: opts.attrComp,\n rootDir: opts.rootDir\n };\n\n return {\n name: 'semantic-inspector:stamp-loc',\n enforce: 'pre',\n apply: 'serve',\n async transform(code, id) {\n const file = id.split('?')[0];\n if (!include.test(file) || file.includes('/node_modules/')) return null;\n if (!code.includes('<')) return null; // no JSX tags → nothing to stamp; skip the Babel parse\n\n try {\n const result = await transformAsync(code, {\n filename: file,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['jsx', 'typescript'] },\n plugins: [[stampLocBabel, babelOpts]]\n });\n if (!result?.code) return null;\n return { code: result.code, map: result.map ?? null };\n } catch (err) {\n this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n };\n}\n"]}
package/dist/vite.d.cts CHANGED
@@ -3,18 +3,18 @@ import { StampLocOptions } from './babel.cjs';
3
3
  import '@babel/core';
4
4
 
5
5
  interface StampLocViteOptions extends StampLocOptions {
6
- /** Какие файлы штамповать. Default /\.[jt]sx$/. */
6
+ /** Which files to stamp. Default /\.[jt]sx$/. */
7
7
  include?: RegExp;
8
8
  }
9
9
  /**
10
- * Vite-плагин: штампует data-loc/data-comp на JSX host-элементы.
10
+ * Vite plugin: stamps data-loc/data-comp onto JSX host elements.
11
11
  *
12
- * `@vitejs/plugin-react` v6 транспилит через oxc (без babel-хука), поэтому
13
- * атрибуты добавляем отдельным pre-проходом babel'а (только парсинг + наш
14
- * плагин, JSX/TS сохраняются), а oxc уже делает остальное.
12
+ * `@vitejs/plugin-react` v6 transpiles via oxc (no Babel hook), so the attributes are added in
13
+ * a separate pre-pass (Babel parse + our plugin only; JSX/TS are preserved) and oxc does the
14
+ * rest. It runs only on the dev server (`apply: 'serve'`) so stamps never reach a prod build.
15
15
  *
16
- * Парс-ошибка в отдельном файле не валит сборкуфайл просто остаётся без
17
- * штампов (warn в консоль).
16
+ * A parse error in a single file does not fail the build that file is simply left unstamped
17
+ * (warning in the console).
18
18
  */
19
19
  declare function stampLocVite(opts?: StampLocViteOptions): Plugin;
20
20
 
package/dist/vite.d.ts CHANGED
@@ -3,18 +3,18 @@ import { StampLocOptions } from './babel.js';
3
3
  import '@babel/core';
4
4
 
5
5
  interface StampLocViteOptions extends StampLocOptions {
6
- /** Какие файлы штамповать. Default /\.[jt]sx$/. */
6
+ /** Which files to stamp. Default /\.[jt]sx$/. */
7
7
  include?: RegExp;
8
8
  }
9
9
  /**
10
- * Vite-плагин: штампует data-loc/data-comp на JSX host-элементы.
10
+ * Vite plugin: stamps data-loc/data-comp onto JSX host elements.
11
11
  *
12
- * `@vitejs/plugin-react` v6 транспилит через oxc (без babel-хука), поэтому
13
- * атрибуты добавляем отдельным pre-проходом babel'а (только парсинг + наш
14
- * плагин, JSX/TS сохраняются), а oxc уже делает остальное.
12
+ * `@vitejs/plugin-react` v6 transpiles via oxc (no Babel hook), so the attributes are added in
13
+ * a separate pre-pass (Babel parse + our plugin only; JSX/TS are preserved) and oxc does the
14
+ * rest. It runs only on the dev server (`apply: 'serve'`) so stamps never reach a prod build.
15
15
  *
16
- * Парс-ошибка в отдельном файле не валит сборкуфайл просто остаётся без
17
- * штампов (warn в консоль).
16
+ * A parse error in a single file does not fail the build that file is simply left unstamped
17
+ * (warning in the console).
18
18
  */
19
19
  declare function stampLocVite(opts?: StampLocViteOptions): Plugin;
20
20
 
package/dist/vite.js CHANGED
@@ -1,4 +1,4 @@
1
- import { stampLocBabel } from './chunk-AAPCI2HO.js';
1
+ import { stampLocBabel } from './chunk-AQYKTX6L.js';
2
2
  import { transformAsync } from '@babel/core';
3
3
 
4
4
  function stampLocVite(opts = {}) {
@@ -11,9 +11,11 @@ function stampLocVite(opts = {}) {
11
11
  return {
12
12
  name: "semantic-inspector:stamp-loc",
13
13
  enforce: "pre",
14
+ apply: "serve",
14
15
  async transform(code, id) {
15
16
  const file = id.split("?")[0];
16
17
  if (!include.test(file) || file.includes("/node_modules/")) return null;
18
+ if (!code.includes("<")) return null;
17
19
  try {
18
20
  const result = await transformAsync(code, {
19
21
  filename: file,
@@ -24,7 +26,7 @@ function stampLocVite(opts = {}) {
24
26
  plugins: [[stampLocBabel, babelOpts]]
25
27
  });
26
28
  if (!result?.code) return null;
27
- return { code: result.code, map: result.map };
29
+ return { code: result.code, map: result.map ?? null };
28
30
  } catch (err) {
29
31
  this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);
30
32
  return null;
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":";;;AAmBO,SAAS,YAAA,CAAa,IAAA,GAA4B,EAAC,EAAW;AACnE,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,WAAA;AAChC,EAAA,MAAM,SAAA,GAA6B;AAAA,IACjC,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,SAAS,IAAA,CAAK;AAAA,GAChB;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI;AACxB,MAAA,MAAM,IAAA,GAAO,EAAA,CAAG,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAC5B,MAAA,IAAI,CAAC,QAAQ,IAAA,CAAK,IAAI,KAAK,IAAA,CAAK,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,IAAA;AAEnE,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,IAAA,EAAM;AAAA,UACxC,QAAA,EAAU,IAAA;AAAA,UACV,OAAA,EAAS,KAAA;AAAA,UACT,UAAA,EAAY,KAAA;AAAA,UACZ,UAAA,EAAY,IAAA;AAAA,UACZ,YAAY,EAAE,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY,CAAA,EAAE;AAAA,UAC7C,OAAA,EAAS,CAAC,CAAC,aAAA,EAAe,SAAS,CAAC;AAAA,SACrC,CAAA;AACD,QAAA,IAAI,CAAC,MAAA,EAAQ,IAAA,EAAM,OAAO,IAAA;AAC1B,QAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,GAAA,EAAK,OAAO,GAAA,EAAI;AAAA,MAC9C,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,IAAA,CAAK,CAAA,kBAAA,EAAqB,IAAI,CAAA,EAAA,EAAK,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,CAAA;AAC1F,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAAA,GACF;AACF","file":"vite.js","sourcesContent":["import { transformAsync } from '@babel/core';\nimport type { Plugin } from 'vite';\nimport stampLocBabel, { type StampLocOptions } from './stampLocBabel';\n\nexport interface StampLocViteOptions extends StampLocOptions {\n /** Какие файлы штамповать. Default /\\.[jt]sx$/. */\n include?: RegExp;\n}\n\n/**\n * Vite-плагин: штампует data-loc/data-comp на JSX host-элементы.\n *\n * `@vitejs/plugin-react` v6 транспилит через oxc (без babel-хука), поэтому\n * атрибуты добавляем отдельным pre-проходом babel'а (только парсинг + наш\n * плагин, JSX/TS сохраняются), а oxc уже делает остальное.\n *\n * Парс-ошибка в отдельном файле не валит сборкуфайл просто остаётся без\n * штампов (warn в консоль).\n */\nexport function stampLocVite(opts: StampLocViteOptions = {}): Plugin {\n const include = opts.include ?? /\\.[jt]sx$/;\n const babelOpts: StampLocOptions = {\n attrLoc: opts.attrLoc,\n attrComp: opts.attrComp,\n rootDir: opts.rootDir\n };\n\n return {\n name: 'semantic-inspector:stamp-loc',\n enforce: 'pre',\n async transform(code, id) {\n const file = id.split('?')[0];\n if (!include.test(file) || file.includes('/node_modules/')) return null;\n\n try {\n const result = await transformAsync(code, {\n filename: file,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['jsx', 'typescript'] },\n plugins: [[stampLocBabel, babelOpts]]\n });\n if (!result?.code) return null;\n return { code: result.code, map: result.map };\n } catch (err) {\n this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/vite.ts"],"names":[],"mappings":";;;AAmBO,SAAS,YAAA,CAAa,IAAA,GAA4B,EAAC,EAAW;AACnE,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,WAAA;AAChC,EAAA,MAAM,SAAA,GAA6B;AAAA,IACjC,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,SAAS,IAAA,CAAK;AAAA,GAChB;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,KAAA,EAAO,OAAA;AAAA,IACP,MAAM,SAAA,CAAU,IAAA,EAAM,EAAA,EAAI;AACxB,MAAA,MAAM,IAAA,GAAO,EAAA,CAAG,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AAC5B,MAAA,IAAI,CAAC,QAAQ,IAAA,CAAK,IAAI,KAAK,IAAA,CAAK,QAAA,CAAS,gBAAgB,CAAA,EAAG,OAAO,IAAA;AACnE,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,GAAG,GAAG,OAAO,IAAA;AAEhC,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,IAAA,EAAM;AAAA,UACxC,QAAA,EAAU,IAAA;AAAA,UACV,OAAA,EAAS,KAAA;AAAA,UACT,UAAA,EAAY,KAAA;AAAA,UACZ,UAAA,EAAY,IAAA;AAAA,UACZ,YAAY,EAAE,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY,CAAA,EAAE;AAAA,UAC7C,OAAA,EAAS,CAAC,CAAC,aAAA,EAAe,SAAS,CAAC;AAAA,SACrC,CAAA;AACD,QAAA,IAAI,CAAC,MAAA,EAAQ,IAAA,EAAM,OAAO,IAAA;AAC1B,QAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,MAAM,GAAA,EAAK,MAAA,CAAO,OAAO,IAAA,EAAK;AAAA,MACtD,SAAS,GAAA,EAAK;AACZ,QAAA,IAAA,CAAK,IAAA,CAAK,CAAA,kBAAA,EAAqB,IAAI,CAAA,EAAA,EAAK,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,CAAA;AAC1F,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAAA,GACF;AACF","file":"vite.js","sourcesContent":["import { transformAsync } from '@babel/core';\nimport type { Plugin } from 'vite';\nimport { type StampLocOptions, stampLocBabel } from './stampLocBabel';\n\nexport interface StampLocViteOptions extends StampLocOptions {\n /** Which files to stamp. Default /\\.[jt]sx$/. */\n include?: RegExp;\n}\n\n/**\n * Vite plugin: stamps data-loc/data-comp onto JSX host elements.\n *\n * `@vitejs/plugin-react` v6 transpiles via oxc (no Babel hook), so the attributes are added in\n * a separate pre-pass (Babel parse + our plugin only; JSX/TS are preserved) and oxc does the\n * rest. It runs only on the dev server (`apply: 'serve'`) so stamps never reach a prod build.\n *\n * A parse error in a single file does not fail the build that file is simply left unstamped\n * (warning in the console).\n */\nexport function stampLocVite(opts: StampLocViteOptions = {}): Plugin {\n const include = opts.include ?? /\\.[jt]sx$/;\n const babelOpts: StampLocOptions = {\n attrLoc: opts.attrLoc,\n attrComp: opts.attrComp,\n rootDir: opts.rootDir\n };\n\n return {\n name: 'semantic-inspector:stamp-loc',\n enforce: 'pre',\n apply: 'serve',\n async transform(code, id) {\n const file = id.split('?')[0];\n if (!include.test(file) || file.includes('/node_modules/')) return null;\n if (!code.includes('<')) return null; // no JSX tags → nothing to stamp; skip the Babel parse\n\n try {\n const result = await transformAsync(code, {\n filename: file,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['jsx', 'typescript'] },\n plugins: [[stampLocBabel, babelOpts]]\n });\n if (!result?.code) return null;\n return { code: result.code, map: result.map ?? null };\n } catch (err) {\n this.warn(`stamp-loc skipped ${file}: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n };\n}\n"]}