From ffd33dda7cbf3c27e74e15c1f39d3efc3cd59bc3 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Mon, 9 Mar 2026 15:06:10 +0200 Subject: [PATCH] fix: resolve single box-shadow from runtime CSS variables When a CSS variable holding a single box-shadow value is resolved at runtime, the shorthand handler receives a flat array of tokens like [0, 4, 6, -1, "#000"]. The handler incorrectly iterates each primitive individually, none match any shadow pattern, and the result is an empty array. Detect flat vs nested arrays before entering the multi-shadow handler. A flat array is a single shadow's tokens that should be passed directly to the pattern handler. A nested array contains multiple shadows for the existing flatMap logic. --- src/__tests__/native/box-shadow.test.tsx | 90 ++++++++++++++++++++++ src/native/styles/shorthands/box-shadow.ts | 38 +++++---- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src/__tests__/native/box-shadow.test.tsx b/src/__tests__/native/box-shadow.test.tsx index 48a387a4..4347c995 100644 --- a/src/__tests__/native/box-shadow.test.tsx +++ b/src/__tests__/native/box-shadow.test.tsx @@ -28,6 +28,96 @@ test("shadow values - single nested variables", () => { }); }); +test("single shadow via runtime variable", () => { + registerCSS( + `.test { + --my-shadow: 0 4px 6px -1px #000; + box-shadow: var(--my-shadow); + }`, + { inlineVariables: false }, + ); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style.boxShadow).toStrictEqual([ + { + offsetX: 0, + offsetY: 4, + blurRadius: 6, + spreadDistance: -1, + color: "#000", + }, + ]); +}); + +test("single shadow via multi-definition variable (theme switching)", () => { + registerCSS(` + :root { --themed-shadow: 0 4px 6px -1px #000; } + .dark { --themed-shadow: 0 4px 6px -1px #fff; } + .test { box-shadow: var(--themed-shadow); } + `); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style.boxShadow).toStrictEqual([ + { + offsetX: 0, + offsetY: 4, + blurRadius: 6, + spreadDistance: -1, + color: "#000", + }, + ]); +}); + +test("transparent shadow via runtime variable is filtered", () => { + registerCSS( + `.test { + --my-shadow: 0 0 0 0 #0000; + box-shadow: var(--my-shadow); + }`, + { inlineVariables: false }, + ); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style.boxShadow).toStrictEqual([]); +}); + +test("multi-shadow via runtime variables (comma-separated)", () => { + registerCSS( + `.test { + --shadow-a: 0 4px 6px -1px #000; + --shadow-b: 0 1px 2px 0 #333; + box-shadow: var(--shadow-a), var(--shadow-b); + }`, + { inlineVariables: false }, + ); + + render(); + const component = screen.getByTestId(testID); + + expect(component.props.style.boxShadow).toStrictEqual([ + { + offsetX: 0, + offsetY: 4, + blurRadius: 6, + spreadDistance: -1, + color: "#000", + }, + { + offsetX: 0, + offsetY: 1, + blurRadius: 2, + spreadDistance: 0, + color: "#333", + }, + ]); +}); + test("shadow values - multiple nested variables", () => { registerCSS(` :root { diff --git a/src/native/styles/shorthands/box-shadow.ts b/src/native/styles/shorthands/box-shadow.ts index f65a9370..4eeaf086 100644 --- a/src/native/styles/shorthands/box-shadow.ts +++ b/src/native/styles/shorthands/box-shadow.ts @@ -34,20 +34,32 @@ export const boxShadow: StyleFunctionResolver = ( if (!isStyleDescriptorArray(args)) { return args; - } else { - return args - .flatMap(flattenShadowDescriptor) - .map((shadows) => { - if (shadows === undefined) { - return; - } else { - return omitTransparentShadows( - handler(resolveValue, shadows, get, options), - ); - } - }) - .filter((v) => v !== undefined); } + + // A flat array of primitives (e.g. [0, 4, 6, -1, "#000"]) is a single shadow + // resolved from a runtime variable. Pass it directly to the pattern handler. + // A nested array (e.g. [[0, 4, 6, -1, "#000"], [...]]) is multiple shadows. + if (args.length > 0 && !Array.isArray(args[0])) { + const result = handler(resolveValue, args, get, options); + if (result === undefined) { + return []; + } + const filtered = omitTransparentShadows(result); + return filtered !== undefined ? [filtered] : []; + } + + return args + .flatMap(flattenShadowDescriptor) + .map((shadows) => { + if (shadows === undefined) { + return; + } else { + return omitTransparentShadows( + handler(resolveValue, shadows, get, options), + ); + } + }) + .filter((v) => v !== undefined); }; function flattenShadowDescriptor(arg: StyleDescriptor): StyleDescriptor[] {