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

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.
@@ -0,0 +1,336 @@
1
+ import { SPACE, COMMA, IDENT, TIME, NUMBER, NONE } 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)
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)
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 => ts.expect(TIME))
111
+ return { animationDelay: delays }
112
+ }
113
+
114
+ // Transform for animation-iteration-count property
115
+ export const animationIterationCount = tokenStream => {
116
+ const counts = parseCommaSeparatedValues(tokenStream, ts => {
117
+ if (ts.matches(IDENT)) {
118
+ const value = ts.lastValue
119
+ return value.toLowerCase() === 'infinite' ? 'infinite' : Number(value)
120
+ }
121
+ if (ts.matches(NUMBER)) {
122
+ return ts.lastValue
123
+ }
124
+ const word = ts.expect(node => {
125
+ if (node.type === 'word') return node.value
126
+ return null
127
+ })
128
+ return word.toLowerCase() === 'infinite' ? 'infinite' : Number(word)
129
+ })
130
+ return { animationIterationCount: counts }
131
+ }
132
+
133
+ // Transform for animation-direction property
134
+ export const animationDirection = tokenStream => {
135
+ const directions = parseCommaSeparatedValues(tokenStream, ts =>
136
+ ts.expect(IDENT)
137
+ )
138
+ return { animationDirection: directions }
139
+ }
140
+
141
+ // Transform for animation-fill-mode property
142
+ export const animationFillMode = tokenStream => {
143
+ const fillModes = parseCommaSeparatedValues(tokenStream, ts =>
144
+ ts.expect(IDENT)
145
+ )
146
+ return { animationFillMode: fillModes }
147
+ }
148
+
149
+ // Transform for animation-play-state property
150
+ export const animationPlayState = tokenStream => {
151
+ const playStates = parseCommaSeparatedValues(tokenStream, ts =>
152
+ ts.expect(IDENT)
153
+ )
154
+ return { animationPlayState: playStates }
155
+ }
156
+
157
+ export default tokenStream => {
158
+ // Handle 'none'
159
+ if (tokenStream.matches(NONE)) {
160
+ tokenStream.expectEmpty()
161
+ return {
162
+ animationName: 'none',
163
+ animationDuration: '0s',
164
+ animationTimingFunction: 'ease',
165
+ animationDelay: '0s',
166
+ animationIterationCount: 1,
167
+ animationDirection: 'normal',
168
+ animationFillMode: 'none',
169
+ animationPlayState: 'running',
170
+ }
171
+ }
172
+
173
+ const names = []
174
+ const durations = []
175
+ const timingFunctions = []
176
+ const delays = []
177
+ const iterationCounts = []
178
+ const directions = []
179
+ const fillModes = []
180
+ const playStates = []
181
+
182
+ let parsingFirst = true
183
+
184
+ while (tokenStream.hasTokens()) {
185
+ if (!parsingFirst) {
186
+ tokenStream.expect(COMMA)
187
+ }
188
+
189
+ // Parse single animation
190
+ let name = null
191
+ let duration = null
192
+ let timingFunction = null
193
+ let delay = null
194
+ let iterationCount = null
195
+ let direction = null
196
+ let fillMode = null
197
+ let playState = null
198
+
199
+ // Skip leading space
200
+ if (tokenStream.matches(SPACE)) {
201
+ // continue
202
+ }
203
+
204
+ // Parse tokens for this animation
205
+ while (tokenStream.hasTokens()) {
206
+ // Check for comma (next animation)
207
+ tokenStream.saveRewindPoint()
208
+ if (tokenStream.matches(SPACE)) {
209
+ if (tokenStream.matches(COMMA)) {
210
+ tokenStream.rewind()
211
+ break
212
+ }
213
+ }
214
+ if (tokenStream.matches(COMMA)) {
215
+ tokenStream.rewind()
216
+ break
217
+ }
218
+ tokenStream.rewind()
219
+
220
+ // Skip spaces
221
+ if (tokenStream.matches(SPACE)) {
222
+ continue
223
+ }
224
+
225
+ // Check for timing function (cubic-bezier or steps)
226
+ const funcStream = tokenStream.matchesFunction()
227
+ if (funcStream) {
228
+ const funcName = funcStream.functionName
229
+ const args = []
230
+ while (funcStream.hasTokens()) {
231
+ if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
232
+ continue
233
+ }
234
+ const val = funcStream.expect(IDENT, TIME, NUMBER, node => {
235
+ if (node.type === 'word') return node.value
236
+ return null
237
+ })
238
+ args.push(val)
239
+ }
240
+ timingFunction = `${funcName}(${args.join(', ')})`
241
+ continue
242
+ }
243
+
244
+ // Match number (iteration count)
245
+ if (tokenStream.matches(NUMBER)) {
246
+ iterationCount = tokenStream.lastValue
247
+ continue
248
+ }
249
+
250
+ // Match time
251
+ if (tokenStream.matches(TIME)) {
252
+ const value = tokenStream.lastValue
253
+ if (duration === null) {
254
+ duration = value
255
+ } else {
256
+ delay = value
257
+ }
258
+ continue
259
+ }
260
+
261
+ // Match identifier
262
+ if (tokenStream.matches(IDENT)) {
263
+ const value = tokenStream.lastValue
264
+ if (isTimingFunction(value)) {
265
+ timingFunction = value
266
+ } else if (isDirection(value)) {
267
+ direction = value
268
+ } else if (isFillMode(value)) {
269
+ fillMode = value
270
+ } else if (isPlayState(value)) {
271
+ playState = value
272
+ } else if (value.toLowerCase() === 'infinite') {
273
+ iterationCount = 'infinite'
274
+ } else {
275
+ // It's the animation name
276
+ name = value
277
+ }
278
+ continue
279
+ }
280
+
281
+ // Try to match as generic word
282
+ const wordMatch = tokenStream.expect(node => {
283
+ if (node.type === 'word') return node.value
284
+ return null
285
+ })
286
+
287
+ if (isTime(wordMatch)) {
288
+ if (duration === null) {
289
+ duration = wordMatch
290
+ } else {
291
+ delay = wordMatch
292
+ }
293
+ } else if (isTimingFunction(wordMatch)) {
294
+ timingFunction = wordMatch
295
+ } else if (isDirection(wordMatch)) {
296
+ direction = wordMatch
297
+ } else if (isFillMode(wordMatch)) {
298
+ fillMode = wordMatch
299
+ } else if (isPlayState(wordMatch)) {
300
+ playState = wordMatch
301
+ } else if (isIterationCount(wordMatch)) {
302
+ iterationCount =
303
+ wordMatch.toLowerCase() === 'infinite'
304
+ ? 'infinite'
305
+ : Number(wordMatch)
306
+ } else {
307
+ name = wordMatch
308
+ }
309
+ }
310
+
311
+ // Apply defaults and push
312
+ names.push(name || 'none')
313
+ durations.push(duration || '0s')
314
+ timingFunctions.push(timingFunction || 'ease')
315
+ delays.push(delay || '0s')
316
+ iterationCounts.push(iterationCount !== null ? iterationCount : 1)
317
+ directions.push(direction || 'normal')
318
+ fillModes.push(fillMode || 'none')
319
+ playStates.push(playState || 'running')
320
+
321
+ parsingFirst = false
322
+ }
323
+
324
+ // Return single values if only one animation, arrays if multiple
325
+ const isSingle = names.length === 1
326
+ return {
327
+ animationName: isSingle ? names[0] : names,
328
+ animationDuration: isSingle ? durations[0] : durations,
329
+ animationTimingFunction: isSingle ? timingFunctions[0] : timingFunctions,
330
+ animationDelay: isSingle ? delays[0] : delays,
331
+ animationIterationCount: isSingle ? iterationCounts[0] : iterationCounts,
332
+ animationDirection: isSingle ? directions[0] : directions,
333
+ animationFillMode: isSingle ? fillModes[0] : fillModes,
334
+ animationPlayState: isSingle ? playStates[0] : playStates,
335
+ }
336
+ }
@@ -1,11 +1,10 @@
1
- import { parseShadow } from './util'
1
+ import { stringify } from 'postcss-value-parser'
2
2
 
3
3
  export default tokenStream => {
4
- const { offset, radius, color } = parseShadow(tokenStream)
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
- shadowOffset: offset,
7
- shadowRadius: radius,
8
- shadowColor: color,
9
- shadowOpacity: 1,
8
+ boxShadow: value,
10
9
  }
11
10
  }
@@ -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 => ({
@@ -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
  }
@@ -0,0 +1,238 @@
1
+ import camelizeStyleName from 'camelize'
2
+ import { SPACE, COMMA, IDENT, TIME, NONE } 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)
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 => ts.expect(TIME))
101
+ return { transitionDelay: delays }
102
+ }
103
+
104
+ export default tokenStream => {
105
+ // Handle 'none'
106
+ if (tokenStream.matches(NONE)) {
107
+ tokenStream.expectEmpty()
108
+ return {
109
+ transitionProperty: 'none',
110
+ transitionDuration: '0s',
111
+ transitionTimingFunction: 'ease',
112
+ transitionDelay: '0s',
113
+ }
114
+ }
115
+
116
+ const properties = []
117
+ const durations = []
118
+ const timingFunctions = []
119
+ const delays = []
120
+
121
+ let parsingFirst = true
122
+
123
+ while (tokenStream.hasTokens()) {
124
+ if (!parsingFirst) {
125
+ tokenStream.expect(COMMA)
126
+ }
127
+
128
+ // Parse single transition: property duration timing-function delay
129
+ // Order can vary, but typically: property duration [timing-function] [delay]
130
+ let property = null
131
+ let duration = null
132
+ let timingFunction = null
133
+ let delay = null
134
+
135
+ // Skip leading space
136
+ if (tokenStream.matches(SPACE)) {
137
+ // continue
138
+ }
139
+
140
+ // Parse tokens for this transition
141
+ while (tokenStream.hasTokens()) {
142
+ // Check for comma (next transition)
143
+ tokenStream.saveRewindPoint()
144
+ if (tokenStream.matches(SPACE)) {
145
+ if (tokenStream.matches(COMMA)) {
146
+ tokenStream.rewind()
147
+ break
148
+ }
149
+ }
150
+ if (tokenStream.matches(COMMA)) {
151
+ tokenStream.rewind()
152
+ break
153
+ }
154
+ tokenStream.rewind()
155
+
156
+ // Try to match different token types
157
+ if (tokenStream.matches(SPACE)) {
158
+ continue
159
+ }
160
+
161
+ // Check for timing function (cubic-bezier or steps)
162
+ const funcStream = tokenStream.matchesFunction()
163
+ if (funcStream) {
164
+ // It's a function like cubic-bezier() or steps()
165
+ const funcName = funcStream.functionName
166
+ const args = []
167
+ while (funcStream.hasTokens()) {
168
+ if (funcStream.matches(SPACE) || funcStream.matches(COMMA)) {
169
+ continue
170
+ }
171
+ const val = funcStream.expect(IDENT, TIME, node => {
172
+ if (node.type === 'word') return node.value
173
+ return null
174
+ })
175
+ args.push(val)
176
+ }
177
+ timingFunction = `${funcName}(${args.join(', ')})`
178
+ continue
179
+ }
180
+
181
+ // Match word or time
182
+ if (tokenStream.matches(IDENT)) {
183
+ const value = tokenStream.lastValue
184
+ if (isTimingFunction(value)) {
185
+ timingFunction = value
186
+ } else {
187
+ property = value
188
+ }
189
+ continue
190
+ }
191
+
192
+ if (tokenStream.matches(TIME)) {
193
+ const value = tokenStream.lastValue
194
+ if (duration === null) {
195
+ duration = value
196
+ } else {
197
+ delay = value
198
+ }
199
+ continue
200
+ }
201
+
202
+ // Try to match as word for property names like 'all', 'opacity', etc.
203
+ const wordMatch = tokenStream.expect(node => {
204
+ if (node.type === 'word') return node.value
205
+ return null
206
+ })
207
+ if (isTime(wordMatch)) {
208
+ if (duration === null) {
209
+ duration = wordMatch
210
+ } else {
211
+ delay = wordMatch
212
+ }
213
+ } else if (isTimingFunction(wordMatch)) {
214
+ timingFunction = wordMatch
215
+ } else {
216
+ property = wordMatch
217
+ }
218
+ }
219
+
220
+ // Apply defaults and camelize property name
221
+ const propName = property || 'all'
222
+ properties.push(propName === 'all' ? 'all' : camelizeStyleName(propName))
223
+ durations.push(duration || '0s')
224
+ timingFunctions.push(timingFunction || 'ease')
225
+ delays.push(delay || '0s')
226
+
227
+ parsingFirst = false
228
+ }
229
+
230
+ // Return single values if only one transition, arrays if multiple
231
+ const isSingle = properties.length === 1
232
+ return {
233
+ transitionProperty: isSingle ? properties[0] : properties,
234
+ transitionDuration: isSingle ? durations[0] : durations,
235
+ transitionTimingFunction: isSingle ? timingFunctions[0] : timingFunctions,
236
+ transitionDelay: isSingle ? delays[0] : delays,
237
+ }
238
+ }