Skip to content

Commit efca4b7

Browse files
authored
Merge pull request #68 from tenphi/fix-canonical-func-names
fix(parser): preserve canonical casing for CSS transform function names
2 parents fc81dde + 9dd1e81 commit efca4b7

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tenphi/tasty': patch
3+
---
4+
5+
Preserve canonical casing for CSS transform function names (e.g. `translateX`, `scaleY`) in parser output instead of lowercasing them.

src/parser/classify.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
RE_RAW_UNIT,
99
RE_UNIT_NUM,
1010
VALUE_KEYWORDS,
11+
canonicalFuncName,
1112
} from './const';
1213
import { StyleParser } from './parser';
1314
import type { ParserOptions, ProcessedStyle } from './types';
@@ -352,7 +353,10 @@ export function classify(
352353
if (COLOR_FUNCS.has(fname)) {
353354
// Process inner to expand nested colors or units.
354355
const argProcessed = recurse(inner).output.replace(/,\s+/g, ','); // color funcs expect no spaces after commas
355-
return { bucket: Bucket.Color, processed: `${fname}(${argProcessed})` };
356+
return {
357+
bucket: Bucket.Color,
358+
processed: `${canonicalFuncName(fname)}(${argProcessed})`,
359+
};
356360
}
357361

358362
// user function (provided via opts)
@@ -367,7 +371,10 @@ export function classify(
367371

368372
// generic: process inner and rebuild
369373
const argProcessed = recurse(inner).output;
370-
return { bucket: Bucket.Value, processed: `${fname}(${argProcessed})` };
374+
return {
375+
bucket: Bucket.Value,
376+
processed: `${canonicalFuncName(fname)}(${argProcessed})`,
377+
};
371378
}
372379

373380
// 6. Color fallback syntax: (#name, fallback)

src/parser/const.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,21 @@ export const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
2929
export const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
3030
// Matches raw CSS unit values like "8px", "1rem", "0.5em" - captures number and unit separately
3131
export const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
32+
33+
const CANONICAL_FUNC_CASE = new Map([
34+
['translatex', 'translateX'],
35+
['translatey', 'translateY'],
36+
['translatez', 'translateZ'],
37+
['scalex', 'scaleX'],
38+
['scaley', 'scaleY'],
39+
['scalez', 'scaleZ'],
40+
['rotatex', 'rotateX'],
41+
['rotatey', 'rotateY'],
42+
['rotatez', 'rotateZ'],
43+
['skewx', 'skewX'],
44+
['skewy', 'skewY'],
45+
]);
46+
47+
export function canonicalFuncName(lowered: string): string {
48+
return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
49+
}

src/parser/parser.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,35 @@ describe('StyleProcessor', () => {
597597
]);
598598
});
599599

600+
test('preserves canonical casing for CSS function names', () => {
601+
expect(parser.process('translateX(10px)').output).toBe('translateX(10px)');
602+
expect(parser.process('translateY(20%)').output).toBe('translateY(20%)');
603+
expect(parser.process('translateZ(5px)').output).toBe('translateZ(5px)');
604+
expect(parser.process('scaleX(1.5)').output).toBe('scaleX(1.5)');
605+
expect(parser.process('scaleY(2)').output).toBe('scaleY(2)');
606+
expect(parser.process('scaleZ(0.5)').output).toBe('scaleZ(0.5)');
607+
expect(parser.process('rotateX(45deg)').output).toBe('rotateX(45deg)');
608+
expect(parser.process('rotateY(90deg)').output).toBe('rotateY(90deg)');
609+
expect(parser.process('rotateZ(180deg)').output).toBe('rotateZ(180deg)');
610+
expect(parser.process('skewX(10deg)').output).toBe('skewX(10deg)');
611+
expect(parser.process('skewY(20deg)').output).toBe('skewY(20deg)');
612+
});
613+
614+
test('lowercase CSS functions remain unaffected', () => {
615+
expect(parser.process('calc(100% - 10px)').output).toBe(
616+
'calc(100% - 10px)',
617+
);
618+
expect(parser.process('min(1x, 100px)').output).toBe('min(8px, 100px)');
619+
expect(parser.process('clamp(10px, 50%, 200px)').output).toBe(
620+
'clamp(10px, 50%, 200px)',
621+
);
622+
});
623+
624+
test('canonical casing works inside nested functions', () => {
625+
const result = parser.process('drop-shadow(0 0 10px translateX(5px))');
626+
expect(result.output).toBe('drop-shadow(0 0 10px translateX(5px))');
627+
});
628+
600629
test('provides input property with original unparsed string for each group', () => {
601630
// Single group
602631
const result1 = parser.process('1x 2x #purple');

0 commit comments

Comments
 (0)