Skip to content

Commit e392ce1

Browse files
committed
Add browser-computed style values to style inspector
Introduces ComputedStyleValues to capture actual pixel values from the browser after CSS calculations (e.g., clamp, calc, rem). Updates the style inspector panel to prioritize these resolved values for display, and extends the orchestrator to fetch and include them in ElementStyleInfo. This improves accuracy and fidelity in the Design tab by showing the true rendered values.
1 parent 6e6b8f0 commit e392ce1

3 files changed

Lines changed: 271 additions & 7 deletions

File tree

src/vs/workbench/contrib/roopik/browser/projectMode/components/styleInspectPanel.ts

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -785,14 +785,107 @@ export class StyleInspectPanel {
785785
// ============================================
786786

787787
/**
788-
* Get computed style value for a property from current data
788+
* Get computed style value for a property from current data.
789+
* For Design tab, we want the RESOLVED computed value (actual pixels),
790+
* not the declared CSS value (like clamp(), calc(), rem, etc.)
791+
*
792+
* Priority order for Design tab (Figma-like):
793+
* 1. Browser computed styles (actual pixel values from CDP getComputedStyleForNode)
794+
* 2. All computed properties (fallback)
795+
* 3. Inline styles
796+
* 4. Matched CSS rules
789797
*/
790798
private getComputedValue(propertyName: string): string {
791799
if (!this.currentData) {
792800
return '';
793801
}
794-
const prop = this.currentData.properties.find(p => p.name === propertyName);
795-
return prop?.value || '';
802+
803+
// Map CSS property names to ComputedStyleValues field names
804+
const computedStylesMap: Record<string, string> = {
805+
'display': 'display',
806+
'position': 'position',
807+
'flex-direction': 'flexDirection',
808+
'justify-content': 'justifyContent',
809+
'align-items': 'alignItems',
810+
'gap': 'gap',
811+
'width': 'width',
812+
'height': 'height',
813+
'min-width': 'minWidth',
814+
'max-width': 'maxWidth',
815+
'min-height': 'minHeight',
816+
'max-height': 'maxHeight',
817+
'margin-top': 'marginTop',
818+
'margin-right': 'marginRight',
819+
'margin-bottom': 'marginBottom',
820+
'margin-left': 'marginLeft',
821+
'padding-top': 'paddingTop',
822+
'padding-right': 'paddingRight',
823+
'padding-bottom': 'paddingBottom',
824+
'padding-left': 'paddingLeft',
825+
'border-width': 'borderWidth',
826+
'border-style': 'borderStyle',
827+
'border-color': 'borderColor',
828+
'border-radius': 'borderRadius',
829+
'font-family': 'fontFamily',
830+
'font-size': 'fontSize',
831+
'font-weight': 'fontWeight',
832+
'line-height': 'lineHeight',
833+
'letter-spacing': 'letterSpacing',
834+
'text-align': 'textAlign',
835+
'color': 'color',
836+
'background-color': 'backgroundColor',
837+
'opacity': 'opacity',
838+
'box-shadow': 'boxShadow',
839+
'overflow': 'overflow',
840+
'transform': 'transform',
841+
'z-index': 'zIndex',
842+
'top': 'top',
843+
'right': 'right',
844+
'bottom': 'bottom',
845+
'left': 'left',
846+
};
847+
848+
// 1. Check browser computed styles FIRST - these are the actual rendered values
849+
// (e.g., "64px" instead of "clamp(2.5rem, 8vw, 4.5rem)")
850+
if (this.currentData.computedStyles) {
851+
const fieldName = computedStylesMap[propertyName];
852+
if (fieldName) {
853+
const value = (this.currentData.computedStyles as Record<string, string | undefined>)[fieldName];
854+
if (value) {
855+
return value;
856+
}
857+
}
858+
}
859+
860+
// 2. Fallback to properties array (also computed values but from a different source)
861+
if (this.currentData.properties && this.currentData.properties.length > 0) {
862+
const prop = this.currentData.properties.find(p => p.name === propertyName);
863+
if (prop && prop.value) {
864+
return prop.value;
865+
}
866+
}
867+
868+
// 3. Fallback to inline styles (declared values)
869+
if (this.currentData.inlineStyles && this.currentData.inlineStyles.length > 0) {
870+
const inlineProp = this.currentData.inlineStyles.find(p => p.name === propertyName);
871+
if (inlineProp) {
872+
return inlineProp.value;
873+
}
874+
}
875+
876+
// 4. Fallback to matched CSS rules
877+
if (this.currentData.matchedRules && this.currentData.matchedRules.length > 0) {
878+
for (const rule of this.currentData.matchedRules) {
879+
if (rule.properties) {
880+
const ruleProp = rule.properties.find(p => p.name === propertyName);
881+
if (ruleProp && !ruleProp.isOverridden) {
882+
return ruleProp.value;
883+
}
884+
}
885+
}
886+
}
887+
888+
return '';
796889
}
797890

798891
/**

src/vs/workbench/contrib/roopik/common/cssResolvers/types.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,76 @@ export interface MatchedCSSProperty {
147147
// Element Style Info (Complete Result)
148148
// ============================================
149149

150+
/**
151+
* Computed styles from browser - actual pixel values after CSS calculations
152+
*/
153+
export interface ComputedStyleValues {
154+
// Layout
155+
display?: string;
156+
position?: string;
157+
flexDirection?: string;
158+
justifyContent?: string;
159+
alignItems?: string;
160+
gap?: string;
161+
162+
// Box Model (all in pixels)
163+
width?: string;
164+
height?: string;
165+
minWidth?: string;
166+
maxWidth?: string;
167+
minHeight?: string;
168+
maxHeight?: string;
169+
170+
// Spacing (individual values)
171+
marginTop?: string;
172+
marginRight?: string;
173+
marginBottom?: string;
174+
marginLeft?: string;
175+
paddingTop?: string;
176+
paddingRight?: string;
177+
paddingBottom?: string;
178+
paddingLeft?: string;
179+
180+
// Border
181+
borderWidth?: string;
182+
borderStyle?: string;
183+
borderColor?: string;
184+
borderRadius?: string;
185+
186+
// Typography
187+
fontFamily?: string;
188+
fontSize?: string;
189+
fontWeight?: string;
190+
lineHeight?: string;
191+
letterSpacing?: string;
192+
textAlign?: string;
193+
194+
// Colors
195+
color?: string;
196+
backgroundColor?: string;
197+
198+
// Effects
199+
opacity?: string;
200+
boxShadow?: string;
201+
overflow?: string;
202+
transform?: string;
203+
zIndex?: string;
204+
205+
// Position values
206+
top?: string;
207+
right?: string;
208+
bottom?: string;
209+
left?: string;
210+
211+
// Bounding box (from getBoundingClientRect)
212+
boundingBox?: {
213+
x: number;
214+
y: number;
215+
width: number;
216+
height: number;
217+
};
218+
}
219+
150220
/**
151221
* Complete style information for an inspected element
152222
*/
@@ -192,6 +262,12 @@ export interface ElementStyleInfo {
192262
* Inherited styles from parent elements
193263
*/
194264
inheritedStyles?: InheritedStyleInfo[];
265+
266+
/**
267+
* Actual computed pixel values from the browser
268+
* Used by the Design tab to show resolved values (e.g., 16px instead of clamp(...))
269+
*/
270+
computedStyles?: ComputedStyleValues;
195271
}
196272

197273
/**

src/vs/workbench/contrib/roopik/electron-main/projectMode/cssResolvers/styleSourceOrchestrator.ts

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import type {
1616
InheritedStyleInfo,
1717
CDPMatchedStylesResponse,
1818
CDPCSSRule,
19-
CDPCSSProperty
19+
CDPCSSProperty,
20+
ComputedStyleValues
2021
} from '../../../common/cssResolvers/types.js';
2122
import type { CDPCssService } from './cdpCssService.js';
2223
import { SourceMapResolver } from './sourceMapResolver.js';
@@ -167,13 +168,16 @@ export class StyleSourceOrchestrator {
167168
inheritedStyles
168169
);
169170

170-
// 8. Detect CSS-in-JS
171+
// 8. Get actual browser-computed styles (resolves clamp(), calc(), rem, etc.)
172+
const computedStyles = await this.getComputedStylesFromCDP(browserViewId, nodeId);
173+
174+
// 9. Detect CSS-in-JS
171175
const cssInJsResult = this.cssInJsDetector.detectFromClassName(
172176
classes.join(' '),
173177
htmlSource?.file
174178
);
175179

176-
// 9. Build final result
180+
// 10. Build final result
177181
const result: ElementStyleInfo = {
178182
tagName: nodeAttrs.tagName,
179183
id: nodeAttrs.id,
@@ -184,7 +188,8 @@ export class StyleSourceOrchestrator {
184188
matchedRules,
185189
inlineStyles,
186190
cssInJs: cssInJsResult.detected ? cssInJsResult : undefined,
187-
inheritedStyles: inheritedStyles.length > 0 ? inheritedStyles : undefined
191+
inheritedStyles: inheritedStyles.length > 0 ? inheritedStyles : undefined,
192+
computedStyles
188193
};
189194

190195
const duration = Date.now() - startTime;
@@ -219,6 +224,96 @@ export class StyleSourceOrchestrator {
219224
// Processing Methods
220225
// ============================================
221226

227+
/**
228+
* Get actual computed pixel values from CDP.
229+
* These are the final resolved values after CSS calculations (clamp, calc, rem, etc.)
230+
*/
231+
private async getComputedStylesFromCDP(
232+
browserViewId: number,
233+
nodeId: number
234+
): Promise<ComputedStyleValues | undefined> {
235+
try {
236+
const computedStyle = await this.cdpService.getComputedStyle(browserViewId, nodeId);
237+
if (!computedStyle || computedStyle.length === 0) {
238+
return undefined;
239+
}
240+
241+
// Convert CDP array to our ComputedStyleValues object
242+
const styleMap = new Map<string, string>();
243+
for (const { name, value } of computedStyle) {
244+
styleMap.set(name, value);
245+
}
246+
247+
// Helper to get a value
248+
const get = (prop: string) => styleMap.get(prop);
249+
250+
// Build ComputedStyleValues object with all the properties we need
251+
const result: ComputedStyleValues = {
252+
// Layout
253+
display: get('display'),
254+
position: get('position'),
255+
flexDirection: get('flex-direction'),
256+
justifyContent: get('justify-content'),
257+
alignItems: get('align-items'),
258+
gap: get('gap'),
259+
260+
// Box Model
261+
width: get('width'),
262+
height: get('height'),
263+
minWidth: get('min-width'),
264+
maxWidth: get('max-width'),
265+
minHeight: get('min-height'),
266+
maxHeight: get('max-height'),
267+
268+
// Spacing
269+
marginTop: get('margin-top'),
270+
marginRight: get('margin-right'),
271+
marginBottom: get('margin-bottom'),
272+
marginLeft: get('margin-left'),
273+
paddingTop: get('padding-top'),
274+
paddingRight: get('padding-right'),
275+
paddingBottom: get('padding-bottom'),
276+
paddingLeft: get('padding-left'),
277+
278+
// Border
279+
borderWidth: get('border-width'),
280+
borderStyle: get('border-style'),
281+
borderColor: get('border-color'),
282+
borderRadius: get('border-radius'),
283+
284+
// Typography
285+
fontFamily: get('font-family'),
286+
fontSize: get('font-size'),
287+
fontWeight: get('font-weight'),
288+
lineHeight: get('line-height'),
289+
letterSpacing: get('letter-spacing'),
290+
textAlign: get('text-align'),
291+
292+
// Colors
293+
color: get('color'),
294+
backgroundColor: get('background-color'),
295+
296+
// Effects
297+
opacity: get('opacity'),
298+
boxShadow: get('box-shadow'),
299+
overflow: get('overflow'),
300+
transform: get('transform'),
301+
zIndex: get('z-index'),
302+
303+
// Position values
304+
top: get('top'),
305+
right: get('right'),
306+
bottom: get('bottom'),
307+
left: get('left'),
308+
};
309+
310+
return result;
311+
} catch (error) {
312+
console.error('[StyleSourceOrchestrator] Failed to get computed styles:', error);
313+
return undefined;
314+
}
315+
}
316+
222317
/**
223318
* Process CDP matched rules into our format
224319
*

0 commit comments

Comments
 (0)