Skip to content

Commit a78cd8f

Browse files
yinmcapricorn86
andauthored
feat: [#1147] Adds support for aspect-ratio to CSSStyleDeclaration (#1537)
* feat: [#1147] Adds support for the aspect-ratio property * chore: [#1147] Adds support for aspect-ratio to CSSStyleDeclaration * chore: [#1147] Adds support for aspect-ratio to CSSStyleDeclaration * chore: [#1147] Adds support for aspect-ratio to CSSStyleDeclaration --------- Co-authored-by: David Ortner <[email protected]>
1 parent e6f8b13 commit a78cd8f

File tree

4 files changed

+144
-30
lines changed

4 files changed

+144
-30
lines changed

packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts

+8
Original file line numberDiff line numberDiff line change
@@ -4791,6 +4791,14 @@ export default class CSSStyleDeclaration {
47914791
this.setProperty('container-name', value);
47924792
}
47934793

4794+
public get aspectRatio(): string {
4795+
return this.getPropertyValue('aspect-ratio');
4796+
}
4797+
4798+
public set aspectRatio(value: string) {
4799+
this.setProperty('aspect-ratio', value);
4800+
}
4801+
47944802
/* eslint-enable jsdoc/require-jsdoc */
47954803

47964804
/**

packages/happy-dom/src/css/declaration/property-manager/CSSStyleDeclarationPropertyManager.ts

+3
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ export default class CSSStyleDeclarationPropertyManager {
509509
case 'visibility':
510510
properties = CSSStyleDeclarationPropertySetParser.getVisibility(value, important);
511511
break;
512+
case 'aspect-ratio':
513+
properties = CSSStyleDeclarationPropertySetParser.getAspectRatio(value, important);
514+
break;
512515

513516
default:
514517
const trimmedValue = value.trim();

packages/happy-dom/src/css/declaration/property-manager/CSSStyleDeclarationPropertySetParser.ts

+93-30
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser.js'
22
import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue.js';
33

44
const RECT_REGEXP = /^rect\((.*)\)$/i;
5-
const SPLIT_COMMA_SEPARATED_REGEXP = /,(?=(?:(?:(?!\))[\s\S])*\()|[^\(\)]*$)/; // Split on commas that are outside of parentheses
6-
const SPLIT_SPACE_SEPARATED_REGEXP = /\s+(?=(?:(?:(?!\))[\s\S])*\()|[^\(\)]*$)/; // Split on spaces that are outside of parentheses
5+
const SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP = /,(?=(?:(?:(?!\))[\s\S])*\()|[^\(\)]*$)/; // Split on commas that are outside of parentheses
6+
const SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP = /\s+(?=(?:(?:(?!\))[\s\S])*\()|[^\(\)]*$)/; // Split on spaces that are outside of parentheses
7+
const WHITE_SPACE_GLOBAL_REGEXP = /\s+/gm;
78
const BORDER_STYLE = [
89
'none',
910
'hidden',
@@ -497,7 +498,7 @@ export default class CSSStyleDeclarationPropertySetParser {
497498
...this.getOutlineWidth('initial', important)
498499
};
499500

500-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
501+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
501502

502503
for (const part of parts) {
503504
const width = this.getOutlineWidth(part, important);
@@ -649,7 +650,9 @@ export default class CSSStyleDeclarationPropertySetParser {
649650
...this.getBorderImage('initial', important)
650651
};
651652

652-
const parts = value.replace(/\s*,\s*/g, ',').split(SPLIT_SPACE_SEPARATED_REGEXP);
653+
const parts = value
654+
.replace(/\s*,\s*/g, ',')
655+
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
653656

654657
for (const part of parts) {
655658
const width = this.getBorderWidth(part, important);
@@ -695,7 +698,7 @@ export default class CSSStyleDeclarationPropertySetParser {
695698
};
696699
}
697700

698-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
701+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
699702
const top = this.getBorderTopWidth(parts[0], important);
700703
const right = this.getBorderRightWidth(parts[1] || parts[0], important);
701704
const bottom = this.getBorderBottomWidth(parts[2] || parts[0], important);
@@ -741,7 +744,7 @@ export default class CSSStyleDeclarationPropertySetParser {
741744
};
742745
}
743746

744-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
747+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
745748
const top = this.getBorderTopStyle(parts[0], important);
746749
const right = this.getBorderRightStyle(parts[1] || parts[0], important);
747750
const bottom = this.getBorderBottomStyle(parts[2] || parts[0], important);
@@ -788,7 +791,7 @@ export default class CSSStyleDeclarationPropertySetParser {
788791
};
789792
}
790793

791-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
794+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
792795
const top = this.getBorderTopColor(parts[0], important);
793796
const right = this.getBorderRightColor(parts[1] || parts[0], important);
794797
const bottom = this.getBorderBottomColor(parts[2] || parts[0], important);
@@ -843,7 +846,7 @@ export default class CSSStyleDeclarationPropertySetParser {
843846
parsedValue = parsedValue.replace(sourceMatch[0], '');
844847
}
845848

846-
const parts = parsedValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
849+
const parts = parsedValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
847850

848851
if (sourceMatch) {
849852
parts.push(sourceMatch[1]);
@@ -1038,7 +1041,7 @@ export default class CSSStyleDeclarationPropertySetParser {
10381041
};
10391042
}
10401043

1041-
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
1044+
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
10421045

10431046
if (parts.length > 4) {
10441047
return null;
@@ -1099,7 +1102,7 @@ export default class CSSStyleDeclarationPropertySetParser {
10991102
};
11001103
}
11011104

1102-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1105+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
11031106

11041107
if (parts.length > 4) {
11051108
return null;
@@ -1154,7 +1157,7 @@ export default class CSSStyleDeclarationPropertySetParser {
11541157
};
11551158
}
11561159

1157-
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
1160+
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
11581161

11591162
if (parts.length > 2) {
11601163
return null;
@@ -1545,7 +1548,7 @@ export default class CSSStyleDeclarationPropertySetParser {
15451548
};
15461549
}
15471550

1548-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1551+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
15491552
const topLeft = this.getBorderTopLeftRadius(parts[0], important);
15501553
const topRight = this.getBorderTopRightRadius(parts[1] || parts[0], important);
15511554
const bottomRight = this.getBorderBottomRightRadius(parts[2] || parts[0], important);
@@ -1683,7 +1686,7 @@ export default class CSSStyleDeclarationPropertySetParser {
16831686
...this.getBorderTopColor('initial', important)
16841687
};
16851688

1686-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1689+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
16871690

16881691
for (const part of parts) {
16891692
const width = this.getBorderTopWidth(part, important);
@@ -1732,7 +1735,7 @@ export default class CSSStyleDeclarationPropertySetParser {
17321735
...this.getBorderRightColor('initial', important)
17331736
};
17341737

1735-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1738+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
17361739

17371740
for (const part of parts) {
17381741
const width = this.getBorderRightWidth(part, important);
@@ -1781,7 +1784,7 @@ export default class CSSStyleDeclarationPropertySetParser {
17811784
...this.getBorderBottomColor('initial', important)
17821785
};
17831786

1784-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1787+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
17851788

17861789
for (const part of parts) {
17871790
const width = this.getBorderBottomWidth(part, important);
@@ -1830,7 +1833,7 @@ export default class CSSStyleDeclarationPropertySetParser {
18301833
...this.getBorderLeftColor('initial', important)
18311834
};
18321835

1833-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1836+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
18341837

18351838
for (const part of parts) {
18361839
const width = this.getBorderLeftWidth(part, important);
@@ -1873,7 +1876,7 @@ export default class CSSStyleDeclarationPropertySetParser {
18731876
};
18741877
}
18751878

1876-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
1879+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
18771880
const top = this.getPaddingTop(parts[0], important);
18781881
const right = this.getPaddingRight(parts[1] || parts[0], important);
18791882
const bottom = this.getPaddingBottom(parts[2] || parts[0], important);
@@ -2006,7 +2009,7 @@ export default class CSSStyleDeclarationPropertySetParser {
20062009
};
20072010
}
20082011

2009-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
2012+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
20102013
const top = this.getMarginTop(parts[0], important);
20112014
const right = this.getMarginRight(parts[1] || parts[0], important);
20122015
const bottom = this.getMarginBottom(parts[2] || parts[0], important);
@@ -2164,7 +2167,7 @@ export default class CSSStyleDeclarationPropertySetParser {
21642167
};
21652168
}
21662169

2167-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
2170+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
21682171
const flexGrow = this.getFlexGrow(parts[0], important);
21692172
const flexShrink = this.getFlexShrink(parts[1] || '1', important);
21702173
const flexBasis = this.getFlexBasis(parts[2] || '0%', important);
@@ -2300,7 +2303,7 @@ export default class CSSStyleDeclarationPropertySetParser {
23002303
const parts = value
23012304
.replace(/\s,\s/g, ',')
23022305
.replace(/\s\/\s/g, '/')
2303-
.split(SPLIT_SPACE_SEPARATED_REGEXP);
2306+
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
23042307

23052308
const backgroundPositions = [];
23062309

@@ -2397,7 +2400,7 @@ export default class CSSStyleDeclarationPropertySetParser {
23972400
return { 'background-size': { value: lowerValue, important } };
23982401
}
23992402

2400-
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
2403+
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
24012404
const parsed = [];
24022405

24032406
for (const imagePart of imageParts) {
@@ -2568,12 +2571,12 @@ export default class CSSStyleDeclarationPropertySetParser {
25682571
};
25692572
}
25702573

2571-
const imageParts = value.split(SPLIT_COMMA_SEPARATED_REGEXP);
2574+
const imageParts = value.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
25722575
let x = '';
25732576
let y = '';
25742577

25752578
for (const imagePart of imageParts) {
2576-
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
2579+
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
25772580

25782581
if (x) {
25792582
x += ',';
@@ -2681,11 +2684,11 @@ export default class CSSStyleDeclarationPropertySetParser {
26812684
return { 'background-position-x': { value: lowerValue, important } };
26822685
}
26832686

2684-
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
2687+
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
26852688
let parsedValue = '';
26862689

26872690
for (const imagePart of imageParts) {
2688-
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
2691+
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
26892692

26902693
if (parsedValue) {
26912694
parsedValue += ',';
@@ -2732,11 +2735,11 @@ export default class CSSStyleDeclarationPropertySetParser {
27322735
return { 'background-position-y': { value: lowerValue, important } };
27332736
}
27342737

2735-
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
2738+
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
27362739
let parsedValue = '';
27372740

27382741
for (const imagePart of imageParts) {
2739-
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
2742+
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
27402743

27412744
if (parsedValue) {
27422745
parsedValue += ',';
@@ -2808,7 +2811,7 @@ export default class CSSStyleDeclarationPropertySetParser {
28082811
return { 'background-image': { value: lowerValue, important } };
28092812
}
28102813

2811-
const parts = value.split(SPLIT_COMMA_SEPARATED_REGEXP);
2814+
const parts = value.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
28122815
const parsed = [];
28132816

28142817
for (const part of parts) {
@@ -2917,7 +2920,9 @@ export default class CSSStyleDeclarationPropertySetParser {
29172920
...this.getLineHeight('normal', important)
29182921
};
29192922

2920-
const parts = value.replace(/\s*\/\s*/g, '/').split(SPLIT_SPACE_SEPARATED_REGEXP);
2923+
const parts = value
2924+
.replace(/\s*\/\s*/g, '/')
2925+
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
29212926

29222927
for (let i = 0, max = parts.length; i < max; i++) {
29232928
const part = parts[i];
@@ -2985,7 +2990,7 @@ export default class CSSStyleDeclarationPropertySetParser {
29852990
if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STYLE.includes(lowerValue)) {
29862991
return { 'font-style': { value: lowerValue, important } };
29872992
}
2988-
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
2993+
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
29892994
if (parts.length === 2 && parts[0] === 'oblique') {
29902995
const degree = CSSStyleDeclarationValueParser.getDegree(parts[1]);
29912996
return degree ? { 'font-style': { value: lowerValue, important } } : null;
@@ -3256,4 +3261,62 @@ export default class CSSStyleDeclarationPropertySetParser {
32563261
}
32573262
return null;
32583263
}
3264+
3265+
/**
3266+
* Returns aspect ratio.
3267+
*
3268+
* @param value Value.
3269+
* @param important Important.
3270+
* @returns Property
3271+
*/
3272+
public static getAspectRatio(
3273+
value: string,
3274+
important: boolean
3275+
): {
3276+
[key: string]: ICSSStyleDeclarationPropertyValue;
3277+
} {
3278+
const variable = CSSStyleDeclarationValueParser.getVariable(value);
3279+
if (variable) {
3280+
return { 'aspect-ratio': { value: variable, important } };
3281+
}
3282+
3283+
const lowerValue = value.toLowerCase();
3284+
3285+
if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) {
3286+
return { 'aspect-ratio': { value: lowerValue, important } };
3287+
}
3288+
3289+
let parsedValue = value;
3290+
3291+
const hasAuto = parsedValue.includes('auto');
3292+
3293+
if (hasAuto) {
3294+
parsedValue = parsedValue.replace('auto', '');
3295+
}
3296+
3297+
parsedValue = parsedValue.replace(WHITE_SPACE_GLOBAL_REGEXP, '');
3298+
3299+
if (!parsedValue) {
3300+
return { 'aspect-ratio': { value: 'auto', important } };
3301+
}
3302+
3303+
const aspectRatio = parsedValue.split('/');
3304+
3305+
if (aspectRatio.length > 3) {
3306+
return null;
3307+
}
3308+
3309+
const width = Number(aspectRatio[0]);
3310+
const height = aspectRatio[1] ? Number(aspectRatio[1]) : 1;
3311+
3312+
if (isNaN(width) || isNaN(height)) {
3313+
return null;
3314+
}
3315+
3316+
if (hasAuto) {
3317+
return { 'aspect-ratio': { value: `auto ${width} / ${height}`, important } };
3318+
}
3319+
3320+
return { 'aspect-ratio': { value: `${width} / ${height}`, important } };
3321+
}
32593322
}

packages/happy-dom/test/css/declaration/CSSStyleDeclaration.test.ts

+40
Original file line numberDiff line numberDiff line change
@@ -2728,6 +2728,46 @@ describe('CSSStyleDeclaration', () => {
27282728
});
27292729
});
27302730

2731+
describe('get aspectRatio()', () => {
2732+
it('Returns style property.', () => {
2733+
const declaration = new CSSStyleDeclaration(PropertySymbol.illegalConstructor, window, {
2734+
element
2735+
});
2736+
2737+
for (const value of [
2738+
'var(--test-variable)',
2739+
'inherit',
2740+
'initial',
2741+
'revert',
2742+
'unset',
2743+
'auto',
2744+
'1 / 1',
2745+
'16 / 9',
2746+
'4 / 3',
2747+
'1 / 2',
2748+
'2 / 1',
2749+
'3 / 4',
2750+
'9 / 16'
2751+
]) {
2752+
element.setAttribute('style', `aspect-ratio: ${value}`);
2753+
2754+
expect(declaration.aspectRatio).toBe(value);
2755+
}
2756+
2757+
element.setAttribute('style', 'aspect-ratio: 2');
2758+
2759+
expect(declaration.aspectRatio).toBe('2 / 1');
2760+
2761+
element.setAttribute('style', 'aspect-ratio: 16/9 auto');
2762+
2763+
expect(declaration.aspectRatio).toBe('auto 16 / 9');
2764+
2765+
element.setAttribute('style', 'aspect-ratio: 16/9');
2766+
2767+
expect(declaration.aspectRatio).toBe('16 / 9');
2768+
});
2769+
});
2770+
27312771
describe('get length()', () => {
27322772
it('Returns length when of styles on element.', () => {
27332773
const declaration = new CSSStyleDeclaration(PropertySymbol.illegalConstructor, window, {

0 commit comments

Comments
 (0)