@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.
- package/index.js +808 -279
- 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__/transition.js +186 -0
- package/src/__tests__/units.js +1 -4
- package/src/index.js +135 -7
- package/src/tokenTypes.js +21 -0
- package/src/transforms/animation.js +336 -0
- package/src/transforms/boxShadow.js +5 -6
- package/src/transforms/index.js +30 -0
- package/src/transforms/transition.js +238 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import transformCss from '..'
|
|
2
|
+
|
|
3
|
+
it('transforms transition shorthand', () => {
|
|
4
|
+
expect(transformCss([['transition', 'opacity 300ms ease']])).toEqual({
|
|
5
|
+
transitionProperty: 'opacity',
|
|
6
|
+
transitionDuration: '300ms',
|
|
7
|
+
transitionTimingFunction: 'ease',
|
|
8
|
+
transitionDelay: '0s',
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('transforms transition with only property and duration', () => {
|
|
13
|
+
expect(transformCss([['transition', 'opacity 300ms']])).toEqual({
|
|
14
|
+
transitionProperty: 'opacity',
|
|
15
|
+
transitionDuration: '300ms',
|
|
16
|
+
transitionTimingFunction: 'ease',
|
|
17
|
+
transitionDelay: '0s',
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('transforms transition property names to camelCase', () => {
|
|
22
|
+
expect(transformCss([['transition', 'background-color 300ms']])).toEqual({
|
|
23
|
+
transitionProperty: 'backgroundColor',
|
|
24
|
+
transitionDuration: '300ms',
|
|
25
|
+
transitionTimingFunction: 'ease',
|
|
26
|
+
transitionDelay: '0s',
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('transforms multiple transition property names to camelCase', () => {
|
|
31
|
+
expect(
|
|
32
|
+
transformCss([
|
|
33
|
+
['transition', 'background-color 300ms, border-radius 500ms ease-out'],
|
|
34
|
+
])
|
|
35
|
+
).toEqual({
|
|
36
|
+
transitionProperty: ['backgroundColor', 'borderRadius'],
|
|
37
|
+
transitionDuration: ['300ms', '500ms'],
|
|
38
|
+
transitionTimingFunction: ['ease', 'ease-out'],
|
|
39
|
+
transitionDelay: ['0s', '0s'],
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('transforms transition with delay', () => {
|
|
44
|
+
expect(transformCss([['transition', 'opacity 300ms ease 100ms']])).toEqual({
|
|
45
|
+
transitionProperty: 'opacity',
|
|
46
|
+
transitionDuration: '300ms',
|
|
47
|
+
transitionTimingFunction: 'ease',
|
|
48
|
+
transitionDelay: '100ms',
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('transforms transition with all property', () => {
|
|
53
|
+
expect(transformCss([['transition', 'all 200ms linear']])).toEqual({
|
|
54
|
+
transitionProperty: 'all',
|
|
55
|
+
transitionDuration: '200ms',
|
|
56
|
+
transitionTimingFunction: 'linear',
|
|
57
|
+
transitionDelay: '0s',
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('transforms multiple transitions', () => {
|
|
62
|
+
expect(
|
|
63
|
+
transformCss([
|
|
64
|
+
['transition', 'opacity 300ms ease, transform 500ms ease-in'],
|
|
65
|
+
])
|
|
66
|
+
).toEqual({
|
|
67
|
+
transitionProperty: ['opacity', 'transform'],
|
|
68
|
+
transitionDuration: ['300ms', '500ms'],
|
|
69
|
+
transitionTimingFunction: ['ease', 'ease-in'],
|
|
70
|
+
transitionDelay: ['0s', '0s'],
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('transforms transition with cubic-bezier', () => {
|
|
75
|
+
expect(
|
|
76
|
+
transformCss([['transition', 'opacity 300ms cubic-bezier(0.4, 0, 0.2, 1)']])
|
|
77
|
+
).toEqual({
|
|
78
|
+
transitionProperty: 'opacity',
|
|
79
|
+
transitionDuration: '300ms',
|
|
80
|
+
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
81
|
+
transitionDelay: '0s',
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('transforms transition none', () => {
|
|
86
|
+
expect(transformCss([['transition', 'none']])).toEqual({
|
|
87
|
+
transitionProperty: 'none',
|
|
88
|
+
transitionDuration: '0s',
|
|
89
|
+
transitionTimingFunction: 'ease',
|
|
90
|
+
transitionDelay: '0s',
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('transforms transition-property', () => {
|
|
95
|
+
expect(transformCss([['transition-property', 'opacity']])).toEqual({
|
|
96
|
+
transitionProperty: 'opacity',
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('transforms transition-property with multiple values', () => {
|
|
101
|
+
expect(
|
|
102
|
+
transformCss([['transition-property', 'opacity, transform, width']])
|
|
103
|
+
).toEqual({
|
|
104
|
+
transitionProperty: ['opacity', 'transform', 'width'],
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('transforms transition-property with camelCase conversion', () => {
|
|
109
|
+
expect(
|
|
110
|
+
transformCss([['transition-property', 'background-color, border-radius']])
|
|
111
|
+
).toEqual({
|
|
112
|
+
transitionProperty: ['backgroundColor', 'borderRadius'],
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('transforms transition-duration', () => {
|
|
117
|
+
expect(transformCss([['transition-duration', '300ms']])).toEqual({
|
|
118
|
+
transitionDuration: '300ms',
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('transforms transition-duration with multiple values', () => {
|
|
123
|
+
expect(transformCss([['transition-duration', '300ms, 500ms, 1s']])).toEqual({
|
|
124
|
+
transitionDuration: ['300ms', '500ms', '1s'],
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('transforms transition-timing-function', () => {
|
|
129
|
+
expect(transformCss([['transition-timing-function', 'ease-in-out']])).toEqual(
|
|
130
|
+
{
|
|
131
|
+
transitionTimingFunction: 'ease-in-out',
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('transforms transition-timing-function with multiple values', () => {
|
|
137
|
+
expect(
|
|
138
|
+
transformCss([['transition-timing-function', 'ease, linear, ease-in-out']])
|
|
139
|
+
).toEqual({
|
|
140
|
+
transitionTimingFunction: ['ease', 'linear', 'ease-in-out'],
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('transforms transition-timing-function with cubic-bezier', () => {
|
|
145
|
+
expect(
|
|
146
|
+
transformCss([
|
|
147
|
+
['transition-timing-function', 'cubic-bezier(0.4, 0, 0.2, 1)'],
|
|
148
|
+
])
|
|
149
|
+
).toEqual({
|
|
150
|
+
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('transforms transition-delay', () => {
|
|
155
|
+
expect(transformCss([['transition-delay', '100ms']])).toEqual({
|
|
156
|
+
transitionDelay: '100ms',
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('transforms transition-delay with multiple values', () => {
|
|
161
|
+
expect(transformCss([['transition-delay', '100ms, 200ms, 0s']])).toEqual({
|
|
162
|
+
transitionDelay: ['100ms', '200ms', '0s'],
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('transforms transition with seconds', () => {
|
|
167
|
+
expect(transformCss([['transition', 'opacity 1s linear 0.5s']])).toEqual({
|
|
168
|
+
transitionProperty: 'opacity',
|
|
169
|
+
transitionDuration: '1s',
|
|
170
|
+
transitionTimingFunction: 'linear',
|
|
171
|
+
transitionDelay: '0.5s',
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('transforms multiple transitions with different timing functions', () => {
|
|
176
|
+
expect(
|
|
177
|
+
transformCss([
|
|
178
|
+
['transition', 'width 200ms ease-in, height 300ms ease-out 100ms'],
|
|
179
|
+
])
|
|
180
|
+
).toEqual({
|
|
181
|
+
transitionProperty: ['width', 'height'],
|
|
182
|
+
transitionDuration: ['200ms', '300ms'],
|
|
183
|
+
transitionTimingFunction: ['ease-in', 'ease-out'],
|
|
184
|
+
transitionDelay: ['0s', '100ms'],
|
|
185
|
+
})
|
|
186
|
+
})
|
package/src/__tests__/units.js
CHANGED
|
@@ -117,10 +117,7 @@ lengthUnits.forEach(unit => {
|
|
|
117
117
|
expect(
|
|
118
118
|
transformCss([['box-shadow', `10px ${value} ${value} red`]])
|
|
119
119
|
).toEqual({
|
|
120
|
-
|
|
121
|
-
shadowRadius: value,
|
|
122
|
-
shadowColor: 'red',
|
|
123
|
-
shadowOpacity: 1,
|
|
120
|
+
boxShadow: `10px ${value} ${value} red`,
|
|
124
121
|
})
|
|
125
122
|
})
|
|
126
123
|
})
|
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
|
@@ -73,6 +73,7 @@ const lengthRe = /^(0$|(?:[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?=px$))/i
|
|
|
73
73
|
const unsupportedUnitRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(ch|em|ex|rem|vh|vw|vmin|vmax|cm|mm|in|pc|pt))$/i
|
|
74
74
|
const angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad|grad|turn))$/i
|
|
75
75
|
const percentRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?%)$/i
|
|
76
|
+
const timeRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:ms|s))$/i
|
|
76
77
|
|
|
77
78
|
const noopToken = predicate => node => (predicate(node) ? '<token>' : null)
|
|
78
79
|
|
|
@@ -105,8 +106,28 @@ export const LENGTH = regExpToken(lengthRe, Number)
|
|
|
105
106
|
export const UNSUPPORTED_LENGTH_UNIT = regExpToken(unsupportedUnitRe)
|
|
106
107
|
export const ANGLE = regExpToken(angleRe, angle => angle.toLowerCase())
|
|
107
108
|
export const PERCENT = regExpToken(percentRe)
|
|
109
|
+
export const TIME = regExpToken(timeRe)
|
|
108
110
|
export const IDENT = regExpToken(identRe)
|
|
109
111
|
export const STRING = matchString
|
|
110
112
|
export const COLOR = matchColor
|
|
111
113
|
export const LINE = regExpToken(/^(none|underline|line-through)$/i)
|
|
112
114
|
export const VARIABLE = matchVariable
|
|
115
|
+
|
|
116
|
+
// Animation/Transition timing functions (keywords)
|
|
117
|
+
export const TIMING_FUNCTION = regExpToken(
|
|
118
|
+
/^(ease|linear|ease-in|ease-out|ease-in-out|step-start|step-end)$/i
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
// Animation iteration count
|
|
122
|
+
export const ITERATION_COUNT = regExpToken(/^(infinite)$/i)
|
|
123
|
+
|
|
124
|
+
// Animation direction
|
|
125
|
+
export const ANIMATION_DIRECTION = regExpToken(
|
|
126
|
+
/^(normal|reverse|alternate|alternate-reverse)$/i
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
// Animation fill mode
|
|
130
|
+
export const FILL_MODE = regExpToken(/^(none|forwards|backwards|both)$/i)
|
|
131
|
+
|
|
132
|
+
// Animation play state
|
|
133
|
+
export const PLAY_STATE = regExpToken(/^(running|paused)$/i)
|