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 {
416
const [topKey, rightKey, bottomKey, leftKey] = sides;