diff --git a/lib/parsers.js b/lib/parsers.js index 6e44f561..9ff8ec4a 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -61,11 +61,14 @@ const urlRegEx = /^url\(\s*([^)]*)\s*\)$/; */ const anglePattern = `(${numberPattern})(deg|grad|rad|turn)`; const lengthPattern = `(${numberPattern})(ch|cm|r?em|ex|in|lh|mm|pc|pt|px|q|vh|vmin|vmax|vw)`; +const linearGradientPositions = [['right', 'left'], ['bottom', 'top']]; +const positions = linearGradientPositions.concat('center'); const angleRegEx = new RegExp(`^${anglePattern}$`, 'i'); const calcRegEx = /^calc\(\s*(.+)\s*\)$/i; const colorHexRegEx = /^#([0-9a-f]{3,4}){1,2}$/i; const colorFnSeparators = [',', '/', ' ']; const colorFnRegex = /^(hsl|rgb)a?\(\s*(.+)\s*\)$/i; +const gradientRegEx = /^(repeating-)?(conic|linear|radial)-gradient\(\s*(.+)\s*\)$/i; const lengthRegEx = new RegExp(`^${lengthPattern}$`, 'i'); const numericRegEx = new RegExp(`^(${numberPattern})(%|${identPattern})?$`, 'i'); const timeRegEx = new RegExp(`^(${numberPattern})(m?s)$`, 'i'); @@ -187,14 +190,14 @@ exports.parseLength = function parseLength(val, resolve = false) { * https://drafts.csswg.org/css-values-4/#percentages * https://drafts.csswg.org/cssom/#ref-for-percentage-value */ -exports.parsePercent = function parsePercent(val, resolve = false) { +exports.parsePercentage = function parsePercentage(val, resolve = false) { if (val === '') { return val; } if (val === '0') { return '0%'; } - const calculated = exports.parseCalc(val, parsePercent); + const calculated = exports.parseCalc(val, parsePercentage); if (calculated) { if (!resolve) { return calculated; @@ -209,13 +212,8 @@ exports.parsePercent = function parsePercent(val, resolve = false) { return serializeNumber(number) + '%'; }; -// either a length or a percent -exports.parseMeasurement = function parseMeasurement(val) { - var length = exports.parseLength(val); - if (length !== undefined) { - return length; - } - return exports.parsePercent(val); +exports.parseLengthOrPercentage = function parseLengthOrPercentage(val, resolve) { + return exports.parseLength(val, resolve) || exports.parsePercentage(val, resolve); }; /** @@ -235,7 +233,7 @@ exports.parseAlpha = function parseAlpha(val, is8Bit = false) { if (parsed !== undefined) { is8Bit = false; val = Math.min(1, Math.max(0, parsed)) * 100; - } else if ((parsed = exports.parsePercent(val, true))) { + } else if ((parsed = exports.parsePercentage(val, true))) { val = Math.min(100, Math.max(0, parsed.slice(0, -1))); } else { return undefined; @@ -294,6 +292,10 @@ exports.parseAngle = function parseAngle(val, resolve = false) { return serializeNumber(number) + unit; }; +exports.parseAngleOrPercentage = function parseAngleOrPercentage(val, resolve) { + return exports.parseAngle(val, resolve) || exports.parsePercentage(val, resolve); +}; + /** * https://drafts.csswg.org/css-values-4/#time * https://drafts.csswg.org/cssom/#ref-for-time-value @@ -477,6 +479,15 @@ exports.parseCustomIdentifier = function parseCustomIdentifier(val) { } }; +/** + * https://drafts.csswg.org/css-images-4/#image-values + * + * TODO: , , . + */ +exports.parseImage = function parseImage(val) { + return exports.parseUrl(val) || exports.parseGradient(val); +}; + /** * https://drafts.csswg.org/css-values-4/#urls * https://drafts.csswg.org/cssom/#ref-for-url-value @@ -645,7 +656,7 @@ exports.parseColor = function parseColor(val) { return undefined; } [arg2, arg3].forEach(val => { - if ((val = exports.parsePercent(val, true))) { + if ((val = exports.parsePercentage(val, true))) { return hsl.push(Math.min(100, Math.max(0, val.slice(0, -1))) / 100); } }); @@ -676,7 +687,7 @@ exports.parseColor = function parseColor(val) { rgb.push(Math.round(Math.min(255, Math.max(0, number)))); return; } - const percentage = exports.parsePercent(val, true); + const percentage = exports.parsePercentage(val, true); if (percentage) { types.add('percent'); rgb.push(Math.round(Math.min(255, Math.max(0, (percentage.slice(0, -1) / 100) * 255)))); @@ -705,6 +716,431 @@ exports.parseColor = function parseColor(val) { return exports.parseKeyword(val, namedColors); }; +/** + * https://drafts.csswg.org/css-images-4/#gradients + * + * , , + * args should be either , , or + * (resolved) args length, excluded, should be > 1 + * should be the first arg + * should be ? ? + * ? should be resolved to ?, + * + * should be [from ]? [at ]? + * , , should be where 0 is 0deg + * , + * , , should be where 0 is 0px + * + * should be or [to ] + * should be in resolved value if not equal or resolved to 180deg + * [to ] should be in resolved value if is not bottom + * + * should be ? ? [at ]? or ? ? [at ]? + * should be circle (default) or ellipse + * should be in resolved value only if defined as circle and either: + * is not + * is defined + * should not be in resolved value if it is farthest-corner + * should be or positive if is circle + * should be or positive {2} if is ellipse + */ +exports.parseGradient = function parseGradient(val) { + if (val === '') { + return val; + } + + const res = gradientRegEx.exec(val); + + if (!res) { + return undefined; + } + + let [, repeating = '', gradientType, stringArgs] = res; + const [args] = exports.splitFnArgs(stringArgs); + + gradientType = gradientType.toLowerCase(); + + if (args.length === 0) { + return undefined; + } + + if (gradientType === 'conic') { + let startAngle; + let startPosition; + let res = /^from\s+(.+)\s+at\s+(.+)$/.exec(args[0]); + if (res) { + startAngle = res[1]; + startPosition = res[2]; + } else if ((res = /^from\s+(.+)$/.exec(args[0]))) { + startAngle = res[1]; + } else if ((res = /^at\s+(.+)$/.exec(args[0]))) { + startPosition = res[1]; + } + + const config = []; + if (startAngle) { + if ((startAngle = exports.parseAngle(startAngle))) { + config.push(`from ${startAngle}`); + } else { + return undefined; + } + } + if (startPosition) { + if ((startPosition = exports.parsePosition(startPosition))) { + config.push(`at ${startPosition}`); + } else { + return undefined; + } + } + + const parsedArgs = []; + if (config.length > 0) { + args.shift(); + parsedArgs.push(config.join(' ')); + } + + const stops = []; + let prevArgType; + for (let i = 0; i < args.length; i++) { + const [stopOrHintArgs] = exports.splitFnArgs(args[i], ' '); + + if (stopOrHintArgs.length > 3) { + return undefined; + } + + let [colorOrHint, start, end] = stopOrHintArgs; + + if ((colorOrHint = exports.parseAngleOrPercentage(colorOrHint))) { + if (prevArgType !== 'color' || stopOrHintArgs.length > 1) { + return undefined; + } + prevArgType = 'hint'; + stops.push(colorOrHint); + continue; + } + + const stopArgs = []; + + if ((colorOrHint = exports.parseColor(stopOrHintArgs[0]))) { + prevArgType = 'color'; + stopArgs.push(colorOrHint); + } else { + return undefined; + } + if (start) { + if ((start = exports.parseAngleOrPercentage(start))) { + stopArgs.push(start); + } else { + return undefined; + } + } + stops.push(stopArgs.join(' ')); + if (end) { + if ((end = exports.parseAngleOrPercentage(end))) { + stops.push(`${colorOrHint} ${end}`); + } else { + return undefined; + } + } + } + + if (stops.length < 2) { + return undefined; + } + parsedArgs.push(...stops); + + return `${repeating}conic-gradient(${parsedArgs.join(', ')})`; + } + + if (gradientType === 'linear') { + const config = []; + let res = /^to\s+(.+)$/.exec(args[0]); + let angle, position; + if (res) { + if ((position = exports.parsePosition(res[1], linearGradientPositions))) { + if (position === 'bottom') { + args.shift(); + } else { + config.push(`to ${position}`); + } + } else { + return undefined; + } + } else if ((angle = exports.parseAngle(args[0]))) { + if (angle === '180deg' || angle === 'calc(180deg)') { + args.shift(); + } else { + config.push(angle); + } + } + + const parsedArgs = []; + if (config.length > 0) { + args.shift(); + parsedArgs.push(config.join(' ')); + } + + const stops = []; + let prevArgType; + for (let i = 0; i < args.length; i++) { + const [stopOrHintArgs] = exports.splitFnArgs(args[i], ' '); + + if (stopOrHintArgs.length > 3) { + return undefined; + } + + let [colorOrHint, start, end] = stopOrHintArgs; + + if ((colorOrHint = exports.parseLengthOrPercentage(colorOrHint))) { + if (prevArgType !== 'color' || stopOrHintArgs.length > 1) { + return undefined; + } + prevArgType = 'hint'; + stops.push(colorOrHint); + continue; + } + + const stopArgs = []; + + if ((colorOrHint = exports.parseColor(stopOrHintArgs[0]))) { + prevArgType = 'color'; + stopArgs.push(colorOrHint); + } else { + return undefined; + } + if (start) { + if ((start = exports.parseLengthOrPercentage(start))) { + stopArgs.push(start); + } else { + return undefined; + } + } + stops.push(stopArgs.join(' ')); + if (end) { + if ((end = exports.parseLengthOrPercentage(end))) { + stops.push(`${colorOrHint} ${end}`); + } else { + return undefined; + } + } + } + + if (stops.length < 2) { + return undefined; + } + parsedArgs.push(...stops); + + return `${repeating}linear-gradient(${parsedArgs.join(', ')})`; + } + + if (gradientType === 'radial') { + let shape; + let size; + let startPosition; + let res; + if ((res = /((.+)\s+)?at\s+(.+)$/.exec(args[0]))) { + startPosition = res[3]; + args[0] = res[2]; + res = null; + } + if (!res && args[0]) { + if ((res = /^((.+)\s+)?(circle|ellipse)$/.exec(args[0]))) { + shape = res[3]; + size = res[2]; + } else if ((res = /^(circle|ellipse)\s+(.+)$/.exec(args[0]))) { + shape = res[1]; + size = res[2]; + } else if (exports.splitFnArgs(args[0], ' ')[0].every(v => !exports.parseColor(v))) { + size = args[0]; + } + } + + let config = []; + + if (shape === 'circle') { + config.push('circle'); + } + if (size) { + shape = shape || 'circle'; + size = exports.splitFnArgs(size, ' ')[0].reduce((components, value, index, { length }) => { + if (components === undefined) { + return undefined; + } + if (/^(closest|farthest)-(corner|side)$/.test(value)) { + if (index === 0 && length === 1) { + return [value]; + } + } + if (shape === 'circle') { + if (index === 0 && length === 1 && (value = exports.parseLength(value))) { + if (!startPosition) { + config.shift(); // Remove 'circle' + } + return [value]; + } + } + if (shape === 'ellipse') { + if ((value = exports.parseLengthOrPercentage(value)) && length === 2) { + return components.concat(value); + } + } + }, []); + if (size) { + config.push(size.join(' ')); + } else { + return undefined; + } + } + if (startPosition) { + if ((startPosition = exports.parsePosition(startPosition))) { + config.push(`at ${startPosition}`); + } else { + return undefined; + } + } + if (config.length > 0 || shape === 'ellipse') { + args.shift(); + } + const parsedArgs = []; + if ((config = config.filter(value => value !== 'farthest-corner').join(' '))) { + parsedArgs.push(config); + } + + const stops = []; + let prevArgType; + for (let i = 0; i < args.length; i++) { + const [stopOrHintArgs] = exports.splitFnArgs(args[i], ' '); + + if (stopOrHintArgs.length > 3) { + return undefined; + } + + let [colorOrHint, start, end] = stopOrHintArgs; + + if ((colorOrHint = exports.parseLengthOrPercentage(colorOrHint))) { + if (prevArgType !== 'color' || stopOrHintArgs.length > 1) { + return undefined; + } + prevArgType = 'hint'; + stops.push(colorOrHint); + continue; + } + + const stopArgs = []; + + if ((colorOrHint = exports.parseColor(stopOrHintArgs[0]))) { + prevArgType = 'color'; + stopArgs.push(colorOrHint); + } else { + return undefined; + } + if (start) { + if ((start = exports.parseLengthOrPercentage(start))) { + stopArgs.push(start); + } else { + return undefined; + } + } + stops.push(stopArgs.join(' ')); + if (end) { + if ((end = exports.parseLengthOrPercentage(end))) { + stops.push(`${colorOrHint} ${end}`); + } else { + return undefined; + } + } + } + + if (stops.length < 2) { + return undefined; + } + parsedArgs.push(...stops); + + return `${repeating}radial-gradient(${parsedArgs.join(', ')})`; + } +}; + +/** + * @param {string} val + * @param {array} validPositions - [horizontal, vertical, initial?] + * + * https://drafts.csswg.org/css-backgrounds-3/#typedef-bg-position + */ +exports.parsePosition = function parsePosition(val, validPositions = positions) { + if (val === '') { + return val; + } + + const [horizontal, vertical, initial] = validPositions; + let [components] = exports.splitFnArgs(val, ' '); + + // + if (components.length === 4) { + return components.reduce((position, value, index) => { + if (position === undefined) { + return undefined; + } + if (index % 2) { + if ((value = exports.parseLengthOrPercentage(value))) { + return `${position} ${value}`; + } + } else if (index === 0) { + value = value.toLowerCase(); + if (horizontal.includes(value)) { + return value; + } + } else if (index === 2) { + value = value.toLowerCase(); + if (vertical.includes(value)) { + return `${position} ${value}`; + } + } + }, ''); + } + if (components.length > 2) { + return undefined; + } + + /** + *
+ * + *
+ *
+ *
+ * + * --- + * + *
+ * + * --- + *
+ * -> reverse + * + * -> reverse + */ + let [x, y = initial] = components; + let parsedX, parsedY; + x = x.toLowerCase(); + if (y) { + y = y.toLowerCase(); + } + if ( + (horizontal.includes(x) || x === initial || (parsedX = exports.parseLengthOrPercentage(x))) && + (vertical.includes(y) || y === initial) + ) { + return `${parsedX || x}${y ? ` ${y}` : ''}`; + } + if ( + (horizontal.includes(x) || x === initial || (parsedX = exports.parseLengthOrPercentage(x))) && + (parsedY = exports.parseLengthOrPercentage(y)) + ) { + return `${parsedX || x}${parsedY ? ` ${parsedY}` : ''}`; + } + if (vertical.includes(x) && (y === initial || horizontal.includes(y))) { + return `${y ? `${y} ` : ''}${x}`; + } +}; + /** * This function is used to split args from a CSS function that can have nested * functions which are sharing the same separator(s). diff --git a/lib/parsers.test.js b/lib/parsers.test.js index 399d1665..46e0ede2 100644 --- a/lib/parsers.test.js +++ b/lib/parsers.test.js @@ -70,26 +70,29 @@ describe('parseLength', () => { expect(parsers.parseLength('calc(1px + 1px)')).toBe('calc(2px)'); }); }); -describe('parsePercent', () => { +describe('parsePercentage', () => { it('returns undefined for invalid values', () => { const invalid = ['string', '1%%', '1px%', '#1%', 'calc(1 * 1px)']; - invalid.forEach(input => expect(parsers.parsePercent(input)).toBeUndefined()); + invalid.forEach(input => expect(parsers.parsePercentage(input)).toBeUndefined()); }); it('parses percent with exponent', () => { - expect(parsers.parsePercent('1e1%')).toBe('10%'); - expect(parsers.parsePercent('1e+1%')).toBe('10%'); - expect(parsers.parsePercent('1e-1%')).toBe('0.1%'); + expect(parsers.parsePercentage('1e1%')).toBe('10%'); + expect(parsers.parsePercentage('1e+1%')).toBe('10%'); + expect(parsers.parsePercentage('1e-1%')).toBe('0.1%'); }); it('parses percent with missing leading 0', () => { - expect(parsers.parsePercent('.1%')).toBe('0.1%'); + expect(parsers.parsePercentage('.1%')).toBe('0.1%'); }); it('returns percent without trailing 0 in decimals', () => { - expect(parsers.parsePercent('0.10%')).toBe('0.1%'); + expect(parsers.parsePercentage('0.10%')).toBe('0.1%'); }); it('works with calc', () => { - expect(parsers.parsePercent('calc(1% + 1%)')).toBe('calc(2%)'); + expect(parsers.parsePercentage('calc(1% + 1%)')).toBe('calc(2%)'); }); }); +describe('parseLengthOrPercentage', () => { + it.todo('test'); +}); describe('parseAlpha', () => { it('returns undefined for invalid values', () => { const invalid = ['string', '1%%', '1px%', '#1%', 'calc(1 * 1px)']; @@ -124,9 +127,6 @@ describe('parseAlpha', () => { expect(parsers.parseAlpha('calc(0.5 + 0.5)')).toBe('1'); }); }); -describe('parseMeasurement', () => { - it.todo('test'); -}); describe('parseAngle', () => { it('returns undefined for invalid values', () => { const invalid = ['string', '1', '1degg', 'a1deg', 'deg', 'calc(1 * 1px)']; @@ -251,12 +251,46 @@ describe('parseCustomIdentifier', () => { expect(parsers.parseCustomIdentifier('myCustomIdentifier')).toBe('myCustomIdentifier'); }); }); +describe('parseImage', () => { + it.todo('tests'); +}); describe('parseUrl', () => { it.todo('test'); }); describe('parseString', () => { it.todo('test'); }); +describe('parsePosition', () => { + it('returns undefined for invalid values', () => { + const invalid = [ + 'side', + '1', + 'left left', + 'top top', + 'left top center', + 'left top 50%', + '0% 0% 0%', + 'top 50%', + '50% left', + ]; + invalid.forEach(input => expect(parsers.parsePosition(input)).toBeUndefined()); + }); + it('resolves 0 as 0px', () => { + expect(parsers.parsePosition('0 0')).toBe('0px 0px'); + }); + it('resolves with center as a default value', () => { + expect(parsers.parsePosition('0%')).toBe('0% center'); + expect(parsers.parsePosition('left')).toBe('left center'); + expect(parsers.parsePosition('top')).toBe('center top'); + expect(parsers.parsePosition('center')).toBe('center center'); + }); + it('resolves with horizontal position first', () => { + expect(parsers.parsePosition('top left')).toBe('left top'); + }); + it('resolves with lowercased position', () => { + expect(parsers.parsePosition('LEFt 0%')).toBe('left 0%'); + }); +}); describe('parseColor', () => { it('returns undefined for invalid values', () => { const invalid = [ @@ -331,6 +365,175 @@ describe('parseColor', () => { ); }); }); +describe('parseGradient', () => { + it('returns undefined for invalid values', () => { + const invalid = [ + 'string', + '1', + 'invalid-gradient(red, cyan)', + 'invalid-conic-gradient(red, cyan)', + 'conic-gradient()', + 'conic-gradient( , red, cyan)', + 'conic-gradient(0, cyan)', + 'conic-gradient(from , red, cyan)', + 'conic-gradient(from 1, red, cyan)', + 'conic-gradient(from 90deg 120deg, red, cyan)', + 'conic-gradient(at , red, cyan)', + 'conic-gradient(at 1, red, cyan)', + 'conic-gradient(at left center center, red, cyan)', + 'conic-gradient(red, 0%, 0%, cyan)', + 'conic-gradient(cyan 0deg)', + 'linear-gradient(0, cyan)', + 'linear-gradient(1, red, cyan)', + 'linear-gradient(90deg 120deg, red, cyan)', + 'linear-gradient(at , red, cyan)', + 'linear-gradient(at 1, red, cyan)', + 'linear-gradient(at left center center, red, cyan)', + 'linear-gradient(red, 0%, 0%, cyan)', + 'linear-gradient(cyan 0%)', + 'radial-gradient(0, cyan)', + 'radial-gradient(1, red, cyan)', + 'radial-gradient(circle 50%, red, cyan)', + 'radial-gradient(circle 100px 120px, red, cyan)', + 'radial-gradient(ellipse 50%, red, cyan)', + 'radial-gradient(50% closest-corner, red, cyan)', + 'radial-gradient(closest-corner 50%, red, cyan)', + 'radial-gradient(at , red, cyan)', + 'radial-gradient(at 1, red, cyan)', + 'radial-gradient(at left center center, red, cyan)', + 'radial-gradient(red, 0%, 0%, cyan)', + 'radial-gradient(cyan 0%)', + ]; + invalid.forEach(input => expect(parsers.parseGradient(input)).toBeUndefined()); + }); + it('parses a conic gradient', () => { + [ + // [input, expected] + ['conic-gradient(red, cyan)', 'conic-gradient(red, cyan)'], + ['CONIC-gradient(red, cyan)', 'conic-gradient(red, cyan)'], + ['repeating-conic-gradient(red, cyan)', 'repeating-conic-gradient(red, cyan)'], + ['conic-gradient(from 0, red, cyan)', 'conic-gradient(from 0deg, red, cyan)'], + ['conic-gradient(from 2turn, red, cyan)', 'conic-gradient(from 2turn, red, cyan)'], + ['conic-gradient(at top, red, cyan)', 'conic-gradient(at center top, red, cyan)'], + ['conic-gradient(at left, red, cyan)', 'conic-gradient(at left center, red, cyan)'], + ['conic-gradient(at top left, red, cyan)', 'conic-gradient(at left top, red, cyan)'], + ['conic-gradient(at 0%, red, cyan)', 'conic-gradient(at 0% center, red, cyan)'], + ['conic-gradient(at -100% 200%, red, cyan)', 'conic-gradient(at -100% 200%, red, cyan)'], + [ + 'conic-gradient(from 0deg at left center, red, cyan)', + 'conic-gradient(from 0deg at left center, red, cyan)', + ], + ['conic-gradient(red, 50%, cyan)', 'conic-gradient(red, 50%, cyan)'], + ['conic-gradient(red 0 0, 0, cyan)', 'conic-gradient(red 0deg, red 0deg, 0deg, cyan)'], + [ + 'conic-gradient(red 0deg 1turn, 50%, cyan)', + 'conic-gradient(red 0deg, red 1turn, 50%, cyan)', + ], + [ + 'conic-gradient(red -1% 200%, 540deg, cyan)', + 'conic-gradient(red -1%, red 200%, 540deg, cyan)', + ], + ['conic-gradient(red 0deg 180deg)', 'conic-gradient(red 0deg, red 180deg)'], + ].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected)); + }); + it('parses a linear gradient', () => { + [ + // [input, expected] + ['linear-gradient(red, cyan)', 'linear-gradient(red, cyan)'], + ['repeating-linear-gradient(red, cyan)', 'repeating-linear-gradient(red, cyan)'], + ['linear-gradient(0, red, cyan)', 'linear-gradient(0deg, red, cyan)'], + ['linear-gradient(2turn, red, cyan)', 'linear-gradient(2turn, red, cyan)'], + ['linear-gradient(to top, red, cyan)', 'linear-gradient(to top, red, cyan)'], + ['linear-gradient(to left, red, cyan)', 'linear-gradient(to left, red, cyan)'], + ['linear-gradient(to top left, red, cyan)', 'linear-gradient(to left top, red, cyan)'], + ['linear-gradient(to bottom, red, cyan)', 'linear-gradient(red, cyan)'], + ['linear-gradient(to left bottom, red, cyan)', 'linear-gradient(to left bottom, red, cyan)'], + ['linear-gradient(to -100% 200%, red, cyan)', 'linear-gradient(to -100% 200%, red, cyan)'], + ['linear-gradient(red, 50%, cyan)', 'linear-gradient(red, 50%, cyan)'], + ['linear-gradient(red 0 0, 0, cyan)', 'linear-gradient(red 0px, red 0px, 0px, cyan)'], + [ + 'linear-gradient(red 0px 100%, 50px, cyan)', + 'linear-gradient(red 0px, red 100%, 50px, cyan)', + ], + [ + 'linear-gradient(red -1% 200px, 150%, cyan)', + 'linear-gradient(red -1%, red 200px, 150%, cyan)', + ], + ['linear-gradient(red 0% 50%)', 'linear-gradient(red 0%, red 50%)'], + ].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected)); + }); + it('parses a radial gradient', () => { + [ + // [input, expected] + ['radial-gradient(red, cyan)', 'radial-gradient(red, cyan)'], + ['repeating-radial-gradient(red, cyan)', 'repeating-radial-gradient(red, cyan)'], + ['radial-gradient(circle, red, cyan)', 'radial-gradient(circle, red, cyan)'], + ['radial-gradient(circle 0, red, cyan)', 'radial-gradient(0px, red, cyan)'], + ['radial-gradient(circle 50px, red, cyan)', 'radial-gradient(50px, red, cyan)'], + ['radial-gradient(50px circle, red, cyan)', 'radial-gradient(50px, red, cyan)'], + ['radial-gradient(circle farthest-corner, red, cyan)', 'radial-gradient(circle, red, cyan)'], + ['radial-gradient(farthest-corner circle, red, cyan)', 'radial-gradient(circle, red, cyan)'], + [ + 'radial-gradient(circle farthest-side, red, cyan)', + 'radial-gradient(circle farthest-side, red, cyan)', + ], + [ + 'radial-gradient(farthest-side circle, red, cyan)', + 'radial-gradient(circle farthest-side, red, cyan)', + ], + ['radial-gradient(ellipse, red, cyan)', 'radial-gradient(red, cyan)'], + ['radial-gradient(ellipse 0 120%, red, cyan)', 'radial-gradient(0px 120%, red, cyan)'], + ['radial-gradient(ellipse 80% 100%, red, cyan)', 'radial-gradient(80% 100%, red, cyan)'], + ['radial-gradient(80% 100% ellipse, red, cyan)', 'radial-gradient(80% 100%, red, cyan)'], + ['radial-gradient(ellipse farthest-corner, red, cyan)', 'radial-gradient(red, cyan)'], + ['radial-gradient(farthest-corner ellipse, red, cyan)', 'radial-gradient(red, cyan)'], + [ + 'radial-gradient(ellipse farthest-side, red, cyan)', + 'radial-gradient(farthest-side, red, cyan)', + ], + [ + 'radial-gradient(farthest-side ellipse, red, cyan)', + 'radial-gradient(farthest-side, red, cyan)', + ], + ['radial-gradient(at top, red, cyan)', 'radial-gradient(at center top, red, cyan)'], + ['radial-gradient(at left, red, cyan)', 'radial-gradient(at left center, red, cyan)'], + ['radial-gradient(at top left, red, cyan)', 'radial-gradient(at left top, red, cyan)'], + ['radial-gradient(at -100% 200%, red, cyan)', 'radial-gradient(at -100% 200%, red, cyan)'], + [ + 'radial-gradient(circle closest-side at center center, red, cyan)', + 'radial-gradient(circle closest-side at center center, red, cyan)', + ], + ['radial-gradient(red, 50%, cyan)', 'radial-gradient(red, 50%, cyan)'], + ['radial-gradient(red 0 0, 0, cyan)', 'radial-gradient(red 0px, red 0px, 0px, cyan)'], + [ + 'radial-gradient(red 0px 100%, 50px, cyan)', + 'radial-gradient(red 0px, red 100%, 50px, cyan)', + ], + [ + 'radial-gradient(red -1% 200px, 150%, cyan)', + 'radial-gradient(red -1%, red 200px, 150%, cyan)', + ], + ['radial-gradient(red 0% 50%)', 'radial-gradient(red 0%, red 50%)'], + ].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected)); + }); + it('works with calc', () => { + [ + // [input, expected] + [ + 'conic-gradient(from calc(90deg * 2) at calc(25% * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)', + 'conic-gradient(from calc(180deg) at calc(50%) center, red calc(10%), red calc(100%), calc(50%), cyan)', + ], + [ + 'linear-gradient(calc(90deg * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)', + 'linear-gradient(red calc(10%), red calc(100%), calc(50%), cyan)', + ], + [ + 'radial-gradient(calc(25px * 2) at calc(25% * 2), red calc(5% * 2) calc(50% * 2), calc(25% * 2), cyan)', + 'radial-gradient(calc(50px) at calc(50%) center, red calc(10%), red calc(100%), calc(50%), cyan)', + ], + ].forEach(([input, expected]) => expect(parsers.parseGradient(input)).toBe(expected)); + }); +}); describe('dashedToCamelCase', () => { it.todo('test'); }); diff --git a/lib/properties/backgroundImage.js b/lib/properties/backgroundImage.js index e882afb0..ae410402 100644 --- a/lib/properties/backgroundImage.js +++ b/lib/properties/backgroundImage.js @@ -1,9 +1,9 @@ 'use strict'; -const { parseKeyword, parseUrl } = require('../parsers'); +const { parseImage, parseKeyword } = require('../parsers'); function parse(v) { - return parseUrl(v) || parseKeyword(v, ['none']); + return parseImage(v) || parseKeyword(v, ['none']); } module.exports.isValid = function isValid(v) { diff --git a/lib/properties/backgroundPosition.js b/lib/properties/backgroundPosition.js index 669d1e82..1c50a9b6 100644 --- a/lib/properties/backgroundPosition.js +++ b/lib/properties/backgroundPosition.js @@ -1,6 +1,6 @@ 'use strict'; -const { parseKeyword, parseMeasurement } = require('../parsers'); +const { parseKeyword, parseLengthOrPercentage } = require('../parsers'); var valid_keywords = ['top', 'center', 'bottom', 'left', 'right']; @@ -13,9 +13,12 @@ var parse = function parse(v) { return undefined; } if (parts.length === 1) { - return parseMeasurement(parts[0]) || parseKeyword(parts[0], valid_keywords); + return parseLengthOrPercentage(parts[0]) || parseKeyword(parts[0], valid_keywords); } - if (parseMeasurement(parts[0]) !== undefined && parseMeasurement(parts[1]) !== undefined) { + if ( + parseLengthOrPercentage(parts[0]) !== undefined && + parseLengthOrPercentage(parts[1]) !== undefined + ) { return v; } if ( diff --git a/lib/properties/bottom.js b/lib/properties/bottom.js index e9d72b22..e695f2bf 100644 --- a/lib/properties/bottom.js +++ b/lib/properties/bottom.js @@ -1,10 +1,10 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; module.exports.definition = { set: function(v) { - this._setProperty('bottom', parseMeasurement(v)); + this._setProperty('bottom', parseLengthOrPercentage(v)); }, get: function() { return this.getPropertyValue('bottom'); diff --git a/lib/properties/clip.js b/lib/properties/clip.js index 0f90c33c..5179634d 100644 --- a/lib/properties/clip.js +++ b/lib/properties/clip.js @@ -1,6 +1,6 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; var shape_regex = /^rect\((.*)\)$/i; @@ -21,7 +21,7 @@ var parse = function(val) { return undefined; } var valid = parts.every(function(part, index) { - var measurement = parseMeasurement(part); + var measurement = parseLengthOrPercentage(part); parts[index] = measurement; return measurement !== undefined; }); diff --git a/lib/properties/flexBasis.js b/lib/properties/flexBasis.js index 0dec04bc..af7f0c55 100644 --- a/lib/properties/flexBasis.js +++ b/lib/properties/flexBasis.js @@ -1,6 +1,6 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; function parse(v) { if (v.toLowerCase() === 'auto') { @@ -9,7 +9,7 @@ function parse(v) { if (v.toLowerCase() === 'inherit') { return 'inherit'; } - return parseMeasurement(v); + return parseLengthOrPercentage(v); } module.exports.isValid = function isValid(v) { diff --git a/lib/properties/fontSize.js b/lib/properties/fontSize.js index b4822430..b616f492 100644 --- a/lib/properties/fontSize.js +++ b/lib/properties/fontSize.js @@ -1,13 +1,13 @@ 'use strict'; -const { parseKeyword, parseMeasurement } = require('../parsers'); +const { parseKeyword, parseLengthOrPercentage } = require('../parsers'); var absoluteSizes = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']; var relativeSizes = ['larger', 'smaller']; const allSizes = absoluteSizes.concat(relativeSizes); function parse(v) { - return parseKeyword(v, allSizes) || parseMeasurement(v); + return parseKeyword(v, allSizes) || parseLengthOrPercentage(v); } module.exports.isValid = function(v) { diff --git a/lib/properties/height.js b/lib/properties/height.js index d3f3ffcf..b0b95b3d 100644 --- a/lib/properties/height.js +++ b/lib/properties/height.js @@ -1,6 +1,6 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; function parse(v) { if (v.toLowerCase() === 'auto') { @@ -9,7 +9,7 @@ function parse(v) { if (v.toLowerCase() === 'inherit') { return 'inherit'; } - return parseMeasurement(v); + return parseLengthOrPercentage(v); } module.exports.definition = { diff --git a/lib/properties/left.js b/lib/properties/left.js index 72bb2faf..c60cd96a 100644 --- a/lib/properties/left.js +++ b/lib/properties/left.js @@ -1,10 +1,10 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; module.exports.definition = { set: function(v) { - this._setProperty('left', parseMeasurement(v)); + this._setProperty('left', parseLengthOrPercentage(v)); }, get: function() { return this.getPropertyValue('left'); diff --git a/lib/properties/lineHeight.js b/lib/properties/lineHeight.js index c3927095..bc2d47d1 100644 --- a/lib/properties/lineHeight.js +++ b/lib/properties/lineHeight.js @@ -1,12 +1,12 @@ 'use strict'; -const { parseKeyword, parseNumber, parseMeasurement } = require('../parsers'); +const { parseKeyword, parseNumber, parseLengthOrPercentage } = require('../parsers'); module.exports.isValid = function isValid(v) { return ( parseKeyword(v, ['normal']) !== undefined || parseNumber(v) !== undefined || - parseMeasurement(v) !== undefined + parseLengthOrPercentage(v) !== undefined ); }; diff --git a/lib/properties/margin.js b/lib/properties/margin.js index 1ff00354..b62e1788 100644 --- a/lib/properties/margin.js +++ b/lib/properties/margin.js @@ -1,12 +1,12 @@ 'use strict'; -const { implicitSetter, parseMeasurement, parseInteger } = require('../parsers'); +const { implicitSetter, parseLengthOrPercentage, parseInteger } = require('../parsers'); var isValid = function(v) { if (v.toLowerCase() === 'auto') { return true; } - return parseMeasurement(v) !== undefined || parseInteger(v) !== undefined; + return parseLengthOrPercentage(v) !== undefined || parseInteger(v) !== undefined; }; var parser = function(v) { @@ -14,7 +14,7 @@ var parser = function(v) { if (V === 'auto') { return V; } - return parseMeasurement(v); + return parseLengthOrPercentage(v); }; var mySetter = implicitSetter('margin', '', isValid, parser); diff --git a/lib/properties/padding.js b/lib/properties/padding.js index 683f82c7..f977ccbb 100644 --- a/lib/properties/padding.js +++ b/lib/properties/padding.js @@ -1,13 +1,13 @@ 'use strict'; -const { implicitSetter, parseMeasurement, parseInteger } = require('../parsers'); +const { implicitSetter, parseLengthOrPercentage, parseInteger } = require('../parsers'); var isValid = function(v) { - return parseMeasurement(v) !== undefined || parseInteger(v) !== undefined; + return parseLengthOrPercentage(v) !== undefined || parseInteger(v) !== undefined; }; var parser = function(v) { - return parseMeasurement(v); + return parseLengthOrPercentage(v); }; var mySetter = implicitSetter('padding', '', isValid, parser); diff --git a/lib/properties/right.js b/lib/properties/right.js index eb4c3d49..ff2f6af5 100644 --- a/lib/properties/right.js +++ b/lib/properties/right.js @@ -1,10 +1,10 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; module.exports.definition = { set: function(v) { - this._setProperty('right', parseMeasurement(v)); + this._setProperty('right', parseLengthOrPercentage(v)); }, get: function() { return this.getPropertyValue('right'); diff --git a/lib/properties/top.js b/lib/properties/top.js index f71986fe..859bb463 100644 --- a/lib/properties/top.js +++ b/lib/properties/top.js @@ -1,10 +1,10 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; module.exports.definition = { set: function(v) { - this._setProperty('top', parseMeasurement(v)); + this._setProperty('top', parseLengthOrPercentage(v)); }, get: function() { return this.getPropertyValue('top'); diff --git a/lib/properties/width.js b/lib/properties/width.js index e15c8230..5610b725 100644 --- a/lib/properties/width.js +++ b/lib/properties/width.js @@ -1,6 +1,6 @@ 'use strict'; -var parseMeasurement = require('../parsers').parseMeasurement; +var parseLengthOrPercentage = require('../parsers').parseLengthOrPercentage; function parse(v) { if (v.toLowerCase() === 'auto') { @@ -9,7 +9,7 @@ function parse(v) { if (v.toLowerCase() === 'inherit') { return 'inherit'; } - return parseMeasurement(v); + return parseLengthOrPercentage(v); } module.exports.definition = {