diff --git a/README.md b/README.md index 78850ab..5ad9890 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Converts CSS text to a React Native stylesheet object. +[Try it here](https://csstox.surge.sh) + ```css font-size: 18px; line-height: 24px; @@ -32,7 +34,7 @@ transform: translate(10px, 5px) scale(5); fontVariant: ['small-caps'], // Fixes backwards transform order transform: [ - { translateY: 10 }, + { translateY: 5 }, { translateX: 10 }, { scale: 5 }, ] @@ -84,7 +86,7 @@ transform([ ]); // => { fontFamily: 'Helvetica', ... } ``` -We don't provide a way to get these style tuples in this library, so you'll need to do that yourself. I expect most people will use postCSS or another CSS parser. You should try avoid getting these with `string.split`, as that has a lot of edge cases (colons and semi-colons apearing in comments etc.) +We don't provide a way to get these style tuples in this library, so you'll need to do that yourself. I expect most people will use postCSS or another CSS parser. You should try avoid getting these with `string.split`, as that has a lot of edge cases (colons and semi-colons appearing in comments etc.) For implementors, there is also a few extra APIs available. diff --git a/package.json b/package.json index 72af934..d26e37c 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "css-to-react-native", - "version": "3.0.0", + "version": "3.2.0", "description": "Convert CSS text to a React Native stylesheet object", "main": "index.js", + "types": "index.d.ts", "scripts": { "build": "rollup ./src/index.js -o index.js --f cjs && babel index.js -o index.js", "test": "jest", @@ -14,6 +15,7 @@ }, "files": [ "index.js", + "index.d.ts", "src" ], "repository": { diff --git a/src/__tests__/aspectRatio.js b/src/__tests__/aspectRatio.js new file mode 100644 index 0000000..eebd699 --- /dev/null +++ b/src/__tests__/aspectRatio.js @@ -0,0 +1,23 @@ +import transformCss from '..' + +it('handles regular aspect ratio values', () => { + expect(transformCss([['aspect-ratio', '1.5']])).toEqual({ + aspectRatio: 1.5, + }) +}) + +it('handles CSS-style aspect ratios', () => { + expect(transformCss([['aspect-ratio', '3 / 2']])).toEqual({ + aspectRatio: 1.5, + }) +}) + +it('handles CSS-style aspect ratios without spaces', () => { + expect(transformCss([['aspect-ratio', '3/2']])).toEqual({ + aspectRatio: 1.5, + }) +}) + +it('throws when omitting second value after slash', () => { + expect(() => transformCss([['aspect-ratio', '3/']])).toThrow() +}) diff --git a/src/__tests__/fontVariant.js b/src/__tests__/fontVariant.js index 21f4cb7..7749dfa 100644 --- a/src/__tests__/fontVariant.js +++ b/src/__tests__/fontVariant.js @@ -5,3 +5,11 @@ it('transforms font variant as an array', () => { fontVariant: ['tabular-nums'], }) }) + +it('transforms multiple font variant as an array', () => { + expect( + transformCss([['font-variant', 'tabular-nums oldstyle-nums']]) + ).toEqual({ + fontVariant: ['tabular-nums', 'oldstyle-nums'], + }) +}) diff --git a/src/__tests__/transform.js b/src/__tests__/transform.js index 71b8a79..6930546 100644 --- a/src/__tests__/transform.js +++ b/src/__tests__/transform.js @@ -12,6 +12,20 @@ it('transforms a single transform value with string', () => { }) }) +it('transforms a single transform value with percentage', () => { + expect(transformCss([['transform', 'translate(100%, 100%)']])).toEqual({ + transform: [{ translateY: '100%' }, { translateX: '100%' }], + }) +}) + +it('transforms multiple transform values with percentage', () => { + expect( + transformCss([['transform', 'translateY(100%) translateX(100%)']]) + ).toEqual({ + transform: [{ translateX: '100%' }, { translateY: '100%' }], + }) +}) + it('transforms multiple transform values', () => { expect(transformCss([['transform', 'scaleX(5) skewX(1deg)']])).toEqual({ transform: [{ skewX: '1deg' }, { scaleX: 5 }], diff --git a/src/tokenTypes.js b/src/tokenTypes.js index 497001d..33e5b89 100644 --- a/src/tokenTypes.js +++ b/src/tokenTypes.js @@ -35,7 +35,7 @@ const numberRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)$/i // Note lengthRe is sneaky: you can omit units for 0 const lengthRe = /^(0$|(?:[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?=px$))/i const unsupportedUnitRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(ch|em|ex|rem|vh|vw|vmin|vmax|cm|mm|in|pc|pt))$/i -const angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad))$/i +const angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad|grad|turn))$/i const percentRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?%)$/i const noopToken = predicate => node => (predicate(node) ? '' : null) diff --git a/src/transforms/aspectRatio.js b/src/transforms/aspectRatio.js new file mode 100644 index 0000000..67156e5 --- /dev/null +++ b/src/transforms/aspectRatio.js @@ -0,0 +1,12 @@ +import { NUMBER, SLASH } from '../tokenTypes' + +export default tokenStream => { + let aspectRatio = tokenStream.expect(NUMBER) + + if (tokenStream.hasTokens()) { + tokenStream.expect(SLASH) + aspectRatio /= tokenStream.expect(NUMBER) + } + + return { aspectRatio } +} diff --git a/src/transforms/fontVariant.js b/src/transforms/fontVariant.js new file mode 100644 index 0000000..244db42 --- /dev/null +++ b/src/transforms/fontVariant.js @@ -0,0 +1,14 @@ +import { SPACE, IDENT } from '../tokenTypes' + +export default tokenStream => { + const values = [tokenStream.expect(IDENT)] + + while (tokenStream.hasTokens()) { + tokenStream.expect(SPACE) + values.push(tokenStream.expect(IDENT)) + } + + return { + fontVariant: values, + } +} diff --git a/src/transforms/index.js b/src/transforms/index.js index 66320ae..8474556 100644 --- a/src/transforms/index.js +++ b/src/transforms/index.js @@ -1,18 +1,19 @@ import { - IDENT, - WORD, + AUTO, COLOR, LENGTH, - UNSUPPORTED_LENGTH_UNIT, PERCENT, - AUTO, + UNSUPPORTED_LENGTH_UNIT, + WORD, } from '../tokenTypes' +import aspectRatio from './aspectRatio' import border from './border' import boxShadow from './boxShadow' import flex from './flex' import flexFlow from './flexFlow' import font from './font' import fontFamily from './fontFamily' +import fontVariant from './fontVariant' import placeContent from './placeContent' import textDecoration from './textDecoration' import textDecorationLine from './textDecorationLine' @@ -39,9 +40,7 @@ const margin = directionFactory({ prefix: 'margin', }) const padding = directionFactory({ prefix: 'padding' }) -const fontVariant = tokenStream => ({ - fontVariant: [tokenStream.expect(IDENT)], -}) + const fontWeight = tokenStream => ({ fontWeight: tokenStream.expect(WORD), // Also match numbers as strings }) @@ -53,6 +52,7 @@ const textShadowOffset = tokenStream => ({ }) export default { + aspectRatio, background, border, borderColor, diff --git a/src/transforms/transform.js b/src/transforms/transform.js index b3c7bc3..c39719b 100644 --- a/src/transforms/transform.js +++ b/src/transforms/transform.js @@ -1,24 +1,24 @@ -import { SPACE, COMMA, LENGTH, NUMBER, ANGLE } from '../tokenTypes' +import { SPACE, COMMA, LENGTH, NUMBER, ANGLE, PERCENT } from '../tokenTypes' -const oneOfType = tokenType => functionStream => { - const value = functionStream.expect(tokenType) +const oneOfTypes = tokenTypes => functionStream => { + const value = functionStream.expect(...tokenTypes) functionStream.expectEmpty() return value } -const singleNumber = oneOfType(NUMBER) -const singleLength = oneOfType(LENGTH) -const singleAngle = oneOfType(ANGLE) -const xyTransformFactory = tokenType => ( +const singleNumber = oneOfTypes([NUMBER]) +const singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT]) +const singleAngle = oneOfTypes([ANGLE]) +const xyTransformFactory = tokenTypes => ( key, valueIfOmitted ) => functionStream => { - const x = functionStream.expect(tokenType) + const x = functionStream.expect(...tokenTypes) let y if (functionStream.hasTokens()) { functionStream.expect(COMMA) - y = functionStream.expect(tokenType) + y = functionStream.expect(...tokenTypes) } else if (valueIfOmitted !== undefined) { y = valueIfOmitted } else { @@ -31,18 +31,18 @@ const xyTransformFactory = tokenType => ( return [{ [`${key}Y`]: y }, { [`${key}X`]: x }] } -const xyNumber = xyTransformFactory(NUMBER) -const xyLength = xyTransformFactory(LENGTH) -const xyAngle = xyTransformFactory(ANGLE) +const xyNumber = xyTransformFactory([NUMBER]) +const xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT]) +const xyAngle = xyTransformFactory([ANGLE]) const partTransforms = { perspective: singleNumber, scale: xyNumber('scale'), scaleX: singleNumber, scaleY: singleNumber, - translate: xyLength('translate', 0), - translateX: singleLength, - translateY: singleLength, + translate: xyLengthOrPercent('translate', 0), + translateX: singleLengthOrPercent, + translateY: singleLengthOrPercent, rotate: singleAngle, rotateX: singleAngle, rotateY: singleAngle, diff --git a/yarn.lock b/yarn.lock index a671395..e2b1ca9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3471,9 +3471,9 @@ lodash.sortby@^4.7.0: integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.4, lodash@^4.3.0: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@^1.0.2: version "1.0.2" @@ -5330,9 +5330,9 @@ write@^0.2.1: mkdirp "^0.5.1" ws@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" - integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + version "5.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== dependencies: async-limiter "~1.0.0"