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/LICENSE +1 -1
- package/README.md +120 -49
- package/dist/babel.cjs +7 -59
- package/dist/babel.cjs.map +1 -1
- package/dist/babel.d.cts +10 -10
- package/dist/babel.d.ts +10 -10
- package/dist/babel.js +1 -1
- package/dist/{chunk-AAPCI2HO.js → chunk-AQYKTX6L.js} +13 -9
- package/dist/chunk-AQYKTX6L.js.map +1 -0
- package/dist/chunk-X6NMJJ2M.cjs +68 -0
- package/dist/chunk-X6NMJJ2M.cjs.map +1 -0
- package/dist/index.cjs +95 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -41
- package/dist/index.d.ts +37 -41
- package/dist/index.js +98 -53
- package/dist/index.js.map +1 -1
- package/dist/vite.cjs +5 -64
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +7 -7
- package/dist/vite.d.ts +7 -7
- package/dist/vite.js +4 -2
- package/dist/vite.js.map +1 -1
- package/package.json +24 -8
- package/dist/chunk-AAPCI2HO.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
138
|
+
function parseHotkey(hotkey) {
|
|
127
139
|
const parts = hotkey.split("+").map((p) => p.trim().toLowerCase());
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
setToast(
|
|
215
|
-
clearTimeout(
|
|
216
|
-
|
|
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
|
-
|
|
269
|
+
setToast(kind === "text" ? `\u2713 ${payload}` : "\u2713 screenshot copied");
|
|
225
270
|
props.onCopy?.(kind, payload);
|
|
226
271
|
},
|
|
227
272
|
onError: (kind, err) => {
|
|
228
|
-
|
|
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,
|
|
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":";;;;AAKA,IAAM,CAAA,GAAI,UAAA;AAEV,IAAM,KAAA,GAAQ;AAAA,EACZ,QAAA,EAAU,OAAA;AAAA,EACV,MAAA,EAAQ,EAAA;AAAA,EACR,IAAA,EAAM,EAAA;AAAA,EACN,QAAQ,CAAA,GAAI,CAAA;AAAA,EACZ,OAAA,EAAS,UAAA;AAAA,EACT,YAAA,EAAc,CAAA;AAAA,EACd,IAAA,EAAM,yDAAA;AAAA,EACN,UAAA,EAAY,qBAAA;AAAA,EACZ,KAAA,EAAO,MAAA;AAAA,EACP,aAAA,EAAe,MAAA;AAAA,EACf,SAAA,EAAW;AACb,CAAA;AAEA,IAAM,UAAA,GAAa;AAAA,EACjB,QAAA,EAAU,OAAA;AAAA,EACV,MAAA,EAAQ,EAAA;AAAA,EACR,KAAA,EAAO,EAAA;AAAA,EACP,QAAQ,CAAA,GAAI,CAAA;AAAA,EACZ,OAAA,EAAS,UAAA;AAAA,EACT,YAAA,EAAc,CAAA;AAAA,EACd,IAAA,EAAM,yDAAA;AAAA,EACN,UAAA,EAAY,sBAAA;AAAA,EACZ,KAAA,EAAO,MAAA;AAAA,EACP,aAAA,EAAe,MAAA;AAAA,EACf,QAAA,EAAU,MAAA;AAAA,EACV,UAAA,EAAY,QAAA;AAAA,EACZ,QAAA,EAAU,QAAA;AAAA,EACV,YAAA,EAAc;AAChB,CAAA;AAEA,SAAS,SAAS,CAAA,EAA2B;AAC3C,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,OAAA;AAAA,IACV,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,KAAK,CAAA,CAAE,GAAA;AAAA,IACP,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,MAAA,EAAQ,CAAA;AAAA,IACR,OAAA,EAAS,mBAAA;AAAA,IACT,UAAA,EAAY,uBAAA;AAAA,IACZ,aAAA,EAAe,MAAA;AAAA,IACf,UAAA,EAAY;AAAA,GACd;AACF;AAEA,SAAS,SAAS,CAAA,EAA2B;AAC3C,EAAA,MAAM,GAAA,GAAM,EAAE,GAAA,GAAM,EAAA,GAAK,EAAE,GAAA,GAAM,EAAA,GAAK,EAAE,MAAA,GAAS,CAAA;AACjD,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,OAAA;AAAA,IACV,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,GAAA;AAAA,IACA,QAAQ,CAAA,GAAI,CAAA;AAAA,IACZ,OAAA,EAAS,SAAA;AAAA,IACT,YAAA,EAAc,CAAA;AAAA,IACd,IAAA,EAAM,yDAAA;AAAA,IACN,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA,EAAO,MAAA;AAAA,IACP,aAAA,EAAe,MAAA;AAAA,IACf,UAAA,EAAY;AAAA,GACd;AACF;AAOO,IAAM,OAAA,GAAqE,IAAA,CAAK,SAASA,QAAAA,CAAQ;AAAA,EACtG,MAAA;AAAA,EACA;AACF,CAAA,EAA8B;AAC5B,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,QAAA,EAAA,oEAAA,EAA+C,CAAA;AAAA,IACjE,0BACC,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA,EAAG,CAAA;AAAA,2BAClC,KAAA,EAAA,EAAI,KAAA,EAAO,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA,EAC7B,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,IAAA;AAAA,6BACP,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,OAAA,EAAS,MAAK,EAAG,QAAA,EAAA;AAAA,UAAA,QAAA;AAAA,UAAI,OAAO,GAAA,IAAO;AAAA,SAAA,EAAY;AAAA,OAAA,EAChE;AAAA,KAAA,EACF,CAAA;AAAA,IAED,KAAA,oBAAS,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,YAAa,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EAC3C,CAAA;AAEJ,CAAC,CAAA;;;AC7FD,eAAsB,SAAS,IAAA,EAA6B;AAC1D,EAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,CAAC,UAAU,SAAA,EAAW;AAC5D,IAAA,MAAM,IAAI,MAAM,wEAAwE,CAAA;AAAA,EAC1F;AACA,EAAA,MAAM,SAAA,CAAU,SAAA,CAAU,SAAA,CAAU,IAAI,CAAA;AAC1C;AASA,eAAsB,gBAAgB,EAAA,EAA4B;AAChE,EAAA,IAAI,OAAO,cAAc,WAAA,IAAe,CAAC,UAAU,SAAA,EAAW,KAAA,IAAS,OAAO,aAAA,KAAkB,WAAA,EAAa;AAC3G,IAAA,MAAM,IAAI,MAAM,gFAAgF,CAAA;AAAA,EAClG;AACA,EAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,OAAO,mBAAmB,CAAA;AAEtD,EAAA,MAAM,OAAO,MAAM,SAAA,CAAU,IAAmB,EAAE,KAAA,EAAO,GAAG,CAAA;AAC5D,EAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,gCAAgC,CAAA;AAC3D,EAAA,MAAM,SAAA,CAAU,SAAA,CAAU,KAAA,CAAM,CAAC,IAAI,aAAA,CAAc,EAAE,WAAA,EAAa,IAAA,EAAM,CAAC,CAAC,CAAA;AAC5E;;;ACtBA,IAAM,QAAA,GAAW,UAAA;AACjB,IAAM,SAAA,GAAY,WAAA;AASX,SAAS,cAAc,EAAA,EAA0C;AACtE,EAAA,IAAI,CAAC,IAAI,OAAO,IAAA;AAChB,EAAA,MAAM,SAAS,EAAA,CAAG,OAAA,CAAQ,CAAA,CAAA,EAAI,QAAQ,GAAG,CAAA,IAAK,EAAA;AAC9C,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,QAAQ,CAAA;AACxC,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,YAAA,CAAa,SAAS,CAAA,IAAK,UAAU,MAAM,CAAA,IAAK,YAAA,CAAa,MAAA,EAAQ,GAAG,CAAA;AAC5F,EAAA,OAAO,EAAE,MAAM,GAAA,EAAK,EAAA,EAAI,QAAQ,IAAA,EAAM,MAAA,CAAO,uBAAsB,EAAE;AACvE;AAEA,SAAS,YAAA,CAAa,IAAa,GAAA,EAA4B;AAC7D,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,MAAM,IAAA,GAAO,GAAA,CACV,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA,CACZ,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,EAAI,EACH,OAAA,CAAQ,cAAc,EAAE,CAAA;AAC5B,IAAA,IAAI,MAAM,OAAO,IAAA;AAAA,EACnB;AACA,EAAA,OAAO,EAAA,CAAG,QAAQ,WAAA,EAAY;AAChC;AASA,SAAS,UAAU,EAAA,EAA4B;AAC7C,EAAA,MAAM,IAAA,GAAO,EAAA;AACb,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,IAAA,CAAK,IAAI,EAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,eAAe,CAAA,IAAK,CAAA,CAAE,UAAA,CAAW,0BAA0B,CAAC,CAAA;AACnH,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI,KAAA,GAA0B,IAAA,CAAK,GAAG,CAAA,IAAK,IAAA;AAC3C,EAAA,OAAO,KAAA,EAAO;AACZ,IAAA,MAAM,IAAI,KAAA,CAAM,IAAA;AAChB,IAAA,MAAM,IAAA,GAAO,KAAK,OAAO,CAAA,KAAM,WAAY,CAAA,CAAE,WAAA,IAAe,EAAE,IAAA,GAAQ,MAAA;AACtE,IAAA,IAAI,IAAA,IAAQ,QAAA,CAAS,IAAA,CAAK,IAAI,GAAG,OAAO,IAAA;AACxC,IAAA,KAAA,GAAQ,KAAA,CAAM,MAAA;AAAA,EAChB;AACA,EAAA,OAAO,IAAA;AACT;;;AC9CA,IAAM,cAAA,GAAiB,aAAA;AAEvB,SAAS,cAAc,CAAA,EAAoB;AACzC,EAAA,OAAO,CAAA,CAAE,MAAM,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,QAAA,EAAM,CAAA,CAAE,GAAG,CAAA,CAAA,GAAK,CAAA,CAAE,IAAA;AAC5C;AAWA,SAAS,YAAY,MAAA,EAAwB;AAC3C,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,EAAK,CAAE,WAAA,EAAa,CAAA;AACjE,EAAA,MAAM,GAAA,GAAM,CAAA,GAAI,KAAA,KAA6B,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,KAAA,CAAM,QAAA,CAAS,CAAC,CAAC,CAAA;AAChF,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACnC,EAAA,OAAO;AAAA,IACL,GAAA,EAAK,IAAI,KAAK,CAAA;AAAA,IACd,KAAA,EAAO,IAAI,OAAO,CAAA;AAAA,IAClB,IAAA,EAAM,GAAA,CAAI,MAAA,EAAQ,SAAS,CAAA;AAAA,IAC3B,IAAA,EAAM,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA;AAAA,IACvB,GAAA,EAAK,IAAA,KAAS,EAAA,GAAK,GAAA,GAAM;AAAA,GAC3B;AACF;AAIA,IAAM,WAAA,GAAsC;AAAA,EAC1C,KAAA,EAAO,GAAA;AAAA,EACP,SAAA,EAAW,IAAA;AAAA,EACX,MAAA,EAAQ,GAAA;AAAA,EACR,KAAA,EAAO,GAAA;AAAA,EACP,SAAA,EAAW,GAAA;AAAA,EACX,KAAA,EAAO,GAAA;AAAA,EACP,SAAA,EAAW,GAAA;AAAA,EACX,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,KAAA,EAAO,GAAA;AAAA,EACP,KAAA,EAAO;AACT,CAAA;AAGA,SAAS,WAAA,CAAY,GAAkB,EAAA,EAAqB;AAC1D,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,EAAA,CAAG,GAAA,IAAO,EAAE,QAAA,KAAa,EAAA,CAAG,KAAA,IAAS,CAAA,CAAE,YAAY,EAAA,CAAG,IAAA,IAAQ,CAAA,CAAE,OAAA,KAAY,GAAG,IAAA,EAAM;AACpG,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAAU,CAAA,CAAE,IAAA,CAAK,WAAA,EAAY;AACnC,EAAA,MAAM,OAAO,WAAA,CAAY,OAAO,KAAK,OAAA,CAAQ,OAAA,CAAQ,gBAAgB,EAAE,CAAA;AACvE,EAAA,OAAO,EAAE,GAAA,CAAI,WAAA,OAAkB,EAAA,CAAG,GAAA,IAAO,SAAS,EAAA,CAAG,GAAA;AACvD;AAEA,SAAS,UAAA,CAAW,GAAyB,CAAA,EAAkC;AAC7E,EAAA,IAAI,CAAC,CAAA,IAAK,CAAC,CAAA,SAAU,CAAA,KAAM,CAAA;AAC3B,EAAA,OACE,CAAA,CAAE,EAAA,KAAO,CAAA,CAAE,EAAA,IACX,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,CAAA,CAAE,IAAA,CAAK,IAAA,IACvB,CAAA,CAAE,IAAA,CAAK,GAAA,KAAQ,EAAE,IAAA,CAAK,GAAA,IACtB,CAAA,CAAE,IAAA,CAAK,KAAA,KAAU,CAAA,CAAE,IAAA,CAAK,KAAA,IACxB,CAAA,CAAE,IAAA,CAAK,MAAA,KAAW,CAAA,CAAE,IAAA,CAAK,MAAA;AAE7B;AAQO,SAAS,YAAA,CAAa,IAAA,GAA+B,EAAC,EAAuB;AAClF,EAAA,MAAM,EAAE,MAAA,GAAS,cAAA,EAAe,GAAI,IAAA;AACpC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA+B,IAAI,CAAA;AAG/D,EAAA,MAAM,KAAA,GAAQ,OAA+B,IAAI,CAAA;AACjD,EAAA,KAAA,CAAM,OAAA,GAAU,IAAA;AAGhB,EAAA,MAAM,SAAA,GAAY,OAA6B,IAAI,CAAA;AAEnD,EAAA,MAAM,EAAA,GAAK,QAAQ,MAAM,WAAA,CAAY,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAS,MAAM,CAAA,EAAwB;AACrC,MAAA,IAAI,WAAA,CAAY,CAAA,EAAG,EAAE,CAAA,EAAG;AACtB,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,SAAA,CAAU,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,MACrB,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU;AAC7B,QAAA,SAAA,CAAU,CAAC,CAAA,KAAO,CAAA,GAAI,KAAA,GAAQ,CAAE,CAAA;AAAA,MAClC;AAAA,IACF;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,KAAK,CAAA;AACxC,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,KAAK,CAAA;AAAA,EAC1D,CAAA,EAAG,CAAC,EAAE,CAAC,CAAA;AAEP,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,IAAI,YAAA,GAAe,KAAA;AAEnB,IAAA,SAAS,OAAO,CAAA,EAAqB;AACnC,MAAA,KAAA,GAAQ,CAAA,CAAE,OAAA;AACV,MAAA,KAAA,GAAQ,CAAA,CAAE,OAAA;AACV,MAAA,IAAI,KAAA,EAAO;AACX,MAAA,KAAA,GAAQ,sBAAsB,MAAM;AAClC,QAAA,KAAA,GAAQ,CAAA;AACR,QAAA,MAAM,OAAO,aAAA,CAAc,QAAA,CAAS,gBAAA,CAAiB,KAAA,EAAO,KAAK,CAAC,CAAA;AAClE,QAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,QAAA,SAAA,CAAU,CAAC,IAAA,KAAU,UAAA,CAAW,MAAM,IAAI,CAAA,GAAI,OAAO,IAAK,CAAA;AAAA,MAC5D,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,SAAS,QAAQ,CAAA,EAAqB;AACpC,MAAA,MAAM,CAAA,GAAI,SAAA,CAAU,OAAA,IAAW,aAAA,CAAc,QAAA,CAAS,iBAAiB,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,OAAO,CAAC,CAAA;AAC5F,MAAA,IAAI,CAAC,CAAA,EAAG;AACR,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,CAAA,CAAE,eAAA,EAAgB;AAClB,MAAA,MAAM,EAAE,UAAA,GAAa,aAAA,EAAe,MAAA,EAAQ,OAAA,KAAY,KAAA,CAAM,OAAA;AAC9D,MAAA,MAAM,OAAO,CAAC,IAAA,EAAgB,OAAA,KAA0B,MAAA,GAAS,MAAM,OAAO,CAAA;AAC9E,MAAA,MAAM,IAAA,GAAO,CAAC,IAAA,EAAgB,GAAA,KAAuB;AACnD,QAAA,IAAI,OAAA,EAAS,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA;AAAA,aAEzB,OAAA,CAAQ,IAAA,CAAK,CAAA,qBAAA,EAAwB,IAAI,iBAAiB,GAAG,CAAA;AAAA,MACpE,CAAA;AAEA,MAAA,IAAI,EAAE,QAAA,EAAU;AACd,QAAA,IAAI,YAAA,EAAc;AAClB,QAAA,YAAA,GAAe,IAAA;AACf,QAAA,eAAA,CAAgB,CAAA,CAAE,EAAE,CAAA,CACjB,IAAA;AAAA,UACC,MAAM,IAAA,CAAK,YAAA,EAAc,CAAA,CAAE,IAAI,CAAA;AAAA,UAC/B,CAAC,GAAA,KAAiB,IAAA,CAAK,YAAA,EAAc,GAAG;AAAA,SAC1C,CACC,QAAQ,MAAM;AACb,UAAA,YAAA,GAAe,KAAA;AAAA,QACjB,CAAC,CAAA;AAAA,MACL,CAAA,MAAO;AACL,QAAA,MAAM,IAAA,GAAO,WAAW,EAAE,IAAA,EAAM,EAAE,IAAA,EAAM,GAAA,EAAK,CAAA,CAAE,GAAA,EAAK,CAAA;AACpD,QAAA,QAAA,CAAS,IAAI,CAAA,CAAE,IAAA;AAAA,UACb,MAAM,IAAA,CAAK,MAAA,EAAQ,IAAI,CAAA;AAAA,UACvB,CAAC,GAAA,KAAiB,IAAA,CAAK,MAAA,EAAQ,GAAG;AAAA,SACpC;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAA,CAAO,gBAAA,CAAiB,aAAa,MAAA,EAAQ,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,MAAM,CAAA;AAC7E,IAAA,MAAA,CAAO,gBAAA,CAAiB,OAAA,EAAS,OAAA,EAAS,IAAI,CAAA;AAC9C,IAAA,MAAM,UAAA,GAAa,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,MAAA;AACvC,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,WAAA;AAC7B,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAA,EAAa,MAAA,EAAQ,IAAI,CAAA;AACpD,MAAA,MAAA,CAAO,mBAAA,CAAoB,OAAA,EAAS,OAAA,EAAS,IAAI,CAAA;AACjD,MAAA,IAAI,KAAA,uBAA4B,KAAK,CAAA;AACrC,MAAA,QAAA,CAAS,IAAA,CAAK,MAAM,MAAA,GAAS,UAAA;AAAA,IAC/B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B;ACxKA,IAAM,QAAA,GAAW,IAAA;AAOV,SAAS,kBAAkB,KAAA,EAAmD;AACnF,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,SAAwB,IAAI,CAAA;AAItD,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,MAAM,KAAK,UAAA,CAAW,MAAM,QAAA,CAAS,IAAI,GAAG,QAAQ,CAAA;AACpD,IAAA,OAAO,MAAM,aAAa,EAAE,CAAA;AAAA,EAC9B,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,YAAA,CAAa;AAAA,IACtC,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,YAAY,KAAA,CAAM,UAAA;AAAA,IAClB,MAAA,EAAQ,CAAC,IAAA,EAAgB,OAAA,KAAoB;AAC3C,MAAA,QAAA,CAAS,IAAA,KAAS,MAAA,GAAS,CAAA,OAAA,EAAK,OAAO,KAAK,0BAAqB,CAAA;AACjE,MAAA,KAAA,CAAM,MAAA,GAAS,MAAM,OAAO,CAAA;AAAA,IAC9B,CAAA;AAAA,IACA,OAAA,EAAS,CAAC,IAAA,EAAgB,GAAA,KAAiB;AACzC,MAAA,QAAA,CAAS,CAAA,OAAA,EAAK,IAAI,CAAA,OAAA,CAAS,CAAA;AAC3B,MAAA,KAAA,CAAM,OAAA,GAAU,MAAM,GAAG,CAAA;AAAA,IAC3B;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,KAAA,EAAO,OAAO,IAAA;AAC9B,EAAA,uBAAOC,GAAAA,CAAC,OAAA,EAAA,EAAQ,QAAQ,MAAA,GAAS,MAAA,GAAS,MAAM,KAAA,EAAc,CAAA;AAChE","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/
|
|
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
|
-
/**
|
|
6
|
+
/** Which files to stamp. Default /\.[jt]sx$/. */
|
|
7
7
|
include?: RegExp;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Vite
|
|
10
|
+
* Vite plugin: stamps data-loc/data-comp onto JSX host elements.
|
|
11
11
|
*
|
|
12
|
-
* `@vitejs/plugin-react` v6
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
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
|
-
/**
|
|
6
|
+
/** Which files to stamp. Default /\.[jt]sx$/. */
|
|
7
7
|
include?: RegExp;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Vite
|
|
10
|
+
* Vite plugin: stamps data-loc/data-comp onto JSX host elements.
|
|
11
11
|
*
|
|
12
|
-
* `@vitejs/plugin-react` v6
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
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-
|
|
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;
|
|
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"]}
|