Skip to content

Commit

Permalink
feat: [#1147] Adds support for aspect-ratio to CSSStyleDeclaration (#…
Browse files Browse the repository at this point in the history
…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 <david@ortner.se>
  • Loading branch information
yinm and capricorn86 authored Nov 5, 2024
1 parent e6f8b13 commit a78cd8f
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 30 deletions.
8 changes: 8 additions & 0 deletions packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4791,6 +4791,14 @@ export default class CSSStyleDeclaration {
this.setProperty('container-name', value);
}

public get aspectRatio(): string {
return this.getPropertyValue('aspect-ratio');
}

public set aspectRatio(value: string) {
this.setProperty('aspect-ratio', value);
}

/* eslint-enable jsdoc/require-jsdoc */

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,9 @@ export default class CSSStyleDeclarationPropertyManager {
case 'visibility':
properties = CSSStyleDeclarationPropertySetParser.getVisibility(value, important);
break;
case 'aspect-ratio':
properties = CSSStyleDeclarationPropertySetParser.getAspectRatio(value, important);
break;

default:
const trimmedValue = value.trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser.js'
import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue.js';

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

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getOutlineWidth(part, important);
Expand Down Expand Up @@ -649,7 +650,9 @@ export default class CSSStyleDeclarationPropertySetParser {
...this.getBorderImage('initial', important)
};

const parts = value.replace(/\s*,\s*/g, ',').split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value
.replace(/\s*,\s*/g, ',')
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getBorderWidth(part, important);
Expand Down Expand Up @@ -695,7 +698,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const top = this.getBorderTopWidth(parts[0], important);
const right = this.getBorderRightWidth(parts[1] || parts[0], important);
const bottom = this.getBorderBottomWidth(parts[2] || parts[0], important);
Expand Down Expand Up @@ -741,7 +744,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const top = this.getBorderTopStyle(parts[0], important);
const right = this.getBorderRightStyle(parts[1] || parts[0], important);
const bottom = this.getBorderBottomStyle(parts[2] || parts[0], important);
Expand Down Expand Up @@ -788,7 +791,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const top = this.getBorderTopColor(parts[0], important);
const right = this.getBorderRightColor(parts[1] || parts[0], important);
const bottom = this.getBorderBottomColor(parts[2] || parts[0], important);
Expand Down Expand Up @@ -843,7 +846,7 @@ export default class CSSStyleDeclarationPropertySetParser {
parsedValue = parsedValue.replace(sourceMatch[0], '');
}

const parts = parsedValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = parsedValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (sourceMatch) {
parts.push(sourceMatch[1]);
Expand Down Expand Up @@ -1038,7 +1041,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (parts.length > 4) {
return null;
Expand Down Expand Up @@ -1099,7 +1102,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (parts.length > 4) {
return null;
Expand Down Expand Up @@ -1154,7 +1157,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = lowerValue.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (parts.length > 2) {
return null;
Expand Down Expand Up @@ -1545,7 +1548,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const topLeft = this.getBorderTopLeftRadius(parts[0], important);
const topRight = this.getBorderTopRightRadius(parts[1] || parts[0], important);
const bottomRight = this.getBorderBottomRightRadius(parts[2] || parts[0], important);
Expand Down Expand Up @@ -1683,7 +1686,7 @@ export default class CSSStyleDeclarationPropertySetParser {
...this.getBorderTopColor('initial', important)
};

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getBorderTopWidth(part, important);
Expand Down Expand Up @@ -1732,7 +1735,7 @@ export default class CSSStyleDeclarationPropertySetParser {
...this.getBorderRightColor('initial', important)
};

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getBorderRightWidth(part, important);
Expand Down Expand Up @@ -1781,7 +1784,7 @@ export default class CSSStyleDeclarationPropertySetParser {
...this.getBorderBottomColor('initial', important)
};

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getBorderBottomWidth(part, important);
Expand Down Expand Up @@ -1830,7 +1833,7 @@ export default class CSSStyleDeclarationPropertySetParser {
...this.getBorderLeftColor('initial', important)
};

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (const part of parts) {
const width = this.getBorderLeftWidth(part, important);
Expand Down Expand Up @@ -1873,7 +1876,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const top = this.getPaddingTop(parts[0], important);
const right = this.getPaddingRight(parts[1] || parts[0], important);
const bottom = this.getPaddingBottom(parts[2] || parts[0], important);
Expand Down Expand Up @@ -2006,7 +2009,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const top = this.getMarginTop(parts[0], important);
const right = this.getMarginRight(parts[1] || parts[0], important);
const bottom = this.getMarginBottom(parts[2] || parts[0], important);
Expand Down Expand Up @@ -2164,7 +2167,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
const flexGrow = this.getFlexGrow(parts[0], important);
const flexShrink = this.getFlexShrink(parts[1] || '1', important);
const flexBasis = this.getFlexBasis(parts[2] || '0%', important);
Expand Down Expand Up @@ -2300,7 +2303,7 @@ export default class CSSStyleDeclarationPropertySetParser {
const parts = value
.replace(/\s,\s/g, ',')
.replace(/\s\/\s/g, '/')
.split(SPLIT_SPACE_SEPARATED_REGEXP);
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

const backgroundPositions = [];

Expand Down Expand Up @@ -2397,7 +2400,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-size': { value: lowerValue, important } };
}

const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
const parsed = [];

for (const imagePart of imageParts) {
Expand Down Expand Up @@ -2568,12 +2571,12 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const imageParts = value.split(SPLIT_COMMA_SEPARATED_REGEXP);
const imageParts = value.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
let x = '';
let y = '';

for (const imagePart of imageParts) {
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (x) {
x += ',';
Expand Down Expand Up @@ -2681,11 +2684,11 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-position-x': { value: lowerValue, important } };
}

const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
let parsedValue = '';

for (const imagePart of imageParts) {
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (parsedValue) {
parsedValue += ',';
Expand Down Expand Up @@ -2732,11 +2735,11 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-position-y': { value: lowerValue, important } };
}

const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_REGEXP);
const imageParts = lowerValue.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
let parsedValue = '';

for (const imagePart of imageParts) {
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = imagePart.trim().split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

if (parsedValue) {
parsedValue += ',';
Expand Down Expand Up @@ -2808,7 +2811,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-image': { value: lowerValue, important } };
}

const parts = value.split(SPLIT_COMMA_SEPARATED_REGEXP);
const parts = value.split(SPLIT_COMMA_SEPARATED_WITH_PARANTHESES_REGEXP);
const parsed = [];

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

const parts = value.replace(/\s*\/\s*/g, '/').split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value
.replace(/\s*\/\s*/g, '/')
.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);

for (let i = 0, max = parts.length; i < max; i++) {
const part = parts[i];
Expand Down Expand Up @@ -2985,7 +2990,7 @@ export default class CSSStyleDeclarationPropertySetParser {
if (CSSStyleDeclarationValueParser.getGlobal(lowerValue) || FONT_STYLE.includes(lowerValue)) {
return { 'font-style': { value: lowerValue, important } };
}
const parts = value.split(SPLIT_SPACE_SEPARATED_REGEXP);
const parts = value.split(SPLIT_SPACE_SEPARATED_WITH_PARANTHESES_REGEXP);
if (parts.length === 2 && parts[0] === 'oblique') {
const degree = CSSStyleDeclarationValueParser.getDegree(parts[1]);
return degree ? { 'font-style': { value: lowerValue, important } } : null;
Expand Down Expand Up @@ -3256,4 +3261,62 @@ export default class CSSStyleDeclarationPropertySetParser {
}
return null;
}

/**
* Returns aspect ratio.
*
* @param value Value.
* @param important Important.
* @returns Property
*/
public static getAspectRatio(
value: string,
important: boolean
): {
[key: string]: ICSSStyleDeclarationPropertyValue;
} {
const variable = CSSStyleDeclarationValueParser.getVariable(value);
if (variable) {
return { 'aspect-ratio': { value: variable, important } };
}

const lowerValue = value.toLowerCase();

if (CSSStyleDeclarationValueParser.getGlobal(lowerValue)) {
return { 'aspect-ratio': { value: lowerValue, important } };
}

let parsedValue = value;

const hasAuto = parsedValue.includes('auto');

if (hasAuto) {
parsedValue = parsedValue.replace('auto', '');
}

parsedValue = parsedValue.replace(WHITE_SPACE_GLOBAL_REGEXP, '');

if (!parsedValue) {
return { 'aspect-ratio': { value: 'auto', important } };
}

const aspectRatio = parsedValue.split('/');

if (aspectRatio.length > 3) {
return null;
}

const width = Number(aspectRatio[0]);
const height = aspectRatio[1] ? Number(aspectRatio[1]) : 1;

if (isNaN(width) || isNaN(height)) {
return null;
}

if (hasAuto) {
return { 'aspect-ratio': { value: `auto ${width} / ${height}`, important } };
}

return { 'aspect-ratio': { value: `${width} / ${height}`, important } };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2728,6 +2728,46 @@ describe('CSSStyleDeclaration', () => {
});
});

describe('get aspectRatio()', () => {
it('Returns style property.', () => {
const declaration = new CSSStyleDeclaration(PropertySymbol.illegalConstructor, window, {
element
});

for (const value of [
'var(--test-variable)',
'inherit',
'initial',
'revert',
'unset',
'auto',
'1 / 1',
'16 / 9',
'4 / 3',
'1 / 2',
'2 / 1',
'3 / 4',
'9 / 16'
]) {
element.setAttribute('style', `aspect-ratio: ${value}`);

expect(declaration.aspectRatio).toBe(value);
}

element.setAttribute('style', 'aspect-ratio: 2');

expect(declaration.aspectRatio).toBe('2 / 1');

element.setAttribute('style', 'aspect-ratio: 16/9 auto');

expect(declaration.aspectRatio).toBe('auto 16 / 9');

element.setAttribute('style', 'aspect-ratio: 16/9');

expect(declaration.aspectRatio).toBe('16 / 9');
});
});

describe('get length()', () => {
it('Returns length when of styles on element.', () => {
const declaration = new CSSStyleDeclaration(PropertySymbol.illegalConstructor, window, {
Expand Down

0 comments on commit a78cd8f

Please sign in to comment.