uilint-react 0.1.37 → 0.1.39

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,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- // src/components/ui-lint/fiber-utils.ts
3
+ // src/components/ui-lint/dom-utils.ts
4
4
  var DATA_ATTR = "data-ui-lint-id";
5
5
  var COLORS = [
6
6
  "#3B82F6",
@@ -27,30 +27,6 @@ var SKIP_TAGS = /* @__PURE__ */ new Set([
27
27
  "LINK"
28
28
  ]);
29
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
- }
40
- function getFiberFromElement(element) {
41
- const keys = Object.keys(element);
42
- const fiberKey = keys.find((k) => k.startsWith("__reactFiber$"));
43
- if (!fiberKey) return null;
44
- return element[fiberKey];
45
- }
46
- function getDebugSource(fiber) {
47
- if (!fiber._debugSource) return null;
48
- return {
49
- fileName: fiber._debugSource.fileName,
50
- lineNumber: fiber._debugSource.lineNumber,
51
- columnNumber: fiber._debugSource.columnNumber
52
- };
53
- }
54
30
  function getSourceFromDataLoc(element) {
55
31
  const loc = element.getAttribute("data-loc");
56
32
  if (!loc) return null;
@@ -74,31 +50,6 @@ function getSourceFromDataLoc(element) {
74
50
  }
75
51
  return null;
76
52
  }
77
- function getDebugOwner(fiber) {
78
- return fiber._debugOwner ?? null;
79
- }
80
- function getComponentName(fiber) {
81
- if (!fiber.type) return "Unknown";
82
- if (typeof fiber.type === "string") return fiber.type;
83
- if (typeof fiber.type === "function") {
84
- const fn = fiber.type;
85
- return fn.displayName || fn.name || "Anonymous";
86
- }
87
- return "Unknown";
88
- }
89
- function getComponentStack(fiber) {
90
- const stack = [];
91
- let current = fiber._debugOwner ?? null;
92
- while (current && stack.length < 20) {
93
- const name = getComponentName(current);
94
- const source = getDebugSource(current);
95
- if (current.tag <= 2 && name !== "Unknown") {
96
- stack.push({ name, source });
97
- }
98
- current = current._debugOwner ?? current.return;
99
- }
100
- return stack;
101
- }
102
53
  function isNodeModulesPath(path) {
103
54
  return path.includes("node_modules");
104
55
  }
@@ -119,57 +70,29 @@ function shouldSkipElement(element) {
119
70
  function scanDOMForSources(root = document.body, hideNodeModules = true) {
120
71
  const elements = [];
121
72
  elementCounter = 0;
73
+ const occurrenceByDataLoc = /* @__PURE__ */ new Map();
122
74
  cleanupDataAttributes();
123
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
124
- acceptNode: (node2) => {
125
- const el = node2;
126
- if (shouldSkipElement(el)) return NodeFilter.FILTER_REJECT;
127
- return NodeFilter.FILTER_ACCEPT;
128
- }
129
- });
130
- let node = walker.currentNode;
131
- while (node) {
132
- if (node instanceof Element) {
133
- let source = null;
134
- let componentStack = [];
135
- const fiber = getFiberFromElement(node);
136
- if (fiber) {
137
- source = getDebugSource(fiber);
138
- if (!source && fiber._debugOwner) {
139
- source = getDebugSource(fiber._debugOwner);
140
- }
141
- componentStack = getComponentStack(fiber);
142
- }
143
- if (!source) {
144
- source = getSourceFromDataLoc(node);
145
- }
146
- if (hideNodeModules && source && isNodeModulesPath(source.fileName)) {
147
- const appSource = componentStack.find(
148
- (c) => c.source && !isNodeModulesPath(c.source.fileName)
149
- );
150
- if (appSource?.source) {
151
- source = appSource.source;
152
- } else {
153
- node = walker.nextNode();
154
- continue;
155
- }
156
- }
157
- if (source) {
158
- const id = generateStableId(node, source);
159
- node.setAttribute(DATA_ATTR, id);
160
- const scannedElement = {
161
- id,
162
- element: node,
163
- tagName: node.tagName.toLowerCase(),
164
- className: typeof node.className === "string" ? node.className : "",
165
- source,
166
- componentStack,
167
- rect: node.getBoundingClientRect()
168
- };
169
- elements.push(scannedElement);
170
- }
75
+ const locElements = root.querySelectorAll("[data-loc]");
76
+ for (const el of locElements) {
77
+ if (shouldSkipElement(el)) continue;
78
+ const source = getSourceFromDataLoc(el);
79
+ if (!source) continue;
80
+ if (hideNodeModules && isNodeModulesPath(source.fileName)) {
81
+ continue;
171
82
  }
172
- node = walker.nextNode();
83
+ const dataLoc = el.getAttribute("data-loc");
84
+ const occurrence = (occurrenceByDataLoc.get(dataLoc) ?? 0) + 1;
85
+ occurrenceByDataLoc.set(dataLoc, occurrence);
86
+ const id = `loc:${dataLoc}#${occurrence}`;
87
+ el.setAttribute(DATA_ATTR, id);
88
+ elements.push({
89
+ id,
90
+ element: el,
91
+ tagName: el.tagName.toLowerCase(),
92
+ className: typeof el.className === "string" ? el.className : "",
93
+ source,
94
+ rect: el.getBoundingClientRect()
95
+ });
173
96
  }
174
97
  return elements;
175
98
  }
@@ -184,12 +107,12 @@ function groupBySourceFile(elements) {
184
107
  }
185
108
  const sourceFiles = [];
186
109
  let colorIndex = 0;
187
- for (const [path, elements2] of fileMap) {
110
+ for (const [path, fileElements] of fileMap) {
188
111
  sourceFiles.push({
189
112
  path,
190
113
  displayName: getDisplayName(path),
191
114
  color: COLORS[colorIndex % COLORS.length],
192
- elements: elements2
115
+ elements: fileElements
193
116
  });
194
117
  colorIndex++;
195
118
  }
@@ -266,7 +189,8 @@ var DATA_UILINT_ID = "data-ui-lint-id";
266
189
  import { create } from "zustand";
267
190
  function getDataLocFromId(id) {
268
191
  if (id.startsWith("loc:")) {
269
- return id.slice(4);
192
+ const raw = id.slice(4);
193
+ return raw.split("#")[0] || null;
270
194
  }
271
195
  return null;
272
196
  }
@@ -291,21 +215,25 @@ async function scanFileForIssues(sourceFile, store) {
291
215
  return { issues };
292
216
  }
293
217
  function distributeIssuesToElements(issues, elements, updateElementIssue, hasError) {
294
- const dataLocToElementId = /* @__PURE__ */ new Map();
218
+ const dataLocToElementIds = /* @__PURE__ */ new Map();
295
219
  for (const el of elements) {
296
220
  const dataLoc = getDataLocFromId(el.id);
297
221
  if (dataLoc) {
298
- dataLocToElementId.set(dataLoc, el.id);
222
+ const existing = dataLocToElementIds.get(dataLoc);
223
+ if (existing) existing.push(el.id);
224
+ else dataLocToElementIds.set(dataLoc, [el.id]);
299
225
  }
300
226
  }
301
227
  const issuesByElement = /* @__PURE__ */ new Map();
302
228
  for (const issue of issues) {
303
229
  if (issue.dataLoc) {
304
- const elementId = dataLocToElementId.get(issue.dataLoc);
305
- if (elementId) {
306
- const existing = issuesByElement.get(elementId) || [];
307
- existing.push(issue);
308
- issuesByElement.set(elementId, existing);
230
+ const elementIds = dataLocToElementIds.get(issue.dataLoc);
231
+ if (elementIds) {
232
+ for (const elementId of elementIds) {
233
+ const existing = issuesByElement.get(elementId) || [];
234
+ existing.push(issue);
235
+ issuesByElement.set(elementId, existing);
236
+ }
309
237
  }
310
238
  }
311
239
  }
@@ -342,27 +270,14 @@ var useUILintStore = create()((set, get) => ({
342
270
  setAltKeyHeld: (held) => set({ altKeyHeld: held }),
343
271
  locatorTarget: null,
344
272
  setLocatorTarget: (target) => set({ locatorTarget: target }),
345
- locatorStackIndex: 0,
346
- setLocatorStackIndex: (index) => set({ locatorStackIndex: index }),
347
- locatorGoUp: () => {
348
- const { locatorTarget, locatorStackIndex } = get();
349
- if (!locatorTarget) return;
350
- const maxIndex = locatorTarget.componentStack.length;
351
- set({ locatorStackIndex: Math.min(locatorStackIndex + 1, maxIndex) });
352
- },
353
- locatorGoDown: () => {
354
- const { locatorStackIndex } = get();
355
- set({ locatorStackIndex: Math.max(locatorStackIndex - 1, 0) });
356
- },
357
273
  // ============ Inspection ============
358
274
  inspectedElement: null,
359
275
  setInspectedElement: (el) => set({ inspectedElement: el }),
360
- // ============ Auto-Scan ============
276
+ // ============ Live Scanning ============
277
+ liveScanEnabled: false,
361
278
  autoScanState: DEFAULT_AUTO_SCAN_STATE,
362
279
  elementIssuesCache: /* @__PURE__ */ new Map(),
363
280
  scanLock: false,
364
- scanPaused: false,
365
- scanAborted: false,
366
281
  _setScanState: (partial) => set((state) => ({
367
282
  autoScanState: { ...state.autoScanState, ...partial }
368
283
  })),
@@ -371,16 +286,15 @@ var useUILintStore = create()((set, get) => ({
371
286
  newCache.set(id, issue);
372
287
  return { elementIssuesCache: newCache };
373
288
  }),
374
- startAutoScan: async (hideNodeModules) => {
289
+ enableLiveScan: async (hideNodeModules) => {
375
290
  const state = get();
376
291
  if (state.scanLock) {
377
292
  console.warn("UILint: Scan already in progress");
378
293
  return;
379
294
  }
380
295
  set({
381
- scanLock: true,
382
- scanPaused: false,
383
- scanAborted: false
296
+ liveScanEnabled: true,
297
+ scanLock: true
384
298
  });
385
299
  const elements = scanDOMForSources(document.body, hideNodeModules);
386
300
  const initialCache = /* @__PURE__ */ new Map();
@@ -400,59 +314,72 @@ var useUILintStore = create()((set, get) => ({
400
314
  elements
401
315
  }
402
316
  });
403
- await get()._runScanLoop(elements, 0);
404
- },
405
- pauseAutoScan: () => {
406
- set({ scanPaused: true });
407
- get()._setScanState({ status: "paused" });
408
- },
409
- resumeAutoScan: () => {
410
- const state = get();
411
- if (state.autoScanState.status !== "paused") return;
412
- set({ scanPaused: false });
413
- get()._setScanState({ status: "scanning" });
414
- get()._runScanLoop(
415
- state.autoScanState.elements,
416
- state.autoScanState.currentIndex
417
- );
317
+ await get()._runScanLoop(elements);
418
318
  },
419
- stopAutoScan: () => {
319
+ disableLiveScan: () => {
420
320
  set({
421
- scanAborted: true,
422
- scanPaused: false,
321
+ liveScanEnabled: false,
423
322
  scanLock: false,
424
323
  autoScanState: DEFAULT_AUTO_SCAN_STATE,
425
324
  elementIssuesCache: /* @__PURE__ */ new Map()
426
325
  });
427
326
  },
428
- _runScanLoop: async (elements, startIndex) => {
327
+ scanNewElements: async (newElements) => {
328
+ const state = get();
329
+ if (!state.liveScanEnabled) return;
330
+ if (newElements.length === 0) return;
331
+ set((s) => {
332
+ const newCache = new Map(s.elementIssuesCache);
333
+ for (const el of newElements) {
334
+ newCache.set(el.id, {
335
+ elementId: el.id,
336
+ issues: [],
337
+ status: "pending"
338
+ });
339
+ }
340
+ return {
341
+ elementIssuesCache: newCache,
342
+ autoScanState: {
343
+ ...s.autoScanState,
344
+ elements: [...s.autoScanState.elements, ...newElements],
345
+ totalElements: s.autoScanState.totalElements + newElements.length
346
+ }
347
+ };
348
+ });
349
+ const sourceFiles = groupBySourceFile(newElements);
350
+ for (const sourceFile of sourceFiles) {
351
+ for (const el of sourceFile.elements) {
352
+ get().updateElementIssue(el.id, {
353
+ elementId: el.id,
354
+ issues: [],
355
+ status: "scanning"
356
+ });
357
+ }
358
+ await new Promise((resolve) => requestAnimationFrame(resolve));
359
+ const { issues, error } = await scanFileForIssues(sourceFile, get());
360
+ distributeIssuesToElements(
361
+ issues,
362
+ sourceFile.elements,
363
+ get().updateElementIssue,
364
+ error ?? false
365
+ );
366
+ if (get().wsConnected && get().wsConnection) {
367
+ get().subscribeToFile(sourceFile.path);
368
+ }
369
+ await new Promise((resolve) => requestAnimationFrame(resolve));
370
+ }
371
+ },
372
+ _runScanLoop: async (elements) => {
429
373
  const sourceFiles = groupBySourceFile(elements);
430
374
  let processedElements = 0;
431
- let skipElements = startIndex;
432
375
  for (const sourceFile of sourceFiles) {
433
- if (skipElements >= sourceFile.elements.length) {
434
- skipElements -= sourceFile.elements.length;
435
- processedElements += sourceFile.elements.length;
436
- continue;
437
- }
438
- skipElements = 0;
439
- if (get().scanAborted) {
376
+ if (!get().liveScanEnabled) {
440
377
  set({
441
378
  scanLock: false,
442
- autoScanState: { ...get().autoScanState, status: "idle" }
379
+ autoScanState: DEFAULT_AUTO_SCAN_STATE
443
380
  });
444
381
  return;
445
382
  }
446
- while (get().scanPaused) {
447
- await new Promise((resolve) => setTimeout(resolve, 100));
448
- if (get().scanAborted) {
449
- set({
450
- scanLock: false,
451
- autoScanState: { ...get().autoScanState, status: "idle" }
452
- });
453
- return;
454
- }
455
- }
456
383
  get()._setScanState({ currentIndex: processedElements });
457
384
  for (const el of sourceFile.elements) {
458
385
  get().updateElementIssue(el.id, {
@@ -484,6 +411,33 @@ var useUILintStore = create()((set, get) => ({
484
411
  }
485
412
  });
486
413
  },
414
+ // ============ DOM Observer ============
415
+ removeStaleResults: (elementIds) => set((state) => {
416
+ const newCache = new Map(state.elementIssuesCache);
417
+ const newElements = state.autoScanState.elements.filter(
418
+ (el) => !elementIds.includes(el.id)
419
+ );
420
+ for (const id of elementIds) {
421
+ newCache.delete(id);
422
+ }
423
+ return {
424
+ elementIssuesCache: newCache,
425
+ autoScanState: {
426
+ ...state.autoScanState,
427
+ elements: newElements,
428
+ totalElements: newElements.length
429
+ }
430
+ };
431
+ }),
432
+ // ============ File/Element Selection ============
433
+ hoveredFilePath: null,
434
+ selectedFilePath: null,
435
+ selectedElementId: null,
436
+ hoveredElementId: null,
437
+ setHoveredFilePath: (path) => set({ hoveredFilePath: path }),
438
+ setSelectedFilePath: (path) => set({ selectedFilePath: path }),
439
+ setSelectedElementId: (id) => set({ selectedElementId: id }),
440
+ setHoveredElementId: (id) => set({ hoveredElementId: id }),
487
441
  // ============ WebSocket ============
488
442
  wsConnection: null,
489
443
  wsConnected: false,
@@ -642,7 +596,7 @@ var useUILintStore = create()((set, get) => ({
642
596
  return { eslintIssuesCache: next };
643
597
  });
644
598
  const state = get();
645
- if (state.autoScanState.status !== "idle") {
599
+ if (state.liveScanEnabled) {
646
600
  const sourceFiles = groupBySourceFile(state.autoScanState.elements);
647
601
  const sf = sourceFiles.find((s) => s.path === filePath);
648
602
  if (sf) {
@@ -702,7 +656,7 @@ var useUILintStore = create()((set, get) => ({
702
656
  return { eslintIssuesCache: next };
703
657
  });
704
658
  const state = get();
705
- if (state.autoScanState.status !== "idle") {
659
+ if (state.liveScanEnabled) {
706
660
  const sourceFiles = groupBySourceFile(state.autoScanState.elements);
707
661
  const sf = sourceFiles.find((s) => s.path === filePath);
708
662
  if (sf) {
@@ -735,27 +689,131 @@ var useUILintStore = create()((set, get) => ({
735
689
  get().connectWebSocket(wsUrl);
736
690
  }
737
691
  }));
738
- function useEffectiveLocatorTarget() {
739
- const locatorTarget = useUILintStore((s) => s.locatorTarget);
740
- const locatorStackIndex = useUILintStore(
741
- (s) => s.locatorStackIndex
742
- );
743
- if (!locatorTarget) return null;
744
- return {
745
- ...locatorTarget,
746
- stackIndex: locatorStackIndex
747
- };
748
- }
749
692
 
750
693
  // src/components/ui-lint/UILintProvider.tsx
751
694
  import {
752
695
  createContext,
753
696
  useContext,
754
697
  useState,
755
- useEffect,
756
- useCallback,
698
+ useEffect as useEffect2,
699
+ useCallback as useCallback2,
757
700
  useMemo
758
701
  } from "react";
702
+
703
+ // src/components/ui-lint/useDOMObserver.ts
704
+ import { useEffect, useRef, useCallback } from "react";
705
+ var RECONCILE_DEBOUNCE_MS = 100;
706
+ function useDOMObserver(enabled = true) {
707
+ const observerRef = useRef(null);
708
+ const reconcileTimeoutRef = useRef(
709
+ null
710
+ );
711
+ const knownElementIdsRef = useRef(/* @__PURE__ */ new Set());
712
+ const liveScanEnabled = useUILintStore((s) => s.liveScanEnabled);
713
+ const settings = useUILintStore((s) => s.settings);
714
+ const autoScanState = useUILintStore((s) => s.autoScanState);
715
+ const removeStaleResults = useUILintStore(
716
+ (s) => s.removeStaleResults
717
+ );
718
+ const scanNewElements = useUILintStore((s) => s.scanNewElements);
719
+ useEffect(() => {
720
+ if (autoScanState.elements.length > 0) {
721
+ const ids = new Set(autoScanState.elements.map((el) => el.id));
722
+ knownElementIdsRef.current = ids;
723
+ }
724
+ }, [autoScanState.elements]);
725
+ const reconcileElements = useCallback(() => {
726
+ if (!liveScanEnabled) return;
727
+ const currentElements = scanDOMForSources(
728
+ document.body,
729
+ settings.hideNodeModules
730
+ );
731
+ const currentIds = new Set(currentElements.map((el) => el.id));
732
+ const knownIds = knownElementIdsRef.current;
733
+ const newElements = [];
734
+ for (const el of currentElements) {
735
+ if (!knownIds.has(el.id)) {
736
+ newElements.push(el);
737
+ }
738
+ }
739
+ const removedElementIds = [];
740
+ for (const id of knownIds) {
741
+ if (!currentIds.has(id)) {
742
+ removedElementIds.push(id);
743
+ }
744
+ }
745
+ knownElementIdsRef.current = currentIds;
746
+ if (newElements.length > 0) {
747
+ scanNewElements(newElements);
748
+ }
749
+ if (removedElementIds.length > 0) {
750
+ removeStaleResults(removedElementIds);
751
+ }
752
+ }, [
753
+ liveScanEnabled,
754
+ settings.hideNodeModules,
755
+ scanNewElements,
756
+ removeStaleResults
757
+ ]);
758
+ const debouncedReconcile = useCallback(() => {
759
+ if (reconcileTimeoutRef.current) {
760
+ clearTimeout(reconcileTimeoutRef.current);
761
+ }
762
+ reconcileTimeoutRef.current = setTimeout(() => {
763
+ reconcileElements();
764
+ reconcileTimeoutRef.current = null;
765
+ }, RECONCILE_DEBOUNCE_MS);
766
+ }, [reconcileElements]);
767
+ useEffect(() => {
768
+ if (!enabled) return;
769
+ if (typeof window === "undefined") return;
770
+ const observer = new MutationObserver((mutations) => {
771
+ let hasRelevantChanges = false;
772
+ for (const mutation of mutations) {
773
+ for (const node of mutation.addedNodes) {
774
+ if (node instanceof Element) {
775
+ if (node.hasAttribute("data-loc")) {
776
+ hasRelevantChanges = true;
777
+ break;
778
+ }
779
+ if (node.querySelector("[data-loc]")) {
780
+ hasRelevantChanges = true;
781
+ break;
782
+ }
783
+ }
784
+ }
785
+ if (hasRelevantChanges) break;
786
+ for (const node of mutation.removedNodes) {
787
+ if (node instanceof Element) {
788
+ if (node.hasAttribute("data-loc") || node.querySelector("[data-loc]")) {
789
+ hasRelevantChanges = true;
790
+ break;
791
+ }
792
+ }
793
+ }
794
+ if (hasRelevantChanges) break;
795
+ }
796
+ if (hasRelevantChanges) {
797
+ debouncedReconcile();
798
+ }
799
+ });
800
+ observer.observe(document.body, {
801
+ childList: true,
802
+ subtree: true
803
+ });
804
+ observerRef.current = observer;
805
+ return () => {
806
+ observer.disconnect();
807
+ observerRef.current = null;
808
+ if (reconcileTimeoutRef.current) {
809
+ clearTimeout(reconcileTimeoutRef.current);
810
+ reconcileTimeoutRef.current = null;
811
+ }
812
+ };
813
+ }, [enabled, debouncedReconcile]);
814
+ }
815
+
816
+ // src/components/ui-lint/UILintProvider.tsx
759
817
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
760
818
  var UILintContext = createContext(null);
761
819
  function useUILintContext() {
@@ -777,76 +835,49 @@ function UILintProvider({
777
835
  const updateSettings = useUILintStore((s) => s.updateSettings);
778
836
  const altKeyHeld = useUILintStore((s) => s.altKeyHeld);
779
837
  const setAltKeyHeld = useUILintStore((s) => s.setAltKeyHeld);
838
+ const locatorTarget = useUILintStore((s) => s.locatorTarget);
780
839
  const setLocatorTarget = useUILintStore(
781
840
  (s) => s.setLocatorTarget
782
841
  );
783
- const locatorStackIndex = useUILintStore(
784
- (s) => s.locatorStackIndex
785
- );
786
- const setLocatorStackIndex = useUILintStore(
787
- (s) => s.setLocatorStackIndex
788
- );
789
- const locatorGoUp = useUILintStore((s) => s.locatorGoUp);
790
- const locatorGoDown = useUILintStore((s) => s.locatorGoDown);
791
842
  const inspectedElement = useUILintStore(
792
843
  (s) => s.inspectedElement
793
844
  );
794
845
  const setInspectedElement = useUILintStore(
795
846
  (s) => s.setInspectedElement
796
847
  );
848
+ const liveScanEnabled = useUILintStore((s) => s.liveScanEnabled);
797
849
  const autoScanState = useUILintStore((s) => s.autoScanState);
798
850
  const elementIssuesCache = useUILintStore(
799
851
  (s) => s.elementIssuesCache
800
852
  );
801
- const startAutoScan = useUILintStore((s) => s.startAutoScan);
802
- const pauseAutoScan = useUILintStore((s) => s.pauseAutoScan);
803
- const resumeAutoScan = useUILintStore((s) => s.resumeAutoScan);
804
- const stopAutoScan = useUILintStore((s) => s.stopAutoScan);
853
+ const storeEnableLiveScan = useUILintStore(
854
+ (s) => s.enableLiveScan
855
+ );
856
+ const disableLiveScan = useUILintStore((s) => s.disableLiveScan);
805
857
  const connectWebSocket = useUILintStore(
806
858
  (s) => s.connectWebSocket
807
859
  );
808
860
  const disconnectWebSocket = useUILintStore(
809
861
  (s) => s.disconnectWebSocket
810
862
  );
811
- const effectiveLocatorTarget = useEffectiveLocatorTarget();
812
- const getLocatorTargetFromElement = useCallback(
863
+ useDOMObserver(enabled && isMounted);
864
+ const getLocatorTargetFromElement = useCallback2(
813
865
  (element) => {
814
866
  if (element.closest("[data-ui-lint]")) return null;
815
- let source = null;
816
- let componentStack = [];
817
- const fiber = getFiberFromElement(element);
818
- if (fiber) {
819
- source = getDebugSource(fiber);
820
- if (!source && fiber._debugOwner) {
821
- source = getDebugSource(fiber._debugOwner);
822
- }
823
- componentStack = getComponentStack(fiber);
824
- }
825
- if (!source) {
826
- source = getSourceFromDataLoc(element);
827
- }
828
- if (!source && componentStack.length === 0) return null;
829
- if (settings.hideNodeModules && source && isNodeModulesPath(source.fileName)) {
830
- const appSource = componentStack.find(
831
- (c) => c.source && !isNodeModulesPath(c.source.fileName)
832
- );
833
- if (appSource?.source) {
834
- source = appSource.source;
835
- } else if (componentStack.length === 0) {
836
- return null;
837
- }
867
+ const source = getSourceFromDataLoc(element);
868
+ if (!source) return null;
869
+ if (settings.hideNodeModules && isNodeModulesPath(source.fileName)) {
870
+ return null;
838
871
  }
839
872
  return {
840
873
  element,
841
874
  source,
842
- componentStack,
843
- rect: element.getBoundingClientRect(),
844
- stackIndex: 0
875
+ rect: element.getBoundingClientRect()
845
876
  };
846
877
  },
847
878
  [settings.hideNodeModules]
848
879
  );
849
- const handleMouseMove = useCallback(
880
+ const handleMouseMove = useCallback2(
850
881
  (e) => {
851
882
  if (!altKeyHeld && !inspectedElement) return;
852
883
  const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
@@ -872,58 +903,44 @@ function UILintProvider({
872
903
  setLocatorTarget
873
904
  ]
874
905
  );
875
- const handleLocatorClick = useCallback(
906
+ const handleLocatorClick = useCallback2(
876
907
  (e) => {
877
- if (!altKeyHeld && !inspectedElement || !effectiveLocatorTarget) return;
908
+ if (!altKeyHeld && !inspectedElement || !locatorTarget) return;
878
909
  const targetEl = e.target;
879
910
  if (targetEl?.closest?.("[data-ui-lint]")) return;
880
911
  e.preventDefault();
881
912
  e.stopPropagation();
882
- let source = effectiveLocatorTarget.source;
883
- if (locatorStackIndex > 0 && effectiveLocatorTarget.componentStack.length > 0) {
884
- const stackItem = effectiveLocatorTarget.componentStack[locatorStackIndex - 1];
885
- if (stackItem?.source) {
886
- source = stackItem.source;
887
- }
888
- }
889
913
  setInspectedElement({
890
- element: effectiveLocatorTarget.element,
891
- source,
892
- componentStack: effectiveLocatorTarget.componentStack,
893
- rect: effectiveLocatorTarget.rect
914
+ element: locatorTarget.element,
915
+ source: locatorTarget.source,
916
+ rect: locatorTarget.rect
894
917
  });
895
918
  setLocatorTarget(null);
896
- setLocatorStackIndex(0);
897
919
  },
898
920
  [
899
921
  altKeyHeld,
900
- effectiveLocatorTarget,
922
+ locatorTarget,
901
923
  inspectedElement,
902
- locatorStackIndex,
903
924
  setInspectedElement,
904
- setLocatorTarget,
905
- setLocatorStackIndex
925
+ setLocatorTarget
906
926
  ]
907
927
  );
908
- useEffect(() => {
928
+ useEffect2(() => {
909
929
  if (!isBrowser() || !enabled) return;
910
930
  const handleKeyDown = (e) => {
911
931
  if (e.key === "Alt") {
912
932
  setAltKeyHeld(true);
913
- setLocatorStackIndex(0);
914
933
  }
915
934
  };
916
935
  const handleKeyUp = (e) => {
917
936
  if (e.key === "Alt") {
918
937
  setAltKeyHeld(false);
919
938
  setLocatorTarget(null);
920
- setLocatorStackIndex(0);
921
939
  }
922
940
  };
923
941
  const handleBlur = () => {
924
942
  setAltKeyHeld(false);
925
943
  setLocatorTarget(null);
926
- setLocatorStackIndex(0);
927
944
  };
928
945
  window.addEventListener("keydown", handleKeyDown);
929
946
  window.addEventListener("keyup", handleKeyUp);
@@ -933,8 +950,8 @@ function UILintProvider({
933
950
  window.removeEventListener("keyup", handleKeyUp);
934
951
  window.removeEventListener("blur", handleBlur);
935
952
  };
936
- }, [enabled, setAltKeyHeld, setLocatorTarget, setLocatorStackIndex]);
937
- useEffect(() => {
953
+ }, [enabled, setAltKeyHeld, setLocatorTarget]);
954
+ useEffect2(() => {
938
955
  if (!isBrowser() || !enabled) return;
939
956
  if (!altKeyHeld && !inspectedElement) return;
940
957
  window.addEventListener("mousemove", handleMouseMove);
@@ -950,21 +967,7 @@ function UILintProvider({
950
967
  handleMouseMove,
951
968
  handleLocatorClick
952
969
  ]);
953
- useEffect(() => {
954
- if (!isBrowser() || !enabled || !altKeyHeld) return;
955
- const handleWheel = (e) => {
956
- if (!effectiveLocatorTarget) return;
957
- e.preventDefault();
958
- if (e.deltaY > 0) {
959
- locatorGoUp();
960
- } else {
961
- locatorGoDown();
962
- }
963
- };
964
- window.addEventListener("wheel", handleWheel, { passive: false });
965
- return () => window.removeEventListener("wheel", handleWheel);
966
- }, [enabled, altKeyHeld, effectiveLocatorTarget, locatorGoUp, locatorGoDown]);
967
- useEffect(() => {
970
+ useEffect2(() => {
968
971
  if (!isBrowser() || !enabled) return;
969
972
  const handleKeyDown = (e) => {
970
973
  if (e.key === "Escape" && inspectedElement) {
@@ -974,10 +977,10 @@ function UILintProvider({
974
977
  window.addEventListener("keydown", handleKeyDown);
975
978
  return () => window.removeEventListener("keydown", handleKeyDown);
976
979
  }, [enabled, inspectedElement, setInspectedElement]);
977
- useEffect(() => {
980
+ useEffect2(() => {
978
981
  setIsMounted(true);
979
982
  }, []);
980
- useEffect(() => {
983
+ useEffect2(() => {
981
984
  if (!isBrowser() || !enabled) return;
982
985
  if (!isMounted) return;
983
986
  connectWebSocket();
@@ -985,41 +988,35 @@ function UILintProvider({
985
988
  disconnectWebSocket();
986
989
  };
987
990
  }, [enabled, isMounted, connectWebSocket, disconnectWebSocket]);
988
- const wrappedStartAutoScan = useCallback(() => {
989
- startAutoScan(settings.hideNodeModules);
990
- }, [startAutoScan, settings.hideNodeModules]);
991
+ const enableLiveScan = useCallback2(() => {
992
+ storeEnableLiveScan(settings.hideNodeModules);
993
+ }, [storeEnableLiveScan, settings.hideNodeModules]);
991
994
  const contextValue = useMemo(
992
995
  () => ({
993
996
  settings,
994
997
  updateSettings,
995
998
  altKeyHeld,
996
- locatorTarget: effectiveLocatorTarget,
997
- locatorGoUp,
998
- locatorGoDown,
999
+ locatorTarget,
999
1000
  inspectedElement,
1000
1001
  setInspectedElement,
1002
+ liveScanEnabled,
1001
1003
  autoScanState,
1002
1004
  elementIssuesCache,
1003
- startAutoScan: wrappedStartAutoScan,
1004
- pauseAutoScan,
1005
- resumeAutoScan,
1006
- stopAutoScan
1005
+ enableLiveScan,
1006
+ disableLiveScan
1007
1007
  }),
1008
1008
  [
1009
1009
  settings,
1010
1010
  updateSettings,
1011
1011
  altKeyHeld,
1012
- effectiveLocatorTarget,
1013
- locatorGoUp,
1014
- locatorGoDown,
1012
+ locatorTarget,
1015
1013
  inspectedElement,
1016
1014
  setInspectedElement,
1015
+ liveScanEnabled,
1017
1016
  autoScanState,
1018
1017
  elementIssuesCache,
1019
- wrappedStartAutoScan,
1020
- pauseAutoScan,
1021
- resumeAutoScan,
1022
- stopAutoScan
1018
+ enableLiveScan,
1019
+ disableLiveScan
1023
1020
  ]
1024
1021
  );
1025
1022
  const shouldRenderUI = enabled && isMounted;
@@ -1029,14 +1026,14 @@ function UILintProvider({
1029
1026
  ] });
1030
1027
  }
1031
1028
  function UILintUI() {
1032
- const { altKeyHeld, inspectedElement, autoScanState } = useUILintContext();
1029
+ const { altKeyHeld, inspectedElement, liveScanEnabled } = useUILintContext();
1033
1030
  const [components, setComponents] = useState(null);
1034
- useEffect(() => {
1031
+ useEffect2(() => {
1035
1032
  Promise.all([
1036
- import("./UILintToolbar-TM3DVGPO.js"),
1037
- import("./InspectionPanel-FY7QVWP5.js"),
1038
- import("./LocatorOverlay-IUZV5OVI.js"),
1039
- import("./ElementBadges-BASN6P5L.js")
1033
+ import("./UILintToolbar-4SH33QJU.js"),
1034
+ import("./InspectionPanel-VAHZBVVL.js"),
1035
+ import("./LocatorOverlay-BEJYHU6S.js"),
1036
+ import("./ElementBadges-3AFD2W4Z.js")
1040
1037
  ]).then(([toolbar, panel, locator, badges]) => {
1041
1038
  setComponents({
1042
1039
  Toolbar: toolbar.UILintToolbar,
@@ -1049,11 +1046,10 @@ function UILintUI() {
1049
1046
  }, []);
1050
1047
  if (!components) return null;
1051
1048
  const { Toolbar, Panel, LocatorOverlay, InspectedHighlight, ElementBadges } = components;
1052
- const showBadges = autoScanState.status !== "idle";
1053
1049
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1054
1050
  /* @__PURE__ */ jsx(Toolbar, {}),
1055
1051
  (altKeyHeld || inspectedElement) && /* @__PURE__ */ jsx(LocatorOverlay, {}),
1056
- showBadges && /* @__PURE__ */ jsx(ElementBadges, {}),
1052
+ liveScanEnabled && /* @__PURE__ */ jsx(ElementBadges, {}),
1057
1053
  inspectedElement && /* @__PURE__ */ jsxs(Fragment, { children: [
1058
1054
  /* @__PURE__ */ jsx(InspectedHighlight, {}),
1059
1055
  /* @__PURE__ */ jsx(Panel, {})
@@ -1062,10 +1058,7 @@ function UILintUI() {
1062
1058
  }
1063
1059
 
1064
1060
  export {
1065
- getFiberFromElement,
1066
- getDebugSource,
1067
- getDebugOwner,
1068
- getComponentStack,
1061
+ getSourceFromDataLoc,
1069
1062
  isNodeModulesPath,
1070
1063
  getDisplayName,
1071
1064
  scanDOMForSources,