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