diff --git a/src/__tests__/vendor/tailwind.test.tsx b/src/__tests__/vendor/tailwind.test.tsx index cabee1f..c3360df 100644 --- a/src/__tests__/vendor/tailwind.test.tsx +++ b/src/__tests__/vendor/tailwind.test.tsx @@ -111,3 +111,118 @@ test("box-shadow", () => { testID, }); }); + +test("filter", () => { + const compiled = registerCSS(` +:root, :host { + --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); +} + +.brightness-50 { + --tw-brightness: brightness(50%); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); +} + +.drop-shadow-md { + --tw-drop-shadow-size: drop-shadow(0 3px 3px var(--tw-drop-shadow-color, rgb(0 0 0 / 0.12))); + --tw-drop-shadow: drop-shadow(var(--drop-shadow-md)); + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); +} + `); + + expect(compiled).toStrictEqual({ + s: [ + [ + "brightness-50", + [ + { + d: [ + [ + [ + [{}, "var", "tw-blur", 1], + [{}, "var", "tw-brightness", 1], + [{}, "var", "tw-contrast", 1], + [{}, "var", "tw-grayscale", 1], + [{}, "var", "tw-hue-rotate", 1], + [{}, "var", "tw-invert", 1], + [{}, "var", "tw-saturate", 1], + [{}, "var", "tw-sepia", 1], + [{}, "var", "tw-drop-shadow", 1], + ], + "filter", + ], + ], + s: [2, 1], + v: [["tw-brightness", [{}, "brightness", "50%"]]], + }, + ], + ], + [ + "drop-shadow-md", + [ + { + d: [ + [ + [ + [{}, "var", "tw-blur", 1], + [{}, "var", "tw-brightness", 1], + [{}, "var", "tw-contrast", 1], + [{}, "var", "tw-grayscale", 1], + [{}, "var", "tw-hue-rotate", 1], + [{}, "var", "tw-invert", 1], + [{}, "var", "tw-saturate", 1], + [{}, "var", "tw-sepia", 1], + [{}, "var", "tw-drop-shadow", 1], + ], + "filter", + ], + ], + s: [3, 1], + v: [ + [ + "tw-drop-shadow-size", + [ + {}, + "drop-shadow", + [ + 0, + 3, + 3, + [{}, "var", ["tw-drop-shadow-color", "#0000001f"], 1], + ], + ], + ], + [ + "tw-drop-shadow", + [{}, "drop-shadow", [{}, "var", "drop-shadow-md", 1]], + ], + ], + }, + ], + ], + ], + vr: [["drop-shadow-md", [[0, 3, 3, "#0000001f"]]]], + }); + + render(); + const component = screen.getByTestId(testID); + + expect(component.type).toBe("View"); + expect(component.props).toStrictEqual({ + children: undefined, + style: { + filter: [ + { brightness: "50%" }, + { + dropShadow: { + blurRadius: 3, + color: "#0000001f", + offsetX: 0, + offsetY: 3, + }, + }, + ], + }, + testID, + }); +}); diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts index 3793502..3c514f2 100644 --- a/src/compiler/declarations.ts +++ b/src/compiler/declarations.ts @@ -142,6 +142,7 @@ const parsers: { "container-type": parseContainerType, "display": parseDisplay, "fill": parseSVGPaint, + "filter": parseFilter, "flex": parseFlex, "flex-basis": parseLengthPercentageOrAutoDeclaration, "flex-direction": ({ value }) => value, @@ -993,19 +994,28 @@ export function parseUnparsed( case "translateY": tokenOrValue.value.name = `@${tokenOrValue.value.name}`; return unparsedFunction(tokenOrValue, builder); - case "platformColor": - case "pixelSizeForLayoutSize": - case "roundToNearestPixel": - case "pixelScale": + case "brightness": + case "contrast": + case "cubic-bezier": + case "drop-shadow": case "fontScale": - case "shadow": - case "rgb": - case "rgba": + case "grayscale": case "hsl": case "hsla": + case "hue-rotate": + case "invert": case "linear-gradient": + case "opacity": + case "pixelScale": + case "pixelSizeForLayoutSize": + case "platformColor": case "radial-gradient": - case "cubic-bezier": + case "rgb": + case "rgba": + case "roundToNearestPixel": + case "saturate": + case "sepia": + case "shadow": case "steps": return unparsedFunction(tokenOrValue, builder); case "hairlineWidth": @@ -2768,6 +2778,40 @@ function parseGradientItem( } } +function parseFilter( + declaration: DeclarationType<"filter">, + builder: StylesheetBuilder, +) { + if (declaration.value.type === "none") { + return "unset"; + } + + return declaration.value.value + .map((value) => { + switch (value.type) { + case "opacity": + case "blur": + case "brightness": + case "contrast": + case "grayscale": + case "invert": + case "saturate": + case "sepia": + return { + [value.type]: parseLength(value.value, builder), + } as unknown as StyleDescriptor; + case "hue-rotate": + return { + [value.type]: parseAngle(value.value, builder), + } as unknown as StyleDescriptor; + case "drop-shadow": + case "url": + return; + } + }) + .filter((value) => value !== undefined); +} + const namedColors = new Set([ "aliceblue", "antiquewhite", diff --git a/src/runtime/native/styles/filters.ts b/src/runtime/native/styles/filters.ts new file mode 100644 index 0000000..f1bdd8a --- /dev/null +++ b/src/runtime/native/styles/filters.ts @@ -0,0 +1,98 @@ +import { isStyleDescriptorArray } from "../../utils"; +import type { StyleFunctionResolver } from "./resolve"; +import { shorthandHandler } from "./shorthand"; + +export const blur: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + blur: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const brightness: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + brightness: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const contrast: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + contrast: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const grayscale: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + grayscale: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const hueRotate: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + hueRotate: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const invert: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + invert: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const sepia: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + sepia: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const saturate: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + saturate: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +export const opacity: StyleFunctionResolver = (resolveValue, value) => { + const args = resolveValue(value[2]); + return { + hueRotate: (Array.isArray(args) ? args[0] : args) as unknown, + }; +}; + +const color = ["color", "string"] as const; +const offsetX = ["offsetX", "number"] as const; +const offsetY = ["offsetY", "number"] as const; +const blurRadius = ["blurRadius", "number"] as const; + +const handler = shorthandHandler( + [ + [offsetX, offsetY, blurRadius, color], + [color, offsetX, offsetY], + [color, offsetX, offsetY, blurRadius], + [offsetX, offsetY, color], + [offsetX, offsetY, blurRadius, color], + ], + [], + "object", +); + +export const dropShadow: StyleFunctionResolver = ( + resolveValue, + func, + get, + options, +) => { + const args = resolveValue(func[2]); + + return isStyleDescriptorArray(args) + ? { + dropShadow: handler(resolveValue, args, get, options), + } + : undefined; +}; diff --git a/src/runtime/native/styles/resolve.ts b/src/runtime/native/styles/resolve.ts index aac223f..e4a453b 100644 --- a/src/runtime/native/styles/resolve.ts +++ b/src/runtime/native/styles/resolve.ts @@ -1,4 +1,5 @@ /* eslint-disable */ + import type { InlineVariable, StyleDescriptor, @@ -12,6 +13,18 @@ import { boxShadow } from "./box-shadow"; import { calc } from "./calc"; import type { calculateProps } from "./calculate-props"; import { transformKeys } from "./defaults"; +import { + blur, + brightness, + contrast, + dropShadow, + grayscale, + hueRotate, + invert, + opacity, + saturate, + sepia, +} from "./filters"; import { fontScale, hairlineWidth, @@ -44,31 +57,36 @@ export type StyleResolver = ( options: ResolveValueOptions, ) => unknown; -const shorthands: Record<`@${string}`, StyleFunctionResolver | StyleResolver> = - { - "@animation": animation, - "@textShadow": textShadow, - "@transform": transform, - "@boxShadow": boxShadow, - "@border": border, - }; - const functions: Record = { + "@animation": animation, + "@border": border, + "@boxShadow": boxShadow, + "@textShadow": textShadow, + "@transform": transform, + "animationName": animation, + "cubic-bezier": timingFunctionResolver, + "steps": timingFunctionResolver, + "hue-rotate": hueRotate, + "drop-shadow": dropShadow, + blur, + brightness, calc, + contrast, em, - vw, - vh, - rem, - platformColor, + fontScale, + grayscale, hairlineWidth, + invert, + opacity, pixelRatio, - fontScale, pixelSizeForLayoutSize, + platformColor, + rem, roundToNearestPixel, - "animationName": animation, - "cubic-bezier": timingFunctionResolver, - "steps": timingFunctionResolver, - ...shorthands, + saturate, + sepia, + vh, + vw, }; export type ResolveValueOptions = {