uilint-react 0.1.22 → 0.1.24
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-T4N3VQRI.js +767 -0
- package/dist/{InspectionPanel-ZBDXQ2LU.js → InspectionPanel-47JBBKBL.js} +2 -2
- package/dist/{LocatorOverlay-3H446RPO.js → LocatorOverlay-ADJUWU2H.js} +2 -2
- package/dist/{UILintToolbar-C6HOAJA4.js → UILintToolbar-5PG6WVW6.js} +2 -2
- package/dist/{chunk-3DNDKMZ4.js → chunk-EB6K2KGR.js} +1 -1
- package/dist/{chunk-CWCKS753.js → chunk-FWYNI6JG.js} +337 -30
- package/dist/chunk-GFURSJEQ.js +537 -0
- package/dist/{chunk-EBU7YY73.js → chunk-I4C3NAUH.js} +226 -17
- package/dist/index.d.ts +35 -0
- package/dist/index.js +4 -4
- package/package.json +2 -2
- package/dist/chunk-GUF36FGA.js +0 -276
|
@@ -28,7 +28,14 @@ var FILE_COLORS = [
|
|
|
28
28
|
// purple
|
|
29
29
|
];
|
|
30
30
|
var DEFAULT_SETTINGS = {
|
|
31
|
-
hideNodeModules: true
|
|
31
|
+
hideNodeModules: true,
|
|
32
|
+
autoScanEnabled: false
|
|
33
|
+
};
|
|
34
|
+
var DEFAULT_AUTO_SCAN_STATE = {
|
|
35
|
+
status: "idle",
|
|
36
|
+
currentIndex: 0,
|
|
37
|
+
totalElements: 0,
|
|
38
|
+
elements: []
|
|
32
39
|
};
|
|
33
40
|
var DATA_UILINT_ID = "data-ui-lint-id";
|
|
34
41
|
|
|
@@ -254,7 +261,8 @@ import {
|
|
|
254
261
|
useState,
|
|
255
262
|
useEffect,
|
|
256
263
|
useCallback,
|
|
257
|
-
useMemo
|
|
264
|
+
useMemo,
|
|
265
|
+
useRef
|
|
258
266
|
} from "react";
|
|
259
267
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
260
268
|
var UILintContext = createContext(null);
|
|
@@ -280,9 +288,185 @@ function UILintProvider({
|
|
|
280
288
|
);
|
|
281
289
|
const [locatorStackIndex, setLocatorStackIndex] = useState(0);
|
|
282
290
|
const [inspectedElement, setInspectedElement] = useState(null);
|
|
291
|
+
const [autoScanState, setAutoScanState] = useState(
|
|
292
|
+
DEFAULT_AUTO_SCAN_STATE
|
|
293
|
+
);
|
|
294
|
+
const [elementIssuesCache, setElementIssuesCache] = useState(/* @__PURE__ */ new Map());
|
|
295
|
+
const scanPausedRef = useRef(false);
|
|
296
|
+
const scanAbortRef = useRef(false);
|
|
283
297
|
const updateSettings = useCallback((partial) => {
|
|
284
298
|
setSettings((prev) => ({ ...prev, ...partial }));
|
|
285
299
|
}, []);
|
|
300
|
+
const scanElementForIssues = useCallback(
|
|
301
|
+
async (element) => {
|
|
302
|
+
if (!element.source) {
|
|
303
|
+
return {
|
|
304
|
+
elementId: element.id,
|
|
305
|
+
issues: [],
|
|
306
|
+
status: "complete"
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const sourceResponse = await fetch(
|
|
311
|
+
`/api/.uilint/source?path=${encodeURIComponent(
|
|
312
|
+
element.source.fileName
|
|
313
|
+
)}`
|
|
314
|
+
);
|
|
315
|
+
if (!sourceResponse.ok) {
|
|
316
|
+
return {
|
|
317
|
+
elementId: element.id,
|
|
318
|
+
issues: [],
|
|
319
|
+
status: "error"
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const sourceData = await sourceResponse.json();
|
|
323
|
+
const analyzeResponse = await fetch("/api/.uilint/analyze", {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: { "Content-Type": "application/json" },
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
sourceCode: sourceData.content,
|
|
328
|
+
filePath: sourceData.relativePath || element.source.fileName,
|
|
329
|
+
componentName: element.componentStack[0]?.name || element.tagName,
|
|
330
|
+
componentLine: element.source.lineNumber
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
if (!analyzeResponse.ok) {
|
|
334
|
+
return {
|
|
335
|
+
elementId: element.id,
|
|
336
|
+
issues: [],
|
|
337
|
+
status: "error"
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const result = await analyzeResponse.json();
|
|
341
|
+
return {
|
|
342
|
+
elementId: element.id,
|
|
343
|
+
issues: result.issues || [],
|
|
344
|
+
status: "complete"
|
|
345
|
+
};
|
|
346
|
+
} catch {
|
|
347
|
+
return {
|
|
348
|
+
elementId: element.id,
|
|
349
|
+
issues: [],
|
|
350
|
+
status: "error"
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
[]
|
|
355
|
+
);
|
|
356
|
+
const runScanLoop = useCallback(
|
|
357
|
+
async (elements, startIndex) => {
|
|
358
|
+
const fileToElements = /* @__PURE__ */ new Map();
|
|
359
|
+
const scannedFiles = /* @__PURE__ */ new Set();
|
|
360
|
+
for (const el of elements) {
|
|
361
|
+
if (el.source) {
|
|
362
|
+
const file = el.source.fileName;
|
|
363
|
+
const existing = fileToElements.get(file) || [];
|
|
364
|
+
existing.push(el);
|
|
365
|
+
fileToElements.set(file, existing);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
for (let i = startIndex; i < elements.length; i++) {
|
|
369
|
+
if (scanAbortRef.current) {
|
|
370
|
+
setAutoScanState((prev) => ({ ...prev, status: "idle" }));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
while (scanPausedRef.current) {
|
|
374
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
375
|
+
if (scanAbortRef.current) {
|
|
376
|
+
setAutoScanState((prev) => ({ ...prev, status: "idle" }));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const element = elements[i];
|
|
381
|
+
setAutoScanState((prev) => ({
|
|
382
|
+
...prev,
|
|
383
|
+
currentIndex: i
|
|
384
|
+
}));
|
|
385
|
+
if (element.source && scannedFiles.has(element.source.fileName)) {
|
|
386
|
+
const existingElements = fileToElements.get(element.source.fileName);
|
|
387
|
+
if (existingElements && existingElements.length > 0) {
|
|
388
|
+
const firstId = existingElements[0].id;
|
|
389
|
+
setElementIssuesCache((prev) => {
|
|
390
|
+
const cached = prev.get(firstId);
|
|
391
|
+
if (cached) {
|
|
392
|
+
const newCache = new Map(prev);
|
|
393
|
+
newCache.set(element.id, { ...cached, elementId: element.id });
|
|
394
|
+
return newCache;
|
|
395
|
+
}
|
|
396
|
+
return prev;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
setElementIssuesCache((prev) => {
|
|
402
|
+
const newCache = new Map(prev);
|
|
403
|
+
newCache.set(element.id, {
|
|
404
|
+
elementId: element.id,
|
|
405
|
+
issues: [],
|
|
406
|
+
status: "scanning"
|
|
407
|
+
});
|
|
408
|
+
return newCache;
|
|
409
|
+
});
|
|
410
|
+
const result = await scanElementForIssues(element);
|
|
411
|
+
setElementIssuesCache((prev) => {
|
|
412
|
+
const newCache = new Map(prev);
|
|
413
|
+
newCache.set(element.id, result);
|
|
414
|
+
return newCache;
|
|
415
|
+
});
|
|
416
|
+
if (element.source) {
|
|
417
|
+
scannedFiles.add(element.source.fileName);
|
|
418
|
+
}
|
|
419
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
420
|
+
}
|
|
421
|
+
setAutoScanState((prev) => ({
|
|
422
|
+
...prev,
|
|
423
|
+
status: "complete",
|
|
424
|
+
currentIndex: elements.length
|
|
425
|
+
}));
|
|
426
|
+
},
|
|
427
|
+
[scanElementForIssues]
|
|
428
|
+
);
|
|
429
|
+
const startAutoScan = useCallback(() => {
|
|
430
|
+
scanPausedRef.current = false;
|
|
431
|
+
scanAbortRef.current = false;
|
|
432
|
+
const elements = scanDOMForSources(document.body, settings.hideNodeModules);
|
|
433
|
+
const initialCache = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const el of elements) {
|
|
435
|
+
initialCache.set(el.id, {
|
|
436
|
+
elementId: el.id,
|
|
437
|
+
issues: [],
|
|
438
|
+
status: "pending"
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
setElementIssuesCache(initialCache);
|
|
442
|
+
setAutoScanState({
|
|
443
|
+
status: "scanning",
|
|
444
|
+
currentIndex: 0,
|
|
445
|
+
totalElements: elements.length,
|
|
446
|
+
elements
|
|
447
|
+
});
|
|
448
|
+
runScanLoop(elements, 0);
|
|
449
|
+
}, [settings.hideNodeModules, runScanLoop]);
|
|
450
|
+
const pauseAutoScan = useCallback(() => {
|
|
451
|
+
scanPausedRef.current = true;
|
|
452
|
+
setAutoScanState((prev) => ({ ...prev, status: "paused" }));
|
|
453
|
+
}, []);
|
|
454
|
+
const resumeAutoScan = useCallback(() => {
|
|
455
|
+
scanPausedRef.current = false;
|
|
456
|
+
setAutoScanState((prev) => {
|
|
457
|
+
if (prev.status === "paused") {
|
|
458
|
+
runScanLoop(prev.elements, prev.currentIndex);
|
|
459
|
+
return { ...prev, status: "scanning" };
|
|
460
|
+
}
|
|
461
|
+
return prev;
|
|
462
|
+
});
|
|
463
|
+
}, [runScanLoop]);
|
|
464
|
+
const stopAutoScan = useCallback(() => {
|
|
465
|
+
scanAbortRef.current = true;
|
|
466
|
+
scanPausedRef.current = false;
|
|
467
|
+
setAutoScanState(DEFAULT_AUTO_SCAN_STATE);
|
|
468
|
+
setElementIssuesCache(/* @__PURE__ */ new Map());
|
|
469
|
+
}, []);
|
|
286
470
|
const locatorGoUp = useCallback(() => {
|
|
287
471
|
if (!locatorTarget) return;
|
|
288
472
|
const maxIndex = locatorTarget.componentStack.length;
|
|
@@ -329,7 +513,7 @@ function UILintProvider({
|
|
|
329
513
|
);
|
|
330
514
|
const handleMouseMove = useCallback(
|
|
331
515
|
(e) => {
|
|
332
|
-
if (!altKeyHeld) return;
|
|
516
|
+
if (!altKeyHeld && !inspectedElement) return;
|
|
333
517
|
const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY);
|
|
334
518
|
if (!elementAtPoint) {
|
|
335
519
|
setLocatorTarget(null);
|
|
@@ -346,7 +530,7 @@ function UILintProvider({
|
|
|
346
530
|
}
|
|
347
531
|
setLocatorTarget(null);
|
|
348
532
|
},
|
|
349
|
-
[altKeyHeld, getLocatorTargetFromElement]
|
|
533
|
+
[altKeyHeld, inspectedElement, getLocatorTargetFromElement]
|
|
350
534
|
);
|
|
351
535
|
const handleLocatorClick = useCallback(
|
|
352
536
|
(e) => {
|
|
@@ -402,14 +586,23 @@ function UILintProvider({
|
|
|
402
586
|
};
|
|
403
587
|
}, [enabled]);
|
|
404
588
|
useEffect(() => {
|
|
405
|
-
if (!isBrowser() || !enabled
|
|
589
|
+
if (!isBrowser() || !enabled) return;
|
|
590
|
+
if (!altKeyHeld && !inspectedElement) return;
|
|
406
591
|
window.addEventListener("mousemove", handleMouseMove);
|
|
407
|
-
|
|
592
|
+
if (altKeyHeld) {
|
|
593
|
+
window.addEventListener("click", handleLocatorClick, true);
|
|
594
|
+
}
|
|
408
595
|
return () => {
|
|
409
596
|
window.removeEventListener("mousemove", handleMouseMove);
|
|
410
597
|
window.removeEventListener("click", handleLocatorClick, true);
|
|
411
598
|
};
|
|
412
|
-
}, [
|
|
599
|
+
}, [
|
|
600
|
+
enabled,
|
|
601
|
+
altKeyHeld,
|
|
602
|
+
inspectedElement,
|
|
603
|
+
handleMouseMove,
|
|
604
|
+
handleLocatorClick
|
|
605
|
+
]);
|
|
413
606
|
useEffect(() => {
|
|
414
607
|
if (!isBrowser() || !enabled || !altKeyHeld) return;
|
|
415
608
|
const handleWheel = (e) => {
|
|
@@ -453,7 +646,13 @@ function UILintProvider({
|
|
|
453
646
|
locatorGoUp,
|
|
454
647
|
locatorGoDown,
|
|
455
648
|
inspectedElement,
|
|
456
|
-
setInspectedElement
|
|
649
|
+
setInspectedElement,
|
|
650
|
+
autoScanState,
|
|
651
|
+
elementIssuesCache,
|
|
652
|
+
startAutoScan,
|
|
653
|
+
pauseAutoScan,
|
|
654
|
+
resumeAutoScan,
|
|
655
|
+
stopAutoScan
|
|
457
656
|
}),
|
|
458
657
|
[
|
|
459
658
|
settings,
|
|
@@ -462,7 +661,13 @@ function UILintProvider({
|
|
|
462
661
|
effectiveLocatorTarget,
|
|
463
662
|
locatorGoUp,
|
|
464
663
|
locatorGoDown,
|
|
465
|
-
inspectedElement
|
|
664
|
+
inspectedElement,
|
|
665
|
+
autoScanState,
|
|
666
|
+
elementIssuesCache,
|
|
667
|
+
startAutoScan,
|
|
668
|
+
pauseAutoScan,
|
|
669
|
+
resumeAutoScan,
|
|
670
|
+
stopAutoScan
|
|
466
671
|
]
|
|
467
672
|
);
|
|
468
673
|
const shouldRenderUI = enabled && isMounted;
|
|
@@ -472,27 +677,31 @@ function UILintProvider({
|
|
|
472
677
|
] });
|
|
473
678
|
}
|
|
474
679
|
function UILintUI() {
|
|
475
|
-
const { altKeyHeld, inspectedElement } = useUILintContext();
|
|
680
|
+
const { altKeyHeld, inspectedElement, autoScanState } = useUILintContext();
|
|
476
681
|
const [components, setComponents] = useState(null);
|
|
477
682
|
useEffect(() => {
|
|
478
683
|
Promise.all([
|
|
479
|
-
import("./UILintToolbar-
|
|
480
|
-
import("./InspectionPanel-
|
|
481
|
-
import("./LocatorOverlay-
|
|
482
|
-
|
|
684
|
+
import("./UILintToolbar-5PG6WVW6.js"),
|
|
685
|
+
import("./InspectionPanel-47JBBKBL.js"),
|
|
686
|
+
import("./LocatorOverlay-ADJUWU2H.js"),
|
|
687
|
+
import("./ElementBadges-T4N3VQRI.js")
|
|
688
|
+
]).then(([toolbar, panel, locator, badges]) => {
|
|
483
689
|
setComponents({
|
|
484
690
|
Toolbar: toolbar.UILintToolbar,
|
|
485
691
|
Panel: panel.InspectionPanel,
|
|
486
692
|
LocatorOverlay: locator.LocatorOverlay,
|
|
487
|
-
InspectedHighlight: locator.InspectedElementHighlight
|
|
693
|
+
InspectedHighlight: locator.InspectedElementHighlight,
|
|
694
|
+
ElementBadges: badges.ElementBadges
|
|
488
695
|
});
|
|
489
696
|
});
|
|
490
697
|
}, []);
|
|
491
698
|
if (!components) return null;
|
|
492
|
-
const { Toolbar, Panel, LocatorOverlay, InspectedHighlight } = components;
|
|
699
|
+
const { Toolbar, Panel, LocatorOverlay, InspectedHighlight, ElementBadges } = components;
|
|
700
|
+
const showBadges = autoScanState.status !== "idle";
|
|
493
701
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
494
702
|
/* @__PURE__ */ jsx(Toolbar, {}),
|
|
495
|
-
altKeyHeld && /* @__PURE__ */ jsx(LocatorOverlay, {}),
|
|
703
|
+
(altKeyHeld || inspectedElement) && /* @__PURE__ */ jsx(LocatorOverlay, {}),
|
|
704
|
+
showBadges && /* @__PURE__ */ jsx(ElementBadges, {}),
|
|
496
705
|
inspectedElement && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
497
706
|
/* @__PURE__ */ jsx(InspectedHighlight, {}),
|
|
498
707
|
/* @__PURE__ */ jsx(Panel, {})
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,27 @@ interface SourceFile {
|
|
|
47
47
|
*/
|
|
48
48
|
interface UILintSettings {
|
|
49
49
|
hideNodeModules: boolean;
|
|
50
|
+
autoScanEnabled: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* State for the auto-scan feature
|
|
54
|
+
*/
|
|
55
|
+
interface AutoScanState {
|
|
56
|
+
status: "idle" | "scanning" | "paused" | "complete";
|
|
57
|
+
currentIndex: number;
|
|
58
|
+
totalElements: number;
|
|
59
|
+
elements: ScannedElement[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Cached issue data for a scanned element
|
|
63
|
+
*/
|
|
64
|
+
interface ElementIssue {
|
|
65
|
+
elementId: string;
|
|
66
|
+
issues: Array<{
|
|
67
|
+
line?: number;
|
|
68
|
+
message: string;
|
|
69
|
+
}>;
|
|
70
|
+
status: "pending" | "scanning" | "complete" | "error";
|
|
50
71
|
}
|
|
51
72
|
/**
|
|
52
73
|
* Element detected under the cursor during Alt-key locator mode
|
|
@@ -67,6 +88,8 @@ interface InspectedElement {
|
|
|
67
88
|
source: SourceLocation | null;
|
|
68
89
|
componentStack: ComponentInfo[];
|
|
69
90
|
rect: DOMRect;
|
|
91
|
+
/** Optional ID from auto-scan to link to cached results */
|
|
92
|
+
scannedElementId?: string;
|
|
70
93
|
}
|
|
71
94
|
/**
|
|
72
95
|
* Context value provided by UILintProvider
|
|
@@ -86,6 +109,18 @@ interface UILintContextValue {
|
|
|
86
109
|
inspectedElement: InspectedElement | null;
|
|
87
110
|
/** Set the element to inspect (opens sidebar) */
|
|
88
111
|
setInspectedElement: (element: InspectedElement | null) => void;
|
|
112
|
+
/** Auto-scan state */
|
|
113
|
+
autoScanState: AutoScanState;
|
|
114
|
+
/** Cache of element issues from auto-scan */
|
|
115
|
+
elementIssuesCache: Map<string, ElementIssue>;
|
|
116
|
+
/** Start auto-scanning all page elements */
|
|
117
|
+
startAutoScan: () => void;
|
|
118
|
+
/** Pause the auto-scan */
|
|
119
|
+
pauseAutoScan: () => void;
|
|
120
|
+
/** Resume the auto-scan */
|
|
121
|
+
resumeAutoScan: () => void;
|
|
122
|
+
/** Stop and reset the auto-scan */
|
|
123
|
+
stopAutoScan: () => void;
|
|
89
124
|
}
|
|
90
125
|
/**
|
|
91
126
|
* Props for the UILintProvider component
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
UILintToolbar
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GFURSJEQ.js";
|
|
5
5
|
import {
|
|
6
6
|
InspectionPanel,
|
|
7
7
|
clearSourceCache,
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
fetchSourceWithContext,
|
|
10
10
|
getCachedSource,
|
|
11
11
|
prefetchSources
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-FWYNI6JG.js";
|
|
13
13
|
import {
|
|
14
14
|
LocatorOverlay
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EB6K2KGR.js";
|
|
16
16
|
import {
|
|
17
17
|
DATA_UILINT_ID,
|
|
18
18
|
DEFAULT_SETTINGS,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
scanDOMForSources,
|
|
32
32
|
updateElementRects,
|
|
33
33
|
useUILintContext
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-I4C3NAUH.js";
|
|
35
35
|
|
|
36
36
|
// src/consistency/snapshot.ts
|
|
37
37
|
var DATA_ELEMENTS_ATTR = "data-elements";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uilint-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "React component for AI-powered UI consistency checking",
|
|
5
5
|
"author": "Peter Suggate",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"node": ">=20.0.0"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"uilint-core": "^0.1.
|
|
37
|
+
"uilint-core": "^0.1.24"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^19.0.0",
|
package/dist/chunk-GUF36FGA.js
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import {
|
|
3
|
-
useUILintContext
|
|
4
|
-
} from "./chunk-EBU7YY73.js";
|
|
5
|
-
|
|
6
|
-
// src/components/ui-lint/UILintToolbar.tsx
|
|
7
|
-
import { useState, useRef, useEffect } from "react";
|
|
8
|
-
import { createPortal } from "react-dom";
|
|
9
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
var STYLES = {
|
|
11
|
-
bg: "rgba(17, 24, 39, 0.9)",
|
|
12
|
-
bgHover: "rgba(31, 41, 55, 0.95)",
|
|
13
|
-
border: "rgba(75, 85, 99, 0.5)",
|
|
14
|
-
text: "#F9FAFB",
|
|
15
|
-
textMuted: "#9CA3AF",
|
|
16
|
-
accent: "#3B82F6",
|
|
17
|
-
accentHover: "#2563EB",
|
|
18
|
-
shadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
|
|
19
|
-
blur: "blur(12px)",
|
|
20
|
-
font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
21
|
-
fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace'
|
|
22
|
-
};
|
|
23
|
-
function UILintToolbar() {
|
|
24
|
-
const { settings, updateSettings, inspectedElement } = useUILintContext();
|
|
25
|
-
const [showSettings, setShowSettings] = useState(false);
|
|
26
|
-
const [mounted, setMounted] = useState(false);
|
|
27
|
-
const settingsRef = useRef(null);
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
setMounted(true);
|
|
30
|
-
}, []);
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
const handleClickOutside = (e) => {
|
|
33
|
-
if (settingsRef.current && !settingsRef.current.contains(e.target)) {
|
|
34
|
-
setShowSettings(false);
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
if (showSettings) {
|
|
38
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
39
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
40
|
-
}
|
|
41
|
-
}, [showSettings]);
|
|
42
|
-
if (!mounted) return null;
|
|
43
|
-
if (inspectedElement) return null;
|
|
44
|
-
const content = /* @__PURE__ */ jsxs(
|
|
45
|
-
"div",
|
|
46
|
-
{
|
|
47
|
-
"data-ui-lint": true,
|
|
48
|
-
style: {
|
|
49
|
-
position: "fixed",
|
|
50
|
-
top: "24px",
|
|
51
|
-
right: "24px",
|
|
52
|
-
zIndex: 99999,
|
|
53
|
-
fontFamily: STYLES.font
|
|
54
|
-
},
|
|
55
|
-
children: [
|
|
56
|
-
/* @__PURE__ */ jsx("style", { children: `
|
|
57
|
-
@keyframes uilint-fade-in {
|
|
58
|
-
from { opacity: 0; transform: scale(0.95); }
|
|
59
|
-
to { opacity: 1; transform: scale(1); }
|
|
60
|
-
}
|
|
61
|
-
` }),
|
|
62
|
-
/* @__PURE__ */ jsxs("div", { style: { position: "relative" }, ref: settingsRef, children: [
|
|
63
|
-
/* @__PURE__ */ jsx(
|
|
64
|
-
"button",
|
|
65
|
-
{
|
|
66
|
-
onClick: () => setShowSettings(!showSettings),
|
|
67
|
-
style: {
|
|
68
|
-
display: "flex",
|
|
69
|
-
alignItems: "center",
|
|
70
|
-
justifyContent: "center",
|
|
71
|
-
width: "48px",
|
|
72
|
-
height: "48px",
|
|
73
|
-
borderRadius: "50%",
|
|
74
|
-
border: `1px solid ${STYLES.border}`,
|
|
75
|
-
backgroundColor: showSettings ? STYLES.bgHover : STYLES.bg,
|
|
76
|
-
backdropFilter: STYLES.blur,
|
|
77
|
-
WebkitBackdropFilter: STYLES.blur,
|
|
78
|
-
boxShadow: STYLES.shadow,
|
|
79
|
-
cursor: "pointer",
|
|
80
|
-
transition: "all 0.2s ease-out",
|
|
81
|
-
color: showSettings ? STYLES.text : STYLES.textMuted,
|
|
82
|
-
fontSize: "20px"
|
|
83
|
-
},
|
|
84
|
-
onMouseEnter: (e) => {
|
|
85
|
-
e.currentTarget.style.transform = "scale(1.05)";
|
|
86
|
-
e.currentTarget.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.5)";
|
|
87
|
-
},
|
|
88
|
-
onMouseLeave: (e) => {
|
|
89
|
-
e.currentTarget.style.transform = "scale(1)";
|
|
90
|
-
e.currentTarget.style.boxShadow = STYLES.shadow;
|
|
91
|
-
},
|
|
92
|
-
title: "UILint Settings (Alt+Click any element to inspect)",
|
|
93
|
-
children: /* @__PURE__ */ jsx(UILintIcon, { active: showSettings })
|
|
94
|
-
}
|
|
95
|
-
),
|
|
96
|
-
showSettings && /* @__PURE__ */ jsx(SettingsPopover, { settings, onUpdate: updateSettings }),
|
|
97
|
-
/* @__PURE__ */ jsxs(
|
|
98
|
-
"div",
|
|
99
|
-
{
|
|
100
|
-
style: {
|
|
101
|
-
position: "absolute",
|
|
102
|
-
left: "100%",
|
|
103
|
-
top: "50%",
|
|
104
|
-
transform: "translateY(-50%)",
|
|
105
|
-
marginLeft: "12px",
|
|
106
|
-
padding: "8px 12px",
|
|
107
|
-
borderRadius: "8px",
|
|
108
|
-
backgroundColor: STYLES.bg,
|
|
109
|
-
border: `1px solid ${STYLES.border}`,
|
|
110
|
-
backdropFilter: STYLES.blur,
|
|
111
|
-
WebkitBackdropFilter: STYLES.blur,
|
|
112
|
-
boxShadow: STYLES.shadow,
|
|
113
|
-
fontSize: "12px",
|
|
114
|
-
color: STYLES.textMuted,
|
|
115
|
-
whiteSpace: "nowrap",
|
|
116
|
-
opacity: 0,
|
|
117
|
-
transition: "opacity 0.2s",
|
|
118
|
-
pointerEvents: "none"
|
|
119
|
-
},
|
|
120
|
-
className: "uilint-hint",
|
|
121
|
-
children: [
|
|
122
|
-
/* @__PURE__ */ jsx("span", { style: { color: STYLES.text }, children: "Alt+Click" }),
|
|
123
|
-
" any element to inspect"
|
|
124
|
-
]
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
] })
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
);
|
|
131
|
-
return createPortal(content, document.body);
|
|
132
|
-
}
|
|
133
|
-
function SettingsPopover({
|
|
134
|
-
settings,
|
|
135
|
-
onUpdate
|
|
136
|
-
}) {
|
|
137
|
-
return /* @__PURE__ */ jsxs(
|
|
138
|
-
"div",
|
|
139
|
-
{
|
|
140
|
-
style: {
|
|
141
|
-
position: "absolute",
|
|
142
|
-
top: "100%",
|
|
143
|
-
right: 0,
|
|
144
|
-
marginTop: "8px",
|
|
145
|
-
width: "280px",
|
|
146
|
-
padding: "16px",
|
|
147
|
-
borderRadius: "12px",
|
|
148
|
-
border: `1px solid ${STYLES.border}`,
|
|
149
|
-
backgroundColor: STYLES.bg,
|
|
150
|
-
backdropFilter: STYLES.blur,
|
|
151
|
-
WebkitBackdropFilter: STYLES.blur,
|
|
152
|
-
boxShadow: STYLES.shadow,
|
|
153
|
-
animation: "uilint-fade-in 0.15s ease-out"
|
|
154
|
-
},
|
|
155
|
-
children: [
|
|
156
|
-
/* @__PURE__ */ jsx(
|
|
157
|
-
"div",
|
|
158
|
-
{
|
|
159
|
-
style: {
|
|
160
|
-
fontSize: "13px",
|
|
161
|
-
fontWeight: 600,
|
|
162
|
-
color: STYLES.text,
|
|
163
|
-
marginBottom: "12px"
|
|
164
|
-
},
|
|
165
|
-
children: "UILint Settings"
|
|
166
|
-
}
|
|
167
|
-
),
|
|
168
|
-
/* @__PURE__ */ jsx(
|
|
169
|
-
SettingToggle,
|
|
170
|
-
{
|
|
171
|
-
label: "Hide node_modules",
|
|
172
|
-
checked: settings.hideNodeModules,
|
|
173
|
-
onChange: (checked) => onUpdate({ hideNodeModules: checked })
|
|
174
|
-
}
|
|
175
|
-
),
|
|
176
|
-
/* @__PURE__ */ jsxs(
|
|
177
|
-
"div",
|
|
178
|
-
{
|
|
179
|
-
style: {
|
|
180
|
-
marginTop: "12px",
|
|
181
|
-
paddingTop: "12px",
|
|
182
|
-
borderTop: `1px solid ${STYLES.border}`,
|
|
183
|
-
fontSize: "11px",
|
|
184
|
-
color: STYLES.textMuted,
|
|
185
|
-
lineHeight: 1.5
|
|
186
|
-
},
|
|
187
|
-
children: [
|
|
188
|
-
/* @__PURE__ */ jsx("strong", { style: { color: STYLES.text }, children: "Alt+Click" }),
|
|
189
|
-
" any element to open the inspector sidebar"
|
|
190
|
-
]
|
|
191
|
-
}
|
|
192
|
-
)
|
|
193
|
-
]
|
|
194
|
-
}
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
function SettingToggle({
|
|
198
|
-
label,
|
|
199
|
-
checked,
|
|
200
|
-
onChange
|
|
201
|
-
}) {
|
|
202
|
-
return /* @__PURE__ */ jsxs(
|
|
203
|
-
"label",
|
|
204
|
-
{
|
|
205
|
-
style: {
|
|
206
|
-
display: "flex",
|
|
207
|
-
alignItems: "center",
|
|
208
|
-
justifyContent: "space-between",
|
|
209
|
-
padding: "8px 0",
|
|
210
|
-
cursor: "pointer"
|
|
211
|
-
},
|
|
212
|
-
children: [
|
|
213
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: STYLES.textMuted }, children: label }),
|
|
214
|
-
/* @__PURE__ */ jsx(
|
|
215
|
-
"div",
|
|
216
|
-
{
|
|
217
|
-
onClick: () => onChange(!checked),
|
|
218
|
-
style: {
|
|
219
|
-
width: "36px",
|
|
220
|
-
height: "20px",
|
|
221
|
-
borderRadius: "10px",
|
|
222
|
-
backgroundColor: checked ? STYLES.accent : "rgba(75, 85, 99, 0.5)",
|
|
223
|
-
position: "relative",
|
|
224
|
-
transition: "background-color 0.2s"
|
|
225
|
-
},
|
|
226
|
-
children: /* @__PURE__ */ jsx(
|
|
227
|
-
"div",
|
|
228
|
-
{
|
|
229
|
-
style: {
|
|
230
|
-
position: "absolute",
|
|
231
|
-
top: "2px",
|
|
232
|
-
left: checked ? "18px" : "2px",
|
|
233
|
-
width: "16px",
|
|
234
|
-
height: "16px",
|
|
235
|
-
borderRadius: "50%",
|
|
236
|
-
backgroundColor: "#FFFFFF",
|
|
237
|
-
transition: "left 0.2s",
|
|
238
|
-
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
)
|
|
244
|
-
]
|
|
245
|
-
}
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
function UILintIcon({ active }) {
|
|
249
|
-
return /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [
|
|
250
|
-
/* @__PURE__ */ jsx(
|
|
251
|
-
"rect",
|
|
252
|
-
{
|
|
253
|
-
x: "3",
|
|
254
|
-
y: "3",
|
|
255
|
-
width: "18",
|
|
256
|
-
height: "18",
|
|
257
|
-
rx: "3",
|
|
258
|
-
stroke: active ? STYLES.accent : "currentColor",
|
|
259
|
-
strokeWidth: "2"
|
|
260
|
-
}
|
|
261
|
-
),
|
|
262
|
-
/* @__PURE__ */ jsx(
|
|
263
|
-
"path",
|
|
264
|
-
{
|
|
265
|
-
d: "M7 12h10M12 7v10",
|
|
266
|
-
stroke: active ? STYLES.accent : "currentColor",
|
|
267
|
-
strokeWidth: "2",
|
|
268
|
-
strokeLinecap: "round"
|
|
269
|
-
}
|
|
270
|
-
)
|
|
271
|
-
] });
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export {
|
|
275
|
-
UILintToolbar
|
|
276
|
-
};
|