@react-component-selector-mcp/react 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,1198 @@
1
+ "use client";
2
+ import { useCallback, useState, useRef, useEffect } from 'react';
3
+ import { z } from 'zod';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { nanoid } from 'nanoid';
6
+ import { SourceMapConsumer } from 'source-map-js';
7
+ import { toPng } from 'html-to-image';
8
+
9
+ var ComponentTypeSchema = z.enum(["function", "class", "forwardRef", "memo"]);
10
+ var ComponentInfoSchema = z.object({
11
+ name: z.string(),
12
+ type: ComponentTypeSchema
13
+ });
14
+ var SourceLocationSchema = z.object({
15
+ filePath: z.string().nullable(),
16
+ lineNumber: z.number().nullable(),
17
+ columnNumber: z.number().nullable()
18
+ });
19
+ var BoundingRectSchema = z.object({
20
+ x: z.number(),
21
+ y: z.number(),
22
+ width: z.number(),
23
+ height: z.number(),
24
+ top: z.number(),
25
+ right: z.number(),
26
+ bottom: z.number(),
27
+ left: z.number()
28
+ });
29
+ var DOMInfoSchema = z.object({
30
+ tagName: z.string(),
31
+ className: z.string().nullable(),
32
+ boundingRect: BoundingRectSchema
33
+ });
34
+ var ScreenshotSchema = z.object({
35
+ dataUrl: z.string(),
36
+ width: z.number(),
37
+ height: z.number()
38
+ });
39
+ var SelectionContextSchema = z.object({
40
+ pageUrl: z.string(),
41
+ parentComponents: z.array(z.string())
42
+ });
43
+ var SelectionDataSchema = z.object({
44
+ id: z.string(),
45
+ timestamp: z.number(),
46
+ component: ComponentInfoSchema,
47
+ source: SourceLocationSchema,
48
+ props: z.record(z.unknown()),
49
+ state: z.record(z.unknown()).nullable(),
50
+ dom: DOMInfoSchema,
51
+ screenshot: ScreenshotSchema,
52
+ context: SelectionContextSchema
53
+ });
54
+ var MessageTypeSchema = z.enum([
55
+ "selection",
56
+ "ping",
57
+ "pong",
58
+ "connect",
59
+ "disconnect",
60
+ "error",
61
+ "selectionMode"
62
+ ]);
63
+ var BaseMessageSchema = z.object({
64
+ type: MessageTypeSchema,
65
+ timestamp: z.number()
66
+ });
67
+ var SelectionMessageSchema = BaseMessageSchema.extend({
68
+ type: z.literal("selection"),
69
+ payload: SelectionDataSchema
70
+ });
71
+ var PingMessageSchema = BaseMessageSchema.extend({
72
+ type: z.literal("ping")
73
+ });
74
+ var PongMessageSchema = BaseMessageSchema.extend({
75
+ type: z.literal("pong")
76
+ });
77
+ var ConnectMessageSchema = BaseMessageSchema.extend({
78
+ type: z.literal("connect"),
79
+ payload: z.object({
80
+ clientId: z.string(),
81
+ userAgent: z.string().optional()
82
+ })
83
+ });
84
+ var DisconnectMessageSchema = BaseMessageSchema.extend({
85
+ type: z.literal("disconnect"),
86
+ payload: z.object({
87
+ clientId: z.string(),
88
+ reason: z.string().optional()
89
+ })
90
+ });
91
+ var ErrorMessageSchema = BaseMessageSchema.extend({
92
+ type: z.literal("error"),
93
+ payload: z.object({
94
+ code: z.string(),
95
+ message: z.string()
96
+ })
97
+ });
98
+ var SelectionModeMessageSchema = BaseMessageSchema.extend({
99
+ type: z.literal("selectionMode"),
100
+ payload: z.object({
101
+ enabled: z.boolean(),
102
+ message: z.string().optional()
103
+ })
104
+ });
105
+ var WebSocketMessageSchema = z.discriminatedUnion("type", [
106
+ SelectionMessageSchema,
107
+ PingMessageSchema,
108
+ PongMessageSchema,
109
+ ConnectMessageSchema,
110
+ DisconnectMessageSchema,
111
+ ErrorMessageSchema,
112
+ SelectionModeMessageSchema
113
+ ]);
114
+ function createMessage(type, payload) {
115
+ const base = { type, timestamp: Date.now() };
116
+ if (payload !== void 0) {
117
+ return { ...base, payload };
118
+ }
119
+ return base;
120
+ }
121
+
122
+ // src/hooks/useWebSocketClient.ts
123
+ function useWebSocketClient(options) {
124
+ const { port } = options;
125
+ const onSelectionModeChangeRef = useRef(options.onSelectionModeChange);
126
+ const onConnectionChangeRef = useRef(options.onConnectionChange);
127
+ onSelectionModeChangeRef.current = options.onSelectionModeChange;
128
+ onConnectionChangeRef.current = options.onConnectionChange;
129
+ const wsRef = useRef(null);
130
+ const reconnectTimeoutRef = useRef(null);
131
+ const pingIntervalRef = useRef(null);
132
+ const isCleaningUpRef = useRef(false);
133
+ const [connected, setConnected] = useState(false);
134
+ const [clientId, setClientId] = useState(null);
135
+ const sendSelection = useCallback((data) => {
136
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
137
+ const message = createMessage("selection", data);
138
+ wsRef.current.send(JSON.stringify(message));
139
+ } else {
140
+ console.warn("[component-picker] Cannot send selection - not connected");
141
+ }
142
+ }, []);
143
+ useEffect(() => {
144
+ isCleaningUpRef.current = false;
145
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
146
+ return;
147
+ }
148
+ const connect = () => {
149
+ if (wsRef.current?.readyState === WebSocket.OPEN || isCleaningUpRef.current) {
150
+ return;
151
+ }
152
+ try {
153
+ const ws = new WebSocket(`ws://localhost:${port}`);
154
+ ws.onopen = () => {
155
+ if (isCleaningUpRef.current) {
156
+ ws.close();
157
+ return;
158
+ }
159
+ console.log("[component-picker] Connected to server");
160
+ setConnected(true);
161
+ onConnectionChangeRef.current?.(true);
162
+ pingIntervalRef.current = setInterval(() => {
163
+ if (ws.readyState === WebSocket.OPEN) {
164
+ ws.send(JSON.stringify(createMessage("ping")));
165
+ }
166
+ }, 25e3);
167
+ };
168
+ ws.onclose = () => {
169
+ console.log("[component-picker] Disconnected from server");
170
+ setConnected(false);
171
+ setClientId(null);
172
+ onConnectionChangeRef.current?.(false);
173
+ if (pingIntervalRef.current) {
174
+ clearInterval(pingIntervalRef.current);
175
+ pingIntervalRef.current = null;
176
+ }
177
+ if (!isCleaningUpRef.current) {
178
+ reconnectTimeoutRef.current = setTimeout(() => {
179
+ connect();
180
+ }, 3e3);
181
+ }
182
+ };
183
+ ws.onerror = () => {
184
+ };
185
+ ws.onmessage = (event) => {
186
+ try {
187
+ const parsed = JSON.parse(event.data);
188
+ const result = WebSocketMessageSchema.safeParse(parsed);
189
+ if (!result.success) {
190
+ console.warn("[component-picker] Invalid message:", result.error);
191
+ return;
192
+ }
193
+ const message = result.data;
194
+ switch (message.type) {
195
+ case "connect":
196
+ setClientId(message.payload.clientId);
197
+ break;
198
+ case "selectionMode":
199
+ onSelectionModeChangeRef.current?.(
200
+ message.payload.enabled,
201
+ message.payload.message
202
+ );
203
+ break;
204
+ case "pong":
205
+ break;
206
+ default:
207
+ break;
208
+ }
209
+ } catch (error) {
210
+ console.error("[component-picker] Error handling message:", error);
211
+ }
212
+ };
213
+ wsRef.current = ws;
214
+ } catch (error) {
215
+ console.error("[component-picker] Failed to connect:", error);
216
+ if (!isCleaningUpRef.current) {
217
+ reconnectTimeoutRef.current = setTimeout(() => {
218
+ connect();
219
+ }, 3e3);
220
+ }
221
+ }
222
+ };
223
+ connect();
224
+ return () => {
225
+ isCleaningUpRef.current = true;
226
+ if (reconnectTimeoutRef.current) {
227
+ clearTimeout(reconnectTimeoutRef.current);
228
+ reconnectTimeoutRef.current = null;
229
+ }
230
+ if (pingIntervalRef.current) {
231
+ clearInterval(pingIntervalRef.current);
232
+ pingIntervalRef.current = null;
233
+ }
234
+ if (wsRef.current) {
235
+ wsRef.current.close();
236
+ wsRef.current = null;
237
+ }
238
+ };
239
+ }, [port]);
240
+ return {
241
+ connected,
242
+ sendSelection,
243
+ clientId
244
+ };
245
+ }
246
+ function useKeyboardShortcut(options) {
247
+ const { key = "C", onTrigger, enabled = true } = options;
248
+ const handleKeyDown = useCallback(
249
+ (event) => {
250
+ if (!enabled) return;
251
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
252
+ const modifierKey = isMac ? event.metaKey : event.ctrlKey;
253
+ if (modifierKey && event.altKey && event.key.toUpperCase() === key.toUpperCase()) {
254
+ event.preventDefault();
255
+ event.stopPropagation();
256
+ onTrigger();
257
+ }
258
+ },
259
+ [key, onTrigger, enabled]
260
+ );
261
+ useEffect(() => {
262
+ if (!enabled) return;
263
+ window.addEventListener("keydown", handleKeyDown, true);
264
+ return () => {
265
+ window.removeEventListener("keydown", handleKeyDown, true);
266
+ };
267
+ }, [handleKeyDown, enabled]);
268
+ }
269
+ function useSelectionMode() {
270
+ const [isSelectionMode, setIsSelectionMode] = useState(false);
271
+ const [selectionMessage, setSelectionMessage] = useState();
272
+ const enableSelectionMode = useCallback((message) => {
273
+ setIsSelectionMode(true);
274
+ setSelectionMessage(message);
275
+ }, []);
276
+ const disableSelectionMode = useCallback(() => {
277
+ setIsSelectionMode(false);
278
+ setSelectionMessage(void 0);
279
+ }, []);
280
+ const toggleSelectionMode = useCallback(() => {
281
+ setIsSelectionMode((prev) => !prev);
282
+ if (isSelectionMode) {
283
+ setSelectionMessage(void 0);
284
+ }
285
+ }, [isSelectionMode]);
286
+ return {
287
+ isSelectionMode,
288
+ selectionMessage,
289
+ enableSelectionMode,
290
+ disableSelectionMode,
291
+ toggleSelectionMode
292
+ };
293
+ }
294
+ var FIBER_TAGS = {
295
+ FunctionComponent: 0,
296
+ ClassComponent: 1,
297
+ ForwardRef: 11,
298
+ MemoComponent: 14,
299
+ SimpleMemoComponent: 15
300
+ };
301
+ function useFiberInspector() {
302
+ const getFiberFromElement = useCallback((element) => {
303
+ if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers) {
304
+ for (const renderer of window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values()) {
305
+ const fiber = renderer.findFiberByHostInstance?.(element);
306
+ if (fiber) return fiber;
307
+ }
308
+ }
309
+ const fiberKey = Object.keys(element).find(
310
+ (key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
311
+ );
312
+ if (fiberKey) {
313
+ const fiber = element[fiberKey];
314
+ return fiber ?? null;
315
+ }
316
+ return null;
317
+ }, []);
318
+ const getComponentType = useCallback((fiber) => {
319
+ switch (fiber.tag) {
320
+ case FIBER_TAGS.ClassComponent:
321
+ return "class";
322
+ case FIBER_TAGS.ForwardRef:
323
+ return "forwardRef";
324
+ case FIBER_TAGS.MemoComponent:
325
+ case FIBER_TAGS.SimpleMemoComponent:
326
+ return "memo";
327
+ case FIBER_TAGS.FunctionComponent:
328
+ default:
329
+ return "function";
330
+ }
331
+ }, []);
332
+ const getComponentName2 = useCallback((fiber) => {
333
+ const type = fiber.type;
334
+ if (!type) return "Unknown";
335
+ if (typeof type === "function") {
336
+ const fn = type;
337
+ return fn.displayName || fn.name || "Anonymous";
338
+ }
339
+ if (typeof type === "object" && type !== null) {
340
+ const obj = type;
341
+ if (obj.displayName) return obj.displayName;
342
+ if (obj.render) return obj.render.displayName || obj.render.name || "ForwardRef";
343
+ if (obj.type) return obj.type.displayName || obj.type.name || "Memo";
344
+ }
345
+ return "Unknown";
346
+ }, []);
347
+ const findNearestComponentFiber = useCallback((fiber) => {
348
+ let current = fiber;
349
+ while (current) {
350
+ const tag = current.tag;
351
+ if (tag === FIBER_TAGS.FunctionComponent || tag === FIBER_TAGS.ClassComponent || tag === FIBER_TAGS.ForwardRef || tag === FIBER_TAGS.MemoComponent || tag === FIBER_TAGS.SimpleMemoComponent) {
352
+ const name = getComponentName2(current);
353
+ if (!name.startsWith("_") && name !== "Unknown" && name !== "Anonymous") {
354
+ return current;
355
+ }
356
+ }
357
+ current = current.return;
358
+ }
359
+ return null;
360
+ }, [getComponentName2]);
361
+ const getParentComponents = useCallback(
362
+ (fiber) => {
363
+ const parents = [];
364
+ let current = fiber.return;
365
+ while (current && parents.length < 10) {
366
+ const tag = current.tag;
367
+ if (tag === FIBER_TAGS.FunctionComponent || tag === FIBER_TAGS.ClassComponent || tag === FIBER_TAGS.ForwardRef || tag === FIBER_TAGS.MemoComponent || tag === FIBER_TAGS.SimpleMemoComponent) {
368
+ const name = getComponentName2(current);
369
+ if (!name.startsWith("_") && name !== "Unknown") {
370
+ parents.push(name);
371
+ }
372
+ }
373
+ current = current.return;
374
+ }
375
+ return parents;
376
+ },
377
+ [getComponentName2]
378
+ );
379
+ const serializeProps = useCallback((props) => {
380
+ const result = {};
381
+ for (const [key, value] of Object.entries(props)) {
382
+ if (key === "children" || key === "key" || key === "ref") continue;
383
+ try {
384
+ if (typeof value === "function") {
385
+ result[key] = "[Function]";
386
+ } else if (value instanceof Element) {
387
+ result[key] = "[Element]";
388
+ } else if (typeof value === "object" && value !== null) {
389
+ JSON.stringify(value);
390
+ result[key] = value;
391
+ } else {
392
+ result[key] = value;
393
+ }
394
+ } catch {
395
+ result[key] = "[Circular or Unserializable]";
396
+ }
397
+ }
398
+ return result;
399
+ }, []);
400
+ const extractState = useCallback((fiber) => {
401
+ if (fiber.tag !== FIBER_TAGS.ClassComponent) {
402
+ return null;
403
+ }
404
+ const instance = fiber.stateNode;
405
+ if (instance?.state) {
406
+ try {
407
+ JSON.stringify(instance.state);
408
+ return instance.state;
409
+ } catch {
410
+ return { error: "[Unserializable state]" };
411
+ }
412
+ }
413
+ return null;
414
+ }, []);
415
+ const extractFiberData = useCallback(
416
+ (fiber) => {
417
+ return {
418
+ componentInfo: {
419
+ name: getComponentName2(fiber),
420
+ type: getComponentType(fiber)
421
+ },
422
+ props: serializeProps(fiber.memoizedProps || {}),
423
+ state: extractState(fiber),
424
+ parentComponents: getParentComponents(fiber),
425
+ debugSource: {
426
+ fileName: fiber._debugSource?.fileName ?? null,
427
+ lineNumber: fiber._debugSource?.lineNumber ?? null,
428
+ columnNumber: fiber._debugSource?.columnNumber ?? null
429
+ }
430
+ };
431
+ },
432
+ [getComponentName2, getComponentType, serializeProps, extractState, getParentComponents]
433
+ );
434
+ return {
435
+ getFiberFromElement,
436
+ extractFiberData,
437
+ findNearestComponentFiber
438
+ };
439
+ }
440
+ function SelectionOverlay({
441
+ enabled,
442
+ message,
443
+ onSelect,
444
+ onCancel
445
+ }) {
446
+ const [highlight, setHighlight] = useState(null);
447
+ const hoveredElementRef = useRef(null);
448
+ const getComponentName2 = useCallback((element) => {
449
+ const fiberKey = Object.keys(element).find(
450
+ (k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$")
451
+ );
452
+ let reactName = null;
453
+ if (fiberKey) {
454
+ const fiber = element[fiberKey];
455
+ if (fiber?.type && typeof fiber.type === "function") {
456
+ const fn = fiber.type;
457
+ reactName = fn.displayName || fn.name || null;
458
+ }
459
+ if (!reactName || reactName === "div" || reactName === "button") {
460
+ let current = fiber;
461
+ while (current?.return) {
462
+ current = current.return;
463
+ if (current?.type && typeof current.type === "function") {
464
+ const fn = current.type;
465
+ const name = fn.displayName || fn.name;
466
+ if (name && !["Fragment", "Suspense", "Provider", "Consumer"].includes(name)) {
467
+ reactName = name;
468
+ break;
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ const textContent = element.textContent?.trim().slice(0, 20) || "";
475
+ const textSuffix = textContent ? ` "${textContent}${element.textContent && element.textContent.length > 20 ? "..." : ""}"` : "";
476
+ if (reactName && !["div", "button", "span", "p", "h1", "h2", "h3"].includes(reactName.toLowerCase())) {
477
+ return `<${reactName}>${textSuffix}`;
478
+ }
479
+ if (element.className && typeof element.className === "string" && element.className.trim()) {
480
+ const classes = element.className.trim().split(/\s+/)[0];
481
+ return `.${classes}${textSuffix}`;
482
+ }
483
+ return `<${element.tagName.toLowerCase()}>${textSuffix}`;
484
+ }, []);
485
+ const handleMouseMove = useCallback(
486
+ (event) => {
487
+ if (!enabled) return;
488
+ const target = event.target;
489
+ if (target.closest("[data-component-picker]")) {
490
+ setHighlight(null);
491
+ hoveredElementRef.current = null;
492
+ return;
493
+ }
494
+ if (["HTML", "BODY", "SCRIPT", "STYLE", "NOSCRIPT"].includes(target.tagName)) {
495
+ setHighlight(null);
496
+ hoveredElementRef.current = null;
497
+ return;
498
+ }
499
+ const rect = target.getBoundingClientRect();
500
+ hoveredElementRef.current = target;
501
+ setHighlight({
502
+ top: rect.top,
503
+ left: rect.left,
504
+ width: rect.width,
505
+ height: rect.height,
506
+ componentName: getComponentName2(target)
507
+ });
508
+ },
509
+ [enabled, getComponentName2]
510
+ );
511
+ const handleClick = useCallback(
512
+ (event) => {
513
+ if (!enabled) return;
514
+ const target = event.target;
515
+ if (target.closest("[data-component-picker]")) {
516
+ return;
517
+ }
518
+ event.preventDefault();
519
+ event.stopPropagation();
520
+ if (hoveredElementRef.current) {
521
+ onSelect(hoveredElementRef.current);
522
+ }
523
+ },
524
+ [enabled, onSelect]
525
+ );
526
+ const handleKeyDown = useCallback(
527
+ (event) => {
528
+ if (!enabled) return;
529
+ if (event.key === "Escape") {
530
+ event.preventDefault();
531
+ onCancel();
532
+ }
533
+ },
534
+ [enabled, onCancel]
535
+ );
536
+ useEffect(() => {
537
+ if (!enabled) {
538
+ setHighlight(null);
539
+ hoveredElementRef.current = null;
540
+ return;
541
+ }
542
+ document.addEventListener("mousemove", handleMouseMove, true);
543
+ document.addEventListener("click", handleClick, true);
544
+ document.addEventListener("keydown", handleKeyDown, true);
545
+ document.body.style.cursor = "crosshair";
546
+ return () => {
547
+ document.removeEventListener("mousemove", handleMouseMove, true);
548
+ document.removeEventListener("click", handleClick, true);
549
+ document.removeEventListener("keydown", handleKeyDown, true);
550
+ document.body.style.cursor = "";
551
+ };
552
+ }, [enabled, handleMouseMove, handleClick, handleKeyDown]);
553
+ if (!enabled) return null;
554
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
555
+ highlight && /* @__PURE__ */ jsx(
556
+ "div",
557
+ {
558
+ "data-component-picker": "highlight",
559
+ style: {
560
+ position: "fixed",
561
+ top: highlight.top,
562
+ left: highlight.left,
563
+ width: highlight.width,
564
+ height: highlight.height,
565
+ border: "2px solid #3b82f6",
566
+ backgroundColor: "rgba(59, 130, 246, 0.1)",
567
+ pointerEvents: "none",
568
+ zIndex: 999998,
569
+ boxSizing: "border-box"
570
+ },
571
+ children: /* @__PURE__ */ jsx(
572
+ "div",
573
+ {
574
+ style: {
575
+ position: "absolute",
576
+ top: -24,
577
+ left: -2,
578
+ padding: "2px 8px",
579
+ backgroundColor: "#3b82f6",
580
+ color: "white",
581
+ fontSize: "12px",
582
+ fontFamily: "system-ui, sans-serif",
583
+ fontWeight: 500,
584
+ borderRadius: "4px 4px 0 0",
585
+ whiteSpace: "nowrap",
586
+ maxWidth: "300px",
587
+ overflow: "hidden",
588
+ textOverflow: "ellipsis"
589
+ },
590
+ children: highlight.componentName
591
+ }
592
+ )
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsxs(
596
+ "div",
597
+ {
598
+ "data-component-picker": "info-bar",
599
+ style: {
600
+ position: "fixed",
601
+ top: 0,
602
+ left: 0,
603
+ right: 0,
604
+ padding: "12px 16px",
605
+ backgroundColor: "#3b82f6",
606
+ color: "white",
607
+ fontFamily: "system-ui, sans-serif",
608
+ fontSize: "14px",
609
+ textAlign: "center",
610
+ zIndex: 999999,
611
+ display: "flex",
612
+ justifyContent: "center",
613
+ alignItems: "center",
614
+ gap: "16px",
615
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
616
+ },
617
+ children: [
618
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: message || "Click a component to select it" }),
619
+ /* @__PURE__ */ jsx("span", { style: { opacity: 0.8, fontSize: "12px" }, children: "Press ESC to cancel" })
620
+ ]
621
+ }
622
+ )
623
+ ] });
624
+ }
625
+ var sourceMapCache = /* @__PURE__ */ new Map();
626
+ var failedFetches = /* @__PURE__ */ new Set();
627
+ async function fetchSourceMap(scriptUrl) {
628
+ if (sourceMapCache.has(scriptUrl)) {
629
+ return sourceMapCache.get(scriptUrl) || null;
630
+ }
631
+ if (failedFetches.has(scriptUrl)) {
632
+ return null;
633
+ }
634
+ try {
635
+ const sourceMapUrl = await findSourceMapUrl(scriptUrl);
636
+ if (!sourceMapUrl) {
637
+ failedFetches.add(scriptUrl);
638
+ sourceMapCache.set(scriptUrl, null);
639
+ return null;
640
+ }
641
+ const response = await fetch(sourceMapUrl);
642
+ if (!response.ok) {
643
+ failedFetches.add(scriptUrl);
644
+ sourceMapCache.set(scriptUrl, null);
645
+ return null;
646
+ }
647
+ const sourceMapData = await response.json();
648
+ const consumer = new SourceMapConsumer(sourceMapData);
649
+ sourceMapCache.set(scriptUrl, consumer);
650
+ return consumer;
651
+ } catch (error) {
652
+ console.debug("[component-picker] Failed to fetch source map:", error);
653
+ failedFetches.add(scriptUrl);
654
+ sourceMapCache.set(scriptUrl, null);
655
+ return null;
656
+ }
657
+ }
658
+ async function findSourceMapUrl(scriptUrl) {
659
+ try {
660
+ const scriptResponse = await fetch(scriptUrl);
661
+ if (scriptResponse.ok) {
662
+ const scriptContent = await scriptResponse.text();
663
+ const match = scriptContent.match(
664
+ /\/\/[#@]\s*sourceMappingURL=([^\s'"]+)/
665
+ );
666
+ if (match?.[1]) {
667
+ const mapUrl = match[1];
668
+ if (mapUrl.startsWith("data:")) {
669
+ return null;
670
+ }
671
+ return new URL(mapUrl, scriptUrl).href;
672
+ }
673
+ }
674
+ const patterns = [
675
+ `${scriptUrl}.map`,
676
+ scriptUrl.replace(/\.js$/, ".js.map"),
677
+ scriptUrl.replace(/\.mjs$/, ".mjs.map")
678
+ ];
679
+ for (const pattern of patterns) {
680
+ try {
681
+ const response = await fetch(pattern, { method: "HEAD" });
682
+ if (response.ok) {
683
+ return pattern;
684
+ }
685
+ } catch {
686
+ }
687
+ }
688
+ return null;
689
+ } catch {
690
+ return null;
691
+ }
692
+ }
693
+ function resolveSourcePath(sourcePath, scriptUrl) {
694
+ if (sourcePath.startsWith("webpack://")) {
695
+ const match = sourcePath.match(/webpack:\/\/(?:[^/]+)?\/\.?\/?(.+)/);
696
+ if (match?.[1]) {
697
+ return match[1];
698
+ }
699
+ }
700
+ if (sourcePath.startsWith("[project]/")) {
701
+ return sourcePath.replace("[project]/", "");
702
+ }
703
+ if (sourcePath.startsWith("./") || sourcePath.startsWith("../")) {
704
+ try {
705
+ const scriptDir = scriptUrl.substring(0, scriptUrl.lastIndexOf("/"));
706
+ const resolved = new URL(sourcePath, scriptDir + "/").pathname;
707
+ return resolved.replace(/^\//, "");
708
+ } catch {
709
+ return sourcePath;
710
+ }
711
+ }
712
+ if (sourcePath.startsWith("/")) {
713
+ return sourcePath.substring(1);
714
+ }
715
+ return sourcePath;
716
+ }
717
+ async function searchSourceMapsForComponent(componentName) {
718
+ const scripts = Array.from(document.querySelectorAll("script[src]")).map((s) => s.src).filter((src) => src && !src.includes("node_modules"));
719
+ const nextScripts = Array.from(
720
+ document.querySelectorAll('script[src*="/_next/"]')
721
+ ).map((s) => s.src);
722
+ const allScripts = [.../* @__PURE__ */ new Set([...scripts, ...nextScripts])];
723
+ for (const scriptUrl of allScripts) {
724
+ const consumer = await fetchSourceMap(scriptUrl);
725
+ if (!consumer) continue;
726
+ const sources = consumer.sources || [];
727
+ for (const source of sources) {
728
+ const fileName = source.split("/").pop() || "";
729
+ const baseName = fileName.replace(/\.(tsx?|jsx?)$/, "");
730
+ if (baseName === componentName || fileName.toLowerCase().includes(componentName.toLowerCase())) {
731
+ try {
732
+ let firstMapping = null;
733
+ consumer.eachMapping((mapping) => {
734
+ if (mapping.source === source && !firstMapping) {
735
+ if (mapping.name === componentName && mapping.originalLine !== null && mapping.originalColumn !== null) {
736
+ firstMapping = {
737
+ line: mapping.originalLine,
738
+ column: mapping.originalColumn
739
+ };
740
+ }
741
+ }
742
+ });
743
+ if (firstMapping !== null) {
744
+ return {
745
+ source: resolveSourcePath(source, scriptUrl),
746
+ line: firstMapping.line,
747
+ column: firstMapping.column
748
+ };
749
+ }
750
+ if (baseName === componentName) {
751
+ return {
752
+ source: resolveSourcePath(source, scriptUrl),
753
+ line: 1,
754
+ column: 0
755
+ };
756
+ }
757
+ } catch {
758
+ }
759
+ }
760
+ }
761
+ }
762
+ return null;
763
+ }
764
+
765
+ // src/utils/stackTraceParser.ts
766
+ function parseStackTrace(error) {
767
+ const stack = error.stack;
768
+ if (!stack) return [];
769
+ const frames = [];
770
+ const lines = stack.split("\n");
771
+ for (const line of lines) {
772
+ const frame = parseStackLine(line);
773
+ if (frame) {
774
+ frames.push(frame);
775
+ }
776
+ }
777
+ return frames;
778
+ }
779
+ function parseStackLine(line) {
780
+ const chromeMatch = line.match(
781
+ /^\s*at\s+(?:async\s+)?(?:(\S+)\s+)?\(?(https?:\/\/[^)]+|file:\/\/[^)]+):(\d+):(\d+)\)?/
782
+ );
783
+ if (chromeMatch) {
784
+ return {
785
+ functionName: chromeMatch[1] || null,
786
+ url: chromeMatch[2],
787
+ lineNumber: parseInt(chromeMatch[3], 10),
788
+ columnNumber: parseInt(chromeMatch[4], 10)
789
+ };
790
+ }
791
+ const firefoxMatch = line.match(
792
+ /^(?:(\S*)@)?(https?:\/\/[^:]+|file:\/\/[^:]+):(\d+):(\d+)/
793
+ );
794
+ if (firefoxMatch) {
795
+ return {
796
+ functionName: firefoxMatch[1] || null,
797
+ url: firefoxMatch[2],
798
+ lineNumber: parseInt(firefoxMatch[3], 10),
799
+ columnNumber: parseInt(firefoxMatch[4], 10)
800
+ };
801
+ }
802
+ return null;
803
+ }
804
+ function filterInternalFrames(frames) {
805
+ const internalPatterns = [
806
+ /node_modules/,
807
+ /react-dom/,
808
+ /react\.production/,
809
+ /react\.development/,
810
+ /scheduler/,
811
+ /\/_next\/static\/chunks\/webpack/,
812
+ /\/__webpack_/,
813
+ /\/turbopack-/,
814
+ // React internal function names
815
+ /^(?:renderWithHooks|mountIndeterminateComponent|beginWork|performUnitOfWork)/,
816
+ /^(?:callCallback|invokeGuardedCallbackDev|invokeGuardedCallback)/,
817
+ /^(?:commitRoot|flushSync|batchedUpdates)/
818
+ ];
819
+ return frames.filter((frame) => {
820
+ for (const pattern of internalPatterns) {
821
+ if (pattern.test(frame.url)) {
822
+ return false;
823
+ }
824
+ }
825
+ if (frame.functionName) {
826
+ for (const pattern of internalPatterns) {
827
+ if (pattern.test(frame.functionName)) {
828
+ return false;
829
+ }
830
+ }
831
+ }
832
+ return true;
833
+ });
834
+ }
835
+
836
+ // src/utils/sourceLocationResolver.ts
837
+ async function resolveSourceLocation(fiber, element) {
838
+ const debugSourceResult = tryDebugSource(fiber);
839
+ if (debugSourceResult.filePath) {
840
+ return debugSourceResult;
841
+ }
842
+ const sourceMapResult = await trySourceMapResolution(fiber);
843
+ if (sourceMapResult?.filePath) {
844
+ return sourceMapResult;
845
+ }
846
+ const stackResult = tryStackTraceParsing();
847
+ if (stackResult?.filePath) {
848
+ return stackResult;
849
+ }
850
+ return {
851
+ filePath: null,
852
+ lineNumber: null,
853
+ columnNumber: null
854
+ };
855
+ }
856
+ function tryDebugSource(fiber) {
857
+ if (fiber) {
858
+ console.log("[component-picker] Fiber keys:", Object.keys(fiber));
859
+ console.log("[component-picker] _debugSource:", fiber._debugSource);
860
+ console.log("[component-picker] _debugInfo:", fiber._debugInfo);
861
+ }
862
+ if (!fiber?._debugSource) {
863
+ return { filePath: null, lineNumber: null, columnNumber: null };
864
+ }
865
+ const { fileName, lineNumber, columnNumber } = fiber._debugSource;
866
+ if (!fileName) {
867
+ return { filePath: null, lineNumber: null, columnNumber: null };
868
+ }
869
+ return {
870
+ filePath: formatFilePath(fileName),
871
+ lineNumber: lineNumber ?? null,
872
+ columnNumber: columnNumber ?? null
873
+ };
874
+ }
875
+ async function trySourceMapResolution(fiber, _element) {
876
+ try {
877
+ const componentName = fiber?.type ? getComponentName(fiber.type) : null;
878
+ if (!componentName) {
879
+ return null;
880
+ }
881
+ const result = await searchSourceMapsForComponent(componentName);
882
+ if (!result) {
883
+ return null;
884
+ }
885
+ return {
886
+ filePath: formatFilePath(result.source),
887
+ lineNumber: result.line,
888
+ columnNumber: result.column
889
+ };
890
+ } catch (error) {
891
+ console.debug("[component-picker] Source map resolution failed:", error);
892
+ return null;
893
+ }
894
+ }
895
+ function tryStackTraceParsing() {
896
+ try {
897
+ const error = new Error();
898
+ const frames = parseStackTrace(error);
899
+ const userFrames = filterInternalFrames(frames);
900
+ if (userFrames.length === 0) {
901
+ return null;
902
+ }
903
+ const frame = userFrames[0];
904
+ const filePath = extractFilePathFromUrl(frame.url);
905
+ if (!filePath) {
906
+ return null;
907
+ }
908
+ return {
909
+ filePath,
910
+ lineNumber: frame.lineNumber,
911
+ columnNumber: frame.columnNumber
912
+ };
913
+ } catch {
914
+ return null;
915
+ }
916
+ }
917
+ function getComponentName(type) {
918
+ if (!type) return null;
919
+ if (typeof type === "function") {
920
+ return type.displayName || type.name || null;
921
+ }
922
+ if (typeof type === "object" && type !== null) {
923
+ const obj = type;
924
+ return obj.displayName || obj.render?.displayName || obj.render?.name || null;
925
+ }
926
+ return null;
927
+ }
928
+ function extractFilePathFromUrl(url) {
929
+ try {
930
+ const urlObj = new URL(url);
931
+ let path = urlObj.pathname;
932
+ path = path.replace(/^\/+/, "");
933
+ path = path.replace(/^_next\/static\/chunks\//, "").replace(/^_next\/static\/[^/]+\/pages\//, "pages/").replace(/^\/@fs\//, "").replace(/^@vite\//, "").replace(/^webpack:\/\/[^/]+\//, "").replace(/^\[project\]\//, "");
934
+ path = path.split("?")[0]?.split("#")[0] || path;
935
+ if (!path.includes("/") && !path.match(/\.(tsx?|jsx?|mjs)$/)) {
936
+ return null;
937
+ }
938
+ return path || null;
939
+ } catch {
940
+ return url;
941
+ }
942
+ }
943
+ function formatFilePath(filePath) {
944
+ if (!filePath) return null;
945
+ let cleaned = filePath.replace(/^webpack:\/\/[^/]+\//, "").replace(/^\.\//g, "").replace(/^\/+/, "").replace(/^\[project\]\//, "");
946
+ cleaned = cleaned.replace(/\\/g, "/");
947
+ return cleaned;
948
+ }
949
+ var DEFAULT_OPTIONS = {
950
+ maxWidth: 800,
951
+ maxHeight: 600,
952
+ backgroundColor: "#ffffff",
953
+ pixelRatio: 1,
954
+ padding: 10
955
+ };
956
+ async function captureScreenshot(element, options = {}) {
957
+ const opts = { ...DEFAULT_OPTIONS, ...options };
958
+ try {
959
+ const rect = element.getBoundingClientRect();
960
+ const width = Math.min(rect.width + opts.padding * 2, opts.maxWidth);
961
+ const height = Math.min(rect.height + opts.padding * 2, opts.maxHeight);
962
+ const dataUrl = await toPng(element, {
963
+ backgroundColor: opts.backgroundColor,
964
+ pixelRatio: opts.pixelRatio,
965
+ width: rect.width,
966
+ height: rect.height,
967
+ style: {
968
+ margin: "0",
969
+ padding: "0"
970
+ },
971
+ filter: (node) => {
972
+ if (node instanceof Element) {
973
+ if (node.hasAttribute("data-component-picker")) {
974
+ return false;
975
+ }
976
+ }
977
+ return true;
978
+ }
979
+ });
980
+ return {
981
+ dataUrl,
982
+ width: Math.round(width),
983
+ height: Math.round(height)
984
+ };
985
+ } catch (error) {
986
+ console.error("[component-picker] Screenshot capture failed:", error);
987
+ return {
988
+ dataUrl: createFallbackScreenshot(element),
989
+ width: 200,
990
+ height: 100
991
+ };
992
+ }
993
+ }
994
+ function createFallbackScreenshot(element) {
995
+ const canvas = document.createElement("canvas");
996
+ canvas.width = 200;
997
+ canvas.height = 100;
998
+ const ctx = canvas.getContext("2d");
999
+ if (ctx) {
1000
+ ctx.fillStyle = "#f0f0f0";
1001
+ ctx.fillRect(0, 0, 200, 100);
1002
+ ctx.fillStyle = "#666";
1003
+ ctx.font = "12px sans-serif";
1004
+ ctx.textAlign = "center";
1005
+ ctx.fillText("Screenshot unavailable", 100, 45);
1006
+ ctx.fillText(element.tagName.toLowerCase(), 100, 65);
1007
+ }
1008
+ return canvas.toDataURL("image/png");
1009
+ }
1010
+
1011
+ // src/utils/componentMetadata.ts
1012
+ function extractDOMInfo(element) {
1013
+ const rect = element.getBoundingClientRect();
1014
+ return {
1015
+ tagName: element.tagName.toLowerCase(),
1016
+ className: element.className || null,
1017
+ boundingRect: {
1018
+ x: rect.x,
1019
+ y: rect.y,
1020
+ width: rect.width,
1021
+ height: rect.height,
1022
+ top: rect.top,
1023
+ right: rect.right,
1024
+ bottom: rect.bottom,
1025
+ left: rect.left
1026
+ }
1027
+ };
1028
+ }
1029
+ async function buildSelectionData(element, fiberData, options = {}) {
1030
+ const fiber = options.fiber ?? {
1031
+ _debugSource: fiberData.debugSource
1032
+ };
1033
+ const source = await resolveSourceLocation(fiber);
1034
+ const screenshot = await captureScreenshot(element, options.screenshotOptions);
1035
+ const selectionData = {
1036
+ id: nanoid(),
1037
+ timestamp: Date.now(),
1038
+ component: fiberData.componentInfo,
1039
+ source: {
1040
+ filePath: formatFilePath(source.filePath),
1041
+ lineNumber: source.lineNumber,
1042
+ columnNumber: source.columnNumber
1043
+ },
1044
+ props: fiberData.props,
1045
+ state: fiberData.state,
1046
+ dom: extractDOMInfo(element),
1047
+ screenshot,
1048
+ context: {
1049
+ pageUrl: window.location.href,
1050
+ parentComponents: fiberData.parentComponents
1051
+ }
1052
+ };
1053
+ return selectionData;
1054
+ }
1055
+ function ComponentPicker(props) {
1056
+ if (process.env.NODE_ENV !== "development") {
1057
+ return props.children;
1058
+ }
1059
+ return /* @__PURE__ */ jsx(ComponentPickerImpl, { ...props });
1060
+ }
1061
+ function ComponentPickerImpl({
1062
+ port = 3333,
1063
+ children,
1064
+ shortcutKey = "C",
1065
+ onConnectionChange,
1066
+ onSelect
1067
+ }) {
1068
+ const { isSelectionMode, selectionMessage, enableSelectionMode, disableSelectionMode } = useSelectionMode();
1069
+ const { getFiberFromElement, extractFiberData, findNearestComponentFiber } = useFiberInspector();
1070
+ const { connected, sendSelection } = useWebSocketClient({
1071
+ port,
1072
+ onSelectionModeChange: (enabled, message) => {
1073
+ if (enabled) {
1074
+ enableSelectionMode(message);
1075
+ } else {
1076
+ disableSelectionMode();
1077
+ }
1078
+ },
1079
+ onConnectionChange
1080
+ });
1081
+ useKeyboardShortcut({
1082
+ key: shortcutKey,
1083
+ onTrigger: () => {
1084
+ if (isSelectionMode) {
1085
+ disableSelectionMode();
1086
+ } else {
1087
+ enableSelectionMode();
1088
+ }
1089
+ },
1090
+ enabled: connected
1091
+ });
1092
+ const handleSelect = useCallback(
1093
+ async (element) => {
1094
+ try {
1095
+ const fiber = getFiberFromElement(element);
1096
+ if (!fiber) {
1097
+ console.warn("[component-picker] No React fiber found for element");
1098
+ disableSelectionMode();
1099
+ return;
1100
+ }
1101
+ const componentFiber = findNearestComponentFiber(fiber);
1102
+ if (!componentFiber) {
1103
+ console.warn("[component-picker] No component fiber found");
1104
+ disableSelectionMode();
1105
+ return;
1106
+ }
1107
+ const fiberData = extractFiberData(componentFiber);
1108
+ const selectionData = await buildSelectionData(element, fiberData, {
1109
+ fiber: componentFiber
1110
+ });
1111
+ sendSelection(selectionData);
1112
+ onSelect?.(selectionData.component.name, selectionData.source.filePath);
1113
+ console.log(
1114
+ `[component-picker] Selected: ${selectionData.component.name}`,
1115
+ selectionData.source.filePath ? `at ${selectionData.source.filePath}:${selectionData.source.lineNumber}` : ""
1116
+ );
1117
+ } catch (error) {
1118
+ console.error("[component-picker] Selection error:", error);
1119
+ } finally {
1120
+ disableSelectionMode();
1121
+ }
1122
+ },
1123
+ [
1124
+ getFiberFromElement,
1125
+ findNearestComponentFiber,
1126
+ extractFiberData,
1127
+ sendSelection,
1128
+ disableSelectionMode,
1129
+ onSelect
1130
+ ]
1131
+ );
1132
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1133
+ children,
1134
+ /* @__PURE__ */ jsx(
1135
+ SelectionOverlay,
1136
+ {
1137
+ enabled: isSelectionMode,
1138
+ message: selectionMessage,
1139
+ onSelect: handleSelect,
1140
+ onCancel: disableSelectionMode
1141
+ }
1142
+ ),
1143
+ process.env.NODE_ENV === "development" && /* @__PURE__ */ jsxs(
1144
+ "button",
1145
+ {
1146
+ "data-component-picker": "status",
1147
+ onClick: () => {
1148
+ if (!connected) return;
1149
+ if (isSelectionMode) {
1150
+ disableSelectionMode();
1151
+ } else {
1152
+ enableSelectionMode();
1153
+ }
1154
+ },
1155
+ style: {
1156
+ position: "fixed",
1157
+ bottom: 16,
1158
+ right: 16,
1159
+ padding: "8px 12px",
1160
+ backgroundColor: !connected ? "#ef4444" : isSelectionMode ? "#f59e0b" : "#22c55e",
1161
+ color: "white",
1162
+ borderRadius: "9999px",
1163
+ fontSize: "12px",
1164
+ fontFamily: "system-ui, sans-serif",
1165
+ fontWeight: 500,
1166
+ zIndex: 999997,
1167
+ opacity: 0.9,
1168
+ display: "flex",
1169
+ alignItems: "center",
1170
+ gap: "6px",
1171
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
1172
+ border: "none",
1173
+ cursor: connected ? "pointer" : "not-allowed",
1174
+ transition: "background-color 0.2s ease"
1175
+ },
1176
+ children: [
1177
+ /* @__PURE__ */ jsx(
1178
+ "span",
1179
+ {
1180
+ style: {
1181
+ width: 8,
1182
+ height: 8,
1183
+ borderRadius: "50%",
1184
+ backgroundColor: "white",
1185
+ animation: !connected ? "pulse 2s infinite" : "none"
1186
+ }
1187
+ }
1188
+ ),
1189
+ !connected ? "Connecting..." : isSelectionMode ? "Click a Component" : "Select Component"
1190
+ ]
1191
+ }
1192
+ )
1193
+ ] });
1194
+ }
1195
+
1196
+ export { ComponentPicker };
1197
+ //# sourceMappingURL=index.js.map
1198
+ //# sourceMappingURL=index.js.map