uilint-react 0.1.18 → 0.1.19
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/dist/InspectionPanel-6DBGEWWD.js +9 -0
- package/dist/LocatorOverlay-FQEYAMT6.js +9 -0
- package/dist/SourceOverlays-2SEINA2B.js +9 -0
- package/dist/UILintToolbar-7ZYCQC4M.js +9 -0
- package/dist/chunk-3TA6OKS6.js +725 -0
- package/dist/chunk-7WYVWDRU.js +686 -0
- package/dist/chunk-KUFV22FO.js +213 -0
- package/dist/chunk-MEP7WO7U.js +210 -0
- package/dist/chunk-OWX36QE3.js +595 -0
- package/dist/index.d.ts +284 -6
- package/dist/index.js +62 -1
- package/dist/node.js +1 -4
- package/package.json +2 -2
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/components/ui-lint/types.ts
|
|
4
|
+
var FILE_COLORS = [
|
|
5
|
+
"#3B82F6",
|
|
6
|
+
// blue
|
|
7
|
+
"#8B5CF6",
|
|
8
|
+
// violet
|
|
9
|
+
"#EC4899",
|
|
10
|
+
// pink
|
|
11
|
+
"#10B981",
|
|
12
|
+
// emerald
|
|
13
|
+
"#F59E0B",
|
|
14
|
+
// amber
|
|
15
|
+
"#06B6D4",
|
|
16
|
+
// cyan
|
|
17
|
+
"#EF4444",
|
|
18
|
+
// red
|
|
19
|
+
"#84CC16",
|
|
20
|
+
// lime
|
|
21
|
+
"#6366F1",
|
|
22
|
+
// indigo
|
|
23
|
+
"#F97316",
|
|
24
|
+
// orange
|
|
25
|
+
"#14B8A6",
|
|
26
|
+
// teal
|
|
27
|
+
"#A855F7"
|
|
28
|
+
// purple
|
|
29
|
+
];
|
|
30
|
+
var DEFAULT_SETTINGS = {
|
|
31
|
+
showLabels: true,
|
|
32
|
+
hideNodeModules: true,
|
|
33
|
+
overlayOpacity: 0.2,
|
|
34
|
+
labelPosition: "top-left"
|
|
35
|
+
};
|
|
36
|
+
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
37
|
+
|
|
38
|
+
// src/components/ui-lint/fiber-utils.ts
|
|
39
|
+
var DATA_ATTR = "data-ui-lint-id";
|
|
40
|
+
var COLORS = [
|
|
41
|
+
"#3B82F6",
|
|
42
|
+
"#8B5CF6",
|
|
43
|
+
"#EC4899",
|
|
44
|
+
"#10B981",
|
|
45
|
+
"#F59E0B",
|
|
46
|
+
"#06B6D4",
|
|
47
|
+
"#EF4444",
|
|
48
|
+
"#84CC16",
|
|
49
|
+
"#6366F1",
|
|
50
|
+
"#F97316",
|
|
51
|
+
"#14B8A6",
|
|
52
|
+
"#A855F7"
|
|
53
|
+
];
|
|
54
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
55
|
+
"SCRIPT",
|
|
56
|
+
"STYLE",
|
|
57
|
+
"SVG",
|
|
58
|
+
"NOSCRIPT",
|
|
59
|
+
"TEMPLATE",
|
|
60
|
+
"HEAD",
|
|
61
|
+
"META",
|
|
62
|
+
"LINK"
|
|
63
|
+
]);
|
|
64
|
+
var elementCounter = 0;
|
|
65
|
+
function getFiberFromElement(element) {
|
|
66
|
+
const keys = Object.keys(element);
|
|
67
|
+
const fiberKey = keys.find((k) => k.startsWith("__reactFiber$"));
|
|
68
|
+
if (!fiberKey) return null;
|
|
69
|
+
return element[fiberKey];
|
|
70
|
+
}
|
|
71
|
+
function getDebugSource(fiber) {
|
|
72
|
+
if (!fiber._debugSource) return null;
|
|
73
|
+
return {
|
|
74
|
+
fileName: fiber._debugSource.fileName,
|
|
75
|
+
lineNumber: fiber._debugSource.lineNumber,
|
|
76
|
+
columnNumber: fiber._debugSource.columnNumber
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getSourceFromDataLoc(element) {
|
|
80
|
+
const loc = element.getAttribute("data-loc");
|
|
81
|
+
if (!loc) return null;
|
|
82
|
+
const parts = loc.split(":");
|
|
83
|
+
if (parts.length < 2) return null;
|
|
84
|
+
const lastPart = parts[parts.length - 1];
|
|
85
|
+
const secondLastPart = parts[parts.length - 2];
|
|
86
|
+
const lastIsNumber = /^\d+$/.test(lastPart);
|
|
87
|
+
const secondLastIsNumber = /^\d+$/.test(secondLastPart);
|
|
88
|
+
if (lastIsNumber && secondLastIsNumber) {
|
|
89
|
+
const columnNumber = parseInt(lastPart, 10);
|
|
90
|
+
const lineNumber = parseInt(secondLastPart, 10);
|
|
91
|
+
const fileName = parts.slice(0, -2).join(":");
|
|
92
|
+
if (isNaN(lineNumber) || isNaN(columnNumber) || !fileName) return null;
|
|
93
|
+
return { fileName, lineNumber, columnNumber };
|
|
94
|
+
} else if (lastIsNumber) {
|
|
95
|
+
const lineNumber = parseInt(lastPart, 10);
|
|
96
|
+
const fileName = parts.slice(0, -1).join(":");
|
|
97
|
+
if (isNaN(lineNumber) || !fileName) return null;
|
|
98
|
+
return { fileName, lineNumber };
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function getDebugOwner(fiber) {
|
|
103
|
+
return fiber._debugOwner ?? null;
|
|
104
|
+
}
|
|
105
|
+
function getComponentName(fiber) {
|
|
106
|
+
if (!fiber.type) return "Unknown";
|
|
107
|
+
if (typeof fiber.type === "string") return fiber.type;
|
|
108
|
+
if (typeof fiber.type === "function") {
|
|
109
|
+
const fn = fiber.type;
|
|
110
|
+
return fn.displayName || fn.name || "Anonymous";
|
|
111
|
+
}
|
|
112
|
+
return "Unknown";
|
|
113
|
+
}
|
|
114
|
+
function getComponentStack(fiber) {
|
|
115
|
+
const stack = [];
|
|
116
|
+
let current = fiber._debugOwner ?? null;
|
|
117
|
+
while (current && stack.length < 20) {
|
|
118
|
+
const name = getComponentName(current);
|
|
119
|
+
const source = getDebugSource(current);
|
|
120
|
+
if (current.tag <= 2 && name !== "Unknown") {
|
|
121
|
+
stack.push({ name, source });
|
|
122
|
+
}
|
|
123
|
+
current = current._debugOwner ?? current.return;
|
|
124
|
+
}
|
|
125
|
+
return stack;
|
|
126
|
+
}
|
|
127
|
+
function isNodeModulesPath(path) {
|
|
128
|
+
return path.includes("node_modules");
|
|
129
|
+
}
|
|
130
|
+
function getDisplayName(path) {
|
|
131
|
+
const parts = path.split("/");
|
|
132
|
+
return parts[parts.length - 1] || path;
|
|
133
|
+
}
|
|
134
|
+
function shouldSkipElement(element) {
|
|
135
|
+
if (SKIP_TAGS.has(element.tagName.toUpperCase())) return true;
|
|
136
|
+
if (element.hasAttribute("data-ui-lint")) return true;
|
|
137
|
+
if (element.getAttribute("aria-hidden") === "true") return true;
|
|
138
|
+
const styles = window.getComputedStyle(element);
|
|
139
|
+
if (styles.display === "none" || styles.visibility === "hidden") return true;
|
|
140
|
+
const rect = element.getBoundingClientRect();
|
|
141
|
+
if (rect.width === 0 || rect.height === 0) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function generateElementId() {
|
|
145
|
+
return `uilint-${++elementCounter}`;
|
|
146
|
+
}
|
|
147
|
+
function scanDOMForSources(root = document.body, hideNodeModules = true) {
|
|
148
|
+
const elements = [];
|
|
149
|
+
elementCounter = 0;
|
|
150
|
+
cleanupDataAttributes();
|
|
151
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
|
152
|
+
acceptNode: (node2) => {
|
|
153
|
+
const el = node2;
|
|
154
|
+
if (shouldSkipElement(el)) return NodeFilter.FILTER_REJECT;
|
|
155
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
let node = walker.currentNode;
|
|
159
|
+
while (node) {
|
|
160
|
+
if (node instanceof Element) {
|
|
161
|
+
let source = getSourceFromDataLoc(node);
|
|
162
|
+
let componentStack = [];
|
|
163
|
+
if (!source) {
|
|
164
|
+
const fiber = getFiberFromElement(node);
|
|
165
|
+
if (fiber) {
|
|
166
|
+
source = getDebugSource(fiber);
|
|
167
|
+
if (!source && fiber._debugOwner) {
|
|
168
|
+
source = getDebugSource(fiber._debugOwner);
|
|
169
|
+
}
|
|
170
|
+
componentStack = getComponentStack(fiber);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (hideNodeModules && source && isNodeModulesPath(source.fileName)) {
|
|
174
|
+
const appSource = componentStack.find(
|
|
175
|
+
(c) => c.source && !isNodeModulesPath(c.source.fileName)
|
|
176
|
+
);
|
|
177
|
+
if (appSource?.source) {
|
|
178
|
+
source = appSource.source;
|
|
179
|
+
} else {
|
|
180
|
+
node = walker.nextNode();
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (source) {
|
|
185
|
+
const id = generateElementId();
|
|
186
|
+
node.setAttribute(DATA_ATTR, id);
|
|
187
|
+
const scannedElement = {
|
|
188
|
+
id,
|
|
189
|
+
element: node,
|
|
190
|
+
tagName: node.tagName.toLowerCase(),
|
|
191
|
+
className: typeof node.className === "string" ? node.className : "",
|
|
192
|
+
source,
|
|
193
|
+
componentStack,
|
|
194
|
+
rect: node.getBoundingClientRect()
|
|
195
|
+
};
|
|
196
|
+
elements.push(scannedElement);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
node = walker.nextNode();
|
|
200
|
+
}
|
|
201
|
+
return elements;
|
|
202
|
+
}
|
|
203
|
+
function groupBySourceFile(elements) {
|
|
204
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
205
|
+
for (const element of elements) {
|
|
206
|
+
if (!element.source) continue;
|
|
207
|
+
const path = element.source.fileName;
|
|
208
|
+
const existing = fileMap.get(path) || [];
|
|
209
|
+
existing.push(element);
|
|
210
|
+
fileMap.set(path, existing);
|
|
211
|
+
}
|
|
212
|
+
const sourceFiles = [];
|
|
213
|
+
let colorIndex = 0;
|
|
214
|
+
for (const [path, elements2] of fileMap) {
|
|
215
|
+
sourceFiles.push({
|
|
216
|
+
path,
|
|
217
|
+
displayName: getDisplayName(path),
|
|
218
|
+
color: COLORS[colorIndex % COLORS.length],
|
|
219
|
+
elements: elements2
|
|
220
|
+
});
|
|
221
|
+
colorIndex++;
|
|
222
|
+
}
|
|
223
|
+
sourceFiles.sort((a, b) => b.elements.length - a.elements.length);
|
|
224
|
+
return sourceFiles;
|
|
225
|
+
}
|
|
226
|
+
function cleanupDataAttributes() {
|
|
227
|
+
const elements = document.querySelectorAll(`[${DATA_ATTR}]`);
|
|
228
|
+
elements.forEach((el) => el.removeAttribute(DATA_ATTR));
|
|
229
|
+
elementCounter = 0;
|
|
230
|
+
}
|
|
231
|
+
function getElementById(id) {
|
|
232
|
+
return document.querySelector(`[${DATA_ATTR}="${id}"]`);
|
|
233
|
+
}
|
|
234
|
+
function updateElementRects(elements) {
|
|
235
|
+
return elements.map((el) => ({
|
|
236
|
+
...el,
|
|
237
|
+
rect: el.element.getBoundingClientRect()
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
function buildEditorUrl(source, editor = "cursor") {
|
|
241
|
+
const { fileName, lineNumber, columnNumber } = source;
|
|
242
|
+
const column = columnNumber ?? 1;
|
|
243
|
+
if (editor === "cursor") {
|
|
244
|
+
return `cursor://file/${encodeURIComponent(
|
|
245
|
+
fileName
|
|
246
|
+
)}:${lineNumber}:${column}`;
|
|
247
|
+
}
|
|
248
|
+
return `vscode://file/${encodeURIComponent(
|
|
249
|
+
fileName
|
|
250
|
+
)}:${lineNumber}:${column}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/components/ui-lint/use-element-scan.ts
|
|
254
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
255
|
+
function debounce(fn, delay) {
|
|
256
|
+
let timeoutId;
|
|
257
|
+
return (...args) => {
|
|
258
|
+
clearTimeout(timeoutId);
|
|
259
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function useElementScan({
|
|
263
|
+
enabled,
|
|
264
|
+
settings
|
|
265
|
+
}) {
|
|
266
|
+
const [elements, setElements] = useState([]);
|
|
267
|
+
const [sourceFiles, setSourceFiles] = useState([]);
|
|
268
|
+
const [isScanning, setIsScanning] = useState(false);
|
|
269
|
+
const mutationObserverRef = useRef(null);
|
|
270
|
+
const resizeObserverRef = useRef(null);
|
|
271
|
+
const performScan = useCallback(() => {
|
|
272
|
+
if (!enabled || typeof window === "undefined") return;
|
|
273
|
+
setIsScanning(true);
|
|
274
|
+
const scan = () => {
|
|
275
|
+
try {
|
|
276
|
+
const scannedElements = scanDOMForSources(
|
|
277
|
+
document.body,
|
|
278
|
+
settings.hideNodeModules
|
|
279
|
+
);
|
|
280
|
+
const files = groupBySourceFile(scannedElements);
|
|
281
|
+
setElements(scannedElements);
|
|
282
|
+
setSourceFiles(files);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error("[UILint] Scan error:", error);
|
|
285
|
+
} finally {
|
|
286
|
+
setIsScanning(false);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
if ("requestIdleCallback" in window) {
|
|
290
|
+
window.requestIdleCallback(scan, { timeout: 1e3 });
|
|
291
|
+
} else {
|
|
292
|
+
setTimeout(scan, 0);
|
|
293
|
+
}
|
|
294
|
+
}, [enabled, settings.hideNodeModules]);
|
|
295
|
+
const updatePositions = useCallback(() => {
|
|
296
|
+
if (elements.length === 0) return;
|
|
297
|
+
setElements((prev) => updateElementRects(prev));
|
|
298
|
+
}, [elements.length]);
|
|
299
|
+
const debouncedRescan = useCallback(
|
|
300
|
+
debounce(() => {
|
|
301
|
+
performScan();
|
|
302
|
+
}, 500),
|
|
303
|
+
[performScan]
|
|
304
|
+
);
|
|
305
|
+
const handleScroll = useCallback(
|
|
306
|
+
debounce(() => {
|
|
307
|
+
updatePositions();
|
|
308
|
+
}, 16),
|
|
309
|
+
// ~60fps
|
|
310
|
+
[updatePositions]
|
|
311
|
+
);
|
|
312
|
+
const handleResize = useCallback(
|
|
313
|
+
debounce(() => {
|
|
314
|
+
updatePositions();
|
|
315
|
+
}, 100),
|
|
316
|
+
[updatePositions]
|
|
317
|
+
);
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (!enabled) {
|
|
320
|
+
cleanupDataAttributes();
|
|
321
|
+
setElements([]);
|
|
322
|
+
setSourceFiles([]);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const initialScanTimer = setTimeout(performScan, 100);
|
|
326
|
+
mutationObserverRef.current = new MutationObserver((mutations) => {
|
|
327
|
+
const hasRelevantMutation = mutations.some((mutation) => {
|
|
328
|
+
if (mutation.type === "attributes") {
|
|
329
|
+
return !mutation.attributeName?.startsWith("data-ui-lint");
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
});
|
|
333
|
+
if (hasRelevantMutation) {
|
|
334
|
+
debouncedRescan();
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
mutationObserverRef.current.observe(document.body, {
|
|
338
|
+
childList: true,
|
|
339
|
+
subtree: true,
|
|
340
|
+
attributes: true,
|
|
341
|
+
attributeFilter: ["class", "style"]
|
|
342
|
+
});
|
|
343
|
+
window.addEventListener("scroll", handleScroll, true);
|
|
344
|
+
window.addEventListener("resize", handleResize);
|
|
345
|
+
return () => {
|
|
346
|
+
clearTimeout(initialScanTimer);
|
|
347
|
+
mutationObserverRef.current?.disconnect();
|
|
348
|
+
resizeObserverRef.current?.disconnect();
|
|
349
|
+
window.removeEventListener("scroll", handleScroll, true);
|
|
350
|
+
window.removeEventListener("resize", handleResize);
|
|
351
|
+
cleanupDataAttributes();
|
|
352
|
+
};
|
|
353
|
+
}, [enabled, performScan, debouncedRescan, handleScroll, handleResize]);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (enabled && elements.length > 0) {
|
|
356
|
+
performScan();
|
|
357
|
+
}
|
|
358
|
+
}, [settings.hideNodeModules]);
|
|
359
|
+
return {
|
|
360
|
+
elements,
|
|
361
|
+
sourceFiles,
|
|
362
|
+
isScanning,
|
|
363
|
+
rescan: performScan
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/components/ui-lint/UILintProvider.tsx
|
|
368
|
+
import {
|
|
369
|
+
createContext,
|
|
370
|
+
useContext,
|
|
371
|
+
useState as useState2,
|
|
372
|
+
useEffect as useEffect2,
|
|
373
|
+
useCallback as useCallback2,
|
|
374
|
+
useMemo
|
|
375
|
+
} from "react";
|
|
376
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
377
|
+
var UILintContext = createContext(null);
|
|
378
|
+
function useUILintContext() {
|
|
379
|
+
const context = useContext(UILintContext);
|
|
380
|
+
if (!context) {
|
|
381
|
+
throw new Error("useUILintContext must be used within a UILintProvider");
|
|
382
|
+
}
|
|
383
|
+
return context;
|
|
384
|
+
}
|
|
385
|
+
function isBrowser() {
|
|
386
|
+
return typeof window !== "undefined";
|
|
387
|
+
}
|
|
388
|
+
function UILintProvider({
|
|
389
|
+
children,
|
|
390
|
+
enabled = true,
|
|
391
|
+
defaultMode = "off"
|
|
392
|
+
}) {
|
|
393
|
+
const [mode, setMode] = useState2(defaultMode);
|
|
394
|
+
const [settings, setSettings] = useState2(DEFAULT_SETTINGS);
|
|
395
|
+
const [selectedElement, setSelectedElement] = useState2(
|
|
396
|
+
null
|
|
397
|
+
);
|
|
398
|
+
const [hoveredElement, setHoveredElement] = useState2(
|
|
399
|
+
null
|
|
400
|
+
);
|
|
401
|
+
const [isMounted, setIsMounted] = useState2(false);
|
|
402
|
+
const [altKeyHeld, setAltKeyHeld] = useState2(false);
|
|
403
|
+
const [locatorTarget, setLocatorTarget] = useState2(
|
|
404
|
+
null
|
|
405
|
+
);
|
|
406
|
+
const [locatorStackIndex, setLocatorStackIndex] = useState2(0);
|
|
407
|
+
const isActive = enabled && mode !== "off";
|
|
408
|
+
const { elements, sourceFiles, isScanning, rescan } = useElementScan({
|
|
409
|
+
enabled: isActive,
|
|
410
|
+
settings
|
|
411
|
+
});
|
|
412
|
+
const updateSettings = useCallback2((partial) => {
|
|
413
|
+
setSettings((prev) => ({ ...prev, ...partial }));
|
|
414
|
+
}, []);
|
|
415
|
+
const toggleMode = useCallback2(() => {
|
|
416
|
+
setMode((prev) => {
|
|
417
|
+
if (prev === "off") return "sources";
|
|
418
|
+
if (prev === "sources") return "inspect";
|
|
419
|
+
return "off";
|
|
420
|
+
});
|
|
421
|
+
}, []);
|
|
422
|
+
const handleEscape = useCallback2(() => {
|
|
423
|
+
if (selectedElement) {
|
|
424
|
+
setSelectedElement(null);
|
|
425
|
+
} else if (mode !== "off") {
|
|
426
|
+
setMode("off");
|
|
427
|
+
}
|
|
428
|
+
}, [selectedElement, mode]);
|
|
429
|
+
const locatorGoUp = useCallback2(() => {
|
|
430
|
+
if (!locatorTarget) return;
|
|
431
|
+
const maxIndex = locatorTarget.componentStack.length;
|
|
432
|
+
setLocatorStackIndex((prev) => Math.min(prev + 1, maxIndex));
|
|
433
|
+
}, [locatorTarget]);
|
|
434
|
+
const locatorGoDown = useCallback2(() => {
|
|
435
|
+
setLocatorStackIndex((prev) => Math.max(prev - 1, 0));
|
|
436
|
+
}, []);
|
|
437
|
+
const getLocatorTargetFromElement = useCallback2(
|
|
438
|
+
(element) => {
|
|
439
|
+
if (element.closest("[data-ui-lint]")) return null;
|
|
440
|
+
let source = getSourceFromDataLoc(element);
|
|
441
|
+
let componentStack = [];
|
|
442
|
+
if (!source) {
|
|
443
|
+
const fiber = getFiberFromElement(element);
|
|
444
|
+
if (fiber) {
|
|
445
|
+
source = getDebugSource(fiber);
|
|
446
|
+
if (!source && fiber._debugOwner) {
|
|
447
|
+
source = getDebugSource(fiber._debugOwner);
|
|
448
|
+
}
|
|
449
|
+
componentStack = getComponentStack(fiber);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!source && componentStack.length === 0) return null;
|
|
453
|
+
if (settings.hideNodeModules && source && isNodeModulesPath(source.fileName)) {
|
|
454
|
+
const appSource = componentStack.find(
|
|
455
|
+
(c) => c.source && !isNodeModulesPath(c.source.fileName)
|
|
456
|
+
);
|
|
457
|
+
if (appSource?.source) {
|
|
458
|
+
source = appSource.source;
|
|
459
|
+
} else if (componentStack.length === 0) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
element,
|
|
465
|
+
source,
|
|
466
|
+
componentStack,
|
|
467
|
+
rect: element.getBoundingClientRect(),
|
|
468
|
+
stackIndex: 0
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
[settings.hideNodeModules]
|
|
472
|
+
);
|
|
473
|
+
const handleMouseMove = useCallback2(
|
|
474
|
+
(e) => {
|
|
475
|
+
if (!altKeyHeld) return;
|
|
476
|
+
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
|
|
477
|
+
if (!elementAtPoint) {
|
|
478
|
+
setLocatorTarget(null);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
let current = elementAtPoint;
|
|
482
|
+
while (current) {
|
|
483
|
+
const target = getLocatorTargetFromElement(current);
|
|
484
|
+
if (target) {
|
|
485
|
+
setLocatorTarget(target);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
current = current.parentElement;
|
|
489
|
+
}
|
|
490
|
+
setLocatorTarget(null);
|
|
491
|
+
},
|
|
492
|
+
[altKeyHeld, getLocatorTargetFromElement]
|
|
493
|
+
);
|
|
494
|
+
const handleLocatorClick = useCallback2(
|
|
495
|
+
(e) => {
|
|
496
|
+
if (!altKeyHeld || !locatorTarget) return;
|
|
497
|
+
e.preventDefault();
|
|
498
|
+
e.stopPropagation();
|
|
499
|
+
let source = locatorTarget.source;
|
|
500
|
+
if (locatorStackIndex > 0 && locatorTarget.componentStack.length > 0) {
|
|
501
|
+
const stackItem = locatorTarget.componentStack[locatorStackIndex - 1];
|
|
502
|
+
if (stackItem?.source) {
|
|
503
|
+
source = stackItem.source;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (source) {
|
|
507
|
+
const url = buildEditorUrl(source, "cursor");
|
|
508
|
+
window.open(url, "_blank");
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
[altKeyHeld, locatorTarget, locatorStackIndex]
|
|
512
|
+
);
|
|
513
|
+
useEffect2(() => {
|
|
514
|
+
if (!isBrowser() || !enabled) return;
|
|
515
|
+
const handleKeyDown = (e) => {
|
|
516
|
+
if (e.key === "Alt") {
|
|
517
|
+
setAltKeyHeld(true);
|
|
518
|
+
setLocatorStackIndex(0);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
const handleKeyUp = (e) => {
|
|
522
|
+
if (e.key === "Alt") {
|
|
523
|
+
setAltKeyHeld(false);
|
|
524
|
+
setLocatorTarget(null);
|
|
525
|
+
setLocatorStackIndex(0);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const handleBlur = () => {
|
|
529
|
+
setAltKeyHeld(false);
|
|
530
|
+
setLocatorTarget(null);
|
|
531
|
+
setLocatorStackIndex(0);
|
|
532
|
+
};
|
|
533
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
534
|
+
window.addEventListener("keyup", handleKeyUp);
|
|
535
|
+
window.addEventListener("blur", handleBlur);
|
|
536
|
+
return () => {
|
|
537
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
538
|
+
window.removeEventListener("keyup", handleKeyUp);
|
|
539
|
+
window.removeEventListener("blur", handleBlur);
|
|
540
|
+
};
|
|
541
|
+
}, [enabled]);
|
|
542
|
+
useEffect2(() => {
|
|
543
|
+
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
544
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
545
|
+
window.addEventListener("click", handleLocatorClick, true);
|
|
546
|
+
return () => {
|
|
547
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
548
|
+
window.removeEventListener("click", handleLocatorClick, true);
|
|
549
|
+
};
|
|
550
|
+
}, [enabled, altKeyHeld, handleMouseMove, handleLocatorClick]);
|
|
551
|
+
useEffect2(() => {
|
|
552
|
+
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
553
|
+
const handleWheel = (e) => {
|
|
554
|
+
if (!locatorTarget) return;
|
|
555
|
+
e.preventDefault();
|
|
556
|
+
if (e.deltaY > 0) {
|
|
557
|
+
locatorGoUp();
|
|
558
|
+
} else {
|
|
559
|
+
locatorGoDown();
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
window.addEventListener("wheel", handleWheel, { passive: false });
|
|
563
|
+
return () => window.removeEventListener("wheel", handleWheel);
|
|
564
|
+
}, [enabled, altKeyHeld, locatorTarget, locatorGoUp, locatorGoDown]);
|
|
565
|
+
useEffect2(() => {
|
|
566
|
+
if (!isBrowser() || !enabled) return;
|
|
567
|
+
const handleKeyDown = (e) => {
|
|
568
|
+
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "d") {
|
|
569
|
+
e.preventDefault();
|
|
570
|
+
toggleMode();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (e.key === "Escape") {
|
|
574
|
+
handleEscape();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
579
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
580
|
+
}, [enabled, toggleMode, handleEscape]);
|
|
581
|
+
useEffect2(() => {
|
|
582
|
+
setIsMounted(true);
|
|
583
|
+
}, []);
|
|
584
|
+
useEffect2(() => {
|
|
585
|
+
setSelectedElement(null);
|
|
586
|
+
setHoveredElement(null);
|
|
587
|
+
}, [mode]);
|
|
588
|
+
const effectiveLocatorTarget = useMemo(() => {
|
|
589
|
+
if (!locatorTarget) return null;
|
|
590
|
+
return {
|
|
591
|
+
...locatorTarget,
|
|
592
|
+
stackIndex: locatorStackIndex
|
|
593
|
+
};
|
|
594
|
+
}, [locatorTarget, locatorStackIndex]);
|
|
595
|
+
const contextValue = useMemo(
|
|
596
|
+
() => ({
|
|
597
|
+
mode,
|
|
598
|
+
setMode,
|
|
599
|
+
scannedElements: elements,
|
|
600
|
+
sourceFiles,
|
|
601
|
+
selectedElement,
|
|
602
|
+
setSelectedElement,
|
|
603
|
+
hoveredElement,
|
|
604
|
+
setHoveredElement,
|
|
605
|
+
settings,
|
|
606
|
+
updateSettings,
|
|
607
|
+
rescan,
|
|
608
|
+
isScanning,
|
|
609
|
+
altKeyHeld,
|
|
610
|
+
locatorTarget: effectiveLocatorTarget,
|
|
611
|
+
locatorGoUp,
|
|
612
|
+
locatorGoDown
|
|
613
|
+
}),
|
|
614
|
+
[
|
|
615
|
+
mode,
|
|
616
|
+
elements,
|
|
617
|
+
sourceFiles,
|
|
618
|
+
selectedElement,
|
|
619
|
+
hoveredElement,
|
|
620
|
+
settings,
|
|
621
|
+
updateSettings,
|
|
622
|
+
rescan,
|
|
623
|
+
isScanning,
|
|
624
|
+
altKeyHeld,
|
|
625
|
+
effectiveLocatorTarget,
|
|
626
|
+
locatorGoUp,
|
|
627
|
+
locatorGoDown
|
|
628
|
+
]
|
|
629
|
+
);
|
|
630
|
+
const shouldRenderUI = enabled && isMounted;
|
|
631
|
+
return /* @__PURE__ */ jsxs(UILintContext.Provider, { value: contextValue, children: [
|
|
632
|
+
children,
|
|
633
|
+
shouldRenderUI && /* @__PURE__ */ jsx(UILintUI, {})
|
|
634
|
+
] });
|
|
635
|
+
}
|
|
636
|
+
function UILintUI() {
|
|
637
|
+
const { mode, altKeyHeld } = useUILintContext();
|
|
638
|
+
const [components, setComponents] = useState2(null);
|
|
639
|
+
useEffect2(() => {
|
|
640
|
+
Promise.all([
|
|
641
|
+
import("./UILintToolbar-7ZYCQC4M.js"),
|
|
642
|
+
import("./SourceOverlays-2SEINA2B.js"),
|
|
643
|
+
import("./InspectionPanel-6DBGEWWD.js"),
|
|
644
|
+
import("./LocatorOverlay-FQEYAMT6.js")
|
|
645
|
+
]).then(([toolbar, overlays, panel, locator]) => {
|
|
646
|
+
setComponents({
|
|
647
|
+
Toolbar: toolbar.UILintToolbar,
|
|
648
|
+
Overlays: overlays.SourceOverlays,
|
|
649
|
+
Panel: panel.InspectionPanel,
|
|
650
|
+
LocatorOverlay: locator.LocatorOverlay
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}, []);
|
|
654
|
+
if (!components) return null;
|
|
655
|
+
const { Toolbar, Overlays, Panel, LocatorOverlay } = components;
|
|
656
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
657
|
+
/* @__PURE__ */ jsx(Toolbar, {}),
|
|
658
|
+
mode === "sources" && /* @__PURE__ */ jsx(Overlays, {}),
|
|
659
|
+
mode === "inspect" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
660
|
+
/* @__PURE__ */ jsx(Overlays, {}),
|
|
661
|
+
/* @__PURE__ */ jsx(Panel, {})
|
|
662
|
+
] }),
|
|
663
|
+
altKeyHeld && /* @__PURE__ */ jsx(LocatorOverlay, {})
|
|
664
|
+
] });
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export {
|
|
668
|
+
FILE_COLORS,
|
|
669
|
+
DEFAULT_SETTINGS,
|
|
670
|
+
DATA_UILINT_ID,
|
|
671
|
+
getFiberFromElement,
|
|
672
|
+
getDebugSource,
|
|
673
|
+
getDebugOwner,
|
|
674
|
+
getComponentStack,
|
|
675
|
+
isNodeModulesPath,
|
|
676
|
+
getDisplayName,
|
|
677
|
+
scanDOMForSources,
|
|
678
|
+
groupBySourceFile,
|
|
679
|
+
cleanupDataAttributes,
|
|
680
|
+
getElementById,
|
|
681
|
+
updateElementRects,
|
|
682
|
+
buildEditorUrl,
|
|
683
|
+
useElementScan,
|
|
684
|
+
useUILintContext,
|
|
685
|
+
UILintProvider
|
|
686
|
+
};
|