@cssxjs/css-to-react-native 3.2.0-0 → 3.2.0-2
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.
- package/index.js +835 -287
- package/package.json +1 -1
- package/src/__tests__/animation.js +417 -0
- package/src/__tests__/boxModel.js +6 -1
- package/src/__tests__/boxShadow.js +31 -74
- package/src/__tests__/index.js +10 -2
- package/src/__tests__/textShadow.js +23 -0
- package/src/__tests__/transition.js +186 -0
- package/src/__tests__/units.js +1 -4
- package/src/__tests__/varComplex.js +385 -0
- package/src/index.js +135 -7
- package/src/tokenTypes.js +23 -1
- package/src/transforms/animation.js +338 -0
- package/src/transforms/boxShadow.js +5 -6
- package/src/transforms/index.js +31 -1
- package/src/transforms/transform.js +15 -7
- package/src/transforms/transition.js +240 -0
- package/src/transforms/util.js +24 -2
package/src/index.js
CHANGED
|
@@ -78,13 +78,141 @@ export const getPropertyName = propName => {
|
|
|
78
78
|
return camelizeStyleName(propName)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Strip CSS comments from a string
|
|
83
|
+
* Handles both single-line and multi-line comments
|
|
84
|
+
*/
|
|
85
|
+
const stripCssComments = css => css.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parse CSS declarations into React Native styles (for keyframes)
|
|
89
|
+
*/
|
|
90
|
+
const parseKeyframeDeclarations = declarationsStr => {
|
|
91
|
+
const declarations = []
|
|
92
|
+
const parts = declarationsStr.split(';')
|
|
93
|
+
|
|
94
|
+
for (const part of parts) {
|
|
95
|
+
const trimmed = part.trim()
|
|
96
|
+
if (!trimmed) continue
|
|
97
|
+
|
|
98
|
+
const colonIndex = trimmed.indexOf(':')
|
|
99
|
+
if (colonIndex === -1) continue
|
|
100
|
+
|
|
101
|
+
const property = trimmed.substring(0, colonIndex).trim()
|
|
102
|
+
const value = trimmed.substring(colonIndex + 1).trim()
|
|
103
|
+
|
|
104
|
+
if (property && value) {
|
|
105
|
+
declarations.push([property, value])
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (declarations.length === 0) {
|
|
110
|
+
return {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Transform each declaration
|
|
114
|
+
return declarations.reduce((accum, rule) => {
|
|
83
115
|
const propertyName = getPropertyName(rule[0])
|
|
84
116
|
const value = rule[1]
|
|
85
|
-
|
|
86
|
-
return Object.assign(
|
|
87
|
-
accum,
|
|
88
|
-
getStylesForProperty(propertyName, value, allowShorthand)
|
|
89
|
-
)
|
|
117
|
+
return Object.assign(accum, getStylesForProperty(propertyName, value, true))
|
|
90
118
|
}, {})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse keyframe body CSS into a keyframe object
|
|
123
|
+
* @param {string} body - CSS keyframe body
|
|
124
|
+
* @returns {Object} Keyframe object with selectors as keys
|
|
125
|
+
*/
|
|
126
|
+
const parseKeyframeBody = body => {
|
|
127
|
+
// Strip CSS comments before parsing
|
|
128
|
+
const cleanBody = stripCssComments(body)
|
|
129
|
+
|
|
130
|
+
const keyframeObject = {}
|
|
131
|
+
const selectorRegex = /([a-zA-Z0-9%,\s]+)\s*\{\s*([^}]*)\s*\}/g
|
|
132
|
+
let selectorMatch
|
|
133
|
+
|
|
134
|
+
// eslint-disable-next-line no-cond-assign
|
|
135
|
+
while ((selectorMatch = selectorRegex.exec(cleanBody)) !== null) {
|
|
136
|
+
const selectors = selectorMatch[1]
|
|
137
|
+
.split(',')
|
|
138
|
+
.map(s => s.trim())
|
|
139
|
+
.filter(s => s)
|
|
140
|
+
const declarations = selectorMatch[2]
|
|
141
|
+
|
|
142
|
+
// Parse CSS declarations into style object
|
|
143
|
+
const styles = parseKeyframeDeclarations(declarations)
|
|
144
|
+
|
|
145
|
+
for (const selector of selectors) {
|
|
146
|
+
keyframeObject[selector] = styles
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return keyframeObject
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if a property name is a @keyframes rule
|
|
155
|
+
*/
|
|
156
|
+
const isKeyframesRule = propName =>
|
|
157
|
+
propName.startsWith('@keyframes ') || propName.startsWith('@keyframes\t')
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Extract keyframe name from @keyframes rule
|
|
161
|
+
*/
|
|
162
|
+
const getKeyframeName = propName =>
|
|
163
|
+
propName.replace(/^@keyframes\s+/, '').trim()
|
|
164
|
+
|
|
165
|
+
export default (rules, shorthandBlacklist = []) => {
|
|
166
|
+
// First pass: collect @keyframes definitions
|
|
167
|
+
const keyframesMap = {}
|
|
168
|
+
|
|
169
|
+
for (const rule of rules) {
|
|
170
|
+
const propName = rule[0]
|
|
171
|
+
if (isKeyframesRule(propName)) {
|
|
172
|
+
const keyframeName = getKeyframeName(propName)
|
|
173
|
+
const keyframeBody = rule[1]
|
|
174
|
+
keyframesMap[keyframeName] = parseKeyframeBody(keyframeBody)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Second pass: transform all non-keyframes rules
|
|
179
|
+
const result = {}
|
|
180
|
+
|
|
181
|
+
for (const rule of rules) {
|
|
182
|
+
const propName = rule[0]
|
|
183
|
+
|
|
184
|
+
// Skip @keyframes rules in the output
|
|
185
|
+
if (isKeyframesRule(propName)) {
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const propertyName = getPropertyName(propName)
|
|
190
|
+
const value = rule[1]
|
|
191
|
+
const allowShorthand = shorthandBlacklist.indexOf(propertyName) === -1
|
|
192
|
+
const propValues = getStylesForProperty(propertyName, value, allowShorthand)
|
|
193
|
+
|
|
194
|
+
Object.assign(result, propValues)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Third pass: replace animationName strings with actual keyframe objects
|
|
198
|
+
if (result.animationName) {
|
|
199
|
+
if (Array.isArray(result.animationName)) {
|
|
200
|
+
result.animationName = result.animationName.map(name => {
|
|
201
|
+
if (typeof name === 'string' && name !== 'none' && keyframesMap[name]) {
|
|
202
|
+
return keyframesMap[name]
|
|
203
|
+
}
|
|
204
|
+
return name
|
|
205
|
+
})
|
|
206
|
+
} else if (typeof result.animationName === 'string') {
|
|
207
|
+
// Handle single value case
|
|
208
|
+
if (
|
|
209
|
+
result.animationName !== 'none' &&
|
|
210
|
+
keyframesMap[result.animationName]
|
|
211
|
+
) {
|
|
212
|
+
result.animationName = keyframesMap[result.animationName]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
}
|
package/src/tokenTypes.js
CHANGED
|
@@ -29,7 +29,8 @@ const matchColor = node => {
|
|
|
29
29
|
|
|
30
30
|
const matchVariable = node => {
|
|
31
31
|
if (
|
|
32
|
-
|
|
32
|
+
node.type !== 'function' ||
|
|
33
|
+
node.value !== 'var' ||
|
|
33
34
|
node.nodes.length === 0
|
|
34
35
|
)
|
|
35
36
|
return null
|
|
@@ -73,6 +74,7 @@ const lengthRe = /^(0$|(?:[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?=px$))/i
|
|
|
73
74
|
const unsupportedUnitRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(ch|em|ex|rem|vh|vw|vmin|vmax|cm|mm|in|pc|pt))$/i
|
|
74
75
|
const angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad|grad|turn))$/i
|
|
75
76
|
const percentRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?%)$/i
|
|
77
|
+
const timeRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:ms|s))$/i
|
|
76
78
|
|
|
77
79
|
const noopToken = predicate => node => (predicate(node) ? '<token>' : null)
|
|
78
80
|
|
|
@@ -105,8 +107,28 @@ export const LENGTH = regExpToken(lengthRe, Number)
|
|
|
105
107
|
export const UNSUPPORTED_LENGTH_UNIT = regExpToken(unsupportedUnitRe)
|
|
106
108
|
export const ANGLE = regExpToken(angleRe, angle => angle.toLowerCase())
|
|
107
109
|
export const PERCENT = regExpToken(percentRe)
|
|
110
|
+
export const TIME = regExpToken(timeRe)
|
|
108
111
|
export const IDENT = regExpToken(identRe)
|
|
109
112
|
export const STRING = matchString
|
|
110
113
|
export const COLOR = matchColor
|
|
111
114
|
export const LINE = regExpToken(/^(none|underline|line-through)$/i)
|
|
112
115
|
export const VARIABLE = matchVariable
|
|
116
|
+
|
|
117
|
+
// Animation/Transition timing functions (keywords)
|
|
118
|
+
export const TIMING_FUNCTION = regExpToken(
|
|
119
|
+
/^(ease|linear|ease-in|ease-out|ease-in-out|step-start|step-end)$/i
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Animation iteration count
|
|
123
|
+
export const ITERATION_COUNT = regExpToken(/^(infinite)$/i)
|
|
124
|
+
|
|
125
|
+
// Animation direction
|
|
126
|
+
export const ANIMATION_DIRECTION = regExpToken(
|
|
127
|
+
/^(normal|reverse|alternate|alternate-reverse)$/i
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Animation fill mode
|
|
131
|
+
export const FILL_MODE = regExpToken(/^(none|forwards|backwards|both)$/i)
|
|
132
|
+
|
|
133
|
+
// Animation play state
|
|
134
|
+
export const PLAY_STATE = regExpToken(/^(running|paused)$/i)
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { SPACE, COMMA, IDENT, TIME, NUMBER, NONE, VARIABLE } from '../tokenTypes'
|
|
2
|
+
|
|
3
|
+
// Timing function keywords
|
|
4
|
+
const timingFunctionKeywords = [
|
|
5
|
+
'ease',
|
|
6
|
+
'linear',
|
|
7
|
+
'ease-in',
|
|
8
|
+
'ease-out',
|
|
9
|
+
'ease-in-out',
|
|
10
|
+
'step-start',
|
|
11
|
+
'step-end',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
// Direction keywords
|
|
15
|
+
const directionKeywords = [
|
|
16
|
+
'normal',
|
|
17
|
+
'reverse',
|
|
18
|
+
'alternate',
|
|
19
|
+
'alternate-reverse',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
// Fill mode keywords
|
|
23
|
+
const fillModeKeywords = ['none', 'forwards', 'backwards', 'both']
|
|
24
|
+
|
|
25
|
+
// Play state keywords
|
|
26
|
+
const playStateKeywords = ['running', 'paused']
|
|
27
|
+
|
|
28
|
+
const isTimingFunction = value =>
|
|
29
|
+
timingFunctionKeywords.includes(value.toLowerCase())
|
|
30
|
+
const isDirection = value => directionKeywords.includes(value.toLowerCase())
|
|
31
|
+
const isFillMode = value => fillModeKeywords.includes(value.toLowerCase())
|
|
32
|
+
const isPlayState = value => playStateKeywords.includes(value.toLowerCase())
|
|
33
|
+
const isTime = value => /^[+-]?(?:\d*\.)?\d+(?:ms|s)$/i.test(value)
|
|
34
|
+
const isIterationCount = value =>
|
|
35
|
+
value.toLowerCase() === 'infinite' || /^[+-]?(?:\d*\.)?\d+$/.test(value)
|
|
36
|
+
|
|
37
|
+
// Helper to parse comma-separated values
|
|
38
|
+
// Returns single value if only one, array if multiple
|
|
39
|
+
const parseCommaSeparatedValues = (tokenStream, parseValue) => {
|
|
40
|
+
const values = []
|
|
41
|
+
let parsingFirst = true
|
|
42
|
+
|
|
43
|
+
while (tokenStream.hasTokens()) {
|
|
44
|
+
if (!parsingFirst) {
|
|
45
|
+
tokenStream.expect(COMMA)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Skip leading/trailing spaces
|
|
49
|
+
if (tokenStream.matches(SPACE)) {
|
|
50
|
+
// continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const value = parseValue(tokenStream)
|
|
54
|
+
values.push(value)
|
|
55
|
+
|
|
56
|
+
// Skip trailing spaces
|
|
57
|
+
if (tokenStream.matches(SPACE)) {
|
|
58
|
+
// continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
parsingFirst = false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return values.length === 1 ? values[0] : values
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Transform for animation-name property
|
|
68
|
+
export const animationName = tokenStream => {
|
|
69
|
+
const names = parseCommaSeparatedValues(tokenStream, ts =>
|
|
70
|
+
ts.expect(IDENT, NONE, VARIABLE)
|
|
71
|
+
)
|
|
72
|
+
return { animationName: names }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Transform for animation-duration property
|
|
76
|
+
export const animationDuration = tokenStream => {
|
|
77
|
+
const durations = parseCommaSeparatedValues(tokenStream, ts =>
|
|
78
|
+
ts.expect(TIME, VARIABLE)
|
|
79
|
+
)
|
|
80
|
+
return { animationDuration: durations }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Transform for animation-timing-function property
|
|
84
|
+
export const animationTimingFunction = tokenStream => {
|
|
85
|
+
const timingFunctions = parseCommaSeparatedValues(tokenStream, ts => {
|
|
86
|
+
// Check for function (cubic-bezier or steps)
|
|
87
|
+
const funcStream = ts.matchesFunction()
|
|
88
|
+
if (funcStream) {
|
|
89
|
+
const funcName = funcStream.functionName
|
|
90
|
+
const args = []
|
|
91
|
+
while (funcStream.hasTokens()) {
|
|
92
|
+
if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
const val = funcStream.expect(IDENT, TIME, NUMBER, node => {
|
|
96
|
+
if (node.type === 'word') return node.value
|
|
97
|
+
return null
|
|
98
|
+
})
|
|
99
|
+
args.push(val)
|
|
100
|
+
}
|
|
101
|
+
return `${funcName}(${args.join(', ')})`
|
|
102
|
+
}
|
|
103
|
+
return ts.expect(IDENT)
|
|
104
|
+
})
|
|
105
|
+
return { animationTimingFunction: timingFunctions }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Transform for animation-delay property
|
|
109
|
+
export const animationDelay = tokenStream => {
|
|
110
|
+
const delays = parseCommaSeparatedValues(tokenStream, ts =>
|
|
111
|
+
ts.expect(TIME, VARIABLE)
|
|
112
|
+
)
|
|
113
|
+
return { animationDelay: delays }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Transform for animation-iteration-count property
|
|
117
|
+
export const animationIterationCount = tokenStream => {
|
|
118
|
+
const counts = parseCommaSeparatedValues(tokenStream, ts => {
|
|
119
|
+
if (ts.matches(IDENT)) {
|
|
120
|
+
const value = ts.lastValue
|
|
121
|
+
return value.toLowerCase() === 'infinite' ? 'infinite' : Number(value)
|
|
122
|
+
}
|
|
123
|
+
if (ts.matches(NUMBER)) {
|
|
124
|
+
return ts.lastValue
|
|
125
|
+
}
|
|
126
|
+
const word = ts.expect(node => {
|
|
127
|
+
if (node.type === 'word') return node.value
|
|
128
|
+
return null
|
|
129
|
+
})
|
|
130
|
+
return word.toLowerCase() === 'infinite' ? 'infinite' : Number(word)
|
|
131
|
+
})
|
|
132
|
+
return { animationIterationCount: counts }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Transform for animation-direction property
|
|
136
|
+
export const animationDirection = tokenStream => {
|
|
137
|
+
const directions = parseCommaSeparatedValues(tokenStream, ts =>
|
|
138
|
+
ts.expect(IDENT)
|
|
139
|
+
)
|
|
140
|
+
return { animationDirection: directions }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Transform for animation-fill-mode property
|
|
144
|
+
export const animationFillMode = tokenStream => {
|
|
145
|
+
const fillModes = parseCommaSeparatedValues(tokenStream, ts =>
|
|
146
|
+
ts.expect(IDENT)
|
|
147
|
+
)
|
|
148
|
+
return { animationFillMode: fillModes }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Transform for animation-play-state property
|
|
152
|
+
export const animationPlayState = tokenStream => {
|
|
153
|
+
const playStates = parseCommaSeparatedValues(tokenStream, ts =>
|
|
154
|
+
ts.expect(IDENT)
|
|
155
|
+
)
|
|
156
|
+
return { animationPlayState: playStates }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default tokenStream => {
|
|
160
|
+
// Handle 'none'
|
|
161
|
+
if (tokenStream.matches(NONE)) {
|
|
162
|
+
tokenStream.expectEmpty()
|
|
163
|
+
return {
|
|
164
|
+
animationName: 'none',
|
|
165
|
+
animationDuration: '0s',
|
|
166
|
+
animationTimingFunction: 'ease',
|
|
167
|
+
animationDelay: '0s',
|
|
168
|
+
animationIterationCount: 1,
|
|
169
|
+
animationDirection: 'normal',
|
|
170
|
+
animationFillMode: 'none',
|
|
171
|
+
animationPlayState: 'running',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const names = []
|
|
176
|
+
const durations = []
|
|
177
|
+
const timingFunctions = []
|
|
178
|
+
const delays = []
|
|
179
|
+
const iterationCounts = []
|
|
180
|
+
const directions = []
|
|
181
|
+
const fillModes = []
|
|
182
|
+
const playStates = []
|
|
183
|
+
|
|
184
|
+
let parsingFirst = true
|
|
185
|
+
|
|
186
|
+
while (tokenStream.hasTokens()) {
|
|
187
|
+
if (!parsingFirst) {
|
|
188
|
+
tokenStream.expect(COMMA)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Parse single animation
|
|
192
|
+
let name = null
|
|
193
|
+
let duration = null
|
|
194
|
+
let timingFunction = null
|
|
195
|
+
let delay = null
|
|
196
|
+
let iterationCount = null
|
|
197
|
+
let direction = null
|
|
198
|
+
let fillMode = null
|
|
199
|
+
let playState = null
|
|
200
|
+
|
|
201
|
+
// Skip leading space
|
|
202
|
+
if (tokenStream.matches(SPACE)) {
|
|
203
|
+
// continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Parse tokens for this animation
|
|
207
|
+
while (tokenStream.hasTokens()) {
|
|
208
|
+
// Check for comma (next animation)
|
|
209
|
+
tokenStream.saveRewindPoint()
|
|
210
|
+
if (tokenStream.matches(SPACE)) {
|
|
211
|
+
if (tokenStream.matches(COMMA)) {
|
|
212
|
+
tokenStream.rewind()
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (tokenStream.matches(COMMA)) {
|
|
217
|
+
tokenStream.rewind()
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
tokenStream.rewind()
|
|
221
|
+
|
|
222
|
+
// Skip spaces
|
|
223
|
+
if (tokenStream.matches(SPACE)) {
|
|
224
|
+
continue
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Match time or variable (for duration/delay) - check before functions
|
|
228
|
+
if (tokenStream.matches(TIME) || tokenStream.matches(VARIABLE)) {
|
|
229
|
+
const value = tokenStream.lastValue
|
|
230
|
+
if (duration === null) {
|
|
231
|
+
duration = value
|
|
232
|
+
} else {
|
|
233
|
+
delay = value
|
|
234
|
+
}
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for timing function (cubic-bezier or steps)
|
|
239
|
+
const funcStream = tokenStream.matchesFunction()
|
|
240
|
+
if (funcStream) {
|
|
241
|
+
const funcName = funcStream.functionName
|
|
242
|
+
const args = []
|
|
243
|
+
while (funcStream.hasTokens()) {
|
|
244
|
+
if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
const val = funcStream.expect(IDENT, TIME, NUMBER, node => {
|
|
248
|
+
if (node.type === 'word') return node.value
|
|
249
|
+
return null
|
|
250
|
+
})
|
|
251
|
+
args.push(val)
|
|
252
|
+
}
|
|
253
|
+
timingFunction = `${funcName}(${args.join(', ')})`
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Match number (iteration count)
|
|
258
|
+
if (tokenStream.matches(NUMBER)) {
|
|
259
|
+
iterationCount = tokenStream.lastValue
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Match identifier
|
|
264
|
+
if (tokenStream.matches(IDENT)) {
|
|
265
|
+
const value = tokenStream.lastValue
|
|
266
|
+
if (isTimingFunction(value)) {
|
|
267
|
+
timingFunction = value
|
|
268
|
+
} else if (isDirection(value)) {
|
|
269
|
+
direction = value
|
|
270
|
+
} else if (isFillMode(value)) {
|
|
271
|
+
fillMode = value
|
|
272
|
+
} else if (isPlayState(value)) {
|
|
273
|
+
playState = value
|
|
274
|
+
} else if (value.toLowerCase() === 'infinite') {
|
|
275
|
+
iterationCount = 'infinite'
|
|
276
|
+
} else {
|
|
277
|
+
// It's the animation name
|
|
278
|
+
name = value
|
|
279
|
+
}
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Try to match as generic word
|
|
284
|
+
const wordMatch = tokenStream.expect(node => {
|
|
285
|
+
if (node.type === 'word') return node.value
|
|
286
|
+
return null
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
if (isTime(wordMatch)) {
|
|
290
|
+
if (duration === null) {
|
|
291
|
+
duration = wordMatch
|
|
292
|
+
} else {
|
|
293
|
+
delay = wordMatch
|
|
294
|
+
}
|
|
295
|
+
} else if (isTimingFunction(wordMatch)) {
|
|
296
|
+
timingFunction = wordMatch
|
|
297
|
+
} else if (isDirection(wordMatch)) {
|
|
298
|
+
direction = wordMatch
|
|
299
|
+
} else if (isFillMode(wordMatch)) {
|
|
300
|
+
fillMode = wordMatch
|
|
301
|
+
} else if (isPlayState(wordMatch)) {
|
|
302
|
+
playState = wordMatch
|
|
303
|
+
} else if (isIterationCount(wordMatch)) {
|
|
304
|
+
iterationCount =
|
|
305
|
+
wordMatch.toLowerCase() === 'infinite'
|
|
306
|
+
? 'infinite'
|
|
307
|
+
: Number(wordMatch)
|
|
308
|
+
} else {
|
|
309
|
+
name = wordMatch
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Apply defaults and push
|
|
314
|
+
names.push(name || 'none')
|
|
315
|
+
durations.push(duration || '0s')
|
|
316
|
+
timingFunctions.push(timingFunction || 'ease')
|
|
317
|
+
delays.push(delay || '0s')
|
|
318
|
+
iterationCounts.push(iterationCount !== null ? iterationCount : 1)
|
|
319
|
+
directions.push(direction || 'normal')
|
|
320
|
+
fillModes.push(fillMode || 'none')
|
|
321
|
+
playStates.push(playState || 'running')
|
|
322
|
+
|
|
323
|
+
parsingFirst = false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Return single values if only one animation, arrays if multiple
|
|
327
|
+
const isSingle = names.length === 1
|
|
328
|
+
return {
|
|
329
|
+
animationName: isSingle ? names[0] : names,
|
|
330
|
+
animationDuration: isSingle ? durations[0] : durations,
|
|
331
|
+
animationTimingFunction: isSingle ? timingFunctions[0] : timingFunctions,
|
|
332
|
+
animationDelay: isSingle ? delays[0] : delays,
|
|
333
|
+
animationIterationCount: isSingle ? iterationCounts[0] : iterationCounts,
|
|
334
|
+
animationDirection: isSingle ? directions[0] : directions,
|
|
335
|
+
animationFillMode: isSingle ? fillModes[0] : fillModes,
|
|
336
|
+
animationPlayState: isSingle ? playStates[0] : playStates,
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stringify } from 'postcss-value-parser'
|
|
2
2
|
|
|
3
3
|
export default tokenStream => {
|
|
4
|
-
|
|
4
|
+
// React Native now supports web-style box-shadow format directly
|
|
5
|
+
// Pass through the original CSS value as boxShadow
|
|
6
|
+
const value = stringify(tokenStream.nodes)
|
|
5
7
|
return {
|
|
6
|
-
|
|
7
|
-
shadowRadius: radius,
|
|
8
|
-
shadowColor: color,
|
|
9
|
-
shadowOpacity: 1,
|
|
8
|
+
boxShadow: value,
|
|
10
9
|
}
|
|
11
10
|
}
|
package/src/transforms/index.js
CHANGED
|
@@ -7,6 +7,16 @@ import {
|
|
|
7
7
|
WORD,
|
|
8
8
|
VARIABLE,
|
|
9
9
|
} from '../tokenTypes'
|
|
10
|
+
import animation, {
|
|
11
|
+
animationName,
|
|
12
|
+
animationDuration,
|
|
13
|
+
animationTimingFunction,
|
|
14
|
+
animationDelay,
|
|
15
|
+
animationIterationCount,
|
|
16
|
+
animationDirection,
|
|
17
|
+
animationFillMode,
|
|
18
|
+
animationPlayState,
|
|
19
|
+
} from './animation'
|
|
10
20
|
import aspectRatio from './aspectRatio'
|
|
11
21
|
import border from './border'
|
|
12
22
|
import boxShadow from './boxShadow'
|
|
@@ -20,6 +30,12 @@ import textDecoration from './textDecoration'
|
|
|
20
30
|
import textDecorationLine from './textDecorationLine'
|
|
21
31
|
import textShadow from './textShadow'
|
|
22
32
|
import transform from './transform'
|
|
33
|
+
import transition, {
|
|
34
|
+
transitionProperty,
|
|
35
|
+
transitionDuration,
|
|
36
|
+
transitionTimingFunction,
|
|
37
|
+
transitionDelay,
|
|
38
|
+
} from './transition'
|
|
23
39
|
import { directionFactory, parseShadowOffset } from './util'
|
|
24
40
|
|
|
25
41
|
const background = tokenStream => ({
|
|
@@ -37,7 +53,7 @@ const borderRadius = directionFactory({
|
|
|
37
53
|
})
|
|
38
54
|
const borderWidth = directionFactory({ prefix: 'border', suffix: 'Width' })
|
|
39
55
|
const margin = directionFactory({
|
|
40
|
-
types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO],
|
|
56
|
+
types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO, VARIABLE],
|
|
41
57
|
prefix: 'margin',
|
|
42
58
|
})
|
|
43
59
|
const padding = directionFactory({ prefix: 'padding' })
|
|
@@ -53,6 +69,15 @@ const textShadowOffset = tokenStream => ({
|
|
|
53
69
|
})
|
|
54
70
|
|
|
55
71
|
export default {
|
|
72
|
+
animation,
|
|
73
|
+
animationName,
|
|
74
|
+
animationDuration,
|
|
75
|
+
animationTimingFunction,
|
|
76
|
+
animationDelay,
|
|
77
|
+
animationIterationCount,
|
|
78
|
+
animationDirection,
|
|
79
|
+
animationFillMode,
|
|
80
|
+
animationPlayState,
|
|
56
81
|
aspectRatio,
|
|
57
82
|
background,
|
|
58
83
|
border,
|
|
@@ -75,4 +100,9 @@ export default {
|
|
|
75
100
|
textDecoration,
|
|
76
101
|
textDecorationLine,
|
|
77
102
|
transform,
|
|
103
|
+
transition,
|
|
104
|
+
transitionProperty,
|
|
105
|
+
transitionDuration,
|
|
106
|
+
transitionTimingFunction,
|
|
107
|
+
transitionDelay,
|
|
78
108
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
SPACE,
|
|
3
|
+
COMMA,
|
|
4
|
+
LENGTH,
|
|
5
|
+
NUMBER,
|
|
6
|
+
ANGLE,
|
|
7
|
+
PERCENT,
|
|
8
|
+
VARIABLE,
|
|
9
|
+
} from '../tokenTypes'
|
|
2
10
|
|
|
3
11
|
const oneOfTypes = tokenTypes => functionStream => {
|
|
4
12
|
const value = functionStream.expect(...tokenTypes)
|
|
@@ -6,9 +14,9 @@ const oneOfTypes = tokenTypes => functionStream => {
|
|
|
6
14
|
return value
|
|
7
15
|
}
|
|
8
16
|
|
|
9
|
-
const singleNumber = oneOfTypes([NUMBER])
|
|
10
|
-
const singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT])
|
|
11
|
-
const singleAngle = oneOfTypes([ANGLE])
|
|
17
|
+
const singleNumber = oneOfTypes([NUMBER, VARIABLE])
|
|
18
|
+
const singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT, VARIABLE])
|
|
19
|
+
const singleAngle = oneOfTypes([ANGLE, VARIABLE])
|
|
12
20
|
const xyTransformFactory = tokenTypes => (
|
|
13
21
|
key,
|
|
14
22
|
valueIfOmitted
|
|
@@ -31,9 +39,9 @@ const xyTransformFactory = tokenTypes => (
|
|
|
31
39
|
|
|
32
40
|
return [{ [`${key}Y`]: y }, { [`${key}X`]: x }]
|
|
33
41
|
}
|
|
34
|
-
const xyNumber = xyTransformFactory([NUMBER])
|
|
35
|
-
const xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT])
|
|
36
|
-
const xyAngle = xyTransformFactory([ANGLE])
|
|
42
|
+
const xyNumber = xyTransformFactory([NUMBER, VARIABLE])
|
|
43
|
+
const xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT, VARIABLE])
|
|
44
|
+
const xyAngle = xyTransformFactory([ANGLE, VARIABLE])
|
|
37
45
|
|
|
38
46
|
const partTransforms = {
|
|
39
47
|
perspective: singleNumber,
|