fixdog 0.0.1 → 0.0.2

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -433
  3. package/dist/api/client.d.ts +74 -0
  4. package/dist/components/ConversationalInputReact.d.ts +26 -0
  5. package/dist/components/ElementInfoDisplayReact.d.ts +9 -0
  6. package/dist/components/FixdogSidebarReact.d.ts +29 -0
  7. package/dist/fiber.d.ts +9 -0
  8. package/dist/index.cjs.js +33 -0
  9. package/dist/index.cjs.js.map +1 -0
  10. package/dist/index.d.ts +6 -158
  11. package/dist/index.esm.js +29 -0
  12. package/dist/index.esm.js.map +1 -0
  13. package/dist/inspector-B4F5CBT7.cjs.js +1159 -0
  14. package/dist/inspector-B4F5CBT7.cjs.js.map +1 -0
  15. package/dist/inspector-BL2pNjn-.cjs.js +1173 -0
  16. package/dist/inspector-BL2pNjn-.cjs.js.map +1 -0
  17. package/dist/inspector-Bg6uSvk0.esm.js +1273 -0
  18. package/dist/inspector-Bg6uSvk0.esm.js.map +1 -0
  19. package/dist/inspector-BuOffbVc.cjs.js +1280 -0
  20. package/dist/inspector-BuOffbVc.cjs.js.map +1 -0
  21. package/dist/inspector-CNgFkZOU.esm.js +1185 -0
  22. package/dist/inspector-CNgFkZOU.esm.js.map +1 -0
  23. package/dist/inspector-CPF1N9dL.esm.js +1185 -0
  24. package/dist/inspector-CPF1N9dL.esm.js.map +1 -0
  25. package/dist/inspector-CPGK5Lg7.esm.js +1155 -0
  26. package/dist/inspector-CPGK5Lg7.esm.js.map +1 -0
  27. package/dist/inspector-CWcTSREy.cjs.js +1174 -0
  28. package/dist/inspector-CWcTSREy.cjs.js.map +1 -0
  29. package/dist/inspector-Cn_bl9Io.cjs.js +1189 -0
  30. package/dist/inspector-Cn_bl9Io.cjs.js.map +1 -0
  31. package/dist/inspector-D9DuXirp.cjs.js +1189 -0
  32. package/dist/inspector-D9DuXirp.cjs.js.map +1 -0
  33. package/dist/inspector-DQEtAjyM.esm.js +1129 -0
  34. package/dist/inspector-DQEtAjyM.esm.js.map +1 -0
  35. package/dist/inspector-DVlU9p44.cjs.js +1189 -0
  36. package/dist/inspector-DVlU9p44.cjs.js.map +1 -0
  37. package/dist/inspector-DaRVppX9.cjs.js +1134 -0
  38. package/dist/inspector-DaRVppX9.cjs.js.map +1 -0
  39. package/dist/inspector-huqtI2MD.esm.js +1170 -0
  40. package/dist/inspector-huqtI2MD.esm.js.map +1 -0
  41. package/dist/inspector-spoCY1tf.esm.js +1169 -0
  42. package/dist/inspector-spoCY1tf.esm.js.map +1 -0
  43. package/dist/inspector-tY1kJK5_.esm.js +1185 -0
  44. package/dist/inspector-tY1kJK5_.esm.js.map +1 -0
  45. package/dist/inspector.d.ts +43 -0
  46. package/dist/keyboard.d.ts +10 -0
  47. package/dist/overlay.d.ts +31 -0
  48. package/dist/react/InspectorProvider.d.ts +6 -0
  49. package/dist/react/index.cjs.js +32 -0
  50. package/dist/react/index.cjs.js.map +1 -0
  51. package/dist/react/index.esm.js +30 -0
  52. package/dist/react/index.esm.js.map +1 -0
  53. package/dist/sidebar/SidebarRuntime.d.ts +8 -0
  54. package/dist/sidebar-runtime.esm.js +2122 -0
  55. package/dist/sidebar-runtime.esm.js.map +1 -0
  56. package/dist/sidebar-runtime.iife.js +2991 -0
  57. package/dist/styles/sidebarStyles.d.ts +2 -0
  58. package/dist/styles.d.ts +8 -0
  59. package/dist/types/sidebar.d.ts +62 -0
  60. package/dist/types.d.ts +47 -0
  61. package/dist/utils/cookies.d.ts +10 -0
  62. package/dist/utils/devMode.d.ts +17 -0
  63. package/dist/utils/sessionStorage.d.ts +19 -0
  64. package/package.json +57 -40
  65. package/USAGE.md +0 -77
  66. package/dist/client/index.d.mts +0 -110
  67. package/dist/client/index.d.ts +0 -110
  68. package/dist/client/index.js +0 -1601
  69. package/dist/client/index.mjs +0 -1582
  70. package/dist/client/init.d.mts +0 -67
  71. package/dist/client/init.d.ts +0 -67
  72. package/dist/client/init.js +0 -1609
  73. package/dist/client/init.mjs +0 -1593
  74. package/dist/index.d.mts +0 -158
  75. package/dist/index.js +0 -1635
  76. package/dist/index.mjs +0 -1606
  77. package/src/api/client.ts +0 -141
  78. package/src/client/index.ts +0 -75
  79. package/src/client/init.tsx +0 -78
  80. package/src/components/ConversationalInputReact.tsx +0 -406
  81. package/src/components/ElementInfoDisplayReact.tsx +0 -84
  82. package/src/components/UiDogSidebarReact.tsx +0 -49
  83. package/src/element-detector.ts +0 -186
  84. package/src/index.ts +0 -228
  85. package/src/instrument.ts +0 -171
  86. package/src/sidebar-initializer.ts +0 -171
  87. package/src/source-resolver.ts +0 -121
  88. package/src/styles/sidebarStyles.ts +0 -597
  89. package/src/types/css.d.ts +0 -9
  90. package/src/types/sidebar.ts +0 -56
  91. package/src/types.ts +0 -119
  92. package/tsconfig.json +0 -23
  93. package/tsup.config.ts +0 -40
@@ -0,0 +1,1134 @@
1
+ 'use strict';
2
+
3
+ var sourceMapJs = require('source-map-js');
4
+
5
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
+ const STYLE_ELEMENT_ID = 'fixdog-sdk-styles';
7
+ const OVERLAY_CLASS = 'fixdog-overlay';
8
+ const TOOLTIP_CLASS = 'fixdog-tooltip';
9
+ const HIGHLIGHT_CLASS = 'fixdog-highlight';
10
+ const SELECTION_CLASS = 'fixdog-selection';
11
+ const SELECTION_BADGE_CLASS = 'fixdog-selection-badge';
12
+ // Selection highlight color (blue, distinct from hover cyan)
13
+ const SELECTION_COLOR = '#3b82f6';
14
+ const baseStyles = (options) => `
15
+ .${OVERLAY_CLASS} {
16
+ position: fixed;
17
+ pointer-events: none;
18
+ border: 2px solid ${options.borderColor};
19
+ background: rgba(97, 218, 251, 0.08);
20
+ border-radius: 4px;
21
+ box-sizing: border-box;
22
+ transition: all 50ms ease-out;
23
+ z-index: ${options.zIndex};
24
+ }
25
+
26
+ .${TOOLTIP_CLASS} {
27
+ position: fixed;
28
+ pointer-events: none;
29
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
30
+ font-size: 12px;
31
+ line-height: 1.4;
32
+ color: #e5f6ff;
33
+ background: rgba(0, 0, 0, 0.78);
34
+ border: 1px solid ${options.borderColor};
35
+ border-radius: 6px;
36
+ padding: 8px 10px;
37
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
38
+ z-index: ${options.zIndex};
39
+ max-width: 320px;
40
+ transform: translateY(-4px);
41
+ transition: opacity 50ms ease-out, transform 50ms ease-out;
42
+ opacity: 0;
43
+ }
44
+
45
+ .${TOOLTIP_CLASS} .fixdog-name {
46
+ display: block;
47
+ font-weight: 700;
48
+ color: ${options.borderColor};
49
+ margin-bottom: 2px;
50
+ }
51
+
52
+ .${TOOLTIP_CLASS} .fixdog-file {
53
+ display: block;
54
+ color: #98c379;
55
+ word-break: break-all;
56
+ margin-bottom: 2px;
57
+ }
58
+
59
+ .${TOOLTIP_CLASS} .fixdog-hint {
60
+ display: block;
61
+ color: #c9d1d9;
62
+ opacity: 0.8;
63
+ }
64
+
65
+ .${HIGHLIGHT_CLASS} {
66
+ outline: 2px solid ${options.borderColor};
67
+ outline-offset: 2px;
68
+ background: rgba(97, 218, 251, 0.08);
69
+ }
70
+
71
+ /* Selection highlight - persistent blue border for selected components */
72
+ .${SELECTION_CLASS} {
73
+ position: fixed;
74
+ pointer-events: none;
75
+ border: 2px solid ${SELECTION_COLOR};
76
+ background: rgba(59, 130, 246, 0.1);
77
+ border-radius: 4px;
78
+ box-sizing: border-box;
79
+ z-index: ${options.zIndex - 1};
80
+ }
81
+
82
+ /* Numbered badge for selected components */
83
+ .${SELECTION_BADGE_CLASS} {
84
+ position: fixed;
85
+ pointer-events: none;
86
+ width: 24px;
87
+ height: 24px;
88
+ background: ${SELECTION_COLOR};
89
+ color: #ffffff;
90
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
91
+ font-size: 12px;
92
+ font-weight: 700;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ border-radius: 50%;
97
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
98
+ z-index: ${options.zIndex + 1};
99
+ }
100
+ `;
101
+
102
+ const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
103
+ class Overlay {
104
+ constructor(options) {
105
+ this.styleEl = null;
106
+ this.highlight = null;
107
+ this.tooltip = null;
108
+ this.mounted = false;
109
+ this.doc = document;
110
+ // Multi-selection tracking
111
+ this.selections = new Map();
112
+ this.options = options;
113
+ }
114
+ mount(doc = document) {
115
+ if (this.mounted)
116
+ return;
117
+ this.mounted = true;
118
+ this.doc = doc;
119
+ this.styleEl = doc.getElementById(STYLE_ELEMENT_ID);
120
+ if (!this.styleEl) {
121
+ this.styleEl = doc.createElement("style");
122
+ this.styleEl.id = STYLE_ELEMENT_ID;
123
+ this.styleEl.textContent = baseStyles(this.options);
124
+ doc.head.appendChild(this.styleEl);
125
+ }
126
+ this.highlight = doc.createElement("div");
127
+ this.highlight.className = `${OVERLAY_CLASS} ${HIGHLIGHT_CLASS}`;
128
+ this.tooltip = doc.createElement("div");
129
+ this.tooltip.className = TOOLTIP_CLASS;
130
+ doc.body.appendChild(this.highlight);
131
+ doc.body.appendChild(this.tooltip);
132
+ }
133
+ update(element, info) {
134
+ if (!this.mounted || !this.highlight || !this.tooltip)
135
+ return;
136
+ if (!element)
137
+ return this.hide();
138
+ const rect = element.getBoundingClientRect();
139
+ const position = {
140
+ top: rect.top,
141
+ left: rect.left,
142
+ width: rect.width,
143
+ height: rect.height,
144
+ };
145
+ this.applyHighlight(position);
146
+ this.applyTooltip(position, info);
147
+ }
148
+ hide() {
149
+ if (!this.highlight || !this.tooltip)
150
+ return;
151
+ this.highlight.style.display = "none";
152
+ this.tooltip.style.opacity = "0";
153
+ this.tooltip.style.transform = "translateY(-4px)";
154
+ }
155
+ destroy() {
156
+ if (!this.mounted)
157
+ return;
158
+ this.mounted = false;
159
+ this.hide();
160
+ this.clearSelections();
161
+ this.highlight?.remove();
162
+ this.tooltip?.remove();
163
+ // Do not remove style tag to allow re-use across mounts within the same page.
164
+ }
165
+ // Add a selection highlight with numbered badge
166
+ addSelection(id, element, componentName) {
167
+ if (!this.mounted)
168
+ return;
169
+ // Remove existing selection with same ID if any
170
+ this.removeSelection(id);
171
+ const rect = element.getBoundingClientRect();
172
+ // Create selection highlight
173
+ const highlight = this.doc.createElement("div");
174
+ highlight.className = `${OVERLAY_CLASS} ${SELECTION_CLASS}`;
175
+ highlight.style.top = `${rect.top}px`;
176
+ highlight.style.left = `${rect.left}px`;
177
+ highlight.style.width = `${rect.width}px`;
178
+ highlight.style.height = `${rect.height}px`;
179
+ highlight.dataset.selectionId = String(id);
180
+ // Create numbered badge
181
+ const badge = this.doc.createElement("div");
182
+ badge.className = SELECTION_BADGE_CLASS;
183
+ badge.textContent = String(id);
184
+ badge.style.top = `${rect.top - 12}px`;
185
+ badge.style.left = `${rect.left - 12}px`;
186
+ badge.dataset.selectionId = String(id);
187
+ this.doc.body.appendChild(highlight);
188
+ this.doc.body.appendChild(badge);
189
+ this.selections.set(id, { element, highlight, badge, componentName });
190
+ // Set up scroll/resize listener for this selection
191
+ this.updateSelectionPositions();
192
+ }
193
+ // Remove a specific selection
194
+ removeSelection(id) {
195
+ const selection = this.selections.get(id);
196
+ if (selection) {
197
+ selection.highlight.remove();
198
+ selection.badge.remove();
199
+ this.selections.delete(id);
200
+ }
201
+ }
202
+ // Clear all selections
203
+ clearSelections() {
204
+ this.selections.forEach((selection) => {
205
+ selection.highlight.remove();
206
+ selection.badge.remove();
207
+ });
208
+ this.selections.clear();
209
+ }
210
+ // Update positions of all selection overlays (called on scroll/resize)
211
+ updateSelectionPositions() {
212
+ this.selections.forEach((selection, id) => {
213
+ const rect = selection.element.getBoundingClientRect();
214
+ selection.highlight.style.top = `${rect.top}px`;
215
+ selection.highlight.style.left = `${rect.left}px`;
216
+ selection.highlight.style.width = `${rect.width}px`;
217
+ selection.highlight.style.height = `${rect.height}px`;
218
+ selection.badge.style.top = `${rect.top - 12}px`;
219
+ selection.badge.style.left = `${rect.left - 12}px`;
220
+ });
221
+ }
222
+ // Get all current selections
223
+ getSelections() {
224
+ return this.selections;
225
+ }
226
+ applyHighlight(pos) {
227
+ if (!this.highlight)
228
+ return;
229
+ this.highlight.style.display = "block";
230
+ this.highlight.style.top = `${pos.top}px`;
231
+ this.highlight.style.left = `${pos.left}px`;
232
+ this.highlight.style.width = `${pos.width}px`;
233
+ this.highlight.style.height = `${pos.height}px`;
234
+ }
235
+ applyTooltip(pos, info) {
236
+ if (!this.tooltip)
237
+ return;
238
+ // const name = info.componentName || "Unknown component";
239
+ const line = Number.isFinite(info.lineNumber) ? info.lineNumber : "?";
240
+ const column = Number.isFinite(info.columnNumber)
241
+ ? `:${info.columnNumber}`
242
+ : "";
243
+ const file = info.fileName
244
+ ? `${info.fileName}:${line}${column}`
245
+ : "Source not available - ensure dev mode";
246
+ this.tooltip.innerHTML = `
247
+ <span class="fixdog-name">${name}</span>
248
+ <span class="fixdog-file">${file}</span>
249
+ <span class="fixdog-hint">Click to log • Esc to cancel</span>
250
+ `;
251
+ this.tooltip.style.opacity = "1";
252
+ this.tooltip.style.transform = "translateY(0)";
253
+ // Positioning
254
+ this.positionTooltip(pos);
255
+ }
256
+ positionTooltip(pos) {
257
+ if (!this.tooltip)
258
+ return;
259
+ const gap = 8;
260
+ const viewportWidth = window.innerWidth;
261
+ const viewportHeight = window.innerHeight;
262
+ // Measure after ensuring content is set
263
+ const tooltipRect = this.tooltip.getBoundingClientRect();
264
+ const preferTop = this.shouldPlaceTop(pos, tooltipRect.height);
265
+ const top = preferTop
266
+ ? pos.top - tooltipRect.height - gap
267
+ : pos.top + pos.height + gap;
268
+ const left = clamp(pos.left + pos.width / 2 - tooltipRect.width / 2, 8, viewportWidth - tooltipRect.width - 8);
269
+ this.tooltip.style.top = `${clamp(top, 8, viewportHeight - tooltipRect.height - 8)}px`;
270
+ this.tooltip.style.left = `${left}px`;
271
+ }
272
+ shouldPlaceTop(pos, tooltipHeight) {
273
+ const spaceAbove = pos.top;
274
+ const spaceBelow = window.innerHeight - (pos.top + pos.height);
275
+ const preference = this.options.tooltipPosition;
276
+ if (preference === "top")
277
+ return true;
278
+ if (preference === "bottom")
279
+ return false;
280
+ if (spaceAbove >= tooltipHeight + 12)
281
+ return true;
282
+ if (spaceBelow >= tooltipHeight + 12)
283
+ return false;
284
+ return spaceAbove >= spaceBelow; // fallback to the larger side
285
+ }
286
+ }
287
+
288
+ const REACT_FIBER_PROP = "__reactFiber$";
289
+ const REACT_INTERNAL_PROP = "__reactInternalInstance$";
290
+ const REACT_CONTAINER_PROP = "__reactContainer$";
291
+ const STACK_TOP = "react-stack-top-frame";
292
+ const STACK_BOTTOM = "react_stack_bottom_frame";
293
+ // Debug mode - check global flag or set to true for development
294
+ const isDebugEnabled = () => {
295
+ return globalThis.__FIXDOG_DEBUG__ === true;
296
+ };
297
+ const debugLog = (...args) => {
298
+ if (isDebugEnabled())
299
+ console.log("[fixdog-sdk:debug]", ...args);
300
+ };
301
+ const possibleFiberKeys = (node) => {
302
+ const keys = [];
303
+ for (const key in node) {
304
+ if (key.startsWith(REACT_FIBER_PROP) ||
305
+ key.startsWith(REACT_INTERNAL_PROP) ||
306
+ key.startsWith(REACT_CONTAINER_PROP)) {
307
+ keys.push(key);
308
+ }
309
+ }
310
+ return keys;
311
+ };
312
+ const getFiberFromNode = (node) => {
313
+ let current = node;
314
+ while (current) {
315
+ const keys = possibleFiberKeys(current);
316
+ if (keys.length) {
317
+ const value = current[keys[0]]; // accessing React internals intentionally
318
+ if (value?.current)
319
+ return value.current; // container root form
320
+ return value;
321
+ }
322
+ const root = current.getRootNode();
323
+ if (root &&
324
+ root.host &&
325
+ current !== root.host) {
326
+ current = root.host;
327
+ continue;
328
+ }
329
+ current = current.parentElement;
330
+ }
331
+ return null;
332
+ };
333
+ const unwrapType = (type) => {
334
+ // handle memo/forwardRef/lazy wrappers
335
+ if (!type)
336
+ return type;
337
+ if (type.render)
338
+ return type.render; // forwardRef
339
+ if (type.type)
340
+ return type.type; // memo wraps actual type
341
+ if (type._payload && type._payload._result)
342
+ return type._payload._result; // lazy resolved
343
+ return type;
344
+ };
345
+ const getComponentName = (fiber, element) => {
346
+ const rawType = fiber?.type || fiber?.elementType;
347
+ const unwrapped = unwrapType(rawType);
348
+ const displayName = unwrapped?.displayName || unwrapped?.name;
349
+ if (displayName)
350
+ return displayName;
351
+ if (fiber?.tag === 5 && element)
352
+ return element.tagName.toLowerCase();
353
+ if (fiber?.tag === 7)
354
+ return "Fragment";
355
+ if (fiber?.tag === 4)
356
+ return "Portal";
357
+ return "AnonymousComponent";
358
+ };
359
+ // Read source from various possible locations
360
+ const readSource = (candidate) => {
361
+ if (!candidate)
362
+ return null;
363
+ // Standard React _debugSource
364
+ if (candidate._debugSource) {
365
+ return {
366
+ fileName: candidate._debugSource.fileName,
367
+ lineNumber: candidate._debugSource.lineNumber,
368
+ columnNumber: candidate._debugSource.columnNumber ?? 0,
369
+ };
370
+ }
371
+ // Some bundlers attach __source to the element
372
+ if (candidate.__source) {
373
+ return {
374
+ fileName: candidate.__source.fileName,
375
+ lineNumber: candidate.__source.lineNumber,
376
+ columnNumber: candidate.__source.columnNumber ?? 0,
377
+ };
378
+ }
379
+ return null;
380
+ };
381
+ // Check for source info on the component type itself
382
+ const readSourceFromType = (type) => {
383
+ if (!type)
384
+ return null;
385
+ const unwrapped = unwrapType(type);
386
+ // Some transforms add __source to the function
387
+ if (unwrapped?.__source) {
388
+ return {
389
+ fileName: unwrapped.__source.fileName,
390
+ lineNumber: unwrapped.__source.lineNumber,
391
+ columnNumber: unwrapped.__source.columnNumber ?? 0,
392
+ };
393
+ }
394
+ // Check _source (used by some bundlers)
395
+ if (unwrapped?._source) {
396
+ return {
397
+ fileName: unwrapped._source.fileName,
398
+ lineNumber: unwrapped._source.lineNumber,
399
+ columnNumber: unwrapped._source.columnNumber ?? 0,
400
+ };
401
+ }
402
+ return null;
403
+ };
404
+ const findDebugSource = (fiber) => {
405
+ const visited = new Set();
406
+ let current = fiber;
407
+ while (current && !visited.has(current)) {
408
+ visited.add(current);
409
+ // Check fiber's _debugSource
410
+ const direct = readSource(current) || readSource(current.alternate);
411
+ if (direct) {
412
+ debugLog("Found source via _debugSource on fiber");
413
+ return direct;
414
+ }
415
+ // Check type for source info
416
+ const typeSource = readSourceFromType(current.type) ||
417
+ readSourceFromType(current.elementType);
418
+ if (typeSource) {
419
+ debugLog("Found source via type");
420
+ return typeSource;
421
+ }
422
+ // Check owner
423
+ const owner = current._debugOwner;
424
+ const ownerSource = readSource(owner) || readSource(owner?.alternate);
425
+ if (ownerSource) {
426
+ debugLog("Found source via _debugOwner");
427
+ return ownerSource;
428
+ }
429
+ // Check owner's type
430
+ const ownerTypeSource = readSourceFromType(owner?.type) || readSourceFromType(owner?.elementType);
431
+ if (ownerTypeSource) {
432
+ debugLog("Found source via owner type");
433
+ return ownerTypeSource;
434
+ }
435
+ current = current.return || owner;
436
+ }
437
+ debugLog("No _debugSource found, will try stack trace");
438
+ return null;
439
+ };
440
+ const isInternalFrame = (frame) => {
441
+ const fn = frame.functionName || "";
442
+ const url = frame.url || "";
443
+ if (fn.includes(STACK_TOP) || fn.includes(STACK_BOTTOM))
444
+ return true;
445
+ if (fn === "exports.jsxDEV" || fn === "jsxDEV" || fn === "jsx")
446
+ return true;
447
+ if (url.includes("react-dom"))
448
+ return true;
449
+ if (url.includes("react-refresh"))
450
+ return true;
451
+ if (url.includes("react.development"))
452
+ return true;
453
+ if (url.includes("node_modules/react"))
454
+ return true;
455
+ return false;
456
+ };
457
+ // Extended regex to handle various stack trace formats including URLs with special characters
458
+ const FRAME_REGEX = /at\s+(?:(.*?)\s+\()?([^():\s]+(?::[^():\s]+)*):(\d+):(\d+)\)?/;
459
+ // Simpler regex for URL extraction - handles Next.js Turbopack URLs
460
+ const URL_REGEX = /(https?:\/\/[^:\s]+):(\d+):(\d+)/;
461
+ const parseStack = (stack) => {
462
+ if (!stack)
463
+ return [];
464
+ debugLog("Parsing stack:", stack);
465
+ const results = stack
466
+ .split("\n")
467
+ .map((line) => line.trim())
468
+ .map((line) => {
469
+ // Try URL regex first (more reliable for complex URLs)
470
+ const urlMatch = line.match(URL_REGEX);
471
+ if (urlMatch) {
472
+ const [, url, lineNum, colNum] = urlMatch;
473
+ // Extract function name if present
474
+ const fnMatch = line.match(/at\s+([^\s(]+)/);
475
+ const fn = fnMatch ? fnMatch[1] : null;
476
+ return {
477
+ functionName: fn,
478
+ url,
479
+ line: Number(lineNum),
480
+ column: Number(colNum),
481
+ };
482
+ }
483
+ // Fallback to original regex
484
+ const match = line.match(FRAME_REGEX);
485
+ if (!match)
486
+ return null;
487
+ const [, fn, url, lineNum, colNum] = match;
488
+ return {
489
+ functionName: fn || null,
490
+ url: url || null,
491
+ line: lineNum ? Number(lineNum) : null,
492
+ column: colNum ? Number(colNum) : null,
493
+ };
494
+ })
495
+ .filter((f) => !!f && !!f.url && f.line != null && f.column != null);
496
+ debugLog("Parsed frames:", results);
497
+ return results;
498
+ };
499
+ // Generate possible source map URLs for a JS file
500
+ const getSourceMapUrls = (url) => {
501
+ const urls = [];
502
+ const withoutQuery = url.split("?")[0];
503
+ // Standard .map suffix
504
+ urls.push(`${withoutQuery}.map`);
505
+ // Next.js Turbopack might use different patterns
506
+ // Try adding .map before query params
507
+ if (url.includes("?")) {
508
+ urls.push(`${withoutQuery}.map${url.slice(url.indexOf("?"))}`);
509
+ }
510
+ return urls;
511
+ };
512
+ const sourceMapCache = new Map();
513
+ const failedSourceMaps = new Set();
514
+ const inlineSourceMapCache = new Map();
515
+ // Try to extract inline source map from JS file content
516
+ const extractInlineSourceMap = async (jsUrl) => {
517
+ if (inlineSourceMapCache.has(jsUrl))
518
+ return inlineSourceMapCache.get(jsUrl);
519
+ const promise = (async () => {
520
+ try {
521
+ debugLog("Fetching JS file to check for inline source map:", jsUrl);
522
+ const res = await fetch(jsUrl);
523
+ if (!res.ok)
524
+ return null;
525
+ const text = await res.text();
526
+ // Look for inline source map (base64 encoded)
527
+ const inlineMatch = text.match(/\/\/# sourceMappingURL=data:application\/json;(?:charset=utf-8;)?base64,([^\s]+)/);
528
+ if (inlineMatch) {
529
+ debugLog("Found inline source map in:", jsUrl);
530
+ const base64 = inlineMatch[1];
531
+ const decoded = atob(base64);
532
+ const map = JSON.parse(decoded);
533
+ return await new sourceMapJs.SourceMapConsumer(map);
534
+ }
535
+ // Also check for sourceMappingURL comment pointing to a file
536
+ const urlMatch = text.match(/\/\/# sourceMappingURL=([^\s]+)/);
537
+ if (urlMatch && !urlMatch[1].startsWith("data:")) {
538
+ const mapPath = urlMatch[1];
539
+ // Resolve relative to JS file
540
+ const baseUrl = jsUrl.substring(0, jsUrl.lastIndexOf("/") + 1);
541
+ const mapUrl = mapPath.startsWith("http") ? mapPath : baseUrl + mapPath;
542
+ debugLog("Found sourceMappingURL comment pointing to:", mapUrl);
543
+ const mapRes = await fetch(mapUrl);
544
+ if (mapRes.ok) {
545
+ const mapText = await mapRes.text();
546
+ const map = JSON.parse(mapText);
547
+ return await new sourceMapJs.SourceMapConsumer(map);
548
+ }
549
+ }
550
+ return null;
551
+ }
552
+ catch (e) {
553
+ debugLog("Error extracting inline source map:", e);
554
+ return null;
555
+ }
556
+ })();
557
+ inlineSourceMapCache.set(jsUrl, promise);
558
+ return promise;
559
+ };
560
+ const fetchSourceMap = async (jsUrl) => {
561
+ // Check if we already tried and failed
562
+ if (failedSourceMaps.has(jsUrl))
563
+ return null;
564
+ if (sourceMapCache.has(jsUrl))
565
+ return sourceMapCache.get(jsUrl);
566
+ const promise = (async () => {
567
+ const mapUrls = getSourceMapUrls(jsUrl);
568
+ // First try standard .map URLs
569
+ for (const mapUrl of mapUrls) {
570
+ try {
571
+ debugLog("Fetching source map:", mapUrl);
572
+ const res = await fetch(mapUrl);
573
+ if (!res.ok) {
574
+ debugLog("Source map not found at:", mapUrl, res.status);
575
+ continue;
576
+ }
577
+ const text = await res.text();
578
+ // Check if it's valid JSON
579
+ let map;
580
+ try {
581
+ map = JSON.parse(text);
582
+ }
583
+ catch {
584
+ debugLog("Invalid source map JSON at:", mapUrl);
585
+ continue;
586
+ }
587
+ debugLog("Source map loaded successfully from:", mapUrl);
588
+ const consumer = await new sourceMapJs.SourceMapConsumer(map);
589
+ return consumer;
590
+ }
591
+ catch (e) {
592
+ debugLog("Error fetching source map:", mapUrl, e);
593
+ continue;
594
+ }
595
+ }
596
+ // Try inline source map as fallback
597
+ const inlineConsumer = await extractInlineSourceMap(jsUrl);
598
+ if (inlineConsumer) {
599
+ return inlineConsumer;
600
+ }
601
+ failedSourceMaps.add(jsUrl);
602
+ return null;
603
+ })();
604
+ sourceMapCache.set(jsUrl, promise);
605
+ return promise;
606
+ };
607
+ const resolveFromStack = async (fiber) => {
608
+ // Handle both Error object and string stack
609
+ let stackString;
610
+ if (fiber?._debugStack?.stack) {
611
+ stackString = fiber._debugStack.stack;
612
+ }
613
+ else if (typeof fiber?._debugStack === "string") {
614
+ stackString = fiber._debugStack;
615
+ }
616
+ if (!stackString) {
617
+ debugLog("No stack string found on fiber");
618
+ return null;
619
+ }
620
+ const frames = parseStack(stackString);
621
+ // Find first non-internal frame (this is the component that created the element)
622
+ for (const frame of frames) {
623
+ if (!frame.url || frame.line == null || frame.column == null)
624
+ continue;
625
+ if (isInternalFrame(frame)) {
626
+ debugLog("Skipping internal frame:", frame.functionName);
627
+ continue;
628
+ }
629
+ debugLog("Processing frame:", frame);
630
+ const consumer = await fetchSourceMap(frame.url);
631
+ if (!consumer) {
632
+ debugLog("No source map for:", frame.url);
633
+ // Even without source map, we can use the bundled location
634
+ // This at least gives some info for debugging
635
+ continue;
636
+ }
637
+ const original = consumer.originalPositionFor({
638
+ line: frame.line,
639
+ column: frame.column,
640
+ });
641
+ debugLog("Original position:", original);
642
+ if (original.source && original.line != null && original.column != null) {
643
+ // Clean up the source path
644
+ let fileName = original.source;
645
+ // Remove webpack/turbopack prefixes
646
+ fileName = fileName.replace(/^webpack:\/\/[^/]*\//, "");
647
+ fileName = fileName.replace(/^\[project\]\//, "");
648
+ fileName = fileName.replace(/^\.\//, "");
649
+ return {
650
+ fileName,
651
+ lineNumber: original.line,
652
+ columnNumber: original.column,
653
+ };
654
+ }
655
+ }
656
+ debugLog("Could not resolve source from stack");
657
+ return null;
658
+ };
659
+ const resolveFromFiber = async (fiber, element) => {
660
+ if (!fiber)
661
+ return Promise.resolve(null);
662
+ const componentName = getComponentName(fiber, element);
663
+ debugLog("Resolving source for:", componentName);
664
+ // Try to find source from _debugSource first
665
+ const immediate = findDebugSource(fiber);
666
+ if (immediate) {
667
+ debugLog("Found immediate source:", immediate);
668
+ return Promise.resolve({
669
+ componentName,
670
+ fileName: immediate.fileName,
671
+ lineNumber: immediate.lineNumber,
672
+ columnNumber: immediate.columnNumber,
673
+ fiber,
674
+ element,
675
+ });
676
+ }
677
+ // Fall back to stack trace parsing
678
+ const stackSource = await resolveFromStack(fiber);
679
+ if (stackSource) {
680
+ debugLog("Found stack source:", stackSource);
681
+ return {
682
+ componentName,
683
+ fileName: stackSource.fileName,
684
+ lineNumber: stackSource.lineNumber,
685
+ columnNumber: stackSource.columnNumber,
686
+ fiber,
687
+ element,
688
+ };
689
+ }
690
+ // If we have a function name from the component, we can still return useful info
691
+ debugLog("No source found for:", componentName);
692
+ return {
693
+ componentName,
694
+ fileName: "Source not available - ensure dev build with source maps",
695
+ lineNumber: 0,
696
+ columnNumber: 0,
697
+ fiber,
698
+ element,
699
+ };
700
+ };
701
+ const getSourceFromElement = (element) => {
702
+ if (!element)
703
+ return Promise.resolve(null);
704
+ const fiber = getFiberFromNode(element);
705
+ if (!fiber) {
706
+ debugLog("No fiber found for element:", element);
707
+ return Promise.resolve(null);
708
+ }
709
+ debugLog("Found fiber for element:", element, fiber);
710
+ return resolveFromFiber(fiber, element);
711
+ };
712
+ /**
713
+ * Get the ancestor component chain from a fiber up to the root.
714
+ * Returns an array of component names, starting from the immediate parent.
715
+ * e.g., ['Header', 'Layout', 'App'] for a Button inside Header inside Layout inside App
716
+ */
717
+ const getComponentAncestors = (fiber) => {
718
+ const ancestors = [];
719
+ let current = fiber?.return;
720
+ while (current) {
721
+ const name = getComponentName(current);
722
+ if (name && name !== 'AnonymousComponent') {
723
+ ancestors.push(name);
724
+ }
725
+ current = current.return;
726
+ }
727
+ return ancestors;
728
+ };
729
+ // Export for debugging purposes
730
+ const enableDebug = () => {
731
+ globalThis.__FIXDOG_DEBUG__ = true;
732
+ };
733
+
734
+ /*
735
+ * fixdog-sdk types
736
+ * Core interfaces shared across inspector modules.
737
+ */
738
+ const DEFAULT_SHORTCUT = 'ctrl+shift+x';
739
+ const isProd = typeof process !== 'undefined' &&
740
+ typeof process.env !== 'undefined' &&
741
+ process.env.NODE_ENV === 'production';
742
+ const DEFAULT_OPTIONS = {
743
+ shortcut: DEFAULT_SHORTCUT,
744
+ useOptionClick: true,
745
+ enabled: !isProd,
746
+ zIndex: 999999,
747
+ borderColor: '#61dafb',
748
+ tooltipPosition: 'auto',
749
+ enableSidebar: true,
750
+ apiEndpoint: 'http://localhost:3000',
751
+ };
752
+
753
+ const normalize = (token) => token.trim().toLowerCase();
754
+ const tokenToFlags = (tokens) => {
755
+ const flags = { ctrl: false, meta: false, alt: false, shift: false, key: null };
756
+ tokens.forEach((token) => {
757
+ switch (normalize(token)) {
758
+ case 'ctrl':
759
+ case 'control':
760
+ flags.ctrl = true;
761
+ break;
762
+ case 'cmd':
763
+ case 'command':
764
+ case 'meta':
765
+ flags.meta = true;
766
+ break;
767
+ case 'alt':
768
+ case 'option':
769
+ flags.alt = true;
770
+ break;
771
+ case 'shift':
772
+ flags.shift = true;
773
+ break;
774
+ default:
775
+ flags.key = normalize(token);
776
+ }
777
+ });
778
+ return flags;
779
+ };
780
+ const parseShortcut = (shortcut) => {
781
+ const value = shortcut || DEFAULT_SHORTCUT;
782
+ return tokenToFlags(value.split('+'));
783
+ };
784
+ const matchesShortcut = (event, shortcut) => {
785
+ const parsed = parseShortcut(shortcut);
786
+ const key = event.key?.toLowerCase();
787
+ return ((!!parsed.key ? key === parsed.key : true) &&
788
+ event.ctrlKey === parsed.ctrl &&
789
+ event.metaKey === parsed.meta &&
790
+ event.altKey === parsed.alt &&
791
+ event.shiftKey === parsed.shift);
792
+ };
793
+ const isOptionClick = (event, useOptionClick) => {
794
+ if (!useOptionClick)
795
+ return false;
796
+ return event.altKey && event.button === 0; // left click + alt/option
797
+ };
798
+
799
+ // Dynamic import helper for the isolated sidebar runtime
800
+ // This loads a separate bundle with its own React instance to avoid version conflicts
801
+ async function loadSidebarRuntime() {
802
+ // Get the URL of the current module and resolve the sidebar bundle path
803
+ const currentModuleUrl = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('inspector-DaRVppX9.cjs.js', document.baseURI).href));
804
+ const sidebarUrl = new URL("./sidebar-runtime.esm.js", currentModuleUrl).href;
805
+ try {
806
+ const module = await import(/* @vite-ignore */ sidebarUrl);
807
+ return module;
808
+ }
809
+ catch (error) {
810
+ console.error("[fixdog-sdk] Failed to load sidebar runtime:", error);
811
+ throw error;
812
+ }
813
+ }
814
+ const message = {
815
+ noFiber: "[fixdog-sdk] No React fiber found for element.",
816
+ disabled: "[fixdog-sdk] Inspector disabled (production build)."};
817
+ class Inspector {
818
+ async findInfoInElementAndAncestors(element) {
819
+ let current = element;
820
+ while (current) {
821
+ const info = await getSourceFromElement(current);
822
+ if (info)
823
+ return { element: current, info };
824
+ current = current.parentElement;
825
+ }
826
+ return { element: null, info: null };
827
+ }
828
+ async findInfoFromEvent(event) {
829
+ const path = event.composedPath?.() ?? [];
830
+ for (const node of path) {
831
+ if (node instanceof Element) {
832
+ const found = await this.findInfoInElementAndAncestors(node);
833
+ if (found.info)
834
+ return found;
835
+ }
836
+ }
837
+ const target = event.target;
838
+ return this.findInfoInElementAndAncestors(target);
839
+ }
840
+ constructor(options = {}) {
841
+ this.state = { active: false, persistent: false };
842
+ this.lastElement = null;
843
+ this.lastInfo = null;
844
+ this.boundMouseMove = (e) => this.handleMouseMove(e);
845
+ this.boundClick = (e) => this.handleClick(e);
846
+ this.boundKeyDown = (e) => this.handleKeyDown(e);
847
+ this.boundScroll = () => this.reposition();
848
+ this.boundResize = () => this.reposition();
849
+ this.mounted = false;
850
+ this.requestId = 0;
851
+ // Multi-select state
852
+ this.selectedComponents = [];
853
+ this.nextComponentId = 1;
854
+ this.sidebarOpen = false;
855
+ this.sidebarInitialized = false;
856
+ this.boundClearSelections = () => this.handleClearSelections();
857
+ this.boundRemoveComponent = (e) => this.handleRemoveComponent(e);
858
+ this.options = {
859
+ ...DEFAULT_OPTIONS,
860
+ ...options,
861
+ enabled: typeof options.enabled === "boolean"
862
+ ? options.enabled
863
+ : DEFAULT_OPTIONS.enabled,
864
+ };
865
+ this.overlayOptions = {
866
+ zIndex: this.options.zIndex ?? DEFAULT_OPTIONS.zIndex,
867
+ borderColor: this.options.borderColor ?? DEFAULT_OPTIONS.borderColor,
868
+ tooltipPosition: this.options.tooltipPosition ?? DEFAULT_OPTIONS.tooltipPosition,
869
+ };
870
+ this.overlay = new Overlay(this.overlayOptions);
871
+ // Initialize sidebar if enabled
872
+ if (this.options.enableSidebar && !this.sidebarInitialized) {
873
+ this.initializeSidebar();
874
+ }
875
+ }
876
+ async initializeSidebar() {
877
+ if (typeof window === "undefined")
878
+ return;
879
+ try {
880
+ // Dynamically load the sidebar runtime which has its own bundled React
881
+ // This ensures the sidebar works regardless of the host app's React version
882
+ const { initializeSidebarRuntime } = await loadSidebarRuntime();
883
+ initializeSidebarRuntime({
884
+ apiEndpoint: this.options.apiEndpoint ?? DEFAULT_OPTIONS.apiEndpoint,
885
+ });
886
+ this.sidebarInitialized = true;
887
+ console.log("[fixdog-sdk] Sidebar runtime loaded successfully (isolated React)");
888
+ }
889
+ catch (error) {
890
+ console.error("[fixdog-sdk] Failed to initialize sidebar:", error);
891
+ }
892
+ }
893
+ activate() {
894
+ if (!this.options.enabled) {
895
+ console.info(message.disabled);
896
+ return;
897
+ }
898
+ if (this.mounted)
899
+ return;
900
+ this.mounted = true;
901
+ this.overlay.mount();
902
+ window.addEventListener("mousemove", this.boundMouseMove, true);
903
+ window.addEventListener("click", this.boundClick, true);
904
+ window.addEventListener("keydown", this.boundKeyDown, true);
905
+ window.addEventListener("scroll", this.boundScroll, true);
906
+ window.addEventListener("resize", this.boundResize, true);
907
+ window.addEventListener("fixdog:clear-selections", this.boundClearSelections);
908
+ window.addEventListener("fixdog:remove-component", this.boundRemoveComponent);
909
+ this.state.active = true;
910
+ }
911
+ deactivate() {
912
+ if (!this.mounted)
913
+ return;
914
+ window.removeEventListener("mousemove", this.boundMouseMove, true);
915
+ window.removeEventListener("click", this.boundClick, true);
916
+ window.removeEventListener("keydown", this.boundKeyDown, true);
917
+ window.removeEventListener("scroll", this.boundScroll, true);
918
+ window.removeEventListener("resize", this.boundResize, true);
919
+ window.removeEventListener("fixdog:clear-selections", this.boundClearSelections);
920
+ window.removeEventListener("fixdog:remove-component", this.boundRemoveComponent);
921
+ this.overlay.hide();
922
+ this.clearSelections();
923
+ this.lastElement = null;
924
+ this.lastInfo = null;
925
+ this.state = { active: false, persistent: false };
926
+ this.mounted = false;
927
+ }
928
+ destroy() {
929
+ this.deactivate();
930
+ this.overlay.destroy();
931
+ }
932
+ async handleMouseMove(event) {
933
+ if (!this.state.active || !this.options.enabled)
934
+ return;
935
+ const currentId = ++this.requestId;
936
+ const { element: target, info } = await this.findInfoFromEvent(event);
937
+ const shouldInspect = this.state.persistent || event.altKey;
938
+ if (!shouldInspect || !target) {
939
+ this.overlay.hide();
940
+ this.lastElement = null;
941
+ this.lastInfo = null;
942
+ this.options.onHover?.(null);
943
+ return;
944
+ }
945
+ if (currentId !== this.requestId)
946
+ return; // stale
947
+ if (!info) {
948
+ this.overlay.hide();
949
+ this.options.onHover?.(null);
950
+ return;
951
+ }
952
+ this.lastElement = target;
953
+ this.lastInfo = info;
954
+ this.overlay.update(target, info);
955
+ this.options.onHover?.(info);
956
+ }
957
+ handleClick(event) {
958
+ if (!this.state.active || !this.options.enabled)
959
+ return;
960
+ const shouldHandle = this.state.persistent ||
961
+ isOptionClick(event, this.options.useOptionClick ?? true);
962
+ if (!shouldHandle)
963
+ return;
964
+ const currentId = ++this.requestId;
965
+ this.findInfoFromEvent(event).then(({ element: target, info }) => {
966
+ if (!target)
967
+ return;
968
+ event.preventDefault();
969
+ event.stopPropagation();
970
+ if (currentId !== this.requestId)
971
+ return; // stale
972
+ if (!info) {
973
+ console.warn(message.noFiber);
974
+ return;
975
+ }
976
+ this.lastElement = target;
977
+ this.lastInfo = info;
978
+ this.overlay.update(target, info);
979
+ this.log(info);
980
+ // Open sidebar with element info
981
+ if (this.options.enableSidebar) {
982
+ this.openSidebar(info);
983
+ }
984
+ this.options.onClick?.(info);
985
+ });
986
+ }
987
+ createSelectedComponent(info) {
988
+ const elementInfo = {
989
+ componentName: info.componentName,
990
+ filePath: info.fileName,
991
+ line: info.lineNumber,
992
+ column: info.columnNumber,
993
+ componentTree: info.fiber ? getComponentAncestors(info.fiber) : undefined,
994
+ };
995
+ // Get bounding box if element is available
996
+ if (info.element) {
997
+ const rect = info.element.getBoundingClientRect();
998
+ elementInfo.box = {
999
+ x: rect.x,
1000
+ y: rect.y,
1001
+ width: rect.width,
1002
+ height: rect.height,
1003
+ };
1004
+ }
1005
+ // Create editor URL format
1006
+ const editorUrl = `${info.fileName}:${info.lineNumber}:${info.columnNumber}`;
1007
+ // Get next available ID (fills gaps)
1008
+ const id = this.getNextAvailableId();
1009
+ const selectedComponent = {
1010
+ id,
1011
+ editorUrl,
1012
+ elementInfo,
1013
+ elementRef: info.element ?? undefined,
1014
+ };
1015
+ return selectedComponent;
1016
+ }
1017
+ openSidebar(info) {
1018
+ const selectedComponent = this.createSelectedComponent(info);
1019
+ if (!this.sidebarOpen) {
1020
+ // First selection - open sidebar
1021
+ this.selectedComponents = [selectedComponent];
1022
+ this.sidebarOpen = true;
1023
+ // Add selection highlight to overlay (if element exists)
1024
+ if (info.element) {
1025
+ this.overlay.addSelection(selectedComponent.id, info.element, selectedComponent.elementInfo.componentName);
1026
+ }
1027
+ // Dispatch open sidebar event
1028
+ const event = new CustomEvent("fixdog:open-sidebar", {
1029
+ detail: {
1030
+ editorUrl: selectedComponent.editorUrl,
1031
+ elementInfo: selectedComponent.elementInfo,
1032
+ selectedComponent,
1033
+ },
1034
+ });
1035
+ window.dispatchEvent(event);
1036
+ }
1037
+ else {
1038
+ // Sidebar already open - add to selection
1039
+ this.selectedComponents.push(selectedComponent);
1040
+ // Add selection highlight to overlay (if element exists)
1041
+ if (info.element) {
1042
+ this.overlay.addSelection(selectedComponent.id, info.element, selectedComponent.elementInfo.componentName);
1043
+ }
1044
+ // Dispatch add component event
1045
+ const event = new CustomEvent("fixdog:add-component", {
1046
+ detail: {
1047
+ selectedComponent,
1048
+ },
1049
+ });
1050
+ window.dispatchEvent(event);
1051
+ }
1052
+ // Store selected components globally for reference
1053
+ window.__FIXDOG_SELECTED_COMPONENTS__ = this.selectedComponents;
1054
+ }
1055
+ clearSelections() {
1056
+ this.selectedComponents = [];
1057
+ this.nextComponentId = 1;
1058
+ this.sidebarOpen = false;
1059
+ this.overlay.clearSelections();
1060
+ window.__FIXDOG_SELECTED_COMPONENTS__ = [];
1061
+ }
1062
+ handleClearSelections() {
1063
+ this.clearSelections();
1064
+ }
1065
+ handleRemoveComponent(event) {
1066
+ const { componentId } = event.detail;
1067
+ this.removeSelection(componentId);
1068
+ }
1069
+ // Calculate the next available ID (fill gaps first)
1070
+ getNextAvailableId() {
1071
+ if (this.selectedComponents.length === 0) {
1072
+ return 1;
1073
+ }
1074
+ // Get all current IDs sorted
1075
+ const usedIds = new Set(this.selectedComponents.map((c) => c.id));
1076
+ // Find the first gap starting from 1
1077
+ let nextId = 1;
1078
+ while (usedIds.has(nextId)) {
1079
+ nextId++;
1080
+ }
1081
+ return nextId;
1082
+ }
1083
+ // Public method to remove a specific selection
1084
+ removeSelection(componentId) {
1085
+ this.selectedComponents = this.selectedComponents.filter((c) => c.id !== componentId);
1086
+ this.overlay.removeSelection(componentId);
1087
+ window.__FIXDOG_SELECTED_COMPONENTS__ = this.selectedComponents;
1088
+ // Recalculate next ID to fill gaps
1089
+ this.nextComponentId = this.getNextAvailableId();
1090
+ // If no more selections, close sidebar
1091
+ if (this.selectedComponents.length === 0) {
1092
+ this.sidebarOpen = false;
1093
+ this.nextComponentId = 1;
1094
+ }
1095
+ }
1096
+ handleKeyDown(event) {
1097
+ if (!this.options.enabled)
1098
+ return;
1099
+ if (matchesShortcut(event, this.options.shortcut)) {
1100
+ event.preventDefault();
1101
+ event.stopPropagation();
1102
+ this.togglePersistent();
1103
+ return;
1104
+ }
1105
+ if (event.key === "Escape") {
1106
+ this.state.persistent = false;
1107
+ this.overlay.hide();
1108
+ // Don't clear selections on Escape - let sidebar handle that
1109
+ }
1110
+ }
1111
+ togglePersistent() {
1112
+ this.state.persistent = !this.state.persistent;
1113
+ if (!this.state.persistent) {
1114
+ this.overlay.hide();
1115
+ }
1116
+ }
1117
+ reposition() {
1118
+ if (this.lastElement && this.lastInfo) {
1119
+ this.overlay.update(this.lastElement, this.lastInfo);
1120
+ }
1121
+ // Also update selection positions
1122
+ this.overlay.updateSelectionPositions();
1123
+ }
1124
+ log(info) {
1125
+ const { componentName, fileName, lineNumber } = info;
1126
+ console.log("%c[fixdog-sdk]%c %s %c@ %s:%d", "color: #61dafb; font-weight: bold", "color: #98c379; font-weight: bold", componentName, "color: #888", fileName, lineNumber);
1127
+ console.log(info);
1128
+ }
1129
+ }
1130
+
1131
+ exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
1132
+ exports.Inspector = Inspector;
1133
+ exports.enableDebug = enableDebug;
1134
+ //# sourceMappingURL=inspector-DaRVppX9.cjs.js.map