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.
@@ -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 = getSourceFromDataLoc(node);
133
+ let source = null;
166
134
  let componentStack = [];
167
- if (!source) {
168
- const fiber = getFiberFromElement(node);
169
- if (fiber) {
170
- source = getDebugSource(fiber);
171
- if (!source && fiber._debugOwner) {
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 = generateElementId();
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 [altKeyHeld, setAltKeyHeld] = useState(false);
286
- const [locatorTarget, setLocatorTarget] = useState(
287
- null
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 [locatorStackIndex, setLocatorStackIndex] = useState(0);
290
- const [inspectedElement, setInspectedElement] = useState(null);
291
- const [autoScanState, setAutoScanState] = useState(
292
- DEFAULT_AUTO_SCAN_STATE
560
+ const locatorStackIndex = useUILintStore(
561
+ (s) => s.locatorStackIndex
293
562
  );
294
- const [elementIssuesCache, setElementIssuesCache] = useState(/* @__PURE__ */ new Map());
295
- const scanPausedRef = useRef(false);
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 runScanLoop = useCallback(
357
- async (elements, startIndex) => {
358
- const fileToElements = /* @__PURE__ */ new Map();
359
- const scannedFiles = /* @__PURE__ */ new Set();
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 startAutoScan = useCallback(() => {
430
- scanPausedRef.current = false;
431
- scanAbortRef.current = false;
432
- const elements = scanDOMForSources(document.body, settings.hideNodeModules);
433
- const initialCache = /* @__PURE__ */ new Map();
434
- for (const el of elements) {
435
- initialCache.set(el.id, {
436
- elementId: el.id,
437
- issues: [],
438
- status: "pending"
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 = getSourceFromDataLoc(element);
586
+ let source = null;
482
587
  let componentStack = [];
483
- if (!source) {
484
- const fiber = getFiberFromElement(element);
485
- if (fiber) {
486
- source = getDebugSource(fiber);
487
- if (!source && fiber._debugOwner) {
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
- [altKeyHeld, inspectedElement, getLocatorTargetFromElement]
639
+ [
640
+ altKeyHeld,
641
+ inspectedElement,
642
+ getLocatorTargetFromElement,
643
+ setLocatorTarget
644
+ ]
534
645
  );
535
646
  const handleLocatorClick = useCallback(
536
647
  (e) => {
537
- if (!altKeyHeld || !locatorTarget) return;
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 = locatorTarget.source;
541
- if (locatorStackIndex > 0 && locatorTarget.componentStack.length > 0) {
542
- const stackItem = locatorTarget.componentStack[locatorStackIndex - 1];
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: locatorTarget.element,
661
+ element: effectiveLocatorTarget.element,
549
662
  source,
550
- componentStack: locatorTarget.componentStack,
551
- rect: locatorTarget.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
- [altKeyHeld, locatorTarget, locatorStackIndex]
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
- if (altKeyHeld) {
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 (!locatorTarget) return;
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, locatorTarget, locatorGoUp, locatorGoDown]);
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 effectiveLocatorTarget = useMemo(() => {
634
- if (!locatorTarget) return null;
635
- return {
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
- startAutoScan,
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-5PG6WVW6.js"),
685
- import("./InspectionPanel-47JBBKBL.js"),
686
- import("./LocatorOverlay-ADJUWU2H.js"),
687
- import("./ElementBadges-T4N3VQRI.js")
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
  };