@linhey/react-debug-inspector 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.js +477 -156
- package/dist/index.mjs +477 -156
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -62,17 +62,31 @@ function babelPluginDebugLabel() {
|
|
|
62
62
|
// src/runtime.ts
|
|
63
63
|
function initInspector() {
|
|
64
64
|
if (typeof window === "undefined") return;
|
|
65
|
+
const win = window;
|
|
66
|
+
const doc = document;
|
|
67
|
+
const scopedWindow = win;
|
|
68
|
+
scopedWindow.__reactDebugInspectorCleanup__?.();
|
|
65
69
|
let isInspecting = false;
|
|
66
|
-
let hasUserPosition = false;
|
|
67
|
-
let isDragging = false;
|
|
68
|
-
let pointerMoved = false;
|
|
69
|
-
let dragOffsetX = 0;
|
|
70
|
-
let dragOffsetY = 0;
|
|
71
|
-
let dragStartX = 0;
|
|
72
|
-
let dragStartY = 0;
|
|
73
70
|
let overlay = null;
|
|
74
71
|
let tooltip = null;
|
|
72
|
+
let actionMenu = null;
|
|
73
|
+
let latestContext = null;
|
|
74
|
+
let lastHoveredDebugEl = null;
|
|
75
|
+
let latestHoverEvent = null;
|
|
76
|
+
let pendingHoverFrame = false;
|
|
77
|
+
let anchorUpdatePending = false;
|
|
78
|
+
let selectionLocked = false;
|
|
75
79
|
const edgeOffset = 24;
|
|
80
|
+
const successColor = "#10b981";
|
|
81
|
+
const defaultOverlayBg = "rgba(14, 165, 233, 0.15)";
|
|
82
|
+
const defaultOverlayBorder = "#0ea5e9";
|
|
83
|
+
const actionButtons = {};
|
|
84
|
+
const scheduleFrame = (cb) => {
|
|
85
|
+
if (typeof win.requestAnimationFrame === "function") {
|
|
86
|
+
return win.requestAnimationFrame(cb);
|
|
87
|
+
}
|
|
88
|
+
return win.setTimeout(() => cb(Date.now()), 16);
|
|
89
|
+
};
|
|
76
90
|
const toggleBtn = document.createElement("button");
|
|
77
91
|
toggleBtn.innerHTML = "\u{1F3AF}";
|
|
78
92
|
toggleBtn.title = "\u5F00\u542F\u7EC4\u4EF6\u5B9A\u4F4D\u5668";
|
|
@@ -95,9 +109,8 @@ function initInspector() {
|
|
|
95
109
|
justify-content: center;
|
|
96
110
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
97
111
|
`;
|
|
98
|
-
|
|
112
|
+
doc.body.appendChild(toggleBtn);
|
|
99
113
|
const applyAnchor = (anchor) => {
|
|
100
|
-
if (hasUserPosition) return;
|
|
101
114
|
toggleBtn.style.top = anchor.startsWith("top") ? `${edgeOffset}px` : "";
|
|
102
115
|
toggleBtn.style.bottom = anchor.startsWith("bottom") ? `${edgeOffset}px` : "";
|
|
103
116
|
if (anchor.endsWith("left")) {
|
|
@@ -109,7 +122,6 @@ function initInspector() {
|
|
|
109
122
|
toggleBtn.style.left = "";
|
|
110
123
|
};
|
|
111
124
|
const getVisibleDialogs = () => {
|
|
112
|
-
if (typeof document === "undefined") return [];
|
|
113
125
|
const candidates = Array.from(
|
|
114
126
|
document.querySelectorAll('[role="dialog"], dialog[open], [aria-modal="true"]')
|
|
115
127
|
);
|
|
@@ -120,18 +132,35 @@ function initInspector() {
|
|
|
120
132
|
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
|
|
121
133
|
});
|
|
122
134
|
};
|
|
135
|
+
const getPreferredHost = (dialogs) => {
|
|
136
|
+
if (dialogs.length === 0) return document.body;
|
|
137
|
+
const topDialog = dialogs[dialogs.length - 1];
|
|
138
|
+
const portalHost = topDialog.closest("[data-radix-portal]");
|
|
139
|
+
if (portalHost) return topDialog;
|
|
140
|
+
return document.body;
|
|
141
|
+
};
|
|
123
142
|
const ensureToggleHost = (host) => {
|
|
124
143
|
if (toggleBtn.parentElement !== host) {
|
|
125
144
|
host.appendChild(toggleBtn);
|
|
126
145
|
}
|
|
127
146
|
};
|
|
147
|
+
const ensureToggleVisible = () => {
|
|
148
|
+
toggleBtn.removeAttribute("aria-hidden");
|
|
149
|
+
toggleBtn.removeAttribute("data-aria-hidden");
|
|
150
|
+
toggleBtn.removeAttribute("inert");
|
|
151
|
+
if ("inert" in toggleBtn) {
|
|
152
|
+
toggleBtn.inert = false;
|
|
153
|
+
}
|
|
154
|
+
toggleBtn.style.pointerEvents = "auto";
|
|
155
|
+
};
|
|
128
156
|
const isIgnorableObstacle = (el) => {
|
|
129
|
-
if (el === toggleBtn || el === overlay || el === tooltip) return true;
|
|
157
|
+
if (el === toggleBtn || el === overlay || el === tooltip || el === actionMenu) return true;
|
|
130
158
|
if (el instanceof HTMLElement) {
|
|
131
159
|
if (el.contains(toggleBtn) || toggleBtn.contains(el)) return true;
|
|
132
160
|
if (overlay && el.contains(overlay)) return true;
|
|
133
161
|
if (tooltip && el.contains(tooltip)) return true;
|
|
134
|
-
if (
|
|
162
|
+
if (actionMenu && (el.contains(actionMenu) || actionMenu.contains(el))) return true;
|
|
163
|
+
if (el === doc.body || el === doc.documentElement) return true;
|
|
135
164
|
if (el.getAttribute("data-inspector-ignore") === "true") return true;
|
|
136
165
|
const style = window.getComputedStyle(el);
|
|
137
166
|
if (style.pointerEvents === "none") return true;
|
|
@@ -141,7 +170,7 @@ function initInspector() {
|
|
|
141
170
|
};
|
|
142
171
|
const sampleAnchorObstructionScore = () => {
|
|
143
172
|
const pickStack = typeof document.elementsFromPoint === "function" ? (x, y) => document.elementsFromPoint(x, y) : (x, y) => {
|
|
144
|
-
const one =
|
|
173
|
+
const one = doc.elementFromPoint?.(x, y);
|
|
145
174
|
return one ? [one] : [];
|
|
146
175
|
};
|
|
147
176
|
const rect = toggleBtn.getBoundingClientRect();
|
|
@@ -176,27 +205,313 @@ function initInspector() {
|
|
|
176
205
|
applyAnchor(best);
|
|
177
206
|
};
|
|
178
207
|
const updateAnchorForDialogs = () => {
|
|
179
|
-
if (typeof document === "undefined") return;
|
|
180
208
|
const dialogs = getVisibleDialogs();
|
|
209
|
+
ensureToggleHost(getPreferredHost(dialogs));
|
|
210
|
+
ensureToggleVisible();
|
|
181
211
|
if (dialogs.length > 0) {
|
|
182
|
-
ensureToggleHost(dialogs[dialogs.length - 1]);
|
|
183
212
|
pickBestAnchor(true);
|
|
184
213
|
return;
|
|
185
214
|
}
|
|
186
|
-
ensureToggleHost(document.body);
|
|
187
215
|
pickBestAnchor(false);
|
|
188
216
|
};
|
|
189
|
-
let anchorUpdatePending = false;
|
|
190
217
|
const scheduleAnchorUpdate = () => {
|
|
191
|
-
if (typeof window === "undefined") return;
|
|
192
218
|
if (anchorUpdatePending) return;
|
|
193
219
|
anchorUpdatePending = true;
|
|
194
|
-
|
|
195
|
-
schedule(() => {
|
|
220
|
+
scheduleFrame(() => {
|
|
196
221
|
anchorUpdatePending = false;
|
|
197
222
|
updateAnchorForDialogs();
|
|
198
223
|
});
|
|
199
224
|
};
|
|
225
|
+
const formatDebugId = (debugId) => {
|
|
226
|
+
const parts = debugId.split(":");
|
|
227
|
+
if (parts.length === 4) {
|
|
228
|
+
const [filePath, componentName, tagName, line] = parts;
|
|
229
|
+
const fileName = filePath.split("/").pop() || filePath;
|
|
230
|
+
return `${fileName} \u203A ${componentName} \u203A ${tagName}:${line}`;
|
|
231
|
+
}
|
|
232
|
+
return debugId.replace(/:/g, " \u203A ");
|
|
233
|
+
};
|
|
234
|
+
const normalizeText = (value) => value.replace(/\s+/g, " ").trim();
|
|
235
|
+
const truncateText = (value, limit = 500) => {
|
|
236
|
+
if (value.length <= limit) return value;
|
|
237
|
+
return `${value.slice(0, limit - 3)}...`;
|
|
238
|
+
};
|
|
239
|
+
const suppressEvent = (event, { preventDefault = false, immediate = false } = {}) => {
|
|
240
|
+
if (preventDefault) {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
}
|
|
243
|
+
event.stopPropagation();
|
|
244
|
+
if (immediate) {
|
|
245
|
+
event.stopImmediatePropagation?.();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const isInspectorChromeTarget = (target) => {
|
|
249
|
+
if (!target) return false;
|
|
250
|
+
if (target === toggleBtn || target === overlay || target === tooltip || target === actionMenu) return true;
|
|
251
|
+
return !!target.closest('[data-inspector-ignore="true"]');
|
|
252
|
+
};
|
|
253
|
+
const extractTextContent = (target) => {
|
|
254
|
+
const ariaLabel = normalizeText(target.getAttribute("aria-label") || "");
|
|
255
|
+
if (ariaLabel) return truncateText(ariaLabel);
|
|
256
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
257
|
+
const inputValue = normalizeText(target.value || "");
|
|
258
|
+
if (inputValue) return truncateText(inputValue);
|
|
259
|
+
}
|
|
260
|
+
const alt = normalizeText(target.getAttribute("alt") || "");
|
|
261
|
+
if (alt) return truncateText(alt);
|
|
262
|
+
const title = normalizeText(target.getAttribute("title") || "");
|
|
263
|
+
if (title) return truncateText(title);
|
|
264
|
+
const textValue = normalizeText(target.innerText || target.textContent || "");
|
|
265
|
+
if (textValue) return truncateText(textValue);
|
|
266
|
+
return null;
|
|
267
|
+
};
|
|
268
|
+
const resolveImageTarget = (target) => {
|
|
269
|
+
const imageEl = target instanceof HTMLImageElement ? target : target.querySelector("img") || target.closest("img");
|
|
270
|
+
if (imageEl instanceof HTMLImageElement) {
|
|
271
|
+
const url = imageEl.currentSrc || imageEl.src;
|
|
272
|
+
if (!url) return null;
|
|
273
|
+
return {
|
|
274
|
+
url,
|
|
275
|
+
alt: imageEl.alt || "",
|
|
276
|
+
title: imageEl.title || "",
|
|
277
|
+
filename: url.split("/").pop()?.split("?")[0] || "",
|
|
278
|
+
source: "img"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
let node = target;
|
|
282
|
+
while (node) {
|
|
283
|
+
const bg = window.getComputedStyle(node).backgroundImage;
|
|
284
|
+
const match = bg.match(/url\(["']?(.*?)["']?\)/);
|
|
285
|
+
if (match?.[1]) {
|
|
286
|
+
const url = match[1];
|
|
287
|
+
return {
|
|
288
|
+
url,
|
|
289
|
+
alt: node.getAttribute("aria-label") || "",
|
|
290
|
+
title: node.getAttribute("title") || "",
|
|
291
|
+
filename: url.split("/").pop()?.split("?")[0] || "",
|
|
292
|
+
source: "background"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
node = node.parentElement;
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
};
|
|
299
|
+
const buildImageMetadataText = (image, debugId) => [
|
|
300
|
+
"[image]",
|
|
301
|
+
`url: ${image.url}`,
|
|
302
|
+
`alt: ${image.alt}`,
|
|
303
|
+
`title: ${image.title}`,
|
|
304
|
+
`filename: ${image.filename}`,
|
|
305
|
+
`debugId: ${debugId}`
|
|
306
|
+
].join("\n");
|
|
307
|
+
const buildCopyAllPayload = (context) => {
|
|
308
|
+
const textValue = extractTextContent(context.debugEl);
|
|
309
|
+
const image = resolveImageTarget(context.debugEl);
|
|
310
|
+
const lines = [
|
|
311
|
+
"[debug]",
|
|
312
|
+
`id: ${context.debugId}`,
|
|
313
|
+
`display: ${formatDebugId(context.debugId)}`
|
|
314
|
+
];
|
|
315
|
+
if (textValue) {
|
|
316
|
+
lines.push("", "[text]", `value: ${textValue}`);
|
|
317
|
+
}
|
|
318
|
+
if (image) {
|
|
319
|
+
lines.push("", "[image]", `url: ${image.url}`, `alt: ${image.alt}`, `title: ${image.title}`, `filename: ${image.filename}`);
|
|
320
|
+
}
|
|
321
|
+
return lines.join("\n");
|
|
322
|
+
};
|
|
323
|
+
const syncActionMenuVisibility = (context) => {
|
|
324
|
+
actionButtons.id && (actionButtons.id.style.display = "");
|
|
325
|
+
actionButtons.all && (actionButtons.all.style.display = "");
|
|
326
|
+
if (actionButtons.text) {
|
|
327
|
+
actionButtons.text.style.display = extractTextContent(context.debugEl) ? "" : "none";
|
|
328
|
+
}
|
|
329
|
+
if (actionButtons.image) {
|
|
330
|
+
actionButtons.image.style.display = resolveImageTarget(context.debugEl) ? "" : "none";
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const setOverlayTone = (tone) => {
|
|
334
|
+
if (!overlay) return;
|
|
335
|
+
if (tone === "success") {
|
|
336
|
+
overlay.style.background = "rgba(16, 185, 129, 0.2)";
|
|
337
|
+
overlay.style.borderColor = successColor;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
overlay.style.background = "rgba(245, 158, 11, 0.18)";
|
|
341
|
+
overlay.style.borderColor = "#f59e0b";
|
|
342
|
+
};
|
|
343
|
+
const resetOverlayTone = () => {
|
|
344
|
+
if (!overlay) return;
|
|
345
|
+
overlay.style.background = defaultOverlayBg;
|
|
346
|
+
overlay.style.borderColor = defaultOverlayBorder;
|
|
347
|
+
};
|
|
348
|
+
const showCopyFeedback = (message, tone) => {
|
|
349
|
+
if (!tooltip) return;
|
|
350
|
+
tooltip.textContent = tone === "success" ? `\u2705 ${message}` : `\u26A0\uFE0F ${message}`;
|
|
351
|
+
tooltip.style.color = tone === "success" ? successColor : "#f59e0b";
|
|
352
|
+
tooltip.title = latestContext?.debugId || "";
|
|
353
|
+
setOverlayTone(tone);
|
|
354
|
+
};
|
|
355
|
+
const resetTooltipContent = (context) => {
|
|
356
|
+
if (!tooltip) return;
|
|
357
|
+
tooltip.textContent = formatDebugId(context.debugId);
|
|
358
|
+
tooltip.style.color = "#38bdf8";
|
|
359
|
+
tooltip.title = context.debugId;
|
|
360
|
+
resetOverlayTone();
|
|
361
|
+
};
|
|
362
|
+
const positionActionMenu = (context) => {
|
|
363
|
+
if (!tooltip || !actionMenu) return;
|
|
364
|
+
const rect = context.rect;
|
|
365
|
+
const menuWidth = actionMenu.offsetWidth || 248;
|
|
366
|
+
const menuHeight = actionMenu.offsetHeight || 40;
|
|
367
|
+
const tooltipRect = tooltip.getBoundingClientRect();
|
|
368
|
+
let left = tooltipRect.right + 8;
|
|
369
|
+
if (left + menuWidth > win.innerWidth - 8) {
|
|
370
|
+
left = Math.max(8, tooltipRect.left - menuWidth - 8);
|
|
371
|
+
}
|
|
372
|
+
let top = tooltipRect.top;
|
|
373
|
+
if (top + menuHeight > win.innerHeight - 8) {
|
|
374
|
+
top = Math.max(8, rect.bottom - menuHeight);
|
|
375
|
+
}
|
|
376
|
+
if (top < 8) {
|
|
377
|
+
top = Math.min(win.innerHeight - menuHeight - 8, rect.bottom + 8);
|
|
378
|
+
}
|
|
379
|
+
actionMenu.style.left = `${left}px`;
|
|
380
|
+
actionMenu.style.top = `${top}px`;
|
|
381
|
+
};
|
|
382
|
+
const showActionMenu = (context) => {
|
|
383
|
+
if (!actionMenu) return;
|
|
384
|
+
syncActionMenuVisibility(context);
|
|
385
|
+
actionMenu.style.display = "flex";
|
|
386
|
+
positionActionMenu(context);
|
|
387
|
+
};
|
|
388
|
+
const hideActionMenu = () => {
|
|
389
|
+
if (!actionMenu) return;
|
|
390
|
+
actionMenu.style.display = "none";
|
|
391
|
+
};
|
|
392
|
+
const finalizeSelection = (debugId) => {
|
|
393
|
+
selectionLocked = true;
|
|
394
|
+
copyText(debugId).then(() => {
|
|
395
|
+
showCopyFeedback("Copied!", "success");
|
|
396
|
+
win.setTimeout(() => {
|
|
397
|
+
selectionLocked = false;
|
|
398
|
+
stopInspecting();
|
|
399
|
+
}, 600);
|
|
400
|
+
}).catch(() => {
|
|
401
|
+
selectionLocked = false;
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
const selectDebugTarget = (target) => {
|
|
405
|
+
if (selectionLocked) return;
|
|
406
|
+
const debugEl = target?.closest("[data-debug]");
|
|
407
|
+
if (!debugEl) {
|
|
408
|
+
selectionLocked = false;
|
|
409
|
+
stopInspecting();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const debugId = debugEl.getAttribute("data-debug");
|
|
413
|
+
if (!debugId) return;
|
|
414
|
+
finalizeSelection(debugId);
|
|
415
|
+
};
|
|
416
|
+
const copyText = async (value) => {
|
|
417
|
+
await navigator.clipboard.writeText(value);
|
|
418
|
+
};
|
|
419
|
+
const copyImageBinary = async (image, debugId) => {
|
|
420
|
+
if (image.url.startsWith("data:")) {
|
|
421
|
+
await copyText(buildImageMetadataText(image, debugId));
|
|
422
|
+
return "metadata";
|
|
423
|
+
}
|
|
424
|
+
const canWriteBinary = typeof navigator.clipboard?.write === "function" && typeof window.ClipboardItem === "function";
|
|
425
|
+
if (canWriteBinary) {
|
|
426
|
+
try {
|
|
427
|
+
const response = await win.fetch(image.url);
|
|
428
|
+
if (!response.ok) throw new Error(`failed:${response.status}`);
|
|
429
|
+
const blob = await response.blob();
|
|
430
|
+
if (!blob.type.startsWith("image/")) throw new Error("not-image");
|
|
431
|
+
const item = new window.ClipboardItem({ [blob.type]: blob });
|
|
432
|
+
await navigator.clipboard.write([item]);
|
|
433
|
+
return "binary";
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
await copyText(buildImageMetadataText(image, debugId));
|
|
438
|
+
return "metadata";
|
|
439
|
+
};
|
|
440
|
+
const performCopyAction = async (action, context) => {
|
|
441
|
+
if (action === "id") {
|
|
442
|
+
await copyText(context.debugId);
|
|
443
|
+
showCopyFeedback("\u5DF2\u590D\u5236 Debug ID", "success");
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (action === "text") {
|
|
447
|
+
const textValue = extractTextContent(context.debugEl);
|
|
448
|
+
if (!textValue) return;
|
|
449
|
+
await copyText(textValue);
|
|
450
|
+
showCopyFeedback("\u5DF2\u590D\u5236\u6587\u6848", "success");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (action === "image") {
|
|
454
|
+
const image = resolveImageTarget(context.debugEl);
|
|
455
|
+
if (!image) {
|
|
456
|
+
showCopyFeedback("\u672A\u627E\u5230\u56FE\u7247", "warning");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const copyResult = await copyImageBinary(image, context.debugId);
|
|
460
|
+
showCopyFeedback(copyResult === "binary" ? "\u5DF2\u590D\u5236\u56FE\u7247" : "\u5DF2\u590D\u5236\u56FE\u7247\u4FE1\u606F", "success");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
await copyText(buildCopyAllPayload(context));
|
|
464
|
+
showCopyFeedback("\u5DF2\u590D\u5236\u5168\u90E8\u4FE1\u606F", "success");
|
|
465
|
+
};
|
|
466
|
+
const inspectByPointer = (event) => {
|
|
467
|
+
if (!isInspecting || !overlay || !tooltip) return;
|
|
468
|
+
const target = event.target;
|
|
469
|
+
if (!target) return;
|
|
470
|
+
if (isInspectorChromeTarget(target)) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const debugEl = target.closest("[data-debug]");
|
|
474
|
+
if (debugEl && debugEl === lastHoveredDebugEl && latestContext) {
|
|
475
|
+
showActionMenu(latestContext);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (!debugEl) {
|
|
479
|
+
overlay.style.display = "none";
|
|
480
|
+
tooltip.style.display = "none";
|
|
481
|
+
hideActionMenu();
|
|
482
|
+
latestContext = null;
|
|
483
|
+
lastHoveredDebugEl = null;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const debugId = debugEl.getAttribute("data-debug") || "";
|
|
487
|
+
const rect = debugEl.getBoundingClientRect();
|
|
488
|
+
latestContext = { debugEl, debugId, rect };
|
|
489
|
+
lastHoveredDebugEl = debugEl;
|
|
490
|
+
overlay.style.display = "block";
|
|
491
|
+
overlay.style.top = `${rect.top}px`;
|
|
492
|
+
overlay.style.left = `${rect.left}px`;
|
|
493
|
+
overlay.style.width = `${rect.width}px`;
|
|
494
|
+
overlay.style.height = `${rect.height}px`;
|
|
495
|
+
tooltip.style.display = "block";
|
|
496
|
+
resetTooltipContent(latestContext);
|
|
497
|
+
const tooltipY = rect.top < 30 ? rect.bottom + 4 : rect.top - 28;
|
|
498
|
+
tooltip.style.top = `${tooltipY}px`;
|
|
499
|
+
tooltip.style.left = `${rect.left}px`;
|
|
500
|
+
showActionMenu(latestContext);
|
|
501
|
+
};
|
|
502
|
+
const stopInspecting = () => {
|
|
503
|
+
isInspecting = false;
|
|
504
|
+
selectionLocked = false;
|
|
505
|
+
latestContext = null;
|
|
506
|
+
lastHoveredDebugEl = null;
|
|
507
|
+
toggleBtn.style.transform = "scale(1)";
|
|
508
|
+
toggleBtn.style.background = "#0ea5e9";
|
|
509
|
+
document.body.style.cursor = "";
|
|
510
|
+
if (overlay) overlay.style.display = "none";
|
|
511
|
+
if (tooltip) tooltip.style.display = "none";
|
|
512
|
+
hideActionMenu();
|
|
513
|
+
resetOverlayTone();
|
|
514
|
+
};
|
|
200
515
|
const dialogObserver = new MutationObserver(() => {
|
|
201
516
|
scheduleAnchorUpdate();
|
|
202
517
|
});
|
|
@@ -205,12 +520,12 @@ function initInspector() {
|
|
|
205
520
|
position: fixed;
|
|
206
521
|
pointer-events: none;
|
|
207
522
|
z-index: 9999998;
|
|
208
|
-
background:
|
|
209
|
-
border: 2px dashed
|
|
523
|
+
background: ${defaultOverlayBg};
|
|
524
|
+
border: 2px dashed ${defaultOverlayBorder};
|
|
210
525
|
display: none;
|
|
211
526
|
transition: all 0.1s ease-out;
|
|
212
527
|
`;
|
|
213
|
-
|
|
528
|
+
doc.body.appendChild(overlay);
|
|
214
529
|
tooltip = document.createElement("div");
|
|
215
530
|
tooltip.style.cssText = `
|
|
216
531
|
position: fixed;
|
|
@@ -229,153 +544,159 @@ function initInspector() {
|
|
|
229
544
|
transition: all 0.1s ease-out;
|
|
230
545
|
cursor: help;
|
|
231
546
|
`;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
547
|
+
doc.body.appendChild(tooltip);
|
|
548
|
+
actionMenu = document.createElement("div");
|
|
549
|
+
actionMenu.setAttribute("data-inspector-ignore", "true");
|
|
550
|
+
actionMenu.style.cssText = `
|
|
551
|
+
position: fixed;
|
|
552
|
+
z-index: 10000000;
|
|
553
|
+
display: none;
|
|
554
|
+
gap: 6px;
|
|
555
|
+
padding: 6px;
|
|
556
|
+
border-radius: 10px;
|
|
557
|
+
background: rgba(15, 23, 42, 0.96);
|
|
558
|
+
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.28);
|
|
559
|
+
border: 1px solid rgba(148, 163, 184, 0.25);
|
|
560
|
+
align-items: center;
|
|
561
|
+
`;
|
|
562
|
+
const actionDefinitions = [
|
|
563
|
+
{ action: "id", label: "\u590D\u5236 ID" },
|
|
564
|
+
{ action: "text", label: "\u590D\u5236\u6587\u6848" },
|
|
565
|
+
{ action: "image", label: "\u590D\u5236\u56FE\u7247" },
|
|
566
|
+
{ action: "all", label: "\u5168\u90E8\u590D\u5236" }
|
|
567
|
+
];
|
|
568
|
+
for (const definition of actionDefinitions) {
|
|
569
|
+
const button = document.createElement("button");
|
|
570
|
+
button.type = "button";
|
|
571
|
+
button.textContent = definition.label;
|
|
572
|
+
button.dataset.inspectorIgnore = "true";
|
|
573
|
+
button.dataset.inspectorAction = definition.action;
|
|
574
|
+
button.style.cssText = `
|
|
575
|
+
border: 0;
|
|
576
|
+
border-radius: 8px;
|
|
577
|
+
padding: 6px 10px;
|
|
578
|
+
font-size: 12px;
|
|
579
|
+
font-weight: 600;
|
|
580
|
+
color: #e2e8f0;
|
|
581
|
+
background: rgba(30, 41, 59, 0.95);
|
|
582
|
+
cursor: pointer;
|
|
583
|
+
white-space: nowrap;
|
|
584
|
+
`;
|
|
585
|
+
button.addEventListener("click", async (event) => {
|
|
586
|
+
suppressEvent(event, { preventDefault: true });
|
|
587
|
+
if (!latestContext) return;
|
|
588
|
+
await performCopyAction(definition.action, latestContext);
|
|
589
|
+
});
|
|
590
|
+
actionButtons[definition.action] = button;
|
|
591
|
+
actionMenu.appendChild(button);
|
|
592
|
+
}
|
|
593
|
+
doc.body.appendChild(actionMenu);
|
|
594
|
+
const stopTogglePropagation = (event) => {
|
|
595
|
+
suppressEvent(event);
|
|
596
|
+
};
|
|
597
|
+
const shieldedEvents = ["pointerdown", "pointerup", "mousedown", "mouseup", "click", "touchstart", "touchend"];
|
|
598
|
+
for (const eventName of shieldedEvents) {
|
|
599
|
+
toggleBtn.addEventListener(eventName, stopTogglePropagation);
|
|
600
|
+
}
|
|
601
|
+
const handleToggleClick = (event) => {
|
|
602
|
+
suppressEvent(event);
|
|
289
603
|
isInspecting = !isInspecting;
|
|
290
604
|
if (isInspecting) {
|
|
291
605
|
toggleBtn.style.transform = "scale(0.9)";
|
|
292
606
|
toggleBtn.style.background = "#ef4444";
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
stopInspecting();
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
const formatDebugId = (debugId) => {
|
|
299
|
-
const parts = debugId.split(":");
|
|
300
|
-
if (parts.length === 4) {
|
|
301
|
-
const [filePath, componentName, tagName, line] = parts;
|
|
302
|
-
const fileName = filePath.split("/").pop() || filePath;
|
|
303
|
-
return `${fileName} \u203A ${componentName} \u203A ${tagName}:${line}`;
|
|
304
|
-
}
|
|
305
|
-
return debugId.replace(/:/g, " \u203A ");
|
|
306
|
-
};
|
|
307
|
-
const inspectByPointer = (e) => {
|
|
308
|
-
if (!isInspecting) return;
|
|
309
|
-
const target = e.target;
|
|
310
|
-
if (target === toggleBtn || target === overlay || target === tooltip) return;
|
|
311
|
-
const debugEl = target.closest("[data-debug]");
|
|
312
|
-
if (debugEl && debugEl === lastHoveredDebugEl) return;
|
|
313
|
-
if (debugEl) {
|
|
314
|
-
const debugId = debugEl.getAttribute("data-debug") || "";
|
|
315
|
-
const rect = debugEl.getBoundingClientRect();
|
|
316
|
-
overlay.style.display = "block";
|
|
317
|
-
overlay.style.top = rect.top + "px";
|
|
318
|
-
overlay.style.left = rect.left + "px";
|
|
319
|
-
overlay.style.width = rect.width + "px";
|
|
320
|
-
overlay.style.height = rect.height + "px";
|
|
321
|
-
tooltip.style.display = "block";
|
|
322
|
-
tooltip.textContent = formatDebugId(debugId);
|
|
323
|
-
tooltip.style.color = "#38bdf8";
|
|
324
|
-
tooltip.title = debugId;
|
|
325
|
-
const tooltipY = rect.top < 30 ? rect.bottom + 4 : rect.top - 28;
|
|
326
|
-
tooltip.style.top = tooltipY + "px";
|
|
327
|
-
tooltip.style.left = rect.left + "px";
|
|
328
|
-
lastHoveredDebugEl = debugEl;
|
|
329
|
-
} else {
|
|
330
|
-
overlay.style.display = "none";
|
|
331
|
-
tooltip.style.display = "none";
|
|
332
|
-
lastHoveredDebugEl = null;
|
|
607
|
+
doc.body.style.cursor = "crosshair";
|
|
608
|
+
return;
|
|
333
609
|
}
|
|
610
|
+
stopInspecting();
|
|
334
611
|
};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
let lastHoveredDebugEl = null;
|
|
338
|
-
document.addEventListener("mousemove", (e) => {
|
|
612
|
+
toggleBtn.onclick = handleToggleClick;
|
|
613
|
+
const handleMouseMove = (event) => {
|
|
339
614
|
if (!isInspecting) return;
|
|
340
|
-
latestHoverEvent =
|
|
615
|
+
latestHoverEvent = event;
|
|
341
616
|
if (pendingHoverFrame) return;
|
|
342
617
|
pendingHoverFrame = true;
|
|
343
|
-
|
|
344
|
-
schedule(() => {
|
|
618
|
+
scheduleFrame(() => {
|
|
345
619
|
pendingHoverFrame = false;
|
|
346
620
|
if (!latestHoverEvent) return;
|
|
347
621
|
inspectByPointer(latestHoverEvent);
|
|
348
622
|
});
|
|
623
|
+
};
|
|
624
|
+
const handlePointerSuppression = (event) => {
|
|
625
|
+
if (!isInspecting) return;
|
|
626
|
+
const target = event.target;
|
|
627
|
+
if (!target || isInspectorChromeTarget(target)) return;
|
|
628
|
+
if (selectionLocked) {
|
|
629
|
+
suppressEvent(event, { preventDefault: true, immediate: true });
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const isTouchSelectionEvent = event.type === "touchend" || event.type === "pointerup" && "pointerType" in event && event.pointerType !== "mouse";
|
|
633
|
+
suppressEvent(event, { preventDefault: true, immediate: true });
|
|
634
|
+
if (isTouchSelectionEvent) {
|
|
635
|
+
selectDebugTarget(target);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
const handleWindowClick = (event) => {
|
|
639
|
+
if (!isInspecting) return;
|
|
640
|
+
const target = event.target;
|
|
641
|
+
if (!target || target === toggleBtn) return;
|
|
642
|
+
if (isInspectorChromeTarget(target)) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
suppressEvent(event, { preventDefault: true, immediate: true });
|
|
646
|
+
if (selectionLocked) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
selectDebugTarget(target);
|
|
650
|
+
};
|
|
651
|
+
const handleKeyDown = (event) => {
|
|
652
|
+
if (event.key === "Escape" && isInspecting) stopInspecting();
|
|
653
|
+
};
|
|
654
|
+
const handleBeforeUnload = () => {
|
|
655
|
+
dialogObserver.disconnect();
|
|
656
|
+
};
|
|
657
|
+
dialogObserver.observe(doc.body, {
|
|
658
|
+
childList: true,
|
|
659
|
+
subtree: true,
|
|
660
|
+
attributes: true,
|
|
661
|
+
attributeFilter: ["style", "class", "open", "aria-hidden", "data-aria-hidden", "inert"]
|
|
349
662
|
});
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
{ capture: true }
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
663
|
+
win.addEventListener("beforeunload", handleBeforeUnload, { once: true });
|
|
664
|
+
win.addEventListener("resize", scheduleAnchorUpdate);
|
|
665
|
+
win.addEventListener("scroll", scheduleAnchorUpdate, true);
|
|
666
|
+
doc.addEventListener("mousemove", handleMouseMove);
|
|
667
|
+
win.addEventListener("pointerdown", handlePointerSuppression, { capture: true });
|
|
668
|
+
win.addEventListener("pointerup", handlePointerSuppression, { capture: true });
|
|
669
|
+
win.addEventListener("mousedown", handlePointerSuppression, { capture: true });
|
|
670
|
+
win.addEventListener("mouseup", handlePointerSuppression, { capture: true });
|
|
671
|
+
win.addEventListener("touchstart", handlePointerSuppression, { capture: true });
|
|
672
|
+
win.addEventListener("touchend", handlePointerSuppression, { capture: true });
|
|
673
|
+
win.addEventListener("click", handleWindowClick, { capture: true });
|
|
674
|
+
win.addEventListener("keydown", handleKeyDown);
|
|
675
|
+
updateAnchorForDialogs();
|
|
676
|
+
scopedWindow.__reactDebugInspectorCleanup__ = () => {
|
|
677
|
+
dialogObserver.disconnect();
|
|
678
|
+
win.removeEventListener("beforeunload", handleBeforeUnload);
|
|
679
|
+
win.removeEventListener("resize", scheduleAnchorUpdate);
|
|
680
|
+
win.removeEventListener("scroll", scheduleAnchorUpdate, true);
|
|
681
|
+
doc.removeEventListener("mousemove", handleMouseMove);
|
|
682
|
+
win.removeEventListener("pointerdown", handlePointerSuppression, { capture: true });
|
|
683
|
+
win.removeEventListener("pointerup", handlePointerSuppression, { capture: true });
|
|
684
|
+
win.removeEventListener("mousedown", handlePointerSuppression, { capture: true });
|
|
685
|
+
win.removeEventListener("mouseup", handlePointerSuppression, { capture: true });
|
|
686
|
+
win.removeEventListener("touchstart", handlePointerSuppression, { capture: true });
|
|
687
|
+
win.removeEventListener("touchend", handlePointerSuppression, { capture: true });
|
|
688
|
+
win.removeEventListener("click", handleWindowClick, { capture: true });
|
|
689
|
+
win.removeEventListener("keydown", handleKeyDown);
|
|
690
|
+
for (const eventName of shieldedEvents) {
|
|
691
|
+
toggleBtn.removeEventListener(eventName, stopTogglePropagation);
|
|
692
|
+
}
|
|
693
|
+
toggleBtn.onclick = null;
|
|
694
|
+
overlay?.remove();
|
|
695
|
+
tooltip?.remove();
|
|
696
|
+
actionMenu?.remove();
|
|
697
|
+
toggleBtn.remove();
|
|
698
|
+
delete scopedWindow.__reactDebugInspectorCleanup__;
|
|
699
|
+
};
|
|
379
700
|
}
|
|
380
701
|
|
|
381
702
|
// src/index.ts
|