uilint-react 0.1.38 → 0.1.40

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,1628 @@
1
+ "use client";
2
+ import {
3
+ groupBySourceFile,
4
+ useUILintContext,
5
+ useUILintStore
6
+ } from "./chunk-LAL3JTAA.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/SettingsPopover.tsx
64
+ import { jsx, jsxs } from "react/jsx-runtime";
65
+ function SettingsPopover({ settings }) {
66
+ const wsConnected = useUILintStore((s) => s.wsConnected);
67
+ const wsUrl = useUILintStore((s) => s.wsUrl);
68
+ const connectWebSocket = useUILintStore(
69
+ (s) => s.connectWebSocket
70
+ );
71
+ const disconnectWebSocket = useUILintStore(
72
+ (s) => s.disconnectWebSocket
73
+ );
74
+ return /* @__PURE__ */ jsxs(
75
+ "div",
76
+ {
77
+ style: {
78
+ position: "absolute",
79
+ bottom: "100%",
80
+ left: 0,
81
+ marginBottom: "8px",
82
+ width: "260px",
83
+ padding: "14px",
84
+ borderRadius: STYLES.popoverRadius,
85
+ border: `1px solid ${STYLES.border}`,
86
+ backgroundColor: STYLES.bgPopover,
87
+ backdropFilter: STYLES.blur,
88
+ WebkitBackdropFilter: STYLES.blur,
89
+ boxShadow: STYLES.shadowLg,
90
+ animation: "uilint-fade-in 0.15s ease-out"
91
+ },
92
+ children: [
93
+ /* @__PURE__ */ jsx(
94
+ "div",
95
+ {
96
+ style: {
97
+ fontSize: "12px",
98
+ fontWeight: 600,
99
+ color: STYLES.text,
100
+ marginBottom: "12px"
101
+ },
102
+ children: "UILint Settings"
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsxs(
106
+ "div",
107
+ {
108
+ style: {
109
+ padding: "10px 12px",
110
+ borderRadius: STYLES.buttonRadius,
111
+ border: `1px solid ${STYLES.border}`,
112
+ backgroundColor: STYLES.bgSegment
113
+ },
114
+ children: [
115
+ /* @__PURE__ */ jsx(
116
+ "div",
117
+ {
118
+ style: {
119
+ display: "flex",
120
+ alignItems: "center",
121
+ justifyContent: "space-between",
122
+ marginBottom: "8px"
123
+ },
124
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
125
+ /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ style: {
129
+ width: "8px",
130
+ height: "8px",
131
+ borderRadius: "50%",
132
+ backgroundColor: wsConnected ? STYLES.success : STYLES.error,
133
+ boxShadow: wsConnected ? `0 0 8px ${STYLES.success}` : `0 0 8px ${STYLES.error}`
134
+ }
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsx(
138
+ "span",
139
+ {
140
+ style: {
141
+ fontSize: "12px",
142
+ fontWeight: 600,
143
+ color: wsConnected ? STYLES.success : STYLES.error
144
+ },
145
+ children: wsConnected ? "Connected" : "Disconnected"
146
+ }
147
+ )
148
+ ] })
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx(
152
+ "div",
153
+ {
154
+ style: {
155
+ fontSize: "10px",
156
+ color: STYLES.textDim,
157
+ fontFamily: STYLES.fontMono,
158
+ wordBreak: "break-all",
159
+ marginBottom: "10px"
160
+ },
161
+ children: wsUrl
162
+ }
163
+ ),
164
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "6px" }, children: [
165
+ /* @__PURE__ */ jsx(
166
+ "button",
167
+ {
168
+ onClick: () => connectWebSocket(wsUrl),
169
+ style: {
170
+ flex: 1,
171
+ padding: "6px 10px",
172
+ borderRadius: "6px",
173
+ border: `1px solid ${STYLES.border}`,
174
+ backgroundColor: wsConnected ? "transparent" : STYLES.accent,
175
+ color: wsConnected ? STYLES.textMuted : "#FFFFFF",
176
+ fontSize: "11px",
177
+ fontWeight: 600,
178
+ cursor: "pointer",
179
+ transition: STYLES.transitionFast
180
+ },
181
+ title: "Reconnect to WebSocket server",
182
+ children: wsConnected ? "Reconnect" : "Connect"
183
+ }
184
+ ),
185
+ /* @__PURE__ */ jsx(
186
+ "button",
187
+ {
188
+ onClick: () => disconnectWebSocket(),
189
+ disabled: !wsConnected,
190
+ style: {
191
+ padding: "6px 10px",
192
+ borderRadius: "6px",
193
+ border: `1px solid ${STYLES.border}`,
194
+ backgroundColor: "transparent",
195
+ color: wsConnected ? STYLES.textMuted : STYLES.textDim,
196
+ fontSize: "11px",
197
+ fontWeight: 600,
198
+ cursor: wsConnected ? "pointer" : "not-allowed",
199
+ opacity: wsConnected ? 1 : 0.5,
200
+ transition: STYLES.transitionFast
201
+ },
202
+ title: "Disconnect from WebSocket server",
203
+ children: "Disconnect"
204
+ }
205
+ )
206
+ ] })
207
+ ]
208
+ }
209
+ ),
210
+ /* @__PURE__ */ jsxs(
211
+ "div",
212
+ {
213
+ style: {
214
+ marginTop: "12px",
215
+ fontSize: "11px",
216
+ color: STYLES.textMuted,
217
+ lineHeight: 1.5
218
+ },
219
+ children: [
220
+ /* @__PURE__ */ jsx("strong", { style: { color: STYLES.text }, children: "\u2325+Click" }),
221
+ " any element to open the inspector sidebar"
222
+ ]
223
+ }
224
+ )
225
+ ]
226
+ }
227
+ );
228
+ }
229
+
230
+ // src/components/ui-lint/ScanPanelStack.tsx
231
+ import { useRef, useEffect } from "react";
232
+
233
+ // src/components/ui-lint/ScanResultsPopover.tsx
234
+ import { useState, useCallback, useMemo } from "react";
235
+
236
+ // src/components/ui-lint/toolbar-icons.tsx
237
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
238
+ function StopIcon() {
239
+ return /* @__PURE__ */ jsx2("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx2("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1", fill: "currentColor" }) });
240
+ }
241
+ function ChevronIcon() {
242
+ return /* @__PURE__ */ jsx2("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx2(
243
+ "path",
244
+ {
245
+ d: "M9 6l6 6-6 6",
246
+ stroke: "currentColor",
247
+ strokeWidth: "2",
248
+ strokeLinecap: "round",
249
+ strokeLinejoin: "round"
250
+ }
251
+ ) });
252
+ }
253
+
254
+ // src/components/ui-lint/ScanResultsPopover.tsx
255
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
256
+ function getDisambiguatedName(path, allPaths) {
257
+ const parts = path.split("/");
258
+ const fileName = parts[parts.length - 1] || path;
259
+ const duplicates = allPaths.filter((p) => {
260
+ const pParts = p.split("/");
261
+ return pParts[pParts.length - 1] === fileName && p !== path;
262
+ });
263
+ if (duplicates.length === 0) {
264
+ return fileName;
265
+ }
266
+ if (parts.length >= 2) {
267
+ return `${parts[parts.length - 2]}/${fileName}`;
268
+ }
269
+ return fileName;
270
+ }
271
+ function ScanResultsPopover() {
272
+ const { autoScanState, liveScanEnabled, disableLiveScan } = useUILintContext();
273
+ const elementIssuesCache = useUILintStore(
274
+ (s) => s.elementIssuesCache
275
+ );
276
+ const setLocatorTarget = useUILintStore(
277
+ (s) => s.setLocatorTarget
278
+ );
279
+ const setInspectedElement = useUILintStore(
280
+ (s) => s.setInspectedElement
281
+ );
282
+ const [expandedFiles, setExpandedFiles] = useState(/* @__PURE__ */ new Set());
283
+ const [searchQuery, setSearchQuery] = useState("");
284
+ const isScanning = autoScanState.status === "scanning";
285
+ const isComplete = autoScanState.status === "complete";
286
+ const allFilesWithIssues = useMemo(() => {
287
+ const sourceFiles = groupBySourceFile(autoScanState.elements);
288
+ const allPaths = sourceFiles.map((sf) => sf.path);
289
+ const result = [];
290
+ for (const sf of sourceFiles) {
291
+ const elementsWithIssues = [];
292
+ for (const el of sf.elements) {
293
+ const cached = elementIssuesCache.get(el.id);
294
+ const issueCount = cached?.issues.length || 0;
295
+ if (issueCount > 0) {
296
+ const ruleIds = Array.from(
297
+ new Set(
298
+ (cached?.issues || []).map((i) => i.ruleId).filter((r) => typeof r === "string")
299
+ )
300
+ );
301
+ elementsWithIssues.push({ element: el, issueCount, ruleIds });
302
+ }
303
+ }
304
+ if (elementsWithIssues.length > 0) {
305
+ elementsWithIssues.sort(
306
+ (a, b) => a.element.source.lineNumber - b.element.source.lineNumber
307
+ );
308
+ const totalIssues2 = elementsWithIssues.reduce(
309
+ (sum, e) => sum + e.issueCount,
310
+ 0
311
+ );
312
+ result.push({
313
+ path: sf.path,
314
+ displayName: sf.displayName,
315
+ disambiguatedName: getDisambiguatedName(sf.path, allPaths),
316
+ issueCount: totalIssues2,
317
+ elementsWithIssues
318
+ });
319
+ }
320
+ }
321
+ result.sort((a, b) => b.issueCount - a.issueCount);
322
+ return result;
323
+ }, [autoScanState.elements, elementIssuesCache]);
324
+ const filesWithIssues = useMemo(() => {
325
+ if (!searchQuery.trim()) {
326
+ return allFilesWithIssues;
327
+ }
328
+ const raw = searchQuery.toLowerCase().trim();
329
+ const tagQuery = raw.replace(/[<>]/g, "").replace("/", "").trim();
330
+ const query = raw;
331
+ return allFilesWithIssues.map((file) => {
332
+ const fileMatches = file.path.toLowerCase().includes(query) || file.displayName.toLowerCase().includes(query) || file.disambiguatedName.toLowerCase().includes(query);
333
+ const matchingElements = file.elementsWithIssues.filter((item) => {
334
+ const tagMatches = item.element.tagName.toLowerCase().includes(tagQuery);
335
+ const ruleMatches = item.ruleIds.some(
336
+ (r) => r.toLowerCase().includes(query)
337
+ );
338
+ return tagMatches || ruleMatches;
339
+ });
340
+ if (fileMatches) {
341
+ return file;
342
+ }
343
+ if (matchingElements.length > 0) {
344
+ const filteredIssueCount = matchingElements.reduce(
345
+ (sum, e) => sum + e.issueCount,
346
+ 0
347
+ );
348
+ return {
349
+ ...file,
350
+ elementsWithIssues: matchingElements,
351
+ issueCount: filteredIssueCount
352
+ };
353
+ }
354
+ return null;
355
+ }).filter((file) => file !== null);
356
+ }, [allFilesWithIssues, searchQuery]);
357
+ const totalIssues = filesWithIssues.reduce((sum, f) => sum + f.issueCount, 0);
358
+ const totalAllIssues = allFilesWithIssues.reduce(
359
+ (sum, f) => sum + f.issueCount,
360
+ 0
361
+ );
362
+ const progress = autoScanState.totalElements > 0 ? autoScanState.currentIndex / autoScanState.totalElements * 100 : 0;
363
+ const toggleFile = useCallback((path) => {
364
+ setExpandedFiles((prev) => {
365
+ const next = new Set(prev);
366
+ if (next.has(path)) {
367
+ next.delete(path);
368
+ } else {
369
+ next.add(path);
370
+ }
371
+ return next;
372
+ });
373
+ }, []);
374
+ const handleElementHover = useCallback(
375
+ (element) => {
376
+ if (!element) {
377
+ setLocatorTarget(null);
378
+ return;
379
+ }
380
+ const target = {
381
+ element: element.element,
382
+ source: element.source,
383
+ rect: element.element.getBoundingClientRect()
384
+ };
385
+ setLocatorTarget(target);
386
+ },
387
+ [setLocatorTarget]
388
+ );
389
+ const handleElementClick = useCallback(
390
+ (element) => {
391
+ element.element.scrollIntoView({
392
+ behavior: "smooth",
393
+ inline: "nearest",
394
+ block: "nearest"
395
+ });
396
+ setInspectedElement({
397
+ element: element.element,
398
+ source: element.source,
399
+ rect: element.element.getBoundingClientRect(),
400
+ scannedElementId: element.id
401
+ });
402
+ setLocatorTarget(null);
403
+ },
404
+ [setInspectedElement, setLocatorTarget]
405
+ );
406
+ return /* @__PURE__ */ jsxs3(
407
+ "div",
408
+ {
409
+ "data-ui-lint": true,
410
+ style: {
411
+ position: "absolute",
412
+ bottom: "100%",
413
+ left: 0,
414
+ marginBottom: "8px",
415
+ width: "320px",
416
+ maxHeight: "450px",
417
+ borderRadius: STYLES.popoverRadius,
418
+ border: `1px solid ${STYLES.border}`,
419
+ backgroundColor: STYLES.bgPopover,
420
+ backdropFilter: STYLES.blur,
421
+ WebkitBackdropFilter: STYLES.blur,
422
+ boxShadow: STYLES.shadowLg,
423
+ animation: "uilint-fade-in 0.15s ease-out",
424
+ display: "flex",
425
+ flexDirection: "column",
426
+ overflow: "hidden"
427
+ },
428
+ children: [
429
+ /* @__PURE__ */ jsxs3(
430
+ "div",
431
+ {
432
+ style: {
433
+ padding: "12px 14px",
434
+ borderBottom: `1px solid ${STYLES.border}`
435
+ },
436
+ children: [
437
+ /* @__PURE__ */ jsxs3(
438
+ "div",
439
+ {
440
+ style: {
441
+ display: "flex",
442
+ alignItems: "center",
443
+ justifyContent: "space-between"
444
+ },
445
+ children: [
446
+ /* @__PURE__ */ jsx3(
447
+ "div",
448
+ {
449
+ style: {
450
+ fontSize: "12px",
451
+ fontWeight: 600,
452
+ color: STYLES.text
453
+ },
454
+ children: allFilesWithIssues.length === 0 ? "No issues found" : searchQuery ? `${filesWithIssues.length} of ${allFilesWithIssues.length} files` : `${filesWithIssues.length} ${filesWithIssues.length === 1 ? "file" : "files"} with ${totalAllIssues} ${totalAllIssues === 1 ? "issue" : "issues"}`
455
+ }
456
+ ),
457
+ liveScanEnabled && /* @__PURE__ */ jsx3(
458
+ "button",
459
+ {
460
+ onClick: disableLiveScan,
461
+ style: {
462
+ display: "flex",
463
+ alignItems: "center",
464
+ justifyContent: "center",
465
+ width: "24px",
466
+ height: "24px",
467
+ borderRadius: "6px",
468
+ border: `1px solid ${STYLES.border}`,
469
+ backgroundColor: "transparent",
470
+ color: STYLES.textMuted,
471
+ cursor: "pointer",
472
+ transition: STYLES.transitionFast
473
+ },
474
+ title: "Disable live scanning",
475
+ children: /* @__PURE__ */ jsx3(StopIcon, {})
476
+ }
477
+ )
478
+ ]
479
+ }
480
+ ),
481
+ allFilesWithIssues.length > 0 && /* @__PURE__ */ jsx3("div", { style: { marginTop: "10px" }, children: /* @__PURE__ */ jsxs3(
482
+ "div",
483
+ {
484
+ style: {
485
+ display: "flex",
486
+ alignItems: "center",
487
+ gap: "8px",
488
+ padding: "6px 10px",
489
+ backgroundColor: "rgba(17, 24, 39, 0.6)",
490
+ borderRadius: "6px",
491
+ border: `1px solid ${STYLES.border}`
492
+ },
493
+ children: [
494
+ /* @__PURE__ */ jsx3(SearchIcon, {}),
495
+ /* @__PURE__ */ jsx3(
496
+ "input",
497
+ {
498
+ type: "text",
499
+ value: searchQuery,
500
+ onChange: (e) => setSearchQuery(e.target.value),
501
+ placeholder: "Filter by file or tag...",
502
+ style: {
503
+ flex: 1,
504
+ border: "none",
505
+ backgroundColor: "transparent",
506
+ color: STYLES.text,
507
+ fontSize: "11px",
508
+ fontFamily: STYLES.font,
509
+ outline: "none"
510
+ }
511
+ }
512
+ ),
513
+ searchQuery && /* @__PURE__ */ jsx3(
514
+ "button",
515
+ {
516
+ onClick: () => setSearchQuery(""),
517
+ style: {
518
+ display: "flex",
519
+ alignItems: "center",
520
+ justifyContent: "center",
521
+ width: "16px",
522
+ height: "16px",
523
+ borderRadius: "50%",
524
+ border: "none",
525
+ backgroundColor: "rgba(75, 85, 99, 0.5)",
526
+ color: STYLES.textMuted,
527
+ cursor: "pointer",
528
+ padding: 0
529
+ },
530
+ title: "Clear search",
531
+ children: /* @__PURE__ */ jsx3(ClearIcon, {})
532
+ }
533
+ )
534
+ ]
535
+ }
536
+ ) }),
537
+ isScanning && /* @__PURE__ */ jsxs3("div", { style: { marginTop: "10px" }, children: [
538
+ /* @__PURE__ */ jsxs3(
539
+ "div",
540
+ {
541
+ style: {
542
+ display: "flex",
543
+ justifyContent: "space-between",
544
+ fontSize: "10px",
545
+ color: STYLES.textMuted,
546
+ marginBottom: "4px"
547
+ },
548
+ children: [
549
+ /* @__PURE__ */ jsx3("span", { children: "Scanning..." }),
550
+ /* @__PURE__ */ jsxs3("span", { children: [
551
+ autoScanState.currentIndex,
552
+ " / ",
553
+ autoScanState.totalElements
554
+ ] })
555
+ ]
556
+ }
557
+ ),
558
+ /* @__PURE__ */ jsx3(
559
+ "div",
560
+ {
561
+ style: {
562
+ height: "3px",
563
+ backgroundColor: "rgba(75, 85, 99, 0.5)",
564
+ borderRadius: "2px",
565
+ overflow: "hidden"
566
+ },
567
+ children: /* @__PURE__ */ jsx3(
568
+ "div",
569
+ {
570
+ style: {
571
+ height: "100%",
572
+ width: `${progress}%`,
573
+ backgroundColor: STYLES.accent,
574
+ transition: "width 0.2s ease-out"
575
+ }
576
+ }
577
+ )
578
+ }
579
+ )
580
+ ] })
581
+ ]
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsx3(
585
+ "div",
586
+ {
587
+ style: {
588
+ flex: 1,
589
+ overflowY: "auto",
590
+ padding: "4px 0"
591
+ },
592
+ onMouseLeave: () => handleElementHover(null),
593
+ children: filesWithIssues.length === 0 ? /* @__PURE__ */ jsx3(
594
+ "div",
595
+ {
596
+ style: {
597
+ padding: "24px 14px",
598
+ textAlign: "center",
599
+ fontSize: "11px",
600
+ color: STYLES.textMuted
601
+ },
602
+ children: isScanning ? "Scanning page elements..." : searchQuery && allFilesWithIssues.length > 0 ? /* @__PURE__ */ jsxs3("span", { children: [
603
+ 'No matches for "',
604
+ searchQuery,
605
+ '"'
606
+ ] }) : isComplete ? /* @__PURE__ */ jsx3("span", { style: { color: STYLES.success }, children: "\u2713 No issues found" }) : "No files scanned"
607
+ }
608
+ ) : filesWithIssues.map((file) => /* @__PURE__ */ jsx3(
609
+ FileRow,
610
+ {
611
+ file,
612
+ isExpanded: expandedFiles.has(file.path),
613
+ onToggle: () => toggleFile(file.path),
614
+ onElementHover: handleElementHover,
615
+ onElementClick: handleElementClick
616
+ },
617
+ file.path
618
+ ))
619
+ }
620
+ )
621
+ ]
622
+ }
623
+ );
624
+ }
625
+ function FileRow({
626
+ file,
627
+ isExpanded,
628
+ onToggle,
629
+ onElementHover,
630
+ onElementClick
631
+ }) {
632
+ return /* @__PURE__ */ jsxs3("div", { children: [
633
+ /* @__PURE__ */ jsxs3(
634
+ "div",
635
+ {
636
+ style: {
637
+ display: "flex",
638
+ alignItems: "center",
639
+ padding: "8px 12px",
640
+ cursor: "pointer",
641
+ transition: STYLES.transitionFast,
642
+ backgroundColor: isExpanded ? "rgba(59, 130, 246, 0.08)" : "transparent"
643
+ },
644
+ title: file.path,
645
+ onClick: onToggle,
646
+ onMouseEnter: (e) => {
647
+ if (!isExpanded) {
648
+ e.currentTarget.style.backgroundColor = "rgba(55, 65, 81, 0.5)";
649
+ }
650
+ },
651
+ onMouseLeave: (e) => {
652
+ e.currentTarget.style.backgroundColor = isExpanded ? "rgba(59, 130, 246, 0.08)" : "transparent";
653
+ },
654
+ children: [
655
+ /* @__PURE__ */ jsx3(
656
+ "span",
657
+ {
658
+ style: {
659
+ display: "flex",
660
+ alignItems: "center",
661
+ justifyContent: "center",
662
+ width: "16px",
663
+ height: "16px",
664
+ marginRight: "6px",
665
+ color: STYLES.textMuted,
666
+ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
667
+ transition: "transform 0.15s ease-out"
668
+ },
669
+ children: /* @__PURE__ */ jsx3(ChevronIcon, {})
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsx3(
673
+ "span",
674
+ {
675
+ style: {
676
+ fontSize: "12px",
677
+ fontFamily: STYLES.fontMono,
678
+ color: STYLES.text,
679
+ overflow: "hidden",
680
+ textOverflow: "ellipsis",
681
+ whiteSpace: "nowrap",
682
+ flex: 1,
683
+ marginRight: "12px"
684
+ },
685
+ children: file.disambiguatedName
686
+ }
687
+ ),
688
+ /* @__PURE__ */ jsx3(
689
+ "span",
690
+ {
691
+ style: {
692
+ display: "inline-flex",
693
+ alignItems: "center",
694
+ justifyContent: "center",
695
+ minWidth: "22px",
696
+ height: "18px",
697
+ padding: "0 6px",
698
+ borderRadius: "9px",
699
+ backgroundColor: getStatusColor(file.issueCount),
700
+ color: "#FFFFFF",
701
+ fontSize: "10px",
702
+ fontWeight: 700
703
+ },
704
+ children: file.issueCount
705
+ }
706
+ )
707
+ ]
708
+ }
709
+ ),
710
+ isExpanded && /* @__PURE__ */ jsx3(
711
+ "div",
712
+ {
713
+ style: {
714
+ backgroundColor: "rgba(17, 24, 39, 0.4)",
715
+ borderTop: `1px solid ${STYLES.border}`,
716
+ borderBottom: `1px solid ${STYLES.border}`
717
+ },
718
+ children: file.elementsWithIssues.map((item) => /* @__PURE__ */ jsx3(
719
+ ElementRow,
720
+ {
721
+ item,
722
+ onHover: onElementHover,
723
+ onClick: onElementClick
724
+ },
725
+ item.element.id
726
+ ))
727
+ }
728
+ )
729
+ ] });
730
+ }
731
+ function ElementRow({ item, onHover, onClick }) {
732
+ const { element, issueCount } = item;
733
+ return /* @__PURE__ */ jsxs3(
734
+ "div",
735
+ {
736
+ style: {
737
+ display: "flex",
738
+ alignItems: "center",
739
+ padding: "6px 12px 6px 34px",
740
+ cursor: "pointer",
741
+ transition: STYLES.transitionFast,
742
+ backgroundColor: "transparent"
743
+ },
744
+ onMouseEnter: (e) => {
745
+ e.currentTarget.style.backgroundColor = "rgba(59, 130, 246, 0.15)";
746
+ onHover(element);
747
+ },
748
+ onMouseLeave: (e) => {
749
+ e.currentTarget.style.backgroundColor = "transparent";
750
+ },
751
+ onClick: () => onClick(element),
752
+ children: [
753
+ /* @__PURE__ */ jsxs3(
754
+ "div",
755
+ {
756
+ style: {
757
+ display: "flex",
758
+ alignItems: "center",
759
+ gap: "4px",
760
+ flex: 1,
761
+ marginRight: "12px"
762
+ },
763
+ children: [
764
+ /* @__PURE__ */ jsxs3(
765
+ "span",
766
+ {
767
+ style: {
768
+ fontSize: "11px",
769
+ fontFamily: STYLES.fontMono,
770
+ color: STYLES.accent
771
+ },
772
+ children: [
773
+ "<",
774
+ element.tagName,
775
+ ">"
776
+ ]
777
+ }
778
+ ),
779
+ /* @__PURE__ */ jsxs3(
780
+ "span",
781
+ {
782
+ style: {
783
+ fontSize: "10px",
784
+ color: STYLES.textDim
785
+ },
786
+ children: [
787
+ ":",
788
+ element.source.lineNumber
789
+ ]
790
+ }
791
+ )
792
+ ]
793
+ }
794
+ ),
795
+ /* @__PURE__ */ jsx3(
796
+ "span",
797
+ {
798
+ style: {
799
+ display: "inline-flex",
800
+ alignItems: "center",
801
+ justifyContent: "center",
802
+ minWidth: "18px",
803
+ height: "16px",
804
+ padding: "0 5px",
805
+ borderRadius: "8px",
806
+ backgroundColor: getStatusColor(issueCount),
807
+ color: "#FFFFFF",
808
+ fontSize: "9px",
809
+ fontWeight: 700
810
+ },
811
+ children: issueCount
812
+ }
813
+ )
814
+ ]
815
+ }
816
+ );
817
+ }
818
+ function SearchIcon() {
819
+ return /* @__PURE__ */ jsxs3(
820
+ "svg",
821
+ {
822
+ width: "12",
823
+ height: "12",
824
+ viewBox: "0 0 24 24",
825
+ fill: "none",
826
+ style: { flexShrink: 0 },
827
+ children: [
828
+ /* @__PURE__ */ jsx3(
829
+ "circle",
830
+ {
831
+ cx: "11",
832
+ cy: "11",
833
+ r: "7",
834
+ stroke: STYLES.textMuted,
835
+ strokeWidth: "2",
836
+ strokeLinecap: "round"
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx3(
840
+ "path",
841
+ {
842
+ d: "M21 21l-4.35-4.35",
843
+ stroke: STYLES.textMuted,
844
+ strokeWidth: "2",
845
+ strokeLinecap: "round"
846
+ }
847
+ )
848
+ ]
849
+ }
850
+ );
851
+ }
852
+ function ClearIcon() {
853
+ return /* @__PURE__ */ jsx3("svg", { width: "8", height: "8", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx3(
854
+ "path",
855
+ {
856
+ d: "M18 6L6 18M6 6l12 12",
857
+ stroke: "currentColor",
858
+ strokeWidth: "3",
859
+ strokeLinecap: "round",
860
+ strokeLinejoin: "round"
861
+ }
862
+ ) });
863
+ }
864
+
865
+ // src/components/ui-lint/ScanPanelStack.tsx
866
+ import { jsx as jsx4 } from "react/jsx-runtime";
867
+ function ScanPanelStack({ show, onClose }) {
868
+ const containerRef = useRef(null);
869
+ useEffect(() => {
870
+ if (!show) return;
871
+ const handleClickOutside = (e) => {
872
+ const target = e.target;
873
+ if (containerRef.current && !containerRef.current.contains(target)) {
874
+ const isUILintElement = target.closest?.("[data-ui-lint]");
875
+ if (!isUILintElement) {
876
+ onClose();
877
+ }
878
+ }
879
+ };
880
+ const timeoutId = setTimeout(() => {
881
+ document.addEventListener("mousedown", handleClickOutside);
882
+ }, 100);
883
+ return () => {
884
+ clearTimeout(timeoutId);
885
+ document.removeEventListener("mousedown", handleClickOutside);
886
+ };
887
+ }, [show, onClose]);
888
+ useEffect(() => {
889
+ if (!show) return;
890
+ const handleKeyDown = (e) => {
891
+ if (e.key === "Escape") {
892
+ onClose();
893
+ }
894
+ };
895
+ window.addEventListener("keydown", handleKeyDown);
896
+ return () => window.removeEventListener("keydown", handleKeyDown);
897
+ }, [show, onClose]);
898
+ if (!show) return null;
899
+ return /* @__PURE__ */ jsx4("div", { ref: containerRef, style: { position: "relative" }, children: /* @__PURE__ */ jsx4(ScanResultsPopover, {}) });
900
+ }
901
+
902
+ // src/components/ui-lint/UILintToolbar.tsx
903
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
904
+ var TOKENS = {
905
+ // Colors
906
+ bgBase: "rgba(15, 15, 15, 0.85)",
907
+ bgElevated: "rgba(25, 25, 25, 0.95)",
908
+ bgHover: "rgba(255, 255, 255, 0.08)",
909
+ bgActive: "rgba(255, 255, 255, 0.12)",
910
+ border: "rgba(255, 255, 255, 0.1)",
911
+ borderFocus: "rgba(99, 179, 237, 0.6)",
912
+ textPrimary: "rgba(255, 255, 255, 0.95)",
913
+ textSecondary: "rgba(255, 255, 255, 0.7)",
914
+ textMuted: "rgba(255, 255, 255, 0.4)",
915
+ textDisabled: "rgba(255, 255, 255, 0.25)",
916
+ accent: "#63b3ed",
917
+ // Calm blue
918
+ success: "#68d391",
919
+ // Soft green
920
+ warning: "#f6ad55",
921
+ // Warm orange
922
+ // Typography
923
+ fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,
924
+ fontMono: `"SF Mono", Monaco, "Cascadia Code", monospace`,
925
+ // Sizing
926
+ pillHeight: "44px",
927
+ // Touch-friendly
928
+ pillRadius: "22px",
929
+ buttonMinWidth: "44px",
930
+ // Touch target minimum
931
+ // Effects
932
+ blur: "blur(20px)",
933
+ shadowSm: "0 2px 8px rgba(0, 0, 0, 0.3)",
934
+ shadowMd: "0 4px 20px rgba(0, 0, 0, 0.4)",
935
+ shadowGlow: (color) => `0 0 20px ${color}`,
936
+ // Animation
937
+ transitionFast: "150ms cubic-bezier(0.4, 0, 0.2, 1)",
938
+ transitionBase: "200ms cubic-bezier(0.4, 0, 0.2, 1)",
939
+ transitionSlow: "300ms cubic-bezier(0.4, 0, 0.2, 1)"
940
+ };
941
+ var Icons = {
942
+ Eye: () => /* @__PURE__ */ jsxs4(
943
+ "svg",
944
+ {
945
+ width: "18",
946
+ height: "18",
947
+ viewBox: "0 0 24 24",
948
+ fill: "none",
949
+ stroke: "currentColor",
950
+ strokeWidth: "2",
951
+ strokeLinecap: "round",
952
+ strokeLinejoin: "round",
953
+ children: [
954
+ /* @__PURE__ */ jsx5("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
955
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "3" })
956
+ ]
957
+ }
958
+ ),
959
+ EyeOff: () => /* @__PURE__ */ jsxs4(
960
+ "svg",
961
+ {
962
+ width: "18",
963
+ height: "18",
964
+ viewBox: "0 0 24 24",
965
+ fill: "none",
966
+ stroke: "currentColor",
967
+ strokeWidth: "2",
968
+ strokeLinecap: "round",
969
+ strokeLinejoin: "round",
970
+ children: [
971
+ /* @__PURE__ */ jsx5("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" }),
972
+ /* @__PURE__ */ jsx5("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
973
+ ]
974
+ }
975
+ ),
976
+ Scan: () => /* @__PURE__ */ jsxs4(
977
+ "svg",
978
+ {
979
+ width: "16",
980
+ height: "16",
981
+ viewBox: "0 0 24 24",
982
+ fill: "none",
983
+ stroke: "currentColor",
984
+ strokeWidth: "2",
985
+ strokeLinecap: "round",
986
+ strokeLinejoin: "round",
987
+ children: [
988
+ /* @__PURE__ */ jsx5("path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }),
989
+ /* @__PURE__ */ jsx5("path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }),
990
+ /* @__PURE__ */ jsx5("path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }),
991
+ /* @__PURE__ */ jsx5("path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }),
992
+ /* @__PURE__ */ jsx5("line", { x1: "7", y1: "12", x2: "17", y2: "12" })
993
+ ]
994
+ }
995
+ ),
996
+ Check: () => /* @__PURE__ */ jsx5(
997
+ "svg",
998
+ {
999
+ width: "14",
1000
+ height: "14",
1001
+ viewBox: "0 0 24 24",
1002
+ fill: "none",
1003
+ stroke: "currentColor",
1004
+ strokeWidth: "2.5",
1005
+ strokeLinecap: "round",
1006
+ strokeLinejoin: "round",
1007
+ children: /* @__PURE__ */ jsx5("polyline", { points: "20 6 9 17 4 12" })
1008
+ }
1009
+ ),
1010
+ AlertTriangle: () => /* @__PURE__ */ jsxs4(
1011
+ "svg",
1012
+ {
1013
+ width: "14",
1014
+ height: "14",
1015
+ viewBox: "0 0 24 24",
1016
+ fill: "none",
1017
+ stroke: "currentColor",
1018
+ strokeWidth: "2",
1019
+ strokeLinecap: "round",
1020
+ strokeLinejoin: "round",
1021
+ children: [
1022
+ /* @__PURE__ */ jsx5("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
1023
+ /* @__PURE__ */ jsx5("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
1024
+ /* @__PURE__ */ jsx5("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
1025
+ ]
1026
+ }
1027
+ ),
1028
+ Settings: () => /* @__PURE__ */ jsxs4(
1029
+ "svg",
1030
+ {
1031
+ width: "16",
1032
+ height: "16",
1033
+ viewBox: "0 0 24 24",
1034
+ fill: "none",
1035
+ stroke: "currentColor",
1036
+ strokeWidth: "2",
1037
+ strokeLinecap: "round",
1038
+ strokeLinejoin: "round",
1039
+ children: [
1040
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "3" }),
1041
+ /* @__PURE__ */ jsx5("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" })
1042
+ ]
1043
+ }
1044
+ ),
1045
+ ChevronRight: () => /* @__PURE__ */ jsx5(
1046
+ "svg",
1047
+ {
1048
+ width: "14",
1049
+ height: "14",
1050
+ viewBox: "0 0 24 24",
1051
+ fill: "none",
1052
+ stroke: "currentColor",
1053
+ strokeWidth: "2",
1054
+ strokeLinecap: "round",
1055
+ strokeLinejoin: "round",
1056
+ children: /* @__PURE__ */ jsx5("polyline", { points: "9 18 15 12 9 6" })
1057
+ }
1058
+ )
1059
+ };
1060
+ var globalStyles = `
1061
+ @keyframes uilint-fade-in {
1062
+ from { opacity: 0; transform: translateY(8px) scale(0.98); }
1063
+ to { opacity: 1; transform: translateY(0) scale(1); }
1064
+ }
1065
+
1066
+ @keyframes uilint-fade-out {
1067
+ from { opacity: 1; transform: translateY(0) scale(1); }
1068
+ to { opacity: 0; transform: translateY(8px) scale(0.98); }
1069
+ }
1070
+
1071
+ @keyframes uilint-pulse {
1072
+ 0%, 100% { opacity: 1; }
1073
+ 50% { opacity: 0.5; }
1074
+ }
1075
+
1076
+ @keyframes uilint-scan-line {
1077
+ 0% { transform: translateX(-100%); }
1078
+ 100% { transform: translateX(100%); }
1079
+ }
1080
+
1081
+ @keyframes uilint-spin {
1082
+ from { transform: rotate(0deg); }
1083
+ to { transform: rotate(360deg); }
1084
+ }
1085
+
1086
+ .uilint-toolbar-btn {
1087
+ display: flex;
1088
+ align-items: center;
1089
+ justify-content: center;
1090
+ height: 100%;
1091
+ border: none;
1092
+ background: transparent;
1093
+ color: ${TOKENS.textSecondary};
1094
+ cursor: pointer;
1095
+ transition:
1096
+ background-color ${TOKENS.transitionFast},
1097
+ color ${TOKENS.transitionFast},
1098
+ transform ${TOKENS.transitionFast};
1099
+ outline: none;
1100
+ position: relative;
1101
+ }
1102
+
1103
+ .uilint-toolbar-btn:hover:not(:disabled) {
1104
+ background: ${TOKENS.bgHover};
1105
+ color: ${TOKENS.textPrimary};
1106
+ }
1107
+
1108
+ .uilint-toolbar-btn:active:not(:disabled) {
1109
+ background: ${TOKENS.bgActive};
1110
+ transform: scale(0.97);
1111
+ }
1112
+
1113
+ .uilint-toolbar-btn:focus-visible {
1114
+ box-shadow: inset 0 0 0 2px ${TOKENS.borderFocus};
1115
+ }
1116
+
1117
+ .uilint-toolbar-btn:disabled {
1118
+ cursor: not-allowed;
1119
+ color: ${TOKENS.textDisabled};
1120
+ }
1121
+
1122
+ .uilint-toolbar-btn--active {
1123
+ background: ${TOKENS.bgActive} !important;
1124
+ color: ${TOKENS.accent} !important;
1125
+ }
1126
+
1127
+ .uilint-toolbar-btn--warning {
1128
+ color: ${TOKENS.warning} !important;
1129
+ }
1130
+
1131
+ .uilint-toolbar-btn--success {
1132
+ color: ${TOKENS.success} !important;
1133
+ }
1134
+
1135
+ .uilint-hint {
1136
+ opacity: 0;
1137
+ transform: translateY(4px);
1138
+ transition:
1139
+ opacity ${TOKENS.transitionBase},
1140
+ transform ${TOKENS.transitionBase};
1141
+ pointer-events: none;
1142
+ }
1143
+
1144
+ .uilint-hint--visible {
1145
+ opacity: 1;
1146
+ transform: translateY(0);
1147
+ }
1148
+
1149
+ .uilint-badge {
1150
+ display: inline-flex;
1151
+ align-items: center;
1152
+ justify-content: center;
1153
+ min-width: 20px;
1154
+ height: 20px;
1155
+ padding: 0 6px;
1156
+ border-radius: 10px;
1157
+ font-size: 11px;
1158
+ font-weight: 600;
1159
+ font-family: ${TOKENS.fontMono};
1160
+ letter-spacing: -0.02em;
1161
+ }
1162
+
1163
+ .uilint-scanning-indicator {
1164
+ position: relative;
1165
+ overflow: hidden;
1166
+ }
1167
+
1168
+ .uilint-scanning-indicator::after {
1169
+ content: '';
1170
+ position: absolute;
1171
+ top: 0;
1172
+ left: 0;
1173
+ right: 0;
1174
+ bottom: 0;
1175
+ background: linear-gradient(
1176
+ 90deg,
1177
+ transparent,
1178
+ rgba(99, 179, 237, 0.3),
1179
+ transparent
1180
+ );
1181
+ animation: uilint-scan-line 1.5s ease-in-out infinite;
1182
+ }
1183
+
1184
+ .uilint-popover {
1185
+ animation: uilint-fade-in ${TOKENS.transitionSlow} forwards;
1186
+ }
1187
+
1188
+ .uilint-popover--closing {
1189
+ animation: uilint-fade-out ${TOKENS.transitionBase} forwards;
1190
+ }
1191
+ `;
1192
+ function ToolbarButton({
1193
+ onClick,
1194
+ disabled,
1195
+ active,
1196
+ variant = "default",
1197
+ title,
1198
+ ariaLabel,
1199
+ width = TOKENS.buttonMinWidth,
1200
+ children
1201
+ }) {
1202
+ const classes = [
1203
+ "uilint-toolbar-btn",
1204
+ active && "uilint-toolbar-btn--active",
1205
+ variant === "warning" && "uilint-toolbar-btn--warning",
1206
+ variant === "success" && "uilint-toolbar-btn--success"
1207
+ ].filter(Boolean).join(" ");
1208
+ return /* @__PURE__ */ jsx5(
1209
+ "button",
1210
+ {
1211
+ className: classes,
1212
+ onClick,
1213
+ disabled,
1214
+ title,
1215
+ "aria-label": ariaLabel,
1216
+ "aria-pressed": active,
1217
+ style: { minWidth: width },
1218
+ children
1219
+ }
1220
+ );
1221
+ }
1222
+ function Divider() {
1223
+ return /* @__PURE__ */ jsx5(
1224
+ "div",
1225
+ {
1226
+ style: {
1227
+ width: "1px",
1228
+ height: "20px",
1229
+ backgroundColor: TOKENS.border,
1230
+ flexShrink: 0
1231
+ },
1232
+ "aria-hidden": "true"
1233
+ }
1234
+ );
1235
+ }
1236
+ function ScanStatus({ status, issueCount, enabled }) {
1237
+ if (!enabled) {
1238
+ return /* @__PURE__ */ jsx5(
1239
+ "span",
1240
+ {
1241
+ style: {
1242
+ fontSize: "12px",
1243
+ color: TOKENS.textDisabled,
1244
+ fontStyle: "italic"
1245
+ },
1246
+ children: "Off"
1247
+ }
1248
+ );
1249
+ }
1250
+ if (status === "scanning" || status === "paused") {
1251
+ return /* @__PURE__ */ jsxs4(
1252
+ "span",
1253
+ {
1254
+ className: "uilint-scanning-indicator",
1255
+ style: {
1256
+ display: "flex",
1257
+ alignItems: "center",
1258
+ gap: "6px",
1259
+ padding: "0 4px"
1260
+ },
1261
+ children: [
1262
+ /* @__PURE__ */ jsx5(Icons.Scan, {}),
1263
+ /* @__PURE__ */ jsx5(
1264
+ "span",
1265
+ {
1266
+ style: {
1267
+ fontSize: "12px",
1268
+ fontFamily: TOKENS.fontMono,
1269
+ animation: `uilint-pulse 1s ease-in-out infinite`
1270
+ },
1271
+ children: "Scanning"
1272
+ }
1273
+ )
1274
+ ]
1275
+ }
1276
+ );
1277
+ }
1278
+ if (issueCount === 0) {
1279
+ return /* @__PURE__ */ jsxs4(
1280
+ "span",
1281
+ {
1282
+ style: {
1283
+ display: "flex",
1284
+ alignItems: "center",
1285
+ gap: "5px"
1286
+ },
1287
+ children: [
1288
+ /* @__PURE__ */ jsx5(
1289
+ "span",
1290
+ {
1291
+ style: {
1292
+ display: "flex",
1293
+ alignItems: "center",
1294
+ justifyContent: "center",
1295
+ width: "18px",
1296
+ height: "18px",
1297
+ borderRadius: "50%",
1298
+ backgroundColor: `${TOKENS.success}20`,
1299
+ color: TOKENS.success
1300
+ },
1301
+ children: /* @__PURE__ */ jsx5(Icons.Check, {})
1302
+ }
1303
+ ),
1304
+ /* @__PURE__ */ jsx5(
1305
+ "span",
1306
+ {
1307
+ style: {
1308
+ fontSize: "12px",
1309
+ fontWeight: 500,
1310
+ color: TOKENS.success
1311
+ },
1312
+ children: "Clear"
1313
+ }
1314
+ )
1315
+ ]
1316
+ }
1317
+ );
1318
+ }
1319
+ return /* @__PURE__ */ jsxs4(
1320
+ "span",
1321
+ {
1322
+ style: {
1323
+ display: "flex",
1324
+ alignItems: "center",
1325
+ gap: "5px"
1326
+ },
1327
+ children: [
1328
+ /* @__PURE__ */ jsx5(
1329
+ "span",
1330
+ {
1331
+ style: {
1332
+ display: "flex",
1333
+ alignItems: "center",
1334
+ justifyContent: "center",
1335
+ width: "18px",
1336
+ height: "18px",
1337
+ borderRadius: "50%",
1338
+ backgroundColor: `${TOKENS.warning}20`,
1339
+ color: TOKENS.warning
1340
+ },
1341
+ children: /* @__PURE__ */ jsx5(Icons.AlertTriangle, {})
1342
+ }
1343
+ ),
1344
+ /* @__PURE__ */ jsx5(
1345
+ "span",
1346
+ {
1347
+ className: "uilint-badge",
1348
+ style: {
1349
+ backgroundColor: `${TOKENS.warning}20`,
1350
+ color: TOKENS.warning
1351
+ },
1352
+ children: issueCount
1353
+ }
1354
+ )
1355
+ ]
1356
+ }
1357
+ );
1358
+ }
1359
+ function UILintToolbar() {
1360
+ const {
1361
+ settings,
1362
+ liveScanEnabled,
1363
+ autoScanState,
1364
+ enableLiveScan,
1365
+ disableLiveScan
1366
+ } = useUILintContext();
1367
+ const elementIssuesCache = useUILintStore(
1368
+ (s) => s.elementIssuesCache
1369
+ );
1370
+ const [showSettings, setShowSettings] = useState2(false);
1371
+ const [showResults, setShowResults] = useState2(false);
1372
+ const [mounted, setMounted] = useState2(false);
1373
+ const [settingsClosing, setSettingsClosing] = useState2(false);
1374
+ const [nextjsOverlayVisible, setNextjsOverlayVisible] = useState2(false);
1375
+ const toolbarRef = useRef2(null);
1376
+ const settingsRef = useRef2(null);
1377
+ useEffect2(() => {
1378
+ const checkForNextOverlay = () => {
1379
+ const overlaySelectors = [
1380
+ "nextjs-portal",
1381
+ "[data-nextjs-dialog]",
1382
+ "[data-nextjs-dialog-overlay]",
1383
+ "#__next-build-watcher",
1384
+ "[data-nextjs-toast]"
1385
+ ];
1386
+ const hasOverlay = overlaySelectors.some((selector) => {
1387
+ const el = document.querySelector(selector);
1388
+ if (!el) return false;
1389
+ const style = window.getComputedStyle(el);
1390
+ return style.display !== "none" && style.visibility !== "hidden";
1391
+ });
1392
+ setNextjsOverlayVisible(hasOverlay);
1393
+ };
1394
+ checkForNextOverlay();
1395
+ const observer = new MutationObserver(checkForNextOverlay);
1396
+ observer.observe(document.body, {
1397
+ childList: true,
1398
+ subtree: true,
1399
+ attributes: true,
1400
+ attributeFilter: ["style", "class"]
1401
+ });
1402
+ return () => observer.disconnect();
1403
+ }, []);
1404
+ const isScanning = autoScanState.status === "scanning";
1405
+ const isComplete = autoScanState.status === "complete";
1406
+ let totalIssues = 0;
1407
+ elementIssuesCache.forEach((el) => {
1408
+ totalIssues += el.issues.length;
1409
+ });
1410
+ const hasIssues = totalIssues > 0;
1411
+ useEffect2(() => {
1412
+ setMounted(true);
1413
+ }, []);
1414
+ useEffect2(() => {
1415
+ const handleClickOutside = (e) => {
1416
+ const target = e.target;
1417
+ if (showSettings && settingsRef.current && toolbarRef.current) {
1418
+ if (!settingsRef.current.contains(target) && !toolbarRef.current.contains(target)) {
1419
+ handleCloseSettings();
1420
+ }
1421
+ }
1422
+ };
1423
+ const handleEscape = (e) => {
1424
+ if (e.key === "Escape") {
1425
+ if (showSettings) handleCloseSettings();
1426
+ if (showResults) setShowResults(false);
1427
+ }
1428
+ };
1429
+ document.addEventListener("mousedown", handleClickOutside);
1430
+ document.addEventListener("keydown", handleEscape);
1431
+ return () => {
1432
+ document.removeEventListener("mousedown", handleClickOutside);
1433
+ document.removeEventListener("keydown", handleEscape);
1434
+ };
1435
+ }, [showSettings, showResults]);
1436
+ useEffect2(() => {
1437
+ if (hasIssues && liveScanEnabled && isComplete && !showSettings) {
1438
+ setShowResults(true);
1439
+ }
1440
+ }, [hasIssues, liveScanEnabled, isComplete, showSettings]);
1441
+ const handleToggleClick = useCallback2(() => {
1442
+ if (liveScanEnabled) {
1443
+ disableLiveScan();
1444
+ setShowResults(false);
1445
+ } else {
1446
+ enableLiveScan();
1447
+ }
1448
+ if (showSettings) handleCloseSettings();
1449
+ }, [liveScanEnabled, enableLiveScan, disableLiveScan, showSettings]);
1450
+ const handleIssuesClick = useCallback2(() => {
1451
+ if (!liveScanEnabled) {
1452
+ enableLiveScan();
1453
+ return;
1454
+ }
1455
+ setShowResults((prev) => !prev);
1456
+ }, [liveScanEnabled, enableLiveScan]);
1457
+ const handleSettingsClick = useCallback2(() => {
1458
+ if (showSettings) {
1459
+ handleCloseSettings();
1460
+ } else {
1461
+ setShowSettings(true);
1462
+ setShowResults(false);
1463
+ }
1464
+ }, [showSettings]);
1465
+ const handleCloseSettings = useCallback2(() => {
1466
+ setSettingsClosing(true);
1467
+ setTimeout(() => {
1468
+ setShowSettings(false);
1469
+ setSettingsClosing(false);
1470
+ }, 150);
1471
+ }, []);
1472
+ if (!mounted) return null;
1473
+ const issueVariant = !liveScanEnabled ? "default" : hasIssues ? "warning" : isComplete ? "success" : "default";
1474
+ const bottomPosition = nextjsOverlayVisible ? "80px" : "20px";
1475
+ const content = /* @__PURE__ */ jsxs4(
1476
+ "div",
1477
+ {
1478
+ "data-ui-lint": true,
1479
+ style: {
1480
+ position: "fixed",
1481
+ bottom: bottomPosition,
1482
+ left: "20px",
1483
+ zIndex: 99999,
1484
+ fontFamily: TOKENS.fontFamily,
1485
+ transition: `bottom ${TOKENS.transitionSlow}`
1486
+ },
1487
+ children: [
1488
+ /* @__PURE__ */ jsx5("style", { children: globalStyles }),
1489
+ /* @__PURE__ */ jsxs4(
1490
+ "div",
1491
+ {
1492
+ className: `uilint-hint ${liveScanEnabled ? "uilint-hint--visible" : ""}`,
1493
+ style: {
1494
+ textAlign: "center",
1495
+ marginBottom: "10px",
1496
+ fontSize: "11px",
1497
+ color: TOKENS.textMuted,
1498
+ letterSpacing: "0.02em",
1499
+ // Subtle dark halo for readability on any background
1500
+ textShadow: `
1501
+ 0 0 4px rgba(0, 0, 0, 0.8),
1502
+ 0 0 8px rgba(0, 0, 0, 0.5),
1503
+ 0 1px 2px rgba(0, 0, 0, 0.9)
1504
+ `
1505
+ },
1506
+ "aria-hidden": !liveScanEnabled,
1507
+ children: [
1508
+ /* @__PURE__ */ jsx5(
1509
+ "kbd",
1510
+ {
1511
+ style: {
1512
+ display: "inline-block",
1513
+ padding: "2px 5px",
1514
+ marginRight: "4px",
1515
+ borderRadius: "4px",
1516
+ backgroundColor: TOKENS.bgElevated,
1517
+ border: `1px solid ${TOKENS.border}`,
1518
+ fontSize: "10px",
1519
+ fontFamily: TOKENS.fontMono,
1520
+ color: TOKENS.textSecondary,
1521
+ boxShadow: `0 1px 3px rgba(0, 0, 0, 0.5)`
1522
+ },
1523
+ children: "\u2325"
1524
+ }
1525
+ ),
1526
+ /* @__PURE__ */ jsx5("span", { children: "+ Click to inspect element" })
1527
+ ]
1528
+ }
1529
+ ),
1530
+ /* @__PURE__ */ jsxs4(
1531
+ "div",
1532
+ {
1533
+ ref: toolbarRef,
1534
+ role: "toolbar",
1535
+ "aria-label": "UI Lint toolbar",
1536
+ style: {
1537
+ position: "relative",
1538
+ display: "inline-flex",
1539
+ alignItems: "center",
1540
+ height: TOKENS.pillHeight,
1541
+ borderRadius: TOKENS.pillRadius,
1542
+ border: `1px solid ${TOKENS.border}`,
1543
+ backgroundColor: TOKENS.bgBase,
1544
+ backdropFilter: TOKENS.blur,
1545
+ WebkitBackdropFilter: TOKENS.blur,
1546
+ boxShadow: liveScanEnabled && hasIssues ? `${TOKENS.shadowMd}, ${TOKENS.shadowGlow(
1547
+ `${TOKENS.warning}30`
1548
+ )}` : TOKENS.shadowMd,
1549
+ overflow: "hidden",
1550
+ transition: `box-shadow ${TOKENS.transitionBase}`
1551
+ },
1552
+ children: [
1553
+ /* @__PURE__ */ jsx5(
1554
+ ToolbarButton,
1555
+ {
1556
+ onClick: handleToggleClick,
1557
+ active: liveScanEnabled,
1558
+ title: liveScanEnabled ? "Stop scanning (\u2325S)" : "Start scanning (\u2325S)",
1559
+ ariaLabel: liveScanEnabled ? "Stop live scanning" : "Start live scanning",
1560
+ width: "48px",
1561
+ children: liveScanEnabled ? /* @__PURE__ */ jsx5(Icons.Eye, {}) : /* @__PURE__ */ jsx5(Icons.EyeOff, {})
1562
+ }
1563
+ ),
1564
+ /* @__PURE__ */ jsx5(Divider, {}),
1565
+ /* @__PURE__ */ jsx5(
1566
+ ToolbarButton,
1567
+ {
1568
+ onClick: handleIssuesClick,
1569
+ active: showResults && liveScanEnabled,
1570
+ variant: issueVariant,
1571
+ title: !liveScanEnabled ? "Click to enable scanning" : `${totalIssues} issue${totalIssues !== 1 ? "s" : ""} found`,
1572
+ ariaLabel: !liveScanEnabled ? "Enable scanning to see issues" : `View ${totalIssues} issues`,
1573
+ width: "auto",
1574
+ children: /* @__PURE__ */ jsx5("span", { style: { padding: "0 12px" }, children: /* @__PURE__ */ jsx5(
1575
+ ScanStatus,
1576
+ {
1577
+ status: autoScanState.status,
1578
+ issueCount: totalIssues,
1579
+ enabled: liveScanEnabled
1580
+ }
1581
+ ) })
1582
+ }
1583
+ ),
1584
+ /* @__PURE__ */ jsx5(Divider, {}),
1585
+ /* @__PURE__ */ jsx5(
1586
+ ToolbarButton,
1587
+ {
1588
+ onClick: handleSettingsClick,
1589
+ active: showSettings,
1590
+ title: "Settings",
1591
+ ariaLabel: "Open settings",
1592
+ width: "44px",
1593
+ children: /* @__PURE__ */ jsx5(Icons.Settings, {})
1594
+ }
1595
+ )
1596
+ ]
1597
+ }
1598
+ ),
1599
+ showSettings && /* @__PURE__ */ jsx5(
1600
+ "div",
1601
+ {
1602
+ ref: settingsRef,
1603
+ className: `uilint-popover ${settingsClosing ? "uilint-popover--closing" : ""}`,
1604
+ style: {
1605
+ position: "absolute",
1606
+ bottom: "100%",
1607
+ left: 0,
1608
+ marginBottom: "8px"
1609
+ },
1610
+ children: /* @__PURE__ */ jsx5(SettingsPopover, { settings })
1611
+ }
1612
+ ),
1613
+ /* @__PURE__ */ jsx5(
1614
+ ScanPanelStack,
1615
+ {
1616
+ show: showResults && liveScanEnabled,
1617
+ onClose: () => setShowResults(false)
1618
+ }
1619
+ )
1620
+ ]
1621
+ }
1622
+ );
1623
+ return createPortal(content, document.body);
1624
+ }
1625
+
1626
+ export {
1627
+ UILintToolbar
1628
+ };