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