uilint-react 0.1.45 → 0.1.47

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.
@@ -1063,10 +1063,10 @@ function UILintUI() {
1063
1063
  const [components, setComponents] = useState(null);
1064
1064
  useEffect2(() => {
1065
1065
  Promise.all([
1066
- import("./UILintToolbar-5NK77IQJ.js"),
1067
- import("./InspectionPanel-QC6WR5IG.js"),
1068
- import("./LocatorOverlay-Z24VU27L.js"),
1069
- import("./ElementBadges-RV7I6QXS.js")
1066
+ import("./UILintToolbar-FWVC6MNG.js"),
1067
+ import("./InspectionPanel-4P7I4QVY.js"),
1068
+ import("./LocatorOverlay-L3EPYQEF.js"),
1069
+ import("./ElementBadges-N2N3CBLI.js")
1070
1070
  ]).then(([toolbar, panel, locator, badges]) => {
1071
1071
  setComponents({
1072
1072
  Toolbar: toolbar.UILintToolbar,
@@ -0,0 +1,178 @@
1
+ "use client";
2
+
3
+ // src/components/ui-lint/Badge.tsx
4
+ import { jsx } from "react/jsx-runtime";
5
+ var BADGE_COLORS = {
6
+ success: "#68d391",
7
+ // Soft green
8
+ warning: "#f6ad55",
9
+ // Warm orange
10
+ error: "#ef4444"
11
+ // Red (for future use)
12
+ };
13
+ var FONT_MONO = `"SF Mono", Monaco, "Cascadia Code", monospace`;
14
+ function getBadgeTextColor(issueCount) {
15
+ if (issueCount === 0) return BADGE_COLORS.success;
16
+ return BADGE_COLORS.warning;
17
+ }
18
+ function getBadgeBackgroundColor(issueCount) {
19
+ const color = getBadgeTextColor(issueCount);
20
+ return `${color}20`;
21
+ }
22
+ var BADGE_STYLES = {
23
+ default: {
24
+ minWidth: "20px",
25
+ height: "20px",
26
+ padding: "0 6px",
27
+ borderRadius: "10px",
28
+ fontSize: "11px",
29
+ fontWeight: 600,
30
+ letterSpacing: "-0.02em"
31
+ },
32
+ small: {
33
+ minWidth: "18px",
34
+ height: "18px",
35
+ padding: "0 5px",
36
+ borderRadius: "9px",
37
+ fontSize: "10px",
38
+ fontWeight: 700,
39
+ letterSpacing: "0"
40
+ },
41
+ // For file row badges (slightly larger than small)
42
+ medium: {
43
+ minWidth: "22px",
44
+ height: "18px",
45
+ padding: "0 6px",
46
+ borderRadius: "9px",
47
+ fontSize: "10px",
48
+ fontWeight: 700,
49
+ letterSpacing: "0"
50
+ }
51
+ };
52
+ function Badge({
53
+ count,
54
+ size = "default",
55
+ backgroundColor,
56
+ color
57
+ }) {
58
+ const sizeStyles = BADGE_STYLES[size];
59
+ const bgColor = backgroundColor ?? getBadgeBackgroundColor(count);
60
+ const textColor = color ?? getBadgeTextColor(count);
61
+ return /* @__PURE__ */ jsx(
62
+ "span",
63
+ {
64
+ style: {
65
+ display: "inline-flex",
66
+ alignItems: "center",
67
+ justifyContent: "center",
68
+ ...sizeStyles,
69
+ backgroundColor: bgColor,
70
+ color: textColor,
71
+ fontFamily: FONT_MONO
72
+ },
73
+ children: count
74
+ }
75
+ );
76
+ }
77
+
78
+ // src/components/ui-lint/source-fetcher.ts
79
+ var sourceCache = /* @__PURE__ */ new Map();
80
+ var CACHE_TTL = 5 * 60 * 1e3;
81
+ var API_ENDPOINT = "/api/.uilint/source";
82
+ async function fetchSource(filePath) {
83
+ const cached = sourceCache.get(filePath);
84
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
85
+ return {
86
+ content: cached.content,
87
+ relativePath: cached.relativePath
88
+ };
89
+ }
90
+ try {
91
+ const response = await fetch(
92
+ `${API_ENDPOINT}?path=${encodeURIComponent(filePath)}`
93
+ );
94
+ if (!response.ok) {
95
+ console.warn(`[UILint] Failed to fetch source: ${response.statusText}`);
96
+ return null;
97
+ }
98
+ const data = await response.json();
99
+ sourceCache.set(filePath, {
100
+ ...data,
101
+ fetchedAt: Date.now()
102
+ });
103
+ return data;
104
+ } catch (error) {
105
+ console.error("[UILint] Error fetching source:", error);
106
+ return null;
107
+ }
108
+ }
109
+ async function fetchSourceWithContext(source, contextLines = 5) {
110
+ const result = await fetchSource(source.fileName);
111
+ if (!result) return null;
112
+ const allLines = result.content.split("\n");
113
+ const targetLine = source.lineNumber - 1;
114
+ const startLine = Math.max(0, targetLine - contextLines);
115
+ const endLine = Math.min(allLines.length, targetLine + contextLines + 1);
116
+ return {
117
+ lines: allLines.slice(startLine, endLine),
118
+ startLine: startLine + 1,
119
+ // Back to 1-indexed
120
+ highlightLine: source.lineNumber,
121
+ relativePath: result.relativePath
122
+ };
123
+ }
124
+ async function fetchSourceWithWindow(source, window) {
125
+ const result = await fetchSource(source.fileName);
126
+ if (!result) return null;
127
+ const allLines = result.content.split("\n");
128
+ const targetLine = source.lineNumber - 1;
129
+ const startLine = Math.max(0, targetLine - Math.max(0, window.linesAbove));
130
+ const endLine = Math.min(
131
+ allLines.length,
132
+ targetLine + Math.max(0, window.linesBelow) + 1
133
+ );
134
+ return {
135
+ lines: allLines.slice(startLine, endLine),
136
+ startLine: startLine + 1,
137
+ // Back to 1-indexed
138
+ highlightLine: source.lineNumber,
139
+ relativePath: result.relativePath
140
+ };
141
+ }
142
+ function clearSourceCache() {
143
+ sourceCache.clear();
144
+ }
145
+ function getCachedSource(filePath) {
146
+ const cached = sourceCache.get(filePath);
147
+ if (!cached) return null;
148
+ if (Date.now() - cached.fetchedAt >= CACHE_TTL) {
149
+ sourceCache.delete(filePath);
150
+ return null;
151
+ }
152
+ return {
153
+ content: cached.content,
154
+ relativePath: cached.relativePath
155
+ };
156
+ }
157
+ async function prefetchSources(filePaths) {
158
+ const uniquePaths = [...new Set(filePaths)].filter((path) => {
159
+ const cached = sourceCache.get(path);
160
+ return !cached || Date.now() - cached.fetchedAt >= CACHE_TTL;
161
+ });
162
+ const BATCH_SIZE = 5;
163
+ for (let i = 0; i < uniquePaths.length; i += BATCH_SIZE) {
164
+ const batch = uniquePaths.slice(i, i + BATCH_SIZE);
165
+ await Promise.all(batch.map(fetchSource));
166
+ }
167
+ }
168
+
169
+ export {
170
+ BADGE_COLORS,
171
+ Badge,
172
+ fetchSource,
173
+ fetchSourceWithContext,
174
+ fetchSourceWithWindow,
175
+ clearSourceCache,
176
+ getCachedSource,
177
+ prefetchSources
178
+ };
@@ -1,12 +1,13 @@
1
1
  "use client";
2
2
  import {
3
- Badge
4
- } from "./chunk-W42PI2OF.js";
3
+ Badge,
4
+ fetchSourceWithWindow
5
+ } from "./chunk-S4IWHBOQ.js";
5
6
  import {
6
7
  buildEditorUrl,
7
8
  useUILintContext,
8
9
  useUILintStore
9
- } from "./chunk-BJD2V2LF.js";
10
+ } from "./chunk-RAPUZC5J.js";
10
11
 
11
12
  // src/components/ui-lint/InspectionPanel.tsx
12
13
  import React, {
@@ -18,97 +19,6 @@ import React, {
18
19
  } from "react";
19
20
  import { createPortal } from "react-dom";
20
21
 
21
- // src/components/ui-lint/source-fetcher.ts
22
- var sourceCache = /* @__PURE__ */ new Map();
23
- var CACHE_TTL = 5 * 60 * 1e3;
24
- var API_ENDPOINT = "/api/.uilint/source";
25
- async function fetchSource(filePath) {
26
- const cached = sourceCache.get(filePath);
27
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
28
- return {
29
- content: cached.content,
30
- relativePath: cached.relativePath
31
- };
32
- }
33
- try {
34
- const response = await fetch(
35
- `${API_ENDPOINT}?path=${encodeURIComponent(filePath)}`
36
- );
37
- if (!response.ok) {
38
- console.warn(`[UILint] Failed to fetch source: ${response.statusText}`);
39
- return null;
40
- }
41
- const data = await response.json();
42
- sourceCache.set(filePath, {
43
- ...data,
44
- fetchedAt: Date.now()
45
- });
46
- return data;
47
- } catch (error) {
48
- console.error("[UILint] Error fetching source:", error);
49
- return null;
50
- }
51
- }
52
- async function fetchSourceWithContext(source, contextLines = 5) {
53
- const result = await fetchSource(source.fileName);
54
- if (!result) return null;
55
- const allLines = result.content.split("\n");
56
- const targetLine = source.lineNumber - 1;
57
- const startLine = Math.max(0, targetLine - contextLines);
58
- const endLine = Math.min(allLines.length, targetLine + contextLines + 1);
59
- return {
60
- lines: allLines.slice(startLine, endLine),
61
- startLine: startLine + 1,
62
- // Back to 1-indexed
63
- highlightLine: source.lineNumber,
64
- relativePath: result.relativePath
65
- };
66
- }
67
- async function fetchSourceWithWindow(source, window2) {
68
- const result = await fetchSource(source.fileName);
69
- if (!result) return null;
70
- const allLines = result.content.split("\n");
71
- const targetLine = source.lineNumber - 1;
72
- const startLine = Math.max(0, targetLine - Math.max(0, window2.linesAbove));
73
- const endLine = Math.min(
74
- allLines.length,
75
- targetLine + Math.max(0, window2.linesBelow) + 1
76
- );
77
- return {
78
- lines: allLines.slice(startLine, endLine),
79
- startLine: startLine + 1,
80
- // Back to 1-indexed
81
- highlightLine: source.lineNumber,
82
- relativePath: result.relativePath
83
- };
84
- }
85
- function clearSourceCache() {
86
- sourceCache.clear();
87
- }
88
- function getCachedSource(filePath) {
89
- const cached = sourceCache.get(filePath);
90
- if (!cached) return null;
91
- if (Date.now() - cached.fetchedAt >= CACHE_TTL) {
92
- sourceCache.delete(filePath);
93
- return null;
94
- }
95
- return {
96
- content: cached.content,
97
- relativePath: cached.relativePath
98
- };
99
- }
100
- async function prefetchSources(filePaths) {
101
- const uniquePaths = [...new Set(filePaths)].filter((path) => {
102
- const cached = sourceCache.get(path);
103
- return !cached || Date.now() - cached.fetchedAt >= CACHE_TTL;
104
- });
105
- const BATCH_SIZE = 5;
106
- for (let i = 0; i < uniquePaths.length; i += BATCH_SIZE) {
107
- const batch = uniquePaths.slice(i, i + BATCH_SIZE);
108
- await Promise.all(batch.map(fetchSource));
109
- }
110
- }
111
-
112
22
  // src/components/ui-lint/code-formatting.ts
113
23
  function leadingWhitespace(s) {
114
24
  const match = s.match(/^[\t ]+/);
@@ -278,11 +188,11 @@ function InspectionPanel() {
278
188
  setInspectedElement(null);
279
189
  };
280
190
  const timer = setTimeout(() => {
281
- document.addEventListener("click", handleClickOutside);
191
+ document.addEventListener("click", handleClickOutside, true);
282
192
  }, 50);
283
193
  return () => {
284
194
  clearTimeout(timer);
285
- document.removeEventListener("click", handleClickOutside);
195
+ document.removeEventListener("click", handleClickOutside, true);
286
196
  };
287
197
  }, [inspectedElement, setInspectedElement]);
288
198
  const isFileLevelIssue = useMemo(() => {
@@ -439,13 +349,21 @@ function InspectionPanel() {
439
349
  );
440
350
  window.open(url, "_blank");
441
351
  }, [inspectedElement, editorBaseDir]);
352
+ const handleUILintInteraction = useCallback(
353
+ (e) => {
354
+ e.stopPropagation();
355
+ },
356
+ []
357
+ );
442
358
  if (!mounted || !inspectedElement) return null;
443
359
  const content = /* @__PURE__ */ jsxs(
444
360
  "div",
445
361
  {
446
362
  ref: popoverRef,
447
363
  "data-ui-lint": true,
448
- onClick: (e) => e.stopPropagation(),
364
+ onMouseDown: handleUILintInteraction,
365
+ onClick: handleUILintInteraction,
366
+ onKeyDown: handleUILintInteraction,
449
367
  style: {
450
368
  position: "fixed",
451
369
  top: position.top,
@@ -462,7 +380,9 @@ function InspectionPanel() {
462
380
  color: STYLES.text,
463
381
  overflow: "hidden",
464
382
  zIndex: 99998,
465
- animation: "uilint-popover-appear 0.15s ease-out"
383
+ animation: "uilint-popover-appear 0.15s ease-out",
384
+ pointerEvents: "auto"
385
+ // Ensure panel is interactive
466
386
  },
467
387
  children: [
468
388
  /* @__PURE__ */ jsx("style", { children: `
@@ -1008,10 +928,5 @@ function CollapseIcon() {
1008
928
  }
1009
929
 
1010
930
  export {
1011
- fetchSource,
1012
- fetchSourceWithContext,
1013
- clearSourceCache,
1014
- getCachedSource,
1015
- prefetchSources,
1016
931
  InspectionPanel
1017
932
  };
package/dist/index.d.ts CHANGED
@@ -169,18 +169,12 @@ declare function useUILintContext(): UILintContextValue;
169
169
  declare function UILintProvider({ children, enabled, }: UILintProviderProps): react_jsx_runtime.JSX.Element;
170
170
 
171
171
  /**
172
- * UILint Toolbar - Improved UX Version
172
+ * UILint Toolbar - Simplified Mode-Based Design
173
173
  *
174
- * Key improvements:
175
- * - Clear visual hierarchy: Primary (toggle) Secondary (issues) → Tertiary (settings)
176
- * - Contextual hints that only show when relevant
177
- * - CSS-based hover/focus states (no inline handlers)
178
- * - Full keyboard navigation with visible focus rings
179
- * - Smooth animations for all state changes
180
- * - Better disabled state communication
181
- * - Expanded panel that doesn't conflict with settings
182
- * - Touch-friendly targets (min 44px)
183
- * - ARIA labels and semantic markup
174
+ * Three distinct modes:
175
+ * 1. Disconnected: Minimal pill with settings only
176
+ * 2. Connected/Idle: Two-segment pill (Start Scanning + Settings)
177
+ * 3. Scanning: Compact floating UI with hint, status dropdown, and stop button
184
178
  */
185
179
 
186
180
  declare function UILintToolbar(): React$1.ReactPortal | null;
package/dist/index.js CHANGED
@@ -1,19 +1,20 @@
1
1
  "use client";
2
2
  import {
3
3
  UILintToolbar
4
- } from "./chunk-EQKEHJI4.js";
4
+ } from "./chunk-OSYIUF52.js";
5
+ import {
6
+ InspectionPanel
7
+ } from "./chunk-UF6KN2JJ.js";
5
8
  import {
6
- InspectionPanel,
7
9
  clearSourceCache,
8
10
  fetchSource,
9
11
  fetchSourceWithContext,
10
12
  getCachedSource,
11
13
  prefetchSources
12
- } from "./chunk-ZZOKTGSU.js";
13
- import "./chunk-W42PI2OF.js";
14
+ } from "./chunk-S4IWHBOQ.js";
14
15
  import {
15
16
  LocatorOverlay
16
- } from "./chunk-YLTHKMTO.js";
17
+ } from "./chunk-LNLTM7N6.js";
17
18
  import {
18
19
  DATA_UILINT_ID,
19
20
  DEFAULT_SETTINGS,
@@ -29,7 +30,7 @@ import {
29
30
  scanDOMForSources,
30
31
  updateElementRects,
31
32
  useUILintContext
32
- } from "./chunk-BJD2V2LF.js";
33
+ } from "./chunk-RAPUZC5J.js";
33
34
 
34
35
  // src/consistency/snapshot.ts
35
36
  var DATA_ELEMENTS_ATTR = "data-elements";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uilint-react",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "React component for AI-powered UI consistency checking",
5
5
  "author": "Peter Suggate",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "node": ">=20.0.0"
35
35
  },
36
36
  "dependencies": {
37
- "uilint-core": "^0.1.45",
37
+ "uilint-core": "^0.1.47",
38
38
  "zustand": "^5.0.5"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,10 +0,0 @@
1
- "use client";
2
- "use client";
3
- import {
4
- InspectionPanel
5
- } from "./chunk-ZZOKTGSU.js";
6
- import "./chunk-W42PI2OF.js";
7
- import "./chunk-BJD2V2LF.js";
8
- export {
9
- InspectionPanel
10
- };
@@ -1,10 +0,0 @@
1
- "use client";
2
- "use client";
3
- import {
4
- UILintToolbar
5
- } from "./chunk-EQKEHJI4.js";
6
- import "./chunk-W42PI2OF.js";
7
- import "./chunk-BJD2V2LF.js";
8
- export {
9
- UILintToolbar
10
- };
@@ -1,81 +0,0 @@
1
- "use client";
2
-
3
- // src/components/ui-lint/Badge.tsx
4
- import { jsx } from "react/jsx-runtime";
5
- var BADGE_COLORS = {
6
- success: "#68d391",
7
- // Soft green
8
- warning: "#f6ad55",
9
- // Warm orange
10
- error: "#ef4444"
11
- // Red (for future use)
12
- };
13
- var FONT_MONO = `"SF Mono", Monaco, "Cascadia Code", monospace`;
14
- function getBadgeTextColor(issueCount) {
15
- if (issueCount === 0) return BADGE_COLORS.success;
16
- return BADGE_COLORS.warning;
17
- }
18
- function getBadgeBackgroundColor(issueCount) {
19
- const color = getBadgeTextColor(issueCount);
20
- return `${color}20`;
21
- }
22
- var BADGE_STYLES = {
23
- default: {
24
- minWidth: "20px",
25
- height: "20px",
26
- padding: "0 6px",
27
- borderRadius: "10px",
28
- fontSize: "11px",
29
- fontWeight: 600,
30
- letterSpacing: "-0.02em"
31
- },
32
- small: {
33
- minWidth: "18px",
34
- height: "18px",
35
- padding: "0 5px",
36
- borderRadius: "9px",
37
- fontSize: "10px",
38
- fontWeight: 700,
39
- letterSpacing: "0"
40
- },
41
- // For file row badges (slightly larger than small)
42
- medium: {
43
- minWidth: "22px",
44
- height: "18px",
45
- padding: "0 6px",
46
- borderRadius: "9px",
47
- fontSize: "10px",
48
- fontWeight: 700,
49
- letterSpacing: "0"
50
- }
51
- };
52
- function Badge({
53
- count,
54
- size = "default",
55
- backgroundColor,
56
- color
57
- }) {
58
- const sizeStyles = BADGE_STYLES[size];
59
- const bgColor = backgroundColor ?? getBadgeBackgroundColor(count);
60
- const textColor = color ?? getBadgeTextColor(count);
61
- return /* @__PURE__ */ jsx(
62
- "span",
63
- {
64
- style: {
65
- display: "inline-flex",
66
- alignItems: "center",
67
- justifyContent: "center",
68
- ...sizeStyles,
69
- backgroundColor: bgColor,
70
- color: textColor,
71
- fontFamily: FONT_MONO
72
- },
73
- children: count
74
- }
75
- );
76
- }
77
-
78
- export {
79
- BADGE_COLORS,
80
- Badge
81
- };