@cssxjs/css-to-react-native 3.2.0-1 → 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 CHANGED
@@ -32,7 +32,7 @@ var matchColor = function matchColor(node) {
32
32
  return null;
33
33
  };
34
34
  var matchVariable = function matchVariable(node) {
35
- if (node.type !== 'function' && node.value !== 'var' || node.nodes.length === 0) return null;
35
+ if (node.type !== 'function' || node.value !== 'var' || node.nodes.length === 0) return null;
36
36
  var variableName = node.nodes[0].value;
37
37
  if (node.nodes.length === 1) {
38
38
  return "var(" + variableName + ")";
@@ -169,7 +169,7 @@ var parseCommaSeparatedValues = function parseCommaSeparatedValues(tokenStream,
169
169
  // Transform for animation-name property
170
170
  var animationName = function animationName(tokenStream) {
171
171
  var names = parseCommaSeparatedValues(tokenStream, function (ts) {
172
- return ts.expect(IDENT, NONE);
172
+ return ts.expect(IDENT, NONE, VARIABLE);
173
173
  });
174
174
  return {
175
175
  animationName: names
@@ -179,7 +179,7 @@ var animationName = function animationName(tokenStream) {
179
179
  // Transform for animation-duration property
180
180
  var animationDuration = function animationDuration(tokenStream) {
181
181
  var durations = parseCommaSeparatedValues(tokenStream, function (ts) {
182
- return ts.expect(TIME);
182
+ return ts.expect(TIME, VARIABLE);
183
183
  });
184
184
  return {
185
185
  animationDuration: durations
@@ -216,7 +216,7 @@ var animationTimingFunction = function animationTimingFunction(tokenStream) {
216
216
  // Transform for animation-delay property
217
217
  var animationDelay = function animationDelay(tokenStream) {
218
218
  var delays = parseCommaSeparatedValues(tokenStream, function (ts) {
219
- return ts.expect(TIME);
219
+ return ts.expect(TIME, VARIABLE);
220
220
  });
221
221
  return {
222
222
  animationDelay: delays
@@ -336,6 +336,17 @@ var animation = function animation(tokenStream) {
336
336
  continue;
337
337
  }
338
338
 
339
+ // Match time or variable (for duration/delay) - check before functions
340
+ if (tokenStream.matches(TIME) || tokenStream.matches(VARIABLE)) {
341
+ var value = tokenStream.lastValue;
342
+ if (duration === null) {
343
+ duration = value;
344
+ } else {
345
+ delay = value;
346
+ }
347
+ continue;
348
+ }
349
+
339
350
  // Check for timing function (cubic-bezier or steps)
340
351
  var funcStream = tokenStream.matchesFunction();
341
352
  if (funcStream) {
@@ -361,17 +372,6 @@ var animation = function animation(tokenStream) {
361
372
  continue;
362
373
  }
363
374
 
364
- // Match time
365
- if (tokenStream.matches(TIME)) {
366
- var value = tokenStream.lastValue;
367
- if (duration === null) {
368
- duration = value;
369
- } else {
370
- delay = value;
371
- }
372
- continue;
373
- }
374
-
375
375
  // Match identifier
376
376
  if (tokenStream.matches(IDENT)) {
377
377
  var _value = tokenStream.lastValue;
@@ -727,7 +727,7 @@ var textDecorationLine = function textDecorationLine(tokenStream) {
727
727
  };
728
728
  var directionFactory = function directionFactory(_ref) {
729
729
  var _ref$types = _ref.types,
730
- types = _ref$types === void 0 ? [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT] : _ref$types,
730
+ types = _ref$types === void 0 ? [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, VARIABLE] : _ref$types,
731
731
  _ref$directions = _ref.directions,
732
732
  directions = _ref$directions === void 0 ? ['Top', 'Right', 'Bottom', 'Left'] : _ref$directions,
733
733
  _ref$prefix = _ref.prefix,
@@ -787,15 +787,33 @@ var parseShadow = function parseShadow(tokenStream) {
787
787
  while (tokenStream.hasTokens()) {
788
788
  if (didParseFirst) tokenStream.expect(SPACE);
789
789
  if (offsetX === undefined && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)) {
790
+ // First offset must be a concrete LENGTH to distinguish from color
790
791
  offsetX = tokenStream.lastValue;
791
792
  tokenStream.expect(SPACE);
792
- offsetY = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT);
793
+ // Second offset and radius can be VARIABLE
794
+ offsetY = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT, VARIABLE);
795
+
796
+ // Try to match optional blur-radius (concrete LENGTH only here)
793
797
  tokenStream.saveRewindPoint();
794
798
  if (tokenStream.matches(SPACE) && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)) {
795
799
  radius = tokenStream.lastValue;
796
800
  } else {
797
801
  tokenStream.rewind();
798
802
  }
803
+ } else if (offsetX !== undefined && radius === undefined && color === undefined && tokenStream.matches(VARIABLE)) {
804
+ // VARIABLE after offsets - could be radius or color
805
+ // Peek ahead to determine which
806
+ var potentialValue = tokenStream.lastValue;
807
+ tokenStream.saveRewindPoint();
808
+ if (tokenStream.matches(SPACE) && tokenStream.hasTokens()) {
809
+ // There's more content - this VARIABLE is the radius
810
+ tokenStream.rewind();
811
+ radius = potentialValue;
812
+ } else {
813
+ // No more content - this VARIABLE is the color
814
+ tokenStream.rewind();
815
+ color = potentialValue;
816
+ }
799
817
  } else if (color === undefined && (tokenStream.matches(COLOR) || tokenStream.matches(VARIABLE))) {
800
818
  color = tokenStream.lastValue;
801
819
  } else {
@@ -831,9 +849,9 @@ var oneOfTypes = function oneOfTypes(tokenTypes) {
831
849
  return value;
832
850
  };
833
851
  };
834
- var singleNumber = oneOfTypes([NUMBER]);
835
- var singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT]);
836
- var singleAngle = oneOfTypes([ANGLE]);
852
+ var singleNumber = oneOfTypes([NUMBER, VARIABLE]);
853
+ var singleLengthOrPercent = oneOfTypes([LENGTH, PERCENT, VARIABLE]);
854
+ var singleAngle = oneOfTypes([ANGLE, VARIABLE]);
837
855
  var xyTransformFactory = function xyTransformFactory(tokenTypes) {
838
856
  return function (key, valueIfOmitted) {
839
857
  return function (functionStream) {
@@ -855,9 +873,9 @@ var xyTransformFactory = function xyTransformFactory(tokenTypes) {
855
873
  };
856
874
  };
857
875
  };
858
- var xyNumber = xyTransformFactory([NUMBER]);
859
- var xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT]);
860
- var xyAngle = xyTransformFactory([ANGLE]);
876
+ var xyNumber = xyTransformFactory([NUMBER, VARIABLE]);
877
+ var xyLengthOrPercent = xyTransformFactory([LENGTH, PERCENT, VARIABLE]);
878
+ var xyAngle = xyTransformFactory([ANGLE, VARIABLE]);
861
879
  var partTransforms = {
862
880
  perspective: singleNumber,
863
881
  scale: xyNumber('scale'),
@@ -944,7 +962,7 @@ var transitionProperty = function transitionProperty(tokenStream) {
944
962
  // Transform for transition-duration
945
963
  var transitionDuration = function transitionDuration(tokenStream) {
946
964
  var durations = parseCommaSeparatedValues$1(tokenStream, function (ts) {
947
- return ts.expect(TIME);
965
+ return ts.expect(TIME, VARIABLE);
948
966
  });
949
967
  return {
950
968
  transitionDuration: durations
@@ -981,7 +999,7 @@ var transitionTimingFunction = function transitionTimingFunction(tokenStream) {
981
999
  // Transform for transition-delay
982
1000
  var transitionDelay = function transitionDelay(tokenStream) {
983
1001
  var delays = parseCommaSeparatedValues$1(tokenStream, function (ts) {
984
- return ts.expect(TIME);
1002
+ return ts.expect(TIME, VARIABLE);
985
1003
  });
986
1004
  return {
987
1005
  transitionDelay: delays
@@ -1039,10 +1057,20 @@ var transition = function transition(tokenStream) {
1039
1057
  continue;
1040
1058
  }
1041
1059
 
1060
+ // Match time or variable (for duration/delay) - check before functions
1061
+ if (tokenStream.matches(TIME) || tokenStream.matches(VARIABLE)) {
1062
+ var value = tokenStream.lastValue;
1063
+ if (duration === null) {
1064
+ duration = value;
1065
+ } else {
1066
+ delay = value;
1067
+ }
1068
+ continue;
1069
+ }
1070
+
1042
1071
  // Check for timing function (cubic-bezier or steps)
1043
1072
  var funcStream = tokenStream.matchesFunction();
1044
1073
  if (funcStream) {
1045
- // It's a function like cubic-bezier() or steps()
1046
1074
  var funcName = funcStream.functionName;
1047
1075
  var args = [];
1048
1076
  while (funcStream.hasTokens()) {
@@ -1061,20 +1089,11 @@ var transition = function transition(tokenStream) {
1061
1089
 
1062
1090
  // Match word or time
1063
1091
  if (tokenStream.matches(IDENT)) {
1064
- var value = tokenStream.lastValue;
1065
- if (isTimingFunction$1(value)) {
1066
- timingFunction = value;
1067
- } else {
1068
- property = value;
1069
- }
1070
- continue;
1071
- }
1072
- if (tokenStream.matches(TIME)) {
1073
1092
  var _value2 = tokenStream.lastValue;
1074
- if (duration === null) {
1075
- duration = _value2;
1093
+ if (isTimingFunction$1(_value2)) {
1094
+ timingFunction = _value2;
1076
1095
  } else {
1077
- delay = _value2;
1096
+ property = _value2;
1078
1097
  }
1079
1098
  continue;
1080
1099
  }
@@ -1135,7 +1154,7 @@ var borderWidth = directionFactory({
1135
1154
  suffix: 'Width'
1136
1155
  });
1137
1156
  var margin = directionFactory({
1138
- types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO],
1157
+ types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO, VARIABLE],
1139
1158
  prefix: 'margin'
1140
1159
  });
1141
1160
  var padding = directionFactory({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssxjs/css-to-react-native",
3
- "version": "3.2.0-1",
3
+ "version": "3.2.0-2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -105,3 +105,26 @@ it('textShadow with var() color before offset', () => {
105
105
  textShadowColor: 'var(--primary-color)',
106
106
  })
107
107
  })
108
+
109
+ // Note: var() for offset-x is not supported because it's ambiguous with color-first syntax
110
+ // e.g., 'var(--x) 20px red' could be interpreted as color=var(--x), offsets=20px red (invalid)
111
+ // To use var() for offsets, offset-x must be a concrete value like '10px var(--y) red'
112
+
113
+ it('textShadow with var() for offset-y', () => {
114
+ expect(transformCss([['text-shadow', '10px var(--y) red']])).toEqual({
115
+ textShadowOffset: { width: 10, height: 'var(--y)' },
116
+ textShadowRadius: 0,
117
+ textShadowColor: 'red',
118
+ })
119
+ })
120
+
121
+ it('textShadow with var() for blur-radius', () => {
122
+ expect(transformCss([['text-shadow', '10px 20px var(--blur) red']])).toEqual({
123
+ textShadowOffset: { width: 10, height: 20 },
124
+ textShadowRadius: 'var(--blur)',
125
+ textShadowColor: 'red',
126
+ })
127
+ })
128
+
129
+ // Note: 'var(--x) var(--y) var(--blur) var(--color)' is not supported
130
+ // because offset-x must be a concrete value to distinguish from color-first syntax
@@ -0,0 +1,385 @@
1
+ import transformCss from '..'
2
+
3
+ // =============================================================================
4
+ // Transform functions with var()
5
+ // =============================================================================
6
+
7
+ describe('transform with var()', () => {
8
+ it('transforms translateX with var()', () => {
9
+ expect(transformCss([['transform', 'translateX(var(--x))']])).toEqual({
10
+ transform: [{ translateX: 'var(--x)' }],
11
+ })
12
+ })
13
+
14
+ it('transforms translateX with var() and fallback', () => {
15
+ expect(transformCss([['transform', 'translateX(var(--x, 10px))']])).toEqual({
16
+ transform: [{ translateX: 'var(--x, 10px)' }],
17
+ })
18
+ })
19
+
20
+ it('transforms multiple transform functions with var()', () => {
21
+ expect(
22
+ transformCss([
23
+ ['transform', 'translateX(var(--x, 10px)) translateY(var(--y, 20px))'],
24
+ ])
25
+ ).toEqual({
26
+ transform: [
27
+ { translateY: 'var(--y, 20px)' },
28
+ { translateX: 'var(--x, 10px)' },
29
+ ],
30
+ })
31
+ })
32
+
33
+ it('transforms rotate with var()', () => {
34
+ expect(transformCss([['transform', 'rotate(var(--angle, 45deg))']])).toEqual(
35
+ {
36
+ transform: [{ rotate: 'var(--angle, 45deg)' }],
37
+ }
38
+ )
39
+ })
40
+
41
+ it('transforms scale with var()', () => {
42
+ expect(transformCss([['transform', 'scale(var(--scale, 1.5))']])).toEqual({
43
+ transform: [{ scale: 'var(--scale, 1.5)' }],
44
+ })
45
+ })
46
+
47
+ it('transforms mixed concrete and var() values', () => {
48
+ expect(
49
+ transformCss([['transform', 'translateX(10px) rotate(var(--angle))']])
50
+ ).toEqual({
51
+ transform: [{ rotate: 'var(--angle)' }, { translateX: 10 }],
52
+ })
53
+ })
54
+
55
+ it('transforms translate with two var() args', () => {
56
+ expect(
57
+ transformCss([['transform', 'translate(var(--x, 10px), var(--y, 20px))']])
58
+ ).toEqual({
59
+ transform: [
60
+ { translateY: 'var(--y, 20px)' },
61
+ { translateX: 'var(--x, 10px)' },
62
+ ],
63
+ })
64
+ })
65
+
66
+ it('transforms scale with two var() args', () => {
67
+ expect(
68
+ transformCss([['transform', 'scale(var(--sx, 1), var(--sy, 2))']])
69
+ ).toEqual({
70
+ transform: [{ scaleY: 'var(--sy, 2)' }, { scaleX: 'var(--sx, 1)' }],
71
+ })
72
+ })
73
+
74
+ it('transforms skew with var()', () => {
75
+ expect(
76
+ transformCss([['transform', 'skew(var(--skew-x, 10deg), var(--skew-y, 5deg))']])
77
+ ).toEqual({
78
+ transform: [
79
+ { skewY: 'var(--skew-y, 5deg)' },
80
+ { skewX: 'var(--skew-x, 10deg)' },
81
+ ],
82
+ })
83
+ })
84
+ })
85
+
86
+ // =============================================================================
87
+ // Box-shadow with multiple var() values and comma-separated shadows
88
+ // =============================================================================
89
+
90
+ describe('box-shadow with complex var()', () => {
91
+ it('transforms box-shadow with var() for offset-x', () => {
92
+ expect(
93
+ transformCss([['box-shadow', 'var(--x) 10px 5px red']])
94
+ ).toEqual({
95
+ boxShadow: 'var(--x) 10px 5px red',
96
+ })
97
+ })
98
+
99
+ it('transforms box-shadow with var() for offset-y', () => {
100
+ expect(
101
+ transformCss([['box-shadow', '10px var(--y) 5px red']])
102
+ ).toEqual({
103
+ boxShadow: '10px var(--y) 5px red',
104
+ })
105
+ })
106
+
107
+ it('transforms box-shadow with var() for blur', () => {
108
+ expect(
109
+ transformCss([['box-shadow', '10px 20px var(--blur) red']])
110
+ ).toEqual({
111
+ boxShadow: '10px 20px var(--blur) red',
112
+ })
113
+ })
114
+
115
+ it('transforms box-shadow with multiple var() values', () => {
116
+ expect(
117
+ transformCss([['box-shadow', 'var(--x) var(--y) var(--blur) var(--color)']])
118
+ ).toEqual({
119
+ boxShadow: 'var(--x) var(--y) var(--blur) var(--color)',
120
+ })
121
+ })
122
+
123
+ it('transforms multiple box-shadows with var()', () => {
124
+ expect(
125
+ transformCss([
126
+ [
127
+ 'box-shadow',
128
+ 'var(--x) var(--y) var(--color, rgba(0, 0, 0, 0.2)), var(--x-2) var(--y-2) var(--color-2, rgba(0, 0, 0, 0.5))',
129
+ ],
130
+ ])
131
+ ).toEqual({
132
+ boxShadow:
133
+ 'var(--x) var(--y) var(--color, rgba(0, 0, 0, 0.2)), var(--x-2) var(--y-2) var(--color-2, rgba(0, 0, 0, 0.5))',
134
+ })
135
+ })
136
+
137
+ it('transforms multiple box-shadows with mixed concrete and var() values', () => {
138
+ expect(
139
+ transformCss([
140
+ ['box-shadow', '10px 20px red, var(--x-2) var(--y-2) blue'],
141
+ ])
142
+ ).toEqual({
143
+ boxShadow: '10px 20px red, var(--x-2) var(--y-2) blue',
144
+ })
145
+ })
146
+
147
+ it('transforms box-shadow with var() having rgba fallback', () => {
148
+ expect(
149
+ transformCss([
150
+ ['box-shadow', '10px 20px var(--blur, 5px) var(--color, rgba(0, 0, 0, 0.2))'],
151
+ ])
152
+ ).toEqual({
153
+ boxShadow: '10px 20px var(--blur, 5px) var(--color, rgba(0, 0, 0, 0.2))',
154
+ })
155
+ })
156
+ })
157
+
158
+ // =============================================================================
159
+ // Shorthand properties with multiple var() values
160
+ // =============================================================================
161
+
162
+ describe('shorthand properties with var()', () => {
163
+ it('transforms padding with multiple var() values', () => {
164
+ expect(
165
+ transformCss([
166
+ ['padding', 'var(--top) var(--right) var(--bottom) var(--left)'],
167
+ ])
168
+ ).toEqual({
169
+ paddingTop: 'var(--top)',
170
+ paddingRight: 'var(--right)',
171
+ paddingBottom: 'var(--bottom)',
172
+ paddingLeft: 'var(--left)',
173
+ })
174
+ })
175
+
176
+ it('transforms padding with var() and fallbacks', () => {
177
+ expect(
178
+ transformCss([
179
+ ['padding', 'var(--top, 10px) var(--right, 20px) var(--bottom, 10px) var(--left, 20px)'],
180
+ ])
181
+ ).toEqual({
182
+ paddingTop: 'var(--top, 10px)',
183
+ paddingRight: 'var(--right, 20px)',
184
+ paddingBottom: 'var(--bottom, 10px)',
185
+ paddingLeft: 'var(--left, 20px)',
186
+ })
187
+ })
188
+
189
+ it('transforms padding with two var() values', () => {
190
+ expect(
191
+ transformCss([['padding', 'var(--vertical) var(--horizontal)']])
192
+ ).toEqual({
193
+ paddingTop: 'var(--vertical)',
194
+ paddingRight: 'var(--horizontal)',
195
+ paddingBottom: 'var(--vertical)',
196
+ paddingLeft: 'var(--horizontal)',
197
+ })
198
+ })
199
+
200
+ it('transforms margin with multiple var() values', () => {
201
+ expect(
202
+ transformCss([
203
+ ['margin', 'var(--top) var(--right) var(--bottom) var(--left)'],
204
+ ])
205
+ ).toEqual({
206
+ marginTop: 'var(--top)',
207
+ marginRight: 'var(--right)',
208
+ marginBottom: 'var(--bottom)',
209
+ marginLeft: 'var(--left)',
210
+ })
211
+ })
212
+
213
+ it('transforms margin with mixed concrete and var() values', () => {
214
+ expect(
215
+ transformCss([['margin', '10px var(--horizontal) 20px']])
216
+ ).toEqual({
217
+ marginTop: 10,
218
+ marginRight: 'var(--horizontal)',
219
+ marginBottom: 20,
220
+ marginLeft: 'var(--horizontal)',
221
+ })
222
+ })
223
+
224
+ it('transforms border-radius with multiple var() values', () => {
225
+ expect(
226
+ transformCss([
227
+ ['border-radius', 'var(--tl) var(--tr) var(--br) var(--bl)'],
228
+ ])
229
+ ).toEqual({
230
+ borderTopLeftRadius: 'var(--tl)',
231
+ borderTopRightRadius: 'var(--tr)',
232
+ borderBottomRightRadius: 'var(--br)',
233
+ borderBottomLeftRadius: 'var(--bl)',
234
+ })
235
+ })
236
+
237
+ it('transforms border-width with multiple var() values', () => {
238
+ expect(
239
+ transformCss([
240
+ ['border-width', 'var(--top) var(--right) var(--bottom) var(--left)'],
241
+ ])
242
+ ).toEqual({
243
+ borderTopWidth: 'var(--top)',
244
+ borderRightWidth: 'var(--right)',
245
+ borderBottomWidth: 'var(--bottom)',
246
+ borderLeftWidth: 'var(--left)',
247
+ })
248
+ })
249
+ })
250
+
251
+ // =============================================================================
252
+ // Animation with var()
253
+ // =============================================================================
254
+
255
+ describe('animation with var()', () => {
256
+ it('transforms animation-duration with var()', () => {
257
+ expect(
258
+ transformCss([['animation-duration', 'var(--duration, 300ms)']])
259
+ ).toEqual({
260
+ animationDuration: 'var(--duration, 300ms)',
261
+ })
262
+ })
263
+
264
+ it('transforms animation-delay with var()', () => {
265
+ expect(
266
+ transformCss([['animation-delay', 'var(--delay, 100ms)']])
267
+ ).toEqual({
268
+ animationDelay: 'var(--delay, 100ms)',
269
+ })
270
+ })
271
+
272
+ it('transforms animation-name with var()', () => {
273
+ expect(transformCss([['animation-name', 'var(--animation)']])).toEqual({
274
+ animationName: 'var(--animation)',
275
+ })
276
+ })
277
+
278
+ it('transforms animation shorthand with var() for duration', () => {
279
+ expect(
280
+ transformCss([['animation', 'fadeIn var(--duration, 300ms) ease']])
281
+ ).toEqual({
282
+ animationName: 'fadeIn',
283
+ animationDuration: 'var(--duration, 300ms)',
284
+ animationTimingFunction: 'ease',
285
+ animationDelay: '0s',
286
+ animationIterationCount: 1,
287
+ animationDirection: 'normal',
288
+ animationFillMode: 'none',
289
+ animationPlayState: 'running',
290
+ })
291
+ })
292
+
293
+ it('transforms multiple animation values with var()', () => {
294
+ expect(
295
+ transformCss([
296
+ ['animation-duration', 'var(--dur1), var(--dur2)'],
297
+ ])
298
+ ).toEqual({
299
+ animationDuration: ['var(--dur1)', 'var(--dur2)'],
300
+ })
301
+ })
302
+ })
303
+
304
+ // =============================================================================
305
+ // Transition with var()
306
+ // =============================================================================
307
+
308
+ describe('transition with var()', () => {
309
+ it('transforms transition-duration with var()', () => {
310
+ expect(
311
+ transformCss([['transition-duration', 'var(--duration, 300ms)']])
312
+ ).toEqual({
313
+ transitionDuration: 'var(--duration, 300ms)',
314
+ })
315
+ })
316
+
317
+ it('transforms transition-delay with var()', () => {
318
+ expect(
319
+ transformCss([['transition-delay', 'var(--delay, 100ms)']])
320
+ ).toEqual({
321
+ transitionDelay: 'var(--delay, 100ms)',
322
+ })
323
+ })
324
+
325
+ it('transforms transition shorthand with var() for duration', () => {
326
+ expect(
327
+ transformCss([['transition', 'opacity var(--duration, 300ms) ease']])
328
+ ).toEqual({
329
+ transitionProperty: 'opacity',
330
+ transitionDuration: 'var(--duration, 300ms)',
331
+ transitionTimingFunction: 'ease',
332
+ transitionDelay: '0s',
333
+ })
334
+ })
335
+
336
+ it('transforms multiple transition values with var()', () => {
337
+ expect(
338
+ transformCss([['transition-duration', 'var(--dur1), var(--dur2)']])
339
+ ).toEqual({
340
+ transitionDuration: ['var(--dur1)', 'var(--dur2)'],
341
+ })
342
+ })
343
+
344
+ it('transforms transition with var() for delay', () => {
345
+ expect(
346
+ transformCss([['transition', 'opacity 300ms ease var(--delay)']])
347
+ ).toEqual({
348
+ transitionProperty: 'opacity',
349
+ transitionDuration: '300ms',
350
+ transitionTimingFunction: 'ease',
351
+ transitionDelay: 'var(--delay)',
352
+ })
353
+ })
354
+ })
355
+
356
+ // =============================================================================
357
+ // Edge cases with var() fallbacks containing commas
358
+ // =============================================================================
359
+
360
+ describe('var() with complex fallbacks', () => {
361
+ it('handles var() with rgba fallback containing commas', () => {
362
+ expect(
363
+ transformCss([['color', 'var(--color, rgba(100, 100, 100, 0.5))']])
364
+ ).toEqual({
365
+ color: 'var(--color, rgba(100, 100, 100, 0.5))',
366
+ })
367
+ })
368
+
369
+ it('handles var() with rgb fallback containing commas', () => {
370
+ expect(
371
+ transformCss([['color', 'var(--color, rgb(100, 100, 100))']])
372
+ ).toEqual({
373
+ color: 'var(--color, rgb(100, 100, 100))',
374
+ })
375
+ })
376
+
377
+ it('handles nested var() fallbacks', () => {
378
+ // This might not work but let's test it
379
+ expect(
380
+ transformCss([['color', 'var(--primary, var(--fallback, red))']])
381
+ ).toEqual({
382
+ color: 'var(--primary, var(--fallback, red))',
383
+ })
384
+ })
385
+ })
package/src/tokenTypes.js CHANGED
@@ -29,7 +29,8 @@ const matchColor = node => {
29
29
 
30
30
  const matchVariable = node => {
31
31
  if (
32
- (node.type !== 'function' && node.value !== 'var') ||
32
+ node.type !== 'function' ||
33
+ node.value !== 'var' ||
33
34
  node.nodes.length === 0
34
35
  )
35
36
  return null
@@ -1,4 +1,4 @@
1
- import { SPACE, COMMA, IDENT, TIME, NUMBER, NONE } from '../tokenTypes'
1
+ import { SPACE, COMMA, IDENT, TIME, NUMBER, NONE, VARIABLE } from '../tokenTypes'
2
2
 
3
3
  // Timing function keywords
4
4
  const timingFunctionKeywords = [
@@ -67,7 +67,7 @@ const parseCommaSeparatedValues = (tokenStream, parseValue) => {
67
67
  // Transform for animation-name property
68
68
  export const animationName = tokenStream => {
69
69
  const names = parseCommaSeparatedValues(tokenStream, ts =>
70
- ts.expect(IDENT, NONE)
70
+ ts.expect(IDENT, NONE, VARIABLE)
71
71
  )
72
72
  return { animationName: names }
73
73
  }
@@ -75,7 +75,7 @@ export const animationName = tokenStream => {
75
75
  // Transform for animation-duration property
76
76
  export const animationDuration = tokenStream => {
77
77
  const durations = parseCommaSeparatedValues(tokenStream, ts =>
78
- ts.expect(TIME)
78
+ ts.expect(TIME, VARIABLE)
79
79
  )
80
80
  return { animationDuration: durations }
81
81
  }
@@ -107,7 +107,9 @@ export const animationTimingFunction = tokenStream => {
107
107
 
108
108
  // Transform for animation-delay property
109
109
  export const animationDelay = tokenStream => {
110
- const delays = parseCommaSeparatedValues(tokenStream, ts => ts.expect(TIME))
110
+ const delays = parseCommaSeparatedValues(tokenStream, ts =>
111
+ ts.expect(TIME, VARIABLE)
112
+ )
111
113
  return { animationDelay: delays }
112
114
  }
113
115
 
@@ -222,6 +224,17 @@ export default tokenStream => {
222
224
  continue
223
225
  }
224
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
+
225
238
  // Check for timing function (cubic-bezier or steps)
226
239
  const funcStream = tokenStream.matchesFunction()
227
240
  if (funcStream) {
@@ -247,17 +260,6 @@ export default tokenStream => {
247
260
  continue
248
261
  }
249
262
 
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
263
  // Match identifier
262
264
  if (tokenStream.matches(IDENT)) {
263
265
  const value = tokenStream.lastValue
@@ -53,7 +53,7 @@ const borderRadius = directionFactory({
53
53
  })
54
54
  const borderWidth = directionFactory({ prefix: 'border', suffix: 'Width' })
55
55
  const margin = directionFactory({
56
- types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO],
56
+ types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO, VARIABLE],
57
57
  prefix: 'margin',
58
58
  })
59
59
  const padding = directionFactory({ prefix: 'padding' })
@@ -1,4 +1,12 @@
1
- import { SPACE, COMMA, LENGTH, NUMBER, ANGLE, PERCENT } from '../tokenTypes'
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,
@@ -1,5 +1,5 @@
1
1
  import camelizeStyleName from 'camelize'
2
- import { SPACE, COMMA, IDENT, TIME, NONE } from '../tokenTypes'
2
+ import { SPACE, COMMA, IDENT, TIME, NONE, VARIABLE } from '../tokenTypes'
3
3
 
4
4
  // Timing function keywords
5
5
  const timingFunctionKeywords = [
@@ -65,7 +65,7 @@ export const transitionProperty = tokenStream => {
65
65
  // Transform for transition-duration
66
66
  export const transitionDuration = tokenStream => {
67
67
  const durations = parseCommaSeparatedValues(tokenStream, ts =>
68
- ts.expect(TIME)
68
+ ts.expect(TIME, VARIABLE)
69
69
  )
70
70
  return { transitionDuration: durations }
71
71
  }
@@ -97,7 +97,9 @@ export const transitionTimingFunction = tokenStream => {
97
97
 
98
98
  // Transform for transition-delay
99
99
  export const transitionDelay = tokenStream => {
100
- const delays = parseCommaSeparatedValues(tokenStream, ts => ts.expect(TIME))
100
+ const delays = parseCommaSeparatedValues(tokenStream, ts =>
101
+ ts.expect(TIME, VARIABLE)
102
+ )
101
103
  return { transitionDelay: delays }
102
104
  }
103
105
 
@@ -158,10 +160,20 @@ export default tokenStream => {
158
160
  continue
159
161
  }
160
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
+
161
174
  // Check for timing function (cubic-bezier or steps)
162
175
  const funcStream = tokenStream.matchesFunction()
163
176
  if (funcStream) {
164
- // It's a function like cubic-bezier() or steps()
165
177
  const funcName = funcStream.functionName
166
178
  const args = []
167
179
  while (funcStream.hasTokens()) {
@@ -189,16 +201,6 @@ export default tokenStream => {
189
201
  continue
190
202
  }
191
203
 
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
204
  // Try to match as word for property names like 'all', 'opacity', etc.
203
205
  const wordMatch = tokenStream.expect(node => {
204
206
  if (node.type === 'word') return node.value
@@ -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
- offsetY = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT)
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))