uilint-react 0.2.0 → 0.2.3
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/DevTool.d.ts +11 -0
- package/dist/DevTool.d.ts.map +1 -0
- package/dist/ElementBadges-BNRIjtRW.js +672 -0
- package/dist/ElementBadges-BNRIjtRW.js.map +1 -0
- package/dist/VisionIssueBadge-C6vfwtec.js +154 -0
- package/dist/VisionIssueBadge-C6vfwtec.js.map +1 -0
- package/dist/components/Highlighter.d.ts +6 -0
- package/dist/components/Highlighter.d.ts.map +1 -0
- package/dist/components/ui-lint/ElementBadges.d.ts +6 -0
- package/dist/components/ui-lint/ElementBadges.d.ts.map +1 -0
- package/dist/components/ui-lint/InspectionPanel.d.ts +6 -0
- package/dist/components/ui-lint/InspectionPanel.d.ts.map +1 -0
- package/dist/components/ui-lint/LocatorOverlay.d.ts +14 -0
- package/dist/components/ui-lint/LocatorOverlay.d.ts.map +1 -0
- package/dist/components/ui-lint/RegionSelector.d.ts +18 -0
- package/dist/components/ui-lint/RegionSelector.d.ts.map +1 -0
- package/dist/components/ui-lint/ScanResultsPopover.d.ts +6 -0
- package/dist/components/ui-lint/ScanResultsPopover.d.ts.map +1 -0
- package/dist/components/ui-lint/ScreenshotViewer.d.ts +11 -0
- package/dist/components/ui-lint/ScreenshotViewer.d.ts.map +1 -0
- package/dist/components/ui-lint/UILintProvider.d.ts +11 -0
- package/dist/components/ui-lint/UILintProvider.d.ts.map +1 -0
- package/dist/components/ui-lint/VisionIssueBadge.d.ts +6 -0
- package/dist/components/ui-lint/VisionIssueBadge.d.ts.map +1 -0
- package/dist/components/ui-lint/VisionIssuesPanel.d.ts +12 -0
- package/dist/components/ui-lint/VisionIssuesPanel.d.ts.map +1 -0
- package/dist/components/ui-lint/badge-layout.d.ts +91 -0
- package/dist/components/ui-lint/badge-layout.d.ts.map +1 -0
- package/dist/components/ui-lint/code-formatting.d.ts +14 -0
- package/dist/components/ui-lint/code-formatting.d.ts.map +1 -0
- package/dist/components/ui-lint/dom-utils.d.ts +43 -0
- package/dist/components/ui-lint/dom-utils.d.ts.map +1 -0
- package/dist/components/ui-lint/index.d.ts +22 -0
- package/dist/components/ui-lint/index.d.ts.map +1 -0
- package/dist/components/ui-lint/inspection-panel-positioning.d.ts +41 -0
- package/dist/components/ui-lint/inspection-panel-positioning.d.ts.map +1 -0
- package/dist/components/ui-lint/portal-host.d.ts +3 -0
- package/dist/components/ui-lint/portal-host.d.ts.map +1 -0
- package/dist/components/ui-lint/scan-results/FileTree.d.ts +25 -0
- package/dist/components/ui-lint/scan-results/FileTree.d.ts.map +1 -0
- package/dist/components/ui-lint/scan-results/IssueRow.d.ts +16 -0
- package/dist/components/ui-lint/scan-results/IssueRow.d.ts.map +1 -0
- package/dist/components/ui-lint/scan-results/SearchFilter.d.ts +8 -0
- package/dist/components/ui-lint/scan-results/SearchFilter.d.ts.map +1 -0
- package/dist/components/ui-lint/source-fetcher.d.ts +40 -0
- package/dist/components/ui-lint/source-fetcher.d.ts.map +1 -0
- package/dist/components/ui-lint/store.d.ts +194 -0
- package/dist/components/ui-lint/store.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/TabbedToolbar.d.ts +2 -0
- package/dist/components/ui-lint/toolbar/TabbedToolbar.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/icons.d.ts +29 -0
- package/dist/components/ui-lint/toolbar/icons.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/index.d.ts +3 -0
- package/dist/components/ui-lint/toolbar/index.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/tabs/ConfigureTab.d.ts +2 -0
- package/dist/components/ui-lint/toolbar/tabs/ConfigureTab.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/tabs/ESLintTab.d.ts +2 -0
- package/dist/components/ui-lint/toolbar/tabs/ESLintTab.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/tabs/VisionTab.d.ts +2 -0
- package/dist/components/ui-lint/toolbar/tabs/VisionTab.d.ts.map +1 -0
- package/dist/components/ui-lint/toolbar/tokens.d.ts +45 -0
- package/dist/components/ui-lint/toolbar/tokens.d.ts.map +1 -0
- package/dist/components/ui-lint/types.d.ts +170 -0
- package/dist/components/ui-lint/types.d.ts.map +1 -0
- package/dist/components/ui-lint/useDOMObserver.d.ts +11 -0
- package/dist/components/ui-lint/useDOMObserver.d.ts.map +1 -0
- package/dist/components/ui-lint/visibility-utils.d.ts +41 -0
- package/dist/components/ui-lint/visibility-utils.d.ts.map +1 -0
- package/dist/consistency/highlights.d.ts +14 -0
- package/dist/consistency/highlights.d.ts.map +1 -0
- package/dist/consistency/index.d.ts +7 -0
- package/dist/consistency/index.d.ts.map +1 -0
- package/dist/consistency/snapshot.d.ts +14 -0
- package/dist/consistency/snapshot.d.ts.map +1 -0
- package/dist/consistency/types.d.ts +5 -0
- package/dist/consistency/types.d.ts.map +1 -0
- package/dist/devtools.d.ts +8 -0
- package/dist/devtools.d.ts.map +1 -0
- package/dist/devtools.js +259 -0
- package/dist/devtools.js.map +1 -0
- package/dist/environment-DVxa60C6.js +26 -0
- package/dist/environment-DVxa60C6.js.map +1 -0
- package/dist/index-BGzkrD0y.js +12304 -0
- package/dist/index-BGzkrD0y.js.map +1 -0
- package/dist/index-L3Zm-CoX.js +513 -0
- package/dist/index-L3Zm-CoX.js.map +1 -0
- package/dist/index.d.ts +13 -339
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -482
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/node.d.ts +7 -38
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +32 -79
- package/dist/node.js.map +1 -0
- package/dist/scanner/dom-scanner.d.ts +7 -0
- package/dist/scanner/dom-scanner.d.ts.map +1 -0
- package/dist/scanner/environment.d.ts +16 -0
- package/dist/scanner/environment.d.ts.map +1 -0
- package/dist/scanner/jsdom-adapter.d.ts +30 -0
- package/dist/scanner/jsdom-adapter.d.ts.map +1 -0
- package/dist/scanner/vision-capture.d.ts +131 -0
- package/dist/scanner/vision-capture.d.ts.map +1 -0
- package/dist/styles/inject-styles.d.ts +2 -0
- package/dist/styles/inject-styles.d.ts.map +1 -0
- package/dist/vision-capture-EIrYA_XF.js +216 -0
- package/dist/vision-capture-EIrYA_XF.js.map +1 -0
- package/dist/web-component.d.ts +2 -0
- package/dist/web-component.d.ts.map +1 -0
- package/package.json +36 -7
- package/dist/ElementBadges-2CTPMJ6L.js +0 -825
- package/dist/InspectionPanel-NXSE7CMH.js +0 -10
- package/dist/LocatorOverlay-3TKK74BD.js +0 -11
- package/dist/UILintToolbar-CLVXQHCZ.js +0 -10
- package/dist/chunk-552GIJIQ.js +0 -1109
- package/dist/chunk-K46BWHFU.js +0 -271
- package/dist/chunk-KKNPFLL2.js +0 -1853
- package/dist/chunk-S4IWHBOQ.js +0 -178
- package/dist/chunk-Z4AAGFIN.js +0 -933
package/dist/chunk-552GIJIQ.js
DELETED
|
@@ -1,1109 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// src/components/ui-lint/dom-utils.ts
|
|
4
|
-
var DATA_ATTR = "data-ui-lint-id";
|
|
5
|
-
var COLORS = [
|
|
6
|
-
"#3B82F6",
|
|
7
|
-
"#8B5CF6",
|
|
8
|
-
"#EC4899",
|
|
9
|
-
"#10B981",
|
|
10
|
-
"#F59E0B",
|
|
11
|
-
"#06B6D4",
|
|
12
|
-
"#EF4444",
|
|
13
|
-
"#84CC16",
|
|
14
|
-
"#6366F1",
|
|
15
|
-
"#F97316",
|
|
16
|
-
"#14B8A6",
|
|
17
|
-
"#A855F7"
|
|
18
|
-
];
|
|
19
|
-
var SKIP_TAGS = /* @__PURE__ */ new Set([
|
|
20
|
-
"SCRIPT",
|
|
21
|
-
"STYLE",
|
|
22
|
-
"SVG",
|
|
23
|
-
"NOSCRIPT",
|
|
24
|
-
"TEMPLATE",
|
|
25
|
-
"HEAD",
|
|
26
|
-
"META",
|
|
27
|
-
"LINK"
|
|
28
|
-
]);
|
|
29
|
-
var elementCounter = 0;
|
|
30
|
-
function getSourceFromDataLoc(element) {
|
|
31
|
-
const loc = element.getAttribute("data-loc");
|
|
32
|
-
if (!loc) return null;
|
|
33
|
-
const parts = loc.split(":");
|
|
34
|
-
if (parts.length < 2) return null;
|
|
35
|
-
const lastPart = parts[parts.length - 1];
|
|
36
|
-
const secondLastPart = parts[parts.length - 2];
|
|
37
|
-
const lastIsNumber = /^\d+$/.test(lastPart);
|
|
38
|
-
const secondLastIsNumber = /^\d+$/.test(secondLastPart);
|
|
39
|
-
if (lastIsNumber && secondLastIsNumber) {
|
|
40
|
-
const columnNumber = parseInt(lastPart, 10);
|
|
41
|
-
const lineNumber = parseInt(secondLastPart, 10);
|
|
42
|
-
const fileName = parts.slice(0, -2).join(":");
|
|
43
|
-
if (isNaN(lineNumber) || isNaN(columnNumber) || !fileName) return null;
|
|
44
|
-
return { fileName, lineNumber, columnNumber };
|
|
45
|
-
} else if (lastIsNumber) {
|
|
46
|
-
const lineNumber = parseInt(lastPart, 10);
|
|
47
|
-
const fileName = parts.slice(0, -1).join(":");
|
|
48
|
-
if (isNaN(lineNumber) || !fileName) return null;
|
|
49
|
-
return { fileName, lineNumber };
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
function isNodeModulesPath(path) {
|
|
54
|
-
return path.includes("node_modules");
|
|
55
|
-
}
|
|
56
|
-
function getDisplayName(path) {
|
|
57
|
-
const parts = path.split("/");
|
|
58
|
-
return parts[parts.length - 1] || path;
|
|
59
|
-
}
|
|
60
|
-
function shouldSkipElement(element) {
|
|
61
|
-
if (SKIP_TAGS.has(element.tagName.toUpperCase())) return true;
|
|
62
|
-
if (element.hasAttribute("data-ui-lint")) return true;
|
|
63
|
-
if (element.getAttribute("aria-hidden") === "true") return true;
|
|
64
|
-
const styles = window.getComputedStyle(element);
|
|
65
|
-
if (styles.display === "none" || styles.visibility === "hidden") return true;
|
|
66
|
-
const rect = element.getBoundingClientRect();
|
|
67
|
-
if (rect.width === 0 || rect.height === 0) return true;
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
function scanDOMForSources(root = document.body, hideNodeModules = true) {
|
|
71
|
-
const elements = [];
|
|
72
|
-
elementCounter = 0;
|
|
73
|
-
const occurrenceByDataLoc = /* @__PURE__ */ new Map();
|
|
74
|
-
cleanupDataAttributes();
|
|
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;
|
|
82
|
-
}
|
|
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
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return elements;
|
|
98
|
-
}
|
|
99
|
-
function groupBySourceFile(elements) {
|
|
100
|
-
const fileMap = /* @__PURE__ */ new Map();
|
|
101
|
-
for (const element of elements) {
|
|
102
|
-
if (!element.source) continue;
|
|
103
|
-
const path = element.source.fileName;
|
|
104
|
-
const existing = fileMap.get(path) || [];
|
|
105
|
-
existing.push(element);
|
|
106
|
-
fileMap.set(path, existing);
|
|
107
|
-
}
|
|
108
|
-
const sourceFiles = [];
|
|
109
|
-
let colorIndex = 0;
|
|
110
|
-
for (const [path, fileElements] of fileMap) {
|
|
111
|
-
sourceFiles.push({
|
|
112
|
-
path,
|
|
113
|
-
displayName: getDisplayName(path),
|
|
114
|
-
color: COLORS[colorIndex % COLORS.length],
|
|
115
|
-
elements: fileElements
|
|
116
|
-
});
|
|
117
|
-
colorIndex++;
|
|
118
|
-
}
|
|
119
|
-
sourceFiles.sort((a, b) => b.elements.length - a.elements.length);
|
|
120
|
-
return sourceFiles;
|
|
121
|
-
}
|
|
122
|
-
function cleanupDataAttributes() {
|
|
123
|
-
const elements = document.querySelectorAll(`[${DATA_ATTR}]`);
|
|
124
|
-
elements.forEach((el) => el.removeAttribute(DATA_ATTR));
|
|
125
|
-
elementCounter = 0;
|
|
126
|
-
}
|
|
127
|
-
function getElementById(id) {
|
|
128
|
-
return document.querySelector(`[${DATA_ATTR}="${id}"]`);
|
|
129
|
-
}
|
|
130
|
-
function updateElementRects(elements) {
|
|
131
|
-
return elements.map((el) => ({
|
|
132
|
-
...el,
|
|
133
|
-
rect: el.element.getBoundingClientRect()
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
136
|
-
function buildEditorUrl(source, editor = "cursor", workspaceRoot) {
|
|
137
|
-
const { fileName, lineNumber, columnNumber } = source;
|
|
138
|
-
const column = columnNumber ?? 1;
|
|
139
|
-
let absolutePath = fileName;
|
|
140
|
-
if (workspaceRoot && !fileName.startsWith("/")) {
|
|
141
|
-
const root = workspaceRoot.endsWith("/") ? workspaceRoot.slice(0, -1) : workspaceRoot;
|
|
142
|
-
absolutePath = `${root}/${fileName}`;
|
|
143
|
-
}
|
|
144
|
-
if (editor === "cursor") {
|
|
145
|
-
return `cursor://file/${encodeURIComponent(
|
|
146
|
-
absolutePath
|
|
147
|
-
)}:${lineNumber}:${column}`;
|
|
148
|
-
}
|
|
149
|
-
return `vscode://file/${encodeURIComponent(
|
|
150
|
-
absolutePath
|
|
151
|
-
)}:${lineNumber}:${column}`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/components/ui-lint/types.ts
|
|
155
|
-
var FILE_COLORS = [
|
|
156
|
-
"#3B82F6",
|
|
157
|
-
// blue
|
|
158
|
-
"#8B5CF6",
|
|
159
|
-
// violet
|
|
160
|
-
"#EC4899",
|
|
161
|
-
// pink
|
|
162
|
-
"#10B981",
|
|
163
|
-
// emerald
|
|
164
|
-
"#F59E0B",
|
|
165
|
-
// amber
|
|
166
|
-
"#06B6D4",
|
|
167
|
-
// cyan
|
|
168
|
-
"#EF4444",
|
|
169
|
-
// red
|
|
170
|
-
"#84CC16",
|
|
171
|
-
// lime
|
|
172
|
-
"#6366F1",
|
|
173
|
-
// indigo
|
|
174
|
-
"#F97316",
|
|
175
|
-
// orange
|
|
176
|
-
"#14B8A6",
|
|
177
|
-
// teal
|
|
178
|
-
"#A855F7"
|
|
179
|
-
// purple
|
|
180
|
-
];
|
|
181
|
-
var DEFAULT_SETTINGS = {
|
|
182
|
-
hideNodeModules: true,
|
|
183
|
-
autoScanEnabled: false
|
|
184
|
-
};
|
|
185
|
-
var DEFAULT_AUTO_SCAN_STATE = {
|
|
186
|
-
status: "idle",
|
|
187
|
-
currentIndex: 0,
|
|
188
|
-
totalElements: 0,
|
|
189
|
-
elements: []
|
|
190
|
-
};
|
|
191
|
-
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
192
|
-
|
|
193
|
-
// src/components/ui-lint/store.ts
|
|
194
|
-
import { create } from "zustand";
|
|
195
|
-
function getDataLocFromId(id) {
|
|
196
|
-
if (id.startsWith("loc:")) {
|
|
197
|
-
const raw = id.slice(4);
|
|
198
|
-
return raw.split("#")[0] || null;
|
|
199
|
-
}
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
async function scanFileForIssues(sourceFile, store) {
|
|
203
|
-
if (sourceFile.elements.length === 0) {
|
|
204
|
-
return { issues: [] };
|
|
205
|
-
}
|
|
206
|
-
const filePath = sourceFile.path;
|
|
207
|
-
let issues = [];
|
|
208
|
-
if (store.wsConnected && store.wsConnection) {
|
|
209
|
-
try {
|
|
210
|
-
issues = await store.requestFileLint(filePath);
|
|
211
|
-
console.log("[UILint] ESLint issues:", issues);
|
|
212
|
-
} catch (err) {
|
|
213
|
-
console.warn("[UILint] WebSocket lint failed:", err);
|
|
214
|
-
return { issues: [], error: true };
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
console.warn("[UILint] WebSocket not connected");
|
|
218
|
-
return { issues: [], error: true };
|
|
219
|
-
}
|
|
220
|
-
return { issues };
|
|
221
|
-
}
|
|
222
|
-
function distributeIssuesToElements(issues, elements, filePath, updateElementIssue, updateFileIssues, hasError) {
|
|
223
|
-
const dataLocToElementIds = /* @__PURE__ */ new Map();
|
|
224
|
-
for (const el of elements) {
|
|
225
|
-
const dataLoc = getDataLocFromId(el.id);
|
|
226
|
-
if (dataLoc) {
|
|
227
|
-
const existing = dataLocToElementIds.get(dataLoc);
|
|
228
|
-
if (existing) existing.push(el.id);
|
|
229
|
-
else dataLocToElementIds.set(dataLoc, [el.id]);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const issuesByElement = /* @__PURE__ */ new Map();
|
|
233
|
-
const unmappedIssues = [];
|
|
234
|
-
for (const issue of issues) {
|
|
235
|
-
if (issue.dataLoc) {
|
|
236
|
-
const elementIds = dataLocToElementIds.get(issue.dataLoc);
|
|
237
|
-
if (elementIds && elementIds.length > 0) {
|
|
238
|
-
for (const elementId of elementIds) {
|
|
239
|
-
const existing = issuesByElement.get(elementId) || [];
|
|
240
|
-
existing.push(issue);
|
|
241
|
-
issuesByElement.set(elementId, existing);
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
unmappedIssues.push(issue);
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
unmappedIssues.push(issue);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
for (const el of elements) {
|
|
251
|
-
const elementIssues = issuesByElement.get(el.id) || [];
|
|
252
|
-
updateElementIssue(el.id, {
|
|
253
|
-
elementId: el.id,
|
|
254
|
-
issues: elementIssues,
|
|
255
|
-
status: hasError ? "error" : "complete"
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
if (unmappedIssues.length > 0) {
|
|
259
|
-
updateFileIssues(filePath, unmappedIssues);
|
|
260
|
-
} else {
|
|
261
|
-
updateFileIssues(filePath, []);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
var DEFAULT_WS_URL = "ws://localhost:9234";
|
|
265
|
-
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
266
|
-
var RECONNECT_BASE_DELAY = 1e3;
|
|
267
|
-
var pendingRequests = /* @__PURE__ */ new Map();
|
|
268
|
-
var WS_REQUEST_TIMEOUT_MS = 12e4;
|
|
269
|
-
function makeRequestId() {
|
|
270
|
-
try {
|
|
271
|
-
const c = globalThis.crypto;
|
|
272
|
-
if (c?.randomUUID) return c.randomUUID();
|
|
273
|
-
} catch {
|
|
274
|
-
}
|
|
275
|
-
return `req_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
276
|
-
}
|
|
277
|
-
var useUILintStore = create()((set, get) => ({
|
|
278
|
-
// ============ Settings ============
|
|
279
|
-
settings: DEFAULT_SETTINGS,
|
|
280
|
-
updateSettings: (partial) => set((state) => ({
|
|
281
|
-
settings: { ...state.settings, ...partial }
|
|
282
|
-
})),
|
|
283
|
-
// ============ Locator Mode ============
|
|
284
|
-
altKeyHeld: false,
|
|
285
|
-
setAltKeyHeld: (held) => set({ altKeyHeld: held }),
|
|
286
|
-
locatorTarget: null,
|
|
287
|
-
setLocatorTarget: (target) => set({ locatorTarget: target }),
|
|
288
|
-
// ============ Inspection ============
|
|
289
|
-
inspectedElement: null,
|
|
290
|
-
setInspectedElement: (el) => set({ inspectedElement: el }),
|
|
291
|
-
// ============ Live Scanning ============
|
|
292
|
-
liveScanEnabled: false,
|
|
293
|
-
autoScanState: DEFAULT_AUTO_SCAN_STATE,
|
|
294
|
-
elementIssuesCache: /* @__PURE__ */ new Map(),
|
|
295
|
-
fileIssuesCache: /* @__PURE__ */ new Map(),
|
|
296
|
-
scanLock: false,
|
|
297
|
-
_setScanState: (partial) => set((state) => ({
|
|
298
|
-
autoScanState: { ...state.autoScanState, ...partial }
|
|
299
|
-
})),
|
|
300
|
-
updateElementIssue: (id, issue) => set((state) => {
|
|
301
|
-
const newCache = new Map(state.elementIssuesCache);
|
|
302
|
-
newCache.set(id, issue);
|
|
303
|
-
return { elementIssuesCache: newCache };
|
|
304
|
-
}),
|
|
305
|
-
updateFileIssues: (filePath, issues) => set((state) => {
|
|
306
|
-
const newCache = new Map(state.fileIssuesCache);
|
|
307
|
-
if (issues.length > 0) {
|
|
308
|
-
newCache.set(filePath, issues);
|
|
309
|
-
} else {
|
|
310
|
-
newCache.delete(filePath);
|
|
311
|
-
}
|
|
312
|
-
return { fileIssuesCache: newCache };
|
|
313
|
-
}),
|
|
314
|
-
enableLiveScan: async (hideNodeModules) => {
|
|
315
|
-
const state = get();
|
|
316
|
-
if (state.scanLock) {
|
|
317
|
-
console.warn("UILint: Scan already in progress");
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
set({
|
|
321
|
-
liveScanEnabled: true,
|
|
322
|
-
scanLock: true
|
|
323
|
-
});
|
|
324
|
-
const elements = scanDOMForSources(document.body, hideNodeModules);
|
|
325
|
-
const initialCache = /* @__PURE__ */ new Map();
|
|
326
|
-
for (const el of elements) {
|
|
327
|
-
initialCache.set(el.id, {
|
|
328
|
-
elementId: el.id,
|
|
329
|
-
issues: [],
|
|
330
|
-
status: "pending"
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
set({
|
|
334
|
-
elementIssuesCache: initialCache,
|
|
335
|
-
autoScanState: {
|
|
336
|
-
status: "scanning",
|
|
337
|
-
currentIndex: 0,
|
|
338
|
-
totalElements: elements.length,
|
|
339
|
-
elements
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
await get()._runScanLoop(elements);
|
|
343
|
-
},
|
|
344
|
-
disableLiveScan: () => {
|
|
345
|
-
set({
|
|
346
|
-
liveScanEnabled: false,
|
|
347
|
-
scanLock: false,
|
|
348
|
-
autoScanState: DEFAULT_AUTO_SCAN_STATE,
|
|
349
|
-
elementIssuesCache: /* @__PURE__ */ new Map(),
|
|
350
|
-
fileIssuesCache: /* @__PURE__ */ new Map()
|
|
351
|
-
});
|
|
352
|
-
},
|
|
353
|
-
scanNewElements: async (newElements) => {
|
|
354
|
-
const state = get();
|
|
355
|
-
if (!state.liveScanEnabled) return;
|
|
356
|
-
if (newElements.length === 0) return;
|
|
357
|
-
set((s) => {
|
|
358
|
-
const newCache = new Map(s.elementIssuesCache);
|
|
359
|
-
for (const el of newElements) {
|
|
360
|
-
newCache.set(el.id, {
|
|
361
|
-
elementId: el.id,
|
|
362
|
-
issues: [],
|
|
363
|
-
status: "pending"
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
elementIssuesCache: newCache,
|
|
368
|
-
autoScanState: {
|
|
369
|
-
...s.autoScanState,
|
|
370
|
-
elements: [...s.autoScanState.elements, ...newElements],
|
|
371
|
-
totalElements: s.autoScanState.totalElements + newElements.length
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
});
|
|
375
|
-
const sourceFiles = groupBySourceFile(newElements);
|
|
376
|
-
for (const sourceFile of sourceFiles) {
|
|
377
|
-
for (const el of sourceFile.elements) {
|
|
378
|
-
get().updateElementIssue(el.id, {
|
|
379
|
-
elementId: el.id,
|
|
380
|
-
issues: [],
|
|
381
|
-
status: "scanning"
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
385
|
-
const { issues, error } = await scanFileForIssues(sourceFile, get());
|
|
386
|
-
distributeIssuesToElements(
|
|
387
|
-
issues,
|
|
388
|
-
sourceFile.elements,
|
|
389
|
-
sourceFile.path,
|
|
390
|
-
get().updateElementIssue,
|
|
391
|
-
get().updateFileIssues,
|
|
392
|
-
error ?? false
|
|
393
|
-
);
|
|
394
|
-
if (get().wsConnected && get().wsConnection) {
|
|
395
|
-
get().subscribeToFile(sourceFile.path);
|
|
396
|
-
}
|
|
397
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
_runScanLoop: async (elements) => {
|
|
401
|
-
const sourceFiles = groupBySourceFile(elements);
|
|
402
|
-
let processedElements = 0;
|
|
403
|
-
for (const sourceFile of sourceFiles) {
|
|
404
|
-
if (!get().liveScanEnabled) {
|
|
405
|
-
set({
|
|
406
|
-
scanLock: false,
|
|
407
|
-
autoScanState: DEFAULT_AUTO_SCAN_STATE
|
|
408
|
-
});
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
get()._setScanState({ currentIndex: processedElements });
|
|
412
|
-
for (const el of sourceFile.elements) {
|
|
413
|
-
get().updateElementIssue(el.id, {
|
|
414
|
-
elementId: el.id,
|
|
415
|
-
issues: [],
|
|
416
|
-
status: "scanning"
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
420
|
-
const { issues, error } = await scanFileForIssues(sourceFile, get());
|
|
421
|
-
distributeIssuesToElements(
|
|
422
|
-
issues,
|
|
423
|
-
sourceFile.elements,
|
|
424
|
-
sourceFile.path,
|
|
425
|
-
get().updateElementIssue,
|
|
426
|
-
get().updateFileIssues,
|
|
427
|
-
error ?? false
|
|
428
|
-
);
|
|
429
|
-
if (get().wsConnected && get().wsConnection) {
|
|
430
|
-
get().subscribeToFile(sourceFile.path);
|
|
431
|
-
}
|
|
432
|
-
processedElements += sourceFile.elements.length;
|
|
433
|
-
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
434
|
-
}
|
|
435
|
-
set({
|
|
436
|
-
scanLock: false,
|
|
437
|
-
autoScanState: {
|
|
438
|
-
...get().autoScanState,
|
|
439
|
-
status: "complete",
|
|
440
|
-
currentIndex: elements.length
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
},
|
|
444
|
-
// ============ DOM Observer ============
|
|
445
|
-
removeStaleResults: (elementIds) => set((state) => {
|
|
446
|
-
const newCache = new Map(state.elementIssuesCache);
|
|
447
|
-
const newElements = state.autoScanState.elements.filter(
|
|
448
|
-
(el) => !elementIds.includes(el.id)
|
|
449
|
-
);
|
|
450
|
-
for (const id of elementIds) {
|
|
451
|
-
newCache.delete(id);
|
|
452
|
-
}
|
|
453
|
-
return {
|
|
454
|
-
elementIssuesCache: newCache,
|
|
455
|
-
autoScanState: {
|
|
456
|
-
...state.autoScanState,
|
|
457
|
-
elements: newElements,
|
|
458
|
-
totalElements: newElements.length
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
}),
|
|
462
|
-
// ============ File/Element Selection ============
|
|
463
|
-
hoveredFilePath: null,
|
|
464
|
-
selectedFilePath: null,
|
|
465
|
-
selectedElementId: null,
|
|
466
|
-
hoveredElementId: null,
|
|
467
|
-
setHoveredFilePath: (path) => set({ hoveredFilePath: path }),
|
|
468
|
-
setSelectedFilePath: (path) => set({ selectedFilePath: path }),
|
|
469
|
-
setSelectedElementId: (id) => set({ selectedElementId: id }),
|
|
470
|
-
setHoveredElementId: (id) => set({ hoveredElementId: id }),
|
|
471
|
-
// ============ WebSocket ============
|
|
472
|
-
wsConnection: null,
|
|
473
|
-
wsConnected: false,
|
|
474
|
-
wsUrl: DEFAULT_WS_URL,
|
|
475
|
-
wsReconnectAttempts: 0,
|
|
476
|
-
eslintIssuesCache: /* @__PURE__ */ new Map(),
|
|
477
|
-
wsProgressPhase: /* @__PURE__ */ new Map(),
|
|
478
|
-
wsLastActivity: null,
|
|
479
|
-
wsRecentResults: [],
|
|
480
|
-
workspaceRoot: null,
|
|
481
|
-
appRoot: null,
|
|
482
|
-
serverCwd: null,
|
|
483
|
-
connectWebSocket: (url) => {
|
|
484
|
-
const targetUrl = url || get().wsUrl;
|
|
485
|
-
const existing = get().wsConnection;
|
|
486
|
-
if (existing && existing.readyState !== WebSocket.CLOSED) {
|
|
487
|
-
existing.close();
|
|
488
|
-
}
|
|
489
|
-
if (typeof WebSocket === "undefined") {
|
|
490
|
-
console.warn("[UILint] WebSocket not available in this environment");
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
const ws = new WebSocket(targetUrl);
|
|
495
|
-
ws.onopen = () => {
|
|
496
|
-
console.log("[UILint] WebSocket connected to", targetUrl);
|
|
497
|
-
set({
|
|
498
|
-
wsConnected: true,
|
|
499
|
-
wsReconnectAttempts: 0,
|
|
500
|
-
wsUrl: targetUrl
|
|
501
|
-
});
|
|
502
|
-
};
|
|
503
|
-
ws.onclose = () => {
|
|
504
|
-
console.log("[UILint] WebSocket disconnected");
|
|
505
|
-
set({ wsConnected: false, wsConnection: null });
|
|
506
|
-
const attempts = get().wsReconnectAttempts;
|
|
507
|
-
if (attempts < MAX_RECONNECT_ATTEMPTS) {
|
|
508
|
-
const delay = RECONNECT_BASE_DELAY * Math.pow(2, attempts);
|
|
509
|
-
console.log(
|
|
510
|
-
`[UILint] Reconnecting in ${delay}ms (attempt ${attempts + 1})`
|
|
511
|
-
);
|
|
512
|
-
setTimeout(() => {
|
|
513
|
-
set({ wsReconnectAttempts: attempts + 1 });
|
|
514
|
-
get()._reconnectWebSocket();
|
|
515
|
-
}, delay);
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
ws.onerror = (error) => {
|
|
519
|
-
console.error("[UILint] WebSocket error:", error);
|
|
520
|
-
};
|
|
521
|
-
ws.onmessage = (event) => {
|
|
522
|
-
try {
|
|
523
|
-
const data = JSON.parse(event.data);
|
|
524
|
-
get()._handleWsMessage(data);
|
|
525
|
-
} catch (err) {
|
|
526
|
-
console.error("[UILint] Failed to parse WebSocket message:", err);
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
set({ wsConnection: ws, wsUrl: targetUrl });
|
|
530
|
-
} catch (err) {
|
|
531
|
-
console.error("[UILint] Failed to create WebSocket:", err);
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
|
-
disconnectWebSocket: () => {
|
|
535
|
-
const ws = get().wsConnection;
|
|
536
|
-
if (ws) {
|
|
537
|
-
ws.close();
|
|
538
|
-
set({
|
|
539
|
-
wsConnection: null,
|
|
540
|
-
wsConnected: false,
|
|
541
|
-
wsReconnectAttempts: MAX_RECONNECT_ATTEMPTS
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
},
|
|
545
|
-
requestFileLint: async (filePath) => {
|
|
546
|
-
const { wsConnection, wsConnected, eslintIssuesCache } = get();
|
|
547
|
-
const cached = eslintIssuesCache.get(filePath);
|
|
548
|
-
if (cached) {
|
|
549
|
-
console.log("[UILint] using cached issues for", filePath);
|
|
550
|
-
return cached;
|
|
551
|
-
}
|
|
552
|
-
if (!wsConnected || !wsConnection) {
|
|
553
|
-
console.log("[UILint] WebSocket not connected, using HTTP fallback");
|
|
554
|
-
return [];
|
|
555
|
-
}
|
|
556
|
-
return new Promise((resolve, reject) => {
|
|
557
|
-
const requestId = makeRequestId();
|
|
558
|
-
pendingRequests.set(requestId, { resolve, reject });
|
|
559
|
-
const message = {
|
|
560
|
-
type: "lint:file",
|
|
561
|
-
filePath,
|
|
562
|
-
requestId
|
|
563
|
-
};
|
|
564
|
-
wsConnection.send(JSON.stringify(message));
|
|
565
|
-
setTimeout(() => {
|
|
566
|
-
if (pendingRequests.has(requestId)) {
|
|
567
|
-
pendingRequests.delete(requestId);
|
|
568
|
-
reject(new Error("Request timed out"));
|
|
569
|
-
}
|
|
570
|
-
}, WS_REQUEST_TIMEOUT_MS);
|
|
571
|
-
});
|
|
572
|
-
},
|
|
573
|
-
requestElementLint: async (filePath, dataLoc) => {
|
|
574
|
-
const { wsConnection, wsConnected } = get();
|
|
575
|
-
if (!wsConnected || !wsConnection) {
|
|
576
|
-
console.log("[UILint] WebSocket not connected, using HTTP fallback");
|
|
577
|
-
return [];
|
|
578
|
-
}
|
|
579
|
-
return new Promise((resolve, reject) => {
|
|
580
|
-
const requestId = makeRequestId();
|
|
581
|
-
pendingRequests.set(requestId, { resolve, reject });
|
|
582
|
-
const message = {
|
|
583
|
-
type: "lint:element",
|
|
584
|
-
filePath,
|
|
585
|
-
dataLoc,
|
|
586
|
-
requestId
|
|
587
|
-
};
|
|
588
|
-
wsConnection.send(JSON.stringify(message));
|
|
589
|
-
setTimeout(() => {
|
|
590
|
-
if (pendingRequests.has(requestId)) {
|
|
591
|
-
pendingRequests.delete(requestId);
|
|
592
|
-
reject(new Error("Request timed out"));
|
|
593
|
-
}
|
|
594
|
-
}, WS_REQUEST_TIMEOUT_MS);
|
|
595
|
-
});
|
|
596
|
-
},
|
|
597
|
-
subscribeToFile: (filePath) => {
|
|
598
|
-
const { wsConnection, wsConnected } = get();
|
|
599
|
-
if (!wsConnected || !wsConnection) return;
|
|
600
|
-
const message = { type: "subscribe:file", filePath };
|
|
601
|
-
wsConnection.send(JSON.stringify(message));
|
|
602
|
-
},
|
|
603
|
-
invalidateCache: (filePath) => {
|
|
604
|
-
const { wsConnection, wsConnected } = get();
|
|
605
|
-
if (filePath) {
|
|
606
|
-
set((state) => {
|
|
607
|
-
const next = new Map(state.eslintIssuesCache);
|
|
608
|
-
next.delete(filePath);
|
|
609
|
-
return { eslintIssuesCache: next };
|
|
610
|
-
});
|
|
611
|
-
} else {
|
|
612
|
-
set({ eslintIssuesCache: /* @__PURE__ */ new Map() });
|
|
613
|
-
}
|
|
614
|
-
if (wsConnected && wsConnection) {
|
|
615
|
-
const message = {
|
|
616
|
-
type: "cache:invalidate",
|
|
617
|
-
filePath
|
|
618
|
-
};
|
|
619
|
-
wsConnection.send(JSON.stringify(message));
|
|
620
|
-
}
|
|
621
|
-
},
|
|
622
|
-
_handleWsMessage: (data) => {
|
|
623
|
-
switch (data.type) {
|
|
624
|
-
case "lint:result": {
|
|
625
|
-
const { filePath, issues, requestId } = data;
|
|
626
|
-
set((state2) => {
|
|
627
|
-
const next = new Map(state2.eslintIssuesCache);
|
|
628
|
-
next.set(filePath, issues);
|
|
629
|
-
return { eslintIssuesCache: next };
|
|
630
|
-
});
|
|
631
|
-
const state = get();
|
|
632
|
-
if (state.liveScanEnabled) {
|
|
633
|
-
const sourceFiles = groupBySourceFile(state.autoScanState.elements);
|
|
634
|
-
const sf = sourceFiles.find((s) => s.path === filePath);
|
|
635
|
-
if (sf) {
|
|
636
|
-
distributeIssuesToElements(
|
|
637
|
-
issues,
|
|
638
|
-
sf.elements,
|
|
639
|
-
filePath,
|
|
640
|
-
state.updateElementIssue,
|
|
641
|
-
state.updateFileIssues,
|
|
642
|
-
false
|
|
643
|
-
);
|
|
644
|
-
} else {
|
|
645
|
-
const unmappedIssues = issues.filter((i) => !i.dataLoc);
|
|
646
|
-
if (unmappedIssues.length > 0) {
|
|
647
|
-
state.updateFileIssues(filePath, unmappedIssues);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
set((state2) => {
|
|
652
|
-
const next = new Map(state2.wsProgressPhase);
|
|
653
|
-
next.delete(filePath);
|
|
654
|
-
return { wsProgressPhase: next };
|
|
655
|
-
});
|
|
656
|
-
set((state2) => {
|
|
657
|
-
const next = [
|
|
658
|
-
{ filePath, issueCount: issues.length, updatedAt: Date.now() },
|
|
659
|
-
...state2.wsRecentResults.filter((r) => r.filePath !== filePath)
|
|
660
|
-
].slice(0, 8);
|
|
661
|
-
return { wsRecentResults: next };
|
|
662
|
-
});
|
|
663
|
-
set({
|
|
664
|
-
wsLastActivity: {
|
|
665
|
-
filePath,
|
|
666
|
-
phase: `Done (${issues.length} issues)`,
|
|
667
|
-
updatedAt: Date.now()
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
if (requestId) {
|
|
671
|
-
const pending = pendingRequests.get(requestId);
|
|
672
|
-
if (pending) {
|
|
673
|
-
pending.resolve(issues);
|
|
674
|
-
pendingRequests.delete(requestId);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
break;
|
|
678
|
-
}
|
|
679
|
-
case "lint:progress": {
|
|
680
|
-
const { filePath, phase } = data;
|
|
681
|
-
set((state) => {
|
|
682
|
-
const next = new Map(state.wsProgressPhase);
|
|
683
|
-
next.set(filePath, phase);
|
|
684
|
-
return {
|
|
685
|
-
wsProgressPhase: next,
|
|
686
|
-
wsLastActivity: { filePath, phase, updatedAt: Date.now() }
|
|
687
|
-
};
|
|
688
|
-
});
|
|
689
|
-
break;
|
|
690
|
-
}
|
|
691
|
-
case "file:changed": {
|
|
692
|
-
const { filePath } = data;
|
|
693
|
-
set((state2) => {
|
|
694
|
-
const next = new Map(state2.eslintIssuesCache);
|
|
695
|
-
next.delete(filePath);
|
|
696
|
-
return { eslintIssuesCache: next };
|
|
697
|
-
});
|
|
698
|
-
const state = get();
|
|
699
|
-
if (state.liveScanEnabled) {
|
|
700
|
-
const sourceFiles = groupBySourceFile(state.autoScanState.elements);
|
|
701
|
-
const sf = sourceFiles.find((s) => s.path === filePath);
|
|
702
|
-
if (sf) {
|
|
703
|
-
for (const el of sf.elements) {
|
|
704
|
-
const existing = state.elementIssuesCache.get(el.id);
|
|
705
|
-
state.updateElementIssue(el.id, {
|
|
706
|
-
elementId: el.id,
|
|
707
|
-
issues: existing?.issues || [],
|
|
708
|
-
status: "scanning"
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
state.requestFileLint(filePath).catch(() => {
|
|
712
|
-
for (const el of sf.elements) {
|
|
713
|
-
const existing = state.elementIssuesCache.get(el.id);
|
|
714
|
-
state.updateElementIssue(el.id, {
|
|
715
|
-
elementId: el.id,
|
|
716
|
-
issues: existing?.issues || [],
|
|
717
|
-
status: "error"
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
break;
|
|
724
|
-
}
|
|
725
|
-
case "workspace:info": {
|
|
726
|
-
const { appRoot, workspaceRoot, serverCwd } = data;
|
|
727
|
-
console.log("[UILint] Received workspace info:", {
|
|
728
|
-
appRoot,
|
|
729
|
-
workspaceRoot,
|
|
730
|
-
serverCwd
|
|
731
|
-
});
|
|
732
|
-
set({ appRoot, workspaceRoot, serverCwd });
|
|
733
|
-
break;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
},
|
|
737
|
-
_reconnectWebSocket: () => {
|
|
738
|
-
const { wsUrl } = get();
|
|
739
|
-
get().connectWebSocket(wsUrl);
|
|
740
|
-
}
|
|
741
|
-
}));
|
|
742
|
-
|
|
743
|
-
// src/components/ui-lint/UILintProvider.tsx
|
|
744
|
-
import {
|
|
745
|
-
createContext,
|
|
746
|
-
useContext,
|
|
747
|
-
useState,
|
|
748
|
-
useEffect as useEffect2,
|
|
749
|
-
useCallback as useCallback2,
|
|
750
|
-
useMemo
|
|
751
|
-
} from "react";
|
|
752
|
-
|
|
753
|
-
// src/components/ui-lint/useDOMObserver.ts
|
|
754
|
-
import { useEffect, useRef, useCallback } from "react";
|
|
755
|
-
var RECONCILE_DEBOUNCE_MS = 100;
|
|
756
|
-
function useDOMObserver(enabled = true) {
|
|
757
|
-
const observerRef = useRef(null);
|
|
758
|
-
const reconcileTimeoutRef = useRef(
|
|
759
|
-
null
|
|
760
|
-
);
|
|
761
|
-
const knownElementIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
762
|
-
const liveScanEnabled = useUILintStore((s) => s.liveScanEnabled);
|
|
763
|
-
const settings = useUILintStore((s) => s.settings);
|
|
764
|
-
const autoScanState = useUILintStore((s) => s.autoScanState);
|
|
765
|
-
const removeStaleResults = useUILintStore(
|
|
766
|
-
(s) => s.removeStaleResults
|
|
767
|
-
);
|
|
768
|
-
const scanNewElements = useUILintStore((s) => s.scanNewElements);
|
|
769
|
-
useEffect(() => {
|
|
770
|
-
if (autoScanState.elements.length > 0) {
|
|
771
|
-
const ids = new Set(autoScanState.elements.map((el) => el.id));
|
|
772
|
-
knownElementIdsRef.current = ids;
|
|
773
|
-
}
|
|
774
|
-
}, [autoScanState.elements]);
|
|
775
|
-
const reconcileElements = useCallback(() => {
|
|
776
|
-
if (!liveScanEnabled) return;
|
|
777
|
-
const currentElements = scanDOMForSources(
|
|
778
|
-
document.body,
|
|
779
|
-
settings.hideNodeModules
|
|
780
|
-
);
|
|
781
|
-
const currentIds = new Set(currentElements.map((el) => el.id));
|
|
782
|
-
const knownIds = knownElementIdsRef.current;
|
|
783
|
-
const newElements = [];
|
|
784
|
-
for (const el of currentElements) {
|
|
785
|
-
if (!knownIds.has(el.id)) {
|
|
786
|
-
newElements.push(el);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
const removedElementIds = [];
|
|
790
|
-
for (const id of knownIds) {
|
|
791
|
-
if (!currentIds.has(id)) {
|
|
792
|
-
removedElementIds.push(id);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
knownElementIdsRef.current = currentIds;
|
|
796
|
-
if (newElements.length > 0) {
|
|
797
|
-
scanNewElements(newElements);
|
|
798
|
-
}
|
|
799
|
-
if (removedElementIds.length > 0) {
|
|
800
|
-
removeStaleResults(removedElementIds);
|
|
801
|
-
}
|
|
802
|
-
}, [
|
|
803
|
-
liveScanEnabled,
|
|
804
|
-
settings.hideNodeModules,
|
|
805
|
-
scanNewElements,
|
|
806
|
-
removeStaleResults
|
|
807
|
-
]);
|
|
808
|
-
const debouncedReconcile = useCallback(() => {
|
|
809
|
-
if (reconcileTimeoutRef.current) {
|
|
810
|
-
clearTimeout(reconcileTimeoutRef.current);
|
|
811
|
-
}
|
|
812
|
-
reconcileTimeoutRef.current = setTimeout(() => {
|
|
813
|
-
reconcileElements();
|
|
814
|
-
reconcileTimeoutRef.current = null;
|
|
815
|
-
}, RECONCILE_DEBOUNCE_MS);
|
|
816
|
-
}, [reconcileElements]);
|
|
817
|
-
useEffect(() => {
|
|
818
|
-
if (!enabled) return;
|
|
819
|
-
if (typeof window === "undefined") return;
|
|
820
|
-
const observer = new MutationObserver((mutations) => {
|
|
821
|
-
let hasRelevantChanges = false;
|
|
822
|
-
for (const mutation of mutations) {
|
|
823
|
-
for (const node of mutation.addedNodes) {
|
|
824
|
-
if (node instanceof Element) {
|
|
825
|
-
if (node.hasAttribute("data-loc")) {
|
|
826
|
-
hasRelevantChanges = true;
|
|
827
|
-
break;
|
|
828
|
-
}
|
|
829
|
-
if (node.querySelector("[data-loc]")) {
|
|
830
|
-
hasRelevantChanges = true;
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (hasRelevantChanges) break;
|
|
836
|
-
for (const node of mutation.removedNodes) {
|
|
837
|
-
if (node instanceof Element) {
|
|
838
|
-
if (node.hasAttribute("data-loc") || node.querySelector("[data-loc]")) {
|
|
839
|
-
hasRelevantChanges = true;
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
if (hasRelevantChanges) break;
|
|
845
|
-
}
|
|
846
|
-
if (hasRelevantChanges) {
|
|
847
|
-
debouncedReconcile();
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
observer.observe(document.body, {
|
|
851
|
-
childList: true,
|
|
852
|
-
subtree: true
|
|
853
|
-
});
|
|
854
|
-
observerRef.current = observer;
|
|
855
|
-
return () => {
|
|
856
|
-
observer.disconnect();
|
|
857
|
-
observerRef.current = null;
|
|
858
|
-
if (reconcileTimeoutRef.current) {
|
|
859
|
-
clearTimeout(reconcileTimeoutRef.current);
|
|
860
|
-
reconcileTimeoutRef.current = null;
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
}, [enabled, debouncedReconcile]);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// src/components/ui-lint/UILintProvider.tsx
|
|
867
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
868
|
-
var UILintContext = createContext(null);
|
|
869
|
-
function useUILintContext() {
|
|
870
|
-
const context = useContext(UILintContext);
|
|
871
|
-
if (!context) {
|
|
872
|
-
throw new Error("useUILintContext must be used within a UILintProvider");
|
|
873
|
-
}
|
|
874
|
-
return context;
|
|
875
|
-
}
|
|
876
|
-
function isBrowser() {
|
|
877
|
-
return typeof window !== "undefined";
|
|
878
|
-
}
|
|
879
|
-
function UILintProvider({
|
|
880
|
-
children,
|
|
881
|
-
enabled = true
|
|
882
|
-
}) {
|
|
883
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
884
|
-
const settings = useUILintStore((s) => s.settings);
|
|
885
|
-
const updateSettings = useUILintStore((s) => s.updateSettings);
|
|
886
|
-
const altKeyHeld = useUILintStore((s) => s.altKeyHeld);
|
|
887
|
-
const setAltKeyHeld = useUILintStore((s) => s.setAltKeyHeld);
|
|
888
|
-
const locatorTarget = useUILintStore((s) => s.locatorTarget);
|
|
889
|
-
const setLocatorTarget = useUILintStore(
|
|
890
|
-
(s) => s.setLocatorTarget
|
|
891
|
-
);
|
|
892
|
-
const inspectedElement = useUILintStore(
|
|
893
|
-
(s) => s.inspectedElement
|
|
894
|
-
);
|
|
895
|
-
const setInspectedElement = useUILintStore(
|
|
896
|
-
(s) => s.setInspectedElement
|
|
897
|
-
);
|
|
898
|
-
const liveScanEnabled = useUILintStore((s) => s.liveScanEnabled);
|
|
899
|
-
const autoScanState = useUILintStore((s) => s.autoScanState);
|
|
900
|
-
const elementIssuesCache = useUILintStore(
|
|
901
|
-
(s) => s.elementIssuesCache
|
|
902
|
-
);
|
|
903
|
-
const storeEnableLiveScan = useUILintStore(
|
|
904
|
-
(s) => s.enableLiveScan
|
|
905
|
-
);
|
|
906
|
-
const disableLiveScan = useUILintStore((s) => s.disableLiveScan);
|
|
907
|
-
const connectWebSocket = useUILintStore(
|
|
908
|
-
(s) => s.connectWebSocket
|
|
909
|
-
);
|
|
910
|
-
const disconnectWebSocket = useUILintStore(
|
|
911
|
-
(s) => s.disconnectWebSocket
|
|
912
|
-
);
|
|
913
|
-
useDOMObserver(enabled && isMounted);
|
|
914
|
-
const getLocatorTargetFromElement = useCallback2(
|
|
915
|
-
(element) => {
|
|
916
|
-
if (element.closest("[data-ui-lint]")) return null;
|
|
917
|
-
const source = getSourceFromDataLoc(element);
|
|
918
|
-
if (!source) return null;
|
|
919
|
-
if (settings.hideNodeModules && isNodeModulesPath(source.fileName)) {
|
|
920
|
-
return null;
|
|
921
|
-
}
|
|
922
|
-
return {
|
|
923
|
-
element,
|
|
924
|
-
source,
|
|
925
|
-
rect: element.getBoundingClientRect()
|
|
926
|
-
};
|
|
927
|
-
},
|
|
928
|
-
[settings.hideNodeModules]
|
|
929
|
-
);
|
|
930
|
-
const handleMouseMove = useCallback2(
|
|
931
|
-
(e) => {
|
|
932
|
-
if (!altKeyHeld) return;
|
|
933
|
-
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
|
|
934
|
-
if (!elementAtPoint) {
|
|
935
|
-
setLocatorTarget(null);
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
let current = elementAtPoint;
|
|
939
|
-
while (current) {
|
|
940
|
-
const target = getLocatorTargetFromElement(current);
|
|
941
|
-
if (target) {
|
|
942
|
-
setLocatorTarget(target);
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
current = current.parentElement;
|
|
946
|
-
}
|
|
947
|
-
setLocatorTarget(null);
|
|
948
|
-
},
|
|
949
|
-
[altKeyHeld, getLocatorTargetFromElement, setLocatorTarget]
|
|
950
|
-
);
|
|
951
|
-
const handleLocatorClick = useCallback2(
|
|
952
|
-
(e) => {
|
|
953
|
-
if (!altKeyHeld || !locatorTarget) return;
|
|
954
|
-
const targetEl = e.target;
|
|
955
|
-
if (targetEl?.closest?.("[data-ui-lint]")) return;
|
|
956
|
-
e.preventDefault();
|
|
957
|
-
e.stopPropagation();
|
|
958
|
-
setInspectedElement({
|
|
959
|
-
element: locatorTarget.element,
|
|
960
|
-
source: locatorTarget.source,
|
|
961
|
-
rect: locatorTarget.rect
|
|
962
|
-
});
|
|
963
|
-
setLocatorTarget(null);
|
|
964
|
-
},
|
|
965
|
-
[altKeyHeld, locatorTarget, setInspectedElement, setLocatorTarget]
|
|
966
|
-
);
|
|
967
|
-
useEffect2(() => {
|
|
968
|
-
if (!isBrowser() || !enabled) return;
|
|
969
|
-
const handleKeyDown = (e) => {
|
|
970
|
-
if (e.key === "Alt") {
|
|
971
|
-
setAltKeyHeld(true);
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
const handleKeyUp = (e) => {
|
|
975
|
-
if (e.key === "Alt") {
|
|
976
|
-
setAltKeyHeld(false);
|
|
977
|
-
setLocatorTarget(null);
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
const handleBlur = () => {
|
|
981
|
-
setAltKeyHeld(false);
|
|
982
|
-
setLocatorTarget(null);
|
|
983
|
-
};
|
|
984
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
985
|
-
window.addEventListener("keyup", handleKeyUp);
|
|
986
|
-
window.addEventListener("blur", handleBlur);
|
|
987
|
-
return () => {
|
|
988
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
989
|
-
window.removeEventListener("keyup", handleKeyUp);
|
|
990
|
-
window.removeEventListener("blur", handleBlur);
|
|
991
|
-
};
|
|
992
|
-
}, [enabled, setAltKeyHeld, setLocatorTarget]);
|
|
993
|
-
useEffect2(() => {
|
|
994
|
-
if (!isBrowser() || !enabled) return;
|
|
995
|
-
if (!altKeyHeld) return;
|
|
996
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
997
|
-
window.addEventListener("click", handleLocatorClick, true);
|
|
998
|
-
return () => {
|
|
999
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
1000
|
-
window.removeEventListener("click", handleLocatorClick, true);
|
|
1001
|
-
};
|
|
1002
|
-
}, [enabled, altKeyHeld, handleMouseMove, handleLocatorClick]);
|
|
1003
|
-
useEffect2(() => {
|
|
1004
|
-
if (!isBrowser() || !enabled) return;
|
|
1005
|
-
const handleKeyDown = (e) => {
|
|
1006
|
-
if (e.key === "Escape" && inspectedElement) {
|
|
1007
|
-
setInspectedElement(null);
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
1011
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1012
|
-
}, [enabled, inspectedElement, setInspectedElement]);
|
|
1013
|
-
useEffect2(() => {
|
|
1014
|
-
setIsMounted(true);
|
|
1015
|
-
}, []);
|
|
1016
|
-
useEffect2(() => {
|
|
1017
|
-
if (!isBrowser() || !enabled) return;
|
|
1018
|
-
if (!isMounted) return;
|
|
1019
|
-
connectWebSocket();
|
|
1020
|
-
return () => {
|
|
1021
|
-
disconnectWebSocket();
|
|
1022
|
-
};
|
|
1023
|
-
}, [enabled, isMounted, connectWebSocket, disconnectWebSocket]);
|
|
1024
|
-
const enableLiveScan = useCallback2(() => {
|
|
1025
|
-
storeEnableLiveScan(settings.hideNodeModules);
|
|
1026
|
-
}, [storeEnableLiveScan, settings.hideNodeModules]);
|
|
1027
|
-
const contextValue = useMemo(
|
|
1028
|
-
() => ({
|
|
1029
|
-
settings,
|
|
1030
|
-
updateSettings,
|
|
1031
|
-
altKeyHeld,
|
|
1032
|
-
locatorTarget,
|
|
1033
|
-
inspectedElement,
|
|
1034
|
-
setInspectedElement,
|
|
1035
|
-
liveScanEnabled,
|
|
1036
|
-
autoScanState,
|
|
1037
|
-
elementIssuesCache,
|
|
1038
|
-
enableLiveScan,
|
|
1039
|
-
disableLiveScan
|
|
1040
|
-
}),
|
|
1041
|
-
[
|
|
1042
|
-
settings,
|
|
1043
|
-
updateSettings,
|
|
1044
|
-
altKeyHeld,
|
|
1045
|
-
locatorTarget,
|
|
1046
|
-
inspectedElement,
|
|
1047
|
-
setInspectedElement,
|
|
1048
|
-
liveScanEnabled,
|
|
1049
|
-
autoScanState,
|
|
1050
|
-
elementIssuesCache,
|
|
1051
|
-
enableLiveScan,
|
|
1052
|
-
disableLiveScan
|
|
1053
|
-
]
|
|
1054
|
-
);
|
|
1055
|
-
const shouldRenderUI = enabled && isMounted;
|
|
1056
|
-
return /* @__PURE__ */ jsxs(UILintContext.Provider, { value: contextValue, children: [
|
|
1057
|
-
children,
|
|
1058
|
-
shouldRenderUI && /* @__PURE__ */ jsx(UILintUI, {})
|
|
1059
|
-
] });
|
|
1060
|
-
}
|
|
1061
|
-
function UILintUI() {
|
|
1062
|
-
const { altKeyHeld, inspectedElement, liveScanEnabled } = useUILintContext();
|
|
1063
|
-
const [components, setComponents] = useState(null);
|
|
1064
|
-
useEffect2(() => {
|
|
1065
|
-
Promise.all([
|
|
1066
|
-
import("./UILintToolbar-CLVXQHCZ.js"),
|
|
1067
|
-
import("./InspectionPanel-NXSE7CMH.js"),
|
|
1068
|
-
import("./LocatorOverlay-3TKK74BD.js"),
|
|
1069
|
-
import("./ElementBadges-2CTPMJ6L.js")
|
|
1070
|
-
]).then(([toolbar, panel, locator, badges]) => {
|
|
1071
|
-
setComponents({
|
|
1072
|
-
Toolbar: toolbar.UILintToolbar,
|
|
1073
|
-
Panel: panel.InspectionPanel,
|
|
1074
|
-
LocatorOverlay: locator.LocatorOverlay,
|
|
1075
|
-
InspectedHighlight: locator.InspectedElementHighlight,
|
|
1076
|
-
ElementBadges: badges.ElementBadges
|
|
1077
|
-
});
|
|
1078
|
-
});
|
|
1079
|
-
}, []);
|
|
1080
|
-
if (!components) return null;
|
|
1081
|
-
const { Toolbar, Panel, LocatorOverlay, InspectedHighlight, ElementBadges } = components;
|
|
1082
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1083
|
-
/* @__PURE__ */ jsx(Toolbar, {}),
|
|
1084
|
-
(altKeyHeld || inspectedElement) && /* @__PURE__ */ jsx(LocatorOverlay, {}),
|
|
1085
|
-
liveScanEnabled && /* @__PURE__ */ jsx(ElementBadges, {}),
|
|
1086
|
-
inspectedElement && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1087
|
-
/* @__PURE__ */ jsx(InspectedHighlight, {}),
|
|
1088
|
-
/* @__PURE__ */ jsx(Panel, {})
|
|
1089
|
-
] })
|
|
1090
|
-
] });
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
export {
|
|
1094
|
-
getSourceFromDataLoc,
|
|
1095
|
-
isNodeModulesPath,
|
|
1096
|
-
getDisplayName,
|
|
1097
|
-
scanDOMForSources,
|
|
1098
|
-
groupBySourceFile,
|
|
1099
|
-
cleanupDataAttributes,
|
|
1100
|
-
getElementById,
|
|
1101
|
-
updateElementRects,
|
|
1102
|
-
buildEditorUrl,
|
|
1103
|
-
FILE_COLORS,
|
|
1104
|
-
DEFAULT_SETTINGS,
|
|
1105
|
-
DATA_UILINT_ID,
|
|
1106
|
-
useUILintStore,
|
|
1107
|
-
useUILintContext,
|
|
1108
|
-
UILintProvider
|
|
1109
|
-
};
|