@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,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
+ })
@@ -117,10 +117,7 @@ lengthUnits.forEach(unit => {
117
117
  expect(
118
118
  transformCss([['box-shadow', `10px ${value} ${value} red`]])
119
119
  ).toEqual({
120
- shadowOffset: { width: 10, height: value },
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
- export default (rules, shorthandBlacklist = []) =>
82
- rules.reduce((accum, rule) => {
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
- const allowShorthand = shorthandBlacklist.indexOf(propertyName) === -1
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)