Skip to content

Commit 2c9ff3d

Browse files
committed
added support to rotate, skew, translate and transform-none
1 parent 525b20a commit 2c9ff3d

6 files changed

Lines changed: 421 additions & 57 deletions

File tree

src/UtilityParser.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { widthHeight, size, minMaxWidthHeight } from './resolve/width-height';
1616
import { letterSpacing } from './resolve/letter-spacing';
1717
import { opacity } from './resolve/opacity';
1818
import { shadowOpacity, shadowOffset } from './resolve/shadow';
19-
import { transform } from './resolve/transform';
19+
import { rotate, scale, skew, transformNone, translate } from './resolve/transform';
2020

2121
export default class UtilityParser {
2222
private position = 0;
@@ -302,10 +302,29 @@ export default class UtilityParser {
302302
}
303303

304304
if (this.consumePeeked(`scale-`)) {
305-
style = transform(`scale`, this.rest, this.context, theme?.scale);
305+
style = scale(this.rest, this.context, theme?.scale);
306306
if (style) return style;
307307
}
308308

309+
if (this.consumePeeked(`rotate-`)) {
310+
style = rotate(this.rest, this.context, theme?.rotate);
311+
if (style) return style;
312+
}
313+
314+
if (this.consumePeeked(`skew-`)) {
315+
style = skew(this.rest, this.context, theme?.skew);
316+
if (style) return style;
317+
}
318+
319+
if (this.consumePeeked(`translate-`)) {
320+
style = translate(this.rest, this.context, theme?.translate);
321+
if (style) return style;
322+
}
323+
324+
if (this.consumePeeked(`transform-none`)) {
325+
return transformNone();
326+
}
327+
309328
h.warn(`\`${this.rest}\` unknown or invalid utility`);
310329
return null;
311330
}

src/__tests__/transform.spec.ts

Lines changed: 252 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,263 @@ describe(`transform utilities`, () => {
77
tw = create();
88
});
99

10-
const cases: Array<[string, Record<'transform', Record<string, number>[]>]> = [
11-
[`scale-0`, { transform: [{ scale: 0 }] }],
12-
[`scale-x-0`, { transform: [{ scaleX: 0 }] }],
13-
[`scale-y-0`, { transform: [{ scaleY: 0 }] }],
14-
[`scale-50`, { transform: [{ scale: 0.5 }] }],
15-
[`scale-x-50`, { transform: [{ scaleX: 0.5 }] }],
16-
[`scale-y-50`, { transform: [{ scaleY: 0.5 }] }],
17-
[`scale-100`, { transform: [{ scale: 1 }] }],
18-
[`scale-x-100`, { transform: [{ scaleX: 1 }] }],
19-
[`scale-y-100`, { transform: [{ scaleY: 1 }] }],
20-
[`scale-150`, { transform: [{ scale: 1.5 }] }],
21-
[`scale-x-150`, { transform: [{ scaleX: 1.5 }] }],
22-
[`scale-y-150`, { transform: [{ scaleY: 1.5 }] }],
23-
[`-scale-50`, { transform: [{ scale: -0.5 }] }],
24-
[`-scale-x-50`, { transform: [{ scaleX: -0.5 }] }],
25-
[`-scale-y-50`, { transform: [{ scaleY: -0.5 }] }],
26-
[`scale-[1.7]`, { transform: [{ scale: 1.7 }] }],
27-
[`scale-x-[1.7]`, { transform: [{ scaleX: 1.7 }] }],
28-
[`scale-y-[1.7]`, { transform: [{ scaleY: 1.7 }] }],
29-
];
30-
31-
test.each(cases)(`tw\`%s\` -> %s`, (utility, expected) => {
32-
expect(tw.style(utility)).toMatchObject(expected);
10+
describe(`scale`, () => {
11+
const cases: Array<[string, Record<'transform', Record<string, number>[]>]> = [
12+
[`scale-0`, { transform: [{ scale: 0 }] }],
13+
[`scale-x-0`, { transform: [{ scaleX: 0 }] }],
14+
[`scale-y-0`, { transform: [{ scaleY: 0 }] }],
15+
[`scale-50`, { transform: [{ scale: 0.5 }] }],
16+
[`scale-x-50`, { transform: [{ scaleX: 0.5 }] }],
17+
[`scale-y-50`, { transform: [{ scaleY: 0.5 }] }],
18+
[`-scale-50`, { transform: [{ scale: -0.5 }] }],
19+
[`-scale-x-50`, { transform: [{ scaleX: -0.5 }] }],
20+
[`-scale-y-50`, { transform: [{ scaleY: -0.5 }] }],
21+
22+
// arbitrary
23+
[`scale-[1.7]`, { transform: [{ scale: 1.7 }] }],
24+
[`scale-x-[1.7]`, { transform: [{ scaleX: 1.7 }] }],
25+
[`scale-y-[1.7]`, { transform: [{ scaleY: 1.7 }] }],
26+
27+
// not configged
28+
[`scale-99`, { transform: [{ scale: 0.99 }] }],
29+
[`scale-x-99`, { transform: [{ scaleX: 0.99 }] }],
30+
[`scale-y-99`, { transform: [{ scaleY: 0.99 }] }],
31+
];
32+
33+
test.each(cases)(`tw\`%s\` -> %s`, (utility, expected) => {
34+
expect(tw.style(utility)).toMatchObject(expected);
35+
});
36+
37+
test(`scale w/extended theme`, () => {
38+
tw = create({
39+
theme: {
40+
extend: {
41+
scale: {
42+
custom: `1.99`,
43+
},
44+
},
45+
},
46+
});
47+
48+
expect(tw.style(`scale-custom`)).toMatchObject({ transform: [{ scale: 1.99 }] });
49+
expect(tw.style(`scale-x-custom`)).toMatchObject({ transform: [{ scaleX: 1.99 }] });
50+
expect(tw.style(`scale-y-custom`)).toMatchObject({ transform: [{ scaleY: 1.99 }] });
51+
});
52+
53+
test(`combine repeated scale utilities into one`, () => {
54+
expect(tw.style(`scale-50 scale-100`)).toMatchObject({ transform: [{ scale: 1 }] });
55+
expect(tw.style(`scale-x-50 scale-x-100`)).toMatchObject({
56+
transform: [{ scaleX: 1 }],
57+
});
58+
expect(tw.style(`scale-y-50 scale-y-100`)).toMatchObject({
59+
transform: [{ scaleY: 1 }],
60+
});
61+
});
62+
});
63+
64+
describe(`rotate`, () => {
65+
const cases: Array<[string, Record<'transform', Record<string, string>[]>]> = [
66+
[`rotate-0`, { transform: [{ rotate: `0deg` }] }],
67+
[`rotate-x-0`, { transform: [{ rotateX: `0deg` }] }],
68+
[`rotate-y-0`, { transform: [{ rotateY: `0deg` }] }],
69+
[`rotate-z-0`, { transform: [{ rotateZ: `0deg` }] }],
70+
[`rotate-90`, { transform: [{ rotate: `90deg` }] }],
71+
[`rotate-x-90`, { transform: [{ rotateX: `90deg` }] }],
72+
[`rotate-y-90`, { transform: [{ rotateY: `90deg` }] }],
73+
[`rotate-z-90`, { transform: [{ rotateZ: `90deg` }] }],
74+
[`-rotate-90`, { transform: [{ rotate: `-90deg` }] }],
75+
[`-rotate-x-90`, { transform: [{ rotateX: `-90deg` }] }],
76+
[`-rotate-y-90`, { transform: [{ rotateY: `-90deg` }] }],
77+
[`-rotate-z-90`, { transform: [{ rotateZ: `-90deg` }] }],
78+
79+
// arbitrary
80+
[`rotate-[90deg]`, { transform: [{ rotate: `90deg` }] }],
81+
[`rotate-x-[90deg]`, { transform: [{ rotateX: `90deg` }] }],
82+
[`rotate-y-[90deg]`, { transform: [{ rotateY: `90deg` }] }],
83+
[`rotate-z-[90deg]`, { transform: [{ rotateZ: `90deg` }] }],
84+
[`rotate-[3.142rad]`, { transform: [{ rotate: `3.142rad` }] }],
85+
[`rotate-x-[3.142rad]`, { transform: [{ rotateX: `3.142rad` }] }],
86+
[`rotate-y-[3.142rad]`, { transform: [{ rotateY: `3.142rad` }] }],
87+
[`rotate-z-[3.142rad]`, { transform: [{ rotateZ: `3.142rad` }] }],
88+
89+
// not configged
90+
[`rotate-99`, { transform: [{ rotate: `99deg` }] }],
91+
[`rotate-x-99`, { transform: [{ rotateX: `99deg` }] }],
92+
[`rotate-y-99`, { transform: [{ rotateY: `99deg` }] }],
93+
[`rotate-z-99`, { transform: [{ rotateZ: `99deg` }] }],
94+
];
95+
96+
test.each(cases)(`tw\`%s\` -> %s`, (utility, expected) => {
97+
expect(tw.style(utility)).toMatchObject(expected);
98+
});
99+
100+
test(`rotate w/extended theme`, () => {
101+
tw = create({
102+
theme: {
103+
extend: {
104+
rotate: {
105+
custom: `1.99rad`,
106+
},
107+
},
108+
},
109+
});
110+
111+
expect(tw.style(`rotate-custom`)).toMatchObject({
112+
transform: [{ rotate: `1.99rad` }],
113+
});
114+
expect(tw.style(`rotate-x-custom`)).toMatchObject({
115+
transform: [{ rotateX: `1.99rad` }],
116+
});
117+
expect(tw.style(`rotate-y-custom`)).toMatchObject({
118+
transform: [{ rotateY: `1.99rad` }],
119+
});
120+
expect(tw.style(`rotate-z-custom`)).toMatchObject({
121+
transform: [{ rotateZ: `1.99rad` }],
122+
});
123+
});
124+
125+
test(`combine repeated rotate utilities into one`, () => {
126+
expect(tw.style(`rotate-50 rotate-100`)).toMatchObject({ transform: [{ rotate: `100deg` }] });
127+
expect(tw.style(`rotate-x-50 rotate-x-100`)).toMatchObject({
128+
transform: [{ rotateX: `100deg` }],
129+
});
130+
expect(tw.style(`rotate-y-50 rotate-y-100`)).toMatchObject({
131+
transform: [{ rotateY: `100deg` }],
132+
});
133+
});
134+
});
135+
136+
describe(`skew`, () => {
137+
const cases: Array<[string, Record<'transform', Record<string, string>[]>]> = [
138+
[`skew-x-0`, { transform: [{ skewX: `0deg` }] }],
139+
[`skew-y-0`, { transform: [{ skewY: `0deg` }] }],
140+
[`skew-x-12`, { transform: [{ skewX: `12deg` }] }],
141+
[`skew-y-12`, { transform: [{ skewY: `12deg` }] }],
142+
143+
// arbitrary
144+
[`skew-x-[90deg]`, { transform: [{ skewX: `90deg` }] }],
145+
[`skew-y-[90deg]`, { transform: [{ skewY: `90deg` }] }],
146+
[`skew-x-[3.142rad]`, { transform: [{ skewX: `3.142rad` }] }],
147+
[`skew-y-[3.142rad]`, { transform: [{ skewY: `3.142rad` }] }],
148+
149+
// not configged
150+
[`skew-x-99`, { transform: [{ skewX: `99deg` }] }],
151+
[`skew-y-99`, { transform: [{ skewY: `99deg` }] }],
152+
];
153+
154+
test.each(cases)(`tw\`%s\` -> %s`, (utility, expected) => {
155+
expect(tw.style(utility)).toMatchObject(expected);
156+
});
157+
158+
test(`skew w/extended theme`, () => {
159+
tw = create({
160+
theme: {
161+
extend: {
162+
skew: {
163+
custom: `1.99rad`,
164+
},
165+
},
166+
},
167+
});
168+
169+
expect(tw.style(`skew-x-custom`)).toMatchObject({
170+
transform: [{ skewX: `1.99rad` }],
171+
});
172+
expect(tw.style(`skew-y-custom`)).toMatchObject({
173+
transform: [{ skewY: `1.99rad` }],
174+
});
175+
});
176+
177+
test(`combine repeated skew utilities into one`, () => {
178+
expect(tw.style(`skew-x-50 skew-x-100`)).toMatchObject({
179+
transform: [{ skewX: `100deg` }],
180+
});
181+
expect(tw.style(`skew-y-50 skew-y-100`)).toMatchObject({
182+
transform: [{ skewY: `100deg` }],
183+
});
184+
});
33185
});
34186

35-
test(`transform w/extended theme`, () => {
36-
tw = create({
37-
theme: {
38-
extend: {
39-
scale: {
40-
custom: `1.99`,
187+
describe(`translate`, () => {
188+
const cases: Array<[string, Record<'transform', Record<string, number>[]> | object]> =
189+
[
190+
[`translate-x-0`, { transform: [{ translateX: 0 }] }],
191+
[`translate-y-0`, { transform: [{ translateY: 0 }] }],
192+
[`translate-x-px`, { transform: [{ translateX: 1 }] }],
193+
[`translate-y-px`, { transform: [{ translateY: 1 }] }],
194+
[`translate-x-0.5`, { transform: [{ translateX: 2 }] }],
195+
[`translate-y-0.5`, { transform: [{ translateY: 2 }] }],
196+
[`-translate-x-px`, { transform: [{ translateX: -1 }] }],
197+
[`-translate-y-px`, { transform: [{ translateY: -1 }] }],
198+
[`-translate-x-0.5`, { transform: [{ translateX: -2 }] }],
199+
[`-translate-y-0.5`, { transform: [{ translateY: -2 }] }],
200+
201+
// arbitrary
202+
[`translate-x-[17rem]`, { transform: [{ translateX: 272 }] }],
203+
[`translate-y-[17rem]`, { transform: [{ translateY: 272 }] }],
204+
205+
// not configged
206+
[`translate-x-81`, { transform: [{ translateX: (81 / 4) * 16 }] }],
207+
[`translate-y-81`, { transform: [{ translateY: (81 / 4) * 16 }] }],
208+
209+
// unsupported
210+
[`translate-x-full`, {}],
211+
[`translate-y-full`, {}],
212+
[`translate-x-1/2`, {}],
213+
[`translate-y-1/2`, {}],
214+
];
215+
216+
test.each(cases)(`tw\`%s\` -> %s`, (utility, expected) => {
217+
expect(tw.style(utility)).toMatchObject(expected);
218+
});
219+
220+
test(`translate w/extended theme`, () => {
221+
tw = create({
222+
theme: {
223+
extend: {
224+
translate: {
225+
'4.25': `17rem`,
226+
},
41227
},
42228
},
43-
},
229+
});
230+
231+
expect(tw.style(`translate-x-4.25`)).toMatchObject({
232+
transform: [{ translateX: 272 }],
233+
});
234+
expect(tw.style(`translate-y-4.25`)).toMatchObject({
235+
transform: [{ translateY: 272 }],
236+
});
44237
});
45238

46-
expect(tw.style(`scale-custom`)).toMatchObject({ transform: [{ scale: 1.99 }] });
239+
test(`combine repeated translate utilities into one`, () => {
240+
expect(tw.style(`translate-x-2 translate-x-4`)).toMatchObject({
241+
transform: [{ translateX: 16 }],
242+
});
243+
expect(tw.style(`translate-y-2 translate-y-4`)).toMatchObject({
244+
transform: [{ translateY: 16 }],
245+
});
246+
});
247+
});
248+
249+
test(`combine multiple transform utilities `, () => {
250+
expect(tw.style(`scale-50 scale-x-100 scale-y-150 rotate-0 rotate-x-90 rotate-y-45 skew-x-99 skew-y-99 translate-x-px translate-y-px`)).toMatchObject({
251+
transform: [
252+
{ scale: 0.5 },
253+
{ scaleX: 1 },
254+
{ scaleY: 1.5 },
255+
{ rotate: `0deg` },
256+
{ rotateX: `90deg` },
257+
{ rotateY: `45deg` },
258+
{ skewX: `99deg` },
259+
{ skewY: `99deg` },
260+
{ translateX: 1 },
261+
{ translateY: 1 },
262+
],
263+
});
264+
});
265+
266+
test(`transform-none`, () => {
267+
expect(tw.style(`scale-50 scale-x-100 scale-y-150 rotate-0 rotate-x-90 rotate-y-45 skew-x-99 skew-y-99 translate-x-px translate-y-px transform-none`)).toMatchObject({ transform: [] });
47268
});
48269
});

src/helpers.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export function parseNumericValue(
4343
return [number, Unit.vw];
4444
case `vh`:
4545
return [number, Unit.vh];
46+
case `deg`:
47+
return [number, Unit.deg];
48+
case `rad`:
49+
return [number, Unit.rad];
4650
default:
4751
return null;
4852
}
@@ -116,6 +120,9 @@ export function toStyleVal(
116120
return null;
117121
}
118122
return device.windowDimensions.height * (number / 100);
123+
case Unit.deg:
124+
case Unit.rad:
125+
return `${number * (isNegative ? -1 : 1)}${unit}`;
119126
default:
120127
return null;
121128
}
@@ -173,8 +180,6 @@ export function parseUnconfigged(
173180
}
174181
if (value[0] === `[`) {
175182
value = value.slice(1, -1);
176-
const style = unconfiggedStyleVal(value, { ...context, isArbitraryValue: true });
177-
if (style) return style;
178183
}
179184
return unconfiggedStyleVal(value, context);
180185
}
@@ -213,7 +218,7 @@ function unconfiggedStyleVal(
213218
// not sure if this is the right approach, but this allows arbitrary
214219
// non-bracket numbers, like top-73 and it INFERS the meaning to be
215220
// tailwind's default scale for spacing, which is 1 = 0.25rem
216-
if (unit === Unit.none && !context.isArbitraryValue) {
221+
if (unit === Unit.none) {
217222
number = number / 4;
218223
unit = Unit.rem;
219224
}

0 commit comments

Comments
 (0)