uilint-react 0.1.28 → 0.1.29

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.
@@ -2,11 +2,131 @@
2
2
  "use client";
3
3
  import {
4
4
  useUILintContext
5
- } from "./chunk-5VJ2Q2QW.js";
5
+ } from "./chunk-MDEVC3SQ.js";
6
6
 
7
7
  // src/components/ui-lint/ElementBadges.tsx
8
8
  import React, { useState, useEffect, useCallback, useMemo } from "react";
9
9
  import { createPortal } from "react-dom";
10
+
11
+ // src/components/ui-lint/badge-layout.ts
12
+ var DEFAULT_CONFIG = {
13
+ repulsionForce: 50,
14
+ anchorStrength: 0.3,
15
+ minDistance: 24,
16
+ iterations: 50,
17
+ damping: 0.9
18
+ };
19
+ function computeLayout(positions, config) {
20
+ if (positions.length === 0) return [];
21
+ if (positions.length === 1) {
22
+ return [{ ...positions[0], nudgedX: positions[0].x, nudgedY: positions[0].y }];
23
+ }
24
+ const nodes = positions.map((p) => ({
25
+ position: p,
26
+ nudgedX: p.x,
27
+ nudgedY: p.y,
28
+ velocityX: 0,
29
+ velocityY: 0
30
+ }));
31
+ for (let iter = 0; iter < config.iterations; iter++) {
32
+ for (let i = 0; i < nodes.length; i++) {
33
+ let fx = 0;
34
+ let fy = 0;
35
+ for (let j = 0; j < nodes.length; j++) {
36
+ if (i === j) continue;
37
+ const dx = nodes[i].nudgedX - nodes[j].nudgedX;
38
+ const dy = nodes[i].nudgedY - nodes[j].nudgedY;
39
+ const dist = Math.max(Math.hypot(dx, dy), 1);
40
+ if (dist < config.minDistance) {
41
+ const force = config.repulsionForce / (dist * dist);
42
+ fx += dx / dist * force;
43
+ fy += dy / dist * force;
44
+ }
45
+ }
46
+ const anchorDx = positions[i].x - nodes[i].nudgedX;
47
+ const anchorDy = positions[i].y - nodes[i].nudgedY;
48
+ fx += anchorDx * config.anchorStrength;
49
+ fy += anchorDy * config.anchorStrength;
50
+ nodes[i].velocityX = (nodes[i].velocityX + fx) * config.damping;
51
+ nodes[i].velocityY = (nodes[i].velocityY + fy) * config.damping;
52
+ nodes[i].nudgedX += nodes[i].velocityX;
53
+ nodes[i].nudgedY += nodes[i].velocityY;
54
+ }
55
+ }
56
+ return nodes.map((node) => ({
57
+ ...node.position,
58
+ nudgedX: node.nudgedX,
59
+ nudgedY: node.nudgedY
60
+ }));
61
+ }
62
+ var BadgeLayoutBuilder = class _BadgeLayoutBuilder {
63
+ config;
64
+ positions;
65
+ constructor(positions) {
66
+ this.positions = positions;
67
+ this.config = { ...DEFAULT_CONFIG };
68
+ }
69
+ /**
70
+ * Create a new layout builder with badge positions
71
+ */
72
+ static create(positions) {
73
+ return new _BadgeLayoutBuilder(positions);
74
+ }
75
+ /**
76
+ * Set the repulsion force (how strongly badges push apart)
77
+ * Higher values = badges spread more aggressively
78
+ */
79
+ repulsion(force) {
80
+ this.config.repulsionForce = force;
81
+ return this;
82
+ }
83
+ /**
84
+ * Set the anchor strength (how strongly badges stay near origin)
85
+ * Higher values = badges stay closer to their original positions
86
+ */
87
+ anchorStrength(strength) {
88
+ this.config.anchorStrength = strength;
89
+ return this;
90
+ }
91
+ /**
92
+ * Set the minimum distance between badge centers
93
+ * Badges closer than this will be pushed apart
94
+ */
95
+ minDistance(distance) {
96
+ this.config.minDistance = distance;
97
+ return this;
98
+ }
99
+ /**
100
+ * Set the number of simulation iterations
101
+ * More iterations = more stable but slower
102
+ */
103
+ iterations(count) {
104
+ this.config.iterations = count;
105
+ return this;
106
+ }
107
+ /**
108
+ * Set the damping factor (velocity decay per step)
109
+ * Lower values = system settles faster but may be less stable
110
+ */
111
+ damping(factor) {
112
+ this.config.damping = factor;
113
+ return this;
114
+ }
115
+ /**
116
+ * Run the simulation and return nudged positions
117
+ */
118
+ compute() {
119
+ return computeLayout(this.positions, this.config);
120
+ }
121
+ };
122
+ function findNearbyBadges(positions, x, y, threshold) {
123
+ return positions.filter((p) => {
124
+ const dist = Math.hypot(p.nudgedX - x, p.nudgedY - y);
125
+ return dist <= threshold;
126
+ });
127
+ }
128
+
129
+ // src/components/ui-lint/ElementBadges.tsx
10
130
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
131
  var STYLES = {
12
132
  bg: "rgba(17, 24, 39, 0.95)",
@@ -29,260 +149,29 @@ function getScaleFromDistance(distance) {
29
149
  const t = (distance - NEAR_DISTANCE) / (FAR_DISTANCE - NEAR_DISTANCE);
30
150
  return MAX_SCALE - t * (MAX_SCALE - MIN_SCALE);
31
151
  }
32
- var CLUSTER_THRESHOLD = 24;
33
152
  function getBadgeColor(issueCount) {
34
153
  if (issueCount === 0) return STYLES.success;
35
154
  if (issueCount <= 2) return STYLES.warning;
36
155
  return STYLES.error;
37
156
  }
38
- function ElementBadge({
39
- element,
40
- issue,
41
- distance,
42
- onSelect
43
- }) {
44
- const [rect, setRect] = useState(null);
45
- const [isHovered, setIsHovered] = useState(false);
46
- useEffect(() => {
47
- const updateRect = () => {
48
- if (element.element && document.contains(element.element)) {
49
- setRect(element.element.getBoundingClientRect());
50
- } else {
51
- setRect(null);
52
- }
53
- };
54
- updateRect();
55
- let rafId;
56
- const handleUpdate = () => {
57
- updateRect();
58
- rafId = requestAnimationFrame(handleUpdate);
59
- };
60
- rafId = requestAnimationFrame(handleUpdate);
61
- return () => {
62
- cancelAnimationFrame(rafId);
63
- };
64
- }, [element.element]);
65
- const handleClick = useCallback(
66
- (e) => {
67
- e.preventDefault();
68
- e.stopPropagation();
69
- onSelect(element, issue);
70
- },
71
- [element, issue, onSelect]
72
- );
73
- if (!rect) return null;
74
- if (rect.top < -50 || rect.top > window.innerHeight + 50) return null;
75
- if (rect.left < -50 || rect.left > window.innerWidth + 50) return null;
76
- const scale = isHovered ? 1.1 : getScaleFromDistance(distance);
77
- const badgeStyle = {
78
- position: "fixed",
79
- top: rect.top - 8,
80
- left: rect.right - 8,
81
- zIndex: isHovered ? 99999 : 99995,
82
- cursor: "pointer",
83
- transition: "transform 0.1s ease-out",
84
- transform: `scale(${scale})`,
85
- transformOrigin: "center center"
86
- };
87
- return /* @__PURE__ */ jsxs(Fragment, { children: [
88
- isHovered && /* @__PURE__ */ jsx(
89
- "div",
90
- {
91
- style: {
92
- position: "fixed",
93
- top: rect.top - 2,
94
- left: rect.left - 2,
95
- width: rect.width + 4,
96
- height: rect.height + 4,
97
- border: `2px solid ${STYLES.highlight}`,
98
- borderRadius: "4px",
99
- pointerEvents: "none",
100
- zIndex: 99994,
101
- boxShadow: `0 0 0 1px rgba(59, 130, 246, 0.3)`
102
- },
103
- "data-ui-lint": true
104
- }
105
- ),
106
- /* @__PURE__ */ jsxs(
107
- "div",
108
- {
109
- style: badgeStyle,
110
- "data-ui-lint": true,
111
- onMouseEnter: () => setIsHovered(true),
112
- onMouseLeave: () => setIsHovered(false),
113
- onClick: handleClick,
114
- children: [
115
- issue.status === "scanning" && /* @__PURE__ */ jsx(ScanningBadge, {}),
116
- issue.status === "complete" && /* @__PURE__ */ jsx(IssueBadge, { count: issue.issues.length }),
117
- issue.status === "error" && /* @__PURE__ */ jsx(ErrorBadge, {}),
118
- issue.status === "pending" && /* @__PURE__ */ jsx(PendingBadge, {})
119
- ]
120
- }
121
- )
122
- ] });
123
- }
124
- function IssueBadge({ count }) {
125
- const color = getBadgeColor(count);
126
- if (count === 0) {
127
- return /* @__PURE__ */ jsx(
128
- "div",
129
- {
130
- style: {
131
- display: "flex",
132
- alignItems: "center",
133
- justifyContent: "center",
134
- width: "18px",
135
- height: "18px",
136
- borderRadius: "50%",
137
- backgroundColor: color,
138
- boxShadow: STYLES.shadow,
139
- border: `1px solid ${STYLES.border}`
140
- },
141
- children: /* @__PURE__ */ jsx(CheckIcon, {})
142
- }
143
- );
157
+ function formatElementLabel(element) {
158
+ const tag = element.tagName.toLowerCase();
159
+ const source = element.source;
160
+ if (source) {
161
+ const fileName = source.fileName.split("/").pop() || "Unknown";
162
+ return `${tag} > ${fileName}`;
144
163
  }
145
- return /* @__PURE__ */ jsx(
146
- "div",
147
- {
148
- style: {
149
- display: "flex",
150
- alignItems: "center",
151
- justifyContent: "center",
152
- minWidth: "18px",
153
- height: "18px",
154
- padding: "0 5px",
155
- borderRadius: "9px",
156
- backgroundColor: color,
157
- color: STYLES.text,
158
- fontSize: "10px",
159
- fontWeight: 700,
160
- fontFamily: STYLES.font,
161
- boxShadow: STYLES.shadow,
162
- border: `1px solid ${STYLES.border}`
163
- },
164
- children: count > 9 ? "9+" : count
165
- }
166
- );
164
+ const componentName = element.componentStack[0]?.name;
165
+ return componentName ? `${tag} > ${componentName}` : tag;
167
166
  }
168
- function ScanningBadge() {
169
- return /* @__PURE__ */ jsxs(
170
- "div",
171
- {
172
- style: {
173
- display: "flex",
174
- alignItems: "center",
175
- justifyContent: "center",
176
- width: "18px",
177
- height: "18px",
178
- borderRadius: "50%",
179
- backgroundColor: STYLES.bg,
180
- boxShadow: STYLES.shadow,
181
- border: `1px solid ${STYLES.border}`
182
- },
183
- children: [
184
- /* @__PURE__ */ jsx("style", { children: `
185
- @keyframes uilint-badge-spin {
186
- from { transform: rotate(0deg); }
187
- to { transform: rotate(360deg); }
188
- }
189
- ` }),
190
- /* @__PURE__ */ jsx(
191
- "div",
192
- {
193
- style: {
194
- width: "10px",
195
- height: "10px",
196
- border: "2px solid rgba(59, 130, 246, 0.3)",
197
- borderTopColor: "#3B82F6",
198
- borderRadius: "50%",
199
- animation: "uilint-badge-spin 0.8s linear infinite"
200
- }
201
- }
202
- )
203
- ]
204
- }
205
- );
206
- }
207
- function PendingBadge() {
208
- return /* @__PURE__ */ jsx(
209
- "div",
210
- {
211
- style: {
212
- width: "10px",
213
- height: "10px",
214
- borderRadius: "50%",
215
- backgroundColor: "rgba(156, 163, 175, 0.5)",
216
- boxShadow: STYLES.shadow
167
+ var NEARBY_THRESHOLD = 30;
168
+ function BadgeAnimationStyles() {
169
+ return /* @__PURE__ */ jsx("style", { children: `
170
+ @keyframes uilint-badge-spin {
171
+ from { transform: rotate(0deg); }
172
+ to { transform: rotate(360deg); }
217
173
  }
218
- }
219
- );
220
- }
221
- function ErrorBadge() {
222
- return /* @__PURE__ */ jsx(
223
- "div",
224
- {
225
- style: {
226
- display: "flex",
227
- alignItems: "center",
228
- justifyContent: "center",
229
- width: "18px",
230
- height: "18px",
231
- borderRadius: "50%",
232
- backgroundColor: STYLES.error,
233
- boxShadow: STYLES.shadow,
234
- border: `1px solid ${STYLES.border}`
235
- },
236
- children: /* @__PURE__ */ jsx(ExclamationIcon, {})
237
- }
238
- );
239
- }
240
- var UnionFind = class {
241
- parent = /* @__PURE__ */ new Map();
242
- find(x) {
243
- if (!this.parent.has(x)) {
244
- this.parent.set(x, x);
245
- }
246
- if (this.parent.get(x) !== x) {
247
- this.parent.set(x, this.find(this.parent.get(x)));
248
- }
249
- return this.parent.get(x);
250
- }
251
- union(x, y) {
252
- const px = this.find(x);
253
- const py = this.find(y);
254
- if (px !== py) {
255
- this.parent.set(px, py);
256
- }
257
- }
258
- };
259
- function clusterBadges(positions, threshold) {
260
- if (positions.length === 0) return [];
261
- const uf = new UnionFind();
262
- for (let i = 0; i < positions.length; i++) {
263
- for (let j = i + 1; j < positions.length; j++) {
264
- const dist = Math.hypot(
265
- positions[i].x - positions[j].x,
266
- positions[i].y - positions[j].y
267
- );
268
- if (dist <= threshold) {
269
- uf.union(positions[i].element.id, positions[j].element.id);
270
- }
271
- }
272
- }
273
- const clusters = /* @__PURE__ */ new Map();
274
- for (const pos of positions) {
275
- const root = uf.find(pos.element.id);
276
- if (!clusters.has(root)) {
277
- clusters.set(root, []);
278
- }
279
- clusters.get(root).push(pos);
280
- }
281
- return Array.from(clusters.entries()).map(([id, badges]) => {
282
- const centroidX = badges.reduce((sum, b) => sum + b.x, 0) / badges.length;
283
- const centroidY = badges.reduce((sum, b) => sum + b.y, 0) / badges.length;
284
- return { id, badges, centroidX, centroidY };
285
- });
174
+ ` });
286
175
  }
287
176
  function ElementBadges() {
288
177
  const { autoScanState, elementIssuesCache, setInspectedElement } = useUILintContext();
@@ -341,66 +230,61 @@ function ElementBadges() {
341
230
  },
342
231
  [setInspectedElement]
343
232
  );
344
- const clusters = useMemo(
345
- () => clusterBadges(badgePositions, CLUSTER_THRESHOLD),
233
+ const nudgedPositions = useMemo(
234
+ () => BadgeLayoutBuilder.create(badgePositions).minDistance(24).repulsion(50).anchorStrength(0.3).iterations(50).compute(),
346
235
  [badgePositions]
347
236
  );
348
237
  if (!mounted) return null;
349
238
  if (autoScanState.status === "idle") return null;
350
- const content = /* @__PURE__ */ jsx("div", { "data-ui-lint": true, children: clusters.map((cluster) => {
351
- if (cluster.badges.length === 1) {
352
- const { element, issue, x, y } = cluster.badges[0];
353
- const distance = Math.hypot(x - cursorPos.x, y - cursorPos.y);
354
- return /* @__PURE__ */ jsx(
355
- ElementBadge,
356
- {
357
- element,
358
- issue,
359
- distance,
360
- onSelect: handleSelect
361
- },
362
- element.id
363
- );
364
- } else {
239
+ const content = /* @__PURE__ */ jsxs("div", { "data-ui-lint": true, children: [
240
+ /* @__PURE__ */ jsx(BadgeAnimationStyles, {}),
241
+ nudgedPositions.map((nudgedPos) => {
365
242
  const distance = Math.hypot(
366
- cluster.centroidX - cursorPos.x,
367
- cluster.centroidY - cursorPos.y
243
+ nudgedPos.nudgedX - cursorPos.x,
244
+ nudgedPos.nudgedY - cursorPos.y
245
+ );
246
+ const nearbyBadges = findNearbyBadges(
247
+ nudgedPositions,
248
+ nudgedPos.nudgedX,
249
+ nudgedPos.nudgedY,
250
+ NEARBY_THRESHOLD
368
251
  );
369
252
  return /* @__PURE__ */ jsx(
370
- ClusteredBadge,
253
+ NudgedBadge,
371
254
  {
372
- cluster,
255
+ position: nudgedPos,
373
256
  distance,
257
+ nearbyBadges,
258
+ cursorPos,
374
259
  onSelect: handleSelect
375
260
  },
376
- cluster.id
261
+ nudgedPos.element.id
377
262
  );
378
- }
379
- }) });
263
+ })
264
+ ] });
380
265
  return createPortal(content, document.body);
381
266
  }
382
- function ClusteredBadge({ cluster, distance, onSelect }) {
267
+ function NudgedBadge({
268
+ position,
269
+ distance,
270
+ nearbyBadges,
271
+ cursorPos,
272
+ onSelect
273
+ }) {
383
274
  const [isExpanded, setIsExpanded] = useState(false);
384
275
  const [hoveredIndex, setHoveredIndex] = useState(null);
385
276
  const closeTimeoutRef = React.useRef(null);
386
- const badgeSegments = useMemo(() => {
387
- return cluster.badges.map(({ issue }) => {
388
- if (issue.status === "complete") {
389
- const count = issue.issues.length;
390
- return {
391
- type: "count",
392
- count,
393
- color: getBadgeColor(count)
394
- };
395
- } else if (issue.status === "error") {
396
- return { type: "error", color: STYLES.error };
397
- } else if (issue.status === "scanning") {
398
- return { type: "scanning", color: STYLES.highlight };
399
- } else {
400
- return { type: "pending", color: "rgba(156, 163, 175, 0.5)" };
401
- }
402
- });
403
- }, [cluster.badges]);
277
+ const { element, issue, rect, nudgedX, nudgedY } = position;
278
+ const hasNearbyBadges = nearbyBadges.length > 1;
279
+ const badgeColor = useMemo(() => {
280
+ if (issue.status === "error") return STYLES.error;
281
+ if (issue.status === "scanning") return STYLES.highlight;
282
+ if (issue.status === "pending") return "rgba(156, 163, 175, 0.7)";
283
+ if (issue.status === "complete") {
284
+ return getBadgeColor(issue.issues.length);
285
+ }
286
+ return STYLES.success;
287
+ }, [issue]);
404
288
  const handleMouseEnter = useCallback(() => {
405
289
  if (closeTimeoutRef.current) {
406
290
  clearTimeout(closeTimeoutRef.current);
@@ -414,27 +298,58 @@ function ClusteredBadge({ cluster, distance, onSelect }) {
414
298
  setHoveredIndex(null);
415
299
  }, 150);
416
300
  }, []);
417
- const hoveredBadge = hoveredIndex !== null ? cluster.badges[hoveredIndex] : null;
301
+ const handleClick = useCallback(
302
+ (e) => {
303
+ e.preventDefault();
304
+ e.stopPropagation();
305
+ onSelect(element, issue);
306
+ },
307
+ [element, issue, onSelect]
308
+ );
309
+ const hoveredBadge = useMemo(() => {
310
+ if (hoveredIndex === null) return null;
311
+ return nearbyBadges[hoveredIndex] ?? null;
312
+ }, [hoveredIndex, nearbyBadges]);
418
313
  const dropdownStyle = useMemo(() => {
419
- const preferRight = cluster.centroidX < window.innerWidth - 200;
420
- const preferBelow = cluster.centroidY < window.innerHeight - 200;
314
+ const preferRight = nudgedX < window.innerWidth - 220;
315
+ const preferBelow = nudgedY < window.innerHeight - 200;
421
316
  return {
422
317
  position: "fixed",
423
- top: preferBelow ? cluster.centroidY + 12 : void 0,
424
- bottom: preferBelow ? void 0 : window.innerHeight - cluster.centroidY + 12,
425
- left: preferRight ? cluster.centroidX - 8 : void 0,
426
- right: preferRight ? void 0 : window.innerWidth - cluster.centroidX - 8,
318
+ top: preferBelow ? nudgedY + 12 : void 0,
319
+ bottom: preferBelow ? void 0 : window.innerHeight - nudgedY + 12,
320
+ left: preferRight ? nudgedX - 8 : void 0,
321
+ right: preferRight ? void 0 : window.innerWidth - nudgedX - 8,
427
322
  zIndex: 1e5,
428
323
  backgroundColor: STYLES.bg,
429
324
  borderRadius: "8px",
430
325
  border: `1px solid ${STYLES.border}`,
431
326
  boxShadow: "0 4px 20px rgba(0, 0, 0, 0.4)",
432
327
  padding: "4px 0",
433
- minWidth: "180px",
328
+ minWidth: "200px",
434
329
  fontFamily: STYLES.font
435
330
  };
436
- }, [cluster.centroidX, cluster.centroidY]);
331
+ }, [nudgedX, nudgedY]);
332
+ const scale = isExpanded ? 1.1 : getScaleFromDistance(distance);
333
+ const issueCount = issue.status === "complete" ? issue.issues.length : 0;
437
334
  return /* @__PURE__ */ jsxs(Fragment, { children: [
335
+ isExpanded && !hoveredBadge && /* @__PURE__ */ jsx(
336
+ "div",
337
+ {
338
+ style: {
339
+ position: "fixed",
340
+ top: rect.top - 2,
341
+ left: rect.left - 2,
342
+ width: rect.width + 4,
343
+ height: rect.height + 4,
344
+ border: `2px solid ${STYLES.highlight}`,
345
+ borderRadius: "4px",
346
+ pointerEvents: "none",
347
+ zIndex: 99994,
348
+ boxShadow: `0 0 0 1px rgba(59, 130, 246, 0.3)`
349
+ },
350
+ "data-ui-lint": true
351
+ }
352
+ ),
438
353
  hoveredBadge && /* @__PURE__ */ jsx(
439
354
  "div",
440
355
  {
@@ -458,98 +373,79 @@ function ClusteredBadge({ cluster, distance, onSelect }) {
458
373
  {
459
374
  style: {
460
375
  position: "fixed",
461
- top: cluster.centroidY - 9,
462
- left: cluster.centroidX - 9,
376
+ top: nudgedY - 9,
377
+ left: nudgedX - 9,
463
378
  zIndex: isExpanded ? 99999 : 99995,
464
379
  cursor: "pointer",
465
- transition: "transform 0.1s ease-out",
466
- transform: `scale(${isExpanded ? 1.1 : getScaleFromDistance(distance)})`,
380
+ transition: "transform 0.1s ease-out, top 0.15s ease-out, left 0.15s ease-out",
381
+ transform: `scale(${scale})`,
467
382
  transformOrigin: "center center"
468
383
  },
469
384
  "data-ui-lint": true,
470
385
  onMouseEnter: handleMouseEnter,
471
386
  onMouseLeave: handleMouseLeave,
387
+ onClick: handleClick,
472
388
  children: /* @__PURE__ */ jsx(
473
389
  "div",
474
390
  {
475
391
  style: {
476
392
  display: "flex",
477
393
  alignItems: "center",
478
- height: "20px",
479
- borderRadius: "10px",
480
- backgroundColor: STYLES.bg,
394
+ justifyContent: "center",
395
+ width: "18px",
396
+ height: "18px",
397
+ borderRadius: "50%",
398
+ backgroundColor: badgeColor,
481
399
  boxShadow: STYLES.shadow,
482
- border: `1px solid ${STYLES.border}`,
483
- overflow: "hidden"
400
+ border: `1px solid ${STYLES.border}`
484
401
  },
485
- children: badgeSegments.map((segment, index) => /* @__PURE__ */ jsxs(
402
+ children: issue.status === "scanning" ? /* @__PURE__ */ jsx(
403
+ "div",
404
+ {
405
+ style: {
406
+ width: "10px",
407
+ height: "10px",
408
+ border: "2px solid rgba(255, 255, 255, 0.3)",
409
+ borderTopColor: "#FFFFFF",
410
+ borderRadius: "50%",
411
+ animation: "uilint-badge-spin 0.8s linear infinite"
412
+ }
413
+ }
414
+ ) : issue.status === "error" ? /* @__PURE__ */ jsx(ExclamationIconTiny, {}) : issue.status === "pending" ? /* @__PURE__ */ jsx(
486
415
  "div",
487
416
  {
488
417
  style: {
489
- display: "flex",
490
- alignItems: "center",
491
- justifyContent: "center",
492
- minWidth: "18px",
493
- height: "100%",
494
- padding: "0 4px",
495
- backgroundColor: segment.color,
496
- borderRight: index < badgeSegments.length - 1 ? `1px solid rgba(0, 0, 0, 0.2)` : void 0
418
+ width: "6px",
419
+ height: "6px",
420
+ borderRadius: "50%",
421
+ backgroundColor: "rgba(255, 255, 255, 0.4)"
422
+ }
423
+ }
424
+ ) : issueCount === 0 ? /* @__PURE__ */ jsx(CheckIconTiny, {}) : /* @__PURE__ */ jsx(
425
+ "span",
426
+ {
427
+ style: {
428
+ color: STYLES.text,
429
+ fontSize: "10px",
430
+ fontWeight: 700,
431
+ fontFamily: STYLES.font
497
432
  },
498
- children: [
499
- segment.type === "count" && (segment.count === 0 ? /* @__PURE__ */ jsx(CheckIconTiny, {}) : /* @__PURE__ */ jsx(
500
- "span",
501
- {
502
- style: {
503
- color: STYLES.text,
504
- fontSize: "10px",
505
- fontWeight: 700,
506
- fontFamily: STYLES.font
507
- },
508
- children: segment.count > 9 ? "9+" : segment.count
509
- }
510
- )),
511
- segment.type === "error" && /* @__PURE__ */ jsx(ExclamationIconTiny, {}),
512
- segment.type === "scanning" && /* @__PURE__ */ jsx(
513
- "div",
514
- {
515
- style: {
516
- width: "8px",
517
- height: "8px",
518
- border: "1.5px solid rgba(255, 255, 255, 0.3)",
519
- borderTopColor: "#FFFFFF",
520
- borderRadius: "50%",
521
- animation: "uilint-badge-spin 0.8s linear infinite"
522
- }
523
- }
524
- ),
525
- segment.type === "pending" && /* @__PURE__ */ jsx(
526
- "div",
527
- {
528
- style: {
529
- width: "6px",
530
- height: "6px",
531
- borderRadius: "50%",
532
- backgroundColor: "rgba(255, 255, 255, 0.4)"
533
- }
534
- }
535
- )
536
- ]
537
- },
538
- index
539
- ))
433
+ children: issueCount > 9 ? "9+" : issueCount
434
+ }
435
+ )
540
436
  }
541
437
  )
542
438
  }
543
439
  ),
544
- isExpanded && /* @__PURE__ */ jsx(
440
+ isExpanded && hasNearbyBadges && /* @__PURE__ */ jsx(
545
441
  "div",
546
442
  {
547
443
  style: dropdownStyle,
548
444
  "data-ui-lint": true,
549
445
  onMouseEnter: handleMouseEnter,
550
446
  onMouseLeave: handleMouseLeave,
551
- children: cluster.badges.map((badge, index) => /* @__PURE__ */ jsx(
552
- ClusterDropdownItem,
447
+ children: nearbyBadges.map((badge, index) => /* @__PURE__ */ jsx(
448
+ DropdownItem,
553
449
  {
554
450
  badge,
555
451
  isHovered: hoveredIndex === index,
@@ -563,14 +459,14 @@ function ClusteredBadge({ cluster, distance, onSelect }) {
563
459
  )
564
460
  ] });
565
461
  }
566
- function ClusterDropdownItem({
462
+ function DropdownItem({
567
463
  badge,
568
464
  isHovered,
569
465
  onMouseEnter,
570
466
  onMouseLeave,
571
467
  onClick
572
468
  }) {
573
- const componentName = badge.element.componentStack[0]?.name || badge.element.tagName.toLowerCase();
469
+ const elementLabel = formatElementLabel(badge.element);
574
470
  const issueCount = badge.issue.status === "complete" ? badge.issue.issues.length : 0;
575
471
  const color = getBadgeColor(issueCount);
576
472
  return /* @__PURE__ */ jsxs(
@@ -607,12 +503,12 @@ function ClusterDropdownItem({
607
503
  style: {
608
504
  fontSize: "12px",
609
505
  color: STYLES.text,
610
- maxWidth: "120px",
506
+ maxWidth: "160px",
611
507
  overflow: "hidden",
612
508
  textOverflow: "ellipsis",
613
509
  whiteSpace: "nowrap"
614
510
  },
615
- children: componentName
511
+ children: elementLabel
616
512
  }
617
513
  )
618
514
  ] }),
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
  import {
4
4
  InspectionPanel
5
- } from "./chunk-QYRESGFG.js";
6
- import "./chunk-5VJ2Q2QW.js";
5
+ } from "./chunk-LULWI5RD.js";
6
+ import "./chunk-MDEVC3SQ.js";
7
7
  export {
8
8
  InspectionPanel
9
9
  };
@@ -3,8 +3,8 @@
3
3
  import {
4
4
  InspectedElementHighlight,
5
5
  LocatorOverlay
6
- } from "./chunk-XLIDEQXH.js";
7
- import "./chunk-5VJ2Q2QW.js";
6
+ } from "./chunk-WUEPTJ24.js";
7
+ import "./chunk-MDEVC3SQ.js";
8
8
  export {
9
9
  InspectedElementHighlight,
10
10
  LocatorOverlay
@@ -2,8 +2,8 @@
2
2
  "use client";
3
3
  import {
4
4
  UILintToolbar
5
- } from "./chunk-7X5HN55P.js";
6
- import "./chunk-5VJ2Q2QW.js";
5
+ } from "./chunk-PD24RVVC.js";
6
+ import "./chunk-MDEVC3SQ.js";
7
7
  export {
8
8
  UILintToolbar
9
9
  };
@@ -3,7 +3,7 @@ import {
3
3
  buildEditorUrl,
4
4
  useUILintContext,
5
5
  useUILintStore
6
- } from "./chunk-5VJ2Q2QW.js";
6
+ } from "./chunk-MDEVC3SQ.js";
7
7
 
8
8
  // src/components/ui-lint/InspectionPanel.tsx
9
9
  import { useState, useEffect, useCallback, useMemo } from "react";
@@ -796,10 +796,10 @@ function UILintUI() {
796
796
  const [components, setComponents] = useState(null);
797
797
  useEffect(() => {
798
798
  Promise.all([
799
- import("./UILintToolbar-GMZ6YSI2.js"),
800
- import("./InspectionPanel-4OWY4FVY.js"),
801
- import("./LocatorOverlay-JJDOKNOS.js"),
802
- import("./ElementBadges-HFQNIIO2.js")
799
+ import("./UILintToolbar-2FFHUGZF.js"),
800
+ import("./InspectionPanel-G6JJNNY4.js"),
801
+ import("./LocatorOverlay-MLZ3K4PZ.js"),
802
+ import("./ElementBadges-J4QLKPLG.js")
803
803
  ]).then(([toolbar, panel, locator, badges]) => {
804
804
  setComponents({
805
805
  Toolbar: toolbar.UILintToolbar,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useUILintContext
4
- } from "./chunk-5VJ2Q2QW.js";
4
+ } from "./chunk-MDEVC3SQ.js";
5
5
 
6
6
  // src/components/ui-lint/UILintToolbar.tsx
7
7
  import { useState, useRef, useEffect } from "react";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useUILintContext
4
- } from "./chunk-5VJ2Q2QW.js";
4
+ } from "./chunk-MDEVC3SQ.js";
5
5
 
6
6
  // src/components/ui-lint/LocatorOverlay.tsx
7
7
  import { useState, useEffect, useMemo } from "react";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  UILintToolbar
4
- } from "./chunk-7X5HN55P.js";
4
+ } from "./chunk-PD24RVVC.js";
5
5
  import {
6
6
  InspectionPanel,
7
7
  clearSourceCache,
@@ -9,10 +9,10 @@ import {
9
9
  fetchSourceWithContext,
10
10
  getCachedSource,
11
11
  prefetchSources
12
- } from "./chunk-QYRESGFG.js";
12
+ } from "./chunk-LULWI5RD.js";
13
13
  import {
14
14
  LocatorOverlay
15
- } from "./chunk-XLIDEQXH.js";
15
+ } from "./chunk-WUEPTJ24.js";
16
16
  import {
17
17
  DATA_UILINT_ID,
18
18
  DEFAULT_SETTINGS,
@@ -31,7 +31,7 @@ import {
31
31
  scanDOMForSources,
32
32
  updateElementRects,
33
33
  useUILintContext
34
- } from "./chunk-5VJ2Q2QW.js";
34
+ } from "./chunk-MDEVC3SQ.js";
35
35
 
36
36
  // src/consistency/snapshot.ts
37
37
  var DATA_ELEMENTS_ATTR = "data-elements";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uilint-react",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
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.28",
37
+ "uilint-core": "^0.1.29",
38
38
  "zustand": "^5.0.5"
39
39
  },
40
40
  "peerDependencies": {