Skip to content

Commit

Permalink
Support calculated values in sizes and heights (#3078)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvoytenko committed May 5, 2016
1 parent 61e279f commit df2081f
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 11 deletions.
72 changes: 63 additions & 9 deletions src/size-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,72 @@ export function parseSizeList(s, opt_allowPercentAsLength) {

let mediaStr;
let sizeStr;
const spaceIndex = sSize.lastIndexOf(' ');
if (spaceIndex != -1) {
mediaStr = sSize.substring(0, spaceIndex).trim();
sizeStr = sSize.substring(spaceIndex + 1).trim();

// Process the expression from the end.
const lastChar = sSize.charAt(sSize.length - 1);
let div;
let func = false;
if (lastChar == ')') {
// Value is the CSS function, e.g. `calc(50vw + 10px)`.
func = true;

// First, skip to the opening paren.
let parens = 1;
div = sSize.length - 2;
for (; div >= 0; div--) {
const c = sSize.charAt(div);
if (c == '(') {
parens--;
} else if (c == ')') {
parens++;
}
if (parens == 0) {
break;
}
}

// Then, skip to the begining to the function's name.
const funcEnd = div - 1;
if (div > 0) {
div--;
for (; div >= 0; div--) {
const c = sSize.charAt(div);
if (!(c == '%' || c == '-' || c == '_' ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9'))) {
break;
}
}
}
user.assert(div < funcEnd, 'Invalid CSS function in "%s"', sSize);
} else {
// Value is the length or a percent: accept a wide range of values,
// including invalid values - they will be later asserted to conform
// to exact CSS length or percent value.
div = sSize.length - 2;
for (; div >= 0; div--) {
const c = sSize.charAt(div);
if (!(c == '%' || c == '.' ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9'))) {
break;
}
}
}
if (div >= 0) {
mediaStr = sSize.substring(0, div + 1).trim();
sizeStr = sSize.substring(div + 1).trim();
} else {
sizeStr = sSize;
mediaStr = undefined;
}
sizes.push({mediaQuery: mediaStr,
size: opt_allowPercentAsLength ?
assertLengthOrPercent(sizeStr) :
assertLength(sizeStr)});
size: func ? sizeStr :
opt_allowPercentAsLength ?
assertLengthOrPercent(sizeStr) :
assertLength(sizeStr)});
});
return new SizeList(sizes);
};
Expand Down Expand Up @@ -109,7 +163,7 @@ export class SizeList {
*
* See http://www.w3.org/html/wg/drafts/html/master/semantics.html#attr-img-sizes
* @param {!Window} win
* @return {!Length}
* @return {!LengthDef|string}
*/
select(win) {
for (let i = 0; i < this.sizes_.length - 1; i++) {
Expand All @@ -123,7 +177,7 @@ export class SizeList {

/**
* Returns the last size in the SizeList, which is the default.
* @return {!Length}
* @return {!LengthDef|string}
*/
getLast() {
return this.sizes_[this.sizes_.length - 1].size;
Expand Down
48 changes: 46 additions & 2 deletions test/functional/test-size-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,59 @@ describe('SizeList parseSizeList', () => {
expect(res.sizes_[0].size).to.equal('111vw');
});

it('should accept different length units including percent', () => {
it('should accept fractional numbers', () => {
const res = parseSizeList(' \n 11.1vw \n ');
expect(res.sizes_.length).to.equal(1);
expect(res.sizes_[0].mediaQuery).to.equal(undefined);
expect(res.sizes_[0].size).to.equal('11.1vw');
});

it('should accept CSS functions', () => {
const res = parseSizeList('screen calc(111vw + 10px) \n' +
', ca_1-C((50vw+20px) / 2) ');
expect(res.sizes_.length).to.equal(2);
expect(res.sizes_[0].mediaQuery).to.equal('screen');
expect(res.sizes_[0].size).to.equal('calc(111vw + 10px)');
expect(res.sizes_[1].mediaQuery).to.be.undefined;
expect(res.sizes_[1].size).to.equal('ca_1-C((50vw+20px) / 2)');
});

it('should tolerate right paren', () => {
const res = parseSizeList('(min-width:2000px)calc(11px)' +
',(min-width:1000px)11px,12px');
expect(res.sizes_.length).to.equal(3);
expect(res.sizes_[0].mediaQuery).to.equal('(min-width:2000px)');
expect(res.sizes_[0].size).to.equal('calc(11px)');
expect(res.sizes_[1].mediaQuery).to.equal('(min-width:1000px)');
expect(res.sizes_[1].size).to.equal('11px');
expect(res.sizes_[2].mediaQuery).to.be.undefined;
expect(res.sizes_[2].size).to.equal('12px');
});

it('should fail on invalid CSS functions', () => {
// Spaces are not allowed between function name and `(`.
expect(() => {
parseSizeList('screen calc (111vw + 10px) \n, 10px ');
}).to.throw(/Invalid CSS function/);

// Parens don't match.
expect(() => {
parseSizeList('screen calc(111vw + 10px)) \n, 10px ');
}).to.throw(/Invalid CSS function/);
expect(() => {
parseSizeList('screen calc((111vw + 10px) \n, 10px ');
}).to.throw(/Invalid CSS function/);
});

it('should accept percent when allowed', () => {
const res = parseSizeList(' \n 111% \n ',
/* opt_allowPercentAsLength */ true);
expect(res.sizes_.length).to.equal(1);
expect(res.sizes_[0].mediaQuery).to.equal(undefined);
expect(res.sizes_[0].size).to.equal('111%');
});

it('should fail bad length', () => {
it('should not accept percent', () => {
expect(() => {
parseSizeList(' \n 111% \n ', /* opt_allowPercentAsLength */ false);
}).to.throw(/Invalid length value/);
Expand Down

0 comments on commit df2081f

Please sign in to comment.