Skip to content

Commit f827d9d

Browse files
authored
fix: <ratio> parsing (#81)
1 parent b491572 commit f827d9d

File tree

2 files changed

+77
-21
lines changed

2 files changed

+77
-21
lines changed

src/compiler/declarations.ts

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ export function parseUnparsedDeclaration(
915915
builder.descriptorProperty = property;
916916

917917
if (unparsedRuntimeParsing.has(property)) {
918-
const args = parseUnparsed(declaration.value.value, builder);
918+
const args = parseUnparsed(declaration.value.value, builder, property);
919919

920920
if (property === "animation") {
921921
builder.addDescriptor("animation", [
@@ -932,7 +932,7 @@ export function parseUnparsedDeclaration(
932932
]);
933933
}
934934
} else {
935-
const value = parseUnparsed(declaration.value.value, builder);
935+
const value = parseUnparsed(declaration.value.value, builder, property);
936936

937937
builder.addDescriptor(property, value);
938938

@@ -958,13 +958,13 @@ export function parseCustomDeclaration(
958958
) {
959959
builder.addDescriptor(
960960
property,
961-
parseUnparsed(declaration.value.value, builder, allowAuto.has(property)),
961+
parseUnparsed(declaration.value.value, builder, property),
962962
);
963963
} else if (property === "-webkit-line-clamp") {
964964
builder.addMapping({ [property]: ["numberOfLines"] });
965965
builder.addDescriptor(
966966
property,
967-
parseUnparsed(declaration.value.value, builder, allowAuto.has(property)),
967+
parseUnparsed(declaration.value.value, builder, property),
968968
);
969969
} else {
970970
builder.addWarning("property", declaration.value.name);
@@ -974,10 +974,13 @@ export function parseCustomDeclaration(
974974
export function reduceParseUnparsed(
975975
tokenOrValues: TokenOrValue[],
976976
builder: StylesheetBuilder,
977+
property: string,
977978
allowAuto: boolean,
978979
): StyleDescriptor {
979980
const result = tokenOrValues
980-
.map((tokenOrValue) => parseUnparsed(tokenOrValue, builder, allowAuto))
981+
.map((tokenOrValue) =>
982+
parseUnparsed(tokenOrValue, builder, property, allowAuto),
983+
)
981984
.filter((v) => v !== undefined);
982985

983986
if (result.length === 0) {
@@ -996,6 +999,8 @@ export function reduceParseUnparsed(
996999
}
9971000
}
9981001

1002+
// Groups are the tokens grouped together by comma location
1003+
// If a group only has 1 item, it shouldn't be an array
9991004
groups = groups.flatMap((group): StyleDescriptor[] => {
10001005
if (!Array.isArray(group)) {
10011006
return [];
@@ -1011,6 +1016,17 @@ export function reduceParseUnparsed(
10111016
} else {
10121017
return [first];
10131018
}
1019+
} else if (
1020+
// This is a special case for <ratio> values
1021+
group.includes("/") &&
1022+
group.every((item) =>
1023+
typeof item === "string" && item === "/"
1024+
? item
1025+
: typeof item === "number",
1026+
)
1027+
) {
1028+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
1029+
return [group.join(" ")];
10141030
} else {
10151031
return [group];
10161032
}
@@ -1022,12 +1038,13 @@ export function reduceParseUnparsed(
10221038
export function unparsedFunction(
10231039
token: Extract<TokenOrValue, { type: "function" }>,
10241040
builder: StylesheetBuilder,
1041+
property: string,
10251042
allowAuto: boolean,
10261043
): StyleFunction {
10271044
return [
10281045
{},
10291046
token.value.name,
1030-
reduceParseUnparsed(token.value.arguments, builder, allowAuto),
1047+
reduceParseUnparsed(token.value.arguments, builder, property, allowAuto),
10311048
];
10321049
}
10331050

@@ -1044,7 +1061,8 @@ export function parseUnparsed(
10441061
| undefined
10451062
| null,
10461063
builder: StylesheetBuilder,
1047-
allowAuto = false,
1064+
property: string,
1065+
allowAuto = allowAutoProperties.has(property),
10481066
): StyleDescriptor {
10491067
if (tokenOrValue === undefined || tokenOrValue === null) {
10501068
return;
@@ -1067,21 +1085,32 @@ export function parseUnparsed(
10671085
}
10681086

10691087
if (Array.isArray(tokenOrValue)) {
1070-
const args = reduceParseUnparsed(tokenOrValue, builder, allowAuto);
1088+
const args = reduceParseUnparsed(
1089+
tokenOrValue,
1090+
builder,
1091+
property,
1092+
allowAuto,
1093+
);
10711094
if (!args) return;
10721095
if (Array.isArray(args) && args.length === 1) return args[0];
10731096
return args;
10741097
}
10751098

10761099
switch (tokenOrValue.type) {
10771100
case "unresolved-color": {
1078-
return parseUnresolvedColor(tokenOrValue.value, builder, allowAuto);
1101+
return parseUnresolvedColor(
1102+
tokenOrValue.value,
1103+
builder,
1104+
property,
1105+
allowAuto,
1106+
);
10791107
}
10801108
case "var": {
10811109
let args: StyleDescriptor = tokenOrValue.value.name.ident.slice(2);
10821110
const fallback = parseUnparsed(
10831111
tokenOrValue.value.fallback,
10841112
builder,
1113+
property,
10851114
allowAuto,
10861115
);
10871116
if (fallback !== undefined) {
@@ -1104,7 +1133,7 @@ export function parseUnparsed(
11041133
case "translateX":
11051134
case "translateY":
11061135
tokenOrValue.value.name = `@${tokenOrValue.value.name}`;
1107-
return unparsedFunction(tokenOrValue, builder, allowAuto);
1136+
return unparsedFunction(tokenOrValue, builder, property, allowAuto);
11081137
case "blur":
11091138
case "brightness":
11101139
case "contrast":
@@ -1129,7 +1158,7 @@ export function parseUnparsed(
11291158
case "sepia":
11301159
case "shadow":
11311160
case "steps":
1132-
return unparsedFunction(tokenOrValue, builder, allowAuto);
1161+
return unparsedFunction(tokenOrValue, builder, property, allowAuto);
11331162
case "hairlineWidth":
11341163
return [{}, tokenOrValue.value.name, []];
11351164
case "calc":
@@ -1140,6 +1169,7 @@ export function parseUnparsed(
11401169
tokenOrValue.value.name,
11411170
tokenOrValue.value.arguments,
11421171
builder,
1172+
property,
11431173
);
11441174
default: {
11451175
builder.addWarning("value", `${tokenOrValue.value.name}()`);
@@ -1188,11 +1218,16 @@ export function parseUnparsed(
11881218
return parseDimension(tokenOrValue.value, builder);
11891219
case "comma":
11901220
return CommaSeparator as unknown as StyleDescriptor;
1221+
case "delim": {
1222+
if (property === "aspect-ratio" && tokenOrValue.value.value === "/") {
1223+
return tokenOrValue.value.value;
1224+
}
1225+
return;
1226+
}
11911227
case "at-keyword":
11921228
case "hash":
11931229
case "id-hash":
11941230
case "unquoted-url":
1195-
case "delim":
11961231
case "white-space":
11971232
case "comment":
11981233
case "colon":
@@ -2440,7 +2475,7 @@ export function parseDimensionPercentageFor_LengthValue(
24402475
}
24412476
}
24422477

2443-
const allowAuto = new Set(["pointer-events"]);
2478+
const allowAutoProperties = new Set(["pointer-events"]);
24442479

24452480
export function parseEnv(
24462481
value: EnvironmentVariable,
@@ -2458,7 +2493,7 @@ export function parseEnv(
24582493
"var",
24592494
[
24602495
`--react-native-css-${value.name.value}`,
2461-
parseUnparsed(value.fallback, builder),
2496+
parseUnparsed(value.fallback, builder, value.name.value),
24622497
],
24632498
1,
24642499
];
@@ -2481,8 +2516,9 @@ export function parseCalcFn(
24812516
name: string,
24822517
tokens: TokenOrValue[],
24832518
builder: StylesheetBuilder,
2519+
property: string,
24842520
): StyleDescriptor {
2485-
const args = parseCalcArguments(tokens, builder);
2521+
const args = parseCalcArguments(tokens, builder, property);
24862522
if (args) {
24872523
return [{}, name, args];
24882524
}
@@ -2493,6 +2529,7 @@ export function parseCalcFn(
24932529
export function parseCalcArguments(
24942530
[...args]: TokenOrValue[],
24952531
builder: StylesheetBuilder,
2532+
property: string,
24962533
) {
24972534
const parsed: StyleDescriptor[] = [];
24982535

@@ -2507,7 +2544,7 @@ export function parseCalcArguments(
25072544
case "var":
25082545
case "function":
25092546
case "unresolved-color": {
2510-
const value = parseUnparsed(arg, builder);
2547+
const value = parseUnparsed(arg, builder, property);
25112548

25122549
if (value === undefined) {
25132550
return undefined;
@@ -2583,7 +2620,7 @@ export function parseCalcArguments(
25832620
// Then drop the surrounding parenthesis
25842621
.slice(1, -1);
25852622

2586-
parsed.push(parseCalcFn("calc", innerCalcArgs, builder));
2623+
parsed.push(parseCalcFn("calc", innerCalcArgs, builder, property));
25872624

25882625
break;
25892626
}
@@ -2636,6 +2673,7 @@ export function parseTranslateProp(
26362673
export function parseUnresolvedColor(
26372674
color: UnresolvedColor,
26382675
builder: StylesheetBuilder,
2676+
property: string,
26392677
allowAuto: boolean,
26402678
): StyleDescriptor {
26412679
switch (color.type) {
@@ -2647,26 +2685,31 @@ export function parseUnresolvedColor(
26472685
round(color.r * 255),
26482686
round(color.g * 255),
26492687
round(color.b * 255),
2650-
parseUnparsed(color.alpha, builder),
2688+
parseUnparsed(color.alpha, builder, property),
26512689
],
26522690
];
26532691
case "hsl":
26542692
return [
26552693
{},
26562694
color.type,
2657-
[color.h, color.s, color.l, parseUnparsed(color.alpha, builder)],
2695+
[
2696+
color.h,
2697+
color.s,
2698+
color.l,
2699+
parseUnparsed(color.alpha, builder, property),
2700+
],
26582701
];
26592702
case "light-dark": {
26602703
const extraRule = builder.extendRule({
26612704
m: [["=", "prefers-color-scheme", "dark"]],
26622705
});
26632706
builder.addUnnamedDescriptor(
2664-
reduceParseUnparsed(color.dark, builder, allowAuto),
2707+
reduceParseUnparsed(color.dark, builder, property, allowAuto),
26652708
false,
26662709
extraRule,
26672710
);
26682711
builder.addExtraRule(extraRule);
2669-
return reduceParseUnparsed(color.light, builder, allowAuto);
2712+
return reduceParseUnparsed(color.light, builder, property, allowAuto);
26702713
}
26712714
default:
26722715
color satisfies never;

src/runtime/native/__tests__/units.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,16 @@ test("rem - dynamic", () => {
170170
style: { fontSize: 100 },
171171
});
172172
});
173+
174+
test("<ratio>", () => {
175+
registerCSS(`.my-class { aspect-ratio: 16 / 9; }`);
176+
177+
const { result } = renderHook(() => {
178+
return useNativeCss(View, { className: "my-class" });
179+
});
180+
181+
expect(result.current.type).toBe(View);
182+
expect(result.current.props).toStrictEqual({
183+
style: { aspectRatio: "16/9" },
184+
});
185+
});

0 commit comments

Comments
 (0)