@promakeai/inspector-hook 1.0.0 → 1.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.
- package/README.md +1499 -0
- package/dist/utils/jsxUpdater.d.ts.map +1 -1
- package/dist/utils/jsxUpdater.js +7 -2
- package/package.json +3 -9
- package/src/index.ts +0 -384
- package/src/utils/jsxUpdater.ts +0 -274
- package/src/vite-plugin.ts +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsxUpdater.d.ts","sourceRoot":"","sources":["../../src/utils/jsxUpdater.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"jsxUpdater.d.ts","sourceRoot":"","sources":["../../src/utils/jsxUpdater.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuKD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CA+DtF"}
|
package/dist/utils/jsxUpdater.js
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
* AST-based utility for updating styles and classNames in JSX/TSX source code
|
|
4
4
|
*/
|
|
5
5
|
import * as parser from '@babel/parser';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import * as traverseModule from '@babel/traverse';
|
|
7
|
+
import * as generateModule from '@babel/generator';
|
|
8
8
|
import * as t from '@babel/types';
|
|
9
|
+
// Browser compatibility: Handle both CommonJS and ESM default exports
|
|
10
|
+
// @ts-ignore - Babel modules have complex type definitions
|
|
11
|
+
const traverse = traverseModule.default || traverseModule;
|
|
12
|
+
// @ts-ignore - Babel modules have complex type definitions
|
|
13
|
+
const generate = generateModule.default || generateModule;
|
|
9
14
|
/**
|
|
10
15
|
* Create AST ObjectExpression from styles object
|
|
11
16
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promakeai/inspector-hook",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "React hook for controlling inspector in parent applications",
|
|
5
5
|
"author": "Promake",
|
|
6
6
|
"type": "module",
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
"./package.json": "./package.json"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
19
|
-
"src"
|
|
18
|
+
"dist"
|
|
20
19
|
],
|
|
21
20
|
"scripts": {
|
|
22
21
|
"build": "tsc --build",
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
}
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
|
-
"@promakeai/inspector-types": "
|
|
47
|
+
"@promakeai/inspector-types": "^1.0.1",
|
|
49
48
|
"@types/babel__traverse": "^7.20.0",
|
|
50
49
|
"@types/babel__generator": "^7.6.0",
|
|
51
50
|
"vitest": "^1.0.0"
|
|
@@ -56,11 +55,6 @@
|
|
|
56
55
|
"@babel/generator": "^7.23.0",
|
|
57
56
|
"@babel/types": "^7.23.0"
|
|
58
57
|
},
|
|
59
|
-
"repository": {
|
|
60
|
-
"type": "git",
|
|
61
|
-
"url": "https://github.com/promakeai/inspector.git",
|
|
62
|
-
"directory": "packages/hook"
|
|
63
|
-
},
|
|
64
58
|
"publishConfig": {
|
|
65
59
|
"access": "public"
|
|
66
60
|
}
|
package/src/index.ts
DELETED
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback, RefObject } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
InspectorCallbacks,
|
|
4
|
-
InspectorLabels,
|
|
5
|
-
InspectorTheme,
|
|
6
|
-
UseInspectorReturn,
|
|
7
|
-
SelectedElementData,
|
|
8
|
-
UrlChangeData,
|
|
9
|
-
PromptSubmittedData,
|
|
10
|
-
TextUpdatedData,
|
|
11
|
-
ImageUpdatedData,
|
|
12
|
-
StyleUpdatedData,
|
|
13
|
-
ErrorData,
|
|
14
|
-
HighlightOptions,
|
|
15
|
-
ElementInfoData,
|
|
16
|
-
} from "@promakeai/inspector-types";
|
|
17
|
-
|
|
18
|
-
type IframeMessage =
|
|
19
|
-
| {
|
|
20
|
-
type: "TOGGLE_INSPECTOR";
|
|
21
|
-
active: boolean;
|
|
22
|
-
labels?: InspectorLabels;
|
|
23
|
-
theme?: InspectorTheme;
|
|
24
|
-
}
|
|
25
|
-
| {
|
|
26
|
-
type: "SHOW_CONTENT_INPUT";
|
|
27
|
-
show: boolean;
|
|
28
|
-
updateImmediately?: boolean;
|
|
29
|
-
}
|
|
30
|
-
| {
|
|
31
|
-
type: "SHOW_IMAGE_INPUT";
|
|
32
|
-
show: boolean;
|
|
33
|
-
updateImmediately?: boolean;
|
|
34
|
-
}
|
|
35
|
-
| {
|
|
36
|
-
type: "SHOW_STYLE_EDITOR";
|
|
37
|
-
show: boolean;
|
|
38
|
-
}
|
|
39
|
-
| {
|
|
40
|
-
type: "SET_BADGE_VISIBLE";
|
|
41
|
-
visible: boolean;
|
|
42
|
-
badgeText?: string;
|
|
43
|
-
}
|
|
44
|
-
| {
|
|
45
|
-
type: "HIGHLIGHT_ELEMENT";
|
|
46
|
-
identifier: string | SelectedElementData;
|
|
47
|
-
options?: HighlightOptions;
|
|
48
|
-
}
|
|
49
|
-
| {
|
|
50
|
-
type: "GET_ELEMENT_BY_ID";
|
|
51
|
-
inspectorId: string;
|
|
52
|
-
}
|
|
53
|
-
| {
|
|
54
|
-
type: "SET_SHOW_CHILD_BORDERS";
|
|
55
|
-
show: boolean;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type InspectorMessage =
|
|
59
|
-
| {
|
|
60
|
-
type: "INSPECTOR_ELEMENT_SELECTED";
|
|
61
|
-
data: SelectedElementData;
|
|
62
|
-
}
|
|
63
|
-
| {
|
|
64
|
-
type: "URL_CHANGED";
|
|
65
|
-
data: UrlChangeData;
|
|
66
|
-
}
|
|
67
|
-
| {
|
|
68
|
-
type: "INSPECTOR_PROMPT_SUBMITTED";
|
|
69
|
-
data: PromptSubmittedData;
|
|
70
|
-
}
|
|
71
|
-
| {
|
|
72
|
-
type: "INSPECTOR_TEXT_UPDATED";
|
|
73
|
-
data: TextUpdatedData;
|
|
74
|
-
}
|
|
75
|
-
| {
|
|
76
|
-
type: "INSPECTOR_IMAGE_UPDATED";
|
|
77
|
-
data: ImageUpdatedData;
|
|
78
|
-
}
|
|
79
|
-
| {
|
|
80
|
-
type: "INSPECTOR_STYLE_UPDATED";
|
|
81
|
-
data: StyleUpdatedData;
|
|
82
|
-
}
|
|
83
|
-
| {
|
|
84
|
-
type: "INSPECTOR_CLOSED";
|
|
85
|
-
}
|
|
86
|
-
| {
|
|
87
|
-
type: "INSPECTOR_ERROR";
|
|
88
|
-
data: ErrorData;
|
|
89
|
-
}
|
|
90
|
-
| {
|
|
91
|
-
type: "ELEMENT_INFO_RESPONSE";
|
|
92
|
-
data: ElementInfoData;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export function useInspector(
|
|
96
|
-
iframeRef: RefObject<HTMLIFrameElement>,
|
|
97
|
-
callbacks?: InspectorCallbacks,
|
|
98
|
-
labels?: InspectorLabels,
|
|
99
|
-
theme?: InspectorTheme
|
|
100
|
-
): UseInspectorReturn {
|
|
101
|
-
const [isInspecting, setIsInspecting] = useState(false);
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Send message to iframe
|
|
105
|
-
*/
|
|
106
|
-
const sendMessage = useCallback(
|
|
107
|
-
(message: IframeMessage) => {
|
|
108
|
-
const iframe = iframeRef.current;
|
|
109
|
-
if (!iframe || !iframe.contentWindow) {
|
|
110
|
-
console.warn("Inspector: iframe not found or not loaded");
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
iframe.contentWindow.postMessage(message, "*");
|
|
116
|
-
} catch (error) {
|
|
117
|
-
console.error("Inspector: Failed to send message:", error);
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
[iframeRef]
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Toggle inspector mode
|
|
125
|
-
*/
|
|
126
|
-
const toggleInspector = useCallback(
|
|
127
|
-
(active?: boolean) => {
|
|
128
|
-
const newState = active !== undefined ? active : !isInspecting;
|
|
129
|
-
setIsInspecting(newState);
|
|
130
|
-
|
|
131
|
-
sendMessage({
|
|
132
|
-
type: "TOGGLE_INSPECTOR",
|
|
133
|
-
active: newState,
|
|
134
|
-
labels: labels,
|
|
135
|
-
theme: theme,
|
|
136
|
-
});
|
|
137
|
-
},
|
|
138
|
-
[isInspecting, sendMessage, labels, theme]
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Start inspecting
|
|
143
|
-
*/
|
|
144
|
-
const startInspecting = useCallback(() => {
|
|
145
|
-
toggleInspector(true);
|
|
146
|
-
}, [toggleInspector]);
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Stop inspecting
|
|
150
|
-
*/
|
|
151
|
-
const stopInspecting = useCallback(() => {
|
|
152
|
-
toggleInspector(false);
|
|
153
|
-
}, [toggleInspector]);
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Show or hide content input
|
|
157
|
-
*/
|
|
158
|
-
const showContentInput = useCallback(
|
|
159
|
-
(show: boolean, updateImmediately?: boolean) => {
|
|
160
|
-
sendMessage({
|
|
161
|
-
type: "SHOW_CONTENT_INPUT",
|
|
162
|
-
show: show,
|
|
163
|
-
updateImmediately: updateImmediately,
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
[sendMessage]
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Show or hide image input
|
|
171
|
-
*/
|
|
172
|
-
const showImageInput = useCallback(
|
|
173
|
-
(show: boolean, updateImmediately?: boolean) => {
|
|
174
|
-
sendMessage({
|
|
175
|
-
type: "SHOW_IMAGE_INPUT",
|
|
176
|
-
show: show,
|
|
177
|
-
updateImmediately: updateImmediately,
|
|
178
|
-
});
|
|
179
|
-
},
|
|
180
|
-
[sendMessage]
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Show or hide style editor
|
|
185
|
-
*/
|
|
186
|
-
const showStyleEditor = useCallback(
|
|
187
|
-
(show: boolean) => {
|
|
188
|
-
sendMessage({
|
|
189
|
-
type: "SHOW_STYLE_EDITOR",
|
|
190
|
-
show: show,
|
|
191
|
-
});
|
|
192
|
-
},
|
|
193
|
-
[sendMessage]
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Show or hide "Built with Promake" badge
|
|
198
|
-
*/
|
|
199
|
-
const setBadgeVisible = useCallback(
|
|
200
|
-
(visible: boolean) => {
|
|
201
|
-
sendMessage({
|
|
202
|
-
type: "SET_BADGE_VISIBLE",
|
|
203
|
-
visible: visible,
|
|
204
|
-
badgeText: labels?.badgeText,
|
|
205
|
-
});
|
|
206
|
-
},
|
|
207
|
-
[sendMessage, labels]
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Highlight an element in the iframe
|
|
212
|
-
*/
|
|
213
|
-
const highlightElement = useCallback(
|
|
214
|
-
(identifier: string | SelectedElementData, options?: HighlightOptions) => {
|
|
215
|
-
sendMessage({
|
|
216
|
-
type: "HIGHLIGHT_ELEMENT",
|
|
217
|
-
identifier: identifier,
|
|
218
|
-
options: options,
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
[sendMessage]
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Request element info by inspector ID
|
|
226
|
-
*/
|
|
227
|
-
const getElementByInspectorId = useCallback(
|
|
228
|
-
(inspectorId: string) => {
|
|
229
|
-
sendMessage({
|
|
230
|
-
type: "GET_ELEMENT_BY_ID",
|
|
231
|
-
inspectorId: inspectorId,
|
|
232
|
-
});
|
|
233
|
-
},
|
|
234
|
-
[sendMessage]
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Toggle child borders visibility
|
|
239
|
-
*/
|
|
240
|
-
const setShowChildBorders = useCallback(
|
|
241
|
-
(show: boolean) => {
|
|
242
|
-
sendMessage({
|
|
243
|
-
type: "SET_SHOW_CHILD_BORDERS",
|
|
244
|
-
show: show,
|
|
245
|
-
});
|
|
246
|
-
},
|
|
247
|
-
[sendMessage]
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Listen for messages from iframe
|
|
252
|
-
*/
|
|
253
|
-
useEffect(() => {
|
|
254
|
-
const handleMessage = (event: MessageEvent<string | InspectorMessage>) => {
|
|
255
|
-
// Parse message if it's a string (JSON stringified)
|
|
256
|
-
let messageData: InspectorMessage;
|
|
257
|
-
|
|
258
|
-
if (typeof event.data === "string") {
|
|
259
|
-
try {
|
|
260
|
-
messageData = JSON.parse(event.data);
|
|
261
|
-
} catch {
|
|
262
|
-
return; // Invalid JSON, ignore
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
messageData = event.data;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Security: Only handle expected message types
|
|
269
|
-
if (!messageData || typeof messageData.type !== "string") {
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
switch (messageData.type) {
|
|
274
|
-
case "INSPECTOR_ELEMENT_SELECTED":
|
|
275
|
-
callbacks?.onElementSelected?.(messageData.data);
|
|
276
|
-
break;
|
|
277
|
-
|
|
278
|
-
case "URL_CHANGED":
|
|
279
|
-
callbacks?.onUrlChange?.(messageData.data);
|
|
280
|
-
break;
|
|
281
|
-
|
|
282
|
-
case "INSPECTOR_PROMPT_SUBMITTED":
|
|
283
|
-
callbacks?.onPromptSubmitted?.(messageData.data);
|
|
284
|
-
break;
|
|
285
|
-
|
|
286
|
-
case "INSPECTOR_TEXT_UPDATED":
|
|
287
|
-
callbacks?.onTextUpdated?.(messageData.data);
|
|
288
|
-
break;
|
|
289
|
-
|
|
290
|
-
case "INSPECTOR_IMAGE_UPDATED":
|
|
291
|
-
callbacks?.onImageUpdated?.(messageData.data);
|
|
292
|
-
break;
|
|
293
|
-
|
|
294
|
-
case "INSPECTOR_STYLE_UPDATED":
|
|
295
|
-
callbacks?.onStyleUpdated?.(messageData.data);
|
|
296
|
-
break;
|
|
297
|
-
|
|
298
|
-
case "INSPECTOR_CLOSED":
|
|
299
|
-
setIsInspecting(false);
|
|
300
|
-
callbacks?.onInspectorClosed?.();
|
|
301
|
-
break;
|
|
302
|
-
|
|
303
|
-
case "INSPECTOR_ERROR":
|
|
304
|
-
callbacks?.onError?.(messageData.data);
|
|
305
|
-
break;
|
|
306
|
-
|
|
307
|
-
case "ELEMENT_INFO_RESPONSE":
|
|
308
|
-
callbacks?.onElementInfoReceived?.(messageData.data);
|
|
309
|
-
break;
|
|
310
|
-
|
|
311
|
-
default:
|
|
312
|
-
// Unknown message type - ignore
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
window.addEventListener("message", handleMessage);
|
|
318
|
-
|
|
319
|
-
return () => {
|
|
320
|
-
window.removeEventListener("message", handleMessage);
|
|
321
|
-
};
|
|
322
|
-
}, [callbacks]);
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Cleanup: Turn off inspector on unmount
|
|
326
|
-
*/
|
|
327
|
-
useEffect(() => {
|
|
328
|
-
return () => {
|
|
329
|
-
if (isInspecting) {
|
|
330
|
-
sendMessage({
|
|
331
|
-
type: "TOGGLE_INSPECTOR",
|
|
332
|
-
active: false,
|
|
333
|
-
labels: labels,
|
|
334
|
-
theme: theme,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
};
|
|
338
|
-
}, [isInspecting, sendMessage]);
|
|
339
|
-
|
|
340
|
-
return {
|
|
341
|
-
isInspecting,
|
|
342
|
-
toggleInspector,
|
|
343
|
-
startInspecting,
|
|
344
|
-
stopInspecting,
|
|
345
|
-
showContentInput,
|
|
346
|
-
showImageInput,
|
|
347
|
-
showStyleEditor,
|
|
348
|
-
setBadgeVisible,
|
|
349
|
-
highlightElement,
|
|
350
|
-
getElementByInspectorId,
|
|
351
|
-
setShowChildBorders,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Re-export types for convenience
|
|
356
|
-
export type {
|
|
357
|
-
ComponentInfo,
|
|
358
|
-
ElementPosition,
|
|
359
|
-
SelectedElementData,
|
|
360
|
-
UrlChangeData,
|
|
361
|
-
PromptSubmittedData,
|
|
362
|
-
TextUpdatedData,
|
|
363
|
-
ImageUpdatedData,
|
|
364
|
-
StyleChanges,
|
|
365
|
-
StyleUpdatedData,
|
|
366
|
-
ErrorData,
|
|
367
|
-
HighlightOptions,
|
|
368
|
-
ElementInfoData,
|
|
369
|
-
InspectorLabels,
|
|
370
|
-
InspectorTheme,
|
|
371
|
-
ContentInputRequestData,
|
|
372
|
-
InspectorCallbacks,
|
|
373
|
-
UseInspectorReturn,
|
|
374
|
-
} from "@promakeai/inspector-types";
|
|
375
|
-
|
|
376
|
-
// Export utility functions
|
|
377
|
-
export { updateJSXSource } from "./utils/jsxUpdater.js";
|
|
378
|
-
export type {
|
|
379
|
-
UpdateJSXSourceOptions,
|
|
380
|
-
UpdateJSXSourceResult,
|
|
381
|
-
} from "./utils/jsxUpdater.js";
|
|
382
|
-
|
|
383
|
-
// Export Vite plugin
|
|
384
|
-
export { inspectorHookPlugin } from "./vite-plugin.js";
|
package/src/utils/jsxUpdater.ts
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSX Source Code Updater
|
|
3
|
-
* AST-based utility for updating styles and classNames in JSX/TSX source code
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as parser from '@babel/parser';
|
|
7
|
-
import traverse from '@babel/traverse';
|
|
8
|
-
import generate from '@babel/generator';
|
|
9
|
-
import * as t from '@babel/types';
|
|
10
|
-
|
|
11
|
-
export interface UpdateJSXSourceOptions {
|
|
12
|
-
sourceCode: string;
|
|
13
|
-
lineNumber: number;
|
|
14
|
-
columnNumber: number;
|
|
15
|
-
tagName: string;
|
|
16
|
-
styles?: Record<string, string>;
|
|
17
|
-
className?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UpdateJSXSourceResult {
|
|
21
|
-
success: boolean;
|
|
22
|
-
code: string;
|
|
23
|
-
message?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Create AST ObjectExpression from styles object
|
|
28
|
-
*/
|
|
29
|
-
function createStyleObjectExpression(styles: Record<string, string>): t.ObjectExpression {
|
|
30
|
-
return t.objectExpression(
|
|
31
|
-
Object.entries(styles).map(([key, value]) =>
|
|
32
|
-
t.objectProperty(
|
|
33
|
-
t.identifier(key),
|
|
34
|
-
t.stringLiteral(value)
|
|
35
|
-
)
|
|
36
|
-
)
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Find JSX element at specific line and column position
|
|
42
|
-
*/
|
|
43
|
-
function findJSXElementAtPosition(
|
|
44
|
-
ast: t.File,
|
|
45
|
-
targetLine: number,
|
|
46
|
-
targetColumn: number
|
|
47
|
-
): any {
|
|
48
|
-
let foundElement: any = null;
|
|
49
|
-
|
|
50
|
-
traverse(ast, {
|
|
51
|
-
JSXOpeningElement(path: any) {
|
|
52
|
-
const { loc } = path.node;
|
|
53
|
-
if (
|
|
54
|
-
loc &&
|
|
55
|
-
loc.start.line === targetLine &&
|
|
56
|
-
loc.start.column === targetColumn
|
|
57
|
-
) {
|
|
58
|
-
foundElement = path;
|
|
59
|
-
path.stop();
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return foundElement;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Merge new styles with existing style prop
|
|
69
|
-
*/
|
|
70
|
-
function mergeStyleProp(
|
|
71
|
-
jsxOpeningElement: any,
|
|
72
|
-
newStyles: Record<string, string>
|
|
73
|
-
): void {
|
|
74
|
-
const attributes = jsxOpeningElement.node.attributes;
|
|
75
|
-
|
|
76
|
-
// Find existing style attribute
|
|
77
|
-
const styleAttrIndex = attributes.findIndex(
|
|
78
|
-
(attr: any) =>
|
|
79
|
-
t.isJSXAttribute(attr) &&
|
|
80
|
-
t.isJSXIdentifier(attr.name) &&
|
|
81
|
-
attr.name.name === 'style'
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const newStyleObjectExpression = createStyleObjectExpression(newStyles);
|
|
85
|
-
|
|
86
|
-
if (styleAttrIndex !== -1) {
|
|
87
|
-
// Update existing style attribute
|
|
88
|
-
const styleAttr = attributes[styleAttrIndex];
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
t.isJSXExpressionContainer(styleAttr.value) &&
|
|
92
|
-
t.isObjectExpression(styleAttr.value.expression)
|
|
93
|
-
) {
|
|
94
|
-
// Merge with existing styles
|
|
95
|
-
const existingProps = styleAttr.value.expression.properties;
|
|
96
|
-
const newProps = newStyleObjectExpression.properties;
|
|
97
|
-
|
|
98
|
-
// Create map of new style keys
|
|
99
|
-
const newStyleKeys = new Set(Object.keys(newStyles));
|
|
100
|
-
|
|
101
|
-
// Filter out existing properties that will be replaced
|
|
102
|
-
const filteredExisting = existingProps.filter(
|
|
103
|
-
(prop: any) =>
|
|
104
|
-
!t.isObjectProperty(prop) ||
|
|
105
|
-
!t.isIdentifier(prop.key) ||
|
|
106
|
-
!newStyleKeys.has(prop.key.name)
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Combine: keep non-updated existing properties + add new properties
|
|
110
|
-
styleAttr.value.expression.properties = [
|
|
111
|
-
...filteredExisting,
|
|
112
|
-
...newProps,
|
|
113
|
-
];
|
|
114
|
-
} else {
|
|
115
|
-
// Replace entirely if existing value is not a simple object
|
|
116
|
-
styleAttr.value = t.jsxExpressionContainer(newStyleObjectExpression);
|
|
117
|
-
}
|
|
118
|
-
} else {
|
|
119
|
-
// Add new style attribute
|
|
120
|
-
const styleAttr = t.jsxAttribute(
|
|
121
|
-
t.jsxIdentifier('style'),
|
|
122
|
-
t.jsxExpressionContainer(newStyleObjectExpression)
|
|
123
|
-
);
|
|
124
|
-
attributes.push(styleAttr);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Merge new className with existing className prop
|
|
130
|
-
*/
|
|
131
|
-
function mergeClassNameProp(
|
|
132
|
-
jsxOpeningElement: any,
|
|
133
|
-
newClassName: string
|
|
134
|
-
): void {
|
|
135
|
-
const attributes = jsxOpeningElement.node.attributes;
|
|
136
|
-
|
|
137
|
-
// Find existing className attribute
|
|
138
|
-
const classNameAttrIndex = attributes.findIndex(
|
|
139
|
-
(attr: any) =>
|
|
140
|
-
t.isJSXAttribute(attr) &&
|
|
141
|
-
t.isJSXIdentifier(attr.name) &&
|
|
142
|
-
attr.name.name === 'className'
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
if (classNameAttrIndex !== -1) {
|
|
146
|
-
// Merge with existing className
|
|
147
|
-
const classNameAttr = attributes[classNameAttrIndex];
|
|
148
|
-
|
|
149
|
-
if (t.isStringLiteral(classNameAttr.value)) {
|
|
150
|
-
// Simple string className
|
|
151
|
-
const existingClasses = classNameAttr.value.value;
|
|
152
|
-
classNameAttr.value = t.stringLiteral(`${existingClasses} ${newClassName}`.trim());
|
|
153
|
-
} else if (
|
|
154
|
-
t.isJSXExpressionContainer(classNameAttr.value) &&
|
|
155
|
-
t.isStringLiteral(classNameAttr.value.expression)
|
|
156
|
-
) {
|
|
157
|
-
// className={""} format
|
|
158
|
-
const existingClasses = classNameAttr.value.expression.value;
|
|
159
|
-
classNameAttr.value.expression = t.stringLiteral(`${existingClasses} ${newClassName}`.trim());
|
|
160
|
-
} else if (
|
|
161
|
-
t.isJSXExpressionContainer(classNameAttr.value) &&
|
|
162
|
-
t.isTemplateLiteral(classNameAttr.value.expression)
|
|
163
|
-
) {
|
|
164
|
-
// Template literal className
|
|
165
|
-
const templateLiteral = classNameAttr.value.expression;
|
|
166
|
-
const lastQuasi = templateLiteral.quasis[templateLiteral.quasis.length - 1];
|
|
167
|
-
lastQuasi.value.raw += ` ${newClassName}`;
|
|
168
|
-
lastQuasi.value.cooked = lastQuasi.value.raw;
|
|
169
|
-
} else {
|
|
170
|
-
// Complex expression - wrap in template literal
|
|
171
|
-
classNameAttr.value = t.jsxExpressionContainer(
|
|
172
|
-
t.templateLiteral(
|
|
173
|
-
[
|
|
174
|
-
t.templateElement({ raw: '', cooked: '' }, false),
|
|
175
|
-
t.templateElement({ raw: ` ${newClassName}`, cooked: ` ${newClassName}` }, true),
|
|
176
|
-
],
|
|
177
|
-
[classNameAttr.value.expression]
|
|
178
|
-
)
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
// Add new className attribute
|
|
183
|
-
const classNameAttr = t.jsxAttribute(
|
|
184
|
-
t.jsxIdentifier('className'),
|
|
185
|
-
t.stringLiteral(newClassName)
|
|
186
|
-
);
|
|
187
|
-
attributes.push(classNameAttr);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Update JSX source code with new styles and/or className
|
|
193
|
-
*
|
|
194
|
-
* @param options - Configuration options for the update
|
|
195
|
-
* @returns Result object with success status, updated code, and optional message
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```typescript
|
|
199
|
-
* const result = updateJSXSource({
|
|
200
|
-
* sourceCode: '<div className="foo">Hello</div>',
|
|
201
|
-
* lineNumber: 1,
|
|
202
|
-
* columnNumber: 0,
|
|
203
|
-
* tagName: 'div',
|
|
204
|
-
* styles: { backgroundColor: 'red !important' },
|
|
205
|
-
* className: 'bar'
|
|
206
|
-
* });
|
|
207
|
-
* // result.code: '<div className="foo bar" style={{ backgroundColor: "red !important" }}>Hello</div>'
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
export function updateJSXSource(options: UpdateJSXSourceOptions): UpdateJSXSourceResult {
|
|
211
|
-
const { sourceCode, lineNumber, columnNumber, tagName, styles, className } = options;
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
// Parse JSX/TSX source code
|
|
215
|
-
const ast = parser.parse(sourceCode, {
|
|
216
|
-
sourceType: 'module',
|
|
217
|
-
plugins: ['jsx', 'typescript', 'decorators-legacy'],
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Find the target JSX element at the specified position
|
|
221
|
-
const elementPath = findJSXElementAtPosition(ast, lineNumber, columnNumber);
|
|
222
|
-
|
|
223
|
-
if (!elementPath) {
|
|
224
|
-
return {
|
|
225
|
-
success: false,
|
|
226
|
-
code: sourceCode,
|
|
227
|
-
message: `Could not find JSX element <${tagName}> at line ${lineNumber}, column ${columnNumber}`,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Verify tag name matches (optional validation)
|
|
232
|
-
const foundTagName = t.isJSXIdentifier(elementPath.node.name)
|
|
233
|
-
? elementPath.node.name.name
|
|
234
|
-
: '';
|
|
235
|
-
|
|
236
|
-
if (foundTagName.toLowerCase() !== tagName.toLowerCase()) {
|
|
237
|
-
return {
|
|
238
|
-
success: false,
|
|
239
|
-
code: sourceCode,
|
|
240
|
-
message: `Tag name mismatch: expected <${tagName}>, found <${foundTagName}>`,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Apply style updates if provided
|
|
245
|
-
if (styles && Object.keys(styles).length > 0) {
|
|
246
|
-
mergeStyleProp(elementPath, styles);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Apply className updates if provided
|
|
250
|
-
if (className && className.trim()) {
|
|
251
|
-
mergeClassNameProp(elementPath, className.trim());
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Generate updated code
|
|
255
|
-
const output = generate(ast, {
|
|
256
|
-
retainLines: true,
|
|
257
|
-
compact: false,
|
|
258
|
-
concise: false,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
success: true,
|
|
263
|
-
code: output.code,
|
|
264
|
-
message: 'JSX source updated successfully',
|
|
265
|
-
};
|
|
266
|
-
} catch (error) {
|
|
267
|
-
return {
|
|
268
|
-
success: false,
|
|
269
|
-
code: sourceCode,
|
|
270
|
-
message: error instanceof Error ? error.message : 'Unknown error occurred during JSX update',
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|