@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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import camelizeStyleName from 'camelize'
|
|
2
|
+
import { SPACE, COMMA, IDENT, TIME, NONE, VARIABLE } from '../tokenTypes'
|
|
3
|
+
|
|
4
|
+
// Timing function keywords
|
|
5
|
+
const timingFunctionKeywords = [
|
|
6
|
+
'ease',
|
|
7
|
+
'linear',
|
|
8
|
+
'ease-in',
|
|
9
|
+
'ease-out',
|
|
10
|
+
'ease-in-out',
|
|
11
|
+
'step-start',
|
|
12
|
+
'step-end',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const isTimingFunction = value => {
|
|
16
|
+
if (timingFunctionKeywords.includes(value.toLowerCase())) {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
// cubic-bezier and steps are handled as functions
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isTime = value => /^[+-]?(?:\d*\.)?\d+(?:ms|s)$/i.test(value)
|
|
24
|
+
|
|
25
|
+
// Helper to parse comma-separated values
|
|
26
|
+
// Returns single value if only one, array if multiple
|
|
27
|
+
const parseCommaSeparatedValues = (tokenStream, parseValue) => {
|
|
28
|
+
const values = []
|
|
29
|
+
let parsingFirst = true
|
|
30
|
+
|
|
31
|
+
while (tokenStream.hasTokens()) {
|
|
32
|
+
if (!parsingFirst) {
|
|
33
|
+
tokenStream.expect(COMMA)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Skip leading/trailing spaces
|
|
37
|
+
if (tokenStream.matches(SPACE)) {
|
|
38
|
+
// continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const value = parseValue(tokenStream)
|
|
42
|
+
values.push(value)
|
|
43
|
+
|
|
44
|
+
// Skip trailing spaces
|
|
45
|
+
if (tokenStream.matches(SPACE)) {
|
|
46
|
+
// continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parsingFirst = false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return values.length === 1 ? values[0] : values
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Transform for transition-property
|
|
56
|
+
export const transitionProperty = tokenStream => {
|
|
57
|
+
const properties = parseCommaSeparatedValues(tokenStream, ts => {
|
|
58
|
+
const prop = ts.expect(IDENT, NONE)
|
|
59
|
+
// Don't camelize special values like 'all' and 'none'
|
|
60
|
+
return prop === 'all' || prop === 'none' ? prop : camelizeStyleName(prop)
|
|
61
|
+
})
|
|
62
|
+
return { transitionProperty: properties }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Transform for transition-duration
|
|
66
|
+
export const transitionDuration = tokenStream => {
|
|
67
|
+
const durations = parseCommaSeparatedValues(tokenStream, ts =>
|
|
68
|
+
ts.expect(TIME, VARIABLE)
|
|
69
|
+
)
|
|
70
|
+
return { transitionDuration: durations }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Transform for transition-timing-function
|
|
74
|
+
export const transitionTimingFunction = tokenStream => {
|
|
75
|
+
const timingFunctions = parseCommaSeparatedValues(tokenStream, ts => {
|
|
76
|
+
// Check for function (cubic-bezier or steps)
|
|
77
|
+
const funcStream = ts.matchesFunction()
|
|
78
|
+
if (funcStream) {
|
|
79
|
+
const funcName = funcStream.functionName
|
|
80
|
+
const args = []
|
|
81
|
+
while (funcStream.hasTokens()) {
|
|
82
|
+
if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
const val = funcStream.expect(IDENT, TIME, node => {
|
|
86
|
+
if (node.type === 'word') return node.value
|
|
87
|
+
return null
|
|
88
|
+
})
|
|
89
|
+
args.push(val)
|
|
90
|
+
}
|
|
91
|
+
return `${funcName}(${args.join(', ')})`
|
|
92
|
+
}
|
|
93
|
+
return ts.expect(IDENT)
|
|
94
|
+
})
|
|
95
|
+
return { transitionTimingFunction: timingFunctions }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Transform for transition-delay
|
|
99
|
+
export const transitionDelay = tokenStream => {
|
|
100
|
+
const delays = parseCommaSeparatedValues(tokenStream, ts =>
|
|
101
|
+
ts.expect(TIME, VARIABLE)
|
|
102
|
+
)
|
|
103
|
+
return { transitionDelay: delays }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export default tokenStream => {
|
|
107
|
+
// Handle 'none'
|
|
108
|
+
if (tokenStream.matches(NONE)) {
|
|
109
|
+
tokenStream.expectEmpty()
|
|
110
|
+
return {
|
|
111
|
+
transitionProperty: 'none',
|
|
112
|
+
transitionDuration: '0s',
|
|
113
|
+
transitionTimingFunction: 'ease',
|
|
114
|
+
transitionDelay: '0s',
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const properties = []
|
|
119
|
+
const durations = []
|
|
120
|
+
const timingFunctions = []
|
|
121
|
+
const delays = []
|
|
122
|
+
|
|
123
|
+
let parsingFirst = true
|
|
124
|
+
|
|
125
|
+
while (tokenStream.hasTokens()) {
|
|
126
|
+
if (!parsingFirst) {
|
|
127
|
+
tokenStream.expect(COMMA)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Parse single transition: property duration timing-function delay
|
|
131
|
+
// Order can vary, but typically: property duration [timing-function] [delay]
|
|
132
|
+
let property = null
|
|
133
|
+
let duration = null
|
|
134
|
+
let timingFunction = null
|
|
135
|
+
let delay = null
|
|
136
|
+
|
|
137
|
+
// Skip leading space
|
|
138
|
+
if (tokenStream.matches(SPACE)) {
|
|
139
|
+
// continue
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Parse tokens for this transition
|
|
143
|
+
while (tokenStream.hasTokens()) {
|
|
144
|
+
// Check for comma (next transition)
|
|
145
|
+
tokenStream.saveRewindPoint()
|
|
146
|
+
if (tokenStream.matches(SPACE)) {
|
|
147
|
+
if (tokenStream.matches(COMMA)) {
|
|
148
|
+
tokenStream.rewind()
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (tokenStream.matches(COMMA)) {
|
|
153
|
+
tokenStream.rewind()
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
tokenStream.rewind()
|
|
157
|
+
|
|
158
|
+
// Try to match different token types
|
|
159
|
+
if (tokenStream.matches(SPACE)) {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Match time or variable (for duration/delay) - check before functions
|
|
164
|
+
if (tokenStream.matches(TIME) || tokenStream.matches(VARIABLE)) {
|
|
165
|
+
const value = tokenStream.lastValue
|
|
166
|
+
if (duration === null) {
|
|
167
|
+
duration = value
|
|
168
|
+
} else {
|
|
169
|
+
delay = value
|
|
170
|
+
}
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check for timing function (cubic-bezier or steps)
|
|
175
|
+
const funcStream = tokenStream.matchesFunction()
|
|
176
|
+
if (funcStream) {
|
|
177
|
+
const funcName = funcStream.functionName
|
|
178
|
+
const args = []
|
|
179
|
+
while (funcStream.hasTokens()) {
|
|
180
|
+
if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
const val = funcStream.expect(IDENT, TIME, node => {
|
|
184
|
+
if (node.type === 'word') return node.value
|
|
185
|
+
return null
|
|
186
|
+
})
|
|
187
|
+
args.push(val)
|
|
188
|
+
}
|
|
189
|
+
timingFunction = `${funcName}(${args.join(', ')})`
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Match word or time
|
|
194
|
+
if (tokenStream.matches(IDENT)) {
|
|
195
|
+
const value = tokenStream.lastValue
|
|
196
|
+
if (isTimingFunction(value)) {
|
|
197
|
+
timingFunction = value
|
|
198
|
+
} else {
|
|
199
|
+
property = value
|
|
200
|
+
}
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Try to match as word for property names like 'all', 'opacity', etc.
|
|
205
|
+
const wordMatch = tokenStream.expect(node => {
|
|
206
|
+
if (node.type === 'word') return node.value
|
|
207
|
+
return null
|
|
208
|
+
})
|
|
209
|
+
if (isTime(wordMatch)) {
|
|
210
|
+
if (duration === null) {
|
|
211
|
+
duration = wordMatch
|
|
212
|
+
} else {
|
|
213
|
+
delay = wordMatch
|
|
214
|
+
}
|
|
215
|
+
} else if (isTimingFunction(wordMatch)) {
|
|
216
|
+
timingFunction = wordMatch
|
|
217
|
+
} else {
|
|
218
|
+
property = wordMatch
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Apply defaults and camelize property name
|
|
223
|
+
const propName = property || 'all'
|
|
224
|
+
properties.push(propName === 'all' ? 'all' : camelizeStyleName(propName))
|
|
225
|
+
durations.push(duration || '0s')
|
|
226
|
+
timingFunctions.push(timingFunction || 'ease')
|
|
227
|
+
delays.push(delay || '0s')
|
|
228
|
+
|
|
229
|
+
parsingFirst = false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Return single values if only one transition, arrays if multiple
|
|
233
|
+
const isSingle = properties.length === 1
|
|
234
|
+
return {
|
|
235
|
+
transitionProperty: isSingle ? properties[0] : properties,
|
|
236
|
+
transitionDuration: isSingle ? durations[0] : durations,
|
|
237
|
+
transitionTimingFunction: isSingle ? timingFunctions[0] : timingFunctions,
|
|
238
|
+
transitionDelay: isSingle ? delays[0] : delays,
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/transforms/util.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '../tokenTypes'
|
|
10
10
|
|
|
11
11
|
export const directionFactory = ({
|
|
12
|
-
types = [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT],
|
|
12
|
+
types = [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, VARIABLE],
|
|
13
13
|
directions = ['Top', 'Right', 'Bottom', 'Left'],
|
|
14
14
|
prefix = '',
|
|
15
15
|
suffix = '',
|
|
@@ -68,10 +68,13 @@ export const parseShadow = tokenStream => {
|
|
|
68
68
|
offsetX === undefined &&
|
|
69
69
|
tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)
|
|
70
70
|
) {
|
|
71
|
+
// First offset must be a concrete LENGTH to distinguish from color
|
|
71
72
|
offsetX = tokenStream.lastValue
|
|
72
73
|
tokenStream.expect(SPACE)
|
|
73
|
-
|
|
74
|
+
// Second offset and radius can be VARIABLE
|
|
75
|
+
offsetY = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT, VARIABLE)
|
|
74
76
|
|
|
77
|
+
// Try to match optional blur-radius (concrete LENGTH only here)
|
|
75
78
|
tokenStream.saveRewindPoint()
|
|
76
79
|
if (
|
|
77
80
|
tokenStream.matches(SPACE) &&
|
|
@@ -81,6 +84,25 @@ export const parseShadow = tokenStream => {
|
|
|
81
84
|
} else {
|
|
82
85
|
tokenStream.rewind()
|
|
83
86
|
}
|
|
87
|
+
} else if (
|
|
88
|
+
offsetX !== undefined &&
|
|
89
|
+
radius === undefined &&
|
|
90
|
+
color === undefined &&
|
|
91
|
+
tokenStream.matches(VARIABLE)
|
|
92
|
+
) {
|
|
93
|
+
// VARIABLE after offsets - could be radius or color
|
|
94
|
+
// Peek ahead to determine which
|
|
95
|
+
const potentialValue = tokenStream.lastValue
|
|
96
|
+
tokenStream.saveRewindPoint()
|
|
97
|
+
if (tokenStream.matches(SPACE) && tokenStream.hasTokens()) {
|
|
98
|
+
// There's more content - this VARIABLE is the radius
|
|
99
|
+
tokenStream.rewind()
|
|
100
|
+
radius = potentialValue
|
|
101
|
+
} else {
|
|
102
|
+
// No more content - this VARIABLE is the color
|
|
103
|
+
tokenStream.rewind()
|
|
104
|
+
color = potentialValue
|
|
105
|
+
}
|
|
84
106
|
} else if (
|
|
85
107
|
color === undefined &&
|
|
86
108
|
(tokenStream.matches(COLOR) || tokenStream.matches(VARIABLE))
|