uilint-react 0.1.19 → 0.1.20
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/README.md +49 -92
- package/dist/{InspectionPanel-6DBGEWWD.js → InspectionPanel-3ML64TAP.js} +2 -2
- package/dist/LocatorOverlay-GTTWBRKH.js +11 -0
- package/dist/{UILintToolbar-7ZYCQC4M.js → UILintToolbar-GAOYF7GY.js} +2 -2
- package/dist/{chunk-7WYVWDRU.js → chunk-DAFFOBEU.js} +53 -218
- package/dist/{chunk-3TA6OKS6.js → chunk-NOISZ3XP.js} +417 -90
- package/dist/chunk-PBC3J267.js +276 -0
- package/dist/{chunk-KUFV22FO.js → chunk-VYCIUDU7.js} +72 -2
- package/dist/index.d.ts +26 -65
- package/dist/index.js +32 -1096
- package/package.json +2 -2
- package/dist/LocatorOverlay-FQEYAMT6.js +0 -9
- package/dist/SourceOverlays-2SEINA2B.js +0 -9
- package/dist/chunk-MEP7WO7U.js +0 -210
- package/dist/chunk-OWX36QE3.js +0 -595
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ React component for AI-powered UI consistency checking in running applications.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`uilint-react` provides
|
|
7
|
+
`uilint-react` provides the `UILintProvider` component that enables element inspection and LLM-powered code analysis in your React/Next.js application.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -12,127 +12,70 @@ React component for AI-powered UI consistency checking in running applications.
|
|
|
12
12
|
npm install uilint-react uilint-core
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Or use the CLI to install everything automatically:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx uilint-cli install
|
|
19
|
+
```
|
|
20
|
+
|
|
15
21
|
## Usage in a Running App
|
|
16
22
|
|
|
17
|
-
Wrap your app with the
|
|
23
|
+
Wrap your app with the `UILintProvider` component:
|
|
18
24
|
|
|
19
25
|
### Next.js Setup
|
|
20
26
|
|
|
21
27
|
```tsx
|
|
22
28
|
// app/layout.tsx
|
|
23
|
-
import {
|
|
29
|
+
import { UILintProvider } from "uilint-react";
|
|
24
30
|
|
|
25
31
|
export default function RootLayout({ children }) {
|
|
26
32
|
return (
|
|
27
33
|
<html>
|
|
28
34
|
<body>
|
|
29
|
-
<
|
|
30
|
-
enabled={process.env.NODE_ENV !== "production"}
|
|
31
|
-
position="bottom-left"
|
|
32
|
-
autoScan={false}
|
|
33
|
-
>
|
|
35
|
+
<UILintProvider enabled={process.env.NODE_ENV !== "production"}>
|
|
34
36
|
{children}
|
|
35
|
-
</
|
|
37
|
+
</UILintProvider>
|
|
36
38
|
</body>
|
|
37
39
|
</html>
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
```
|
|
41
43
|
|
|
44
|
+
### Features
|
|
45
|
+
|
|
46
|
+
- **Alt+Click** on any element to open the inspector sidebar
|
|
47
|
+
- View component source location and file path
|
|
48
|
+
- Navigate through the component stack (scroll while holding Alt)
|
|
49
|
+
- **Open in Cursor** - jump directly to the source file
|
|
50
|
+
- **Scan with LLM** - analyze the component for style issues
|
|
51
|
+
- **Copy fix prompt** - paste into Cursor agent for automatic fixes
|
|
52
|
+
|
|
42
53
|
### Props
|
|
43
54
|
|
|
44
|
-
| Prop
|
|
45
|
-
|
|
|
46
|
-
| `enabled`
|
|
47
|
-
| `position` | `'bottom-left' \| 'bottom-right' \| 'top-left' \| 'top-right'` | `'bottom-left'` | Overlay position |
|
|
48
|
-
| `autoScan` | `boolean` | `false` | Automatically scan on page load |
|
|
49
|
-
| `apiEndpoint` | `string` | `'/api/uilint/analyze'` | Custom API endpoint |
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
| --------- | --------- | ------- | --------------------- |
|
|
57
|
+
| `enabled` | `boolean` | `true` | Enable/disable UILint |
|
|
50
58
|
|
|
51
59
|
### API Routes
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
The CLI installs these routes automatically, or you can add them manually:
|
|
54
62
|
|
|
55
63
|
```ts
|
|
56
64
|
// app/api/uilint/analyze/route.ts
|
|
57
|
-
|
|
58
|
-
import { OllamaClient, UILINT_DEFAULT_OLLAMA_MODEL } from "uilint-core";
|
|
59
|
-
|
|
60
|
-
export async function POST(request: NextRequest) {
|
|
61
|
-
const { styleSummary, styleGuide, generateGuide, model } =
|
|
62
|
-
await request.json();
|
|
63
|
-
const client = new OllamaClient({
|
|
64
|
-
model: model || UILINT_DEFAULT_OLLAMA_MODEL,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (generateGuide) {
|
|
68
|
-
const styleGuideContent = await client.generateStyleGuide(styleSummary);
|
|
69
|
-
return NextResponse.json({ styleGuide: styleGuideContent });
|
|
70
|
-
} else {
|
|
71
|
-
const result = await client.analyzeStyles(styleSummary, styleGuide);
|
|
72
|
-
return NextResponse.json({ issues: result.issues });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
// app/api/uilint/styleguide/route.ts
|
|
79
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
80
|
-
import {
|
|
81
|
-
readStyleGuideFromProject,
|
|
82
|
-
writeStyleGuide,
|
|
83
|
-
styleGuideExists,
|
|
84
|
-
getDefaultStyleGuidePath,
|
|
85
|
-
} from "uilint-core/node";
|
|
86
|
-
|
|
87
|
-
export async function GET() {
|
|
88
|
-
const projectPath = process.cwd();
|
|
89
|
-
if (!styleGuideExists(projectPath)) {
|
|
90
|
-
return NextResponse.json({ exists: false, content: null });
|
|
91
|
-
}
|
|
92
|
-
const content = await readStyleGuideFromProject(projectPath);
|
|
93
|
-
return NextResponse.json({ exists: true, content });
|
|
94
|
-
}
|
|
65
|
+
// Handles LLM analysis of source code
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const projectPath = process.cwd();
|
|
99
|
-
const stylePath = getDefaultStyleGuidePath(projectPath);
|
|
100
|
-
await writeStyleGuide(stylePath, content);
|
|
101
|
-
return NextResponse.json({ success: true });
|
|
102
|
-
}
|
|
67
|
+
// app/api/dev/source/route.ts
|
|
68
|
+
// Dev-only route for fetching source files
|
|
103
69
|
```
|
|
104
70
|
|
|
105
71
|
## Usage in Tests
|
|
106
72
|
|
|
107
73
|
UILint can run in Vitest/Jest tests with JSDOM:
|
|
108
74
|
|
|
109
|
-
### Basic Test
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
import { render, screen } from "@testing-library/react";
|
|
113
|
-
import { UILint } from "uilint-react";
|
|
114
|
-
import { MyComponent } from "./MyComponent";
|
|
115
|
-
|
|
116
|
-
test("MyComponent has consistent styles", async () => {
|
|
117
|
-
render(
|
|
118
|
-
<UILint enabled={true}>
|
|
119
|
-
<MyComponent />
|
|
120
|
-
</UILint>
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(screen.getByRole("button")).toBeInTheDocument();
|
|
124
|
-
|
|
125
|
-
// UILint automatically outputs warnings to console:
|
|
126
|
-
// ⚠️ [UILint] Button uses #3B82F6 but style guide specifies #2563EB
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
75
|
### Direct JSDOM Adapter
|
|
131
76
|
|
|
132
|
-
For more control, use the `JSDOMAdapter`:
|
|
133
|
-
|
|
134
77
|
```tsx
|
|
135
|
-
import { JSDOMAdapter, runUILintInTest } from "uilint-react";
|
|
78
|
+
import { JSDOMAdapter, runUILintInTest } from "uilint-react/node";
|
|
136
79
|
import { render } from "@testing-library/react";
|
|
137
80
|
|
|
138
81
|
test("detect style inconsistencies", async () => {
|
|
@@ -160,23 +103,37 @@ test("custom adapter usage", async () => {
|
|
|
160
103
|
|
|
161
104
|
## API
|
|
162
105
|
|
|
163
|
-
###
|
|
106
|
+
### UILintProvider
|
|
164
107
|
|
|
165
108
|
```tsx
|
|
166
|
-
interface
|
|
109
|
+
interface UILintProviderProps {
|
|
167
110
|
enabled?: boolean;
|
|
168
|
-
position?: "bottom-left" | "bottom-right" | "top-left" | "top-right";
|
|
169
|
-
autoScan?: boolean;
|
|
170
|
-
apiEndpoint?: string;
|
|
171
111
|
children: React.ReactNode;
|
|
172
112
|
}
|
|
173
113
|
|
|
174
|
-
function
|
|
114
|
+
function UILintProvider(props: UILintProviderProps): JSX.Element;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### useUILintContext
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
function useUILintContext(): UILintContextValue;
|
|
121
|
+
|
|
122
|
+
interface UILintContextValue {
|
|
123
|
+
settings: UILintSettings;
|
|
124
|
+
updateSettings: (settings: Partial<UILintSettings>) => void;
|
|
125
|
+
altKeyHeld: boolean;
|
|
126
|
+
locatorTarget: LocatorTarget | null;
|
|
127
|
+
inspectedElement: InspectedElement | null;
|
|
128
|
+
setInspectedElement: (element: InspectedElement | null) => void;
|
|
129
|
+
// ... additional context values
|
|
130
|
+
}
|
|
175
131
|
```
|
|
176
132
|
|
|
177
|
-
### JSDOM Adapter
|
|
133
|
+
### JSDOM Adapter (Node.js)
|
|
178
134
|
|
|
179
135
|
```typescript
|
|
136
|
+
// Import from "uilint-react/node" for test environments
|
|
180
137
|
class JSDOMAdapter {
|
|
181
138
|
constructor(styleGuidePath?: string);
|
|
182
139
|
|
|
@@ -28,10 +28,7 @@ var FILE_COLORS = [
|
|
|
28
28
|
// purple
|
|
29
29
|
];
|
|
30
30
|
var DEFAULT_SETTINGS = {
|
|
31
|
-
|
|
32
|
-
hideNodeModules: true,
|
|
33
|
-
overlayOpacity: 0.2,
|
|
34
|
-
labelPosition: "top-left"
|
|
31
|
+
hideNodeModules: true
|
|
35
32
|
};
|
|
36
33
|
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
37
34
|
|
|
@@ -250,127 +247,13 @@ function buildEditorUrl(source, editor = "cursor") {
|
|
|
250
247
|
)}:${lineNumber}:${column}`;
|
|
251
248
|
}
|
|
252
249
|
|
|
253
|
-
// src/components/ui-lint/use-element-scan.ts
|
|
254
|
-
import { useState, useEffect, useCallback, useRef } from "react";
|
|
255
|
-
function debounce(fn, delay) {
|
|
256
|
-
let timeoutId;
|
|
257
|
-
return (...args) => {
|
|
258
|
-
clearTimeout(timeoutId);
|
|
259
|
-
timeoutId = setTimeout(() => fn(...args), delay);
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function useElementScan({
|
|
263
|
-
enabled,
|
|
264
|
-
settings
|
|
265
|
-
}) {
|
|
266
|
-
const [elements, setElements] = useState([]);
|
|
267
|
-
const [sourceFiles, setSourceFiles] = useState([]);
|
|
268
|
-
const [isScanning, setIsScanning] = useState(false);
|
|
269
|
-
const mutationObserverRef = useRef(null);
|
|
270
|
-
const resizeObserverRef = useRef(null);
|
|
271
|
-
const performScan = useCallback(() => {
|
|
272
|
-
if (!enabled || typeof window === "undefined") return;
|
|
273
|
-
setIsScanning(true);
|
|
274
|
-
const scan = () => {
|
|
275
|
-
try {
|
|
276
|
-
const scannedElements = scanDOMForSources(
|
|
277
|
-
document.body,
|
|
278
|
-
settings.hideNodeModules
|
|
279
|
-
);
|
|
280
|
-
const files = groupBySourceFile(scannedElements);
|
|
281
|
-
setElements(scannedElements);
|
|
282
|
-
setSourceFiles(files);
|
|
283
|
-
} catch (error) {
|
|
284
|
-
console.error("[UILint] Scan error:", error);
|
|
285
|
-
} finally {
|
|
286
|
-
setIsScanning(false);
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
if ("requestIdleCallback" in window) {
|
|
290
|
-
window.requestIdleCallback(scan, { timeout: 1e3 });
|
|
291
|
-
} else {
|
|
292
|
-
setTimeout(scan, 0);
|
|
293
|
-
}
|
|
294
|
-
}, [enabled, settings.hideNodeModules]);
|
|
295
|
-
const updatePositions = useCallback(() => {
|
|
296
|
-
if (elements.length === 0) return;
|
|
297
|
-
setElements((prev) => updateElementRects(prev));
|
|
298
|
-
}, [elements.length]);
|
|
299
|
-
const debouncedRescan = useCallback(
|
|
300
|
-
debounce(() => {
|
|
301
|
-
performScan();
|
|
302
|
-
}, 500),
|
|
303
|
-
[performScan]
|
|
304
|
-
);
|
|
305
|
-
const handleScroll = useCallback(
|
|
306
|
-
debounce(() => {
|
|
307
|
-
updatePositions();
|
|
308
|
-
}, 16),
|
|
309
|
-
// ~60fps
|
|
310
|
-
[updatePositions]
|
|
311
|
-
);
|
|
312
|
-
const handleResize = useCallback(
|
|
313
|
-
debounce(() => {
|
|
314
|
-
updatePositions();
|
|
315
|
-
}, 100),
|
|
316
|
-
[updatePositions]
|
|
317
|
-
);
|
|
318
|
-
useEffect(() => {
|
|
319
|
-
if (!enabled) {
|
|
320
|
-
cleanupDataAttributes();
|
|
321
|
-
setElements([]);
|
|
322
|
-
setSourceFiles([]);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const initialScanTimer = setTimeout(performScan, 100);
|
|
326
|
-
mutationObserverRef.current = new MutationObserver((mutations) => {
|
|
327
|
-
const hasRelevantMutation = mutations.some((mutation) => {
|
|
328
|
-
if (mutation.type === "attributes") {
|
|
329
|
-
return !mutation.attributeName?.startsWith("data-ui-lint");
|
|
330
|
-
}
|
|
331
|
-
return true;
|
|
332
|
-
});
|
|
333
|
-
if (hasRelevantMutation) {
|
|
334
|
-
debouncedRescan();
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
mutationObserverRef.current.observe(document.body, {
|
|
338
|
-
childList: true,
|
|
339
|
-
subtree: true,
|
|
340
|
-
attributes: true,
|
|
341
|
-
attributeFilter: ["class", "style"]
|
|
342
|
-
});
|
|
343
|
-
window.addEventListener("scroll", handleScroll, true);
|
|
344
|
-
window.addEventListener("resize", handleResize);
|
|
345
|
-
return () => {
|
|
346
|
-
clearTimeout(initialScanTimer);
|
|
347
|
-
mutationObserverRef.current?.disconnect();
|
|
348
|
-
resizeObserverRef.current?.disconnect();
|
|
349
|
-
window.removeEventListener("scroll", handleScroll, true);
|
|
350
|
-
window.removeEventListener("resize", handleResize);
|
|
351
|
-
cleanupDataAttributes();
|
|
352
|
-
};
|
|
353
|
-
}, [enabled, performScan, debouncedRescan, handleScroll, handleResize]);
|
|
354
|
-
useEffect(() => {
|
|
355
|
-
if (enabled && elements.length > 0) {
|
|
356
|
-
performScan();
|
|
357
|
-
}
|
|
358
|
-
}, [settings.hideNodeModules]);
|
|
359
|
-
return {
|
|
360
|
-
elements,
|
|
361
|
-
sourceFiles,
|
|
362
|
-
isScanning,
|
|
363
|
-
rescan: performScan
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
250
|
// src/components/ui-lint/UILintProvider.tsx
|
|
368
251
|
import {
|
|
369
252
|
createContext,
|
|
370
253
|
useContext,
|
|
371
|
-
useState
|
|
372
|
-
useEffect
|
|
373
|
-
useCallback
|
|
254
|
+
useState,
|
|
255
|
+
useEffect,
|
|
256
|
+
useCallback,
|
|
374
257
|
useMemo
|
|
375
258
|
} from "react";
|
|
376
259
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -387,54 +270,28 @@ function isBrowser() {
|
|
|
387
270
|
}
|
|
388
271
|
function UILintProvider({
|
|
389
272
|
children,
|
|
390
|
-
enabled = true
|
|
391
|
-
defaultMode = "off"
|
|
273
|
+
enabled = true
|
|
392
274
|
}) {
|
|
393
|
-
const [
|
|
394
|
-
const [
|
|
395
|
-
const [
|
|
275
|
+
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
|
276
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
277
|
+
const [altKeyHeld, setAltKeyHeld] = useState(false);
|
|
278
|
+
const [locatorTarget, setLocatorTarget] = useState(
|
|
396
279
|
null
|
|
397
280
|
);
|
|
398
|
-
const [
|
|
399
|
-
|
|
400
|
-
)
|
|
401
|
-
const [isMounted, setIsMounted] = useState2(false);
|
|
402
|
-
const [altKeyHeld, setAltKeyHeld] = useState2(false);
|
|
403
|
-
const [locatorTarget, setLocatorTarget] = useState2(
|
|
404
|
-
null
|
|
405
|
-
);
|
|
406
|
-
const [locatorStackIndex, setLocatorStackIndex] = useState2(0);
|
|
407
|
-
const isActive = enabled && mode !== "off";
|
|
408
|
-
const { elements, sourceFiles, isScanning, rescan } = useElementScan({
|
|
409
|
-
enabled: isActive,
|
|
410
|
-
settings
|
|
411
|
-
});
|
|
412
|
-
const updateSettings = useCallback2((partial) => {
|
|
281
|
+
const [locatorStackIndex, setLocatorStackIndex] = useState(0);
|
|
282
|
+
const [inspectedElement, setInspectedElement] = useState(null);
|
|
283
|
+
const updateSettings = useCallback((partial) => {
|
|
413
284
|
setSettings((prev) => ({ ...prev, ...partial }));
|
|
414
285
|
}, []);
|
|
415
|
-
const
|
|
416
|
-
setMode((prev) => {
|
|
417
|
-
if (prev === "off") return "sources";
|
|
418
|
-
if (prev === "sources") return "inspect";
|
|
419
|
-
return "off";
|
|
420
|
-
});
|
|
421
|
-
}, []);
|
|
422
|
-
const handleEscape = useCallback2(() => {
|
|
423
|
-
if (selectedElement) {
|
|
424
|
-
setSelectedElement(null);
|
|
425
|
-
} else if (mode !== "off") {
|
|
426
|
-
setMode("off");
|
|
427
|
-
}
|
|
428
|
-
}, [selectedElement, mode]);
|
|
429
|
-
const locatorGoUp = useCallback2(() => {
|
|
286
|
+
const locatorGoUp = useCallback(() => {
|
|
430
287
|
if (!locatorTarget) return;
|
|
431
288
|
const maxIndex = locatorTarget.componentStack.length;
|
|
432
289
|
setLocatorStackIndex((prev) => Math.min(prev + 1, maxIndex));
|
|
433
290
|
}, [locatorTarget]);
|
|
434
|
-
const locatorGoDown =
|
|
291
|
+
const locatorGoDown = useCallback(() => {
|
|
435
292
|
setLocatorStackIndex((prev) => Math.max(prev - 1, 0));
|
|
436
293
|
}, []);
|
|
437
|
-
const getLocatorTargetFromElement =
|
|
294
|
+
const getLocatorTargetFromElement = useCallback(
|
|
438
295
|
(element) => {
|
|
439
296
|
if (element.closest("[data-ui-lint]")) return null;
|
|
440
297
|
let source = getSourceFromDataLoc(element);
|
|
@@ -470,7 +327,7 @@ function UILintProvider({
|
|
|
470
327
|
},
|
|
471
328
|
[settings.hideNodeModules]
|
|
472
329
|
);
|
|
473
|
-
const handleMouseMove =
|
|
330
|
+
const handleMouseMove = useCallback(
|
|
474
331
|
(e) => {
|
|
475
332
|
if (!altKeyHeld) return;
|
|
476
333
|
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
|
|
@@ -491,7 +348,7 @@ function UILintProvider({
|
|
|
491
348
|
},
|
|
492
349
|
[altKeyHeld, getLocatorTargetFromElement]
|
|
493
350
|
);
|
|
494
|
-
const handleLocatorClick =
|
|
351
|
+
const handleLocatorClick = useCallback(
|
|
495
352
|
(e) => {
|
|
496
353
|
if (!altKeyHeld || !locatorTarget) return;
|
|
497
354
|
e.preventDefault();
|
|
@@ -503,14 +360,19 @@ function UILintProvider({
|
|
|
503
360
|
source = stackItem.source;
|
|
504
361
|
}
|
|
505
362
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
363
|
+
setInspectedElement({
|
|
364
|
+
element: locatorTarget.element,
|
|
365
|
+
source,
|
|
366
|
+
componentStack: locatorTarget.componentStack,
|
|
367
|
+
rect: locatorTarget.rect
|
|
368
|
+
});
|
|
369
|
+
setAltKeyHeld(false);
|
|
370
|
+
setLocatorTarget(null);
|
|
371
|
+
setLocatorStackIndex(0);
|
|
510
372
|
},
|
|
511
373
|
[altKeyHeld, locatorTarget, locatorStackIndex]
|
|
512
374
|
);
|
|
513
|
-
|
|
375
|
+
useEffect(() => {
|
|
514
376
|
if (!isBrowser() || !enabled) return;
|
|
515
377
|
const handleKeyDown = (e) => {
|
|
516
378
|
if (e.key === "Alt") {
|
|
@@ -539,7 +401,7 @@ function UILintProvider({
|
|
|
539
401
|
window.removeEventListener("blur", handleBlur);
|
|
540
402
|
};
|
|
541
403
|
}, [enabled]);
|
|
542
|
-
|
|
404
|
+
useEffect(() => {
|
|
543
405
|
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
544
406
|
window.addEventListener("mousemove", handleMouseMove);
|
|
545
407
|
window.addEventListener("click", handleLocatorClick, true);
|
|
@@ -548,7 +410,7 @@ function UILintProvider({
|
|
|
548
410
|
window.removeEventListener("click", handleLocatorClick, true);
|
|
549
411
|
};
|
|
550
412
|
}, [enabled, altKeyHeld, handleMouseMove, handleLocatorClick]);
|
|
551
|
-
|
|
413
|
+
useEffect(() => {
|
|
552
414
|
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
553
415
|
const handleWheel = (e) => {
|
|
554
416
|
if (!locatorTarget) return;
|
|
@@ -562,29 +424,19 @@ function UILintProvider({
|
|
|
562
424
|
window.addEventListener("wheel", handleWheel, { passive: false });
|
|
563
425
|
return () => window.removeEventListener("wheel", handleWheel);
|
|
564
426
|
}, [enabled, altKeyHeld, locatorTarget, locatorGoUp, locatorGoDown]);
|
|
565
|
-
|
|
427
|
+
useEffect(() => {
|
|
566
428
|
if (!isBrowser() || !enabled) return;
|
|
567
429
|
const handleKeyDown = (e) => {
|
|
568
|
-
if (
|
|
569
|
-
|
|
570
|
-
toggleMode();
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
if (e.key === "Escape") {
|
|
574
|
-
handleEscape();
|
|
575
|
-
return;
|
|
430
|
+
if (e.key === "Escape" && inspectedElement) {
|
|
431
|
+
setInspectedElement(null);
|
|
576
432
|
}
|
|
577
433
|
};
|
|
578
434
|
window.addEventListener("keydown", handleKeyDown);
|
|
579
435
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
580
|
-
}, [enabled,
|
|
581
|
-
|
|
436
|
+
}, [enabled, inspectedElement]);
|
|
437
|
+
useEffect(() => {
|
|
582
438
|
setIsMounted(true);
|
|
583
439
|
}, []);
|
|
584
|
-
useEffect2(() => {
|
|
585
|
-
setSelectedElement(null);
|
|
586
|
-
setHoveredElement(null);
|
|
587
|
-
}, [mode]);
|
|
588
440
|
const effectiveLocatorTarget = useMemo(() => {
|
|
589
441
|
if (!locatorTarget) return null;
|
|
590
442
|
return {
|
|
@@ -594,37 +446,23 @@ function UILintProvider({
|
|
|
594
446
|
}, [locatorTarget, locatorStackIndex]);
|
|
595
447
|
const contextValue = useMemo(
|
|
596
448
|
() => ({
|
|
597
|
-
mode,
|
|
598
|
-
setMode,
|
|
599
|
-
scannedElements: elements,
|
|
600
|
-
sourceFiles,
|
|
601
|
-
selectedElement,
|
|
602
|
-
setSelectedElement,
|
|
603
|
-
hoveredElement,
|
|
604
|
-
setHoveredElement,
|
|
605
449
|
settings,
|
|
606
450
|
updateSettings,
|
|
607
|
-
rescan,
|
|
608
|
-
isScanning,
|
|
609
451
|
altKeyHeld,
|
|
610
452
|
locatorTarget: effectiveLocatorTarget,
|
|
611
453
|
locatorGoUp,
|
|
612
|
-
locatorGoDown
|
|
454
|
+
locatorGoDown,
|
|
455
|
+
inspectedElement,
|
|
456
|
+
setInspectedElement
|
|
613
457
|
}),
|
|
614
458
|
[
|
|
615
|
-
mode,
|
|
616
|
-
elements,
|
|
617
|
-
sourceFiles,
|
|
618
|
-
selectedElement,
|
|
619
|
-
hoveredElement,
|
|
620
459
|
settings,
|
|
621
460
|
updateSettings,
|
|
622
|
-
rescan,
|
|
623
|
-
isScanning,
|
|
624
461
|
altKeyHeld,
|
|
625
462
|
effectiveLocatorTarget,
|
|
626
463
|
locatorGoUp,
|
|
627
|
-
locatorGoDown
|
|
464
|
+
locatorGoDown,
|
|
465
|
+
inspectedElement
|
|
628
466
|
]
|
|
629
467
|
);
|
|
630
468
|
const shouldRenderUI = enabled && isMounted;
|
|
@@ -634,33 +472,31 @@ function UILintProvider({
|
|
|
634
472
|
] });
|
|
635
473
|
}
|
|
636
474
|
function UILintUI() {
|
|
637
|
-
const {
|
|
638
|
-
const [components, setComponents] =
|
|
639
|
-
|
|
475
|
+
const { altKeyHeld, inspectedElement } = useUILintContext();
|
|
476
|
+
const [components, setComponents] = useState(null);
|
|
477
|
+
useEffect(() => {
|
|
640
478
|
Promise.all([
|
|
641
|
-
import("./UILintToolbar-
|
|
642
|
-
import("./
|
|
643
|
-
import("./
|
|
644
|
-
|
|
645
|
-
]).then(([toolbar, overlays, panel, locator]) => {
|
|
479
|
+
import("./UILintToolbar-GAOYF7GY.js"),
|
|
480
|
+
import("./InspectionPanel-3ML64TAP.js"),
|
|
481
|
+
import("./LocatorOverlay-GTTWBRKH.js")
|
|
482
|
+
]).then(([toolbar, panel, locator]) => {
|
|
646
483
|
setComponents({
|
|
647
484
|
Toolbar: toolbar.UILintToolbar,
|
|
648
|
-
Overlays: overlays.SourceOverlays,
|
|
649
485
|
Panel: panel.InspectionPanel,
|
|
650
|
-
LocatorOverlay: locator.LocatorOverlay
|
|
486
|
+
LocatorOverlay: locator.LocatorOverlay,
|
|
487
|
+
InspectedHighlight: locator.InspectedElementHighlight
|
|
651
488
|
});
|
|
652
489
|
});
|
|
653
490
|
}, []);
|
|
654
491
|
if (!components) return null;
|
|
655
|
-
const { Toolbar,
|
|
492
|
+
const { Toolbar, Panel, LocatorOverlay, InspectedHighlight } = components;
|
|
656
493
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
657
494
|
/* @__PURE__ */ jsx(Toolbar, {}),
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
/* @__PURE__ */ jsx(
|
|
495
|
+
altKeyHeld && /* @__PURE__ */ jsx(LocatorOverlay, {}),
|
|
496
|
+
inspectedElement && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
497
|
+
/* @__PURE__ */ jsx(InspectedHighlight, {}),
|
|
661
498
|
/* @__PURE__ */ jsx(Panel, {})
|
|
662
|
-
] })
|
|
663
|
-
altKeyHeld && /* @__PURE__ */ jsx(LocatorOverlay, {})
|
|
499
|
+
] })
|
|
664
500
|
] });
|
|
665
501
|
}
|
|
666
502
|
|
|
@@ -680,7 +516,6 @@ export {
|
|
|
680
516
|
getElementById,
|
|
681
517
|
updateElementRects,
|
|
682
518
|
buildEditorUrl,
|
|
683
|
-
useElementScan,
|
|
684
519
|
useUILintContext,
|
|
685
520
|
UILintProvider
|
|
686
521
|
};
|