22/* eslint-disable no-underscore-dangle */
33
44import {
5- ComponentInfo , CSSProperties , ElementPosition , TargetedElement ,
5+ ComponentInfo ,
6+ CSSDetailLevel ,
7+ CSSProperties ,
8+ DEFAULT_CSS_LEVEL ,
9+ DEFAULT_TEXT_DETAIL ,
10+ ElementPosition ,
11+ TargetedElement ,
12+ TextDetailLevel ,
13+ TextSnapshots ,
614} from '@mcp-pointer/shared/types' ;
15+ import { CSS_LEVEL_FIELD_MAP } from '@mcp-pointer/shared/detail' ;
716import logger from './logger' ;
817
918export interface ReactSourceInfo {
@@ -12,6 +21,105 @@ export interface ReactSourceInfo {
1221 columnNumber ?: number ;
1322}
1423
24+ export interface ElementSerializationOptions {
25+ textDetail ?: TextDetailLevel ;
26+ cssLevel ?: CSSDetailLevel ;
27+ }
28+
29+ function toKebabCase ( property : string ) : string {
30+ return property
31+ . replace ( / ( [ a - z 0 - 9 ] ) ( [ A - Z ] ) / g, '$1-$2' )
32+ . replace ( / _ / g, '-' )
33+ . toLowerCase ( ) ;
34+ }
35+
36+ function toCamelCase ( property : string ) : string {
37+ return property
38+ . replace ( / ^ - + / , '' )
39+ . replace ( / - ( [ a - z ] ) / g, ( _ , char : string ) => char . toUpperCase ( ) ) ;
40+ }
41+
42+ function getStyleValue ( style : CSSStyleDeclaration , property : string ) : string | undefined {
43+ const camelValue = ( style as any ) [ property ] ;
44+ if ( typeof camelValue === 'string' && camelValue . trim ( ) . length > 0 ) {
45+ return camelValue ;
46+ }
47+
48+ const kebab = toKebabCase ( property ) ;
49+ const value = style . getPropertyValue ( kebab ) ;
50+ if ( typeof value === 'string' && value . trim ( ) . length > 0 ) {
51+ return value ;
52+ }
53+
54+ return undefined ;
55+ }
56+
57+ function extractFullCSSProperties ( style : CSSStyleDeclaration ) : Record < string , string > {
58+ const properties : Record < string , string > = { } ;
59+
60+ for ( let i = 0 ; i < style . length ; i += 1 ) {
61+ const property = style . item ( i ) ;
62+
63+ if ( property && ! property . startsWith ( '-' ) ) {
64+ const value = style . getPropertyValue ( property ) ;
65+ if ( typeof value === 'string' && value . trim ( ) . length > 0 ) {
66+ const camel = toCamelCase ( property ) ;
67+ properties [ camel ] = value ;
68+ }
69+ }
70+ }
71+
72+ return properties ;
73+ }
74+
75+ function getElementCSSProperties (
76+ style : CSSStyleDeclaration ,
77+ cssLevel : CSSDetailLevel ,
78+ fullCSS : Record < string , string > ,
79+ ) : CSSProperties | undefined {
80+ if ( cssLevel === 0 ) {
81+ return undefined ;
82+ }
83+
84+ if ( cssLevel === 3 ) {
85+ return fullCSS ;
86+ }
87+
88+ const fields = CSS_LEVEL_FIELD_MAP [ cssLevel ] ;
89+ const properties : CSSProperties = { } ;
90+
91+ fields . forEach ( ( property ) => {
92+ const value = getStyleValue ( style , property ) ;
93+ if ( value !== undefined ) {
94+ properties [ property ] = value ;
95+ }
96+ } ) ;
97+
98+ return properties ;
99+ }
100+
101+ function collectTextVariants ( element : HTMLElement ) : TextSnapshots {
102+ const visible = element . innerText || '' ;
103+ const full = element . textContent || visible ;
104+
105+ return {
106+ visible,
107+ full,
108+ } ;
109+ }
110+
111+ function resolveTextByDetail ( variants : TextSnapshots , detail : TextDetailLevel ) : string | undefined {
112+ if ( detail === 'none' ) {
113+ return undefined ;
114+ }
115+
116+ if ( detail === 'visible' ) {
117+ return variants . visible ;
118+ }
119+
120+ return variants . full || variants . visible ;
121+ }
122+
15123/**
16124 * Get source file information from a DOM element's React component
17125 */
@@ -172,20 +280,6 @@ export function getElementPosition(element: HTMLElement): ElementPosition {
172280 } ;
173281}
174282
175- /**
176- * Extract relevant CSS properties from an element
177- */
178- export function getElementCSSProperties ( element : HTMLElement ) : CSSProperties {
179- const computedStyle = window . getComputedStyle ( element ) ;
180- return {
181- display : computedStyle . display ,
182- position : computedStyle . position ,
183- fontSize : computedStyle . fontSize ,
184- color : computedStyle . color ,
185- backgroundColor : computedStyle . backgroundColor ,
186- } ;
187- }
188-
189283/**
190284 * Extract CSS classes from an element as an array
191285 */
@@ -197,18 +291,45 @@ export function getElementClasses(element: HTMLElement): string[] {
197291 return classNameStr . split ( ' ' ) . filter ( ( c : string ) => c . trim ( ) ) ;
198292}
199293
200- export function adaptTargetToElement ( element : HTMLElement ) : TargetedElement {
201- return {
294+ export function adaptTargetToElement (
295+ element : HTMLElement ,
296+ options : ElementSerializationOptions = { } ,
297+ ) : TargetedElement {
298+ const textDetail = options . textDetail ?? DEFAULT_TEXT_DETAIL ;
299+ const cssLevel = options . cssLevel ?? DEFAULT_CSS_LEVEL ;
300+
301+ const textVariants = collectTextVariants ( element ) ;
302+ const resolvedText = resolveTextByDetail ( textVariants , textDetail ) ;
303+
304+ const computedStyle = window . getComputedStyle ( element ) ;
305+ const fullCSS = extractFullCSSProperties ( computedStyle ) ;
306+ const cssProperties = getElementCSSProperties ( computedStyle , cssLevel , fullCSS ) ;
307+
308+ const target : TargetedElement = {
202309 selector : generateSelector ( element ) ,
203310 tagName : element . tagName ,
204311 id : element . id || undefined ,
205312 classes : getElementClasses ( element ) ,
206- innerText : element . innerText || element . textContent || '' ,
207313 attributes : getElementAttributes ( element ) ,
208314 position : getElementPosition ( element ) ,
209- cssProperties : getElementCSSProperties ( element ) ,
315+ cssLevel,
316+ cssProperties,
317+ cssComputed : Object . keys ( fullCSS ) . length > 0 ? fullCSS : undefined ,
210318 componentInfo : getReactFiberInfo ( element ) ,
211319 timestamp : Date . now ( ) ,
212320 url : window . location . href ,
321+ textDetail,
322+ textVariants,
323+ textContent : textVariants . full ,
213324 } ;
325+
326+ if ( resolvedText !== undefined ) {
327+ target . innerText = resolvedText ;
328+ }
329+
330+ if ( ! target . textContent && textVariants . visible ) {
331+ target . textContent = textVariants . visible ;
332+ }
333+
334+ return target ;
214335}
0 commit comments