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