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