uilint-react 0.1.24 → 0.1.26
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/{ElementBadges-T4N3VQRI.js → ElementBadges-HFQNIIO2.js} +29 -51
- package/dist/{InspectionPanel-47JBBKBL.js → InspectionPanel-4OWY4FVY.js} +2 -2
- package/dist/{LocatorOverlay-ADJUWU2H.js → LocatorOverlay-JJDOKNOS.js} +2 -2
- package/dist/{UILintToolbar-5PG6WVW6.js → UILintToolbar-GMZ6YSI2.js} +2 -2
- package/dist/{chunk-I4C3NAUH.js → chunk-5VJ2Q2QW.js} +402 -286
- package/dist/{chunk-GFURSJEQ.js → chunk-7X5HN55P.js} +1 -1
- package/dist/chunk-QYRESGFG.js +1463 -0
- package/dist/{chunk-EB6K2KGR.js → chunk-XLIDEQXH.js} +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +4 -4
- package/package.json +3 -2
- package/dist/chunk-FWYNI6JG.js +0 -1359
|
@@ -1,44 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
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
|
-
hideNodeModules: true,
|
|
32
|
-
autoScanEnabled: false
|
|
33
|
-
};
|
|
34
|
-
var DEFAULT_AUTO_SCAN_STATE = {
|
|
35
|
-
status: "idle",
|
|
36
|
-
currentIndex: 0,
|
|
37
|
-
totalElements: 0,
|
|
38
|
-
elements: []
|
|
39
|
-
};
|
|
40
|
-
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
41
|
-
|
|
42
3
|
// src/components/ui-lint/fiber-utils.ts
|
|
43
4
|
var DATA_ATTR = "data-ui-lint-id";
|
|
44
5
|
var COLORS = [
|
|
@@ -66,6 +27,16 @@ var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
|
66
27
|
"LINK"
|
|
67
28
|
]);
|
|
68
29
|
var elementCounter = 0;
|
|
30
|
+
function generateStableId(element, source) {
|
|
31
|
+
const dataLoc = element.getAttribute("data-loc");
|
|
32
|
+
if (dataLoc) {
|
|
33
|
+
return `loc:${dataLoc}`;
|
|
34
|
+
}
|
|
35
|
+
if (source) {
|
|
36
|
+
return `src:${source.fileName}:${source.lineNumber}:${source.columnNumber ?? 0}`;
|
|
37
|
+
}
|
|
38
|
+
return `uilint-${++elementCounter}`;
|
|
39
|
+
}
|
|
69
40
|
function getFiberFromElement(element) {
|
|
70
41
|
const keys = Object.keys(element);
|
|
71
42
|
const fiberKey = keys.find((k) => k.startsWith("__reactFiber$"));
|
|
@@ -145,9 +116,6 @@ function shouldSkipElement(element) {
|
|
|
145
116
|
if (rect.width === 0 || rect.height === 0) return true;
|
|
146
117
|
return false;
|
|
147
118
|
}
|
|
148
|
-
function generateElementId() {
|
|
149
|
-
return `uilint-${++elementCounter}`;
|
|
150
|
-
}
|
|
151
119
|
function scanDOMForSources(root = document.body, hideNodeModules = true) {
|
|
152
120
|
const elements = [];
|
|
153
121
|
elementCounter = 0;
|
|
@@ -162,17 +130,18 @@ function scanDOMForSources(root = document.body, hideNodeModules = true) {
|
|
|
162
130
|
let node = walker.currentNode;
|
|
163
131
|
while (node) {
|
|
164
132
|
if (node instanceof Element) {
|
|
165
|
-
let source =
|
|
133
|
+
let source = null;
|
|
166
134
|
let componentStack = [];
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
source = getDebugSource(fiber._debugOwner);
|
|
173
|
-
}
|
|
174
|
-
componentStack = getComponentStack(fiber);
|
|
135
|
+
const fiber = getFiberFromElement(node);
|
|
136
|
+
if (fiber) {
|
|
137
|
+
source = getDebugSource(fiber);
|
|
138
|
+
if (!source && fiber._debugOwner) {
|
|
139
|
+
source = getDebugSource(fiber._debugOwner);
|
|
175
140
|
}
|
|
141
|
+
componentStack = getComponentStack(fiber);
|
|
142
|
+
}
|
|
143
|
+
if (!source) {
|
|
144
|
+
source = getSourceFromDataLoc(node);
|
|
176
145
|
}
|
|
177
146
|
if (hideNodeModules && source && isNodeModulesPath(source.fileName)) {
|
|
178
147
|
const appSource = componentStack.find(
|
|
@@ -186,7 +155,7 @@ function scanDOMForSources(root = document.body, hideNodeModules = true) {
|
|
|
186
155
|
}
|
|
187
156
|
}
|
|
188
157
|
if (source) {
|
|
189
|
-
const id =
|
|
158
|
+
const id = generateStableId(node, source);
|
|
190
159
|
node.setAttribute(DATA_ATTR, id);
|
|
191
160
|
const scannedElement = {
|
|
192
161
|
id,
|
|
@@ -254,6 +223,307 @@ function buildEditorUrl(source, editor = "cursor") {
|
|
|
254
223
|
)}:${lineNumber}:${column}`;
|
|
255
224
|
}
|
|
256
225
|
|
|
226
|
+
// src/components/ui-lint/types.ts
|
|
227
|
+
var FILE_COLORS = [
|
|
228
|
+
"#3B82F6",
|
|
229
|
+
// blue
|
|
230
|
+
"#8B5CF6",
|
|
231
|
+
// violet
|
|
232
|
+
"#EC4899",
|
|
233
|
+
// pink
|
|
234
|
+
"#10B981",
|
|
235
|
+
// emerald
|
|
236
|
+
"#F59E0B",
|
|
237
|
+
// amber
|
|
238
|
+
"#06B6D4",
|
|
239
|
+
// cyan
|
|
240
|
+
"#EF4444",
|
|
241
|
+
// red
|
|
242
|
+
"#84CC16",
|
|
243
|
+
// lime
|
|
244
|
+
"#6366F1",
|
|
245
|
+
// indigo
|
|
246
|
+
"#F97316",
|
|
247
|
+
// orange
|
|
248
|
+
"#14B8A6",
|
|
249
|
+
// teal
|
|
250
|
+
"#A855F7"
|
|
251
|
+
// purple
|
|
252
|
+
];
|
|
253
|
+
var DEFAULT_SETTINGS = {
|
|
254
|
+
hideNodeModules: true,
|
|
255
|
+
autoScanEnabled: false
|
|
256
|
+
};
|
|
257
|
+
var DEFAULT_AUTO_SCAN_STATE = {
|
|
258
|
+
status: "idle",
|
|
259
|
+
currentIndex: 0,
|
|
260
|
+
totalElements: 0,
|
|
261
|
+
elements: []
|
|
262
|
+
};
|
|
263
|
+
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
264
|
+
|
|
265
|
+
// src/components/ui-lint/store.ts
|
|
266
|
+
import { create } from "zustand";
|
|
267
|
+
function getDataLocFromId(id) {
|
|
268
|
+
if (id.startsWith("loc:")) {
|
|
269
|
+
return id.slice(4);
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
async function scanFileForIssues(sourceFile) {
|
|
274
|
+
if (sourceFile.elements.length === 0) {
|
|
275
|
+
return { issues: [] };
|
|
276
|
+
}
|
|
277
|
+
const filePath = sourceFile.path;
|
|
278
|
+
try {
|
|
279
|
+
const sourceResponse = await fetch(
|
|
280
|
+
`/api/.uilint/source?path=${encodeURIComponent(filePath)}`
|
|
281
|
+
);
|
|
282
|
+
if (!sourceResponse.ok) {
|
|
283
|
+
return { issues: [], error: true };
|
|
284
|
+
}
|
|
285
|
+
const sourceData = await sourceResponse.json();
|
|
286
|
+
const dataLocs = [];
|
|
287
|
+
for (const el of sourceFile.elements) {
|
|
288
|
+
const dataLoc = getDataLocFromId(el.id);
|
|
289
|
+
if (dataLoc) {
|
|
290
|
+
dataLocs.push(dataLoc);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const analyzeResponse = await fetch("/api/.uilint/analyze", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers: { "Content-Type": "application/json" },
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
sourceCode: sourceData.content,
|
|
298
|
+
filePath: sourceData.relativePath || filePath,
|
|
299
|
+
dataLocs
|
|
300
|
+
})
|
|
301
|
+
});
|
|
302
|
+
if (!analyzeResponse.ok) {
|
|
303
|
+
return { issues: [], error: true };
|
|
304
|
+
}
|
|
305
|
+
const result = await analyzeResponse.json();
|
|
306
|
+
return { issues: result.issues || [] };
|
|
307
|
+
} catch {
|
|
308
|
+
return { issues: [], error: true };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function distributeIssuesToElements(issues, elements, updateElementIssue, hasError) {
|
|
312
|
+
const dataLocToElementId = /* @__PURE__ */ new Map();
|
|
313
|
+
for (const el of elements) {
|
|
314
|
+
const dataLoc = getDataLocFromId(el.id);
|
|
315
|
+
if (dataLoc) {
|
|
316
|
+
dataLocToElementId.set(dataLoc, el.id);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const issuesByElement = /* @__PURE__ */ new Map();
|
|
320
|
+
for (const issue of issues) {
|
|
321
|
+
if (issue.dataLoc) {
|
|
322
|
+
const elementId = dataLocToElementId.get(issue.dataLoc);
|
|
323
|
+
if (elementId) {
|
|
324
|
+
const existing = issuesByElement.get(elementId) || [];
|
|
325
|
+
existing.push(issue);
|
|
326
|
+
issuesByElement.set(elementId, existing);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const el of elements) {
|
|
331
|
+
const elementIssues = issuesByElement.get(el.id) || [];
|
|
332
|
+
updateElementIssue(el.id, {
|
|
333
|
+
elementId: el.id,
|
|
334
|
+
issues: elementIssues,
|
|
335
|
+
status: hasError ? "error" : "complete"
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var useUILintStore = create()((set, get) => ({
|
|
340
|
+
// ============ Settings ============
|
|
341
|
+
settings: DEFAULT_SETTINGS,
|
|
342
|
+
updateSettings: (partial) => set((state) => ({
|
|
343
|
+
settings: { ...state.settings, ...partial }
|
|
344
|
+
})),
|
|
345
|
+
// ============ Locator Mode ============
|
|
346
|
+
altKeyHeld: false,
|
|
347
|
+
setAltKeyHeld: (held) => set({ altKeyHeld: held }),
|
|
348
|
+
locatorTarget: null,
|
|
349
|
+
setLocatorTarget: (target) => set({ locatorTarget: target }),
|
|
350
|
+
locatorStackIndex: 0,
|
|
351
|
+
setLocatorStackIndex: (index) => set({ locatorStackIndex: index }),
|
|
352
|
+
locatorGoUp: () => {
|
|
353
|
+
const { locatorTarget, locatorStackIndex } = get();
|
|
354
|
+
if (!locatorTarget) return;
|
|
355
|
+
const maxIndex = locatorTarget.componentStack.length;
|
|
356
|
+
set({ locatorStackIndex: Math.min(locatorStackIndex + 1, maxIndex) });
|
|
357
|
+
},
|
|
358
|
+
locatorGoDown: () => {
|
|
359
|
+
const { locatorStackIndex } = get();
|
|
360
|
+
set({ locatorStackIndex: Math.max(locatorStackIndex - 1, 0) });
|
|
361
|
+
},
|
|
362
|
+
// ============ Inspection ============
|
|
363
|
+
inspectedElement: null,
|
|
364
|
+
setInspectedElement: (el) => set({ inspectedElement: el }),
|
|
365
|
+
// ============ Manual Scan (InspectionPanel) ============
|
|
366
|
+
manualScanCache: /* @__PURE__ */ new Map(),
|
|
367
|
+
upsertManualScan: (key, patch) => set((state) => {
|
|
368
|
+
const next = new Map(state.manualScanCache);
|
|
369
|
+
const existing = next.get(key);
|
|
370
|
+
const base = existing ?? {
|
|
371
|
+
key,
|
|
372
|
+
status: "idle",
|
|
373
|
+
issues: [],
|
|
374
|
+
updatedAt: Date.now()
|
|
375
|
+
};
|
|
376
|
+
next.set(key, {
|
|
377
|
+
...base,
|
|
378
|
+
...patch,
|
|
379
|
+
key,
|
|
380
|
+
updatedAt: Date.now()
|
|
381
|
+
});
|
|
382
|
+
return { manualScanCache: next };
|
|
383
|
+
}),
|
|
384
|
+
clearManualScan: (key) => set((state) => {
|
|
385
|
+
if (!state.manualScanCache.has(key)) return state;
|
|
386
|
+
const next = new Map(state.manualScanCache);
|
|
387
|
+
next.delete(key);
|
|
388
|
+
return { manualScanCache: next };
|
|
389
|
+
}),
|
|
390
|
+
// ============ Auto-Scan ============
|
|
391
|
+
autoScanState: DEFAULT_AUTO_SCAN_STATE,
|
|
392
|
+
elementIssuesCache: /* @__PURE__ */ new Map(),
|
|
393
|
+
scanLock: false,
|
|
394
|
+
scanPaused: false,
|
|
395
|
+
scanAborted: false,
|
|
396
|
+
_setScanState: (partial) => set((state) => ({
|
|
397
|
+
autoScanState: { ...state.autoScanState, ...partial }
|
|
398
|
+
})),
|
|
399
|
+
updateElementIssue: (id, issue) => set((state) => {
|
|
400
|
+
const newCache = new Map(state.elementIssuesCache);
|
|
401
|
+
newCache.set(id, issue);
|
|
402
|
+
return { elementIssuesCache: newCache };
|
|
403
|
+
}),
|
|
404
|
+
startAutoScan: async (hideNodeModules) => {
|
|
405
|
+
const state = get();
|
|
406
|
+
if (state.scanLock) {
|
|
407
|
+
console.warn("UILint: Scan already in progress");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
set({
|
|
411
|
+
scanLock: true,
|
|
412
|
+
scanPaused: false,
|
|
413
|
+
scanAborted: false
|
|
414
|
+
});
|
|
415
|
+
const elements = scanDOMForSources(document.body, hideNodeModules);
|
|
416
|
+
const initialCache = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const el of elements) {
|
|
418
|
+
initialCache.set(el.id, {
|
|
419
|
+
elementId: el.id,
|
|
420
|
+
issues: [],
|
|
421
|
+
status: "pending"
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
set({
|
|
425
|
+
elementIssuesCache: initialCache,
|
|
426
|
+
autoScanState: {
|
|
427
|
+
status: "scanning",
|
|
428
|
+
currentIndex: 0,
|
|
429
|
+
totalElements: elements.length,
|
|
430
|
+
elements
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
await get()._runScanLoop(elements, 0);
|
|
434
|
+
},
|
|
435
|
+
pauseAutoScan: () => {
|
|
436
|
+
set({ scanPaused: true });
|
|
437
|
+
get()._setScanState({ status: "paused" });
|
|
438
|
+
},
|
|
439
|
+
resumeAutoScan: () => {
|
|
440
|
+
const state = get();
|
|
441
|
+
if (state.autoScanState.status !== "paused") return;
|
|
442
|
+
set({ scanPaused: false });
|
|
443
|
+
get()._setScanState({ status: "scanning" });
|
|
444
|
+
get()._runScanLoop(
|
|
445
|
+
state.autoScanState.elements,
|
|
446
|
+
state.autoScanState.currentIndex
|
|
447
|
+
);
|
|
448
|
+
},
|
|
449
|
+
stopAutoScan: () => {
|
|
450
|
+
set({
|
|
451
|
+
scanAborted: true,
|
|
452
|
+
scanPaused: false,
|
|
453
|
+
scanLock: false,
|
|
454
|
+
autoScanState: DEFAULT_AUTO_SCAN_STATE,
|
|
455
|
+
elementIssuesCache: /* @__PURE__ */ new Map()
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
_runScanLoop: async (elements, startIndex) => {
|
|
459
|
+
const sourceFiles = groupBySourceFile(elements);
|
|
460
|
+
let processedElements = 0;
|
|
461
|
+
let skipElements = startIndex;
|
|
462
|
+
for (const sourceFile of sourceFiles) {
|
|
463
|
+
if (skipElements >= sourceFile.elements.length) {
|
|
464
|
+
skipElements -= sourceFile.elements.length;
|
|
465
|
+
processedElements += sourceFile.elements.length;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
skipElements = 0;
|
|
469
|
+
if (get().scanAborted) {
|
|
470
|
+
set({
|
|
471
|
+
scanLock: false,
|
|
472
|
+
autoScanState: { ...get().autoScanState, status: "idle" }
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
while (get().scanPaused) {
|
|
477
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
478
|
+
if (get().scanAborted) {
|
|
479
|
+
set({
|
|
480
|
+
scanLock: false,
|
|
481
|
+
autoScanState: { ...get().autoScanState, status: "idle" }
|
|
482
|
+
});
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
get()._setScanState({ currentIndex: processedElements });
|
|
487
|
+
for (const el of sourceFile.elements) {
|
|
488
|
+
get().updateElementIssue(el.id, {
|
|
489
|
+
elementId: el.id,
|
|
490
|
+
issues: [],
|
|
491
|
+
status: "scanning"
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
495
|
+
const { issues, error } = await scanFileForIssues(sourceFile);
|
|
496
|
+
distributeIssuesToElements(
|
|
497
|
+
issues,
|
|
498
|
+
sourceFile.elements,
|
|
499
|
+
get().updateElementIssue,
|
|
500
|
+
error ?? false
|
|
501
|
+
);
|
|
502
|
+
processedElements += sourceFile.elements.length;
|
|
503
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
504
|
+
}
|
|
505
|
+
set({
|
|
506
|
+
scanLock: false,
|
|
507
|
+
autoScanState: {
|
|
508
|
+
...get().autoScanState,
|
|
509
|
+
status: "complete",
|
|
510
|
+
currentIndex: elements.length
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}));
|
|
515
|
+
function useEffectiveLocatorTarget() {
|
|
516
|
+
const locatorTarget = useUILintStore((s) => s.locatorTarget);
|
|
517
|
+
const locatorStackIndex = useUILintStore(
|
|
518
|
+
(s) => s.locatorStackIndex
|
|
519
|
+
);
|
|
520
|
+
if (!locatorTarget) return null;
|
|
521
|
+
return {
|
|
522
|
+
...locatorTarget,
|
|
523
|
+
stackIndex: locatorStackIndex
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
257
527
|
// src/components/ui-lint/UILintProvider.tsx
|
|
258
528
|
import {
|
|
259
529
|
createContext,
|
|
@@ -261,8 +531,7 @@ import {
|
|
|
261
531
|
useState,
|
|
262
532
|
useEffect,
|
|
263
533
|
useCallback,
|
|
264
|
-
useMemo
|
|
265
|
-
useRef
|
|
534
|
+
useMemo
|
|
266
535
|
} from "react";
|
|
267
536
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
268
537
|
var UILintContext = createContext(null);
|
|
@@ -280,215 +549,52 @@ function UILintProvider({
|
|
|
280
549
|
children,
|
|
281
550
|
enabled = true
|
|
282
551
|
}) {
|
|
283
|
-
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
|
284
552
|
const [isMounted, setIsMounted] = useState(false);
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
553
|
+
const settings = useUILintStore((s) => s.settings);
|
|
554
|
+
const updateSettings = useUILintStore((s) => s.updateSettings);
|
|
555
|
+
const altKeyHeld = useUILintStore((s) => s.altKeyHeld);
|
|
556
|
+
const setAltKeyHeld = useUILintStore((s) => s.setAltKeyHeld);
|
|
557
|
+
const setLocatorTarget = useUILintStore(
|
|
558
|
+
(s) => s.setLocatorTarget
|
|
288
559
|
);
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
const [autoScanState, setAutoScanState] = useState(
|
|
292
|
-
DEFAULT_AUTO_SCAN_STATE
|
|
560
|
+
const locatorStackIndex = useUILintStore(
|
|
561
|
+
(s) => s.locatorStackIndex
|
|
293
562
|
);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
const scanAbortRef = useRef(false);
|
|
297
|
-
const updateSettings = useCallback((partial) => {
|
|
298
|
-
setSettings((prev) => ({ ...prev, ...partial }));
|
|
299
|
-
}, []);
|
|
300
|
-
const scanElementForIssues = useCallback(
|
|
301
|
-
async (element) => {
|
|
302
|
-
if (!element.source) {
|
|
303
|
-
return {
|
|
304
|
-
elementId: element.id,
|
|
305
|
-
issues: [],
|
|
306
|
-
status: "complete"
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
try {
|
|
310
|
-
const sourceResponse = await fetch(
|
|
311
|
-
`/api/.uilint/source?path=${encodeURIComponent(
|
|
312
|
-
element.source.fileName
|
|
313
|
-
)}`
|
|
314
|
-
);
|
|
315
|
-
if (!sourceResponse.ok) {
|
|
316
|
-
return {
|
|
317
|
-
elementId: element.id,
|
|
318
|
-
issues: [],
|
|
319
|
-
status: "error"
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
const sourceData = await sourceResponse.json();
|
|
323
|
-
const analyzeResponse = await fetch("/api/.uilint/analyze", {
|
|
324
|
-
method: "POST",
|
|
325
|
-
headers: { "Content-Type": "application/json" },
|
|
326
|
-
body: JSON.stringify({
|
|
327
|
-
sourceCode: sourceData.content,
|
|
328
|
-
filePath: sourceData.relativePath || element.source.fileName,
|
|
329
|
-
componentName: element.componentStack[0]?.name || element.tagName,
|
|
330
|
-
componentLine: element.source.lineNumber
|
|
331
|
-
})
|
|
332
|
-
});
|
|
333
|
-
if (!analyzeResponse.ok) {
|
|
334
|
-
return {
|
|
335
|
-
elementId: element.id,
|
|
336
|
-
issues: [],
|
|
337
|
-
status: "error"
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const result = await analyzeResponse.json();
|
|
341
|
-
return {
|
|
342
|
-
elementId: element.id,
|
|
343
|
-
issues: result.issues || [],
|
|
344
|
-
status: "complete"
|
|
345
|
-
};
|
|
346
|
-
} catch {
|
|
347
|
-
return {
|
|
348
|
-
elementId: element.id,
|
|
349
|
-
issues: [],
|
|
350
|
-
status: "error"
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
[]
|
|
563
|
+
const setLocatorStackIndex = useUILintStore(
|
|
564
|
+
(s) => s.setLocatorStackIndex
|
|
355
565
|
);
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
for (const el of elements) {
|
|
361
|
-
if (el.source) {
|
|
362
|
-
const file = el.source.fileName;
|
|
363
|
-
const existing = fileToElements.get(file) || [];
|
|
364
|
-
existing.push(el);
|
|
365
|
-
fileToElements.set(file, existing);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
for (let i = startIndex; i < elements.length; i++) {
|
|
369
|
-
if (scanAbortRef.current) {
|
|
370
|
-
setAutoScanState((prev) => ({ ...prev, status: "idle" }));
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
while (scanPausedRef.current) {
|
|
374
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
375
|
-
if (scanAbortRef.current) {
|
|
376
|
-
setAutoScanState((prev) => ({ ...prev, status: "idle" }));
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const element = elements[i];
|
|
381
|
-
setAutoScanState((prev) => ({
|
|
382
|
-
...prev,
|
|
383
|
-
currentIndex: i
|
|
384
|
-
}));
|
|
385
|
-
if (element.source && scannedFiles.has(element.source.fileName)) {
|
|
386
|
-
const existingElements = fileToElements.get(element.source.fileName);
|
|
387
|
-
if (existingElements && existingElements.length > 0) {
|
|
388
|
-
const firstId = existingElements[0].id;
|
|
389
|
-
setElementIssuesCache((prev) => {
|
|
390
|
-
const cached = prev.get(firstId);
|
|
391
|
-
if (cached) {
|
|
392
|
-
const newCache = new Map(prev);
|
|
393
|
-
newCache.set(element.id, { ...cached, elementId: element.id });
|
|
394
|
-
return newCache;
|
|
395
|
-
}
|
|
396
|
-
return prev;
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
setElementIssuesCache((prev) => {
|
|
402
|
-
const newCache = new Map(prev);
|
|
403
|
-
newCache.set(element.id, {
|
|
404
|
-
elementId: element.id,
|
|
405
|
-
issues: [],
|
|
406
|
-
status: "scanning"
|
|
407
|
-
});
|
|
408
|
-
return newCache;
|
|
409
|
-
});
|
|
410
|
-
const result = await scanElementForIssues(element);
|
|
411
|
-
setElementIssuesCache((prev) => {
|
|
412
|
-
const newCache = new Map(prev);
|
|
413
|
-
newCache.set(element.id, result);
|
|
414
|
-
return newCache;
|
|
415
|
-
});
|
|
416
|
-
if (element.source) {
|
|
417
|
-
scannedFiles.add(element.source.fileName);
|
|
418
|
-
}
|
|
419
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
420
|
-
}
|
|
421
|
-
setAutoScanState((prev) => ({
|
|
422
|
-
...prev,
|
|
423
|
-
status: "complete",
|
|
424
|
-
currentIndex: elements.length
|
|
425
|
-
}));
|
|
426
|
-
},
|
|
427
|
-
[scanElementForIssues]
|
|
566
|
+
const locatorGoUp = useUILintStore((s) => s.locatorGoUp);
|
|
567
|
+
const locatorGoDown = useUILintStore((s) => s.locatorGoDown);
|
|
568
|
+
const inspectedElement = useUILintStore(
|
|
569
|
+
(s) => s.inspectedElement
|
|
428
570
|
);
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
setElementIssuesCache(initialCache);
|
|
442
|
-
setAutoScanState({
|
|
443
|
-
status: "scanning",
|
|
444
|
-
currentIndex: 0,
|
|
445
|
-
totalElements: elements.length,
|
|
446
|
-
elements
|
|
447
|
-
});
|
|
448
|
-
runScanLoop(elements, 0);
|
|
449
|
-
}, [settings.hideNodeModules, runScanLoop]);
|
|
450
|
-
const pauseAutoScan = useCallback(() => {
|
|
451
|
-
scanPausedRef.current = true;
|
|
452
|
-
setAutoScanState((prev) => ({ ...prev, status: "paused" }));
|
|
453
|
-
}, []);
|
|
454
|
-
const resumeAutoScan = useCallback(() => {
|
|
455
|
-
scanPausedRef.current = false;
|
|
456
|
-
setAutoScanState((prev) => {
|
|
457
|
-
if (prev.status === "paused") {
|
|
458
|
-
runScanLoop(prev.elements, prev.currentIndex);
|
|
459
|
-
return { ...prev, status: "scanning" };
|
|
460
|
-
}
|
|
461
|
-
return prev;
|
|
462
|
-
});
|
|
463
|
-
}, [runScanLoop]);
|
|
464
|
-
const stopAutoScan = useCallback(() => {
|
|
465
|
-
scanAbortRef.current = true;
|
|
466
|
-
scanPausedRef.current = false;
|
|
467
|
-
setAutoScanState(DEFAULT_AUTO_SCAN_STATE);
|
|
468
|
-
setElementIssuesCache(/* @__PURE__ */ new Map());
|
|
469
|
-
}, []);
|
|
470
|
-
const locatorGoUp = useCallback(() => {
|
|
471
|
-
if (!locatorTarget) return;
|
|
472
|
-
const maxIndex = locatorTarget.componentStack.length;
|
|
473
|
-
setLocatorStackIndex((prev) => Math.min(prev + 1, maxIndex));
|
|
474
|
-
}, [locatorTarget]);
|
|
475
|
-
const locatorGoDown = useCallback(() => {
|
|
476
|
-
setLocatorStackIndex((prev) => Math.max(prev - 1, 0));
|
|
477
|
-
}, []);
|
|
571
|
+
const setInspectedElement = useUILintStore(
|
|
572
|
+
(s) => s.setInspectedElement
|
|
573
|
+
);
|
|
574
|
+
const autoScanState = useUILintStore((s) => s.autoScanState);
|
|
575
|
+
const elementIssuesCache = useUILintStore(
|
|
576
|
+
(s) => s.elementIssuesCache
|
|
577
|
+
);
|
|
578
|
+
const startAutoScan = useUILintStore((s) => s.startAutoScan);
|
|
579
|
+
const pauseAutoScan = useUILintStore((s) => s.pauseAutoScan);
|
|
580
|
+
const resumeAutoScan = useUILintStore((s) => s.resumeAutoScan);
|
|
581
|
+
const stopAutoScan = useUILintStore((s) => s.stopAutoScan);
|
|
582
|
+
const effectiveLocatorTarget = useEffectiveLocatorTarget();
|
|
478
583
|
const getLocatorTargetFromElement = useCallback(
|
|
479
584
|
(element) => {
|
|
480
585
|
if (element.closest("[data-ui-lint]")) return null;
|
|
481
|
-
let source =
|
|
586
|
+
let source = null;
|
|
482
587
|
let componentStack = [];
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
source = getDebugSource(fiber._debugOwner);
|
|
489
|
-
}
|
|
490
|
-
componentStack = getComponentStack(fiber);
|
|
588
|
+
const fiber = getFiberFromElement(element);
|
|
589
|
+
if (fiber) {
|
|
590
|
+
source = getDebugSource(fiber);
|
|
591
|
+
if (!source && fiber._debugOwner) {
|
|
592
|
+
source = getDebugSource(fiber._debugOwner);
|
|
491
593
|
}
|
|
594
|
+
componentStack = getComponentStack(fiber);
|
|
595
|
+
}
|
|
596
|
+
if (!source) {
|
|
597
|
+
source = getSourceFromDataLoc(element);
|
|
492
598
|
}
|
|
493
599
|
if (!source && componentStack.length === 0) return null;
|
|
494
600
|
if (settings.hideNodeModules && source && isNodeModulesPath(source.fileName)) {
|
|
@@ -530,31 +636,45 @@ function UILintProvider({
|
|
|
530
636
|
}
|
|
531
637
|
setLocatorTarget(null);
|
|
532
638
|
},
|
|
533
|
-
[
|
|
639
|
+
[
|
|
640
|
+
altKeyHeld,
|
|
641
|
+
inspectedElement,
|
|
642
|
+
getLocatorTargetFromElement,
|
|
643
|
+
setLocatorTarget
|
|
644
|
+
]
|
|
534
645
|
);
|
|
535
646
|
const handleLocatorClick = useCallback(
|
|
536
647
|
(e) => {
|
|
537
|
-
if (!altKeyHeld || !
|
|
648
|
+
if (!altKeyHeld && !inspectedElement || !effectiveLocatorTarget) return;
|
|
649
|
+
const targetEl = e.target;
|
|
650
|
+
if (targetEl?.closest?.("[data-ui-lint]")) return;
|
|
538
651
|
e.preventDefault();
|
|
539
652
|
e.stopPropagation();
|
|
540
|
-
let source =
|
|
541
|
-
if (locatorStackIndex > 0 &&
|
|
542
|
-
const stackItem =
|
|
653
|
+
let source = effectiveLocatorTarget.source;
|
|
654
|
+
if (locatorStackIndex > 0 && effectiveLocatorTarget.componentStack.length > 0) {
|
|
655
|
+
const stackItem = effectiveLocatorTarget.componentStack[locatorStackIndex - 1];
|
|
543
656
|
if (stackItem?.source) {
|
|
544
657
|
source = stackItem.source;
|
|
545
658
|
}
|
|
546
659
|
}
|
|
547
660
|
setInspectedElement({
|
|
548
|
-
element:
|
|
661
|
+
element: effectiveLocatorTarget.element,
|
|
549
662
|
source,
|
|
550
|
-
componentStack:
|
|
551
|
-
rect:
|
|
663
|
+
componentStack: effectiveLocatorTarget.componentStack,
|
|
664
|
+
rect: effectiveLocatorTarget.rect
|
|
552
665
|
});
|
|
553
|
-
setAltKeyHeld(false);
|
|
554
666
|
setLocatorTarget(null);
|
|
555
667
|
setLocatorStackIndex(0);
|
|
556
668
|
},
|
|
557
|
-
[
|
|
669
|
+
[
|
|
670
|
+
altKeyHeld,
|
|
671
|
+
effectiveLocatorTarget,
|
|
672
|
+
inspectedElement,
|
|
673
|
+
locatorStackIndex,
|
|
674
|
+
setInspectedElement,
|
|
675
|
+
setLocatorTarget,
|
|
676
|
+
setLocatorStackIndex
|
|
677
|
+
]
|
|
558
678
|
);
|
|
559
679
|
useEffect(() => {
|
|
560
680
|
if (!isBrowser() || !enabled) return;
|
|
@@ -584,14 +704,12 @@ function UILintProvider({
|
|
|
584
704
|
window.removeEventListener("keyup", handleKeyUp);
|
|
585
705
|
window.removeEventListener("blur", handleBlur);
|
|
586
706
|
};
|
|
587
|
-
}, [enabled]);
|
|
707
|
+
}, [enabled, setAltKeyHeld, setLocatorTarget, setLocatorStackIndex]);
|
|
588
708
|
useEffect(() => {
|
|
589
709
|
if (!isBrowser() || !enabled) return;
|
|
590
710
|
if (!altKeyHeld && !inspectedElement) return;
|
|
591
711
|
window.addEventListener("mousemove", handleMouseMove);
|
|
592
|
-
|
|
593
|
-
window.addEventListener("click", handleLocatorClick, true);
|
|
594
|
-
}
|
|
712
|
+
window.addEventListener("click", handleLocatorClick, true);
|
|
595
713
|
return () => {
|
|
596
714
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
597
715
|
window.removeEventListener("click", handleLocatorClick, true);
|
|
@@ -606,7 +724,7 @@ function UILintProvider({
|
|
|
606
724
|
useEffect(() => {
|
|
607
725
|
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
608
726
|
const handleWheel = (e) => {
|
|
609
|
-
if (!
|
|
727
|
+
if (!effectiveLocatorTarget) return;
|
|
610
728
|
e.preventDefault();
|
|
611
729
|
if (e.deltaY > 0) {
|
|
612
730
|
locatorGoUp();
|
|
@@ -616,7 +734,7 @@ function UILintProvider({
|
|
|
616
734
|
};
|
|
617
735
|
window.addEventListener("wheel", handleWheel, { passive: false });
|
|
618
736
|
return () => window.removeEventListener("wheel", handleWheel);
|
|
619
|
-
}, [enabled, altKeyHeld,
|
|
737
|
+
}, [enabled, altKeyHeld, effectiveLocatorTarget, locatorGoUp, locatorGoDown]);
|
|
620
738
|
useEffect(() => {
|
|
621
739
|
if (!isBrowser() || !enabled) return;
|
|
622
740
|
const handleKeyDown = (e) => {
|
|
@@ -626,17 +744,13 @@ function UILintProvider({
|
|
|
626
744
|
};
|
|
627
745
|
window.addEventListener("keydown", handleKeyDown);
|
|
628
746
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
629
|
-
}, [enabled, inspectedElement]);
|
|
747
|
+
}, [enabled, inspectedElement, setInspectedElement]);
|
|
630
748
|
useEffect(() => {
|
|
631
749
|
setIsMounted(true);
|
|
632
750
|
}, []);
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
...locatorTarget,
|
|
637
|
-
stackIndex: locatorStackIndex
|
|
638
|
-
};
|
|
639
|
-
}, [locatorTarget, locatorStackIndex]);
|
|
751
|
+
const wrappedStartAutoScan = useCallback(() => {
|
|
752
|
+
startAutoScan(settings.hideNodeModules);
|
|
753
|
+
}, [startAutoScan, settings.hideNodeModules]);
|
|
640
754
|
const contextValue = useMemo(
|
|
641
755
|
() => ({
|
|
642
756
|
settings,
|
|
@@ -649,7 +763,7 @@ function UILintProvider({
|
|
|
649
763
|
setInspectedElement,
|
|
650
764
|
autoScanState,
|
|
651
765
|
elementIssuesCache,
|
|
652
|
-
startAutoScan,
|
|
766
|
+
startAutoScan: wrappedStartAutoScan,
|
|
653
767
|
pauseAutoScan,
|
|
654
768
|
resumeAutoScan,
|
|
655
769
|
stopAutoScan
|
|
@@ -662,9 +776,10 @@ function UILintProvider({
|
|
|
662
776
|
locatorGoUp,
|
|
663
777
|
locatorGoDown,
|
|
664
778
|
inspectedElement,
|
|
779
|
+
setInspectedElement,
|
|
665
780
|
autoScanState,
|
|
666
781
|
elementIssuesCache,
|
|
667
|
-
|
|
782
|
+
wrappedStartAutoScan,
|
|
668
783
|
pauseAutoScan,
|
|
669
784
|
resumeAutoScan,
|
|
670
785
|
stopAutoScan
|
|
@@ -681,10 +796,10 @@ function UILintUI() {
|
|
|
681
796
|
const [components, setComponents] = useState(null);
|
|
682
797
|
useEffect(() => {
|
|
683
798
|
Promise.all([
|
|
684
|
-
import("./UILintToolbar-
|
|
685
|
-
import("./InspectionPanel-
|
|
686
|
-
import("./LocatorOverlay-
|
|
687
|
-
import("./ElementBadges-
|
|
799
|
+
import("./UILintToolbar-GMZ6YSI2.js"),
|
|
800
|
+
import("./InspectionPanel-4OWY4FVY.js"),
|
|
801
|
+
import("./LocatorOverlay-JJDOKNOS.js"),
|
|
802
|
+
import("./ElementBadges-HFQNIIO2.js")
|
|
688
803
|
]).then(([toolbar, panel, locator, badges]) => {
|
|
689
804
|
setComponents({
|
|
690
805
|
Toolbar: toolbar.UILintToolbar,
|
|
@@ -710,9 +825,6 @@ function UILintUI() {
|
|
|
710
825
|
}
|
|
711
826
|
|
|
712
827
|
export {
|
|
713
|
-
FILE_COLORS,
|
|
714
|
-
DEFAULT_SETTINGS,
|
|
715
|
-
DATA_UILINT_ID,
|
|
716
828
|
getFiberFromElement,
|
|
717
829
|
getDebugSource,
|
|
718
830
|
getDebugOwner,
|
|
@@ -725,6 +837,10 @@ export {
|
|
|
725
837
|
getElementById,
|
|
726
838
|
updateElementRects,
|
|
727
839
|
buildEditorUrl,
|
|
840
|
+
FILE_COLORS,
|
|
841
|
+
DEFAULT_SETTINGS,
|
|
842
|
+
DATA_UILINT_ID,
|
|
843
|
+
useUILintStore,
|
|
728
844
|
useUILintContext,
|
|
729
845
|
UILintProvider
|
|
730
846
|
};
|