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.cjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
-
var modernScreenshot = require('modern-screenshot');
|
|
6
5
|
|
|
7
6
|
// src/SemanticInspector.tsx
|
|
8
7
|
var Z = 2147483600;
|
|
@@ -65,7 +64,10 @@ function tipStyle(r) {
|
|
|
65
64
|
whiteSpace: "nowrap"
|
|
66
65
|
};
|
|
67
66
|
}
|
|
68
|
-
|
|
67
|
+
var Overlay = react.memo(function Overlay2({
|
|
68
|
+
target,
|
|
69
|
+
toast
|
|
70
|
+
}) {
|
|
69
71
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
70
72
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: badge, children: "\u2316 inspect \xB7 click=name \xB7 \u21E7click=shot \xB7 Esc=exit" }),
|
|
71
73
|
target && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -80,12 +82,21 @@ function Overlay({ target, toast }) {
|
|
|
80
82
|
] }),
|
|
81
83
|
toast && /* @__PURE__ */ jsxRuntime.jsx("div", { style: toastStyle, children: toast })
|
|
82
84
|
] });
|
|
83
|
-
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// src/clipboard.ts
|
|
84
88
|
async function copyText(text) {
|
|
89
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) {
|
|
90
|
+
throw new Error("Clipboard API unavailable (needs a secure context: https or localhost)");
|
|
91
|
+
}
|
|
85
92
|
await navigator.clipboard.writeText(text);
|
|
86
93
|
}
|
|
87
94
|
async function copyElementShot(el) {
|
|
88
|
-
|
|
95
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
|
|
96
|
+
throw new Error("Clipboard image write unsupported (needs a secure context: https or localhost)");
|
|
97
|
+
}
|
|
98
|
+
const { domToBlob } = await import('modern-screenshot');
|
|
99
|
+
const blob = await domToBlob(el, { scale: 1 });
|
|
89
100
|
if (!blob) throw new Error("screenshot produced empty blob");
|
|
90
101
|
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
|
|
91
102
|
}
|
|
@@ -108,9 +119,10 @@ function fallbackName(el, loc) {
|
|
|
108
119
|
return el.tagName.toLowerCase();
|
|
109
120
|
}
|
|
110
121
|
function fiberName(el) {
|
|
111
|
-
const
|
|
122
|
+
const host = el;
|
|
123
|
+
const key = Object.keys(host).find((k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$"));
|
|
112
124
|
if (!key) return null;
|
|
113
|
-
let fiber =
|
|
125
|
+
let fiber = host[key] ?? null;
|
|
114
126
|
while (fiber) {
|
|
115
127
|
const t = fiber.type;
|
|
116
128
|
const name = t && typeof t !== "string" ? t.displayName ?? t.name : void 0;
|
|
@@ -125,15 +137,42 @@ var DEFAULT_HOTKEY = "Alt+Shift+S";
|
|
|
125
137
|
function defaultFormat(t) {
|
|
126
138
|
return t.loc ? `${t.comp} \u2014 ${t.loc}` : t.comp;
|
|
127
139
|
}
|
|
128
|
-
function
|
|
140
|
+
function parseHotkey(hotkey) {
|
|
129
141
|
const parts = hotkey.split("+").map((p) => p.trim().toLowerCase());
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
const has = (...names) => names.some((n) => parts.includes(n));
|
|
143
|
+
const last = parts[parts.length - 1];
|
|
144
|
+
return {
|
|
145
|
+
alt: has("alt"),
|
|
146
|
+
shift: has("shift"),
|
|
147
|
+
ctrl: has("ctrl", "control"),
|
|
148
|
+
meta: has("meta", "cmd"),
|
|
149
|
+
key: last === "" ? "+" : last
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
var CODE_TO_KEY = {
|
|
153
|
+
slash: "/",
|
|
154
|
+
backslash: "\\",
|
|
155
|
+
period: ".",
|
|
156
|
+
comma: ",",
|
|
157
|
+
semicolon: ";",
|
|
158
|
+
quote: "'",
|
|
159
|
+
backquote: "`",
|
|
160
|
+
bracketleft: "[",
|
|
161
|
+
bracketright: "]",
|
|
162
|
+
minus: "-",
|
|
163
|
+
equal: "="
|
|
164
|
+
};
|
|
165
|
+
function matchHotkey(e, hk) {
|
|
166
|
+
if (e.altKey !== hk.alt || e.shiftKey !== hk.shift || e.ctrlKey !== hk.ctrl || e.metaKey !== hk.meta) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const rawCode = e.code.toLowerCase();
|
|
170
|
+
const code = CODE_TO_KEY[rawCode] ?? rawCode.replace(/^(key|digit)/, "");
|
|
171
|
+
return e.key.toLowerCase() === hk.key || code === hk.key;
|
|
172
|
+
}
|
|
173
|
+
function sameTarget(a, b) {
|
|
174
|
+
if (!a || !b) return a === b;
|
|
175
|
+
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;
|
|
137
176
|
}
|
|
138
177
|
function useInspector(opts = {}) {
|
|
139
178
|
const { hotkey = DEFAULT_HOTKEY } = opts;
|
|
@@ -141,68 +180,77 @@ function useInspector(opts = {}) {
|
|
|
141
180
|
const [target, setTarget] = react.useState(null);
|
|
142
181
|
const cbRef = react.useRef(opts);
|
|
143
182
|
cbRef.current = opts;
|
|
183
|
+
const targetRef = react.useRef(null);
|
|
184
|
+
const hk = react.useMemo(() => parseHotkey(hotkey), [hotkey]);
|
|
144
185
|
react.useEffect(() => {
|
|
145
186
|
function onKey(e) {
|
|
146
|
-
if (matchHotkey(e,
|
|
187
|
+
if (matchHotkey(e, hk)) {
|
|
147
188
|
e.preventDefault();
|
|
148
189
|
setActive((a) => !a);
|
|
149
190
|
} else if (e.key === "Escape") {
|
|
150
|
-
setActive(false);
|
|
191
|
+
setActive((a) => a ? false : a);
|
|
151
192
|
}
|
|
152
193
|
}
|
|
153
194
|
window.addEventListener("keydown", onKey);
|
|
154
|
-
return () =>
|
|
155
|
-
|
|
156
|
-
};
|
|
157
|
-
}, [hotkey]);
|
|
195
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
196
|
+
}, [hk]);
|
|
158
197
|
react.useEffect(() => {
|
|
159
198
|
if (!active) {
|
|
199
|
+
targetRef.current = null;
|
|
160
200
|
setTarget(null);
|
|
161
201
|
return;
|
|
162
202
|
}
|
|
203
|
+
let rafId = 0;
|
|
204
|
+
let lastX = 0;
|
|
205
|
+
let lastY = 0;
|
|
206
|
+
let shotInFlight = false;
|
|
163
207
|
function onMove(e) {
|
|
164
|
-
|
|
208
|
+
lastX = e.clientX;
|
|
209
|
+
lastY = e.clientY;
|
|
210
|
+
if (rafId) return;
|
|
211
|
+
rafId = requestAnimationFrame(() => {
|
|
212
|
+
rafId = 0;
|
|
213
|
+
const next = resolveTarget(document.elementFromPoint(lastX, lastY));
|
|
214
|
+
targetRef.current = next;
|
|
215
|
+
setTarget((prev) => sameTarget(prev, next) ? prev : next);
|
|
216
|
+
});
|
|
165
217
|
}
|
|
166
218
|
function onClick(e) {
|
|
167
|
-
const t = resolveTarget(document.elementFromPoint(e.clientX, e.clientY));
|
|
219
|
+
const t = targetRef.current ?? resolveTarget(document.elementFromPoint(e.clientX, e.clientY));
|
|
168
220
|
if (!t) return;
|
|
169
221
|
e.preventDefault();
|
|
170
222
|
e.stopPropagation();
|
|
171
223
|
const { formatText = defaultFormat, onCopy, onError } = cbRef.current;
|
|
172
|
-
const done = (kind, payload) =>
|
|
173
|
-
onCopy?.(kind, payload);
|
|
174
|
-
};
|
|
224
|
+
const done = (kind, payload) => onCopy?.(kind, payload);
|
|
175
225
|
const fail = (kind, err) => {
|
|
176
|
-
onError
|
|
226
|
+
if (onError) onError(kind, err);
|
|
227
|
+
else console.warn(`[semantic-inspector] ${kind} copy failed:`, err);
|
|
177
228
|
};
|
|
178
229
|
if (e.shiftKey) {
|
|
230
|
+
if (shotInFlight) return;
|
|
231
|
+
shotInFlight = true;
|
|
179
232
|
copyElementShot(t.el).then(
|
|
180
|
-
() =>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
);
|
|
233
|
+
() => done("screenshot", t.comp),
|
|
234
|
+
(err) => fail("screenshot", err)
|
|
235
|
+
).finally(() => {
|
|
236
|
+
shotInFlight = false;
|
|
237
|
+
});
|
|
187
238
|
} else {
|
|
188
239
|
const text = formatText({ comp: t.comp, loc: t.loc });
|
|
189
240
|
copyText(text).then(
|
|
190
|
-
() =>
|
|
191
|
-
|
|
192
|
-
},
|
|
193
|
-
(err) => {
|
|
194
|
-
fail("text", err);
|
|
195
|
-
}
|
|
241
|
+
() => done("text", text),
|
|
242
|
+
(err) => fail("text", err)
|
|
196
243
|
);
|
|
197
244
|
}
|
|
198
245
|
}
|
|
199
|
-
window.addEventListener("mousemove", onMove, true);
|
|
246
|
+
window.addEventListener("mousemove", onMove, { capture: true, passive: true });
|
|
200
247
|
window.addEventListener("click", onClick, true);
|
|
201
248
|
const prevCursor = document.body.style.cursor;
|
|
202
249
|
document.body.style.cursor = "crosshair";
|
|
203
250
|
return () => {
|
|
204
251
|
window.removeEventListener("mousemove", onMove, true);
|
|
205
252
|
window.removeEventListener("click", onClick, true);
|
|
253
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
206
254
|
document.body.style.cursor = prevCursor;
|
|
207
255
|
};
|
|
208
256
|
}, [active]);
|
|
@@ -211,23 +259,20 @@ function useInspector(opts = {}) {
|
|
|
211
259
|
var TOAST_MS = 1400;
|
|
212
260
|
function SemanticInspector(props) {
|
|
213
261
|
const [toast, setToast] = react.useState(null);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
setToast(
|
|
217
|
-
clearTimeout(
|
|
218
|
-
|
|
219
|
-
setToast(null);
|
|
220
|
-
}, TOAST_MS);
|
|
221
|
-
};
|
|
262
|
+
react.useEffect(() => {
|
|
263
|
+
if (toast == null) return;
|
|
264
|
+
const id = setTimeout(() => setToast(null), TOAST_MS);
|
|
265
|
+
return () => clearTimeout(id);
|
|
266
|
+
}, [toast]);
|
|
222
267
|
const { active, target } = useInspector({
|
|
223
268
|
hotkey: props.hotkey,
|
|
224
269
|
formatText: props.formatText,
|
|
225
270
|
onCopy: (kind, payload) => {
|
|
226
|
-
|
|
271
|
+
setToast(kind === "text" ? `\u2713 ${payload}` : "\u2713 screenshot copied");
|
|
227
272
|
props.onCopy?.(kind, payload);
|
|
228
273
|
},
|
|
229
274
|
onError: (kind, err) => {
|
|
230
|
-
|
|
275
|
+
setToast(`\u2717 ${kind} failed`);
|
|
231
276
|
props.onError?.(kind, err);
|
|
232
277
|
}
|
|
233
278
|
});
|
|
@@ -236,9 +281,6 @@ function SemanticInspector(props) {
|
|
|
236
281
|
}
|
|
237
282
|
|
|
238
283
|
exports.SemanticInspector = SemanticInspector;
|
|
239
|
-
exports.copyElementShot = copyElementShot;
|
|
240
|
-
exports.copyText = copyText;
|
|
241
|
-
exports.resolveTarget = resolveTarget;
|
|
242
284
|
exports.useInspector = useInspector;
|
|
243
285
|
//# sourceMappingURL=index.cjs.map
|
|
244
286
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Overlay.tsx","../src/clipboard.ts","../src/resolveTarget.ts","../src/useInspector.ts","../src/SemanticInspector.tsx"],"names":["jsxs","Fragment","jsx","domToBlob","useState","useRef","useEffect"],"mappings":"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","file":"index.cjs","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":["memo","Overlay","jsxs","Fragment","jsx","useState","useRef","useMemo","useEffect"],"mappings":"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","file":"index.cjs","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/index.d.cts
CHANGED
|
@@ -1,62 +1,58 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { JSX } from 'react';
|
|
2
2
|
|
|
3
3
|
type CopyKind = 'text' | 'screenshot';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Component name + source location resolved for an inspected element. */
|
|
5
|
+
interface LocInfo {
|
|
6
|
+
/** Component name (from data-comp) or a fallback (fiber displayName / file name / tag). */
|
|
6
7
|
comp: string;
|
|
7
|
-
/** "<path>:<line>"
|
|
8
|
+
/** "<path>:<line>:<col>" from data-loc, or null when the element is not stamped. */
|
|
8
9
|
loc: string | null;
|
|
9
|
-
|
|
10
|
+
}
|
|
11
|
+
interface InspectTarget extends LocInfo {
|
|
12
|
+
/** The resolved DOM element (nearest ancestor with data-loc, or the element itself). */
|
|
10
13
|
el: Element;
|
|
11
|
-
/**
|
|
14
|
+
/** Geometry for the overlay highlight. */
|
|
12
15
|
rect: DOMRect;
|
|
13
16
|
}
|
|
14
17
|
interface SemanticInspectorProps {
|
|
15
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Toggle hotkey. Default 'Alt+Shift+S'.
|
|
20
|
+
*
|
|
21
|
+
* Format: modifiers (`Alt`, `Shift`, `Ctrl`/`Control`, `Meta`/`Cmd`) joined with `+`
|
|
22
|
+
* followed by a final key, e.g. 'Ctrl+Cmd+I'. Matching is case-insensitive and also
|
|
23
|
+
* accepts the physical `event.code` (so layout-shifted glyphs still work). `Esc` always exits.
|
|
24
|
+
*/
|
|
16
25
|
hotkey?: string;
|
|
17
|
-
/**
|
|
18
|
-
formatText?: (t:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
/** Formats the clipboard text. Default: `${comp} — ${loc}` (or `${comp}` when loc is null). */
|
|
27
|
+
formatText?: (t: LocInfo) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Called after a successful copy. For kind `'text'`, `payload` is the copied string; for
|
|
30
|
+
* kind `'screenshot'`, `payload` is the component name (the PNG itself goes to the clipboard,
|
|
31
|
+
* not to this callback).
|
|
32
|
+
*/
|
|
23
33
|
onCopy?: (kind: CopyKind, payload: string) => void;
|
|
24
|
-
/**
|
|
34
|
+
/** Called when a copy fails (clipboard rejection / screenshot failure). */
|
|
25
35
|
onError?: (kind: CopyKind, err: unknown) => void;
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* Семантический инспектор. Сам по себе ничего не показывает, пока не включён
|
|
30
|
-
* хоткеем. Гейтинг (где монтировать) — забота консьюмера: монтируй под своим
|
|
31
|
-
* dev-флагом и желательно через React.lazy, чтобы не тянуть в prod-бандл.
|
|
32
|
-
*/
|
|
33
|
-
declare function SemanticInspector(props: SemanticInspectorProps): react.JSX.Element | null;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Состояние режима инспекции + слушатели.
|
|
37
|
-
* - keydown: хоткей переключает active, Esc выключает.
|
|
38
|
-
* - active: mousemove обновляет target; click (capture, preventDefault) копирует
|
|
39
|
-
* текст, Shift+click — скриншот элемента.
|
|
40
|
-
*/
|
|
41
|
-
declare function useInspector(opts?: SemanticInspectorProps): {
|
|
37
|
+
/** Return value of `useInspector`. */
|
|
38
|
+
interface UseInspectorResult {
|
|
42
39
|
active: boolean;
|
|
43
40
|
target: InspectTarget | null;
|
|
44
|
-
}
|
|
41
|
+
}
|
|
45
42
|
|
|
46
43
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* затем имя файла из data-loc, затем имя тега.
|
|
44
|
+
* Semantic inspector. Renders nothing until toggled by the hotkey. Gating (where to mount) is
|
|
45
|
+
* the consumer's responsibility: mount it under your dev flag, ideally via React.lazy, so it is
|
|
46
|
+
* not pulled into the production bundle.
|
|
51
47
|
*/
|
|
52
|
-
declare function
|
|
48
|
+
declare function SemanticInspector(props: SemanticInspectorProps): JSX.Element | null;
|
|
53
49
|
|
|
54
|
-
/** Текст в буфер. */
|
|
55
|
-
declare function copyText(text: string): Promise<void>;
|
|
56
50
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
51
|
+
* Inspection-mode state + listeners.
|
|
52
|
+
* - keydown: the hotkey toggles `active`; Esc exits.
|
|
53
|
+
* - active: mousemove (rAF-coalesced) updates `target`; click (capture, preventDefault) copies
|
|
54
|
+
* text, Shift+click copies an element screenshot.
|
|
59
55
|
*/
|
|
60
|
-
declare function
|
|
56
|
+
declare function useInspector(opts?: SemanticInspectorProps): UseInspectorResult;
|
|
61
57
|
|
|
62
|
-
export { type CopyKind, type InspectTarget, SemanticInspector, type SemanticInspectorProps,
|
|
58
|
+
export { type CopyKind, type InspectTarget, type LocInfo, SemanticInspector, type SemanticInspectorProps, type UseInspectorResult, useInspector };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,62 +1,58 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { JSX } from 'react';
|
|
2
2
|
|
|
3
3
|
type CopyKind = 'text' | 'screenshot';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/** Component name + source location resolved for an inspected element. */
|
|
5
|
+
interface LocInfo {
|
|
6
|
+
/** Component name (from data-comp) or a fallback (fiber displayName / file name / tag). */
|
|
6
7
|
comp: string;
|
|
7
|
-
/** "<path>:<line>"
|
|
8
|
+
/** "<path>:<line>:<col>" from data-loc, or null when the element is not stamped. */
|
|
8
9
|
loc: string | null;
|
|
9
|
-
|
|
10
|
+
}
|
|
11
|
+
interface InspectTarget extends LocInfo {
|
|
12
|
+
/** The resolved DOM element (nearest ancestor with data-loc, or the element itself). */
|
|
10
13
|
el: Element;
|
|
11
|
-
/**
|
|
14
|
+
/** Geometry for the overlay highlight. */
|
|
12
15
|
rect: DOMRect;
|
|
13
16
|
}
|
|
14
17
|
interface SemanticInspectorProps {
|
|
15
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Toggle hotkey. Default 'Alt+Shift+S'.
|
|
20
|
+
*
|
|
21
|
+
* Format: modifiers (`Alt`, `Shift`, `Ctrl`/`Control`, `Meta`/`Cmd`) joined with `+`
|
|
22
|
+
* followed by a final key, e.g. 'Ctrl+Cmd+I'. Matching is case-insensitive and also
|
|
23
|
+
* accepts the physical `event.code` (so layout-shifted glyphs still work). `Esc` always exits.
|
|
24
|
+
*/
|
|
16
25
|
hotkey?: string;
|
|
17
|
-
/**
|
|
18
|
-
formatText?: (t:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
/** Formats the clipboard text. Default: `${comp} — ${loc}` (or `${comp}` when loc is null). */
|
|
27
|
+
formatText?: (t: LocInfo) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Called after a successful copy. For kind `'text'`, `payload` is the copied string; for
|
|
30
|
+
* kind `'screenshot'`, `payload` is the component name (the PNG itself goes to the clipboard,
|
|
31
|
+
* not to this callback).
|
|
32
|
+
*/
|
|
23
33
|
onCopy?: (kind: CopyKind, payload: string) => void;
|
|
24
|
-
/**
|
|
34
|
+
/** Called when a copy fails (clipboard rejection / screenshot failure). */
|
|
25
35
|
onError?: (kind: CopyKind, err: unknown) => void;
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* Семантический инспектор. Сам по себе ничего не показывает, пока не включён
|
|
30
|
-
* хоткеем. Гейтинг (где монтировать) — забота консьюмера: монтируй под своим
|
|
31
|
-
* dev-флагом и желательно через React.lazy, чтобы не тянуть в prod-бандл.
|
|
32
|
-
*/
|
|
33
|
-
declare function SemanticInspector(props: SemanticInspectorProps): react.JSX.Element | null;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Состояние режима инспекции + слушатели.
|
|
37
|
-
* - keydown: хоткей переключает active, Esc выключает.
|
|
38
|
-
* - active: mousemove обновляет target; click (capture, preventDefault) копирует
|
|
39
|
-
* текст, Shift+click — скриншот элемента.
|
|
40
|
-
*/
|
|
41
|
-
declare function useInspector(opts?: SemanticInspectorProps): {
|
|
37
|
+
/** Return value of `useInspector`. */
|
|
38
|
+
interface UseInspectorResult {
|
|
42
39
|
active: boolean;
|
|
43
40
|
target: InspectTarget | null;
|
|
44
|
-
}
|
|
41
|
+
}
|
|
45
42
|
|
|
46
43
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* затем имя файла из data-loc, затем имя тега.
|
|
44
|
+
* Semantic inspector. Renders nothing until toggled by the hotkey. Gating (where to mount) is
|
|
45
|
+
* the consumer's responsibility: mount it under your dev flag, ideally via React.lazy, so it is
|
|
46
|
+
* not pulled into the production bundle.
|
|
51
47
|
*/
|
|
52
|
-
declare function
|
|
48
|
+
declare function SemanticInspector(props: SemanticInspectorProps): JSX.Element | null;
|
|
53
49
|
|
|
54
|
-
/** Текст в буфер. */
|
|
55
|
-
declare function copyText(text: string): Promise<void>;
|
|
56
50
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
51
|
+
* Inspection-mode state + listeners.
|
|
52
|
+
* - keydown: the hotkey toggles `active`; Esc exits.
|
|
53
|
+
* - active: mousemove (rAF-coalesced) updates `target`; click (capture, preventDefault) copies
|
|
54
|
+
* text, Shift+click copies an element screenshot.
|
|
59
55
|
*/
|
|
60
|
-
declare function
|
|
56
|
+
declare function useInspector(opts?: SemanticInspectorProps): UseInspectorResult;
|
|
61
57
|
|
|
62
|
-
export { type CopyKind, type InspectTarget, SemanticInspector, type SemanticInspectorProps,
|
|
58
|
+
export { type CopyKind, type InspectTarget, type LocInfo, SemanticInspector, type SemanticInspectorProps, type UseInspectorResult, useInspector };
|