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.
- package/dist/{ElementBadges-BASN6P5L.js → ElementBadges-3AFD2W4Z.js} +49 -18
- package/dist/{InspectionPanel-FY7QVWP5.js → InspectionPanel-VAHZBVVL.js} +2 -2
- package/dist/{LocatorOverlay-IUZV5OVI.js → LocatorOverlay-BEJYHU6S.js} +2 -2
- package/dist/{UILintToolbar-TM3DVGPO.js → UILintToolbar-4SH33QJU.js} +2 -2
- package/dist/chunk-4TLFW7LD.js +1143 -0
- package/dist/{chunk-LZX53CPI.js → chunk-7434TUMX.js} +23 -63
- package/dist/{chunk-DEHJKJNT.js → chunk-ILK73X6L.js} +285 -292
- package/dist/{chunk-ITKEGCAZ.js → chunk-NCNRCF5A.js} +9 -101
- package/dist/index.d.ts +43 -69
- package/dist/index.js +6 -12
- package/package.json +2 -2
- package/dist/chunk-EYWLUDXI.js +0 -695
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
// src/components/ui-lint/
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
305
|
-
if (
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
// ============
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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
|
|
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
|
-
|
|
319
|
+
disableLiveScan: () => {
|
|
420
320
|
set({
|
|
421
|
-
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
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
|
-
|
|
812
|
-
const getLocatorTargetFromElement =
|
|
863
|
+
useDOMObserver(enabled && isMounted);
|
|
864
|
+
const getLocatorTargetFromElement = useCallback2(
|
|
813
865
|
(element) => {
|
|
814
866
|
if (element.closest("[data-ui-lint]")) return null;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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
|
-
|
|
843
|
-
rect: element.getBoundingClientRect(),
|
|
844
|
-
stackIndex: 0
|
|
875
|
+
rect: element.getBoundingClientRect()
|
|
845
876
|
};
|
|
846
877
|
},
|
|
847
878
|
[settings.hideNodeModules]
|
|
848
879
|
);
|
|
849
|
-
const handleMouseMove =
|
|
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 =
|
|
906
|
+
const handleLocatorClick = useCallback2(
|
|
876
907
|
(e) => {
|
|
877
|
-
if (!altKeyHeld && !inspectedElement || !
|
|
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:
|
|
891
|
-
source,
|
|
892
|
-
|
|
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
|
-
|
|
922
|
+
locatorTarget,
|
|
901
923
|
inspectedElement,
|
|
902
|
-
locatorStackIndex,
|
|
903
924
|
setInspectedElement,
|
|
904
|
-
setLocatorTarget
|
|
905
|
-
setLocatorStackIndex
|
|
925
|
+
setLocatorTarget
|
|
906
926
|
]
|
|
907
927
|
);
|
|
908
|
-
|
|
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
|
|
937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
980
|
+
useEffect2(() => {
|
|
978
981
|
setIsMounted(true);
|
|
979
982
|
}, []);
|
|
980
|
-
|
|
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
|
|
989
|
-
|
|
990
|
-
}, [
|
|
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
|
|
997
|
-
locatorGoUp,
|
|
998
|
-
locatorGoDown,
|
|
999
|
+
locatorTarget,
|
|
999
1000
|
inspectedElement,
|
|
1000
1001
|
setInspectedElement,
|
|
1002
|
+
liveScanEnabled,
|
|
1001
1003
|
autoScanState,
|
|
1002
1004
|
elementIssuesCache,
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
resumeAutoScan,
|
|
1006
|
-
stopAutoScan
|
|
1005
|
+
enableLiveScan,
|
|
1006
|
+
disableLiveScan
|
|
1007
1007
|
}),
|
|
1008
1008
|
[
|
|
1009
1009
|
settings,
|
|
1010
1010
|
updateSettings,
|
|
1011
1011
|
altKeyHeld,
|
|
1012
|
-
|
|
1013
|
-
locatorGoUp,
|
|
1014
|
-
locatorGoDown,
|
|
1012
|
+
locatorTarget,
|
|
1015
1013
|
inspectedElement,
|
|
1016
1014
|
setInspectedElement,
|
|
1015
|
+
liveScanEnabled,
|
|
1017
1016
|
autoScanState,
|
|
1018
1017
|
elementIssuesCache,
|
|
1019
|
-
|
|
1020
|
-
|
|
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,
|
|
1029
|
+
const { altKeyHeld, inspectedElement, liveScanEnabled } = useUILintContext();
|
|
1033
1030
|
const [components, setComponents] = useState(null);
|
|
1034
|
-
|
|
1031
|
+
useEffect2(() => {
|
|
1035
1032
|
Promise.all([
|
|
1036
|
-
import("./UILintToolbar-
|
|
1037
|
-
import("./InspectionPanel-
|
|
1038
|
-
import("./LocatorOverlay-
|
|
1039
|
-
import("./ElementBadges-
|
|
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
|
-
|
|
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
|
-
|
|
1066
|
-
getDebugSource,
|
|
1067
|
-
getDebugOwner,
|
|
1068
|
-
getComponentStack,
|
|
1061
|
+
getSourceFromDataLoc,
|
|
1069
1062
|
isNodeModulesPath,
|
|
1070
1063
|
getDisplayName,
|
|
1071
1064
|
scanDOMForSources,
|