@cssxjs/css-to-react-native 3.2.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +115 -0
  3. package/index.d.ts +17 -0
  4. package/index.js +930 -0
  5. package/package.json +68 -0
  6. package/src/TokenStream.js +74 -0
  7. package/src/__tests__/aspectRatio.js +23 -0
  8. package/src/__tests__/border.js +141 -0
  9. package/src/__tests__/borderColor.js +92 -0
  10. package/src/__tests__/boxModel.js +136 -0
  11. package/src/__tests__/boxShadow.js +167 -0
  12. package/src/__tests__/colors.js +31 -0
  13. package/src/__tests__/flex.js +122 -0
  14. package/src/__tests__/flexFlow.js +22 -0
  15. package/src/__tests__/font.js +117 -0
  16. package/src/__tests__/fontFamily.js +43 -0
  17. package/src/__tests__/fontVariant.js +15 -0
  18. package/src/__tests__/fontWeight.js +8 -0
  19. package/src/__tests__/index.js +238 -0
  20. package/src/__tests__/placeContent.js +19 -0
  21. package/src/__tests__/shadowOffsets.js +13 -0
  22. package/src/__tests__/textDecoration.js +165 -0
  23. package/src/__tests__/textDecorationLine.js +23 -0
  24. package/src/__tests__/textShadow.js +107 -0
  25. package/src/__tests__/transform.js +69 -0
  26. package/src/__tests__/units.js +132 -0
  27. package/src/devPropertiesWithoutUnitsRegExp.js +19 -0
  28. package/src/index.js +90 -0
  29. package/src/tokenTypes.js +112 -0
  30. package/src/transforms/aspectRatio.js +12 -0
  31. package/src/transforms/border.js +57 -0
  32. package/src/transforms/boxShadow.js +11 -0
  33. package/src/transforms/flex.js +65 -0
  34. package/src/transforms/flexFlow.js +37 -0
  35. package/src/transforms/font.js +63 -0
  36. package/src/transforms/fontFamily.js +20 -0
  37. package/src/transforms/fontVariant.js +14 -0
  38. package/src/transforms/index.js +78 -0
  39. package/src/transforms/placeContent.js +24 -0
  40. package/src/transforms/textDecoration.js +56 -0
  41. package/src/transforms/textDecorationLine.js +18 -0
  42. package/src/transforms/textShadow.js +10 -0
  43. package/src/transforms/transform.js +74 -0
  44. package/src/transforms/util.js +103 -0
@@ -0,0 +1,112 @@
1
+ import { stringify } from 'postcss-value-parser'
2
+ import cssColorKeywords from 'css-color-keywords'
3
+
4
+ const matchString = node => {
5
+ if (node.type !== 'string') return null
6
+ return node.value
7
+ .replace(/\\([0-9a-f]{1,6})(?:\s|$)/gi, (match, charCode) =>
8
+ String.fromCharCode(parseInt(charCode, 16))
9
+ )
10
+ .replace(/\\/g, '')
11
+ }
12
+
13
+ const hexColorRe = /^(#(?:[0-9a-f]{3,4}){1,2})$/i
14
+ const cssFunctionNameRe = /^(rgba?|hsla?|hwb|lab|lch|gray|color)$/
15
+
16
+ const matchColor = node => {
17
+ if (
18
+ node.type === 'word' &&
19
+ (hexColorRe.test(node.value) ||
20
+ node.value in cssColorKeywords ||
21
+ node.value === 'transparent')
22
+ ) {
23
+ return node.value
24
+ } else if (node.type === 'function' && cssFunctionNameRe.test(node.value)) {
25
+ return stringify(node)
26
+ }
27
+ return null
28
+ }
29
+
30
+ const matchVariable = node => {
31
+ if (
32
+ (node.type !== 'function' && node.value !== 'var') ||
33
+ node.nodes.length === 0
34
+ )
35
+ return null
36
+
37
+ const variableName = node.nodes[0].value
38
+
39
+ if (node.nodes.length === 1) {
40
+ return `var(${variableName})`
41
+ }
42
+
43
+ const defaultValues = node.nodes
44
+ .slice(1)
45
+ .filter(subnode => subnode.type !== 'div')
46
+ .map(subnode => {
47
+ if (subnode.type === 'string') {
48
+ return `'${matchString(subnode)}'`
49
+ }
50
+ if (
51
+ subnode.type === 'function' &&
52
+ ['rgb', 'rgba', 'hls', 'hlsa'].includes(subnode.value)
53
+ ) {
54
+ return `${subnode.value}(${subnode.nodes.map(n => n.value).join('')})`
55
+ }
56
+ return subnode.value
57
+ })
58
+
59
+ if (defaultValues.length !== (node.nodes.length - 1) / 2) {
60
+ return null
61
+ }
62
+
63
+ return `var(${variableName}, ${defaultValues.join`, `})`
64
+ }
65
+
66
+ const noneRe = /^(none)$/i
67
+ const autoRe = /^(auto)$/i
68
+ const identRe = /(^-?[_a-z][_a-z0-9-]*$)/i
69
+ // Note if these are wrong, you'll need to change index.js too
70
+ const numberRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)$/i
71
+ // Note lengthRe is sneaky: you can omit units for 0
72
+ const lengthRe = /^(0$|(?:[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?=px$))/i
73
+ const unsupportedUnitRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(ch|em|ex|rem|vh|vw|vmin|vmax|cm|mm|in|pc|pt))$/i
74
+ const angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad|grad|turn))$/i
75
+ const percentRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?%)$/i
76
+
77
+ const noopToken = predicate => node => (predicate(node) ? '<token>' : null)
78
+
79
+ const valueForTypeToken = type => node =>
80
+ node.type === type ? node.value : null
81
+
82
+ export const regExpToken = (regExp, transform = String) => node => {
83
+ if (node.type !== 'word') return null
84
+
85
+ const match = node.value.match(regExp)
86
+ if (match === null) return null
87
+
88
+ const value = transform(match[1])
89
+
90
+ return value
91
+ }
92
+
93
+ export const SPACE = noopToken(node => node.type === 'space')
94
+ export const SLASH = noopToken(
95
+ node => node.type === 'div' && node.value === '/'
96
+ )
97
+ export const COMMA = noopToken(
98
+ node => node.type === 'div' && node.value === ','
99
+ )
100
+ export const WORD = valueForTypeToken('word')
101
+ export const NONE = regExpToken(noneRe)
102
+ export const AUTO = regExpToken(autoRe)
103
+ export const NUMBER = regExpToken(numberRe, Number)
104
+ export const LENGTH = regExpToken(lengthRe, Number)
105
+ export const UNSUPPORTED_LENGTH_UNIT = regExpToken(unsupportedUnitRe)
106
+ export const ANGLE = regExpToken(angleRe, angle => angle.toLowerCase())
107
+ export const PERCENT = regExpToken(percentRe)
108
+ export const IDENT = regExpToken(identRe)
109
+ export const STRING = matchString
110
+ export const COLOR = matchColor
111
+ export const LINE = regExpToken(/^(none|underline|line-through)$/i)
112
+ export const VARIABLE = matchVariable
@@ -0,0 +1,12 @@
1
+ import { NUMBER, SLASH } from '../tokenTypes'
2
+
3
+ export default tokenStream => {
4
+ let aspectRatio = tokenStream.expect(NUMBER)
5
+
6
+ if (tokenStream.hasTokens()) {
7
+ tokenStream.expect(SLASH)
8
+ aspectRatio /= tokenStream.expect(NUMBER)
9
+ }
10
+
11
+ return { aspectRatio }
12
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ regExpToken,
3
+ NONE,
4
+ COLOR,
5
+ LENGTH,
6
+ UNSUPPORTED_LENGTH_UNIT,
7
+ SPACE,
8
+ VARIABLE,
9
+ } from '../tokenTypes'
10
+
11
+ const BORDER_STYLE = regExpToken(/^(solid|dashed|dotted)$/)
12
+
13
+ const defaultBorderWidth = 1
14
+ const defaultBorderColor = 'black'
15
+ const defaultBorderStyle = 'solid'
16
+
17
+ export default tokenStream => {
18
+ let borderWidth
19
+ let borderColor
20
+ let borderStyle
21
+
22
+ if (tokenStream.matches(NONE)) {
23
+ tokenStream.expectEmpty()
24
+ return { borderWidth: 0, borderColor: 'black', borderStyle: 'solid' }
25
+ }
26
+
27
+ let partsParsed = 0
28
+ while (partsParsed < 3 && tokenStream.hasTokens()) {
29
+ if (partsParsed !== 0) tokenStream.expect(SPACE)
30
+
31
+ if (
32
+ borderWidth === undefined &&
33
+ tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)
34
+ ) {
35
+ borderWidth = tokenStream.lastValue
36
+ } else if (
37
+ borderColor === undefined &&
38
+ (tokenStream.matches(COLOR) || tokenStream.matches(VARIABLE))
39
+ ) {
40
+ borderColor = tokenStream.lastValue
41
+ } else if (borderStyle === undefined && tokenStream.matches(BORDER_STYLE)) {
42
+ borderStyle = tokenStream.lastValue
43
+ } else {
44
+ tokenStream.throw()
45
+ }
46
+
47
+ partsParsed += 1
48
+ }
49
+
50
+ tokenStream.expectEmpty()
51
+
52
+ if (borderWidth === undefined) borderWidth = defaultBorderWidth
53
+ if (borderColor === undefined) borderColor = defaultBorderColor
54
+ if (borderStyle === undefined) borderStyle = defaultBorderStyle
55
+
56
+ return { borderWidth, borderColor, borderStyle }
57
+ }
@@ -0,0 +1,11 @@
1
+ import { parseShadow } from './util'
2
+
3
+ export default tokenStream => {
4
+ const { offset, radius, color } = parseShadow(tokenStream)
5
+ return {
6
+ shadowOffset: offset,
7
+ shadowRadius: radius,
8
+ shadowColor: color,
9
+ shadowOpacity: 1,
10
+ }
11
+ }
@@ -0,0 +1,65 @@
1
+ import {
2
+ NONE,
3
+ AUTO,
4
+ NUMBER,
5
+ LENGTH,
6
+ UNSUPPORTED_LENGTH_UNIT,
7
+ PERCENT,
8
+ SPACE,
9
+ } from '../tokenTypes'
10
+
11
+ const defaultFlexGrow = 1
12
+ const defaultFlexShrink = 1
13
+ const defaultFlexBasis = 0
14
+
15
+ export default tokenStream => {
16
+ let flexGrow
17
+ let flexShrink
18
+ let flexBasis
19
+
20
+ if (tokenStream.matches(NONE)) {
21
+ tokenStream.expectEmpty()
22
+ return { flexGrow: 0, flexShrink: 0, flexBasis: 'auto' }
23
+ }
24
+
25
+ tokenStream.saveRewindPoint()
26
+ if (tokenStream.matches(AUTO) && !tokenStream.hasTokens()) {
27
+ return { flexGrow: 1, flexShrink: 1, flexBasis: 'auto' }
28
+ }
29
+ tokenStream.rewind()
30
+
31
+ let partsParsed = 0
32
+ while (partsParsed < 2 && tokenStream.hasTokens()) {
33
+ if (partsParsed !== 0) tokenStream.expect(SPACE)
34
+
35
+ if (flexGrow === undefined && tokenStream.matches(NUMBER)) {
36
+ flexGrow = tokenStream.lastValue
37
+
38
+ tokenStream.saveRewindPoint()
39
+ if (tokenStream.matches(SPACE) && tokenStream.matches(NUMBER)) {
40
+ flexShrink = tokenStream.lastValue
41
+ } else {
42
+ tokenStream.rewind()
43
+ }
44
+ } else if (
45
+ flexBasis === undefined &&
46
+ tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT)
47
+ ) {
48
+ flexBasis = tokenStream.lastValue
49
+ } else if (flexBasis === undefined && tokenStream.matches(AUTO)) {
50
+ flexBasis = 'auto'
51
+ } else {
52
+ tokenStream.throw()
53
+ }
54
+
55
+ partsParsed += 1
56
+ }
57
+
58
+ tokenStream.expectEmpty()
59
+
60
+ if (flexGrow === undefined) flexGrow = defaultFlexGrow
61
+ if (flexShrink === undefined) flexShrink = defaultFlexShrink
62
+ if (flexBasis === undefined) flexBasis = defaultFlexBasis
63
+
64
+ return { flexGrow, flexShrink, flexBasis }
65
+ }
@@ -0,0 +1,37 @@
1
+ import { regExpToken, SPACE } from '../tokenTypes'
2
+
3
+ const FLEX_WRAP = regExpToken(/(nowrap|wrap|wrap-reverse)/)
4
+ const FLEX_DIRECTION = regExpToken(/(row|row-reverse|column|column-reverse)/)
5
+
6
+ const defaultFlexWrap = 'nowrap'
7
+ const defaultFlexDirection = 'row'
8
+
9
+ export default tokenStream => {
10
+ let flexWrap
11
+ let flexDirection
12
+
13
+ let partsParsed = 0
14
+ while (partsParsed < 2 && tokenStream.hasTokens()) {
15
+ if (partsParsed !== 0) tokenStream.expect(SPACE)
16
+
17
+ if (flexWrap === undefined && tokenStream.matches(FLEX_WRAP)) {
18
+ flexWrap = tokenStream.lastValue
19
+ } else if (
20
+ flexDirection === undefined &&
21
+ tokenStream.matches(FLEX_DIRECTION)
22
+ ) {
23
+ flexDirection = tokenStream.lastValue
24
+ } else {
25
+ tokenStream.throw()
26
+ }
27
+
28
+ partsParsed += 1
29
+ }
30
+
31
+ tokenStream.expectEmpty()
32
+
33
+ if (flexWrap === undefined) flexWrap = defaultFlexWrap
34
+ if (flexDirection === undefined) flexDirection = defaultFlexDirection
35
+
36
+ return { flexWrap, flexDirection }
37
+ }
@@ -0,0 +1,63 @@
1
+ import parseFontFamily from './fontFamily'
2
+ import {
3
+ regExpToken,
4
+ SPACE,
5
+ LENGTH,
6
+ UNSUPPORTED_LENGTH_UNIT,
7
+ SLASH,
8
+ } from '../tokenTypes'
9
+
10
+ const NORMAL = regExpToken(/^(normal)$/)
11
+ const STYLE = regExpToken(/^(italic)$/)
12
+ const WEIGHT = regExpToken(/^([1-9]00|bold)$/)
13
+ const VARIANT = regExpToken(/^(small-caps)$/)
14
+
15
+ const defaultFontStyle = 'normal'
16
+ const defaultFontWeight = 'normal'
17
+ const defaultFontVariant = []
18
+
19
+ export default tokenStream => {
20
+ let fontStyle
21
+ let fontWeight
22
+ let fontVariant
23
+ // let fontSize;
24
+ let lineHeight
25
+ // let fontFamily;
26
+
27
+ let numStyleWeightVariantMatched = 0
28
+ while (numStyleWeightVariantMatched < 3 && tokenStream.hasTokens()) {
29
+ if (tokenStream.matches(NORMAL)) {
30
+ /* pass */
31
+ } else if (fontStyle === undefined && tokenStream.matches(STYLE)) {
32
+ fontStyle = tokenStream.lastValue
33
+ } else if (fontWeight === undefined && tokenStream.matches(WEIGHT)) {
34
+ fontWeight = tokenStream.lastValue
35
+ } else if (fontVariant === undefined && tokenStream.matches(VARIANT)) {
36
+ fontVariant = [tokenStream.lastValue]
37
+ } else {
38
+ break
39
+ }
40
+
41
+ tokenStream.expect(SPACE)
42
+ numStyleWeightVariantMatched += 1
43
+ }
44
+
45
+ const fontSize = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT)
46
+
47
+ if (tokenStream.matches(SLASH)) {
48
+ lineHeight = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT)
49
+ }
50
+
51
+ tokenStream.expect(SPACE)
52
+
53
+ const { fontFamily } = parseFontFamily(tokenStream)
54
+
55
+ if (fontStyle === undefined) fontStyle = defaultFontStyle
56
+ if (fontWeight === undefined) fontWeight = defaultFontWeight
57
+ if (fontVariant === undefined) fontVariant = defaultFontVariant
58
+
59
+ const out = { fontStyle, fontWeight, fontVariant, fontSize, fontFamily }
60
+ if (lineHeight !== undefined) out.lineHeight = lineHeight
61
+
62
+ return out
63
+ }
@@ -0,0 +1,20 @@
1
+ import { SPACE, IDENT, STRING } from '../tokenTypes'
2
+
3
+ export default tokenStream => {
4
+ let fontFamily
5
+
6
+ if (tokenStream.matches(STRING)) {
7
+ fontFamily = tokenStream.lastValue
8
+ } else {
9
+ fontFamily = tokenStream.expect(IDENT)
10
+ while (tokenStream.hasTokens()) {
11
+ tokenStream.expect(SPACE)
12
+ const nextIdent = tokenStream.expect(IDENT)
13
+ fontFamily += ` ${nextIdent}`
14
+ }
15
+ }
16
+
17
+ tokenStream.expectEmpty()
18
+
19
+ return { fontFamily }
20
+ }
@@ -0,0 +1,14 @@
1
+ import { SPACE, IDENT } from '../tokenTypes'
2
+
3
+ export default tokenStream => {
4
+ const values = [tokenStream.expect(IDENT)]
5
+
6
+ while (tokenStream.hasTokens()) {
7
+ tokenStream.expect(SPACE)
8
+ values.push(tokenStream.expect(IDENT))
9
+ }
10
+
11
+ return {
12
+ fontVariant: values,
13
+ }
14
+ }
@@ -0,0 +1,78 @@
1
+ import {
2
+ AUTO,
3
+ COLOR,
4
+ LENGTH,
5
+ PERCENT,
6
+ UNSUPPORTED_LENGTH_UNIT,
7
+ WORD,
8
+ VARIABLE,
9
+ } from '../tokenTypes'
10
+ import aspectRatio from './aspectRatio'
11
+ import border from './border'
12
+ import boxShadow from './boxShadow'
13
+ import flex from './flex'
14
+ import flexFlow from './flexFlow'
15
+ import font from './font'
16
+ import fontFamily from './fontFamily'
17
+ import fontVariant from './fontVariant'
18
+ import placeContent from './placeContent'
19
+ import textDecoration from './textDecoration'
20
+ import textDecorationLine from './textDecorationLine'
21
+ import textShadow from './textShadow'
22
+ import transform from './transform'
23
+ import { directionFactory, parseShadowOffset } from './util'
24
+
25
+ const background = tokenStream => ({
26
+ backgroundColor: tokenStream.expect(COLOR, VARIABLE),
27
+ })
28
+ const borderColor = directionFactory({
29
+ types: [COLOR, VARIABLE],
30
+ prefix: 'border',
31
+ suffix: 'Color',
32
+ })
33
+ const borderRadius = directionFactory({
34
+ directions: ['TopLeft', 'TopRight', 'BottomRight', 'BottomLeft'],
35
+ prefix: 'border',
36
+ suffix: 'Radius',
37
+ })
38
+ const borderWidth = directionFactory({ prefix: 'border', suffix: 'Width' })
39
+ const margin = directionFactory({
40
+ types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO],
41
+ prefix: 'margin',
42
+ })
43
+ const padding = directionFactory({ prefix: 'padding' })
44
+
45
+ const fontWeight = tokenStream => ({
46
+ fontWeight: tokenStream.expect(WORD), // Also match numbers as strings
47
+ })
48
+ const shadowOffset = tokenStream => ({
49
+ shadowOffset: parseShadowOffset(tokenStream),
50
+ })
51
+ const textShadowOffset = tokenStream => ({
52
+ textShadowOffset: parseShadowOffset(tokenStream),
53
+ })
54
+
55
+ export default {
56
+ aspectRatio,
57
+ background,
58
+ border,
59
+ borderColor,
60
+ borderRadius,
61
+ borderWidth,
62
+ boxShadow,
63
+ flex,
64
+ flexFlow,
65
+ font,
66
+ fontFamily,
67
+ fontVariant,
68
+ fontWeight,
69
+ margin,
70
+ padding,
71
+ placeContent,
72
+ shadowOffset,
73
+ textShadow,
74
+ textShadowOffset,
75
+ textDecoration,
76
+ textDecorationLine,
77
+ transform,
78
+ }
@@ -0,0 +1,24 @@
1
+ import { regExpToken, SPACE } from '../tokenTypes'
2
+
3
+ const ALIGN_CONTENT = regExpToken(
4
+ /(flex-(?:start|end)|center|stretch|space-(?:between|around))/
5
+ )
6
+ const JUSTIFY_CONTENT = regExpToken(
7
+ /(flex-(?:start|end)|center|space-(?:between|around|evenly))/
8
+ )
9
+
10
+ export default tokenStream => {
11
+ const alignContent = tokenStream.expect(ALIGN_CONTENT)
12
+
13
+ let justifyContent
14
+ if (tokenStream.hasTokens()) {
15
+ tokenStream.expect(SPACE)
16
+ justifyContent = tokenStream.expect(JUSTIFY_CONTENT)
17
+ } else {
18
+ justifyContent = 'stretch'
19
+ }
20
+
21
+ tokenStream.expectEmpty()
22
+
23
+ return { alignContent, justifyContent }
24
+ }
@@ -0,0 +1,56 @@
1
+ import { regExpToken, SPACE, LINE, COLOR, VARIABLE } from '../tokenTypes'
2
+
3
+ const STYLE = regExpToken(/^(solid|double|dotted|dashed)$/)
4
+
5
+ const defaultTextDecorationLine = 'none'
6
+ const defaultTextDecorationStyle = 'solid'
7
+ const defaultTextDecorationColor = 'black'
8
+
9
+ export default tokenStream => {
10
+ let line
11
+ let style
12
+ let color
13
+
14
+ let didParseFirst = false
15
+ while (tokenStream.hasTokens()) {
16
+ if (didParseFirst) tokenStream.expect(SPACE)
17
+
18
+ if (line === undefined && tokenStream.matches(LINE)) {
19
+ const lines = [tokenStream.lastValue.toLowerCase()]
20
+
21
+ tokenStream.saveRewindPoint()
22
+ if (
23
+ lines[0] !== 'none' &&
24
+ tokenStream.matches(SPACE) &&
25
+ tokenStream.matches(LINE)
26
+ ) {
27
+ lines.push(tokenStream.lastValue.toLowerCase())
28
+ // Underline comes before line-through
29
+ lines.sort().reverse()
30
+ } else {
31
+ tokenStream.rewind()
32
+ }
33
+
34
+ line = lines.join(' ')
35
+ } else if (style === undefined && tokenStream.matches(STYLE)) {
36
+ style = tokenStream.lastValue
37
+ } else if (
38
+ color === undefined &&
39
+ (tokenStream.matches(COLOR) || tokenStream.matches(VARIABLE))
40
+ ) {
41
+ color = tokenStream.lastValue
42
+ } else {
43
+ tokenStream.throw()
44
+ }
45
+
46
+ didParseFirst = true
47
+ }
48
+
49
+ return {
50
+ textDecorationLine: line !== undefined ? line : defaultTextDecorationLine,
51
+ textDecorationColor:
52
+ color !== undefined ? color : defaultTextDecorationColor,
53
+ textDecorationStyle:
54
+ style !== undefined ? style : defaultTextDecorationStyle,
55
+ }
56
+ }
@@ -0,0 +1,18 @@
1
+ import { SPACE, LINE } from '../tokenTypes'
2
+
3
+ export default tokenStream => {
4
+ const lines = []
5
+
6
+ let didParseFirst = false
7
+ while (tokenStream.hasTokens()) {
8
+ if (didParseFirst) tokenStream.expect(SPACE)
9
+
10
+ lines.push(tokenStream.expect(LINE).toLowerCase())
11
+
12
+ didParseFirst = true
13
+ }
14
+
15
+ lines.sort().reverse()
16
+
17
+ return { textDecorationLine: lines.join(' ') }
18
+ }
@@ -0,0 +1,10 @@
1
+ import { parseShadow } from './util'
2
+
3
+ export default tokenStream => {
4
+ const { offset, radius, color } = parseShadow(tokenStream)
5
+ return {
6
+ textShadowOffset: offset,
7
+ textShadowRadius: radius,
8
+ textShadowColor: color,
9
+ }
10
+ }
@@ -0,0 +1,74 @@
1
+ import { SPACE, COMMA, LENGTH, NUMBER, ANGLE, PERCENT } from '../tokenTypes'
2
+
3
+ const oneOfTypes = tokenTypes => functionStream => {
4
+ const value = functionStream.expect(...tokenTypes)
5
+ functionStream.expectEmpty()
6
+ return value
7
+ }
8
+
9
+ const singleNumber = oneOfTypes([NUMBER])
10
+ const singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT])
11
+ const singleAngle = oneOfTypes([ANGLE])
12
+ const xyTransformFactory = tokenTypes => (
13
+ key,
14
+ valueIfOmitted
15
+ ) => functionStream => {
16
+ const x = functionStream.expect(...tokenTypes)
17
+
18
+ let y
19
+ if (functionStream.hasTokens()) {
20
+ functionStream.expect(COMMA)
21
+ y = functionStream.expect(...tokenTypes)
22
+ } else if (valueIfOmitted !== undefined) {
23
+ y = valueIfOmitted
24
+ } else {
25
+ // Assumption, if x === y, then we can omit XY
26
+ // I.e. scale(5) => [{ scale: 5 }] rather than [{ scaleX: 5 }, { scaleY: 5 }]
27
+ return x
28
+ }
29
+
30
+ functionStream.expectEmpty()
31
+
32
+ return [{ [`${key}Y`]: y }, { [`${key}X`]: x }]
33
+ }
34
+ const xyNumber = xyTransformFactory([NUMBER])
35
+ const xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT])
36
+ const xyAngle = xyTransformFactory([ANGLE])
37
+
38
+ const partTransforms = {
39
+ perspective: singleNumber,
40
+ scale: xyNumber('scale'),
41
+ scaleX: singleNumber,
42
+ scaleY: singleNumber,
43
+ translate: xyLengthOrPercent('translate', 0),
44
+ translateX: singleLengthOrPercent,
45
+ translateY: singleLengthOrPercent,
46
+ rotate: singleAngle,
47
+ rotateX: singleAngle,
48
+ rotateY: singleAngle,
49
+ rotateZ: singleAngle,
50
+ skewX: singleAngle,
51
+ skewY: singleAngle,
52
+ skew: xyAngle('skew', '0deg'),
53
+ }
54
+
55
+ export default tokenStream => {
56
+ let transforms = []
57
+
58
+ let didParseFirst = false
59
+ while (tokenStream.hasTokens()) {
60
+ if (didParseFirst) tokenStream.expect(SPACE)
61
+
62
+ const functionStream = tokenStream.expectFunction()
63
+ const { functionName } = functionStream
64
+ let transformedValues = partTransforms[functionName](functionStream)
65
+ if (!Array.isArray(transformedValues)) {
66
+ transformedValues = [{ [functionName]: transformedValues }]
67
+ }
68
+ transforms = transformedValues.concat(transforms)
69
+
70
+ didParseFirst = true
71
+ }
72
+
73
+ return { transform: transforms }
74
+ }