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 = {