uilint-react 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,37 @@
1
1
  "use client";
2
-
3
- // src/components/UILint.tsx
4
2
  import {
5
- createContext,
6
- useContext,
7
- useState as useState4,
8
- useEffect as useEffect2,
9
- useCallback as useCallback2,
10
- useRef
11
- } from "react";
3
+ UILintToolbar
4
+ } from "./chunk-PBC3J267.js";
5
+ import {
6
+ InspectionPanel,
7
+ clearSourceCache,
8
+ fetchSource,
9
+ fetchSourceWithContext,
10
+ getCachedSource,
11
+ prefetchSources
12
+ } from "./chunk-NOISZ3XP.js";
13
+ import {
14
+ LocatorOverlay
15
+ } from "./chunk-VYCIUDU7.js";
16
+ import {
17
+ DATA_UILINT_ID,
18
+ DEFAULT_SETTINGS,
19
+ FILE_COLORS,
20
+ UILintProvider,
21
+ buildEditorUrl,
22
+ cleanupDataAttributes,
23
+ getComponentStack,
24
+ getDebugOwner,
25
+ getDebugSource,
26
+ getDisplayName,
27
+ getElementById,
28
+ getFiberFromElement,
29
+ groupBySourceFile,
30
+ isNodeModulesPath,
31
+ scanDOMForSources,
32
+ updateElementRects,
33
+ useUILintContext
34
+ } from "./chunk-DAFFOBEU.js";
12
35
 
13
36
  // src/consistency/snapshot.ts
14
37
  var DATA_ELEMENTS_ATTR = "data-elements";
@@ -209,950 +232,10 @@ function getElementBySnapshotId(id) {
209
232
  return document.querySelector(`[${DATA_ELEMENTS_ATTR}="${id}"]`);
210
233
  }
211
234
 
212
- // src/scanner/environment.ts
213
- function isBrowser() {
214
- return typeof window !== "undefined" && typeof window.document !== "undefined";
215
- }
216
- function isJSDOM() {
217
- if (!isBrowser()) return false;
218
- const userAgent = window.navigator?.userAgent || "";
219
- return userAgent.includes("jsdom");
220
- }
221
- function isNode() {
222
- return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
223
- }
224
-
225
- // src/components/Overlay.tsx
226
- import { useState as useState2 } from "react";
227
-
228
- // src/components/ViolationList.tsx
229
- import { jsx, jsxs } from "react/jsx-runtime";
230
- function ViolationList() {
231
- const {
232
- violations,
233
- selectedViolation,
234
- setSelectedViolation,
235
- lockedViolation,
236
- setLockedViolation,
237
- isScanning
238
- } = useUILint();
239
- if (isScanning) {
240
- return /* @__PURE__ */ jsxs(
241
- "div",
242
- {
243
- style: {
244
- padding: "32px 16px",
245
- textAlign: "center",
246
- color: "#9CA3AF"
247
- },
248
- children: [
249
- /* @__PURE__ */ jsx("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u{1F50D}" }),
250
- /* @__PURE__ */ jsx("div", { style: { fontSize: "14px" }, children: "Analyzing page..." }),
251
- /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", marginTop: "4px", color: "#6B7280" }, children: "This may take a moment" })
252
- ]
253
- }
254
- );
255
- }
256
- if (violations.length === 0) {
257
- return /* @__PURE__ */ jsxs(
258
- "div",
259
- {
260
- style: {
261
- padding: "32px 16px",
262
- textAlign: "center",
263
- color: "#9CA3AF"
264
- },
265
- children: [
266
- /* @__PURE__ */ jsx("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u2728" }),
267
- /* @__PURE__ */ jsx("div", { style: { fontSize: "14px" }, children: "No consistency issues found" }),
268
- /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", marginTop: "4px" }, children: 'Click "Scan" to analyze the page' })
269
- ]
270
- }
271
- );
272
- }
273
- const handleClick = (violation) => {
274
- if (lockedViolation?.elementIds.join(",") === violation.elementIds.join(",")) {
275
- setLockedViolation(null);
276
- } else {
277
- setLockedViolation(violation);
278
- }
279
- };
280
- return /* @__PURE__ */ jsx("div", { style: { padding: "8px" }, children: violations.map((violation, index) => /* @__PURE__ */ jsx(
281
- ViolationCard,
282
- {
283
- violation,
284
- isSelected: selectedViolation?.elementIds.join(",") === violation.elementIds.join(","),
285
- isLocked: lockedViolation?.elementIds.join(",") === violation.elementIds.join(","),
286
- onHover: () => setSelectedViolation(violation),
287
- onLeave: () => setSelectedViolation(null),
288
- onClick: () => handleClick(violation)
289
- },
290
- `${violation.elementIds.join("-")}-${index}`
291
- )) });
292
- }
293
- function ViolationCard({
294
- violation,
295
- isSelected,
296
- isLocked,
297
- onHover,
298
- onLeave,
299
- onClick
300
- }) {
301
- const categoryColors = {
302
- spacing: "#10B981",
303
- color: "#F59E0B",
304
- typography: "#8B5CF6",
305
- sizing: "#3B82F6",
306
- borders: "#06B6D4",
307
- shadows: "#6B7280"
308
- };
309
- const severityIcons = {
310
- error: "\u2716",
311
- warning: "\u26A0",
312
- info: "\u2139"
313
- };
314
- const categoryColor = categoryColors[violation.category] || "#6B7280";
315
- const severityIcon = severityIcons[violation.severity] || "\u2022";
316
- const isHighlighted = isSelected || isLocked;
317
- return /* @__PURE__ */ jsxs(
318
- "div",
319
- {
320
- onMouseEnter: onHover,
321
- onMouseLeave: onLeave,
322
- onClick,
323
- style: {
324
- padding: "12px",
325
- marginBottom: "8px",
326
- backgroundColor: isHighlighted ? "#374151" : "#111827",
327
- borderRadius: "8px",
328
- border: isLocked ? "1px solid #3B82F6" : isSelected ? "1px solid #4B5563" : "1px solid transparent",
329
- cursor: "pointer",
330
- transition: "all 0.15s"
331
- },
332
- children: [
333
- /* @__PURE__ */ jsxs(
334
- "div",
335
- {
336
- style: {
337
- display: "flex",
338
- alignItems: "center",
339
- gap: "8px",
340
- marginBottom: "8px"
341
- },
342
- children: [
343
- /* @__PURE__ */ jsx(
344
- "div",
345
- {
346
- style: {
347
- display: "inline-block",
348
- padding: "2px 8px",
349
- borderRadius: "4px",
350
- backgroundColor: `${categoryColor}20`,
351
- color: categoryColor,
352
- fontSize: "11px",
353
- fontWeight: "600",
354
- textTransform: "uppercase"
355
- },
356
- children: violation.category
357
- }
358
- ),
359
- /* @__PURE__ */ jsx(
360
- "span",
361
- {
362
- style: {
363
- fontSize: "12px",
364
- color: violation.severity === "error" ? "#EF4444" : violation.severity === "warning" ? "#F59E0B" : "#9CA3AF"
365
- },
366
- children: severityIcon
367
- }
368
- ),
369
- /* @__PURE__ */ jsxs(
370
- "span",
371
- {
372
- style: {
373
- fontSize: "11px",
374
- color: "#6B7280",
375
- marginLeft: "auto"
376
- },
377
- children: [
378
- violation.elementIds.length,
379
- " element",
380
- violation.elementIds.length !== 1 ? "s" : ""
381
- ]
382
- }
383
- )
384
- ]
385
- }
386
- ),
387
- /* @__PURE__ */ jsx(
388
- "div",
389
- {
390
- style: {
391
- fontSize: "13px",
392
- color: "#F3F4F6",
393
- lineHeight: "1.4",
394
- marginBottom: "8px"
395
- },
396
- children: violation.message
397
- }
398
- ),
399
- violation.details && /* @__PURE__ */ jsxs(
400
- "div",
401
- {
402
- style: {
403
- fontSize: "12px",
404
- color: "#9CA3AF"
405
- },
406
- children: [
407
- violation.details.property && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px" }, children: [
408
- /* @__PURE__ */ jsx("span", { style: { color: "#6B7280" }, children: "Property: " }),
409
- /* @__PURE__ */ jsx(
410
- "code",
411
- {
412
- style: {
413
- padding: "2px 4px",
414
- backgroundColor: "#374151",
415
- borderRadius: "3px",
416
- fontSize: "11px"
417
- },
418
- children: violation.details.property
419
- }
420
- )
421
- ] }),
422
- violation.details.values.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px" }, children: [
423
- /* @__PURE__ */ jsx("span", { style: { color: "#6B7280" }, children: "Values: " }),
424
- violation.details.values.map((val, i) => /* @__PURE__ */ jsxs("span", { children: [
425
- /* @__PURE__ */ jsx(
426
- "code",
427
- {
428
- style: {
429
- padding: "2px 4px",
430
- backgroundColor: "#374151",
431
- borderRadius: "3px",
432
- fontSize: "11px"
433
- },
434
- children: val
435
- }
436
- ),
437
- i < violation.details.values.length - 1 && /* @__PURE__ */ jsx("span", { style: { margin: "0 4px", color: "#6B7280" }, children: "vs" })
438
- ] }, i))
439
- ] })
440
- ]
441
- }
442
- ),
443
- violation.details.suggestion && /* @__PURE__ */ jsxs(
444
- "div",
445
- {
446
- style: {
447
- marginTop: "8px",
448
- padding: "8px",
449
- backgroundColor: "#1E3A5F",
450
- borderRadius: "4px",
451
- fontSize: "12px",
452
- color: "#93C5FD"
453
- },
454
- children: [
455
- "\u{1F4A1} ",
456
- violation.details.suggestion
457
- ]
458
- }
459
- ),
460
- isLocked && /* @__PURE__ */ jsx(
461
- "div",
462
- {
463
- style: {
464
- marginTop: "8px",
465
- fontSize: "11px",
466
- color: "#3B82F6"
467
- },
468
- children: "\u{1F512} Click to unlock"
469
- }
470
- )
471
- ]
472
- }
473
- );
474
- }
475
-
476
- // src/components/QuestionPanel.tsx
477
- import { useState } from "react";
478
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
479
- function QuestionPanel() {
480
- const { issues } = useUILint();
481
- const [answers, setAnswers] = useState({});
482
- const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
483
- const [isSaving, setIsSaving] = useState(false);
484
- const [saveSuccess, setSaveSuccess] = useState(false);
485
- const [saveError, setSaveError] = useState(null);
486
- const questions = generateQuestionsFromIssues(issues);
487
- if (questions.length === 0) {
488
- return /* @__PURE__ */ jsxs2(
489
- "div",
490
- {
491
- style: {
492
- padding: "32px 16px",
493
- textAlign: "center",
494
- color: "#9CA3AF"
495
- },
496
- children: [
497
- /* @__PURE__ */ jsx2("div", { style: { fontSize: "32px", marginBottom: "8px" }, children: "\u{1F3AF}" }),
498
- /* @__PURE__ */ jsx2("div", { style: { fontSize: "14px" }, children: "No style conflicts to resolve" }),
499
- /* @__PURE__ */ jsx2("div", { style: { fontSize: "12px", marginTop: "4px" }, children: "Scan the page to detect inconsistencies" })
500
- ]
501
- }
502
- );
503
- }
504
- const currentQuestion = questions[currentQuestionIndex];
505
- const handleAnswer = (value) => {
506
- setAnswers((prev) => ({
507
- ...prev,
508
- [currentQuestion.id]: value
509
- }));
510
- if (currentQuestionIndex < questions.length - 1) {
511
- setCurrentQuestionIndex((prev) => prev + 1);
512
- }
513
- };
514
- const handleSaveToStyleGuide = async () => {
515
- console.log("[UILint] Saving preferences:", answers);
516
- setIsSaving(true);
517
- setSaveSuccess(false);
518
- setSaveError(null);
519
- try {
520
- const getResponse = await fetch("/api/uilint/styleguide");
521
- const data = await getResponse.json().catch(() => ({}));
522
- if (!getResponse.ok || !data?.exists || !data?.content) {
523
- throw new Error(
524
- data?.error || 'No style guide found. Create ".uilint/styleguide.md" at your workspace root first.'
525
- );
526
- }
527
- const updatedContent = applyAnswersToStyleGuide(data.content, answers);
528
- const postResponse = await fetch("/api/uilint/styleguide", {
529
- method: "POST",
530
- headers: { "Content-Type": "application/json" },
531
- body: JSON.stringify({ content: updatedContent })
532
- });
533
- if (!postResponse.ok) {
534
- const err = await postResponse.json().catch(() => ({}));
535
- throw new Error(err?.error || "Failed to save style guide");
536
- }
537
- console.log("[UILint] Style guide saved successfully!");
538
- setSaveSuccess(true);
539
- setTimeout(() => {
540
- setAnswers({});
541
- setCurrentQuestionIndex(0);
542
- setSaveSuccess(false);
543
- }, 1500);
544
- } catch (error) {
545
- console.error("[UILint] Error saving style guide:", error);
546
- setSaveError(error instanceof Error ? error.message : "Save failed");
547
- } finally {
548
- setIsSaving(false);
549
- }
550
- };
551
- const isComplete = Object.keys(answers).length === questions.length;
552
- return /* @__PURE__ */ jsxs2("div", { style: { padding: "16px" }, children: [
553
- /* @__PURE__ */ jsxs2(
554
- "div",
555
- {
556
- style: {
557
- display: "flex",
558
- justifyContent: "space-between",
559
- alignItems: "center",
560
- marginBottom: "16px"
561
- },
562
- children: [
563
- /* @__PURE__ */ jsxs2("span", { style: { fontSize: "12px", color: "#9CA3AF" }, children: [
564
- "Question ",
565
- currentQuestionIndex + 1,
566
- " of ",
567
- questions.length
568
- ] }),
569
- /* @__PURE__ */ jsx2(
570
- "div",
571
- {
572
- style: {
573
- width: "100px",
574
- height: "4px",
575
- backgroundColor: "#374151",
576
- borderRadius: "2px",
577
- overflow: "hidden"
578
- },
579
- children: /* @__PURE__ */ jsx2(
580
- "div",
581
- {
582
- style: {
583
- width: `${(currentQuestionIndex + 1) / questions.length * 100}%`,
584
- height: "100%",
585
- backgroundColor: "#3B82F6",
586
- transition: "width 0.3s"
587
- }
588
- }
589
- )
590
- }
591
- )
592
- ]
593
- }
594
- ),
595
- /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "16px" }, children: [
596
- /* @__PURE__ */ jsx2(
597
- "div",
598
- {
599
- style: {
600
- fontSize: "14px",
601
- fontWeight: "500",
602
- color: "#F3F4F6",
603
- marginBottom: "8px"
604
- },
605
- children: currentQuestion.question
606
- }
607
- ),
608
- currentQuestion.context && /* @__PURE__ */ jsx2(
609
- "div",
610
- {
611
- style: {
612
- fontSize: "12px",
613
- color: "#9CA3AF",
614
- marginBottom: "12px"
615
- },
616
- children: currentQuestion.context
617
- }
618
- )
619
- ] }),
620
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: currentQuestion.options.map((option) => /* @__PURE__ */ jsxs2(
621
- "button",
622
- {
623
- onClick: () => handleAnswer(option.value),
624
- style: {
625
- display: "flex",
626
- alignItems: "center",
627
- gap: "12px",
628
- padding: "12px",
629
- backgroundColor: answers[currentQuestion.id] === option.value ? "#374151" : "#111827",
630
- border: answers[currentQuestion.id] === option.value ? "1px solid #3B82F6" : "1px solid #374151",
631
- borderRadius: "8px",
632
- color: "#F3F4F6",
633
- fontSize: "13px",
634
- textAlign: "left",
635
- cursor: "pointer",
636
- transition: "all 0.15s"
637
- },
638
- children: [
639
- option.preview && /* @__PURE__ */ jsx2(
640
- "div",
641
- {
642
- style: {
643
- width: "32px",
644
- height: "32px",
645
- borderRadius: "4px",
646
- display: "flex",
647
- alignItems: "center",
648
- justifyContent: "center"
649
- },
650
- children: option.preview
651
- }
652
- ),
653
- /* @__PURE__ */ jsx2("span", { children: option.label })
654
- ]
655
- },
656
- option.value
657
- )) }),
658
- /* @__PURE__ */ jsxs2(
659
- "div",
660
- {
661
- style: {
662
- display: "flex",
663
- justifyContent: "space-between",
664
- marginTop: "16px"
665
- },
666
- children: [
667
- /* @__PURE__ */ jsx2(
668
- "button",
669
- {
670
- onClick: () => setCurrentQuestionIndex((prev) => Math.max(0, prev - 1)),
671
- disabled: currentQuestionIndex === 0,
672
- style: {
673
- padding: "8px 16px",
674
- backgroundColor: "transparent",
675
- border: "1px solid #374151",
676
- borderRadius: "6px",
677
- color: currentQuestionIndex === 0 ? "#4B5563" : "#9CA3AF",
678
- fontSize: "12px",
679
- cursor: currentQuestionIndex === 0 ? "not-allowed" : "pointer"
680
- },
681
- children: "\u2190 Back"
682
- }
683
- ),
684
- isComplete && /* @__PURE__ */ jsx2(
685
- "button",
686
- {
687
- onClick: handleSaveToStyleGuide,
688
- disabled: isSaving,
689
- style: {
690
- padding: "8px 16px",
691
- backgroundColor: saveSuccess ? "#059669" : isSaving ? "#6B7280" : "#10B981",
692
- border: "none",
693
- borderRadius: "6px",
694
- color: "white",
695
- fontSize: "12px",
696
- fontWeight: "500",
697
- cursor: isSaving ? "wait" : "pointer",
698
- opacity: isSaving ? 0.8 : 1,
699
- transition: "all 0.2s"
700
- },
701
- children: saveSuccess ? "\u2713 Saved!" : isSaving ? "Saving..." : "Save to Style Guide"
702
- }
703
- )
704
- ]
705
- }
706
- ),
707
- saveError && /* @__PURE__ */ jsx2(
708
- "div",
709
- {
710
- style: {
711
- marginTop: "12px",
712
- padding: "10px",
713
- borderRadius: "8px",
714
- backgroundColor: "#7F1D1D",
715
- border: "1px solid #EF4444",
716
- color: "#FEE2E2",
717
- fontSize: "12px",
718
- lineHeight: 1.4
719
- },
720
- children: saveError
721
- }
722
- )
723
- ] });
724
- }
725
- function generateQuestionsFromIssues(issues) {
726
- const questions = [];
727
- const colorIssues = issues.filter((i) => i.type === "color");
728
- if (colorIssues.length > 0) {
729
- const colors = /* @__PURE__ */ new Set();
730
- colorIssues.forEach((issue) => {
731
- if (issue.currentValue) colors.add(issue.currentValue);
732
- if (issue.expectedValue) colors.add(issue.expectedValue);
733
- });
734
- if (colors.size >= 2) {
735
- const colorArray = Array.from(colors);
736
- questions.push({
737
- id: "primary-color",
738
- question: "Which color should be used as the primary color?",
739
- context: "Multiple similar colors were detected. Choose one for consistency.",
740
- options: colorArray.slice(0, 4).map((color) => ({
741
- value: color,
742
- label: color,
743
- preview: /* @__PURE__ */ jsx2(
744
- "div",
745
- {
746
- style: {
747
- width: "100%",
748
- height: "100%",
749
- backgroundColor: color,
750
- borderRadius: "4px"
751
- }
752
- }
753
- )
754
- }))
755
- });
756
- }
757
- }
758
- const spacingIssues = issues.filter((i) => i.type === "spacing");
759
- if (spacingIssues.length > 0) {
760
- questions.push({
761
- id: "spacing-scale",
762
- question: "What spacing scale should be used?",
763
- context: "Choose a base unit for consistent spacing throughout the UI.",
764
- options: [
765
- { value: "4", label: "4px base (4, 8, 12, 16, 20, 24...)" },
766
- { value: "8", label: "8px base (8, 16, 24, 32, 40...)" },
767
- {
768
- value: "tailwind",
769
- label: "Tailwind scale (4, 8, 12, 16, 20, 24...)"
770
- }
771
- ]
772
- });
773
- }
774
- const typographyIssues = issues.filter((i) => i.type === "typography");
775
- if (typographyIssues.length > 0) {
776
- questions.push({
777
- id: "font-weights",
778
- question: "Which font weights should be used?",
779
- context: "Select the weights to use for consistency.",
780
- options: [
781
- {
782
- value: "400-600-700",
783
- label: "Regular (400), Semibold (600), Bold (700)"
784
- },
785
- {
786
- value: "400-500-700",
787
- label: "Regular (400), Medium (500), Bold (700)"
788
- },
789
- {
790
- value: "300-400-600",
791
- label: "Light (300), Regular (400), Semibold (600)"
792
- }
793
- ]
794
- });
795
- }
796
- return questions;
797
- }
798
- function applyAnswersToStyleGuide(existingContent, answers) {
799
- let content = existingContent;
800
- if (answers["primary-color"]) {
801
- content = upsertBulletInSection(
802
- content,
803
- "Colors",
804
- "Primary",
805
- answers["primary-color"]
806
- );
807
- }
808
- if (answers["font-weights"]) {
809
- const weightMap = {
810
- "400-600-700": "400 (Regular), 600 (Semibold), 700 (Bold)",
811
- "400-500-700": "400 (Regular), 500 (Medium), 700 (Bold)",
812
- "300-400-600": "300 (Light), 400 (Regular), 600 (Semibold)"
813
- };
814
- const value = weightMap[answers["font-weights"]] || answers["font-weights"];
815
- content = upsertBulletInSection(
816
- content,
817
- "Typography",
818
- "Font Weights",
819
- value
820
- );
821
- }
822
- if (answers["spacing-scale"]) {
823
- const spacingMap = {
824
- "4": "4px (4, 8, 12, 16, 20, 24, 32, 40, 48...)",
825
- "8": "8px (8, 16, 24, 32, 40, 48, 56, 64...)",
826
- tailwind: "Tailwind (4, 8, 12, 16, 20, 24, 32, 40, 48...)"
827
- };
828
- const value = spacingMap[answers["spacing-scale"]] || answers["spacing-scale"];
829
- content = upsertBulletInSection(content, "Spacing", "Base unit", value);
830
- }
831
- return content;
832
- }
833
- function upsertBulletInSection(markdown, sectionName, label, value) {
834
- const lines = markdown.split("\n");
835
- const sectionStart = lines.findIndex(
836
- (line) => line.match(new RegExp(`^##\\s+${sectionName}\\s*$`, "i"))
837
- );
838
- if (sectionStart === -1) {
839
- throw new Error(
840
- `Style guide is missing section "## ${sectionName}". Add it to your style guide and try again.`
841
- );
842
- }
843
- let sectionEnd = lines.length;
844
- for (let i = sectionStart + 1; i < lines.length; i++) {
845
- if (lines[i].startsWith("## ")) {
846
- sectionEnd = i;
847
- break;
848
- }
849
- }
850
- const bulletRe = new RegExp(
851
- `^-\\s+\\*\\*${escapeRegExp(label)}\\*\\*:\\s+.*$`
852
- );
853
- const newBullet = `- **${label}**: ${value}`;
854
- for (let i = sectionStart + 1; i < sectionEnd; i++) {
855
- if (bulletRe.test(lines[i])) {
856
- lines[i] = newBullet;
857
- return lines.join("\n");
858
- }
859
- }
860
- let insertAt = sectionStart + 1;
861
- while (insertAt < sectionEnd && lines[insertAt].trim() === "") insertAt++;
862
- lines.splice(insertAt, 0, newBullet);
863
- return lines.join("\n");
864
- }
865
- function escapeRegExp(s) {
866
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
867
- }
868
-
869
- // src/components/Overlay.tsx
870
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
871
- function Overlay({ position }) {
872
- const [isExpanded, setIsExpanded] = useState2(false);
873
- const { violations, isScanning, scan, elementCount } = useUILint();
874
- const positionStyles = {
875
- position: "fixed",
876
- zIndex: 99999,
877
- ...position.includes("bottom") ? { bottom: "16px" } : { top: "16px" },
878
- ...position.includes("left") ? { left: "16px" } : { right: "16px" }
879
- };
880
- const violationCount = violations.length;
881
- const hasViolations = violationCount > 0;
882
- return /* @__PURE__ */ jsx3("div", { style: positionStyles, children: isExpanded ? /* @__PURE__ */ jsx3(
883
- ExpandedPanel,
884
- {
885
- onCollapse: () => setIsExpanded(false),
886
- onScan: scan,
887
- isScanning,
888
- elementCount
889
- }
890
- ) : /* @__PURE__ */ jsx3(
891
- CollapsedButton,
892
- {
893
- onClick: () => setIsExpanded(true),
894
- violationCount,
895
- hasViolations,
896
- isScanning
897
- }
898
- ) });
899
- }
900
- function CollapsedButton({
901
- onClick,
902
- violationCount,
903
- hasViolations,
904
- isScanning
905
- }) {
906
- return /* @__PURE__ */ jsx3(
907
- "button",
908
- {
909
- onClick,
910
- style: {
911
- display: "flex",
912
- alignItems: "center",
913
- justifyContent: "center",
914
- width: "48px",
915
- height: "48px",
916
- borderRadius: "50%",
917
- border: "none",
918
- backgroundColor: isScanning ? "#3B82F6" : hasViolations ? "#EF4444" : "#10B981",
919
- color: "white",
920
- cursor: "pointer",
921
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
922
- transition: "transform 0.2s, box-shadow 0.2s",
923
- fontSize: "20px",
924
- fontWeight: "bold"
925
- },
926
- onMouseEnter: (e) => {
927
- e.currentTarget.style.transform = "scale(1.1)";
928
- e.currentTarget.style.boxShadow = "0 6px 16px rgba(0, 0, 0, 0.2)";
929
- },
930
- onMouseLeave: (e) => {
931
- e.currentTarget.style.transform = "scale(1)";
932
- e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.15)";
933
- },
934
- title: isScanning ? "Analyzing..." : `UILint: ${violationCount} issue${violationCount !== 1 ? "s" : ""} found`,
935
- children: isScanning ? /* @__PURE__ */ jsx3(SpinnerIcon, {}) : hasViolations ? violationCount : "\u2713"
936
- }
937
- );
938
- }
939
- function SpinnerIcon() {
940
- return /* @__PURE__ */ jsxs3(
941
- "svg",
942
- {
943
- width: "20",
944
- height: "20",
945
- viewBox: "0 0 24 24",
946
- fill: "none",
947
- style: {
948
- animation: "uilint-spin 1s linear infinite"
949
- },
950
- children: [
951
- /* @__PURE__ */ jsx3("style", { children: `
952
- @keyframes uilint-spin {
953
- from { transform: rotate(0deg); }
954
- to { transform: rotate(360deg); }
955
- }
956
- ` }),
957
- /* @__PURE__ */ jsx3(
958
- "circle",
959
- {
960
- cx: "12",
961
- cy: "12",
962
- r: "10",
963
- stroke: "currentColor",
964
- strokeWidth: "3",
965
- strokeLinecap: "round",
966
- strokeDasharray: "31.4 31.4",
967
- fill: "none"
968
- }
969
- )
970
- ]
971
- }
972
- );
973
- }
974
- function ExpandedPanel({
975
- onCollapse,
976
- onScan,
977
- isScanning,
978
- elementCount
979
- }) {
980
- const [activeTab, setActiveTab] = useState2(
981
- "violations"
982
- );
983
- return /* @__PURE__ */ jsxs3(
984
- "div",
985
- {
986
- style: {
987
- width: "380px",
988
- maxHeight: "500px",
989
- backgroundColor: "#1F2937",
990
- borderRadius: "12px",
991
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
992
- overflow: "hidden",
993
- fontFamily: "system-ui, -apple-system, sans-serif",
994
- color: "#F9FAFB"
995
- },
996
- children: [
997
- /* @__PURE__ */ jsxs3(
998
- "div",
999
- {
1000
- style: {
1001
- display: "flex",
1002
- alignItems: "center",
1003
- justifyContent: "space-between",
1004
- padding: "12px 16px",
1005
- borderBottom: "1px solid #374151",
1006
- backgroundColor: "#111827"
1007
- },
1008
- children: [
1009
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1010
- /* @__PURE__ */ jsx3("span", { style: { fontSize: "16px" }, children: "\u{1F3A8}" }),
1011
- /* @__PURE__ */ jsx3("span", { style: { fontWeight: "600", fontSize: "14px" }, children: "UILint" }),
1012
- elementCount > 0 && !isScanning && /* @__PURE__ */ jsxs3(
1013
- "span",
1014
- {
1015
- style: {
1016
- fontSize: "11px",
1017
- color: "#6B7280",
1018
- marginLeft: "4px"
1019
- },
1020
- children: [
1021
- "(",
1022
- elementCount,
1023
- " elements)"
1024
- ]
1025
- }
1026
- )
1027
- ] }),
1028
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
1029
- /* @__PURE__ */ jsxs3(
1030
- "button",
1031
- {
1032
- onClick: onScan,
1033
- disabled: isScanning,
1034
- style: {
1035
- padding: "6px 12px",
1036
- borderRadius: "6px",
1037
- border: "none",
1038
- backgroundColor: "#3B82F6",
1039
- color: "white",
1040
- fontSize: "12px",
1041
- fontWeight: "500",
1042
- cursor: isScanning ? "not-allowed" : "pointer",
1043
- opacity: isScanning ? 0.7 : 1,
1044
- display: "flex",
1045
- alignItems: "center",
1046
- gap: "6px"
1047
- },
1048
- children: [
1049
- isScanning && /* @__PURE__ */ jsx3(
1050
- "svg",
1051
- {
1052
- width: "12",
1053
- height: "12",
1054
- viewBox: "0 0 24 24",
1055
- fill: "none",
1056
- style: {
1057
- animation: "uilint-spin 1s linear infinite"
1058
- },
1059
- children: /* @__PURE__ */ jsx3(
1060
- "circle",
1061
- {
1062
- cx: "12",
1063
- cy: "12",
1064
- r: "10",
1065
- stroke: "currentColor",
1066
- strokeWidth: "3",
1067
- strokeLinecap: "round",
1068
- strokeDasharray: "31.4 31.4",
1069
- fill: "none"
1070
- }
1071
- )
1072
- }
1073
- ),
1074
- isScanning ? "Analyzing..." : "Scan"
1075
- ]
1076
- }
1077
- ),
1078
- /* @__PURE__ */ jsx3(
1079
- "button",
1080
- {
1081
- onClick: onCollapse,
1082
- style: {
1083
- padding: "6px 8px",
1084
- borderRadius: "6px",
1085
- border: "none",
1086
- backgroundColor: "transparent",
1087
- color: "#9CA3AF",
1088
- fontSize: "16px",
1089
- cursor: "pointer"
1090
- },
1091
- children: "\u2715"
1092
- }
1093
- )
1094
- ] })
1095
- ]
1096
- }
1097
- ),
1098
- /* @__PURE__ */ jsxs3(
1099
- "div",
1100
- {
1101
- style: {
1102
- display: "flex",
1103
- borderBottom: "1px solid #374151"
1104
- },
1105
- children: [
1106
- /* @__PURE__ */ jsx3(
1107
- TabButton,
1108
- {
1109
- active: activeTab === "violations",
1110
- onClick: () => setActiveTab("violations"),
1111
- children: "Violations"
1112
- }
1113
- ),
1114
- /* @__PURE__ */ jsx3(
1115
- TabButton,
1116
- {
1117
- active: activeTab === "questions",
1118
- onClick: () => setActiveTab("questions"),
1119
- children: "Questions"
1120
- }
1121
- )
1122
- ]
1123
- }
1124
- ),
1125
- /* @__PURE__ */ jsx3("div", { style: { maxHeight: "380px", overflow: "auto" }, children: activeTab === "violations" ? /* @__PURE__ */ jsx3(ViolationList, {}) : /* @__PURE__ */ jsx3(QuestionPanel, {}) })
1126
- ]
1127
- }
1128
- );
1129
- }
1130
- function TabButton({ active, onClick, children }) {
1131
- return /* @__PURE__ */ jsx3(
1132
- "button",
1133
- {
1134
- onClick,
1135
- style: {
1136
- flex: 1,
1137
- padding: "10px 16px",
1138
- border: "none",
1139
- backgroundColor: "transparent",
1140
- color: active ? "#3B82F6" : "#9CA3AF",
1141
- fontSize: "13px",
1142
- fontWeight: "500",
1143
- cursor: "pointer",
1144
- borderBottom: active ? "2px solid #3B82F6" : "2px solid transparent",
1145
- marginBottom: "-1px"
1146
- },
1147
- children
1148
- }
1149
- );
1150
- }
1151
-
1152
235
  // src/consistency/highlights.tsx
1153
- import { useEffect, useState as useState3, useCallback } from "react";
236
+ import { useEffect, useState, useCallback } from "react";
1154
237
  import { createPortal } from "react-dom";
1155
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
238
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1156
239
  var HIGHLIGHT_COLOR = "#3b82f6";
1157
240
  var DOT_SIZE = 8;
1158
241
  var BORDER_WIDTH = 2;
@@ -1183,7 +266,7 @@ function getAllViolatingIds(violations) {
1183
266
  return ids;
1184
267
  }
1185
268
  function OverviewDot({ rect }) {
1186
- return /* @__PURE__ */ jsx4(
269
+ return /* @__PURE__ */ jsx(
1187
270
  "div",
1188
271
  {
1189
272
  style: {
@@ -1205,8 +288,8 @@ function HighlightRect({
1205
288
  rect,
1206
289
  badgeNumber
1207
290
  }) {
1208
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
1209
- /* @__PURE__ */ jsx4(
291
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
292
+ /* @__PURE__ */ jsx(
1210
293
  "div",
1211
294
  {
1212
295
  style: {
@@ -1224,7 +307,7 @@ function HighlightRect({
1224
307
  }
1225
308
  }
1226
309
  ),
1227
- badgeNumber !== void 0 && /* @__PURE__ */ jsx4(
310
+ badgeNumber !== void 0 && /* @__PURE__ */ jsx(
1228
311
  "div",
1229
312
  {
1230
313
  style: {
@@ -1256,11 +339,11 @@ function ConsistencyHighlighter({
1256
339
  selectedViolation,
1257
340
  lockedViolation
1258
341
  }) {
1259
- const [overviewHighlights, setOverviewHighlights] = useState3([]);
1260
- const [activeHighlights, setActiveHighlights] = useState3(
342
+ const [overviewHighlights, setOverviewHighlights] = useState([]);
343
+ const [activeHighlights, setActiveHighlights] = useState(
1261
344
  []
1262
345
  );
1263
- const [mounted, setMounted] = useState3(false);
346
+ const [mounted, setMounted] = useState(false);
1264
347
  const activeViolation = lockedViolation || selectedViolation;
1265
348
  const updateOverviewHighlights = useCallback(() => {
1266
349
  if (activeViolation) {
@@ -1313,135 +396,13 @@ function ConsistencyHighlighter({
1313
396
  }, [updateOverviewHighlights, updateActiveHighlights]);
1314
397
  if (!mounted) return null;
1315
398
  if (violations.length === 0) return null;
1316
- const content = /* @__PURE__ */ jsxs4(Fragment, { children: [
1317
- !activeViolation && overviewHighlights.map((h) => /* @__PURE__ */ jsx4(OverviewDot, { rect: h.rect }, h.id)),
1318
- activeViolation && activeHighlights.map((h) => /* @__PURE__ */ jsx4(HighlightRect, { rect: h.rect, badgeNumber: h.badgeNumber }, h.id))
399
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
400
+ !activeViolation && overviewHighlights.map((h) => /* @__PURE__ */ jsx(OverviewDot, { rect: h.rect }, h.id)),
401
+ activeViolation && activeHighlights.map((h) => /* @__PURE__ */ jsx(HighlightRect, { rect: h.rect, badgeNumber: h.badgeNumber }, h.id))
1319
402
  ] });
1320
403
  return createPortal(content, document.body);
1321
404
  }
1322
405
 
1323
- // src/components/UILint.tsx
1324
- import { countElements } from "uilint-core";
1325
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1326
- var UILintContext = createContext(null);
1327
- function useUILint() {
1328
- const context = useContext(UILintContext);
1329
- if (!context) {
1330
- throw new Error("useUILint must be used within a UILint component");
1331
- }
1332
- return context;
1333
- }
1334
- function UILint({
1335
- children,
1336
- enabled = true,
1337
- position = "bottom-left",
1338
- autoScan = false,
1339
- apiEndpoint = "/api/uilint/consistency"
1340
- }) {
1341
- const [violations, setViolations] = useState4([]);
1342
- const [isScanning, setIsScanning] = useState4(false);
1343
- const [elementCount, setElementCount] = useState4(0);
1344
- const [selectedViolation, setSelectedViolation] = useState4(
1345
- null
1346
- );
1347
- const [lockedViolation, setLockedViolation] = useState4(
1348
- null
1349
- );
1350
- const [isMounted, setIsMounted] = useState4(false);
1351
- const hasInitialized = useRef(false);
1352
- useEffect2(() => {
1353
- setIsMounted(true);
1354
- }, []);
1355
- useEffect2(() => {
1356
- return () => {
1357
- if (isBrowser()) {
1358
- cleanupDataElements();
1359
- }
1360
- };
1361
- }, []);
1362
- const scan = useCallback2(async () => {
1363
- if (!isBrowser()) return;
1364
- setIsScanning(true);
1365
- setSelectedViolation(null);
1366
- setLockedViolation(null);
1367
- try {
1368
- const snapshot = createSnapshot(document.body);
1369
- const count = countElements(snapshot);
1370
- setElementCount(count);
1371
- const response = await fetch(apiEndpoint, {
1372
- method: "POST",
1373
- headers: { "Content-Type": "application/json" },
1374
- body: JSON.stringify({ snapshot })
1375
- });
1376
- if (!response.ok) {
1377
- const errorData = await response.json().catch(() => ({}));
1378
- console.error(
1379
- "[UILint] Analysis failed:",
1380
- errorData.error || response.statusText
1381
- );
1382
- setViolations([]);
1383
- return;
1384
- }
1385
- const result = await response.json();
1386
- setViolations(result.violations);
1387
- if (result.violations.length === 0) {
1388
- console.log(`[UILint] No consistency issues found (${count} elements)`);
1389
- } else {
1390
- console.log(
1391
- `[UILint] Found ${result.violations.length} consistency issue(s)`
1392
- );
1393
- }
1394
- } catch (error) {
1395
- console.error("[UILint] Scan failed:", error);
1396
- setViolations([]);
1397
- } finally {
1398
- setIsScanning(false);
1399
- }
1400
- }, [apiEndpoint]);
1401
- const clearViolations = useCallback2(() => {
1402
- setViolations([]);
1403
- setSelectedViolation(null);
1404
- setLockedViolation(null);
1405
- cleanupDataElements();
1406
- setElementCount(0);
1407
- }, []);
1408
- useEffect2(() => {
1409
- if (!enabled || hasInitialized.current) return;
1410
- hasInitialized.current = true;
1411
- if (!isBrowser()) return;
1412
- if (autoScan) {
1413
- const timer = setTimeout(scan, 1e3);
1414
- return () => clearTimeout(timer);
1415
- }
1416
- }, [enabled, autoScan, scan]);
1417
- const contextValue = {
1418
- violations,
1419
- isScanning,
1420
- elementCount,
1421
- scan,
1422
- clearViolations,
1423
- selectedViolation,
1424
- setSelectedViolation,
1425
- lockedViolation,
1426
- setLockedViolation
1427
- };
1428
- const shouldRenderOverlay = enabled && isMounted;
1429
- return /* @__PURE__ */ jsxs5(UILintContext.Provider, { value: contextValue, children: [
1430
- children,
1431
- shouldRenderOverlay && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1432
- /* @__PURE__ */ jsx5(Overlay, { position }),
1433
- /* @__PURE__ */ jsx5(
1434
- ConsistencyHighlighter,
1435
- {
1436
- violations,
1437
- selectedViolation,
1438
- lockedViolation
1439
- }
1440
- )
1441
- ] })
1442
- ] });
1443
- }
1444
-
1445
406
  // src/scanner/dom-scanner.ts
1446
407
  import {
1447
408
  extractStylesFromDOM,
@@ -1461,6 +422,19 @@ function scanDOM(root) {
1461
422
  };
1462
423
  }
1463
424
 
425
+ // src/scanner/environment.ts
426
+ function isBrowser() {
427
+ return typeof window !== "undefined" && typeof window.document !== "undefined";
428
+ }
429
+ function isJSDOM() {
430
+ if (!isBrowser()) return false;
431
+ const userAgent = window.navigator?.userAgent || "";
432
+ return userAgent.includes("jsdom");
433
+ }
434
+ function isNode() {
435
+ return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
436
+ }
437
+
1464
438
  // src/index.ts
1465
439
  import {
1466
440
  extractStylesFromDOM as extractStylesFromDOM2,
@@ -1549,21 +523,44 @@ import { generateStyleGuideFromStyles } from "uilint-core";
1549
523
  import { createEmptyStyleGuide, mergeStyleGuides } from "uilint-core";
1550
524
  export {
1551
525
  ConsistencyHighlighter,
526
+ DATA_UILINT_ID,
527
+ DEFAULT_SETTINGS,
528
+ FILE_COLORS,
529
+ InspectionPanel,
1552
530
  LLMClient,
1553
- UILint,
531
+ LocatorOverlay,
532
+ UILintProvider,
533
+ UILintToolbar,
534
+ buildEditorUrl,
535
+ cleanupDataAttributes,
1554
536
  cleanupDataElements,
537
+ clearSourceCache,
1555
538
  createEmptyStyleGuide,
1556
539
  createSnapshot,
1557
540
  createStyleSummary3 as createStyleSummary,
1558
541
  extractStylesFromDOM2 as extractStylesFromDOM,
542
+ fetchSource,
543
+ fetchSourceWithContext,
1559
544
  generateStyleGuideFromStyles as generateStyleGuide,
545
+ getCachedSource,
546
+ getComponentStack,
547
+ getDebugOwner,
548
+ getDebugSource,
549
+ getDisplayName,
550
+ getElementById,
1560
551
  getElementBySnapshotId,
552
+ getFiberFromElement,
553
+ groupBySourceFile,
1561
554
  isBrowser,
1562
555
  isJSDOM,
1563
556
  isNode,
557
+ isNodeModulesPath,
1564
558
  mergeStyleGuides,
1565
559
  parseStyleGuide,
560
+ prefetchSources,
1566
561
  scanDOM,
562
+ scanDOMForSources,
1567
563
  serializeStyles2 as serializeStyles,
1568
- useUILint
564
+ updateElementRects,
565
+ useUILintContext
1569
566
  };