diff --git a/src/__tests__/native/absolute-units.test.tsx b/src/__tests__/native/absolute-units.test.tsx new file mode 100644 index 00000000..236ae38f --- /dev/null +++ b/src/__tests__/native/absolute-units.test.tsx @@ -0,0 +1,62 @@ +import { render, screen } from "@testing-library/react-native"; +import { View } from "react-native-css/components/View"; +import { registerCSS, testID } from "react-native-css/jest"; + +// css-values-4 §6.2: 1in = 2.54cm = 25.4mm = 101.6Q = 72pt = 6pc = 96px +const UNITS = [ + ["1in", 96], + ["2.54cm", 96], + ["10mm", 37.7953], + ["4Q", 3.7795], + ["3pt", 4], + ["1pc", 16], +] as const; + +function styleFor(className: string): unknown { + render(); + return screen.getByTestId(testID).props.style; +} + +test.each(UNITS)("width: %s is %d pixels", (length, pixels) => { + registerCSS(`.subject { width: ${length}; }`); + + expect(styleFor("subject")).toStrictEqual({ width: pixels }); +}); + +test.each(UNITS)( + "a variable holding %s resolves to %d pixels at runtime", + (length, pixels) => { + registerCSS(` + .theme { --width: ${length}; } + .subject { --width: ${length}; width: var(--width); } + `); + + expect(styleFor("theme subject")).toStrictEqual({ width: pixels }); + }, +); + +test("an absolute length converts when rem is not inlined", () => { + registerCSS(`.subject { width: 1in; }`, { inlineRem: false }); + + expect(styleFor("subject")).toStrictEqual({ width: 96 }); +}); + +test("a media query reads an absolute length", () => { + registerCSS(` + @media (min-width: 4in) { .subject { width: 1pc; } } + @media (max-width: 4in) { .subject { height: 1pc; } } + `); + + expect(styleFor("subject")).toStrictEqual({ width: 16 }); +}); + +test("a shorthand converts each of its lengths", () => { + registerCSS(`.subject { margin: 1in 1pc; }`); + + expect(styleFor("subject")).toStrictEqual({ + marginTop: 96, + marginRight: 16, + marginBottom: 96, + marginLeft: 16, + }); +}); diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 9ca2aab8..f4e01f3d 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -36,7 +36,7 @@ import type { UnresolvedColor, } from "lightningcss"; -import { isStyleFunction } from "../utilities"; +import { ABSOLUTE_UNIT_PIXELS, isStyleFunction } from "../utilities"; import { isMappingProperty } from "./atRules"; import type { StyleDescriptor, @@ -1461,6 +1461,7 @@ export function parseLength( case "q": case "pt": case "pc": + return round(length.value * ABSOLUTE_UNIT_PIXELS[length.unit]); case "ex": case "rex": case "ch": diff --git a/src/utilities/absolute-units.ts b/src/utilities/absolute-units.ts new file mode 100644 index 00000000..42d0e27c --- /dev/null +++ b/src/utilities/absolute-units.ts @@ -0,0 +1,9 @@ +/** css-values-4 §6.2 fixes each absolute length unit against the inch (1in = 96px) rather than the device. */ +export const ABSOLUTE_UNIT_PIXELS = { + cm: 96 / 2.54, + in: 96, + mm: 96 / 25.4, + pc: 16, + pt: 96 / 72, + q: 96 / 101.6, +} as const satisfies Record; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 0c95da63..96b0ee30 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -1,3 +1,4 @@ export * from "./specificity"; export * from "./style-descriptor"; export * from "./dot-notation.types"; +export * from "./absolute-units";