uilint-react 0.1.37 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1143 @@
1
+ "use client";
2
+ import {
3
+ groupBySourceFile,
4
+ useUILintContext,
5
+ useUILintStore
6
+ } from "./chunk-ILK73X6L.js";
7
+
8
+ // src/components/ui-lint/UILintToolbar.tsx
9
+ import { useState as useState2, useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
10
+ import { createPortal } from "react-dom";
11
+
12
+ // src/components/ui-lint/toolbar-styles.ts
13
+ var STYLES = {
14
+ // Backgrounds
15
+ bg: "rgba(17, 24, 39, 0.92)",
16
+ bgHover: "rgba(31, 41, 55, 0.95)",
17
+ bgSegment: "rgba(31, 41, 55, 0.6)",
18
+ bgSegmentHover: "rgba(55, 65, 81, 0.8)",
19
+ bgPopover: "rgba(17, 24, 39, 0.96)",
20
+ // Borders
21
+ border: "rgba(75, 85, 99, 0.5)",
22
+ borderLight: "rgba(107, 114, 128, 0.3)",
23
+ divider: "rgba(75, 85, 99, 0.4)",
24
+ // Text
25
+ text: "#F9FAFB",
26
+ textMuted: "#9CA3AF",
27
+ textDim: "#6B7280",
28
+ // Accent colors
29
+ accent: "#3B82F6",
30
+ accentHover: "#2563EB",
31
+ accentLight: "rgba(59, 130, 246, 0.15)",
32
+ // Status colors
33
+ success: "#10B981",
34
+ successLight: "rgba(16, 185, 129, 0.15)",
35
+ warning: "#F59E0B",
36
+ warningLight: "rgba(245, 158, 11, 0.15)",
37
+ error: "#EF4444",
38
+ errorLight: "rgba(239, 68, 68, 0.15)",
39
+ // Badge colors
40
+ badgeBg: "#3B82F6",
41
+ badgeText: "#FFFFFF",
42
+ // Effects
43
+ shadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
44
+ shadowLg: "0 12px 48px rgba(0, 0, 0, 0.5)",
45
+ blur: "blur(12px)",
46
+ // Typography
47
+ font: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
48
+ fontMono: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
49
+ // Sizing
50
+ pillHeight: "40px",
51
+ pillRadius: "20px",
52
+ popoverRadius: "12px",
53
+ buttonRadius: "8px",
54
+ // Transitions
55
+ transition: "all 0.15s ease-out",
56
+ transitionFast: "all 0.1s ease-out"
57
+ };
58
+ function getStatusColor(issueCount) {
59
+ if (issueCount === 0) return STYLES.success;
60
+ return STYLES.warning;
61
+ }
62
+
63
+ // src/components/ui-lint/toolbar-icons.tsx
64
+ import { jsx, jsxs } from "react/jsx-runtime";
65
+ function StopIcon() {
66
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1", fill: "currentColor" }) });
67
+ }
68
+ function EllipsisIcon() {
69
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: [
70
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "2", fill: "currentColor" }),
71
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor" }),
72
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "2", fill: "currentColor" })
73
+ ] });
74
+ }
75
+ function CheckCircleIcon() {
76
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
77
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
78
+ /* @__PURE__ */ jsx(
79
+ "path",
80
+ {
81
+ d: "M8 12l3 3 5-6",
82
+ stroke: "currentColor",
83
+ strokeWidth: "2",
84
+ strokeLinecap: "round",
85
+ strokeLinejoin: "round"
86
+ }
87
+ )
88
+ ] });
89
+ }
90
+ function SpinnerIcon() {
91
+ return /* @__PURE__ */ jsxs(
92
+ "svg",
93
+ {
94
+ width: "14",
95
+ height: "14",
96
+ viewBox: "0 0 24 24",
97
+ fill: "none",
98
+ style: {
99
+ animation: "uilint-spin 1s linear infinite"
100
+ },
101
+ children: [
102
+ /* @__PURE__ */ jsx(
103
+ "path",
104
+ {
105
+ d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83",
106
+ stroke: "currentColor",
107
+ strokeWidth: "2",
108
+ strokeLinecap: "round",
109
+ opacity: "0.3"
110
+ }
111
+ ),
112
+ /* @__PURE__ */ jsx(
113
+ "path",
114
+ {
115
+ d: "M12 2v4",
116
+ stroke: "currentColor",
117
+ strokeWidth: "2",
118
+ strokeLinecap: "round"
119
+ }
120
+ )
121
+ ]
122
+ }
123
+ );
124
+ }
125
+ function EyeIcon() {
126
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
127
+ /* @__PURE__ */ jsx(
128
+ "path",
129
+ {
130
+ d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z",
131
+ stroke: "currentColor",
132
+ strokeWidth: "2",
133
+ strokeLinecap: "round",
134
+ strokeLinejoin: "round"
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3", stroke: "currentColor", strokeWidth: "2" })
138
+ ] });
139
+ }
140
+ function EyeOffIcon() {
141
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: [
142
+ /* @__PURE__ */ jsx(
143
+ "path",
144
+ {
145
+ d: "M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24",
146
+ stroke: "currentColor",
147
+ strokeWidth: "2",
148
+ strokeLinecap: "round",
149
+ strokeLinejoin: "round"
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsx(
153
+ "path",
154
+ {
155
+ d: "M1 1l22 22",
156
+ stroke: "currentColor",
157
+ strokeWidth: "2",
158
+ strokeLinecap: "round",
159
+ strokeLinejoin: "round"
160
+ }
161
+ )
162
+ ] });
163
+ }
164
+ function WarningIcon() {
165
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
166
+ "path",
167
+ {
168
+ d: "M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z",
169
+ stroke: "currentColor",
170
+ strokeWidth: "2",
171
+ strokeLinecap: "round",
172
+ strokeLinejoin: "round"
173
+ }
174
+ ) });
175
+ }
176
+ function ChevronIcon() {
177
+ return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
178
+ "path",
179
+ {
180
+ d: "M9 6l6 6-6 6",
181
+ stroke: "currentColor",
182
+ strokeWidth: "2",
183
+ strokeLinecap: "round",
184
+ strokeLinejoin: "round"
185
+ }
186
+ ) });
187
+ }
188
+
189
+ // src/components/ui-lint/SettingsPopover.tsx
190
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
191
+ function SettingsPopover({ settings }) {
192
+ const wsConnected = useUILintStore((s) => s.wsConnected);
193
+ const wsUrl = useUILintStore((s) => s.wsUrl);
194
+ const connectWebSocket = useUILintStore(
195
+ (s) => s.connectWebSocket
196
+ );
197
+ const disconnectWebSocket = useUILintStore(
198
+ (s) => s.disconnectWebSocket
199
+ );
200
+ return /* @__PURE__ */ jsxs2(
201
+ "div",
202
+ {
203
+ style: {
204
+ position: "absolute",
205
+ bottom: "100%",
206
+ left: 0,
207
+ marginBottom: "8px",
208
+ width: "260px",
209
+ padding: "14px",
210
+ borderRadius: STYLES.popoverRadius,
211
+ border: `1px solid ${STYLES.border}`,
212
+ backgroundColor: STYLES.bgPopover,
213
+ backdropFilter: STYLES.blur,
214
+ WebkitBackdropFilter: STYLES.blur,
215
+ boxShadow: STYLES.shadowLg,
216
+ animation: "uilint-fade-in 0.15s ease-out"
217
+ },
218
+ children: [
219
+ /* @__PURE__ */ jsx2(
220
+ "div",
221
+ {
222
+ style: {
223
+ fontSize: "12px",
224
+ fontWeight: 600,
225
+ color: STYLES.text,
226
+ marginBottom: "12px"
227
+ },
228
+ children: "UILint Settings"
229
+ }
230
+ ),
231
+ /* @__PURE__ */ jsxs2(
232
+ "div",
233
+ {
234
+ style: {
235
+ padding: "10px 12px",
236
+ borderRadius: STYLES.buttonRadius,
237
+ border: `1px solid ${STYLES.border}`,
238
+ backgroundColor: STYLES.bgSegment
239
+ },
240
+ children: [
241
+ /* @__PURE__ */ jsx2(
242
+ "div",
243
+ {
244
+ style: {
245
+ display: "flex",
246
+ alignItems: "center",
247
+ justifyContent: "space-between",
248
+ marginBottom: "8px"
249
+ },
250
+ children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
251
+ /* @__PURE__ */ jsx2(
252
+ "div",
253
+ {
254
+ style: {
255
+ width: "8px",
256
+ height: "8px",
257
+ borderRadius: "50%",
258
+ backgroundColor: wsConnected ? STYLES.success : STYLES.error,
259
+ boxShadow: wsConnected ? `0 0 8px ${STYLES.success}` : `0 0 8px ${STYLES.error}`
260
+ }
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsx2(
264
+ "span",
265
+ {
266
+ style: {
267
+ fontSize: "12px",
268
+ fontWeight: 600,
269
+ color: wsConnected ? STYLES.success : STYLES.error
270
+ },
271
+ children: wsConnected ? "Connected" : "Disconnected"
272
+ }
273
+ )
274
+ ] })
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsx2(
278
+ "div",
279
+ {
280
+ style: {
281
+ fontSize: "10px",
282
+ color: STYLES.textDim,
283
+ fontFamily: STYLES.fontMono,
284
+ wordBreak: "break-all",
285
+ marginBottom: "10px"
286
+ },
287
+ children: wsUrl
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "6px" }, children: [
291
+ /* @__PURE__ */ jsx2(
292
+ "button",
293
+ {
294
+ onClick: () => connectWebSocket(wsUrl),
295
+ style: {
296
+ flex: 1,
297
+ padding: "6px 10px",
298
+ borderRadius: "6px",
299
+ border: `1px solid ${STYLES.border}`,
300
+ backgroundColor: wsConnected ? "transparent" : STYLES.accent,
301
+ color: wsConnected ? STYLES.textMuted : "#FFFFFF",
302
+ fontSize: "11px",
303
+ fontWeight: 600,
304
+ cursor: "pointer",
305
+ transition: STYLES.transitionFast
306
+ },
307
+ title: "Reconnect to WebSocket server",
308
+ children: wsConnected ? "Reconnect" : "Connect"
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsx2(
312
+ "button",
313
+ {
314
+ onClick: () => disconnectWebSocket(),
315
+ disabled: !wsConnected,
316
+ style: {
317
+ padding: "6px 10px",
318
+ borderRadius: "6px",
319
+ border: `1px solid ${STYLES.border}`,
320
+ backgroundColor: "transparent",
321
+ color: wsConnected ? STYLES.textMuted : STYLES.textDim,
322
+ fontSize: "11px",
323
+ fontWeight: 600,
324
+ cursor: wsConnected ? "pointer" : "not-allowed",
325
+ opacity: wsConnected ? 1 : 0.5,
326
+ transition: STYLES.transitionFast
327
+ },
328
+ title: "Disconnect from WebSocket server",
329
+ children: "Disconnect"
330
+ }
331
+ )
332
+ ] })
333
+ ]
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsxs2(
337
+ "div",
338
+ {
339
+ style: {
340
+ marginTop: "12px",
341
+ fontSize: "11px",
342
+ color: STYLES.textMuted,
343
+ lineHeight: 1.5
344
+ },
345
+ children: [
346
+ /* @__PURE__ */ jsx2("strong", { style: { color: STYLES.text }, children: "\u2325+Click" }),
347
+ " any element to open the inspector sidebar"
348
+ ]
349
+ }
350
+ )
351
+ ]
352
+ }
353
+ );
354
+ }
355
+
356
+ // src/components/ui-lint/ScanPanelStack.tsx
357
+ import { useRef, useEffect } from "react";
358
+
359
+ // src/components/ui-lint/ScanResultsPopover.tsx
360
+ import { useState, useCallback, useMemo } from "react";
361
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
362
+ function getDisambiguatedName(path, allPaths) {
363
+ const parts = path.split("/");
364
+ const fileName = parts[parts.length - 1] || path;
365
+ const duplicates = allPaths.filter((p) => {
366
+ const pParts = p.split("/");
367
+ return pParts[pParts.length - 1] === fileName && p !== path;
368
+ });
369
+ if (duplicates.length === 0) {
370
+ return fileName;
371
+ }
372
+ if (parts.length >= 2) {
373
+ return `${parts[parts.length - 2]}/${fileName}`;
374
+ }
375
+ return fileName;
376
+ }
377
+ function ScanResultsPopover() {
378
+ const { autoScanState, liveScanEnabled, disableLiveScan } = useUILintContext();
379
+ const elementIssuesCache = useUILintStore(
380
+ (s) => s.elementIssuesCache
381
+ );
382
+ const setLocatorTarget = useUILintStore(
383
+ (s) => s.setLocatorTarget
384
+ );
385
+ const setInspectedElement = useUILintStore(
386
+ (s) => s.setInspectedElement
387
+ );
388
+ const [expandedFiles, setExpandedFiles] = useState(/* @__PURE__ */ new Set());
389
+ const isScanning = autoScanState.status === "scanning";
390
+ const isComplete = autoScanState.status === "complete";
391
+ const filesWithIssues = useMemo(() => {
392
+ const sourceFiles = groupBySourceFile(autoScanState.elements);
393
+ const allPaths = sourceFiles.map((sf) => sf.path);
394
+ const result = [];
395
+ for (const sf of sourceFiles) {
396
+ const elementsWithIssues = [];
397
+ for (const el of sf.elements) {
398
+ const cached = elementIssuesCache.get(el.id);
399
+ const issueCount = cached?.issues.length || 0;
400
+ if (issueCount > 0) {
401
+ elementsWithIssues.push({ element: el, issueCount });
402
+ }
403
+ }
404
+ if (elementsWithIssues.length > 0) {
405
+ elementsWithIssues.sort(
406
+ (a, b) => a.element.source.lineNumber - b.element.source.lineNumber
407
+ );
408
+ const totalIssues2 = elementsWithIssues.reduce(
409
+ (sum, e) => sum + e.issueCount,
410
+ 0
411
+ );
412
+ result.push({
413
+ path: sf.path,
414
+ displayName: sf.displayName,
415
+ disambiguatedName: getDisambiguatedName(sf.path, allPaths),
416
+ issueCount: totalIssues2,
417
+ elementsWithIssues
418
+ });
419
+ }
420
+ }
421
+ result.sort((a, b) => b.issueCount - a.issueCount);
422
+ return result;
423
+ }, [autoScanState.elements, elementIssuesCache]);
424
+ const totalIssues = filesWithIssues.reduce((sum, f) => sum + f.issueCount, 0);
425
+ const progress = autoScanState.totalElements > 0 ? autoScanState.currentIndex / autoScanState.totalElements * 100 : 0;
426
+ const toggleFile = useCallback((path) => {
427
+ setExpandedFiles((prev) => {
428
+ const next = new Set(prev);
429
+ if (next.has(path)) {
430
+ next.delete(path);
431
+ } else {
432
+ next.add(path);
433
+ }
434
+ return next;
435
+ });
436
+ }, []);
437
+ const handleElementHover = useCallback(
438
+ (element) => {
439
+ if (!element) {
440
+ setLocatorTarget(null);
441
+ return;
442
+ }
443
+ const target = {
444
+ element: element.element,
445
+ source: element.source,
446
+ rect: element.element.getBoundingClientRect()
447
+ };
448
+ setLocatorTarget(target);
449
+ },
450
+ [setLocatorTarget]
451
+ );
452
+ const handleElementClick = useCallback(
453
+ (element) => {
454
+ element.element.scrollIntoView({
455
+ behavior: "smooth",
456
+ inline: "nearest",
457
+ block: "nearest"
458
+ });
459
+ setInspectedElement({
460
+ element: element.element,
461
+ source: element.source,
462
+ rect: element.element.getBoundingClientRect(),
463
+ scannedElementId: element.id
464
+ });
465
+ setLocatorTarget(null);
466
+ },
467
+ [setInspectedElement, setLocatorTarget]
468
+ );
469
+ return /* @__PURE__ */ jsxs3(
470
+ "div",
471
+ {
472
+ "data-ui-lint": true,
473
+ style: {
474
+ position: "absolute",
475
+ bottom: "100%",
476
+ left: 0,
477
+ marginBottom: "8px",
478
+ width: "320px",
479
+ maxHeight: "450px",
480
+ borderRadius: STYLES.popoverRadius,
481
+ border: `1px solid ${STYLES.border}`,
482
+ backgroundColor: STYLES.bgPopover,
483
+ backdropFilter: STYLES.blur,
484
+ WebkitBackdropFilter: STYLES.blur,
485
+ boxShadow: STYLES.shadowLg,
486
+ animation: "uilint-fade-in 0.15s ease-out",
487
+ display: "flex",
488
+ flexDirection: "column",
489
+ overflow: "hidden"
490
+ },
491
+ children: [
492
+ /* @__PURE__ */ jsxs3(
493
+ "div",
494
+ {
495
+ style: {
496
+ padding: "12px 14px",
497
+ borderBottom: `1px solid ${STYLES.border}`
498
+ },
499
+ children: [
500
+ /* @__PURE__ */ jsxs3(
501
+ "div",
502
+ {
503
+ style: {
504
+ display: "flex",
505
+ alignItems: "center",
506
+ justifyContent: "space-between"
507
+ },
508
+ children: [
509
+ /* @__PURE__ */ jsx3(
510
+ "div",
511
+ {
512
+ style: {
513
+ fontSize: "12px",
514
+ fontWeight: 600,
515
+ color: STYLES.text
516
+ },
517
+ children: filesWithIssues.length === 0 ? "No issues found" : `${filesWithIssues.length} ${filesWithIssues.length === 1 ? "file" : "files"} with ${totalIssues} ${totalIssues === 1 ? "issue" : "issues"}`
518
+ }
519
+ ),
520
+ liveScanEnabled && /* @__PURE__ */ jsx3(
521
+ "button",
522
+ {
523
+ onClick: disableLiveScan,
524
+ style: {
525
+ display: "flex",
526
+ alignItems: "center",
527
+ justifyContent: "center",
528
+ width: "24px",
529
+ height: "24px",
530
+ borderRadius: "6px",
531
+ border: `1px solid ${STYLES.border}`,
532
+ backgroundColor: "transparent",
533
+ color: STYLES.textMuted,
534
+ cursor: "pointer",
535
+ transition: STYLES.transitionFast
536
+ },
537
+ title: "Disable live scanning",
538
+ children: /* @__PURE__ */ jsx3(StopIcon, {})
539
+ }
540
+ )
541
+ ]
542
+ }
543
+ ),
544
+ isScanning && /* @__PURE__ */ jsxs3("div", { style: { marginTop: "10px" }, children: [
545
+ /* @__PURE__ */ jsxs3(
546
+ "div",
547
+ {
548
+ style: {
549
+ display: "flex",
550
+ justifyContent: "space-between",
551
+ fontSize: "10px",
552
+ color: STYLES.textMuted,
553
+ marginBottom: "4px"
554
+ },
555
+ children: [
556
+ /* @__PURE__ */ jsx3("span", { children: "Scanning..." }),
557
+ /* @__PURE__ */ jsxs3("span", { children: [
558
+ autoScanState.currentIndex,
559
+ " / ",
560
+ autoScanState.totalElements
561
+ ] })
562
+ ]
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsx3(
566
+ "div",
567
+ {
568
+ style: {
569
+ height: "3px",
570
+ backgroundColor: "rgba(75, 85, 99, 0.5)",
571
+ borderRadius: "2px",
572
+ overflow: "hidden"
573
+ },
574
+ children: /* @__PURE__ */ jsx3(
575
+ "div",
576
+ {
577
+ style: {
578
+ height: "100%",
579
+ width: `${progress}%`,
580
+ backgroundColor: STYLES.accent,
581
+ transition: "width 0.2s ease-out"
582
+ }
583
+ }
584
+ )
585
+ }
586
+ )
587
+ ] })
588
+ ]
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx3(
592
+ "div",
593
+ {
594
+ style: {
595
+ flex: 1,
596
+ overflowY: "auto",
597
+ padding: "4px 0"
598
+ },
599
+ onMouseLeave: () => handleElementHover(null),
600
+ children: filesWithIssues.length === 0 ? /* @__PURE__ */ jsx3(
601
+ "div",
602
+ {
603
+ style: {
604
+ padding: "24px 14px",
605
+ textAlign: "center",
606
+ fontSize: "11px",
607
+ color: STYLES.textMuted
608
+ },
609
+ children: isScanning ? "Scanning page elements..." : isComplete ? /* @__PURE__ */ jsx3("span", { style: { color: STYLES.success }, children: "\u2713 No issues found" }) : "No files scanned"
610
+ }
611
+ ) : filesWithIssues.map((file) => /* @__PURE__ */ jsx3(
612
+ FileRow,
613
+ {
614
+ file,
615
+ isExpanded: expandedFiles.has(file.path),
616
+ onToggle: () => toggleFile(file.path),
617
+ onElementHover: handleElementHover,
618
+ onElementClick: handleElementClick
619
+ },
620
+ file.path
621
+ ))
622
+ }
623
+ )
624
+ ]
625
+ }
626
+ );
627
+ }
628
+ function FileRow({
629
+ file,
630
+ isExpanded,
631
+ onToggle,
632
+ onElementHover,
633
+ onElementClick
634
+ }) {
635
+ return /* @__PURE__ */ jsxs3("div", { children: [
636
+ /* @__PURE__ */ jsxs3(
637
+ "div",
638
+ {
639
+ style: {
640
+ display: "flex",
641
+ alignItems: "center",
642
+ padding: "8px 12px",
643
+ cursor: "pointer",
644
+ transition: STYLES.transitionFast,
645
+ backgroundColor: isExpanded ? "rgba(59, 130, 246, 0.08)" : "transparent"
646
+ },
647
+ title: file.path,
648
+ onClick: onToggle,
649
+ onMouseEnter: (e) => {
650
+ if (!isExpanded) {
651
+ e.currentTarget.style.backgroundColor = "rgba(55, 65, 81, 0.5)";
652
+ }
653
+ },
654
+ onMouseLeave: (e) => {
655
+ e.currentTarget.style.backgroundColor = isExpanded ? "rgba(59, 130, 246, 0.08)" : "transparent";
656
+ },
657
+ children: [
658
+ /* @__PURE__ */ jsx3(
659
+ "span",
660
+ {
661
+ style: {
662
+ display: "flex",
663
+ alignItems: "center",
664
+ justifyContent: "center",
665
+ width: "16px",
666
+ height: "16px",
667
+ marginRight: "6px",
668
+ color: STYLES.textMuted,
669
+ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
670
+ transition: "transform 0.15s ease-out"
671
+ },
672
+ children: /* @__PURE__ */ jsx3(ChevronIcon, {})
673
+ }
674
+ ),
675
+ /* @__PURE__ */ jsx3(
676
+ "span",
677
+ {
678
+ style: {
679
+ fontSize: "12px",
680
+ fontFamily: STYLES.fontMono,
681
+ color: STYLES.text,
682
+ overflow: "hidden",
683
+ textOverflow: "ellipsis",
684
+ whiteSpace: "nowrap",
685
+ flex: 1,
686
+ marginRight: "12px"
687
+ },
688
+ children: file.disambiguatedName
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx3(
692
+ "span",
693
+ {
694
+ style: {
695
+ display: "inline-flex",
696
+ alignItems: "center",
697
+ justifyContent: "center",
698
+ minWidth: "22px",
699
+ height: "18px",
700
+ padding: "0 6px",
701
+ borderRadius: "9px",
702
+ backgroundColor: getStatusColor(file.issueCount),
703
+ color: "#FFFFFF",
704
+ fontSize: "10px",
705
+ fontWeight: 700
706
+ },
707
+ children: file.issueCount
708
+ }
709
+ )
710
+ ]
711
+ }
712
+ ),
713
+ isExpanded && /* @__PURE__ */ jsx3(
714
+ "div",
715
+ {
716
+ style: {
717
+ backgroundColor: "rgba(17, 24, 39, 0.4)",
718
+ borderTop: `1px solid ${STYLES.border}`,
719
+ borderBottom: `1px solid ${STYLES.border}`
720
+ },
721
+ children: file.elementsWithIssues.map((item) => /* @__PURE__ */ jsx3(
722
+ ElementRow,
723
+ {
724
+ item,
725
+ onHover: onElementHover,
726
+ onClick: onElementClick
727
+ },
728
+ item.element.id
729
+ ))
730
+ }
731
+ )
732
+ ] });
733
+ }
734
+ function ElementRow({ item, onHover, onClick }) {
735
+ const { element, issueCount } = item;
736
+ return /* @__PURE__ */ jsxs3(
737
+ "div",
738
+ {
739
+ style: {
740
+ display: "flex",
741
+ alignItems: "center",
742
+ padding: "6px 12px 6px 34px",
743
+ cursor: "pointer",
744
+ transition: STYLES.transitionFast,
745
+ backgroundColor: "transparent"
746
+ },
747
+ onMouseEnter: (e) => {
748
+ e.currentTarget.style.backgroundColor = "rgba(59, 130, 246, 0.15)";
749
+ onHover(element);
750
+ },
751
+ onMouseLeave: (e) => {
752
+ e.currentTarget.style.backgroundColor = "transparent";
753
+ },
754
+ onClick: () => onClick(element),
755
+ children: [
756
+ /* @__PURE__ */ jsxs3(
757
+ "div",
758
+ {
759
+ style: {
760
+ display: "flex",
761
+ alignItems: "center",
762
+ gap: "4px",
763
+ flex: 1,
764
+ marginRight: "12px"
765
+ },
766
+ children: [
767
+ /* @__PURE__ */ jsxs3(
768
+ "span",
769
+ {
770
+ style: {
771
+ fontSize: "11px",
772
+ fontFamily: STYLES.fontMono,
773
+ color: STYLES.accent
774
+ },
775
+ children: [
776
+ "<",
777
+ element.tagName,
778
+ ">"
779
+ ]
780
+ }
781
+ ),
782
+ /* @__PURE__ */ jsxs3(
783
+ "span",
784
+ {
785
+ style: {
786
+ fontSize: "10px",
787
+ color: STYLES.textDim
788
+ },
789
+ children: [
790
+ ":",
791
+ element.source.lineNumber
792
+ ]
793
+ }
794
+ )
795
+ ]
796
+ }
797
+ ),
798
+ /* @__PURE__ */ jsx3(
799
+ "span",
800
+ {
801
+ style: {
802
+ display: "inline-flex",
803
+ alignItems: "center",
804
+ justifyContent: "center",
805
+ minWidth: "18px",
806
+ height: "16px",
807
+ padding: "0 5px",
808
+ borderRadius: "8px",
809
+ backgroundColor: getStatusColor(issueCount),
810
+ color: "#FFFFFF",
811
+ fontSize: "9px",
812
+ fontWeight: 700
813
+ },
814
+ children: issueCount
815
+ }
816
+ )
817
+ ]
818
+ }
819
+ );
820
+ }
821
+
822
+ // src/components/ui-lint/ScanPanelStack.tsx
823
+ import { jsx as jsx4 } from "react/jsx-runtime";
824
+ function ScanPanelStack({ show, onClose }) {
825
+ const containerRef = useRef(null);
826
+ useEffect(() => {
827
+ if (!show) return;
828
+ const handleClickOutside = (e) => {
829
+ const target = e.target;
830
+ if (containerRef.current && !containerRef.current.contains(target)) {
831
+ const isUILintElement = target.closest?.("[data-ui-lint]");
832
+ if (!isUILintElement) {
833
+ onClose();
834
+ }
835
+ }
836
+ };
837
+ const timeoutId = setTimeout(() => {
838
+ document.addEventListener("mousedown", handleClickOutside);
839
+ }, 100);
840
+ return () => {
841
+ clearTimeout(timeoutId);
842
+ document.removeEventListener("mousedown", handleClickOutside);
843
+ };
844
+ }, [show, onClose]);
845
+ useEffect(() => {
846
+ if (!show) return;
847
+ const handleKeyDown = (e) => {
848
+ if (e.key === "Escape") {
849
+ onClose();
850
+ }
851
+ };
852
+ window.addEventListener("keydown", handleKeyDown);
853
+ return () => window.removeEventListener("keydown", handleKeyDown);
854
+ }, [show, onClose]);
855
+ if (!show) return null;
856
+ return /* @__PURE__ */ jsx4("div", { ref: containerRef, style: { position: "relative" }, children: /* @__PURE__ */ jsx4(ScanResultsPopover, {}) });
857
+ }
858
+
859
+ // src/components/ui-lint/UILintToolbar.tsx
860
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
861
+ function UILintToolbar() {
862
+ const {
863
+ settings,
864
+ inspectedElement,
865
+ liveScanEnabled,
866
+ autoScanState,
867
+ enableLiveScan,
868
+ disableLiveScan
869
+ } = useUILintContext();
870
+ const elementIssuesCache = useUILintStore(
871
+ (s) => s.elementIssuesCache
872
+ );
873
+ const [showSettings, setShowSettings] = useState2(false);
874
+ const [showResults, setShowResults] = useState2(false);
875
+ const [mounted, setMounted] = useState2(false);
876
+ const toolbarRef = useRef2(null);
877
+ const settingsRef = useRef2(null);
878
+ useEffect2(() => {
879
+ setMounted(true);
880
+ }, []);
881
+ useEffect2(() => {
882
+ const handleClickOutside = (e) => {
883
+ const target = e.target;
884
+ if (showSettings && settingsRef.current) {
885
+ if (!settingsRef.current.contains(target) && !toolbarRef.current?.contains(target)) {
886
+ setShowSettings(false);
887
+ }
888
+ }
889
+ };
890
+ if (showSettings) {
891
+ document.addEventListener("mousedown", handleClickOutside);
892
+ return () => document.removeEventListener("mousedown", handleClickOutside);
893
+ }
894
+ }, [showSettings]);
895
+ const handleToggleClick = useCallback2(() => {
896
+ if (liveScanEnabled) {
897
+ disableLiveScan();
898
+ setShowResults(false);
899
+ } else {
900
+ enableLiveScan();
901
+ }
902
+ setShowSettings(false);
903
+ }, [liveScanEnabled, enableLiveScan, disableLiveScan]);
904
+ const handleIssuesClick = useCallback2(() => {
905
+ if (!liveScanEnabled) return;
906
+ setShowResults(!showResults);
907
+ setShowSettings(false);
908
+ }, [liveScanEnabled, showResults]);
909
+ const handleSettingsClick = useCallback2(() => {
910
+ setShowSettings(!showSettings);
911
+ setShowResults(false);
912
+ }, [showSettings]);
913
+ if (!mounted) return null;
914
+ const isScanning = autoScanState.status === "scanning";
915
+ const isComplete = autoScanState.status === "complete";
916
+ let totalIssues = 0;
917
+ elementIssuesCache.forEach((el) => {
918
+ totalIssues += el.issues.length;
919
+ });
920
+ const hasIssues = totalIssues > 0;
921
+ const getToggleContent = () => {
922
+ if (isScanning) {
923
+ return /* @__PURE__ */ jsx5(SpinnerIcon, {});
924
+ }
925
+ if (liveScanEnabled) {
926
+ return /* @__PURE__ */ jsx5(EyeIcon, {});
927
+ }
928
+ return /* @__PURE__ */ jsx5(EyeOffIcon, {});
929
+ };
930
+ const getIssuesContent = () => {
931
+ if (!liveScanEnabled) {
932
+ return /* @__PURE__ */ jsx5("span", { style: { opacity: 0.5 }, children: "--" });
933
+ }
934
+ if (isScanning) {
935
+ return /* @__PURE__ */ jsx5("span", { style: { opacity: 0.7 }, children: "..." });
936
+ }
937
+ if (hasIssues) {
938
+ return /* @__PURE__ */ jsxs4("span", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
939
+ /* @__PURE__ */ jsx5(WarningIcon, {}),
940
+ /* @__PURE__ */ jsx5("span", { children: totalIssues })
941
+ ] });
942
+ }
943
+ return /* @__PURE__ */ jsxs4("span", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
944
+ /* @__PURE__ */ jsx5(CheckCircleIcon, {}),
945
+ /* @__PURE__ */ jsx5("span", { children: "0" })
946
+ ] });
947
+ };
948
+ const content = /* @__PURE__ */ jsxs4(
949
+ "div",
950
+ {
951
+ "data-ui-lint": true,
952
+ style: {
953
+ position: "fixed",
954
+ bottom: "70px",
955
+ left: "20px",
956
+ zIndex: 99999,
957
+ fontFamily: STYLES.font
958
+ },
959
+ children: [
960
+ /* @__PURE__ */ jsx5("style", { children: `
961
+ @keyframes uilint-fade-in {
962
+ from { opacity: 0; transform: translateY(8px); }
963
+ to { opacity: 1; transform: translateY(0); }
964
+ }
965
+ @keyframes uilint-spin {
966
+ from { transform: rotate(0deg); }
967
+ to { transform: rotate(360deg); }
968
+ }
969
+ ` }),
970
+ /* @__PURE__ */ jsxs4(
971
+ "div",
972
+ {
973
+ style: {
974
+ textAlign: "center",
975
+ marginBottom: "8px",
976
+ fontSize: "11px",
977
+ color: STYLES.textDim,
978
+ letterSpacing: "0.01em"
979
+ },
980
+ children: [
981
+ /* @__PURE__ */ jsx5("span", { style: { color: STYLES.textMuted }, children: "\u2325+Click" }),
982
+ " to inspect"
983
+ ]
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsxs4(
987
+ "div",
988
+ {
989
+ ref: toolbarRef,
990
+ style: {
991
+ position: "relative",
992
+ display: "inline-flex",
993
+ alignItems: "center",
994
+ height: STYLES.pillHeight,
995
+ borderRadius: STYLES.pillRadius,
996
+ border: `1px solid ${STYLES.border}`,
997
+ backgroundColor: STYLES.bg,
998
+ backdropFilter: STYLES.blur,
999
+ WebkitBackdropFilter: STYLES.blur,
1000
+ boxShadow: STYLES.shadow,
1001
+ overflow: "hidden"
1002
+ },
1003
+ children: [
1004
+ /* @__PURE__ */ jsx5(
1005
+ "button",
1006
+ {
1007
+ onClick: handleToggleClick,
1008
+ style: {
1009
+ display: "flex",
1010
+ alignItems: "center",
1011
+ justifyContent: "center",
1012
+ height: "100%",
1013
+ width: "40px",
1014
+ border: "none",
1015
+ backgroundColor: liveScanEnabled ? STYLES.bgSegmentHover : "transparent",
1016
+ color: liveScanEnabled ? STYLES.accent : STYLES.textMuted,
1017
+ cursor: "pointer",
1018
+ transition: STYLES.transition
1019
+ },
1020
+ onMouseEnter: (e) => {
1021
+ if (!liveScanEnabled) {
1022
+ e.currentTarget.style.backgroundColor = STYLES.bgSegmentHover;
1023
+ e.currentTarget.style.color = STYLES.text;
1024
+ }
1025
+ },
1026
+ onMouseLeave: (e) => {
1027
+ if (!liveScanEnabled) {
1028
+ e.currentTarget.style.backgroundColor = "transparent";
1029
+ e.currentTarget.style.color = STYLES.textMuted;
1030
+ }
1031
+ },
1032
+ title: liveScanEnabled ? "Disable live scanning" : "Enable live scanning",
1033
+ children: getToggleContent()
1034
+ }
1035
+ ),
1036
+ /* @__PURE__ */ jsx5(
1037
+ "div",
1038
+ {
1039
+ style: {
1040
+ width: "1px",
1041
+ height: "20px",
1042
+ backgroundColor: STYLES.divider
1043
+ }
1044
+ }
1045
+ ),
1046
+ /* @__PURE__ */ jsx5(
1047
+ "button",
1048
+ {
1049
+ onClick: handleIssuesClick,
1050
+ disabled: !liveScanEnabled,
1051
+ style: {
1052
+ display: "flex",
1053
+ alignItems: "center",
1054
+ justifyContent: "center",
1055
+ gap: "4px",
1056
+ height: "100%",
1057
+ padding: "0 12px",
1058
+ border: "none",
1059
+ backgroundColor: showResults && liveScanEnabled ? STYLES.bgSegmentHover : "transparent",
1060
+ color: !liveScanEnabled ? STYLES.textDim : hasIssues ? STYLES.warning : isComplete ? STYLES.success : STYLES.text,
1061
+ fontSize: "13px",
1062
+ fontWeight: 500,
1063
+ fontFamily: STYLES.font,
1064
+ cursor: liveScanEnabled ? "pointer" : "default",
1065
+ transition: STYLES.transition,
1066
+ opacity: liveScanEnabled ? 1 : 0.6
1067
+ },
1068
+ onMouseEnter: (e) => {
1069
+ if (liveScanEnabled && !showResults) {
1070
+ e.currentTarget.style.backgroundColor = STYLES.bgSegmentHover;
1071
+ }
1072
+ },
1073
+ onMouseLeave: (e) => {
1074
+ if (liveScanEnabled && !showResults) {
1075
+ e.currentTarget.style.backgroundColor = "transparent";
1076
+ }
1077
+ },
1078
+ title: liveScanEnabled ? `${totalIssues} issues found` : "Enable scanning to see issues",
1079
+ children: getIssuesContent()
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ jsx5(
1083
+ "div",
1084
+ {
1085
+ style: {
1086
+ width: "1px",
1087
+ height: "20px",
1088
+ backgroundColor: STYLES.divider
1089
+ }
1090
+ }
1091
+ ),
1092
+ /* @__PURE__ */ jsx5(
1093
+ "button",
1094
+ {
1095
+ onClick: handleSettingsClick,
1096
+ style: {
1097
+ display: "flex",
1098
+ alignItems: "center",
1099
+ justifyContent: "center",
1100
+ height: "100%",
1101
+ width: "40px",
1102
+ border: "none",
1103
+ backgroundColor: showSettings ? STYLES.bgSegmentHover : "transparent",
1104
+ color: showSettings ? STYLES.text : STYLES.textMuted,
1105
+ cursor: "pointer",
1106
+ transition: STYLES.transition
1107
+ },
1108
+ onMouseEnter: (e) => {
1109
+ if (!showSettings) {
1110
+ e.currentTarget.style.backgroundColor = STYLES.bgSegmentHover;
1111
+ e.currentTarget.style.color = STYLES.text;
1112
+ }
1113
+ },
1114
+ onMouseLeave: (e) => {
1115
+ if (!showSettings) {
1116
+ e.currentTarget.style.backgroundColor = "transparent";
1117
+ e.currentTarget.style.color = STYLES.textMuted;
1118
+ }
1119
+ },
1120
+ title: "Settings",
1121
+ children: /* @__PURE__ */ jsx5(EllipsisIcon, {})
1122
+ }
1123
+ )
1124
+ ]
1125
+ }
1126
+ ),
1127
+ showSettings && /* @__PURE__ */ jsx5("div", { ref: settingsRef, children: /* @__PURE__ */ jsx5(SettingsPopover, { settings }) }),
1128
+ /* @__PURE__ */ jsx5(
1129
+ ScanPanelStack,
1130
+ {
1131
+ show: showResults && liveScanEnabled,
1132
+ onClose: () => setShowResults(false)
1133
+ }
1134
+ )
1135
+ ]
1136
+ }
1137
+ );
1138
+ return createPortal(content, document.body);
1139
+ }
1140
+
1141
+ export {
1142
+ UILintToolbar
1143
+ };