uidex 0.2.1 → 0.3.0

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.
Files changed (61) hide show
  1. package/README.md +263 -263
  2. package/dist/cli/cli.cjs +3243 -0
  3. package/dist/cli/cli.cjs.map +1 -0
  4. package/dist/cloud/index.cjs +149 -0
  5. package/dist/cloud/index.cjs.map +1 -0
  6. package/dist/cloud/index.d.cts +108 -0
  7. package/dist/cloud/index.d.ts +108 -0
  8. package/dist/cloud/index.js +120 -0
  9. package/dist/cloud/index.js.map +1 -0
  10. package/dist/headless/index.cjs +3580 -0
  11. package/dist/headless/index.cjs.map +1 -0
  12. package/dist/headless/index.d.cts +214 -0
  13. package/dist/headless/index.d.ts +214 -0
  14. package/dist/headless/index.js +3562 -0
  15. package/dist/headless/index.js.map +1 -0
  16. package/dist/index.cjs +7977 -3301
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +898 -108
  19. package/dist/index.d.ts +898 -108
  20. package/dist/index.js +7934 -3270
  21. package/dist/index.js.map +1 -1
  22. package/dist/playwright/index.cjs +164 -24
  23. package/dist/playwright/index.cjs.map +1 -1
  24. package/dist/playwright/index.d.cts +32 -55
  25. package/dist/playwright/index.d.ts +32 -55
  26. package/dist/playwright/index.js +148 -21
  27. package/dist/playwright/index.js.map +1 -1
  28. package/dist/playwright/reporter.cjs +62 -28
  29. package/dist/playwright/reporter.cjs.map +1 -1
  30. package/dist/playwright/reporter.d.cts +24 -12
  31. package/dist/playwright/reporter.d.ts +24 -12
  32. package/dist/playwright/reporter.js +62 -28
  33. package/dist/playwright/reporter.js.map +1 -1
  34. package/dist/react/index.cjs +7970 -3267
  35. package/dist/react/index.cjs.map +1 -1
  36. package/dist/react/index.d.cts +670 -108
  37. package/dist/react/index.d.ts +670 -108
  38. package/dist/react/index.js +8016 -3274
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/scan/index.cjs +3281 -0
  41. package/dist/scan/index.cjs.map +1 -0
  42. package/dist/scan/index.d.cts +373 -0
  43. package/dist/scan/index.d.ts +373 -0
  44. package/dist/scan/index.js +3224 -0
  45. package/dist/scan/index.js.map +1 -0
  46. package/package.json +74 -56
  47. package/templates/claude/audit.md +37 -0
  48. package/templates/claude/rules.md +212 -0
  49. package/claude/audit-command.md +0 -16
  50. package/claude/rules.md +0 -88
  51. package/dist/core/index.cjs +0 -3490
  52. package/dist/core/index.cjs.map +0 -1
  53. package/dist/core/index.d.cts +0 -441
  54. package/dist/core/index.d.ts +0 -441
  55. package/dist/core/index.global.js +0 -3469
  56. package/dist/core/index.global.js.map +0 -1
  57. package/dist/core/index.js +0 -3444
  58. package/dist/core/index.js.map +0 -1
  59. package/dist/core/style.css +0 -971
  60. package/dist/scripts/cli.cjs +0 -1168
  61. package/uidex.schema.json +0 -93
@@ -1,3469 +0,0 @@
1
- "use strict";
2
- var UidexCore = (() => {
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
-
21
- // src/core/index.ts
22
- var index_exports = {};
23
- __export(index_exports, {
24
- IngestCapture: () => IngestCapture,
25
- Inspector: () => Inspector,
26
- Menu: () => Menu,
27
- Modal: () => Modal,
28
- Overlay: () => Overlay,
29
- UidexUI: () => UidexUI,
30
- classNames: () => classNames,
31
- createUidexUI: () => createUidexUI,
32
- generateSessionId: () => generateSessionId,
33
- getComponents: () => getComponents,
34
- getContrastColor: () => getContrastColor,
35
- getFeatures: () => getFeatures,
36
- getPages: () => getPages,
37
- hexToRgba: () => hexToRgba,
38
- injectStyles: () => injectStyles,
39
- registerComponents: () => registerComponents,
40
- registerFeatures: () => registerFeatures,
41
- registerPages: () => registerPages,
42
- resolveColor: () => resolveColor,
43
- submitFeedback: () => submitFeedback
44
- });
45
-
46
- // src/core/menu.ts
47
- var POSITION_CLASSES = {
48
- "bottom-right": "uidex-bottom-right",
49
- "bottom-left": "uidex-bottom-left",
50
- "top-right": "uidex-top-right",
51
- "top-left": "uidex-top-left"
52
- };
53
- var NS = "http://www.w3.org/2000/svg";
54
- function createIconSVG(children) {
55
- const svg = document.createElementNS(NS, "svg");
56
- svg.setAttribute("class", "uidex-trigger-icon");
57
- svg.setAttribute("viewBox", "0 0 24 24");
58
- svg.setAttribute("fill", "none");
59
- svg.setAttribute("stroke", "currentColor");
60
- svg.setAttribute("stroke-width", "2");
61
- svg.setAttribute("stroke-linecap", "round");
62
- svg.setAttribute("stroke-linejoin", "round");
63
- for (const child of children) svg.appendChild(child);
64
- return svg;
65
- }
66
- function createListSVG() {
67
- const lines = [6, 12, 18].map((y) => {
68
- const line = document.createElementNS(NS, "line");
69
- line.setAttribute("x1", "4");
70
- line.setAttribute("y1", String(y));
71
- line.setAttribute("x2", "20");
72
- line.setAttribute("y2", String(y));
73
- return line;
74
- });
75
- return createIconSVG(lines);
76
- }
77
- function createSelectSVG() {
78
- const path1 = document.createElementNS(NS, "path");
79
- path1.setAttribute("d", "M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z");
80
- const path2 = document.createElementNS(NS, "path");
81
- path2.setAttribute("d", "M13 13l6 6");
82
- return createIconSVG([path1, path2]);
83
- }
84
- function createCopySVG() {
85
- const rect = document.createElementNS(NS, "rect");
86
- rect.setAttribute("x", "9");
87
- rect.setAttribute("y", "9");
88
- rect.setAttribute("width", "13");
89
- rect.setAttribute("height", "13");
90
- rect.setAttribute("rx", "2");
91
- const path = document.createElementNS(NS, "path");
92
- path.setAttribute("d", "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1");
93
- return createIconSVG([rect, path]);
94
- }
95
- function createFeedbackSVG() {
96
- const path = document.createElementNS(NS, "path");
97
- path.setAttribute("d", "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z");
98
- return createIconSVG([path]);
99
- }
100
- var Menu = class {
101
- container = null;
102
- group = null;
103
- expandBtn = null;
104
- inspectBtn = null;
105
- copyBtn = null;
106
- feedbackBtn = null;
107
- options;
108
- // Drag state
109
- isDragging = false;
110
- dragStartX = 0;
111
- dragStartY = 0;
112
- dragStartLeft = 0;
113
- dragStartTop = 0;
114
- hasDragged = false;
115
- boundPointerDown;
116
- boundPointerMove;
117
- boundPointerUp;
118
- constructor(options = {}) {
119
- this.options = options;
120
- this.boundPointerDown = this.handlePointerDown.bind(this);
121
- this.boundPointerMove = this.handlePointerMove.bind(this);
122
- this.boundPointerUp = this.handlePointerUp.bind(this);
123
- }
124
- getContainer() {
125
- return this.container;
126
- }
127
- /** Update a button's active state. */
128
- setButtonActive(btn, active) {
129
- if (!btn) return;
130
- btn.classList.toggle("uidex-trigger-btn--active", active);
131
- btn.setAttribute("aria-pressed", String(active));
132
- }
133
- setInspectActive(active) {
134
- this.setButtonActive(this.inspectBtn, active);
135
- }
136
- setCopyActive(active) {
137
- this.setButtonActive(this.copyBtn, active);
138
- }
139
- setFeedbackActive(active) {
140
- this.setButtonActive(this.feedbackBtn, active);
141
- }
142
- createButton(modifier, ariaLabel, title, icon, onClick, toggle = false) {
143
- const btn = document.createElement("button");
144
- btn.className = `uidex-trigger-btn uidex-trigger-btn--${modifier}`;
145
- btn.setAttribute("aria-label", ariaLabel);
146
- if (toggle) btn.setAttribute("aria-pressed", "false");
147
- btn.title = title;
148
- btn.appendChild(icon);
149
- btn.addEventListener("click", () => {
150
- if (this.hasDragged) return;
151
- onClick();
152
- });
153
- return btn;
154
- }
155
- create() {
156
- this.container = document.createElement("div");
157
- this.container.className = "uidex-container";
158
- this.group = document.createElement("div");
159
- this.group.className = "uidex-trigger-group";
160
- this.inspectBtn = this.createButton("inspect", "Toggle select mode", "Select mode", createSelectSVG(), () => this.options.onInspectToggle?.(), true);
161
- this.copyBtn = this.createButton("copy", "Toggle copy mode", "Copy mode", createCopySVG(), () => this.options.onCopyToggle?.(), true);
162
- this.feedbackBtn = this.createButton("feedback", "Toggle feedback mode", "Feedback mode", createFeedbackSVG(), () => this.options.onFeedbackToggle?.(), true);
163
- this.expandBtn = this.createButton("expand", "Open panel", "Open panel", createListSVG(), () => this.options.onExpandClick?.());
164
- const buttons = [this.inspectBtn, this.copyBtn, this.feedbackBtn, this.expandBtn];
165
- for (let i = 0; i < buttons.length; i++) {
166
- if (i > 0) {
167
- const divider = document.createElement("div");
168
- divider.className = "uidex-trigger-divider";
169
- this.group.appendChild(divider);
170
- }
171
- this.group.appendChild(buttons[i]);
172
- }
173
- this.group.addEventListener("pointerdown", this.boundPointerDown);
174
- this.container.appendChild(this.group);
175
- return this.container;
176
- }
177
- destroy() {
178
- document.removeEventListener("pointermove", this.boundPointerMove);
179
- document.removeEventListener("pointerup", this.boundPointerUp);
180
- if (this.container && this.container.parentNode) {
181
- this.container.parentNode.removeChild(this.container);
182
- }
183
- this.container = null;
184
- this.group = null;
185
- this.expandBtn = null;
186
- this.inspectBtn = null;
187
- this.copyBtn = null;
188
- this.feedbackBtn = null;
189
- }
190
- // ——— Drag handling ———
191
- handlePointerDown(e) {
192
- if (!this.container) return;
193
- this.hasDragged = false;
194
- this.isDragging = true;
195
- this.dragStartX = e.clientX;
196
- this.dragStartY = e.clientY;
197
- const rect = this.container.getBoundingClientRect();
198
- this.dragStartLeft = rect.left;
199
- this.dragStartTop = rect.top;
200
- this.container.classList.add("uidex-container--dragging");
201
- document.addEventListener("pointermove", this.boundPointerMove);
202
- document.addEventListener("pointerup", this.boundPointerUp);
203
- }
204
- handlePointerMove(e) {
205
- if (!this.isDragging || !this.container) return;
206
- const dx = e.clientX - this.dragStartX;
207
- const dy = e.clientY - this.dragStartY;
208
- if (!this.hasDragged && Math.abs(dx) < 4 && Math.abs(dy) < 4) return;
209
- this.hasDragged = true;
210
- const newLeft = this.dragStartLeft + dx;
211
- const newTop = this.dragStartTop + dy;
212
- this.container.classList.remove(
213
- "uidex-bottom-right",
214
- "uidex-bottom-left",
215
- "uidex-top-right",
216
- "uidex-top-left"
217
- );
218
- this.container.style.left = `${newLeft}px`;
219
- this.container.style.top = `${newTop}px`;
220
- this.container.style.right = "auto";
221
- this.container.style.bottom = "auto";
222
- }
223
- handlePointerUp() {
224
- if (!this.isDragging || !this.container) return;
225
- this.isDragging = false;
226
- this.container.classList.remove("uidex-container--dragging");
227
- document.removeEventListener("pointermove", this.boundPointerMove);
228
- document.removeEventListener("pointerup", this.boundPointerUp);
229
- if (!this.hasDragged) return;
230
- this.snapToCorner();
231
- }
232
- snapToCorner() {
233
- if (!this.container) return;
234
- const rect = this.container.getBoundingClientRect();
235
- const centerX = rect.left + rect.width / 2;
236
- const centerY = rect.top + rect.height / 2;
237
- const vpW = window.innerWidth;
238
- const vpH = window.innerHeight;
239
- const isRight = centerX > vpW / 2;
240
- const isBottom = centerY > vpH / 2;
241
- let corner;
242
- if (isBottom && isRight) corner = "bottom-right";
243
- else if (isBottom && !isRight) corner = "bottom-left";
244
- else if (!isBottom && isRight) corner = "top-right";
245
- else corner = "top-left";
246
- this.container.style.left = "";
247
- this.container.style.top = "";
248
- this.container.style.right = "";
249
- this.container.style.bottom = "";
250
- this.container.classList.add(POSITION_CLASSES[corner]);
251
- }
252
- };
253
-
254
- // src/core/utils.ts
255
- function classNames(...classes) {
256
- return classes.filter(Boolean).join(" ");
257
- }
258
- function getContrastColor(hexColor) {
259
- const hex = hexColor.replace("#", "");
260
- const r = parseInt(hex.substring(0, 2), 16);
261
- const g = parseInt(hex.substring(2, 4), 16);
262
- const b = parseInt(hex.substring(4, 6), 16);
263
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
264
- return luminance > 0.5 ? "#000000" : "#ffffff";
265
- }
266
- function hexToRgba(hex, alpha) {
267
- const hexValue = hex.replace("#", "");
268
- const r = parseInt(hexValue.substring(0, 2), 16);
269
- const g = parseInt(hexValue.substring(2, 4), 16);
270
- const b = parseInt(hexValue.substring(4, 6), 16);
271
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
272
- }
273
- function resolveColor(color, colorMap) {
274
- if (!color) return void 0;
275
- if (color.startsWith("#")) return color;
276
- return colorMap?.[color] ?? color;
277
- }
278
-
279
- // src/core/overlay.ts
280
- var DEFAULT_COLOR = "#3b82f6";
281
- var DEFAULT_BORDER_STYLE = "solid";
282
- var DEFAULT_BORDER_WIDTH = 2;
283
- var DEFAULT_LABEL_POSITION = "top-left";
284
- var LABEL_POSITION_CLASSES = {
285
- "top-left": "uidex-label-top-left",
286
- "top-right": "uidex-label-top-right",
287
- "bottom-left": "uidex-label-bottom-left",
288
- "bottom-right": "uidex-label-bottom-right"
289
- };
290
- var Overlay = class {
291
- element = null;
292
- labelElement = null;
293
- target = null;
294
- options;
295
- boundUpdatePosition;
296
- rafId = null;
297
- constructor(options = {}) {
298
- this.options = options;
299
- this.boundUpdatePosition = () => {
300
- if (this.rafId !== null) return;
301
- this.rafId = requestAnimationFrame(() => {
302
- this.rafId = null;
303
- this.updatePosition();
304
- });
305
- };
306
- }
307
- show(target, label) {
308
- this.target = target;
309
- if (!this.element) {
310
- this.createElement();
311
- }
312
- if (label !== void 0) {
313
- this.options.label = label;
314
- }
315
- this.updateLabel();
316
- this.updatePosition();
317
- this.addListeners();
318
- if (this.element && !this.element.parentNode) {
319
- document.body.appendChild(this.element);
320
- }
321
- }
322
- hide() {
323
- this.removeListeners();
324
- if (this.element && this.element.parentNode) {
325
- this.element.parentNode.removeChild(this.element);
326
- }
327
- this.target = null;
328
- }
329
- destroy() {
330
- this.hide();
331
- this.element = null;
332
- this.labelElement = null;
333
- }
334
- updateOptions(options) {
335
- this.options = { ...this.options, ...options };
336
- this.updateStyles();
337
- this.updateLabel();
338
- }
339
- createElement() {
340
- this.element = document.createElement("div");
341
- this.element.className = "uidex-overlay";
342
- this.element.setAttribute("data-testid", "annotation-overlay");
343
- this.element.style.position = "fixed";
344
- this.element.style.pointerEvents = "none";
345
- this.element.style.zIndex = "2147483645";
346
- this.labelElement = document.createElement("span");
347
- this.element.appendChild(this.labelElement);
348
- this.updateStyles();
349
- }
350
- updateStyles() {
351
- if (!this.element) return;
352
- const {
353
- color,
354
- borderStyle = DEFAULT_BORDER_STYLE,
355
- borderWidth = DEFAULT_BORDER_WIDTH,
356
- colors
357
- } = this.options;
358
- const resolvedColor = resolveColor(color, colors) ?? DEFAULT_COLOR;
359
- this.element.style.borderColor = resolvedColor;
360
- this.element.style.borderWidth = `${borderWidth}px`;
361
- this.element.style.borderStyle = borderStyle;
362
- }
363
- updateLabel() {
364
- if (!this.labelElement) return;
365
- const {
366
- label,
367
- showLabel = true,
368
- labelPosition = DEFAULT_LABEL_POSITION,
369
- color,
370
- colors
371
- } = this.options;
372
- if (!showLabel || !label) {
373
- this.labelElement.style.display = "none";
374
- return;
375
- }
376
- const resolvedColor = resolveColor(color, colors) ?? DEFAULT_COLOR;
377
- this.labelElement.style.display = "";
378
- this.labelElement.className = classNames(
379
- "uidex-label",
380
- LABEL_POSITION_CLASSES[labelPosition]
381
- );
382
- this.labelElement.style.position = "absolute";
383
- this.labelElement.style.fontFamily = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
384
- this.labelElement.style.fontSize = "11px";
385
- this.labelElement.style.fontWeight = "500";
386
- this.labelElement.style.padding = "2px 6px";
387
- this.labelElement.style.borderRadius = "4px";
388
- this.labelElement.style.whiteSpace = "nowrap";
389
- this.labelElement.style.backgroundColor = resolvedColor;
390
- this.labelElement.style.color = getContrastColor(resolvedColor);
391
- this.labelElement.textContent = label;
392
- this.applyLabelPosition(labelPosition);
393
- }
394
- applyLabelPosition(position) {
395
- if (!this.labelElement) return;
396
- this.labelElement.style.top = "";
397
- this.labelElement.style.bottom = "";
398
- this.labelElement.style.left = "";
399
- this.labelElement.style.right = "";
400
- this.labelElement.style.transform = "";
401
- switch (position) {
402
- case "top-left":
403
- this.labelElement.style.top = "-8px";
404
- this.labelElement.style.left = "-2px";
405
- this.labelElement.style.transform = "translateY(-100%)";
406
- break;
407
- case "top-right":
408
- this.labelElement.style.top = "-8px";
409
- this.labelElement.style.right = "-2px";
410
- this.labelElement.style.transform = "translateY(-100%)";
411
- break;
412
- case "bottom-left":
413
- this.labelElement.style.bottom = "-8px";
414
- this.labelElement.style.left = "-2px";
415
- this.labelElement.style.transform = "translateY(100%)";
416
- break;
417
- case "bottom-right":
418
- this.labelElement.style.bottom = "-8px";
419
- this.labelElement.style.right = "-2px";
420
- this.labelElement.style.transform = "translateY(100%)";
421
- break;
422
- }
423
- }
424
- updatePosition() {
425
- if (!this.element || !this.target) return;
426
- const rect = this.target.getBoundingClientRect();
427
- this.element.style.top = `${rect.top}px`;
428
- this.element.style.left = `${rect.left}px`;
429
- this.element.style.width = `${rect.width}px`;
430
- this.element.style.height = `${rect.height}px`;
431
- this.clampLabel(rect);
432
- }
433
- /** Move the label inside the overlay when there is no room outside. */
434
- clampLabel(rect) {
435
- if (!this.labelElement || this.labelElement.style.display === "none") return;
436
- const {
437
- labelPosition = DEFAULT_LABEL_POSITION
438
- } = this.options;
439
- const isTop = labelPosition === "top-left" || labelPosition === "top-right";
440
- const labelHeight = 20;
441
- if (isTop && rect.top < labelHeight) {
442
- this.labelElement.style.top = "4px";
443
- this.labelElement.style.transform = "";
444
- } else if (!isTop && window.innerHeight - rect.bottom < labelHeight) {
445
- this.labelElement.style.bottom = "4px";
446
- this.labelElement.style.transform = "";
447
- }
448
- }
449
- addListeners() {
450
- window.addEventListener("resize", this.boundUpdatePosition);
451
- window.addEventListener("scroll", this.boundUpdatePosition, { capture: true, passive: true });
452
- }
453
- removeListeners() {
454
- window.removeEventListener("resize", this.boundUpdatePosition);
455
- window.removeEventListener("scroll", this.boundUpdatePosition, { capture: true });
456
- if (this.rafId !== null) {
457
- cancelAnimationFrame(this.rafId);
458
- this.rafId = null;
459
- }
460
- }
461
- };
462
-
463
- // src/core/inspector.ts
464
- var DEFAULT_SHORTCUT = {
465
- key: "u",
466
- shiftKey: true,
467
- metaKey: true
468
- };
469
- var INSPECTING_CLASS = "uidex-inspecting";
470
- var INSPECTING_STYLE_ID = "uidex-inspecting-style";
471
- var INSPECTING_CSS = `body.${INSPECTING_CLASS},body.${INSPECTING_CLASS} *{cursor:crosshair!important}`;
472
- function isEditableTarget(target) {
473
- if (!(target instanceof HTMLElement)) return false;
474
- return target.isContentEditable || target.getAttribute("contenteditable") === "true" || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
475
- }
476
- function matchesShortcut(e, shortcut) {
477
- return e.key.toLowerCase() === shortcut.key.toLowerCase() && !!e.ctrlKey === !!shortcut.ctrlKey && !!e.shiftKey === !!shortcut.shiftKey && !!e.altKey === !!shortcut.altKey && !!e.metaKey === !!shortcut.metaKey;
478
- }
479
- function findAnnotatedAncestor(target) {
480
- if (!(target instanceof Element)) return null;
481
- if (target.closest(".uidex-container")) return null;
482
- const annotated = target.closest("[data-uidex]");
483
- if (!(annotated instanceof HTMLElement)) return null;
484
- const id = annotated.getAttribute("data-uidex");
485
- if (!id) return null;
486
- return { element: annotated, id };
487
- }
488
- var Inspector = class {
489
- active = false;
490
- shortcut;
491
- currentTarget = null;
492
- onHighlight;
493
- onSelect;
494
- onActivate;
495
- onDeactivate;
496
- boundKeyDown;
497
- boundMouseMove;
498
- boundClick;
499
- constructor(options = {}) {
500
- this.shortcut = options.shortcut ?? DEFAULT_SHORTCUT;
501
- this.onHighlight = options.onHighlight;
502
- this.onSelect = options.onSelect;
503
- this.onActivate = options.onActivate;
504
- this.onDeactivate = options.onDeactivate;
505
- this.boundKeyDown = this.handleKeyDown.bind(this);
506
- this.boundMouseMove = this.handleMouseMove.bind(this);
507
- this.boundClick = this.handleClick.bind(this);
508
- }
509
- mount() {
510
- document.addEventListener("keydown", this.boundKeyDown);
511
- if (!document.getElementById(INSPECTING_STYLE_ID)) {
512
- const style = document.createElement("style");
513
- style.id = INSPECTING_STYLE_ID;
514
- style.textContent = INSPECTING_CSS;
515
- document.head.appendChild(style);
516
- }
517
- }
518
- destroy() {
519
- if (this.active) {
520
- this.deactivate();
521
- }
522
- document.removeEventListener("keydown", this.boundKeyDown);
523
- document.getElementById(INSPECTING_STYLE_ID)?.remove();
524
- }
525
- activate() {
526
- if (this.active) return;
527
- this.active = true;
528
- this.currentTarget = null;
529
- document.addEventListener("mousemove", this.boundMouseMove);
530
- document.addEventListener("click", this.boundClick, true);
531
- document.body.classList.add(INSPECTING_CLASS);
532
- this.onActivate?.();
533
- }
534
- deactivate() {
535
- if (!this.active) return;
536
- this.active = false;
537
- this.currentTarget = null;
538
- document.removeEventListener("mousemove", this.boundMouseMove);
539
- document.removeEventListener("click", this.boundClick, true);
540
- document.body.classList.remove(INSPECTING_CLASS);
541
- this.onHighlight?.(null, null);
542
- this.onDeactivate?.();
543
- }
544
- isActive() {
545
- return this.active;
546
- }
547
- handleKeyDown(e) {
548
- if (this.active && e.key === "Escape") {
549
- e.preventDefault();
550
- this.deactivate();
551
- return;
552
- }
553
- if (isEditableTarget(e.target)) return;
554
- if (matchesShortcut(e, this.shortcut)) {
555
- e.preventDefault();
556
- if (this.active) {
557
- this.deactivate();
558
- } else {
559
- this.activate();
560
- }
561
- }
562
- }
563
- handleMouseMove(e) {
564
- const result = findAnnotatedAncestor(e.target);
565
- if (result) {
566
- if (result.element !== this.currentTarget) {
567
- this.currentTarget = result.element;
568
- this.onHighlight?.(result.element, result.id);
569
- }
570
- } else if (this.currentTarget) {
571
- this.currentTarget = null;
572
- this.onHighlight?.(null, null);
573
- }
574
- }
575
- handleClick(e) {
576
- if (!(e.target instanceof Element)) return;
577
- if (e.target.closest(".uidex-container")) return;
578
- e.preventDefault();
579
- e.stopPropagation();
580
- const result = findAnnotatedAncestor(e.target);
581
- if (result) {
582
- this.onSelect?.(result.element, result.id);
583
- }
584
- this.deactivate();
585
- }
586
- };
587
-
588
- // src/core/ingest.ts
589
- var MAX_CONSOLE_LOGS = 50;
590
- var MAX_NETWORK_ERRORS = 20;
591
- var nativeFetch = null;
592
- function safeStringify(value) {
593
- if (typeof value === "string") return value;
594
- try {
595
- return JSON.stringify(value);
596
- } catch {
597
- return String(value);
598
- }
599
- }
600
- var IngestCapture = class {
601
- constructor(captureConsole, captureNetwork) {
602
- this.captureConsole = captureConsole;
603
- this.captureNetwork = captureNetwork;
604
- }
605
- consoleLogs = [];
606
- networkErrors = [];
607
- originalConsoleWarn = null;
608
- originalConsoleError = null;
609
- originalFetch = null;
610
- start() {
611
- this.consoleLogs = [];
612
- this.networkErrors = [];
613
- if (this.captureConsole) this.interceptConsole();
614
- if (this.captureNetwork) this.interceptNetwork();
615
- }
616
- stop() {
617
- this.restoreConsole();
618
- this.restoreNetwork();
619
- }
620
- getConsoleLogs() {
621
- return [...this.consoleLogs];
622
- }
623
- getNetworkErrors() {
624
- return [...this.networkErrors];
625
- }
626
- interceptConsole() {
627
- if (this.originalConsoleWarn) return;
628
- this.originalConsoleWarn = console.warn;
629
- this.originalConsoleError = console.error;
630
- console.warn = (...args) => {
631
- this.addConsoleLog("warn", args);
632
- this.originalConsoleWarn.apply(console, args);
633
- };
634
- console.error = (...args) => {
635
- this.addConsoleLog("error", args);
636
- this.originalConsoleError.apply(console, args);
637
- };
638
- }
639
- addConsoleLog(level, args) {
640
- this.consoleLogs.push({
641
- level,
642
- message: args.map(safeStringify).join(" "),
643
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
644
- });
645
- if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
646
- this.consoleLogs.shift();
647
- }
648
- }
649
- restoreConsole() {
650
- if (this.originalConsoleWarn) {
651
- console.warn = this.originalConsoleWarn;
652
- this.originalConsoleWarn = null;
653
- }
654
- if (this.originalConsoleError) {
655
- console.error = this.originalConsoleError;
656
- this.originalConsoleError = null;
657
- }
658
- }
659
- interceptNetwork() {
660
- if (this.originalFetch) return;
661
- this.originalFetch = window.fetch;
662
- if (!nativeFetch) nativeFetch = this.originalFetch;
663
- window.fetch = async (...args) => {
664
- try {
665
- const response = await this.originalFetch.apply(window, args);
666
- if (!response.ok) {
667
- this.addNetworkError(args[0], args[1]?.method, response.status, response.statusText);
668
- }
669
- return response;
670
- } catch (error) {
671
- this.addNetworkError(
672
- args[0],
673
- args[1]?.method,
674
- null,
675
- error instanceof Error ? error.message : "Network error"
676
- );
677
- throw error;
678
- }
679
- };
680
- }
681
- addNetworkError(input, method, status, statusText) {
682
- let url;
683
- if (typeof input === "string") {
684
- url = input;
685
- } else if (input instanceof URL) {
686
- url = input.href;
687
- } else {
688
- url = input.url;
689
- method ??= input.method;
690
- }
691
- this.networkErrors.push({
692
- url,
693
- method: method ?? "GET",
694
- status,
695
- statusText,
696
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
697
- });
698
- if (this.networkErrors.length > MAX_NETWORK_ERRORS) {
699
- this.networkErrors.shift();
700
- }
701
- }
702
- restoreNetwork() {
703
- if (this.originalFetch) {
704
- window.fetch = this.originalFetch;
705
- this.originalFetch = null;
706
- }
707
- }
708
- };
709
- function generateSessionId() {
710
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
711
- return crypto.randomUUID();
712
- }
713
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
714
- const r = Math.random() * 16 | 0;
715
- const v = c === "x" ? r : r & 3 | 8;
716
- return v.toString(16);
717
- });
718
- }
719
- async function submitFeedback(endpoint, apiKey, report) {
720
- const fetchFn = nativeFetch ?? fetch;
721
- const response = await fetchFn(endpoint, {
722
- method: "POST",
723
- headers: {
724
- "Content-Type": "application/json",
725
- Authorization: `Bearer ${apiKey}`
726
- },
727
- body: JSON.stringify(report)
728
- });
729
- if (!response.ok) {
730
- const text = await response.text().catch(() => "");
731
- throw new Error(`Ingest failed (${response.status}): ${text}`);
732
- }
733
- return response.json();
734
- }
735
-
736
- // src/core/modal.ts
737
- var Modal = class _Modal {
738
- backdrop = null;
739
- sidebar = null;
740
- mainContent = null;
741
- renderTarget = document.body;
742
- boundKeyDown;
743
- data = {
744
- pages: [],
745
- features: [],
746
- components: {},
747
- presentIds: /* @__PURE__ */ new Set()
748
- };
749
- currentView = null;
750
- modalType = null;
751
- sidebarLevel = "root";
752
- expandedGroups = /* @__PURE__ */ new Set();
753
- searchQuery = "";
754
- searchInput = null;
755
- options;
756
- static VIEW_TYPE_TO_LEVEL = {
757
- page: "pages",
758
- feature: "features",
759
- component: "components",
760
- graph: "root"
761
- };
762
- /** Strip the scanner root dir (e.g. "app") so pages display as routes: app → /, app/foo → /foo */
763
- static formatPageDir(dir) {
764
- const parts = dir.split("/");
765
- const route = parts.slice(1).join("/");
766
- return "/" + route;
767
- }
768
- constructor(options = {}) {
769
- this.options = options;
770
- this.boundKeyDown = this.handleKeyDown.bind(this);
771
- }
772
- /** Set the shadow root to render modals into. */
773
- setShadowRoot(root) {
774
- this.renderTarget = root;
775
- }
776
- /** Update the data model. Refreshes sidebar and content if modal is open. */
777
- setData(data) {
778
- this.data = data;
779
- if (this.backdrop) {
780
- this.renderSidebar();
781
- if (this.currentView) {
782
- this.renderMainContent(this.currentView);
783
- }
784
- }
785
- }
786
- /** Open the modal. If already open, navigates to the given view. */
787
- open(view) {
788
- if (this.backdrop && this.modalType === "panel") {
789
- if (view) this.navigateTo(view);
790
- return;
791
- }
792
- this.createShell();
793
- const targetView = view ?? this.getDefaultView();
794
- if (targetView) {
795
- this.navigateTo(targetView);
796
- }
797
- }
798
- /** Navigate to a view within the open modal. */
799
- navigateTo(view) {
800
- this.currentView = view;
801
- if (this.searchQuery) {
802
- this.searchQuery = "";
803
- if (this.searchInput) this.searchInput.value = "";
804
- }
805
- const newLevel = _Modal.VIEW_TYPE_TO_LEVEL[view.type];
806
- const levelChanged = this.sidebarLevel !== newLevel;
807
- if (levelChanged) {
808
- this.sidebarLevel = newLevel;
809
- }
810
- if (view.type === "component") {
811
- this.expandedGroups.add(this.getComponentPrefix(view.id));
812
- }
813
- if (levelChanged || view.type === "component") {
814
- this.renderSidebar();
815
- } else {
816
- this.updateSidebarActive();
817
- }
818
- this.renderMainContent(view);
819
- if (view.type === "component") {
820
- this.options.onComponentNavigate?.(view.id);
821
- }
822
- }
823
- hide() {
824
- if (this.backdrop && this.backdrop.parentNode) {
825
- this.backdrop.parentNode.removeChild(this.backdrop);
826
- }
827
- this.backdrop = null;
828
- this.sidebar = null;
829
- this.mainContent = null;
830
- this.currentView = null;
831
- this.modalType = null;
832
- this.sidebarLevel = "root";
833
- this.expandedGroups = /* @__PURE__ */ new Set();
834
- this.searchQuery = "";
835
- this.searchInput = null;
836
- document.removeEventListener("keydown", this.boundKeyDown);
837
- }
838
- destroy() {
839
- this.hide();
840
- }
841
- isOpen() {
842
- return this.backdrop !== null;
843
- }
844
- // ——— Default view selection ———
845
- getDefaultView() {
846
- const visiblePage = this.data.pages.find(
847
- (p) => p.componentIds.some((id) => this.data.presentIds.has(id))
848
- );
849
- if (visiblePage) return { type: "page", dir: visiblePage.dir };
850
- if (this.data.pages.length > 0)
851
- return { type: "page", dir: this.data.pages[0].dir };
852
- if (this.data.features.length > 0)
853
- return { type: "feature", dir: this.data.features[0].dir };
854
- const firstPresent = [...this.data.presentIds][0];
855
- if (firstPresent) return { type: "component", id: firstPresent };
856
- const firstId = Object.keys(this.data.components)[0];
857
- if (firstId) return { type: "component", id: firstId };
858
- return null;
859
- }
860
- // ——— Shell creation ———
861
- /**
862
- * Create the shared modal frame: backdrop, dialog, header (title + close), and mount.
863
- * Returns the dialog and header elements so callers can append extra header/body content.
864
- */
865
- createModalFrame(opts) {
866
- this.hide();
867
- this.modalType = opts.type;
868
- this.backdrop = document.createElement("div");
869
- this.backdrop.className = "uidex-modal-backdrop";
870
- this.backdrop.addEventListener("click", (e) => {
871
- if (e.target === this.backdrop) this.hide();
872
- });
873
- const dialog = document.createElement("div");
874
- dialog.className = `uidex-modal uidex-modal--${opts.type}`;
875
- dialog.setAttribute("role", "dialog");
876
- dialog.setAttribute("aria-label", opts.ariaLabel);
877
- const header = document.createElement("div");
878
- header.className = "uidex-modal-header";
879
- const title = document.createElement("h2");
880
- title.className = "uidex-modal-title";
881
- title.textContent = opts.title;
882
- header.appendChild(title);
883
- return { dialog, header };
884
- }
885
- /** Finalize modal frame: append close button to header, header to dialog, mount to DOM. */
886
- mountModalFrame(dialog, header) {
887
- const closeBtn = document.createElement("button");
888
- closeBtn.className = "uidex-modal-close";
889
- closeBtn.setAttribute("aria-label", "Close");
890
- closeBtn.textContent = "\xD7";
891
- closeBtn.addEventListener("click", () => this.hide());
892
- header.appendChild(closeBtn);
893
- dialog.appendChild(header);
894
- this.backdrop.appendChild(dialog);
895
- this.renderTarget.appendChild(this.backdrop);
896
- document.addEventListener("keydown", this.boundKeyDown);
897
- }
898
- createShell() {
899
- const { dialog, header } = this.createModalFrame({
900
- type: "panel",
901
- title: "uidex",
902
- ariaLabel: "uidex"
903
- });
904
- this.searchInput = document.createElement("input");
905
- this.searchInput.type = "text";
906
- this.searchInput.className = "uidex-search-input";
907
- this.searchInput.placeholder = "Search\u2026";
908
- this.searchInput.addEventListener("input", () => {
909
- this.searchQuery = this.searchInput.value;
910
- this.renderSidebar();
911
- });
912
- this.searchInput.addEventListener("keydown", (e) => {
913
- if (e.key !== "Escape" || this.searchQuery) {
914
- e.stopPropagation();
915
- }
916
- if (e.key === "Enter") {
917
- const results = this.getSearchResults();
918
- if (results.pages.length > 0) {
919
- this.navigateTo({ type: "page", dir: results.pages[0].dir });
920
- } else if (results.features.length > 0) {
921
- this.navigateTo({ type: "feature", dir: results.features[0].dir });
922
- } else if (results.components.length > 0) {
923
- this.navigateTo({ type: "component", id: results.components[0] });
924
- }
925
- e.preventDefault();
926
- }
927
- if (e.key === "Escape" && this.searchQuery) {
928
- this.searchQuery = "";
929
- this.searchInput.value = "";
930
- this.renderSidebar();
931
- }
932
- });
933
- header.appendChild(this.searchInput);
934
- this.mountModalFrame(dialog, header);
935
- const body = document.createElement("div");
936
- body.className = "uidex-panel-body";
937
- this.sidebar = document.createElement("div");
938
- this.sidebar.className = "uidex-panel-sidebar";
939
- this.renderSidebar();
940
- this.mainContent = document.createElement("div");
941
- this.mainContent.className = "uidex-panel-main";
942
- body.appendChild(this.sidebar);
943
- body.appendChild(this.mainContent);
944
- dialog.appendChild(body);
945
- }
946
- // ——— Sidebar ———
947
- renderSidebar() {
948
- if (!this.sidebar) return;
949
- this.sidebar.innerHTML = "";
950
- if (this.searchQuery) {
951
- this.renderSearchResults();
952
- } else if (this.sidebarLevel === "root") {
953
- this.renderSidebarRoot();
954
- } else {
955
- this.renderSidebarSublevel(this.sidebarLevel);
956
- }
957
- this.updateSidebarActive();
958
- }
959
- renderSidebarRoot() {
960
- if (!this.sidebar) return;
961
- const categories = [];
962
- if (this.data.pages.length > 0) {
963
- categories.push({ label: "Pages", level: "pages", count: this.data.pages.length });
964
- }
965
- if (this.data.features.length > 0) {
966
- categories.push({ label: "Features", level: "features", count: this.data.features.length });
967
- }
968
- const componentCount = Object.keys(this.data.components).length;
969
- if (componentCount > 0) {
970
- categories.push({ label: "Components", level: "components", count: componentCount });
971
- }
972
- for (const cat of categories) {
973
- const item = document.createElement("div");
974
- item.className = `uidex-sidebar-root-item uidex-sidebar-root-item--${cat.level}`;
975
- item.addEventListener("click", () => {
976
- this.sidebarLevel = cat.level;
977
- this.renderSidebar();
978
- if (!this.currentView || !this.currentViewMatchesLevel(cat.level)) {
979
- const firstView = this.getFirstViewForLevel(cat.level);
980
- if (firstView) this.navigateTo(firstView);
981
- }
982
- });
983
- const label = document.createElement("span");
984
- label.className = "uidex-sidebar-root-label";
985
- label.textContent = cat.label;
986
- item.appendChild(label);
987
- const count = document.createElement("span");
988
- count.className = "uidex-sidebar-count";
989
- count.textContent = `(${cat.count})`;
990
- item.appendChild(count);
991
- const chevron = document.createElement("span");
992
- chevron.className = "uidex-sidebar-chevron";
993
- chevron.textContent = "\u203A";
994
- item.appendChild(chevron);
995
- this.sidebar.appendChild(item);
996
- }
997
- const hasRelationships = this.data.pages.length > 0 || this.data.features.length > 0;
998
- if (hasRelationships) {
999
- const divider = document.createElement("div");
1000
- divider.className = "uidex-sidebar-divider";
1001
- this.sidebar.appendChild(divider);
1002
- const mapItem = document.createElement("div");
1003
- mapItem.className = "uidex-sidebar-root-item";
1004
- mapItem.dataset.viewType = "graph";
1005
- mapItem.addEventListener("click", () => this.navigateTo({ type: "graph" }));
1006
- const mapLabel = document.createElement("span");
1007
- mapLabel.className = "uidex-sidebar-root-label";
1008
- mapLabel.textContent = "Map";
1009
- mapItem.appendChild(mapLabel);
1010
- const mapChevron = document.createElement("span");
1011
- mapChevron.className = "uidex-sidebar-chevron";
1012
- mapChevron.textContent = "\u203A";
1013
- mapItem.appendChild(mapChevron);
1014
- this.sidebar.appendChild(mapItem);
1015
- }
1016
- }
1017
- renderSidebarSublevel(level) {
1018
- if (!this.sidebar) return;
1019
- const labelMap = { pages: "Pages", features: "Features", components: "Components" };
1020
- const back = document.createElement("div");
1021
- back.className = "uidex-sidebar-back";
1022
- back.addEventListener("click", () => {
1023
- this.sidebarLevel = "root";
1024
- this.renderSidebar();
1025
- });
1026
- const arrow = document.createElement("span");
1027
- arrow.className = "uidex-sidebar-back-arrow";
1028
- arrow.textContent = "\u2039";
1029
- back.appendChild(arrow);
1030
- const backLabel = document.createElement("span");
1031
- backLabel.textContent = labelMap[level];
1032
- back.appendChild(backLabel);
1033
- this.sidebar.appendChild(back);
1034
- if (level === "pages") {
1035
- for (const page of this.data.pages) {
1036
- const hasVisible = page.componentIds.some((id) => this.data.presentIds.has(id));
1037
- const item = this.createSidebarItem(_Modal.formatPageDir(page.dir), !hasVisible);
1038
- item.classList.add("uidex-sidebar-item--mono", "uidex-sidebar-item--pages");
1039
- item.dataset.viewType = "page";
1040
- item.dataset.viewId = page.dir;
1041
- item.addEventListener("click", () => this.navigateTo({ type: "page", dir: page.dir }));
1042
- this.sidebar.appendChild(item);
1043
- }
1044
- } else if (level === "features") {
1045
- for (const feature of this.data.features) {
1046
- const hasVisible = feature.componentIds.some((id) => this.data.presentIds.has(id));
1047
- const item = this.createSidebarItem(feature.title, !hasVisible);
1048
- item.classList.add("uidex-sidebar-item--features");
1049
- item.dataset.viewType = "feature";
1050
- item.dataset.viewId = feature.dir;
1051
- item.addEventListener("click", () => this.navigateTo({ type: "feature", dir: feature.dir }));
1052
- this.sidebar.appendChild(item);
1053
- }
1054
- } else {
1055
- const groups = this.groupComponentsByPrefix(Object.keys(this.data.components));
1056
- if (groups.size === 1) {
1057
- const onlyKey = groups.keys().next().value;
1058
- this.expandedGroups.add(onlyKey);
1059
- }
1060
- for (const [prefix, ids] of groups) {
1061
- const isExpanded = this.expandedGroups.has(prefix);
1062
- const header = document.createElement("div");
1063
- header.className = "uidex-sidebar-group-header";
1064
- if (isExpanded) header.classList.add("uidex-sidebar-group-header--expanded");
1065
- header.addEventListener("click", () => {
1066
- if (this.expandedGroups.has(prefix)) {
1067
- this.expandedGroups.delete(prefix);
1068
- } else {
1069
- this.expandedGroups.add(prefix);
1070
- }
1071
- this.renderSidebar();
1072
- });
1073
- const chevron = document.createElement("span");
1074
- chevron.className = "uidex-sidebar-group-chevron";
1075
- chevron.textContent = "\u203A";
1076
- header.appendChild(chevron);
1077
- const label = document.createElement("span");
1078
- label.className = "uidex-sidebar-group-label";
1079
- label.textContent = prefix;
1080
- header.appendChild(label);
1081
- const count = document.createElement("span");
1082
- count.className = "uidex-sidebar-group-count";
1083
- count.textContent = `${ids.length}`;
1084
- header.appendChild(count);
1085
- this.sidebar.appendChild(header);
1086
- if (isExpanded) {
1087
- for (const id of ids) {
1088
- const isPresent = this.data.presentIds.has(id);
1089
- const item = this.createSidebarItem(id, !isPresent);
1090
- item.classList.add("uidex-sidebar-item--mono", "uidex-sidebar-item--grouped", "uidex-sidebar-item--components");
1091
- item.dataset.viewType = "component";
1092
- item.dataset.viewId = id;
1093
- item.addEventListener("click", () => this.navigateTo({ type: "component", id }));
1094
- this.sidebar.appendChild(item);
1095
- }
1096
- }
1097
- }
1098
- }
1099
- }
1100
- currentViewMatchesLevel(level) {
1101
- if (!this.currentView) return false;
1102
- return _Modal.VIEW_TYPE_TO_LEVEL[this.currentView.type] === level;
1103
- }
1104
- getFirstViewForLevel(level) {
1105
- if (level === "pages" && this.data.pages.length > 0) {
1106
- return { type: "page", dir: this.data.pages[0].dir };
1107
- }
1108
- if (level === "features" && this.data.features.length > 0) {
1109
- return { type: "feature", dir: this.data.features[0].dir };
1110
- }
1111
- if (level === "components") {
1112
- const firstId = Object.keys(this.data.components)[0];
1113
- if (firstId) return { type: "component", id: firstId };
1114
- }
1115
- return null;
1116
- }
1117
- getComponentPrefix(id) {
1118
- const dashIndex = id.indexOf("-");
1119
- return dashIndex === -1 ? id : id.substring(0, dashIndex);
1120
- }
1121
- groupComponentsByPrefix(ids) {
1122
- const groups = /* @__PURE__ */ new Map();
1123
- for (const id of ids) {
1124
- const prefix = this.getComponentPrefix(id);
1125
- let group = groups.get(prefix);
1126
- if (!group) {
1127
- group = [];
1128
- groups.set(prefix, group);
1129
- }
1130
- group.push(id);
1131
- }
1132
- return groups;
1133
- }
1134
- createSidebarItem(text, dimmed) {
1135
- const item = document.createElement("div");
1136
- item.className = "uidex-sidebar-item";
1137
- if (dimmed) item.classList.add("uidex-sidebar-item--dimmed");
1138
- item.textContent = text;
1139
- return item;
1140
- }
1141
- updateSidebarActive() {
1142
- if (!this.sidebar || !this.currentView) return;
1143
- const mapItem = this.sidebar.querySelector('[data-view-type="graph"]');
1144
- if (mapItem) {
1145
- mapItem.classList.toggle(
1146
- "uidex-sidebar-root-item--active",
1147
- this.currentView.type === "graph"
1148
- );
1149
- }
1150
- if (this.currentView.type === "graph") return;
1151
- const activeId = this.currentView.type === "component" ? this.currentView.id : this.currentView.dir;
1152
- for (const el of this.sidebar.querySelectorAll(".uidex-sidebar-item")) {
1153
- const item = el;
1154
- const matches = item.dataset.viewType === this.currentView.type && item.dataset.viewId === activeId;
1155
- item.classList.toggle("uidex-sidebar-item--active", matches);
1156
- }
1157
- }
1158
- // ——— Search ———
1159
- getSearchResults() {
1160
- const q = this.searchQuery.toLowerCase();
1161
- return {
1162
- pages: this.data.pages.filter(
1163
- (p) => p.title.toLowerCase().includes(q) || _Modal.formatPageDir(p.dir).toLowerCase().includes(q)
1164
- ),
1165
- features: this.data.features.filter(
1166
- (f) => f.title.toLowerCase().includes(q) || f.dir.toLowerCase().includes(q)
1167
- ),
1168
- components: Object.keys(this.data.components).filter((id) => {
1169
- if (id.toLowerCase().includes(q)) return true;
1170
- const doc = this.data.components[id]?.find((l) => l.doc)?.doc;
1171
- return doc ? doc.toLowerCase().includes(q) : false;
1172
- })
1173
- };
1174
- }
1175
- renderSearchResults() {
1176
- if (!this.sidebar) return;
1177
- const results = this.getSearchResults();
1178
- const hasResults = results.pages.length > 0 || results.features.length > 0 || results.components.length > 0;
1179
- if (!hasResults) {
1180
- const empty = document.createElement("div");
1181
- empty.className = "uidex-search-empty";
1182
- empty.textContent = "No results";
1183
- this.sidebar.appendChild(empty);
1184
- return;
1185
- }
1186
- if (results.pages.length > 0) {
1187
- const label = document.createElement("div");
1188
- label.className = "uidex-search-group-label uidex-search-group-label--pages";
1189
- label.textContent = "Pages";
1190
- this.sidebar.appendChild(label);
1191
- for (const page of results.pages) {
1192
- const item = this.createSidebarItem(_Modal.formatPageDir(page.dir), false);
1193
- item.classList.add("uidex-sidebar-item--mono", "uidex-sidebar-item--pages");
1194
- item.addEventListener(
1195
- "click",
1196
- () => this.navigateTo({ type: "page", dir: page.dir })
1197
- );
1198
- this.sidebar.appendChild(item);
1199
- }
1200
- }
1201
- if (results.features.length > 0) {
1202
- const label = document.createElement("div");
1203
- label.className = "uidex-search-group-label uidex-search-group-label--features";
1204
- label.textContent = "Features";
1205
- this.sidebar.appendChild(label);
1206
- for (const feature of results.features) {
1207
- const item = this.createSidebarItem(feature.title, false);
1208
- item.classList.add("uidex-sidebar-item--features");
1209
- item.addEventListener(
1210
- "click",
1211
- () => this.navigateTo({ type: "feature", dir: feature.dir })
1212
- );
1213
- this.sidebar.appendChild(item);
1214
- }
1215
- }
1216
- if (results.components.length > 0) {
1217
- const label = document.createElement("div");
1218
- label.className = "uidex-search-group-label uidex-search-group-label--components";
1219
- label.textContent = "Components";
1220
- this.sidebar.appendChild(label);
1221
- for (const id of results.components) {
1222
- const item = this.createSidebarItem(id, false);
1223
- item.classList.add("uidex-sidebar-item--mono", "uidex-sidebar-item--components");
1224
- item.addEventListener(
1225
- "click",
1226
- () => this.navigateTo({ type: "component", id })
1227
- );
1228
- this.sidebar.appendChild(item);
1229
- }
1230
- }
1231
- }
1232
- // ——— Main content rendering ———
1233
- renderMainContent(view) {
1234
- if (!this.mainContent) return;
1235
- this.mainContent.innerHTML = "";
1236
- this.mainContent.scrollTop = 0;
1237
- switch (view.type) {
1238
- case "page":
1239
- this.renderPageView(view.dir);
1240
- break;
1241
- case "feature":
1242
- this.renderFeatureView(view.dir);
1243
- break;
1244
- case "component":
1245
- this.renderComponentView(view.id);
1246
- break;
1247
- case "graph":
1248
- this.renderGraphView();
1249
- break;
1250
- }
1251
- }
1252
- renderPageView(dir) {
1253
- const page = this.data.pages.find((p) => p.dir === dir);
1254
- if (!page || !this.mainContent) return;
1255
- const title = document.createElement("h3");
1256
- title.className = "uidex-main-title";
1257
- title.textContent = page.title;
1258
- this.mainContent.appendChild(title);
1259
- const dirCode = document.createElement("code");
1260
- dirCode.className = "uidex-modal-code";
1261
- dirCode.textContent = _Modal.formatPageDir(page.dir);
1262
- this.mainContent.appendChild(dirCode);
1263
- if (page.content.trim()) {
1264
- const section = document.createElement("div");
1265
- section.className = "uidex-modal-section uidex-modal-section--spaced";
1266
- section.appendChild(this.renderMarkdown(page.content));
1267
- this.mainContent.appendChild(section);
1268
- }
1269
- const pageComponentSet = new Set(page.componentIds);
1270
- const relatedFeatures = this.data.features.filter(
1271
- (f) => f.componentIds.some((cid) => pageComponentSet.has(cid))
1272
- );
1273
- if (relatedFeatures.length > 0) {
1274
- this.mainContent.appendChild(
1275
- this.createFeatureChipsSection(relatedFeatures)
1276
- );
1277
- }
1278
- if (page.componentIds.length > 0) {
1279
- this.mainContent.appendChild(
1280
- this.createComponentChipsSection(
1281
- page.componentIds,
1282
- (id) => this.navigateTo({ type: "component", id })
1283
- )
1284
- );
1285
- }
1286
- }
1287
- renderFeatureView(dir) {
1288
- const feature = this.data.features.find((f) => f.dir === dir);
1289
- if (!feature || !this.mainContent) return;
1290
- const badge = document.createElement("div");
1291
- badge.className = "uidex-badge uidex-badge--features";
1292
- badge.textContent = "Feature";
1293
- this.mainContent.appendChild(badge);
1294
- const title = document.createElement("h3");
1295
- title.className = "uidex-main-title";
1296
- title.textContent = feature.title;
1297
- this.mainContent.appendChild(title);
1298
- const dirCode = document.createElement("code");
1299
- dirCode.className = "uidex-modal-code";
1300
- dirCode.textContent = feature.dir;
1301
- this.mainContent.appendChild(dirCode);
1302
- if (feature.content.trim()) {
1303
- const section = document.createElement("div");
1304
- section.className = "uidex-modal-section uidex-modal-section--spaced";
1305
- section.appendChild(this.renderMarkdown(feature.content));
1306
- this.mainContent.appendChild(section);
1307
- }
1308
- if (feature.componentIds.length > 0) {
1309
- const absentIds = new Set(
1310
- feature.componentIds.filter((id) => !this.data.presentIds.has(id))
1311
- );
1312
- this.mainContent.appendChild(
1313
- this.createComponentChipsSection(
1314
- feature.componentIds,
1315
- (id) => this.navigateTo({ type: "component", id }),
1316
- absentIds
1317
- )
1318
- );
1319
- }
1320
- }
1321
- renderComponentView(id) {
1322
- if (!this.mainContent) return;
1323
- const locations = this.data.components[id] ?? [];
1324
- const element = this.options.elementGetter?.(id) ?? null;
1325
- const title = document.createElement("h3");
1326
- title.className = "uidex-main-title uidex-main-title--mono";
1327
- title.textContent = id;
1328
- this.mainContent.appendChild(title);
1329
- if (!this.data.presentIds.has(id)) {
1330
- const notice = document.createElement("div");
1331
- notice.className = "uidex-absent-notice";
1332
- notice.textContent = "Not on current page";
1333
- this.mainContent.appendChild(notice);
1334
- }
1335
- const parentFeatures = this.data.features.filter(
1336
- (f) => f.componentIds.includes(id)
1337
- );
1338
- if (parentFeatures.length > 0) {
1339
- this.mainContent.appendChild(
1340
- this.createFeatureChipsSection(parentFeatures)
1341
- );
1342
- }
1343
- const doc = locations.find((l) => l.doc)?.doc;
1344
- if (doc) {
1345
- const docSection = document.createElement("div");
1346
- docSection.className = "uidex-modal-section";
1347
- const docLabel = document.createElement("div");
1348
- docLabel.className = "uidex-modal-label";
1349
- docLabel.textContent = "Description";
1350
- const docText = document.createElement("p");
1351
- docText.className = "uidex-modal-doc";
1352
- docText.textContent = doc;
1353
- docSection.appendChild(docLabel);
1354
- docSection.appendChild(docText);
1355
- this.mainContent.appendChild(docSection);
1356
- }
1357
- if (element) {
1358
- const elSection = document.createElement("div");
1359
- elSection.className = "uidex-modal-section";
1360
- const elLabel = document.createElement("div");
1361
- elLabel.className = "uidex-modal-label";
1362
- elLabel.textContent = "Element";
1363
- const elCode = document.createElement("code");
1364
- elCode.className = "uidex-modal-code";
1365
- elCode.textContent = this.describeElement(element);
1366
- elSection.appendChild(elLabel);
1367
- elSection.appendChild(elCode);
1368
- this.mainContent.appendChild(elSection);
1369
- }
1370
- if (locations.length > 0) {
1371
- const locSection = document.createElement("div");
1372
- locSection.className = "uidex-modal-section";
1373
- const locLabel = document.createElement("div");
1374
- locLabel.className = "uidex-modal-label";
1375
- locLabel.textContent = locations.length === 1 ? "Source" : "Sources";
1376
- locSection.appendChild(locLabel);
1377
- for (const loc of locations) {
1378
- const row = document.createElement("div");
1379
- row.className = "uidex-modal-location";
1380
- const pathText = document.createElement("code");
1381
- pathText.className = "uidex-modal-code";
1382
- pathText.textContent = `${loc.filePath}:${loc.line}`;
1383
- const copyBtn = document.createElement("button");
1384
- copyBtn.className = "uidex-modal-copy";
1385
- copyBtn.textContent = "Copy";
1386
- copyBtn.addEventListener("click", () => {
1387
- navigator.clipboard.writeText(`${loc.filePath}:${loc.line}`).then(() => {
1388
- copyBtn.textContent = "Copied!";
1389
- setTimeout(() => {
1390
- copyBtn.textContent = "Copy";
1391
- }, 1500);
1392
- }).catch(() => {
1393
- });
1394
- });
1395
- row.appendChild(pathText);
1396
- row.appendChild(copyBtn);
1397
- locSection.appendChild(row);
1398
- }
1399
- this.mainContent.appendChild(locSection);
1400
- }
1401
- const env = this.collectEnv();
1402
- const envSection = document.createElement("div");
1403
- envSection.className = "uidex-modal-section";
1404
- const envLabel = document.createElement("div");
1405
- envLabel.className = "uidex-modal-label";
1406
- envLabel.textContent = "Environment";
1407
- envSection.appendChild(envLabel);
1408
- const envItems = [
1409
- `${env.viewport.width} \xD7 ${env.viewport.height} viewport`,
1410
- env.browserName,
1411
- window.location.hostname + window.location.pathname
1412
- ];
1413
- for (const item of envItems) {
1414
- const envLine = document.createElement("div");
1415
- envLine.className = "uidex-modal-env-item";
1416
- envLine.textContent = item;
1417
- envSection.appendChild(envLine);
1418
- }
1419
- this.mainContent.appendChild(envSection);
1420
- }
1421
- // ——— Graph view ———
1422
- renderGraphView() {
1423
- if (!this.mainContent) return;
1424
- const pages = this.data.pages;
1425
- const features = this.data.features;
1426
- const allComponentIds = Object.keys(this.data.components);
1427
- const connectedIds = /* @__PURE__ */ new Set();
1428
- for (const p of pages) {
1429
- for (const cid of p.componentIds) {
1430
- if (this.data.components[cid]) connectedIds.add(cid);
1431
- }
1432
- }
1433
- for (const f of features) {
1434
- for (const cid of f.componentIds) {
1435
- if (this.data.components[cid]) connectedIds.add(cid);
1436
- }
1437
- }
1438
- const shownComponentIds = allComponentIds.filter((id) => connectedIds.has(id));
1439
- const unconnectedCount = allComponentIds.length - shownComponentIds.length;
1440
- if (shownComponentIds.length === 0 && pages.length === 0 && features.length === 0) {
1441
- const empty = document.createElement("div");
1442
- empty.className = "uidex-empty-message";
1443
- empty.textContent = "No relationships to display";
1444
- this.mainContent.appendChild(empty);
1445
- return;
1446
- }
1447
- const SVG_W = 440;
1448
- const NODE_W = 110;
1449
- const NODE_H = 24;
1450
- const NODE_GAP = 6;
1451
- const COL_HEADER_Y = 14;
1452
- const START_Y = 28;
1453
- const columns = [];
1454
- if (pages.length > 0) columns.push({ type: "pages", x: 0 });
1455
- if (features.length > 0) columns.push({ type: "features", x: 0 });
1456
- if (shownComponentIds.length > 0) columns.push({ type: "components", x: 0 });
1457
- const colSpacing = SVG_W / columns.length;
1458
- for (let i = 0; i < columns.length; i++) {
1459
- columns[i].x = colSpacing * i + colSpacing / 2;
1460
- }
1461
- const colX = {};
1462
- for (const c of columns) colX[c.type] = c.x;
1463
- const nodes = [];
1464
- const posOf = {};
1465
- const addNodes = (type, items, cx) => {
1466
- items.forEach((item, i) => {
1467
- const y = START_Y + i * (NODE_H + NODE_GAP);
1468
- nodes.push({ type, key: item.key, label: item.label, x: cx, y });
1469
- posOf[item.key] = { x: cx, y };
1470
- });
1471
- };
1472
- if (colX["pages"] !== void 0) {
1473
- addNodes(
1474
- "page",
1475
- pages.map((p) => ({ key: `page:${p.dir}`, label: _Modal.formatPageDir(p.dir) })),
1476
- colX["pages"]
1477
- );
1478
- }
1479
- if (colX["features"] !== void 0) {
1480
- addNodes(
1481
- "feature",
1482
- features.map((f) => ({ key: `feature:${f.dir}`, label: f.title })),
1483
- colX["features"]
1484
- );
1485
- }
1486
- if (colX["components"] !== void 0) {
1487
- addNodes(
1488
- "component",
1489
- shownComponentIds.map((id) => ({ key: `component:${id}`, label: id })),
1490
- colX["components"]
1491
- );
1492
- }
1493
- const edges = [];
1494
- for (const p of pages) {
1495
- for (const cid of p.componentIds) {
1496
- if (connectedIds.has(cid)) {
1497
- edges.push({ fromKey: `page:${p.dir}`, toKey: `component:${cid}`, type: "page" });
1498
- }
1499
- }
1500
- }
1501
- for (const f of features) {
1502
- for (const cid of f.componentIds) {
1503
- if (connectedIds.has(cid)) {
1504
- edges.push({ fromKey: `feature:${f.dir}`, toKey: `component:${cid}`, type: "feature" });
1505
- }
1506
- }
1507
- }
1508
- const maxItems = Math.max(pages.length, features.length, shownComponentIds.length);
1509
- const svgH = START_Y + maxItems * (NODE_H + NODE_GAP) + 12;
1510
- const NS2 = "http://www.w3.org/2000/svg";
1511
- const svg = document.createElementNS(NS2, "svg");
1512
- svg.setAttribute("viewBox", `0 0 ${SVG_W} ${svgH}`);
1513
- svg.style.width = "100%";
1514
- svg.style.height = "auto";
1515
- svg.classList.add("uidex-graph-svg");
1516
- const headerLabels = {
1517
- pages: "PAGES",
1518
- features: "FEATURES",
1519
- components: "COMPONENTS"
1520
- };
1521
- for (const col of columns) {
1522
- const text = document.createElementNS(NS2, "text");
1523
- text.setAttribute("x", String(col.x));
1524
- text.setAttribute("y", String(COL_HEADER_Y));
1525
- text.setAttribute("text-anchor", "middle");
1526
- text.classList.add("uidex-graph-header", `uidex-graph-header--${col.type}`);
1527
- text.textContent = headerLabels[col.type];
1528
- svg.appendChild(text);
1529
- }
1530
- const edgeGroup = document.createElementNS(NS2, "g");
1531
- for (const edge of edges) {
1532
- const from = posOf[edge.fromKey];
1533
- const to = posOf[edge.toKey];
1534
- if (!from || !to) continue;
1535
- const x1 = from.x + NODE_W / 2;
1536
- const y1 = from.y + NODE_H / 2;
1537
- const x2 = to.x - NODE_W / 2;
1538
- const y2 = to.y + NODE_H / 2;
1539
- const midX = (x1 + x2) / 2;
1540
- const path = document.createElementNS(NS2, "path");
1541
- path.setAttribute(
1542
- "d",
1543
- `M${x1},${y1} C${midX},${y1} ${midX},${y2} ${x2},${y2}`
1544
- );
1545
- path.classList.add("uidex-graph-edge", `uidex-graph-edge--${edge.type}`);
1546
- path.dataset.from = edge.fromKey;
1547
- path.dataset.to = edge.toKey;
1548
- edgeGroup.appendChild(path);
1549
- }
1550
- svg.appendChild(edgeGroup);
1551
- const nodeGroup = document.createElementNS(NS2, "g");
1552
- const truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1553
- for (const node of nodes) {
1554
- const g = document.createElementNS(NS2, "g");
1555
- g.classList.add("uidex-graph-node", `uidex-graph-node--${node.type}`);
1556
- g.dataset.key = node.key;
1557
- const rect = document.createElementNS(NS2, "rect");
1558
- rect.setAttribute("x", String(node.x - NODE_W / 2));
1559
- rect.setAttribute("y", String(node.y));
1560
- rect.setAttribute("width", String(NODE_W));
1561
- rect.setAttribute("height", String(NODE_H));
1562
- g.appendChild(rect);
1563
- const text = document.createElementNS(NS2, "text");
1564
- text.setAttribute("x", String(node.x));
1565
- text.setAttribute("y", String(node.y + NODE_H / 2 + 3.5));
1566
- text.setAttribute("text-anchor", "middle");
1567
- text.classList.add("uidex-graph-label");
1568
- text.textContent = truncate(node.label, 14);
1569
- g.appendChild(text);
1570
- g.addEventListener("click", () => {
1571
- if (node.type === "page") {
1572
- this.navigateTo({ type: "page", dir: node.key.slice(5) });
1573
- } else if (node.type === "feature") {
1574
- this.navigateTo({ type: "feature", dir: node.key.slice(8) });
1575
- } else {
1576
- this.navigateTo({ type: "component", id: node.key.slice(10) });
1577
- }
1578
- });
1579
- g.addEventListener("mouseenter", () => {
1580
- svg.classList.add("uidex-graph--hover");
1581
- g.classList.add("uidex-graph-node--highlighted");
1582
- for (const edge of edgeGroup.children) {
1583
- const el = edge;
1584
- if (el.dataset.from === node.key || el.dataset.to === node.key) {
1585
- el.classList.add("uidex-graph-edge--highlighted");
1586
- const otherKey = el.dataset.from === node.key ? el.dataset.to : el.dataset.from;
1587
- const otherNode = nodeGroup.querySelector(`[data-key="${otherKey}"]`);
1588
- if (otherNode) otherNode.classList.add("uidex-graph-node--highlighted");
1589
- }
1590
- }
1591
- });
1592
- g.addEventListener("mouseleave", () => {
1593
- svg.classList.remove("uidex-graph--hover");
1594
- for (const el of svg.querySelectorAll(".uidex-graph-node--highlighted")) {
1595
- el.classList.remove("uidex-graph-node--highlighted");
1596
- }
1597
- for (const el of svg.querySelectorAll(".uidex-graph-edge--highlighted")) {
1598
- el.classList.remove("uidex-graph-edge--highlighted");
1599
- }
1600
- });
1601
- nodeGroup.appendChild(g);
1602
- }
1603
- svg.appendChild(nodeGroup);
1604
- const title = document.createElement("h3");
1605
- title.className = "uidex-main-title";
1606
- title.textContent = "Map";
1607
- this.mainContent.appendChild(title);
1608
- const container = document.createElement("div");
1609
- container.className = "uidex-graph";
1610
- container.appendChild(svg);
1611
- this.mainContent.appendChild(container);
1612
- if (unconnectedCount > 0) {
1613
- const note = document.createElement("div");
1614
- note.className = "uidex-graph-note";
1615
- note.textContent = `${unconnectedCount} component${unconnectedCount === 1 ? "" : "s"} without page or feature connections`;
1616
- this.mainContent.appendChild(note);
1617
- }
1618
- }
1619
- // ——— Feedback ———
1620
- /** Open a dedicated feedback modal for a specific component. */
1621
- openFeedback(id) {
1622
- this.createFeedbackShell(id);
1623
- }
1624
- createFeedbackShell(id) {
1625
- const locations = this.data.components[id] ?? [];
1626
- const element = this.options.elementGetter?.(id) ?? null;
1627
- const { dialog, header } = this.createModalFrame({
1628
- type: "feedback",
1629
- title: "Feedback",
1630
- ariaLabel: "Feedback"
1631
- });
1632
- const componentLabel = document.createElement("code");
1633
- componentLabel.className = "uidex-modal-code uidex-modal-code--inline";
1634
- componentLabel.textContent = id;
1635
- header.appendChild(componentLabel);
1636
- const spacer = document.createElement("div");
1637
- spacer.className = "uidex-header-spacer";
1638
- header.appendChild(spacer);
1639
- this.mountModalFrame(dialog, header);
1640
- this.mainContent = document.createElement("div");
1641
- this.mainContent.className = "uidex-modal-body";
1642
- this.renderFeedbackContext(id, locations);
1643
- this.renderFeedbackFormContent(id, locations, element);
1644
- dialog.appendChild(this.mainContent);
1645
- }
1646
- /** Render component context (features, pages, doc) above the feedback form. */
1647
- renderFeedbackContext(id, locations) {
1648
- if (!this.mainContent) return;
1649
- let hasContext = false;
1650
- const parentFeatures = this.data.features.filter(
1651
- (f) => f.componentIds.includes(id)
1652
- );
1653
- if (parentFeatures.length > 0) {
1654
- const container = this.createChipContainer();
1655
- for (const feature of parentFeatures) {
1656
- const chip = document.createElement("span");
1657
- chip.className = "uidex-chip uidex-chip--features";
1658
- chip.textContent = feature.title;
1659
- container.appendChild(chip);
1660
- }
1661
- this.mainContent.appendChild(container);
1662
- hasContext = true;
1663
- }
1664
- const parentPage = this.data.pages.find(
1665
- (p) => p.componentIds.includes(id)
1666
- );
1667
- if (parentPage) {
1668
- const container = this.createChipContainer();
1669
- const chip = document.createElement("span");
1670
- chip.className = "uidex-chip uidex-chip--pages";
1671
- chip.textContent = _Modal.formatPageDir(parentPage.dir);
1672
- container.appendChild(chip);
1673
- this.mainContent.appendChild(container);
1674
- hasContext = true;
1675
- }
1676
- const doc = locations.find((l) => l.doc)?.doc;
1677
- if (doc) {
1678
- const docText = document.createElement("p");
1679
- docText.className = "uidex-modal-doc uidex-modal-doc--spaced";
1680
- docText.textContent = doc;
1681
- this.mainContent.appendChild(docText);
1682
- hasContext = true;
1683
- }
1684
- if (hasContext) {
1685
- const divider = document.createElement("div");
1686
- divider.className = "uidex-main-divider";
1687
- this.mainContent.appendChild(divider);
1688
- }
1689
- }
1690
- createChipContainer() {
1691
- const container = document.createElement("div");
1692
- container.className = "uidex-chips uidex-chips--spaced";
1693
- return container;
1694
- }
1695
- renderFeedbackFormContent(id, locations, element) {
1696
- if (!this.mainContent) return;
1697
- const form = document.createElement("form");
1698
- form.className = "uidex-form";
1699
- form.addEventListener("submit", (e) => e.preventDefault());
1700
- const typeSelect = this.createSelect("Type", [
1701
- { value: "bug", label: "Bug Report" },
1702
- { value: "feature", label: "Feature Request" },
1703
- { value: "improvement", label: "Improvement" },
1704
- { value: "question", label: "Question" }
1705
- ]);
1706
- form.appendChild(typeSelect.group);
1707
- const severitySelect = this.createSelect(
1708
- "Severity",
1709
- [
1710
- { value: "low", label: "Low" },
1711
- { value: "medium", label: "Medium" },
1712
- { value: "high", label: "High" },
1713
- { value: "critical", label: "Critical" }
1714
- ],
1715
- "medium"
1716
- );
1717
- form.appendChild(severitySelect.group);
1718
- const titleGroup = this.createFormGroup("Title");
1719
- const titleInput = document.createElement("input");
1720
- titleInput.type = "text";
1721
- titleInput.className = "uidex-form-input";
1722
- titleInput.placeholder = "Brief summary (optional)";
1723
- titleInput.maxLength = 200;
1724
- titleGroup.appendChild(titleInput);
1725
- form.appendChild(titleGroup);
1726
- const descGroup = this.createFormGroup("Description");
1727
- const textarea = document.createElement("textarea");
1728
- textarea.className = "uidex-form-textarea";
1729
- textarea.placeholder = "Describe the issue or idea...";
1730
- textarea.rows = 4;
1731
- descGroup.appendChild(textarea);
1732
- form.appendChild(descGroup);
1733
- let emailInput;
1734
- let nameInput;
1735
- if (this.options.ingest && !this.options.ingest.reporter) {
1736
- const reporterGroup = this.createFormGroup("Reporter");
1737
- nameInput = document.createElement("input");
1738
- nameInput.type = "text";
1739
- nameInput.className = "uidex-form-input";
1740
- nameInput.placeholder = "Name (optional)";
1741
- reporterGroup.appendChild(nameInput);
1742
- emailInput = document.createElement("input");
1743
- emailInput.type = "email";
1744
- emailInput.className = "uidex-form-input";
1745
- emailInput.placeholder = "Email (optional)";
1746
- reporterGroup.appendChild(emailInput);
1747
- form.appendChild(reporterGroup);
1748
- }
1749
- const screenshotGroup = document.createElement("div");
1750
- screenshotGroup.className = "uidex-form-group";
1751
- const screenshotLabel = document.createElement("label");
1752
- screenshotLabel.className = "uidex-form-toggle";
1753
- const screenshotCheckbox = document.createElement("input");
1754
- screenshotCheckbox.type = "checkbox";
1755
- screenshotCheckbox.className = "uidex-form-checkbox";
1756
- screenshotCheckbox.checked = true;
1757
- screenshotLabel.appendChild(screenshotCheckbox);
1758
- const screenshotText = document.createElement("span");
1759
- screenshotText.className = "uidex-form-toggle-text";
1760
- screenshotText.textContent = "Include screenshot";
1761
- screenshotLabel.appendChild(screenshotText);
1762
- screenshotGroup.appendChild(screenshotLabel);
1763
- form.appendChild(screenshotGroup);
1764
- const submitBtn = document.createElement("button");
1765
- submitBtn.type = "button";
1766
- submitBtn.className = "uidex-form-submit";
1767
- submitBtn.textContent = "Submit";
1768
- submitBtn.addEventListener("click", async () => {
1769
- if (!textarea.value.trim()) {
1770
- textarea.focus();
1771
- return;
1772
- }
1773
- submitBtn.disabled = true;
1774
- let screenshot;
1775
- if (screenshotCheckbox.checked) {
1776
- const freshElement = this.options.elementGetter?.(id) ?? element;
1777
- if (freshElement) {
1778
- submitBtn.textContent = "Capturing\u2026";
1779
- screenshot = await this.captureElementScreenshot(freshElement) ?? void 0;
1780
- }
1781
- }
1782
- const env = this.collectEnv();
1783
- const page = this.data.pages.find((p) => p.componentIds.includes(id));
1784
- const { ingest } = this.options;
1785
- const reporterEmail = ingest?.reporter?.email || emailInput?.value.trim() || void 0;
1786
- const reporterName = ingest?.reporter?.name || nameInput?.value.trim() || void 0;
1787
- const titleValue = titleInput.value.trim();
1788
- const report = {
1789
- type: typeSelect.select.value,
1790
- severity: severitySelect.select.value,
1791
- ...titleValue ? { title: titleValue } : {},
1792
- description: textarea.value.trim(),
1793
- componentId: id,
1794
- element: element ? this.describeElement(element) : null,
1795
- sources: locations.map((l) => ({ filePath: l.filePath, line: l.line })),
1796
- url: env.url,
1797
- path: window.location.pathname,
1798
- route: page?.dir ?? null,
1799
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1800
- pageTitle: document.title,
1801
- locale: navigator.language,
1802
- sessionId: this.options.sessionId ?? "",
1803
- viewport: env.viewport,
1804
- screen: env.screen,
1805
- userAgent: env.userAgent,
1806
- screenshot,
1807
- ...reporterEmail ? { reporterEmail } : {},
1808
- ...reporterName ? { reporterName } : {},
1809
- ...ingest?.environment ? { environment: ingest.environment } : {},
1810
- ...ingest?.appVersion ? { appVersion: ingest.appVersion } : {},
1811
- ...ingest?.metadata ? { metadata: ingest.metadata } : {}
1812
- };
1813
- const consoleLogs = this.options.getConsoleLogs?.();
1814
- if (consoleLogs && consoleLogs.length > 0) {
1815
- report.consoleLogs = consoleLogs;
1816
- }
1817
- const networkErrors = this.options.getNetworkErrors?.();
1818
- if (networkErrors && networkErrors.length > 0) {
1819
- report.networkErrors = networkErrors;
1820
- }
1821
- const showSuccess = (autoClose) => {
1822
- submitBtn.textContent = "Submitted!";
1823
- if (autoClose) {
1824
- setTimeout(() => this.hide(), 1500);
1825
- } else {
1826
- setTimeout(() => {
1827
- submitBtn.textContent = "Submit";
1828
- submitBtn.disabled = false;
1829
- }, 1500);
1830
- }
1831
- };
1832
- if (ingest) {
1833
- submitBtn.textContent = "Submitting\u2026";
1834
- try {
1835
- const serverResult = await submitFeedback(
1836
- ingest.endpoint,
1837
- ingest.apiKey,
1838
- report
1839
- );
1840
- this.options.onSubmit?.(report, {
1841
- ok: true,
1842
- id: serverResult.id,
1843
- sequenceNumber: serverResult.sequenceNumber
1844
- });
1845
- showSuccess(true);
1846
- } catch (err) {
1847
- const errorMessage = err instanceof Error ? err.message : "Unknown error";
1848
- console.warn("[uidex] Feedback submission failed:", errorMessage);
1849
- this.options.onSubmit?.(report, { ok: false, error: errorMessage });
1850
- submitBtn.textContent = "Failed \u2014 retry?";
1851
- submitBtn.disabled = false;
1852
- }
1853
- } else {
1854
- if (!this.options.onSubmit) {
1855
- console.log("[uidex] Feedback submitted:", report);
1856
- }
1857
- this.options.onSubmit?.(report, { ok: true, id: "", sequenceNumber: 0 });
1858
- showSuccess(false);
1859
- }
1860
- });
1861
- form.appendChild(submitBtn);
1862
- this.mainContent.appendChild(form);
1863
- }
1864
- // ——— Component chips ———
1865
- createComponentChip(id, onComponentClick, absent) {
1866
- const chip = document.createElement("span");
1867
- chip.className = "uidex-chip uidex-chip--components";
1868
- if (absent) chip.classList.add("uidex-chip--absent");
1869
- chip.textContent = id;
1870
- if (onComponentClick) {
1871
- chip.style.cursor = "pointer";
1872
- chip.addEventListener("click", () => onComponentClick(id));
1873
- }
1874
- return chip;
1875
- }
1876
- createFeatureChipsSection(features) {
1877
- const section = document.createElement("div");
1878
- section.className = "uidex-modal-section";
1879
- const label = document.createElement("div");
1880
- label.className = "uidex-modal-label";
1881
- label.textContent = features.length === 1 ? "Feature" : "Features";
1882
- section.appendChild(label);
1883
- const container = document.createElement("div");
1884
- container.className = "uidex-chips";
1885
- for (const feature of features) {
1886
- const chip = document.createElement("span");
1887
- chip.className = "uidex-chip uidex-chip--features";
1888
- chip.textContent = feature.title;
1889
- chip.style.cursor = "pointer";
1890
- chip.addEventListener(
1891
- "click",
1892
- () => this.navigateTo({ type: "feature", dir: feature.dir })
1893
- );
1894
- container.appendChild(chip);
1895
- }
1896
- section.appendChild(container);
1897
- return section;
1898
- }
1899
- createComponentChipsSection(componentIds, onComponentClick, absentIds) {
1900
- const section = document.createElement("div");
1901
- section.className = "uidex-modal-section";
1902
- const label = document.createElement("div");
1903
- label.className = "uidex-modal-label";
1904
- label.textContent = `Components (${componentIds.length})`;
1905
- section.appendChild(label);
1906
- const container = document.createElement("div");
1907
- container.className = "uidex-chips";
1908
- for (const id of componentIds) {
1909
- container.appendChild(
1910
- this.createComponentChip(id, onComponentClick, absentIds?.has(id))
1911
- );
1912
- }
1913
- section.appendChild(container);
1914
- return section;
1915
- }
1916
- // ——— Form helpers ———
1917
- createFormGroup(labelText) {
1918
- const group = document.createElement("div");
1919
- group.className = "uidex-form-group";
1920
- const label = document.createElement("label");
1921
- label.className = "uidex-form-label";
1922
- label.textContent = labelText;
1923
- group.appendChild(label);
1924
- return group;
1925
- }
1926
- createSelect(labelText, options, defaultValue) {
1927
- const group = this.createFormGroup(labelText);
1928
- const select = document.createElement("select");
1929
- select.className = "uidex-form-select";
1930
- for (const opt of options) {
1931
- const option = document.createElement("option");
1932
- option.value = opt.value;
1933
- option.textContent = opt.label;
1934
- if (opt.value === defaultValue) option.selected = true;
1935
- select.appendChild(option);
1936
- }
1937
- group.appendChild(select);
1938
- return { group, select };
1939
- }
1940
- // ——— Markdown rendering ———
1941
- createChecklistItem(text, checked = false) {
1942
- const li = document.createElement("li");
1943
- li.className = "uidex-checklist-item";
1944
- const label = document.createElement("label");
1945
- label.className = "uidex-checklist-label";
1946
- const checkbox = document.createElement("input");
1947
- checkbox.type = "checkbox";
1948
- checkbox.className = "uidex-checklist-checkbox";
1949
- checkbox.checked = checked;
1950
- label.appendChild(checkbox);
1951
- const span = document.createElement("span");
1952
- span.textContent = text;
1953
- label.appendChild(span);
1954
- li.appendChild(label);
1955
- return li;
1956
- }
1957
- renderMarkdown(content) {
1958
- const container = document.createElement("div");
1959
- container.className = "uidex-page-content";
1960
- const lines = content.split("\n");
1961
- let currentList = null;
1962
- let skippedFirstH1 = false;
1963
- let inAcceptanceSection = false;
1964
- for (const line of lines) {
1965
- const trimmed = line.trim();
1966
- if (!trimmed) {
1967
- currentList = null;
1968
- continue;
1969
- }
1970
- const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)/);
1971
- if (headingMatch) {
1972
- if (!skippedFirstH1 && headingMatch[1].length === 1) {
1973
- skippedFirstH1 = true;
1974
- continue;
1975
- }
1976
- currentList = null;
1977
- inAcceptanceSection = /^acceptance$/i.test(headingMatch[2].trim());
1978
- const level = headingMatch[1].length;
1979
- const tag = ["h1", "h2", "h3", "h4", "h5", "h6"][level - 1];
1980
- const heading = document.createElement(tag);
1981
- heading.textContent = headingMatch[2];
1982
- container.appendChild(heading);
1983
- continue;
1984
- }
1985
- const checkboxMatch = trimmed.match(/^- \[([ xX])\]\s+(.*)/);
1986
- if (checkboxMatch) {
1987
- if (!currentList) {
1988
- currentList = document.createElement("ul");
1989
- currentList.className = "uidex-checklist";
1990
- container.appendChild(currentList);
1991
- }
1992
- currentList.appendChild(
1993
- this.createChecklistItem(checkboxMatch[2], checkboxMatch[1] !== " ")
1994
- );
1995
- continue;
1996
- }
1997
- if (trimmed.startsWith("- ")) {
1998
- if (!currentList) {
1999
- currentList = document.createElement("ul");
2000
- currentList.className = "uidex-checklist";
2001
- container.appendChild(currentList);
2002
- }
2003
- const text = trimmed.slice(2);
2004
- if (inAcceptanceSection) {
2005
- currentList.appendChild(this.createChecklistItem(text));
2006
- } else {
2007
- const li = document.createElement("li");
2008
- li.className = "uidex-checklist-item";
2009
- li.textContent = text;
2010
- currentList.appendChild(li);
2011
- }
2012
- continue;
2013
- }
2014
- currentList = null;
2015
- const p = document.createElement("p");
2016
- p.textContent = trimmed;
2017
- container.appendChild(p);
2018
- }
2019
- return container;
2020
- }
2021
- // ——— Screenshot capture ———
2022
- /**
2023
- * Capture an element by cloning it with inlined computed styles into an
2024
- * SVG foreignObject, rendering to canvas via an <img> data-URI, and
2025
- * returning a PNG data-URL.
2026
- */
2027
- async captureElementScreenshot(element) {
2028
- try {
2029
- const rect = element.getBoundingClientRect();
2030
- const width = Math.ceil(rect.width);
2031
- const height = Math.ceil(rect.height);
2032
- if (width <= 0 || height <= 0 || width > 4096 || height > 4096)
2033
- return null;
2034
- const clone = element.cloneNode(true);
2035
- this.inlineComputedStyles(element, clone);
2036
- const wrapper = document.createElement("div");
2037
- wrapper.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
2038
- wrapper.style.width = `${width}px`;
2039
- wrapper.style.height = `${height}px`;
2040
- wrapper.style.overflow = "hidden";
2041
- wrapper.appendChild(clone);
2042
- const serialized = new XMLSerializer().serializeToString(wrapper);
2043
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"><foreignObject width="100%" height="100%">` + serialized + `</foreignObject></svg>`;
2044
- const img = new Image();
2045
- img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
2046
- return await Promise.race([
2047
- new Promise((resolve) => {
2048
- img.onload = () => {
2049
- try {
2050
- const canvas = document.createElement("canvas");
2051
- canvas.width = width;
2052
- canvas.height = height;
2053
- const ctx = canvas.getContext("2d");
2054
- if (!ctx) return resolve(null);
2055
- ctx.drawImage(img, 0, 0);
2056
- resolve(canvas.toDataURL("image/png"));
2057
- } catch {
2058
- resolve(null);
2059
- }
2060
- };
2061
- img.onerror = () => resolve(null);
2062
- }),
2063
- new Promise((resolve) => setTimeout(() => resolve(null), 5e3))
2064
- ]);
2065
- } catch {
2066
- return null;
2067
- }
2068
- }
2069
- /** Visual CSS properties worth inlining for a screenshot clone. */
2070
- static VISUAL_PROPS = [
2071
- "display",
2072
- "position",
2073
- "box-sizing",
2074
- "width",
2075
- "min-width",
2076
- "max-width",
2077
- "height",
2078
- "min-height",
2079
- "max-height",
2080
- "margin",
2081
- "padding",
2082
- "border",
2083
- "border-radius",
2084
- "overflow",
2085
- "flex-direction",
2086
- "flex-wrap",
2087
- "flex-grow",
2088
- "flex-shrink",
2089
- "flex-basis",
2090
- "align-items",
2091
- "justify-content",
2092
- "gap",
2093
- "grid-template-columns",
2094
- "grid-template-rows",
2095
- "grid-column",
2096
- "grid-row",
2097
- "color",
2098
- "background",
2099
- "background-color",
2100
- "background-image",
2101
- "font-family",
2102
- "font-size",
2103
- "font-weight",
2104
- "font-style",
2105
- "line-height",
2106
- "letter-spacing",
2107
- "text-align",
2108
- "text-decoration",
2109
- "text-transform",
2110
- "white-space",
2111
- "word-break",
2112
- "opacity",
2113
- "visibility",
2114
- "transform",
2115
- "box-shadow",
2116
- "text-shadow",
2117
- "vertical-align",
2118
- "list-style",
2119
- "text-overflow"
2120
- ];
2121
- /** Recursively copy visual computed styles from source onto a cloned target. */
2122
- inlineComputedStyles(source, target) {
2123
- if (source instanceof HTMLElement && target instanceof HTMLElement) {
2124
- const computed = window.getComputedStyle(source);
2125
- for (const prop of _Modal.VISUAL_PROPS) {
2126
- const value = computed.getPropertyValue(prop);
2127
- if (value) {
2128
- target.style.setProperty(prop, value);
2129
- }
2130
- }
2131
- }
2132
- const srcChildren = source.children;
2133
- const tgtChildren = target.children;
2134
- for (let i = 0; i < srcChildren.length; i++) {
2135
- if (tgtChildren[i]) {
2136
- this.inlineComputedStyles(srcChildren[i], tgtChildren[i]);
2137
- }
2138
- }
2139
- }
2140
- // ——— Utilities ———
2141
- collectEnv() {
2142
- return {
2143
- viewport: { width: window.innerWidth, height: window.innerHeight },
2144
- screen: { width: screen.width, height: screen.height },
2145
- userAgent: navigator.userAgent,
2146
- browserName: this.getBrowserName(),
2147
- url: window.location.href
2148
- };
2149
- }
2150
- getBrowserName() {
2151
- const ua = navigator.userAgent;
2152
- if (ua.includes("Firefox/")) return "Firefox";
2153
- if (ua.includes("Edg/")) return "Edge";
2154
- if (ua.includes("Chrome/")) return "Chrome";
2155
- if (ua.includes("Safari/") && !ua.includes("Chrome")) return "Safari";
2156
- return "Unknown browser";
2157
- }
2158
- describeElement(element) {
2159
- const tag = element.tagName.toLowerCase();
2160
- const id = element.id ? `#${element.id}` : "";
2161
- const classes = element.className ? `.${String(element.className).split(/\s+/).join(".")}` : "";
2162
- return `<${tag}${id}${classes}>`;
2163
- }
2164
- handleKeyDown(e) {
2165
- if (e.key === "Escape") {
2166
- this.hide();
2167
- }
2168
- }
2169
- };
2170
-
2171
- // src/core/registry.ts
2172
- var registeredComponents = null;
2173
- var registeredPages = null;
2174
- var registeredFeatures = null;
2175
- function registerComponents(components) {
2176
- registeredComponents = components;
2177
- }
2178
- function getComponents() {
2179
- return registeredComponents;
2180
- }
2181
- function registerPages(pages) {
2182
- registeredPages = pages;
2183
- }
2184
- function getPages() {
2185
- return registeredPages;
2186
- }
2187
- function registerFeatures(features) {
2188
- registeredFeatures = features;
2189
- }
2190
- function getFeatures() {
2191
- return registeredFeatures;
2192
- }
2193
-
2194
- // src/core/css-text.ts
2195
- var cssText = `/* Design tokens \u2014 :host targets the shadow root, :where(:root) for standalone use */
2196
- /* Theme: shadcn lyra \u2014 monochromatic, sharp, monospace */
2197
- :where(:host, :root) {
2198
- /* Colors \u2014 dark theme (lyra neutral) */
2199
- --uidex-color-primary: #e5e5e5;
2200
- --uidex-color-primary-hover: #ffffff;
2201
- --uidex-color-bg: #1c1c1c;
2202
- --uidex-color-bg-elevated: #2e2e2e;
2203
- --uidex-color-text: #f9f9f9;
2204
- --uidex-color-text-strong: #ffffff;
2205
- --uidex-color-text-muted: #ababab; /* chrome: labels, counts, placeholders */
2206
- --uidex-color-text-secondary: #ababab; /* content: doc text, descriptions */
2207
- --uidex-color-surface-hover: rgba(255, 255, 255, 0.08);
2208
- --uidex-color-surface-active: rgba(255, 255, 255, 0.12);
2209
- --uidex-color-border: rgba(255, 255, 255, 0.1);
2210
- --uidex-color-border-light: rgba(255, 255, 255, 0.06);
2211
- --uidex-color-backdrop: rgba(0, 0, 0, 0.6);
2212
-
2213
- /* Category colors */
2214
- --uidex-color-pages: #60a5fa;
2215
- --uidex-color-features: #a78bfa;
2216
- --uidex-color-components: #34d399;
2217
-
2218
- /* Shadows \u2014 minimal, border-driven definition */
2219
- --uidex-shadow-sm: none;
2220
- --uidex-shadow-md: 0 0 0 1px var(--uidex-color-border);
2221
- --uidex-shadow-lg: 0 0 0 1px var(--uidex-color-border);
2222
-
2223
- /* Border radius \u2014 lyra: none */
2224
- --uidex-radius-sm: 0;
2225
- --uidex-radius-md: 0;
2226
- --uidex-radius-lg: 0;
2227
- --uidex-radius-full: 0;
2228
-
2229
- /* Z-index \u2014 high enough to stay above app UI */
2230
- --uidex-z-container: 2147483644;
2231
- --uidex-z-overlay: 2147483645;
2232
- --uidex-z-modal: 2147483646;
2233
-
2234
- /* Typography \u2014 mono-first (lyra). Both stacks are mono by default;
2235
- consumers can override --uidex-font-sans to restore a proportional body font. */
2236
- --uidex-font-sans: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
2237
- --uidex-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
2238
- --uidex-font-size-xs: 11px;
2239
- --uidex-font-size-sm: 12px;
2240
- --uidex-font-size-md: 13px;
2241
- --uidex-font-size-lg: 14px;
2242
-
2243
- /* Transitions */
2244
- --uidex-transition-fast: 0.1s ease;
2245
- --uidex-transition-normal: 0.15s ease;
2246
- --uidex-transition-smooth: 0.2s ease;
2247
- }
2248
-
2249
- /* \u2014\u2014\u2014 Container \u2014\u2014\u2014 */
2250
- .uidex-container {
2251
- position: fixed;
2252
- z-index: var(--uidex-z-container);
2253
- pointer-events: auto;
2254
- font-family: var(--uidex-font-sans);
2255
- -webkit-font-smoothing: antialiased;
2256
- -moz-osx-font-smoothing: grayscale;
2257
- }
2258
-
2259
- .uidex-bottom-right { bottom: 20px; right: 20px; }
2260
- .uidex-bottom-left { bottom: 20px; left: 20px; }
2261
- .uidex-top-right { top: 20px; right: 20px; }
2262
- .uidex-top-left { top: 20px; left: 20px; }
2263
-
2264
- /* \u2014\u2014\u2014 Trigger button group \u2014\u2014\u2014 */
2265
- .uidex-trigger-group {
2266
- display: flex;
2267
- align-items: center;
2268
- border-radius: 0;
2269
- background: var(--uidex-color-bg);
2270
- border: 1px solid var(--uidex-color-border);
2271
- overflow: hidden;
2272
- user-select: none;
2273
- height: 32px;
2274
- }
2275
-
2276
- .uidex-trigger-group:hover {
2277
- border-color: rgba(255, 255, 255, 0.16);
2278
- }
2279
-
2280
- .uidex-trigger-btn {
2281
- display: flex;
2282
- align-items: center;
2283
- justify-content: center;
2284
- gap: 6px;
2285
- height: 100%;
2286
- padding: 0 10px;
2287
- border: none;
2288
- background: transparent;
2289
- color: var(--uidex-color-text-muted);
2290
- font-size: var(--uidex-font-size-sm);
2291
- font-weight: 500;
2292
- font-family: var(--uidex-font-mono);
2293
- cursor: pointer;
2294
- transition: background-color var(--uidex-transition-fast), color var(--uidex-transition-fast);
2295
- }
2296
-
2297
- .uidex-trigger-btn:hover {
2298
- background: var(--uidex-color-surface-hover);
2299
- color: var(--uidex-color-text-strong);
2300
- }
2301
-
2302
- .uidex-trigger-btn:active {
2303
- background: var(--uidex-color-surface-active);
2304
- transform: translateY(1px);
2305
- }
2306
-
2307
- .uidex-trigger-btn--active {
2308
- color: var(--uidex-color-text-strong);
2309
- background: var(--uidex-color-surface-active);
2310
- }
2311
-
2312
- .uidex-trigger-btn--active:hover {
2313
- background: rgba(255, 255, 255, 0.16);
2314
- }
2315
-
2316
- .uidex-trigger-divider {
2317
- width: 1px;
2318
- height: 16px;
2319
- background: var(--uidex-color-border);
2320
- flex-shrink: 0;
2321
- }
2322
-
2323
- .uidex-trigger-icon {
2324
- width: 14px;
2325
- height: 14px;
2326
- flex-shrink: 0;
2327
- }
2328
-
2329
- /* \u2014\u2014\u2014 Draggable \u2014\u2014\u2014 */
2330
- .uidex-container--dragging {
2331
- transition: none !important;
2332
- }
2333
-
2334
- .uidex-container--dragging .uidex-trigger-btn {
2335
- cursor: grabbing !important;
2336
- }
2337
-
2338
- /* \u2014\u2014\u2014 Inspect mode cursor \u2014\u2014\u2014 */
2339
- body.uidex-inspecting,
2340
- body.uidex-inspecting * {
2341
- cursor: crosshair !important;
2342
- }
2343
-
2344
- /* \u2014\u2014\u2014 Modal \u2014\u2014\u2014 */
2345
- .uidex-modal-backdrop {
2346
- position: fixed;
2347
- inset: 0;
2348
- z-index: var(--uidex-z-modal);
2349
- pointer-events: auto;
2350
- display: flex;
2351
- align-items: center;
2352
- justify-content: center;
2353
- background: var(--uidex-color-backdrop);
2354
- }
2355
-
2356
- .uidex-modal {
2357
- background: var(--uidex-color-bg);
2358
- border: 1px solid var(--uidex-color-border);
2359
- border-radius: 0;
2360
- max-width: calc(100vw - 32px);
2361
- max-height: calc(100vh - 64px);
2362
- font-family: var(--uidex-font-sans);
2363
- }
2364
-
2365
- .uidex-modal-header {
2366
- display: flex;
2367
- align-items: center;
2368
- gap: 12px;
2369
- padding: 12px 16px;
2370
- border-bottom: 1px solid var(--uidex-color-border);
2371
- }
2372
-
2373
- .uidex-modal-title {
2374
- margin: 0;
2375
- font-size: var(--uidex-font-size-sm);
2376
- font-weight: 600;
2377
- color: var(--uidex-color-text-strong);
2378
- font-family: var(--uidex-font-mono);
2379
- text-transform: uppercase;
2380
- letter-spacing: 0.05em;
2381
- }
2382
-
2383
- .uidex-modal-close {
2384
- display: flex;
2385
- align-items: center;
2386
- justify-content: center;
2387
- width: 28px;
2388
- height: 28px;
2389
- border: 1px solid transparent;
2390
- border-radius: 0;
2391
- background: transparent;
2392
- color: var(--uidex-color-text-muted);
2393
- font-size: 18px;
2394
- cursor: pointer;
2395
- transition: background-color var(--uidex-transition-fast), color var(--uidex-transition-fast), border-color var(--uidex-transition-fast);
2396
- }
2397
-
2398
- .uidex-modal-close:hover {
2399
- background: var(--uidex-color-surface-hover);
2400
- border-color: var(--uidex-color-border);
2401
- color: var(--uidex-color-text-strong);
2402
- }
2403
-
2404
- .uidex-modal-close:active {
2405
- transform: translateY(1px);
2406
- }
2407
-
2408
- .uidex-modal-body {
2409
- padding: 12px 16px;
2410
- }
2411
-
2412
- .uidex-modal-section {
2413
- margin-bottom: 12px;
2414
- }
2415
-
2416
- .uidex-modal-section:last-child {
2417
- margin-bottom: 0;
2418
- }
2419
-
2420
- .uidex-modal-label {
2421
- font-size: var(--uidex-font-size-xs);
2422
- font-weight: 600;
2423
- text-transform: uppercase;
2424
- letter-spacing: 0.06em;
2425
- color: var(--uidex-color-text-muted);
2426
- margin-bottom: 6px;
2427
- }
2428
-
2429
- .uidex-modal-section--spaced {
2430
- margin-top: 12px;
2431
- }
2432
-
2433
- .uidex-modal-doc {
2434
- margin: 0;
2435
- font-size: var(--uidex-font-size-sm);
2436
- line-height: 1.5;
2437
- color: var(--uidex-color-text-secondary);
2438
- }
2439
-
2440
- .uidex-modal-code {
2441
- display: block;
2442
- font-family: var(--uidex-font-mono);
2443
- font-size: var(--uidex-font-size-sm);
2444
- color: var(--uidex-color-text);
2445
- background: var(--uidex-color-bg-elevated);
2446
- padding: 6px 10px;
2447
- border-radius: 0;
2448
- border: 1px solid var(--uidex-color-border);
2449
- word-break: break-all;
2450
- }
2451
-
2452
- .uidex-modal-location {
2453
- display: flex;
2454
- align-items: center;
2455
- gap: 8px;
2456
- margin-bottom: 6px;
2457
- }
2458
-
2459
- .uidex-modal-location:last-child {
2460
- margin-bottom: 0;
2461
- }
2462
-
2463
- .uidex-modal-location .uidex-modal-code {
2464
- flex: 1;
2465
- min-width: 0;
2466
- }
2467
-
2468
- .uidex-modal-copy {
2469
- flex-shrink: 0;
2470
- padding: 4px 10px;
2471
- border: 1px solid var(--uidex-color-border);
2472
- border-radius: 0;
2473
- background: var(--uidex-color-bg-elevated);
2474
- color: var(--uidex-color-text-secondary);
2475
- font-size: var(--uidex-font-size-sm);
2476
- font-weight: 500;
2477
- font-family: var(--uidex-font-mono);
2478
- cursor: pointer;
2479
- transition: background-color var(--uidex-transition-fast), color var(--uidex-transition-fast);
2480
- white-space: nowrap;
2481
- }
2482
-
2483
- .uidex-modal-copy:hover {
2484
- background: var(--uidex-color-surface-hover);
2485
- color: var(--uidex-color-text-strong);
2486
- }
2487
-
2488
- .uidex-modal-copy:active {
2489
- transform: translateY(1px);
2490
- }
2491
-
2492
- .uidex-modal-env-item {
2493
- font-size: var(--uidex-font-size-sm);
2494
- color: var(--uidex-color-text-secondary);
2495
- line-height: 1.6;
2496
- }
2497
-
2498
- .uidex-empty-message {
2499
- padding: 16px 12px;
2500
- color: var(--uidex-color-text-muted);
2501
- font-size: var(--uidex-font-size-sm);
2502
- text-align: center;
2503
- }
2504
-
2505
- /* \u2014\u2014\u2014 Panel layout (sidebar modal) \u2014\u2014\u2014 */
2506
- .uidex-modal--panel {
2507
- width: calc(100vw - 32px);
2508
- max-width: 720px;
2509
- height: calc(100vh - 64px);
2510
- max-height: 600px;
2511
- display: flex;
2512
- flex-direction: column;
2513
- overflow: hidden;
2514
- }
2515
-
2516
- .uidex-panel-body {
2517
- display: flex;
2518
- flex: 1;
2519
- min-height: 0;
2520
- }
2521
-
2522
- .uidex-panel-sidebar {
2523
- width: 200px;
2524
- flex-shrink: 0;
2525
- border-right: 1px solid var(--uidex-color-border);
2526
- overflow-y: auto;
2527
- padding: 0;
2528
- scrollbar-width: thin;
2529
- scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
2530
- }
2531
-
2532
- .uidex-panel-sidebar::-webkit-scrollbar {
2533
- width: 6px;
2534
- }
2535
-
2536
- .uidex-panel-sidebar::-webkit-scrollbar-track {
2537
- background: transparent;
2538
- }
2539
-
2540
- .uidex-panel-sidebar::-webkit-scrollbar-thumb {
2541
- background: rgba(255, 255, 255, 0.1);
2542
- border-radius: 0;
2543
- }
2544
-
2545
- .uidex-panel-main {
2546
- flex: 1;
2547
- min-width: 0;
2548
- overflow-y: auto;
2549
- padding: 16px 20px;
2550
- scrollbar-width: thin;
2551
- scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
2552
- }
2553
-
2554
- .uidex-panel-main::-webkit-scrollbar {
2555
- width: 6px;
2556
- }
2557
-
2558
- .uidex-panel-main::-webkit-scrollbar-track {
2559
- background: transparent;
2560
- }
2561
-
2562
- .uidex-panel-main::-webkit-scrollbar-thumb {
2563
- background: rgba(255, 255, 255, 0.1);
2564
- border-radius: 0;
2565
- }
2566
-
2567
- /* \u2014\u2014\u2014 Sidebar navigation \u2014\u2014\u2014 */
2568
- .uidex-sidebar-back {
2569
- display: flex;
2570
- align-items: center;
2571
- gap: 6px;
2572
- padding: 6px 8px;
2573
- margin: 0;
2574
- border-bottom: 1px solid var(--uidex-color-border);
2575
- font-size: var(--uidex-font-size-sm);
2576
- font-weight: 600;
2577
- color: var(--uidex-color-text-secondary);
2578
- cursor: pointer;
2579
- transition: color var(--uidex-transition-fast);
2580
- position: sticky;
2581
- top: 0;
2582
- background: var(--uidex-color-bg);
2583
- z-index: 1;
2584
- }
2585
-
2586
- .uidex-sidebar-back:hover {
2587
- color: var(--uidex-color-text-strong);
2588
- }
2589
-
2590
- .uidex-sidebar-back-arrow {
2591
- font-size: 16px;
2592
- line-height: 1;
2593
- }
2594
-
2595
- .uidex-sidebar-root-item {
2596
- display: flex;
2597
- align-items: center;
2598
- gap: 6px;
2599
- padding: 6px 10px;
2600
- border-radius: 0;
2601
- font-size: var(--uidex-font-size-sm);
2602
- font-weight: 500;
2603
- color: var(--uidex-color-text);
2604
- cursor: pointer;
2605
- transition: background-color var(--uidex-transition-fast);
2606
- }
2607
-
2608
- .uidex-sidebar-root-item:hover {
2609
- background: var(--uidex-color-surface-hover);
2610
- }
2611
-
2612
- .uidex-sidebar-root-label {
2613
- flex: 0 0 auto;
2614
- }
2615
-
2616
- .uidex-sidebar-count {
2617
- font-size: var(--uidex-font-size-xs);
2618
- color: var(--uidex-color-text-muted);
2619
- font-weight: 400;
2620
- }
2621
-
2622
- .uidex-sidebar-chevron {
2623
- margin-left: auto;
2624
- font-size: 16px;
2625
- line-height: 1;
2626
- color: var(--uidex-color-text-muted);
2627
- }
2628
-
2629
- .uidex-sidebar-item {
2630
- padding: 4px 10px;
2631
- border-radius: 0;
2632
- font-size: var(--uidex-font-size-sm);
2633
- color: var(--uidex-color-text);
2634
- cursor: pointer;
2635
- transition: background-color var(--uidex-transition-fast);
2636
- white-space: nowrap;
2637
- overflow: hidden;
2638
- text-overflow: ellipsis;
2639
- }
2640
-
2641
- .uidex-sidebar-item:hover {
2642
- background: var(--uidex-color-surface-hover);
2643
- }
2644
-
2645
- .uidex-sidebar-item--active {
2646
- background: var(--uidex-color-surface-active);
2647
- color: var(--uidex-color-text-strong);
2648
- }
2649
-
2650
- .uidex-sidebar-item--active:hover {
2651
- background: var(--uidex-color-surface-active);
2652
- }
2653
-
2654
- .uidex-sidebar-item--dimmed {
2655
- opacity: 0.5;
2656
- }
2657
-
2658
- .uidex-sidebar-item--mono {
2659
- font-family: var(--uidex-font-mono);
2660
- }
2661
-
2662
- .uidex-sidebar-item--grouped {
2663
- padding-left: 24px;
2664
- }
2665
-
2666
- /* Sidebar category colors */
2667
- .uidex-sidebar-item--pages.uidex-sidebar-item--active {
2668
- color: var(--uidex-color-pages);
2669
- }
2670
-
2671
- .uidex-sidebar-item--features.uidex-sidebar-item--active {
2672
- color: var(--uidex-color-features);
2673
- }
2674
-
2675
- .uidex-sidebar-item--components.uidex-sidebar-item--active {
2676
- color: var(--uidex-color-components);
2677
- }
2678
-
2679
- .uidex-sidebar-root-item--pages .uidex-sidebar-root-label {
2680
- color: var(--uidex-color-pages);
2681
- }
2682
-
2683
- .uidex-sidebar-root-item--features .uidex-sidebar-root-label {
2684
- color: var(--uidex-color-features);
2685
- }
2686
-
2687
- .uidex-sidebar-root-item--components .uidex-sidebar-root-label {
2688
- color: var(--uidex-color-components);
2689
- }
2690
-
2691
- /* \u2014\u2014\u2014 Sidebar component groups \u2014\u2014\u2014 */
2692
- .uidex-sidebar-group-header {
2693
- display: flex;
2694
- align-items: center;
2695
- gap: 4px;
2696
- padding: 4px 10px;
2697
- border-radius: 0;
2698
- font-size: var(--uidex-font-size-sm);
2699
- font-weight: 600;
2700
- color: var(--uidex-color-text);
2701
- cursor: pointer;
2702
- transition: background-color var(--uidex-transition-fast);
2703
- user-select: none;
2704
- }
2705
-
2706
- .uidex-sidebar-group-header:hover {
2707
- background: var(--uidex-color-surface-hover);
2708
- }
2709
-
2710
- .uidex-sidebar-group-chevron {
2711
- font-size: 14px;
2712
- line-height: 1;
2713
- color: var(--uidex-color-text-muted);
2714
- width: 14px;
2715
- text-align: center;
2716
- flex-shrink: 0;
2717
- transition: transform var(--uidex-transition-normal);
2718
- transform: rotate(0deg);
2719
- }
2720
-
2721
- .uidex-sidebar-group-header--expanded .uidex-sidebar-group-chevron {
2722
- transform: rotate(90deg);
2723
- }
2724
-
2725
- .uidex-sidebar-group-count {
2726
- margin-left: auto;
2727
- font-size: var(--uidex-font-size-xs);
2728
- font-weight: 400;
2729
- color: var(--uidex-color-text-muted);
2730
- }
2731
-
2732
- /* \u2014\u2014\u2014 Search \u2014\u2014\u2014 */
2733
- .uidex-search-input {
2734
- flex: 1;
2735
- min-width: 0;
2736
- padding: 4px 8px;
2737
- border: 1px solid var(--uidex-color-border);
2738
- border-radius: 0;
2739
- background: transparent;
2740
- color: var(--uidex-color-text);
2741
- font-size: var(--uidex-font-size-sm);
2742
- font-family: var(--uidex-font-mono);
2743
- outline: none;
2744
- box-sizing: border-box;
2745
- }
2746
-
2747
- .uidex-search-input::placeholder {
2748
- color: var(--uidex-color-text-muted);
2749
- }
2750
-
2751
- .uidex-search-input:focus {
2752
- border-color: rgba(255, 255, 255, 0.3);
2753
- }
2754
-
2755
- .uidex-search-group-label {
2756
- padding: 8px 10px 2px;
2757
- font-size: var(--uidex-font-size-xs);
2758
- font-weight: 600;
2759
- text-transform: uppercase;
2760
- letter-spacing: 0.06em;
2761
- color: var(--uidex-color-text-muted);
2762
- }
2763
-
2764
- .uidex-search-group-label--pages { color: var(--uidex-color-pages); }
2765
- .uidex-search-group-label--features { color: var(--uidex-color-features); }
2766
- .uidex-search-group-label--components { color: var(--uidex-color-components); }
2767
-
2768
- .uidex-search-empty {
2769
- padding: 20px 10px;
2770
- font-size: var(--uidex-font-size-sm);
2771
- color: var(--uidex-color-text-muted);
2772
- text-align: center;
2773
- }
2774
-
2775
- .uidex-sidebar-divider {
2776
- height: 1px;
2777
- background: var(--uidex-color-border);
2778
- margin: 4px 10px;
2779
- }
2780
-
2781
- .uidex-sidebar-root-item--active {
2782
- background: var(--uidex-color-surface-active);
2783
- }
2784
-
2785
- /* \u2014\u2014\u2014 Main content \u2014\u2014\u2014 */
2786
- .uidex-main-title {
2787
- margin: 0 0 8px;
2788
- font-size: var(--uidex-font-size-lg);
2789
- font-weight: 600;
2790
- color: var(--uidex-color-text-strong);
2791
- }
2792
-
2793
- .uidex-main-title--mono {
2794
- font-family: var(--uidex-font-mono);
2795
- color: var(--uidex-color-text-strong);
2796
- }
2797
-
2798
- .uidex-main-divider {
2799
- height: 1px;
2800
- background: var(--uidex-color-border);
2801
- margin: 16px 0;
2802
- }
2803
-
2804
- .uidex-absent-notice {
2805
- font-size: var(--uidex-font-size-sm);
2806
- color: var(--uidex-color-text-muted);
2807
- font-style: italic;
2808
- margin-bottom: 12px;
2809
- }
2810
-
2811
- /* \u2014\u2014\u2014 Page content in modal \u2014\u2014\u2014 */
2812
- .uidex-page-content {
2813
- font-size: var(--uidex-font-size-sm);
2814
- line-height: 1.6;
2815
- color: var(--uidex-color-text);
2816
- }
2817
-
2818
- .uidex-page-content h2,
2819
- .uidex-page-content h3,
2820
- .uidex-page-content h4,
2821
- .uidex-page-content h5,
2822
- .uidex-page-content h6 {
2823
- margin: 12px 0 6px;
2824
- font-size: var(--uidex-font-size-sm);
2825
- font-weight: 600;
2826
- color: var(--uidex-color-text-strong);
2827
- text-transform: uppercase;
2828
- letter-spacing: 0.04em;
2829
- }
2830
-
2831
- .uidex-page-content h2 {
2832
- font-size: var(--uidex-font-size-md);
2833
- }
2834
-
2835
- .uidex-page-content p {
2836
- margin: 0 0 8px;
2837
- }
2838
-
2839
- .uidex-checklist {
2840
- list-style: none;
2841
- margin: 0 0 8px;
2842
- padding-left: 0;
2843
- }
2844
-
2845
- .uidex-checklist-item {
2846
- margin-bottom: 4px;
2847
- }
2848
-
2849
- .uidex-checklist-label {
2850
- display: flex;
2851
- align-items: baseline;
2852
- gap: 8px;
2853
- cursor: pointer;
2854
- }
2855
-
2856
- .uidex-checklist-checkbox {
2857
- flex-shrink: 0;
2858
- margin: 0;
2859
- accent-color: var(--uidex-color-text-strong);
2860
- }
2861
-
2862
- /* \u2014\u2014\u2014 Chips \u2014\u2014\u2014 */
2863
- .uidex-chips {
2864
- display: flex;
2865
- flex-wrap: wrap;
2866
- gap: 4px;
2867
- }
2868
-
2869
- .uidex-chip {
2870
- display: inline-block;
2871
- padding: 2px 8px;
2872
- border-radius: 0;
2873
- background: transparent;
2874
- border: 1px solid var(--uidex-color-border);
2875
- font-family: var(--uidex-font-mono);
2876
- font-size: var(--uidex-font-size-xs);
2877
- color: var(--uidex-color-text);
2878
- transition: background-color var(--uidex-transition-fast);
2879
- }
2880
-
2881
- .uidex-chip:hover {
2882
- background: var(--uidex-color-surface-hover);
2883
- }
2884
-
2885
- .uidex-chip--absent {
2886
- opacity: 0.4;
2887
- }
2888
-
2889
- .uidex-chip--pages {
2890
- color: var(--uidex-color-pages);
2891
- border-color: color-mix(in srgb, var(--uidex-color-pages) 30%, transparent);
2892
- }
2893
-
2894
- .uidex-chip--features {
2895
- color: var(--uidex-color-features);
2896
- border-color: color-mix(in srgb, var(--uidex-color-features) 30%, transparent);
2897
- }
2898
-
2899
- .uidex-chip--components {
2900
- color: var(--uidex-color-components);
2901
- border-color: color-mix(in srgb, var(--uidex-color-components) 30%, transparent);
2902
- }
2903
-
2904
- /* \u2014\u2014\u2014 Category badges \u2014\u2014\u2014 */
2905
- .uidex-badge {
2906
- display: inline-block;
2907
- padding: 2px 8px;
2908
- border-radius: 0;
2909
- border: 1px solid var(--uidex-color-border);
2910
- background: transparent;
2911
- color: var(--uidex-color-text-muted);
2912
- font-size: var(--uidex-font-size-xs);
2913
- font-weight: 600;
2914
- text-transform: uppercase;
2915
- letter-spacing: 0.06em;
2916
- margin-bottom: 12px;
2917
- }
2918
-
2919
- .uidex-badge--pages {
2920
- color: var(--uidex-color-pages);
2921
- border-color: color-mix(in srgb, var(--uidex-color-pages) 30%, transparent);
2922
- }
2923
-
2924
- .uidex-badge--features {
2925
- color: var(--uidex-color-features);
2926
- border-color: color-mix(in srgb, var(--uidex-color-features) 30%, transparent);
2927
- }
2928
-
2929
- .uidex-badge--components {
2930
- color: var(--uidex-color-components);
2931
- border-color: color-mix(in srgb, var(--uidex-color-components) 30%, transparent);
2932
- }
2933
-
2934
- /* \u2014\u2014\u2014 Feedback modal \u2014\u2014\u2014 */
2935
- .uidex-modal--feedback {
2936
- width: calc(100vw - 32px);
2937
- max-width: 400px;
2938
- }
2939
-
2940
- .uidex-modal-code--inline {
2941
- border: none;
2942
- background: none;
2943
- padding: 0;
2944
- }
2945
-
2946
- .uidex-header-spacer {
2947
- flex: 1;
2948
- }
2949
-
2950
- .uidex-chips--spaced {
2951
- margin-bottom: 8px;
2952
- }
2953
-
2954
- .uidex-modal-doc--spaced {
2955
- margin-bottom: 12px;
2956
- }
2957
-
2958
- /* \u2014\u2014\u2014 Feedback form \u2014\u2014\u2014 */
2959
- .uidex-form {
2960
- display: flex;
2961
- flex-direction: column;
2962
- gap: 12px;
2963
- }
2964
-
2965
- .uidex-form-group {
2966
- display: flex;
2967
- flex-direction: column;
2968
- gap: 4px;
2969
- }
2970
-
2971
- .uidex-form-label {
2972
- font-size: var(--uidex-font-size-xs);
2973
- font-weight: 600;
2974
- text-transform: uppercase;
2975
- letter-spacing: 0.06em;
2976
- color: var(--uidex-color-text-muted);
2977
- }
2978
-
2979
- .uidex-form-select,
2980
- .uidex-form-input,
2981
- .uidex-form-textarea {
2982
- border: 1px solid var(--uidex-color-border);
2983
- border-radius: 0;
2984
- background: transparent;
2985
- color: var(--uidex-color-text);
2986
- font-size: var(--uidex-font-size-sm);
2987
- font-family: var(--uidex-font-mono);
2988
- outline: none;
2989
- }
2990
-
2991
- .uidex-form-select:focus,
2992
- .uidex-form-input:focus,
2993
- .uidex-form-textarea:focus {
2994
- border-color: rgba(255, 255, 255, 0.3);
2995
- }
2996
-
2997
- .uidex-form-input::placeholder,
2998
- .uidex-form-textarea::placeholder {
2999
- color: var(--uidex-color-text-muted);
3000
- }
3001
-
3002
- .uidex-form-select {
3003
- appearance: none;
3004
- padding: 6px 10px;
3005
- cursor: pointer;
3006
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ababab' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
3007
- background-repeat: no-repeat;
3008
- background-position: right 8px center;
3009
- padding-right: 26px;
3010
- }
3011
-
3012
- .uidex-form-input {
3013
- padding: 6px 10px;
3014
- width: 100%;
3015
- box-sizing: border-box;
3016
- }
3017
-
3018
- .uidex-form-input + .uidex-form-input {
3019
- margin-top: 6px;
3020
- }
3021
-
3022
- .uidex-form-textarea {
3023
- padding: 8px 10px;
3024
- line-height: 1.5;
3025
- resize: vertical;
3026
- min-height: 80px;
3027
- }
3028
-
3029
- .uidex-form-submit {
3030
- padding: 6px 16px;
3031
- border: 1px solid var(--uidex-color-border);
3032
- border-radius: 0;
3033
- background: var(--uidex-color-primary);
3034
- color: var(--uidex-color-bg);
3035
- font-size: var(--uidex-font-size-sm);
3036
- font-weight: 600;
3037
- font-family: var(--uidex-font-mono);
3038
- cursor: pointer;
3039
- transition: background-color var(--uidex-transition-fast);
3040
- align-self: flex-end;
3041
- }
3042
-
3043
- .uidex-form-submit:hover {
3044
- background: var(--uidex-color-primary-hover);
3045
- }
3046
-
3047
- .uidex-form-submit:not(:disabled):active {
3048
- transform: translateY(1px);
3049
- }
3050
-
3051
- .uidex-form-submit:disabled {
3052
- opacity: 0.5;
3053
- cursor: default;
3054
- }
3055
-
3056
- /* \u2014\u2014\u2014 Form toggle \u2014\u2014\u2014 */
3057
- .uidex-form-toggle {
3058
- display: flex;
3059
- align-items: center;
3060
- gap: 8px;
3061
- cursor: pointer;
3062
- }
3063
-
3064
- .uidex-form-checkbox {
3065
- flex-shrink: 0;
3066
- margin: 0;
3067
- accent-color: var(--uidex-color-text-strong);
3068
- }
3069
-
3070
- .uidex-form-toggle-text {
3071
- font-size: var(--uidex-font-size-sm);
3072
- color: var(--uidex-color-text-secondary);
3073
- }
3074
-
3075
- /* \u2014\u2014\u2014 Responsive \u2014\u2014\u2014 */
3076
- @media (max-width: 600px) {
3077
- .uidex-modal--panel {
3078
- width: auto;
3079
- }
3080
-
3081
- .uidex-panel-body {
3082
- flex-direction: column;
3083
- }
3084
-
3085
- .uidex-panel-sidebar {
3086
- width: auto;
3087
- max-height: 200px;
3088
- border-right: none;
3089
- border-bottom: 1px solid var(--uidex-color-border);
3090
- }
3091
- }
3092
-
3093
- /* \u2014\u2014\u2014 Graph \u2014\u2014\u2014 */
3094
- .uidex-graph {
3095
- margin-top: 8px;
3096
- }
3097
-
3098
- .uidex-graph-svg {
3099
- display: block;
3100
- }
3101
-
3102
- .uidex-graph-header {
3103
- font-size: 10px;
3104
- font-weight: 600;
3105
- letter-spacing: 0.08em;
3106
- }
3107
-
3108
- .uidex-graph-header--pages { fill: var(--uidex-color-pages); }
3109
- .uidex-graph-header--features { fill: var(--uidex-color-features); }
3110
- .uidex-graph-header--components { fill: var(--uidex-color-components); }
3111
-
3112
- .uidex-graph-node {
3113
- cursor: pointer;
3114
- }
3115
-
3116
- .uidex-graph-node rect {
3117
- fill: var(--uidex-color-bg-elevated);
3118
- stroke: var(--uidex-color-border);
3119
- stroke-width: 1;
3120
- }
3121
-
3122
- .uidex-graph-node:hover rect {
3123
- fill: var(--uidex-color-surface-hover);
3124
- }
3125
-
3126
- .uidex-graph-node--page rect { stroke: color-mix(in srgb, var(--uidex-color-pages) 50%, transparent); }
3127
- .uidex-graph-node--feature rect { stroke: color-mix(in srgb, var(--uidex-color-features) 50%, transparent); }
3128
- .uidex-graph-node--component rect { stroke: color-mix(in srgb, var(--uidex-color-components) 50%, transparent); }
3129
-
3130
- .uidex-graph-label {
3131
- font-size: 10px;
3132
- font-family: var(--uidex-font-mono);
3133
- fill: var(--uidex-color-text);
3134
- }
3135
-
3136
- .uidex-graph-edge {
3137
- fill: none;
3138
- stroke-width: 1;
3139
- opacity: 0.3;
3140
- }
3141
-
3142
- .uidex-graph-edge--page { stroke: var(--uidex-color-pages); }
3143
- .uidex-graph-edge--feature { stroke: var(--uidex-color-features); }
3144
-
3145
- .uidex-graph-svg.uidex-graph--hover .uidex-graph-edge { opacity: 0.08; }
3146
- .uidex-graph-svg.uidex-graph--hover .uidex-graph-node { opacity: 0.3; }
3147
- .uidex-graph-svg.uidex-graph--hover .uidex-graph-edge--highlighted { opacity: 0.6; stroke-width: 1.5; }
3148
- .uidex-graph-svg.uidex-graph--hover .uidex-graph-node--highlighted { opacity: 1; }
3149
-
3150
- .uidex-graph-note {
3151
- font-size: var(--uidex-font-size-sm);
3152
- color: var(--uidex-color-text-muted);
3153
- margin-top: 12px;
3154
- font-style: italic;
3155
- }
3156
-
3157
- /* \u2014\u2014\u2014 Overlay \u2014\u2014\u2014
3158
- NOTE: The overlay renders in document.body (outside the Shadow DOM)
3159
- so all its styles are set inline in overlay.ts. These CSS classes
3160
- are retained only for consumers who import style.css standalone. */
3161
- .uidex-overlay {
3162
- position: fixed;
3163
- pointer-events: none;
3164
- z-index: 2147483645;
3165
- }
3166
- `;
3167
- var css_text_default = cssText;
3168
-
3169
- // src/core/shadow.ts
3170
- function injectStyles(shadowRoot) {
3171
- const style = document.createElement("style");
3172
- style.textContent = css_text_default;
3173
- shadowRoot.appendChild(style);
3174
- }
3175
-
3176
- // src/core/uidex-ui.ts
3177
- var UidexUI = class {
3178
- menu;
3179
- overlay;
3180
- modal;
3181
- inspector;
3182
- options;
3183
- mounted = false;
3184
- shadowHost = null;
3185
- shadowRoot = null;
3186
- observer = null;
3187
- refreshTimer = null;
3188
- copyTimer = null;
3189
- currentPresentIds = [];
3190
- activeMode = null;
3191
- sessionId;
3192
- capture = null;
3193
- constructor(options = {}) {
3194
- this.options = options;
3195
- this.sessionId = generateSessionId();
3196
- if (options.ingest?.captureConsole || options.ingest?.captureNetwork) {
3197
- this.capture = new IngestCapture(
3198
- options.ingest.captureConsole ?? false,
3199
- options.ingest.captureNetwork ?? false
3200
- );
3201
- }
3202
- this.overlay = new Overlay({
3203
- color: options.config?.defaults?.color,
3204
- borderStyle: options.config?.defaults?.borderStyle,
3205
- borderWidth: options.config?.defaults?.borderWidth,
3206
- showLabel: options.config?.defaults?.showLabel,
3207
- labelPosition: options.config?.defaults?.labelPosition,
3208
- colors: options.config?.colors
3209
- });
3210
- this.modal = new Modal({
3211
- onComponentNavigate: (id) => {
3212
- const element = this.findElement(id);
3213
- if (element && typeof element.scrollIntoView === "function") {
3214
- element.scrollIntoView({ behavior: "smooth", block: "center" });
3215
- }
3216
- this.options.onSelect?.(id);
3217
- },
3218
- elementGetter: (id) => this.findElement(id),
3219
- ingest: options.ingest,
3220
- onSubmit: options.onSubmit,
3221
- sessionId: this.sessionId,
3222
- getConsoleLogs: () => this.capture?.getConsoleLogs() ?? [],
3223
- getNetworkErrors: () => this.capture?.getNetworkErrors() ?? []
3224
- });
3225
- this.menu = new Menu({
3226
- onInspectToggle: () => this.toggleMode("inspect"),
3227
- onCopyToggle: () => this.toggleMode("copy"),
3228
- onFeedbackToggle: () => this.toggleMode("feedback"),
3229
- onExpandClick: () => this.handleExpandClick()
3230
- });
3231
- if (options.inspectShortcut === false) {
3232
- this.inspector = null;
3233
- } else {
3234
- this.inspector = new Inspector({
3235
- shortcut: options.inspectShortcut,
3236
- onHighlight: (_element, id) => this.handleHighlight(id),
3237
- onSelect: (_element, id) => this.handleSelect(id),
3238
- onActivate: () => {
3239
- this.updateMenuActiveStates();
3240
- },
3241
- onDeactivate: () => {
3242
- this.overlay.hide();
3243
- this.activeMode = null;
3244
- this.updateMenuActiveStates();
3245
- }
3246
- });
3247
- }
3248
- }
3249
- mount(container) {
3250
- if (this.mounted) return;
3251
- const components = this.getComponentsMap();
3252
- const componentIds = Object.keys(components);
3253
- if (componentIds.length === 0) return;
3254
- this.shadowHost = document.createElement("div");
3255
- this.shadowHost.id = "uidex-root";
3256
- this.shadowHost.style.position = "fixed";
3257
- this.shadowHost.style.top = "0";
3258
- this.shadowHost.style.left = "0";
3259
- this.shadowHost.style.width = "0";
3260
- this.shadowHost.style.height = "0";
3261
- this.shadowHost.style.pointerEvents = "none";
3262
- this.shadowHost.style.zIndex = "2147483646";
3263
- this.shadowRoot = this.shadowHost.attachShadow({ mode: "open" });
3264
- injectStyles(this.shadowRoot);
3265
- const presentIds = this.scanPresentIds(componentIds);
3266
- const menuContainer = this.menu.create();
3267
- const position = this.options.buttonPosition ?? "bottom-right";
3268
- menuContainer.classList.add(POSITION_CLASSES[position]);
3269
- const mountTarget = container ?? document.body;
3270
- mountTarget.appendChild(this.shadowHost);
3271
- this.shadowRoot.appendChild(menuContainer);
3272
- this.modal.setShadowRoot(this.shadowRoot);
3273
- this.updateModalData(presentIds);
3274
- this.inspector?.mount();
3275
- this.capture?.start();
3276
- this.startObserving();
3277
- this.mounted = true;
3278
- }
3279
- destroy() {
3280
- if (!this.mounted) return;
3281
- this.stopObserving();
3282
- this.capture?.stop();
3283
- if (this.copyTimer !== null) {
3284
- clearTimeout(this.copyTimer);
3285
- this.copyTimer = null;
3286
- }
3287
- this.inspector?.destroy();
3288
- this.menu.destroy();
3289
- this.overlay.destroy();
3290
- this.modal.destroy();
3291
- if (this.shadowHost && this.shadowHost.parentNode) {
3292
- this.shadowHost.parentNode.removeChild(this.shadowHost);
3293
- }
3294
- this.shadowHost = null;
3295
- this.shadowRoot = null;
3296
- this.mounted = false;
3297
- }
3298
- startInspect() {
3299
- this.inspector?.activate();
3300
- }
3301
- stopInspect() {
3302
- this.inspector?.deactivate();
3303
- }
3304
- isInspecting() {
3305
- return this.inspector?.isActive() ?? false;
3306
- }
3307
- /** Open the modal panel, optionally to a specific view. */
3308
- openPanel(view) {
3309
- this.modal.open(view);
3310
- }
3311
- toggleMode(mode) {
3312
- if (this.inspector?.isActive()) {
3313
- if (this.activeMode === mode) {
3314
- this.inspector.deactivate();
3315
- } else {
3316
- this.activeMode = mode;
3317
- this.updateMenuActiveStates();
3318
- }
3319
- } else {
3320
- this.activeMode = mode;
3321
- this.inspector?.activate();
3322
- }
3323
- }
3324
- updateMenuActiveStates() {
3325
- this.menu.setInspectActive(this.activeMode === "inspect");
3326
- this.menu.setCopyActive(this.activeMode === "copy");
3327
- this.menu.setFeedbackActive(this.activeMode === "feedback");
3328
- }
3329
- getComponentsMap() {
3330
- return this.options.components ?? getComponents() ?? {};
3331
- }
3332
- getPagesArray() {
3333
- return this.options.pages ?? getPages() ?? [];
3334
- }
3335
- getFeaturesArray() {
3336
- return this.options.features ?? getFeatures() ?? [];
3337
- }
3338
- extractPageTitle(content) {
3339
- const match = content.match(/^#\s+(.+)/m);
3340
- return match ? match[1].trim() : "Untitled";
3341
- }
3342
- /** Hamburger button handler — open modal to default (current page) view. */
3343
- handleExpandClick() {
3344
- this.inspector?.deactivate();
3345
- this.overlay.hide();
3346
- this.modal.open();
3347
- }
3348
- /** Selection handler — routes by active mode. */
3349
- handleSelect(id) {
3350
- switch (this.activeMode) {
3351
- case "inspect":
3352
- this.overlay.hide();
3353
- this.modal.open({ type: "component", id });
3354
- break;
3355
- case "copy":
3356
- this.handleCopySelect(id);
3357
- break;
3358
- case "feedback":
3359
- this.overlay.hide();
3360
- this.modal.openFeedback(id);
3361
- break;
3362
- }
3363
- }
3364
- handleCopySelect(id) {
3365
- const components = this.getComponentsMap();
3366
- const locations = components[id];
3367
- if (!locations || locations.length === 0) return;
3368
- const loc = locations[0];
3369
- const text = `${loc.filePath}:${loc.line}`;
3370
- navigator.clipboard.writeText(text).catch(() => {
3371
- });
3372
- const element = this.findElement(id);
3373
- if (element instanceof HTMLElement) {
3374
- this.overlay.show(element, "Copied!");
3375
- this.copyTimer = setTimeout(() => {
3376
- this.copyTimer = null;
3377
- this.overlay.hide();
3378
- }, 600);
3379
- }
3380
- }
3381
- findElement(id) {
3382
- return document.querySelector(`[data-uidex="${id}"]`);
3383
- }
3384
- handleHighlight(id) {
3385
- if (id) {
3386
- const element = this.findElement(id);
3387
- if (element instanceof HTMLElement) {
3388
- this.overlay.show(element, id);
3389
- }
3390
- } else {
3391
- this.overlay.hide();
3392
- }
3393
- }
3394
- /** Push current pages/features/components/presentIds to the modal. */
3395
- updateModalData(presentIds) {
3396
- const pages = this.getPagesArray();
3397
- const features = this.getFeaturesArray();
3398
- const components = this.getComponentsMap();
3399
- this.modal.setData({
3400
- pages: pages.map((p) => ({
3401
- title: this.extractPageTitle(p.content),
3402
- dir: p.dir,
3403
- content: p.content,
3404
- componentIds: p.componentIds
3405
- })),
3406
- features: features.map((f) => ({
3407
- title: this.extractPageTitle(f.content),
3408
- dir: f.dir,
3409
- content: f.content,
3410
- componentIds: f.componentIds
3411
- })),
3412
- components,
3413
- presentIds: new Set(presentIds)
3414
- });
3415
- }
3416
- // ——— DOM observation for SPA navigation ———
3417
- /** Scan which registered component IDs are currently in the DOM. */
3418
- scanPresentIds(allIds) {
3419
- const componentIds = allIds ?? Object.keys(this.getComponentsMap());
3420
- const found = /* @__PURE__ */ new Set();
3421
- for (const el of document.querySelectorAll("[data-uidex]")) {
3422
- const id = el.getAttribute("data-uidex");
3423
- if (id) found.add(id);
3424
- }
3425
- const presentIds = componentIds.filter((id) => found.has(id));
3426
- this.currentPresentIds = presentIds;
3427
- return presentIds;
3428
- }
3429
- /** Schedule a debounced refresh of the modal data after DOM mutations. */
3430
- scheduleRefresh() {
3431
- if (this.refreshTimer !== null) return;
3432
- this.refreshTimer = setTimeout(() => {
3433
- this.refreshTimer = null;
3434
- this.refresh();
3435
- }, 100);
3436
- }
3437
- /** Re-scan the DOM and update modal data if the set of present IDs changed. */
3438
- refresh() {
3439
- const oldIds = this.currentPresentIds;
3440
- const newIds = this.scanPresentIds();
3441
- if (newIds.length === oldIds.length && newIds.every((id, i) => id === oldIds[i])) {
3442
- return;
3443
- }
3444
- this.updateModalData(newIds);
3445
- }
3446
- startObserving() {
3447
- this.observer = new MutationObserver(() => this.scheduleRefresh());
3448
- this.observer.observe(document.body, {
3449
- childList: true,
3450
- subtree: true,
3451
- attributes: true,
3452
- attributeFilter: ["data-uidex"]
3453
- });
3454
- }
3455
- stopObserving() {
3456
- if (this.refreshTimer !== null) {
3457
- clearTimeout(this.refreshTimer);
3458
- this.refreshTimer = null;
3459
- }
3460
- this.observer?.disconnect();
3461
- this.observer = null;
3462
- }
3463
- };
3464
- function createUidexUI(options = {}) {
3465
- return new UidexUI(options);
3466
- }
3467
- return __toCommonJS(index_exports);
3468
- })();
3469
- //# sourceMappingURL=index.global.js.map