src/vs/platform/browserView/common/cssHelpers.ts

616 LOC · 597 covered · 19 uncovered · 81 ranges · 42 concepts · 31 introducers · 29 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filecssHelpers.test|title=collapseToShorthands realistic element with multiple shorthand groups|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×5 · 22 introduced LOCcssHelpers.ts ×5cssHelpers.test|title=collapseToShorthands transition: single property with cubic-bezier|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands transition: multi-property comma-separated|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 7 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×2 · 2 introduced LOCcssHelpers.ts ×2cssHelpers.test|title=collapseToShorthands animation: name and duration only|occurrence=1, cssHelpers.test|title=collapseToShorthands animation: with fill-mode and custom easing|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 3 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 3 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×2 · 10 introduced LOCcssHelpers.ts ×2cssHelpers.ts ×6 · 36 introduced LOCcssHelpers.ts ×6cssHelpers.test|title=collapseToShorthands border: uniform sides → single shorthand|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 3 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×5 · 8 introduced LOCcssHelpers.ts ×5cssHelpers.test|title=collapseToShorthands border-radius: uniform|occurrence=1, cssHelpers.test|title=collapseToShorthands margin: all sides equal → 1-value|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 6 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 1 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×4 · 9 introduced LOCcssHelpers.ts ×4horizontal → 2-value|occurrence=1 · 0 introduced LOChorizontal → 2-value|occ…cssHelpers.test|title=collapseToShorthands animation-range at defaults → dropped|occurrence=1, cssHelpers.test|title=collapseToShorthands border-image at defaults → dropped entirely|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×3 · 4 introduced LOCcssHelpers.ts ×3cssHelpers.test|title=collapseToShorthands background: color-only when others at default|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands unknown properties pass through alphabetically|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands white-space: nowrap|occurrence=1, cssHelpers.test|title=collapseToShorthands white-space: pre-wrap|occurrence=1 · 0 introduced LOCcssHelpers.test|title=co…cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 4 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×2 · 11 introduced LOCcssHelpers.ts ×2cssHelpers.ts ×1 · 3 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 5 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 6 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 6 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×1 · 2 introduced LOCcssHelpers.ts ×1cssHelpers.ts ×12 · 75 introduced LOCcssHelpers.ts ×12cssHelpers.ts ×12 · 47 introduced LOCcssHelpers.ts ×12cssHelpers.ts ×8 · 308 introduced LOCcssHelpers.ts ×8cssHelpers.test|title=collapseToShorthands animation-range at defaults → dropped|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands animation-range at defaults → dropped|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands animation: name and duration only|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands animation: name and duration only|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands animation: with fill-mode and custom easing|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands animation: with fill-mode and custom easing|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands background: color-only when others at default|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands background: color-only when others at default|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands border-image at defaults → dropped entirely|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands border-image at defaults → dropped entirely|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands border-radius: uniform|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands border-radius: uniform|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands border: non-uniform → per-group shorthands|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands border: non-uniform → per-group shorthands|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands border: uniform sides → single shorthand|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands border: uniform sides → single shorthand|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands margin: 3-value when left === right|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands margin: 3-value when left === right|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands margin: 4-value when all differ|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands margin: 4-value when all differ|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands margin: all sides equal → 1-value|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands margin: all sides equal → 1-value|occurrence=1cssHelpers.test|title=co…horizontal → 2-value|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands padding: vertical/horizontal → 2-value|occurrence=1horizontal → 2-value|occ…cssHelpers.test|title=collapseToShorthands realistic element with multiple shorthand groups|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands realistic element with multiple shorthand groups|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands text-decoration: none|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands text-decoration: none|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands text-decoration: underline with non-default style|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands text-decoration: underline with non-default style|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands transition: multi-property comma-separated|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands transition: multi-property comma-separated|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands transition: single property with cubic-bezier|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands transition: single property with cubic-bezier|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands unknown properties pass through alphabetically|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands unknown properties pass through alphabetically|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands white-space: nowrap|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands white-space: nowrap|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=collapseToShorthands white-space: pre-wrap|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=collapseToShorthands white-space: pre-wrap|occurrence=1cssHelpers.test|title=co…cssHelpers.test|title=formatAuthorStyles collects var references from rules|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles collects var references from rules|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles filters inherited rules to inheritable properties only|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles filters inherited rules to inheritable properties only|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles includes direct author rules and skips user-agent|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles includes direct author rules and skips user-agent|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles includes pseudo-element styles|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles includes pseudo-element styles|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles skips user-agent pseudo-element rules|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles skips user-agent pseudo-element rules|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles tracks author property names from cssProperties longhands|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles tracks author property names from cssProperties longhands|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from direct rules|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from direct rules|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from inherited rules (inheritable only)|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from inherited rules (inheritable only)|occurrence=1cssHelpers.test|title=fo…cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from pseudo-element rules|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/browserView/test/common/cssHelpers.test|title=formatAuthorStyles tracks user-agent property names from pseudo-element rules|occurrence=1cssHelpers.test|title=fo…Focused file · src/vs/platform/browserView/common/cssHelpers.ts · 616 LOCcommon/cssHelpers.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- cssHelpers.ts ×8
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > // -- CDP matched-styles types (subset used by formatAuthorStyles) --
7 >
8 > export interface ICSSStyle {
9 > cssText?: string;
10 > cssProperties: Array<{ name: string; value: string; disabled?: boolean }>;
11 > }
12 >
13 > interface ISelectorList {
14 > selectors: Array<{ text: string }>;
15 > }
16 >
17 > interface ICSSRule {
18 > selectorList: ISelectorList;
19 > origin: string;
20 > style: ICSSStyle;
21 > }
22 >
23 > interface IRuleMatch {
24 > rule: ICSSRule;
25 > }
26 >
27 > interface IInheritedStyleEntry {
28 > inlineStyle?: ICSSStyle;
29 > matchedCSSRules: IRuleMatch[];
30 > }
31 >
32 > interface IPseudoElementMatches {
33 > pseudoType: string;
34 > matches: IRuleMatch[];
35 > }
36 >
37 > export interface IMatchedStyles {
38 > inlineStyle?: ICSSStyle;
39 > matchedCSSRules?: IRuleMatch[];
40 > inherited?: IInheritedStyleEntry[];
41 > pseudoElements?: IPseudoElementMatches[];
42 > }
43 >
44 > export interface IFormattedStyles {
45 > /** Compact CSS text for the agent prompt (rules only, without resolved values). */
46 > rulesText: string;
47 > /** Set of CSS variable names referenced by the element's rules. */
48 > referencedVars: Set<string>;
49 > /** Set of CSS property names that were explicitly set by author rules. */
50 > authorPropertyNames: Set<string>;
51 > /** Set of CSS property names that were set by user-agent rules. */
52 > userAgentPropertyNames: Set<string>;
53 > }
54 >
55 > // -- Constants --
56 >
57 > /**
58 > * CSS properties that are inherited by child elements.
59 > */
60 > const inheritableCSSProperties = new Set([
61 > 'color', 'cursor', 'direction', 'font', 'font-family', 'font-feature-settings',
62 > 'font-kerning', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style',
63 > 'font-variant', 'font-weight', 'letter-spacing', 'line-height', 'list-style',
64 > 'list-style-image', 'list-style-position', 'list-style-type', 'orphans',
65 > 'overflow-wrap', 'quotes', 'tab-size', 'text-align', 'text-align-last',
66 > 'text-indent', 'text-transform', 'visibility', 'white-space', 'widows',
67 > 'word-break', 'word-spacing', 'writing-mode',
68 > ]);
69 >
70 > const varReferenceRegex = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
71 >
72 > /**
73 > * Key computed properties included for hover display in the UI.
74 > */
75 > export const keyComputedProperties = new Set([
76 > 'display', 'position', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
77 > 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
78 > 'font-size', 'font-family', 'color', 'background-color',
79 > ]);
80 >
81 > /**
82 > * Properties always included in resolved values even if only set by user-agent rules,
83 > * matching Chrome DevTools' `alwaysShownComputedProperties`.
84 > */
85 > const alwaysResolvedProperties = new Set(['display', 'height', 'width']);
86 >
87 > // -- Helper functions --
88 >
89 > /**
90 > * Collects var(--name) references from a CSS value string.
91 > */
92 > function collectVarReferences(value: string, into: Set<string>): void { cssHelpers.ts ×12
93 > for (const m of value.matchAll(varReferenceRegex)) {
94 > into.add(m[1]); cssHelpers.ts ×1
95 > }
98 > /**
99 > * Collects longhand property names from the `cssProperties` array of a matched rule.
100 > * Skips variable definitions and disabled properties.
101 > */
102 > function collectPropertyNames(cssProperties: Array<{ name: string; value: string; disabled?: boolean }>, into: Set<string>, inheritableOnly?: boolean): void { cssHelpers.ts ×12
103 > for (const prop of cssProperties) {
104 > if (!prop.name || !prop.value || prop.disabled || prop.name.startsWith('--')) {
105 continue;
106 }
107 > if (inheritableOnly && !inheritableCSSProperties.has(prop.name)) { cssHelpers.ts ×12
108 > continue; cssHelpers.ts ×5
109 > }
110 > into.add(prop.name); cssHelpers.ts ×12
111 > }
112 > }
114 > /**
115 > * Filters CSS declarations to only inheritable properties (not variable definitions).
116 > */
117 > export function filterInheritableDeclarations(cssText: string): string | undefined {
118 > const declarations = cssText.split(';').map(d => d.trim()).filter(Boolean); cssHelpers.ts ×5
119 > const filtered = declarations.filter(decl => {
120 > const colonIdx = decl.indexOf(':');
121 > if (colonIdx === -1) {
122 return false;
123 }
124 > const propName = decl.substring(0, colonIdx).trim(); cssHelpers.ts ×5
125 > return inheritableCSSProperties.has(propName);
126 > });
127 > return filtered.length > 0 ? filtered.join('; ') : undefined;
128 > }
130 > /**
131 > * Formats matched styles into a compact representation for agent prompts.
132 > *
133 > * Only includes author-origin rules (not browser defaults), uses the raw
134 > * `cssText` instead of expanded longhand properties, and for inherited
135 > * rules only keeps inheritable CSS properties.
136 > *
137 > * Also includes pseudo-element styles (::before, ::after, etc.) when present.
138 > *
139 > * Uses `cssProperties` (the longhand array) from matched rules to determine
140 > * which computed properties are author-affected, matching Chrome DevTools'
141 > * `computePropertyTraces` approach.
142 > */
143 > export function formatMatchedStyles(matched: IMatchedStyles): IFormattedStyles {
144 > const referencedVars = new Set<string>(); cssHelpers.ts ×12
145 > const authorPropertyNames = new Set<string>();
146 > const userAgentPropertyNames = new Set<string>();
147 > const seenCssTexts = new Set<string>();
148 > const lines: string[] = [];
149 >
150 > // Inline styles on the element itself
151 > if (matched.inlineStyle?.cssText?.trim()) {
152 const cssText = matched.inlineStyle.cssText.trim();
153 collectVarReferences(cssText, referencedVars);
154 collectPropertyNames(matched.inlineStyle.cssProperties, authorPropertyNames);
155 lines.push(`element { ${cssText} }`);
156 }
158 > // Direct author rules: use cssText for display, cssProperties for property tracking
159 > for (const ruleEntry of matched.matchedCSSRules ?? []) {
160 > if (ruleEntry.rule.origin === 'user-agent') {
161 > collectPropertyNames(ruleEntry.rule.style.cssProperties, userAgentPropertyNames); cssHelpers.ts ×1
162 > continue;
163 > }
164 > const cssText = ruleEntry.rule.style.cssText?.trim(); cssHelpers.ts ×12
165 > if (!cssText || seenCssTexts.has(cssText)) {
166 continue;
167 }
168 > seenCssTexts.add(cssText); cssHelpers.ts ×12
169 > collectVarReferences(cssText, referencedVars);
170 > collectPropertyNames(ruleEntry.rule.style.cssProperties, authorPropertyNames);
171 > const selectors = ruleEntry.rule.selectorList.selectors.map(s => s.text).join(', ');
172 > lines.push(`${selectors} { ${cssText} }`);
173 > }
174 >
175 > // Pseudo-element styles (::before, ::after, etc.)
176 > if (matched.pseudoElements?.length) {
177 > const pseudoLines: string[] = []; cssHelpers.ts ×4
178 > for (const pseudo of matched.pseudoElements) {
179 > for (const ruleEntry of pseudo.matches ?? []) {
180 > if (ruleEntry.rule.origin === 'user-agent') {
181 > collectPropertyNames(ruleEntry.rule.style.cssProperties, userAgentPropertyNames); cssHelpers.ts ×1
182 > continue;
183 > }
184 > const cssText = ruleEntry.rule.style.cssText?.trim(); cssHelpers.ts ×4
185 > if (!cssText || seenCssTexts.has(cssText)) {
186 continue;
187 }
188 > seenCssTexts.add(cssText); cssHelpers.ts ×2
189 > collectVarReferences(cssText, referencedVars);
190 > collectPropertyNames(ruleEntry.rule.style.cssProperties, authorPropertyNames);
191 > const selectors = ruleEntry.rule.selectorList.selectors.map(s => s.text).join(', ');
192 > pseudoLines.push(`${selectors} { ${cssText} }`);
193 > }
195 > if (pseudoLines.length > 0) {
196 > lines.push(''); cssHelpers.ts ×2
197 > lines.push('/* Pseudo-elements */');
198 > lines.push(...pseudoLines);
199 > }
202 > // Inherited author rules — only inheritable properties
203 > const inheritedLines: string[] = [];
204 > for (const entry of matched.inherited ?? []) {
205 > for (const ruleEntry of entry.matchedCSSRules ?? []) { cssHelpers.ts ×5
206 > if (ruleEntry.rule.origin === 'user-agent') {
207 > collectPropertyNames(ruleEntry.rule.style.cssProperties, userAgentPropertyNames, true); cssHelpers.ts ×1
208 > continue;
209 > }
210 > const cssText = ruleEntry.rule.style.cssText?.trim(); cssHelpers.ts ×5
211 > if (!cssText) {
212 continue;
213 }
214 > // Display: keep only inheritable properties from cssText cssHelpers.ts ×5
215 > const filtered = filterInheritableDeclarations(cssText);
216 > if (!filtered || seenCssTexts.has(filtered)) { cssHelpers.ts ×5
217 continue;
218 }
219 > seenCssTexts.add(filtered); cssHelpers.ts ×5
220 > // Track: use cssProperties longhands, inheritable only
221 > collectVarReferences(filtered, referencedVars);
222 > collectPropertyNames(ruleEntry.rule.style.cssProperties, authorPropertyNames, true);
223 > const selectors = ruleEntry.rule.selectorList.selectors.map(s => s.text).join(', ');
224 > inheritedLines.push(`${selectors} { ${filtered} }`);
225 > }
228 > if (inheritedLines.length > 0) {
229 > lines.push(''); cssHelpers.ts ×5
230 > lines.push('/* Inherited */');
231 > lines.push(...inheritedLines);
232 > }
234 > // Always include DevTools' alwaysShownComputedProperties
235 > for (const prop of alwaysResolvedProperties) {
236 > authorPropertyNames.add(prop);
237 > }
238 >
239 > return { rulesText: lines.join('\n'), referencedVars, authorPropertyNames, userAgentPropertyNames };
240 > }
242 > /**
243 > * -- Shorthand collapsing configuration ----------------------------------
244 > *
245 > * Each constant below describes one kind of CSS shorthand that can be
246 > * reconstituted from computed longhand values. The `collapseToShorthands`
247 > * function walks these lists in declaration order and produces compact
248 > * output for the agent prompt.
249 > *
250 > * Sources:
251 > * • MDN "Formal definition → Initial value" tables
252 > * • CSS Backgrounds & Borders 3, CSS Transitions 1, CSS Animations 1,
253 > * CSS Text Decoration 4, CSS Text 4
254 > */
255 >
256 > // -- Box model (T R B L) shorthands --
257 > // Collapsed with 1-4-value syntax per CSS spec section 8.3.
258 >
259 > interface IBoxShorthand {
260 > shorthand: string;
261 > sides: [string, string, string, string]; // top/TL, right/TR, bottom/BR, left/BL
262 > }
263 >
264 > const boxShorthands: IBoxShorthand[] = [
265 > // margin: <margin-top> <margin-right> <margin-bottom> <margin-left>
266 > { shorthand: 'margin', sides: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'] },
267 > // padding: <padding-top> <padding-right> <padding-bottom> <padding-left>
268 > { shorthand: 'padding', sides: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'] },
269 > // border-radius: <TL> <TR> <BR> <BL> (clockwise from top-left)
270 > { shorthand: 'border-radius', sides: ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'] },
271 > ];
272 >
273 > // -- Border per-side groups (collapse to border: W S C when uniform) --
274 >
275 > const borderSideGroups: IBoxShorthand[] = [
276 > // border-width: initial medium per MDN (but computed is always an absolute length)
277 > { shorthand: 'border-width', sides: ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'] },
278 > // border-style: initial none per MDN
279 > { shorthand: 'border-style', sides: ['border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'] },
280 > // border-color: initial currentcolor per MDN
281 > { shorthand: 'border-color', sides: ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'] },
282 > ];
283 >
284 > // -- Longhands that are dropped entirely when all at their initial values --
285 >
286 > interface IDefaultsGroup {
287 > /** Longhands to check and remove. */
288 > longhands: Record<string, string>;
289 > }
290 >
291 > const dropWhenAllDefault: IDefaultsGroup[] = [
292 > // border-image (CSS Backgrounds & Borders 3 section 6.8)
293 > {
294 > longhands: {
295 > 'border-image-source': 'none',
296 > 'border-image-slice': '100%',
297 > 'border-image-width': '1',
298 > 'border-image-outset': '0',
299 > 'border-image-repeat': 'stretch',
300 > },
301 > },
302 > // animation-range (CSS Scroll-driven Animations section 5.2) initial: normal
303 > {
304 > longhands: {
305 > 'animation-range-start': 'normal',
306 > 'animation-range-end': 'normal',
307 > },
308 > },
309 > ];
310 >
311 > // -- Background collapse (color-only shorthand when images/position/etc. default) --
312 >
313 > interface IBackgroundCollapseGroup {
314 > /** background-color longhand */
315 > colorLonghand: string;
316 > /** Other background longhands that must all be at their initial value. */
317 > otherLonghands: Record<string, string>;
318 > }
319 >
320 > const backgroundCollapse: IBackgroundCollapseGroup = {
321 > colorLonghand: 'background-color',
322 > otherLonghands: {
323 > // MDN background formal definition initial values:
324 > 'background-image': 'none', // initial: none
325 > 'background-position-x': '0px', // initial: 0% (computed as 0px)
326 > 'background-position-y': '0px', // initial: 0%
327 > 'background-size': 'auto', // initial: auto auto
328 > 'background-repeat': 'repeat', // initial: repeat
329 > 'background-attachment': 'scroll', // initial: scroll
330 > 'background-origin': 'padding-box', // initial: padding-box
331 > 'background-clip': 'border-box', // initial: border-box
332 > },
333 > };
334 >
335 > // -- Simple shorthand collapse (longhands → single shorthand, omit defaults) --
336 >
337 > interface ISimpleShorthand {
338 > shorthand: string;
339 > longhands: Array<{ name: string; initial: string }>;
340 > }
341 >
342 > const simpleShorthands: ISimpleShorthand[] = [
343 > // text-decoration (CSS Text Decoration 4 section 3)
344 > // Constituents: text-decoration-line || text-decoration-style || text-decoration-color || text-decoration-thickness
345 > {
346 > shorthand: 'text-decoration',
347 > longhands: [
348 > { name: 'text-decoration-line', initial: 'none' },
349 > { name: 'text-decoration-style', initial: 'solid' },
350 > { name: 'text-decoration-color', initial: 'currentcolor' },
351 > { name: 'text-decoration-thickness', initial: 'auto' },
352 > ],
353 > },
354 > ];
355 >
356 > // -- white-space (CSS Text 4 section 3) --
357 > // Shorthand for white-space-collapse || text-wrap-mode.
358 > // Named keyword mappings for the well-known combinations:
359 >
360 > const whiteSpaceKeywords: Array<{ collapse: string; wrap: string; keyword: string }> = [
361 > { collapse: 'collapse', wrap: 'wrap', keyword: 'normal' },
362 > { collapse: 'collapse', wrap: 'nowrap', keyword: 'nowrap' },
363 > { collapse: 'preserve', wrap: 'nowrap', keyword: 'pre' },
364 > { collapse: 'preserve', wrap: 'wrap', keyword: 'pre-wrap' },
365 > { collapse: 'preserve-breaks', wrap: 'wrap', keyword: 'pre-line' },
366 > { collapse: 'break-spaces', wrap: 'wrap', keyword: 'break-spaces' },
367 > ];
368 >
369 > // -- Comma-separated list shorthands (transition, animation) --
370 >
371 > interface IListShorthand {
372 > shorthand: string;
373 > longhands: Array<{ name: string; initial: string }>;
374 > }
375 >
376 > const listShorthands: IListShorthand[] = [
377 > // transition (CSS Transitions 1 section 2.1)
378 > // Constituents: transition-property || transition-duration || transition-timing-function || transition-delay || transition-behavior
379 > {
380 > shorthand: 'transition',
381 > longhands: [
382 > { name: 'transition-property', initial: 'all' },
383 > { name: 'transition-duration', initial: '0s' },
384 > { name: 'transition-timing-function', initial: 'ease' },
385 > { name: 'transition-delay', initial: '0s' },
386 > { name: 'transition-behavior', initial: 'normal' },
387 > ],
388 > },
389 > // animation (CSS Animations 1 section 3 + Scroll-driven Animations section 5)
390 > // Constituents: animation-name || animation-duration || animation-timing-function || animation-delay
391 > // || animation-iteration-count || animation-direction || animation-fill-mode
392 > // || animation-play-state || animation-timeline
393 > {
394 > shorthand: 'animation',
395 > longhands: [
396 > { name: 'animation-name', initial: 'none' },
397 > { name: 'animation-duration', initial: '0s' },
398 > { name: 'animation-timing-function', initial: 'ease' },
399 > { name: 'animation-delay', initial: '0s' },
400 > { name: 'animation-iteration-count', initial: '1' },
401 > { name: 'animation-direction', initial: 'normal' },
402 > { name: 'animation-fill-mode', initial: 'none' },
403 > { name: 'animation-play-state', initial: 'running' },
404 > { name: 'animation-timeline', initial: 'auto' },
405 > ],
406 > },
407 > ];
408 >
409 > // -- Helper functions --
410 >
411 > /**
412 > * Tries to collapse a box shorthand (4 sides → 1-4 value shorthand).
413 > * Returns the collapsed value or undefined if not all sides are present.
414 > */
415 > function collapseBoxValues(entries: Map<string, string>, sides: [string, string, string, string]): string | undefined { cssHelpers.ts ×12
416 > const [topKey, rightKey, bottomKey, leftKey] = sides;
417 > const top = entries.get(topKey);
418 > const right = entries.get(rightKey);
419 > const bottom = entries.get(bottomKey);
420 > const left = entries.get(leftKey);
421 >
422 > if (top === undefined || right === undefined || bottom === undefined || left === undefined) {
423 > return undefined;
424 > }
426 > entries.delete(topKey);
427 > entries.delete(rightKey);
428 > entries.delete(bottomKey);
429 > entries.delete(leftKey);
430 >
431 > if (top === right && right === bottom && bottom === left) { cssHelpers.ts ×12
432 > return top; cssHelpers.ts ×1
433 > }
434 > if (top === bottom && right === left) { cssHelpers.ts ×12
435 > return `${top} ${right}`; cssHelpers.ts ×1
436 > }
437 > if (right === left) { cssHelpers.ts ×1
438 > return `${top} ${right} ${bottom}`; cssHelpers.ts ×1
439 > }
440 > return `${top} ${right} ${bottom} ${left}`; cssHelpers.ts ×1
441 > }
443 > /**
444 > * Splits a CSS value by top-level commas, respecting parenthesized groups
445 > * like `cubic-bezier(0.16, 1, 0.3, 1)`.
446 > */
447 > function splitCSSList(value: string): string[] { cssHelpers.ts ×6
448 > const items: string[] = [];
449 > let depth = 0;
450 > let start = 0;
451 > for (let i = 0; i < value.length; i++) {
452 > const ch = value[i];
453 > if (ch === '(') {
454 > depth++; cssHelpers.ts ×2
455 > } else if (ch === ')') { cssHelpers.ts ×6
456 > depth--; cssHelpers.ts ×2
457 > } else if (ch === ',' && depth === 0) { cssHelpers.ts ×6
458 > items.push(value.substring(start, i).trim()); cssHelpers.ts ×1
459 > start = i + 1;
460 > }
462 > items.push(value.substring(start).trim());
463 > return items;
464 > }
466 > /**
467 > * Collapses comma-separated list longhands into a single shorthand declaration.
468 > */
469 > function collapseListShorthand( cssHelpers.ts ×12
470 > entries: Map<string, string>,
471 > output: string[],
472 > shorthand: string,
473 > longhands: Array<{ name: string; initial: string }>,
474 > ): void {
475 > const values = longhands.map(({ name }) => entries.get(name));
476 > if (!values.every(v => v !== undefined)) {
477 > return;
478 > }
480 > const lists = values.map(v => splitCSSList(v as string));
481 > const itemCount = lists[0].length;
482 > if (!lists.every(l => l.length === itemCount)) {
483 return;
484 }
486 > for (const { name } of longhands) {
487 > entries.delete(name);
488 > }
489 >
490 > const items: string[] = [];
491 > for (let i = 0; i < itemCount; i++) {
492 > const parts: string[] = [];
493 > for (let j = 0; j < longhands.length; j++) {
494 > const val = lists[j][i];
495 > if (val !== longhands[j].initial) {
496 > parts.push(val);
497 > }
498 > }
499 > items.push(parts.length > 0 ? parts.join(' ') : longhands[0].initial);
500 > }
501 >
502 > output.push(`${shorthand}: ${items.join(', ')};`);
503 > }
505 > // -- Main entry point --
506 >
507 > /**
508 > * Collapses resolved computed properties into shorthands where possible,
509 > * then returns sorted CSS declaration lines. Driven entirely by the
510 > * constant shorthand configuration tables above.
511 > */
512 > export function collapseToShorthands(entries: Map<string, string>): string[] {
513 > const shorthandLines: string[] = []; cssHelpers.ts ×12
514 >
515 > // 1. Box shorthands (margin, padding, border-radius)
516 > for (const { shorthand, sides } of boxShorthands) {
517 > const collapsed = collapseBoxValues(entries, sides);
518 > if (collapsed !== undefined) {
519 > shorthandLines.push(`${shorthand}: ${collapsed};`); cssHelpers.ts ×1
520 > }
522 >
523 > // 2. Border: try full `border: W S C` when all four sides are uniform,
524 > // otherwise collapse each group (border-width, border-style, border-color).
525 > const borderVals = borderSideGroups.map(g => g.sides.map(s => entries.get(s)));
526 > const hasAllBorderProps = borderVals.every(group => group.every(v => v !== undefined));
527 > if (hasAllBorderProps) {
528 > const allUniform = borderVals.every(group => group.every(v => v === group[0])); cssHelpers.ts ×3
529 > if (allUniform) {
530 > for (const group of borderSideGroups) { cssHelpers.ts ×1
531 > for (const side of group.sides) {
532 > entries.delete(side);
533 > }
534 > }
535 > shorthandLines.push(`border: ${borderVals[0][0]} ${borderVals[1][0]} ${borderVals[2][0]};`);
536 > } else { cssHelpers.ts ×3
537 > for (const group of borderSideGroups) { cssHelpers.ts ×1
538 > const collapsed = collapseBoxValues(entries, group.sides);
539 > if (collapsed !== undefined) {
540 > shorthandLines.push(`${group.shorthand}: ${collapsed};`);
541 > }
542 > }
543 > }
546 > // 3. Drop-when-all-default groups (border-image, etc.)
547 > for (const { longhands } of dropWhenAllDefault) {
548 > const allDefault = Object.entries(longhands).every(([k, v]) => entries.get(k) === v);
549 > if (allDefault && Object.keys(longhands).some(k => entries.has(k))) {
550 > for (const key of Object.keys(longhands)) { cssHelpers.ts ×1
551 > entries.delete(key);
552 > }
553 > }
555 >
556 > // 4. Background collapse (→ `background: <color>` when other props at default)
557 > {
558 > const { colorLonghand, otherLonghands } = backgroundCollapse;
559 > const bgColor = entries.get(colorLonghand);
560 > const allOthersDefault = Object.entries(otherLonghands).every(([k, v]) => entries.get(k) === v);
561 > if (allOthersDefault && bgColor !== undefined) {
562 > entries.delete(colorLonghand); cssHelpers.ts ×1
563 > for (const key of Object.keys(otherLonghands)) {
564 > entries.delete(key);
565 > }
566 > shorthandLines.push(`background: ${bgColor};`);
567 > }
569 >
570 > // 5. Simple shorthands (text-decoration, etc.) — combine longhands, omit defaults
571 > for (const { shorthand, longhands } of simpleShorthands) {
572 > const first = entries.get(longhands[0].name);
573 > if (first === undefined) {
574 > continue; cssHelpers.ts ×1
575 > }
576 > // Snapshot values before deleting cssHelpers.ts ×2
577 > const values = longhands.map(({ name }) => entries.get(name));
578 > for (const { name } of longhands) {
579 > entries.delete(name);
580 > }
581 > // Build shorthand value, omitting longhands at their initial value
582 > const parts: string[] = [];
583 > for (let i = 0; i < longhands.length; i++) {
584 > const val = values[i] ?? longhands[i].initial;
585 > if (val !== longhands[i].initial) {
586 > parts.push(val); cssHelpers.ts ×1
587 > }
589 > shorthandLines.push(`${shorthand}: ${parts.length > 0 ? parts.join(' ') : longhands[0].initial};`); cssHelpers.ts ×12
590 > }
591 >
592 > // 6. white-space (CSS Text 4) — map longhand pair to named keyword
593 > {
594 > const wsCollapse = entries.get('white-space-collapse');
595 > const textWrap = entries.get('text-wrap-mode');
596 > if (wsCollapse !== undefined && textWrap !== undefined) {
597 > entries.delete('white-space-collapse'); cssHelpers.ts ×1
598 > entries.delete('text-wrap-mode');
599 > const match = whiteSpaceKeywords.find(k => k.collapse === wsCollapse && k.wrap === textWrap);
600 > shorthandLines.push(`white-space: ${match ? match.keyword : `${wsCollapse} ${textWrap}`};`);
601 > }
603 >
604 > // 7. Comma-separated list shorthands (transition, animation)
605 > for (const { shorthand, longhands } of listShorthands) {
606 > collapseListShorthand(entries, shorthandLines, shorthand, longhands);
607 > }
608 >
609 > // 8. Remaining properties as individual lines, sorted
610 > const remainingLines: string[] = [];
611 > for (const [name, value] of Array.from(entries.entries()).sort(([a], [b]) => a.localeCompare(b))) {
612 > remainingLines.push(`${name}: ${value};`); cssHelpers.ts ×1
613 > }
615 > return [...shorthandLines, ...remainingLines];
616 > }