diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8c0b935..fee4ceb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -180,17 +180,20 @@ _Do not create pull requests for these reasons:_
> [!IMPORTANT]
> The Metro transformer does not fast refresh. After you make a change, you will need to recompile the project and restart the Metro server with a clean cache.
->
-> ```bash
-> # Build the project
-> yarn build
->
-> # Start the Metro server with a clean cache
-> yarn example start --clean
-> ```
Development on the Metro transformer is done by running the example project.
+Debugging with breakpoints is supported if you run the project in VSCode's JavaScript Debug Terminal, or by setting the NodeJS debugger environment variables
+
+### Compiler
+
+> [!IMPORTANT]
+> The Metro transformer does not fast refresh. After you make a change, you will need to recompile the project and restart the Metro server with a clean cache.
+
+The easiest way to debug the compiler is through Test Driven Development (TDD). The tests are located in the `src/__tests__/compiler` directory.
+
+You can use the JavaScript debugger, but you will need to use VSCode's JavaScript Debug Terminal, or set the NodeJS debugger environment variables to enable the debugger.
+
### Babel Plugin
> [!IMPORTANT]
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 4cfb29e..742a593 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -6,7 +6,9 @@ export default function App() {
return (
<>
- Test Component
+
+ Test Component
+
>
);
diff --git a/src/__tests__/vendor/tailwind.test.tsx b/src/__tests__/vendor/tailwind.test.tsx
new file mode 100644
index 0000000..cabee1f
--- /dev/null
+++ b/src/__tests__/vendor/tailwind.test.tsx
@@ -0,0 +1,113 @@
+import { View } from "react-native-css/components/View";
+import { registerCSS, render, screen, testID } from "react-native-css/jest";
+
+/**
+ * Tailwind CSS utilities
+ *
+ * These tests are designed to ensure that complex Tailwind CSS utilities are compiled correctly.
+ * For the full Tailwind CSS test suite, see the Nativewind repository.
+ */
+
+test("box-shadow", () => {
+ const compiled = registerCSS(`
+.shadow-xl {
+ --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+}
+.shadow-red-500 {
+ --tw-shadow-color: oklch(63.7% 0.237 25.331);
+ @supports (color: color-mix(in lab, red, red)) {
+ --tw-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-shadow-alpha), transparent);
+ }
+}
+ `);
+
+ expect(compiled).toStrictEqual({
+ s: [
+ [
+ "shadow-xl",
+ [
+ {
+ d: [
+ [
+ [
+ {},
+ "@boxShadow",
+ [
+ [{}, "var", "tw-inset-shadow", 1],
+ [{}, "var", "tw-inset-ring-shadow", 1],
+ [{}, "var", "tw-ring-offset-shadow", 1],
+ [{}, "var", "tw-ring-shadow", 1],
+ [{}, "var", "tw-shadow", 1],
+ ],
+ 1,
+ ],
+ "boxShadow",
+ 1,
+ ],
+ ],
+ dv: 1,
+ s: [1, 1],
+ v: [
+ [
+ "tw-shadow",
+ [
+ [
+ 0,
+ 20,
+ 25,
+ -5,
+ [{}, "var", ["tw-shadow-color", "#0000001a"], 1],
+ ],
+ [
+ 0,
+ 8,
+ 10,
+ -6,
+ [{}, "var", ["tw-shadow-color", "#0000001a"], 1],
+ ],
+ ],
+ ],
+ ],
+ },
+ ],
+ ],
+ [
+ "shadow-red-500",
+ [
+ {
+ s: [2, 1],
+ v: [["tw-shadow-color", "#fb2c36"]],
+ },
+ ],
+ ],
+ ],
+ });
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.type).toBe("View");
+ expect(component.props).toStrictEqual({
+ children: undefined,
+ style: {
+ boxShadow: [
+ {
+ blurRadius: 25,
+ color: "#fb2c36",
+ offsetX: 0,
+ offsetY: 20,
+ spreadDistance: -5,
+ },
+ {
+ blurRadius: 10,
+ color: "#fb2c36",
+ offsetX: 0,
+ offsetY: 8,
+ spreadDistance: -6,
+ },
+ ],
+ },
+ testID,
+ });
+});
diff --git a/src/compiler/__tests__/compiler.test.tsx b/src/compiler/__tests__/compiler.test.tsx
index 5694084..b7f610e 100644
--- a/src/compiler/__tests__/compiler.test.tsx
+++ b/src/compiler/__tests__/compiler.test.tsx
@@ -240,6 +240,6 @@ test("breaks apart comma separated variables", () => {
`);
expect(compiled).toStrictEqual({
- vr: [["test", [[["blue"], ["green"]]]]],
+ vr: [["test", [["blue", "green"]]]],
});
});
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index 6f1ef08..bf70bba 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -68,6 +68,7 @@ export function compile(
},
StyleSheetExit(sheet) {
logger(`Found ${sheet.rules.length} rules to process`);
+ logger(JSON.stringify(sheet.rules, null, 2));
for (const rule of sheet.rules) {
// Extract the style declarations and animations from the current rule
diff --git a/src/compiler/compiler.types.ts b/src/compiler/compiler.types.ts
index cbfd25c..d29eb77 100644
--- a/src/compiler/compiler.types.ts
+++ b/src/compiler/compiler.types.ts
@@ -125,12 +125,12 @@ export type StyleFunction =
| [
Record,
string, // string
- undefined | StyleDescriptor[], // arguments
+ StyleDescriptor, // arguments
]
| [
Record,
string, // string
- undefined | StyleDescriptor[], // arguments
+ StyleDescriptor, // arguments
1, // Should process after styles have been calculated
];
@@ -144,7 +144,7 @@ export type LightDarkVariable =
export type InlineVariable = {
[VAR_SYMBOL]: "inline";
- [key: string]: StyleDescriptor | undefined;
+ [key: string]: unknown | undefined;
};
/****************************** Animations V1 ******************************/
diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts
index 79df06e..6d2060b 100644
--- a/src/compiler/declarations.ts
+++ b/src/compiler/declarations.ts
@@ -35,7 +35,6 @@ import type {
UnresolvedColor,
} from "lightningcss";
-import { isStyleDescriptorArray } from "../runtime/utils";
import type { StyleDescriptor, StyleFunction } from "./compiler.types";
import { parseEasingFunction, parseIterationCount } from "./keyframes";
import { toRNProperty } from "./selectors";
@@ -597,19 +596,19 @@ function parseTransform(
value.flatMap((t): StyleDescriptor[] => {
switch (t.type) {
case "perspective":
- return [[{}, "@perspective", [parseLength(t.value, builder)]]];
+ return [[{}, "@perspective", parseLength(t.value, builder)]];
case "translate":
return [
[
{},
"@translateX",
- [parseLengthOrCoercePercentageToRuntime(t.value[0], builder)],
+ parseLengthOrCoercePercentageToRuntime(t.value[0], builder),
],
[
[
{},
"@translateY",
- [parseLengthOrCoercePercentageToRuntime(t.value[1], builder)],
+ parseLengthOrCoercePercentageToRuntime(t.value[1], builder),
],
],
];
@@ -618,7 +617,7 @@ function parseTransform(
[
{},
"@translateX",
- [parseLengthOrCoercePercentageToRuntime(t.value, builder)],
+ parseLengthOrCoercePercentageToRuntime(t.value, builder),
],
];
case "translateY":
@@ -626,35 +625,35 @@ function parseTransform(
[
{},
"@translateY",
- [parseLengthOrCoercePercentageToRuntime(t.value, builder)],
+ parseLengthOrCoercePercentageToRuntime(t.value, builder),
],
];
case "rotate":
- return [[{}, "@rotate", [parseAngle(t.value, builder)]]];
+ return [[{}, "@rotate", parseAngle(t.value, builder)]];
case "rotateX":
- return [[{}, "@rotateX", [parseAngle(t.value, builder)]]];
+ return [[{}, "@rotateX", parseAngle(t.value, builder)]];
case "rotateY":
- return [[{}, "@rotateY", [parseAngle(t.value, builder)]]];
+ return [[{}, "@rotateY", parseAngle(t.value, builder)]];
case "rotateZ":
- return [[{}, "@rotateZ", [parseAngle(t.value, builder)]]];
+ return [[{}, "@rotateZ", parseAngle(t.value, builder)]];
case "scale":
return [
- [{}, "@scaleX", [parseLength(t.value[0], builder)]],
- [{}, "@scaleY", [parseLength(t.value[0], builder)]],
+ [{}, "@scaleX", parseLength(t.value[0], builder)],
+ [{}, "@scaleY", parseLength(t.value[1], builder)],
];
case "scaleX":
- return [[{}, "scaleX", [parseLength(t.value, builder)]]];
+ return [[{}, "scaleX", parseLength(t.value, builder)]];
case "scaleY":
- return [[{}, "scaleY", [parseLength(t.value, builder)]]];
+ return [[{}, "scaleY", parseLength(t.value, builder)]];
case "skew":
return [
- [{}, "skewX", [parseAngle(t.value[0], builder)]],
- [{}, "skewY", [parseAngle(t.value[0], builder)]],
+ [{}, "skewX", parseAngle(t.value[0], builder)],
+ [{}, "skewY", parseAngle(t.value[1], builder)],
];
case "skewX":
- return [[{}, "skewX", [parseAngle(t.value, builder)]]];
+ return [[{}, "skewX", parseAngle(t.value, builder)]];
case "skewY":
- return [[{}, "skewY", [parseAngle(t.value, builder)]]];
+ return [[{}, "skewY", parseAngle(t.value, builder)]];
case "translateZ":
case "translate3d":
case "scaleZ":
@@ -676,12 +675,12 @@ function parseTranslate(
builder.addDescriptor("translateX", [
{},
"translateX",
- [parseTranslateProp(value, "x", builder)],
+ parseTranslateProp(value, "x", builder),
]);
builder.addDescriptor("translateY", [
{},
- "translateX",
- [parseTranslateProp(value, "y", builder)],
+ "translateY",
+ parseTranslateProp(value, "y", builder),
]);
}
@@ -692,17 +691,17 @@ function parseRotate(
builder.addDescriptor("rotateX", [
{},
"rotateX",
- [parseAngle(value.x, builder)],
+ parseAngle(value.x, builder),
]);
builder.addDescriptor("rotateY", [
{},
"rotateY",
- [parseAngle(value.y, builder)],
+ parseAngle(value.y, builder),
]);
builder.addDescriptor("rotateZ", [
{},
"rotateZ",
- [parseAngle(value.z, builder)],
+ parseAngle(value.z, builder),
]);
}
@@ -713,12 +712,12 @@ function parseScale(
builder.addDescriptor("scaleX", [
{},
"scaleX",
- [parseScaleValue(value, "x", builder)],
+ parseScaleValue(value, "x", builder),
]);
builder.addDescriptor("scaleY", [
{},
"scaleY",
- [parseScaleValue(value, "y", builder)],
+ parseScaleValue(value, "y", builder),
]);
}
@@ -813,10 +812,7 @@ export function parseDeclarationUnparsed(
* Unparsed shorthand properties need to be parsed at runtime
*/
if (needsRuntimeParsing.has(property)) {
- let args = parseUnparsed(declaration.value.value, builder);
- if (!isStyleDescriptorArray(args)) {
- args = [args];
- }
+ const args = parseUnparsed(declaration.value.value, builder);
if (property === "animation") {
builder.addDescriptor("animation", [
@@ -862,7 +858,7 @@ export function parseDeclarationCustom(
export function reduceParseUnparsed(
tokenOrValues: TokenOrValue[],
builder: StylesheetBuilder,
-): StyleDescriptor[] | undefined {
+): StyleDescriptor {
const result = tokenOrValues
.map((tokenOrValue) => parseUnparsed(tokenOrValue, builder))
.filter((v) => v !== undefined);
@@ -871,8 +867,8 @@ export function reduceParseUnparsed(
return undefined;
}
- let currentGroup: StyleDescriptor[] = [];
- const groups: StyleDescriptor[][] = [currentGroup];
+ let currentGroup: StyleDescriptor = [];
+ let groups: StyleDescriptor[] = [currentGroup];
for (const value of result) {
if ((value as unknown) === CommaSeparator) {
@@ -883,6 +879,26 @@ export function reduceParseUnparsed(
}
}
+ groups = groups.flatMap((group): StyleDescriptor[] => {
+ if (!Array.isArray(group)) {
+ return [];
+ }
+
+ if (group.length === 0) {
+ return [];
+ } else if (group.length === 1) {
+ const first = group[0];
+
+ if (first === undefined) {
+ return [];
+ } else {
+ return [first];
+ }
+ } else {
+ return [group];
+ }
+ });
+
return groups.length === 1 ? groups[0] : groups;
}
@@ -890,8 +906,11 @@ export function unparsedFunction(
token: Extract,
builder: StylesheetBuilder,
): StyleFunction {
- const args = reduceParseUnparsed(token.value.arguments, builder);
- return [{}, token.value.name, args];
+ return [
+ {},
+ token.value.name,
+ reduceParseUnparsed(token.value.arguments, builder),
+ ];
}
/**
@@ -930,7 +949,7 @@ export function parseUnparsed(
if (Array.isArray(tokenOrValue)) {
const args = reduceParseUnparsed(tokenOrValue, builder);
if (!args) return;
- if (args.length === 1) return args[0];
+ if (Array.isArray(args) && args.length === 1) return args[0];
return args;
}
@@ -939,10 +958,10 @@ export function parseUnparsed(
return parseUnresolvedColor(tokenOrValue.value, builder);
}
case "var": {
- const args: StyleDescriptor[] = [tokenOrValue.value.name.ident.slice(2)];
+ let args: StyleDescriptor = tokenOrValue.value.name.ident.slice(2);
const fallback = parseUnparsed(tokenOrValue.value.fallback, builder);
if (fallback !== undefined) {
- args.push(fallback);
+ args = [args, fallback];
}
return [{}, "var", args, 1];
@@ -1119,12 +1138,12 @@ export function parseLength(
if (typeof inlineRem === "number") {
return length.value * inlineRem;
} else {
- return [{}, "rem", [length.value]];
+ return [{}, "rem", length.value];
}
case "vw":
case "vh":
case "em":
- return [{}, length.unit, [length.value], 1];
+ return [{}, length.unit, length.value, 1];
case "in":
case "cm":
case "mm":
@@ -2025,21 +2044,35 @@ export function parseTextAlign(
}
export function parseBoxShadow(
- _: DeclarationType<"box-shadow">,
- _builder: StylesheetBuilder,
+ { value }: DeclarationType<"box-shadow">,
+ builder: StylesheetBuilder,
) {
- return undefined;
-
- // return value.map(
- // (shadow): BoxShadowValue => ({
- // color: parseColor(shadow.color, builder),
- // offsetX: parseLength(shadow.xOffset, builder) as number,
- // offsetY: parseLength(shadow.yOffset, builder) as number,
- // blurRadius: parseLength(shadow.blur, builder) as number,
- // spreadDistance: parseLength(shadow.spread, builder) as number,
- // inset: shadow.inset,
- // }),
- // );
+ for (const [index, shadow] of value.entries()) {
+ builder.addDescriptor(
+ `boxShadow.[${index}].color`,
+ parseColor(shadow.color, builder),
+ );
+ builder.addDescriptor(
+ `boxShadow.[${index}].offsetX`,
+ parseLength(shadow.xOffset, builder),
+ );
+ builder.addDescriptor(
+ `boxShadow.[${index}].offsetY`,
+ parseLength(shadow.yOffset, builder),
+ );
+ builder.addDescriptor(
+ `boxShadow.[${index}].blurRadius`,
+ parseLength(shadow.blur, builder),
+ );
+ builder.addDescriptor(
+ `boxShadow.[${index}].spreadDistance`,
+ parseLength(shadow.spread, builder),
+ );
+ builder.addDescriptor(
+ `boxShadow.[${index}].inset`,
+ shadow.inset ? true : undefined,
+ );
+ }
}
export function parseDisplay(
diff --git a/src/jest/index.ts b/src/jest/index.ts
index 2ffae3a..de4b6ce 100644
--- a/src/jest/index.ts
+++ b/src/jest/index.ts
@@ -37,4 +37,6 @@ export function registerCSS(
}
StyleCollection.inject(compiled);
+
+ return compiled;
}
diff --git a/src/runtime/native/__tests__/calc.test.tsx b/src/runtime/native/__tests__/calc.test.tsx
index 09d8eb8..868aadf 100644
--- a/src/runtime/native/__tests__/calc.test.tsx
+++ b/src/runtime/native/__tests__/calc.test.tsx
@@ -85,8 +85,8 @@ describe("css", () => {
test("calc(var(--variable) + 20px)", () => {
registerCSS(
`.my-class {
- --variable: 100px;
- width: calc(var(--variable) + 20px)
+ --my-var: 100px;
+ width: calc(var(--my-var) + 20px)
}`,
);
diff --git a/src/runtime/native/__tests__/units.test.tsx b/src/runtime/native/__tests__/units.test.tsx
index 162eb2f..aa5d827 100644
--- a/src/runtime/native/__tests__/units.test.tsx
+++ b/src/runtime/native/__tests__/units.test.tsx
@@ -145,7 +145,7 @@ test("rem - dynamic", () => {
expect(result.current.type).toBe(VariableContext.Provider);
expect(result.current.props.value).toStrictEqual({
[VAR_SYMBOL]: true,
- [emVariableName]: [{}, "rem", [10]],
+ [emVariableName]: [{}, "rem", 10],
});
expect(result.current.props.children.type).toBe(View);
@@ -161,7 +161,7 @@ test("rem - dynamic", () => {
expect(result.current.type).toBe(VariableContext.Provider);
expect(result.current.props.value).toStrictEqual({
[VAR_SYMBOL]: true,
- [emVariableName]: [{}, "rem", [10]],
+ [emVariableName]: [{}, "rem", 10],
});
expect(result.current.props.children.type).toBe(View);
diff --git a/src/runtime/native/styles/animation.ts b/src/runtime/native/styles/animation.ts
index d49e307..255a1e5 100644
--- a/src/runtime/native/styles/animation.ts
+++ b/src/runtime/native/styles/animation.ts
@@ -81,10 +81,14 @@ export const animation: StyleFunctionResolver = (
get,
options,
) => {
- const animationShortHandTuples: [unknown, string][] | undefined =
- animationShorthand(resolveValue, value, get, options);
+ const animationShortHandTuples = animationShorthand(
+ resolveValue,
+ value,
+ get,
+ options,
+ );
- if (!animationShortHandTuples) {
+ if (!Array.isArray(animationShortHandTuples)) {
return;
}
@@ -159,7 +163,11 @@ export const timingFunctionResolver: StyleFunctionResolver = (
return;
}
- const args: unknown[] = resolveValue(value[2]);
+ const args = resolveValue(value[2]);
+
+ if (!Array.isArray(args)) {
+ return;
+ }
const fn = resolver();
diff --git a/src/runtime/native/styles/box-shadow.ts b/src/runtime/native/styles/box-shadow.ts
index e7ad971..29c5c88 100644
--- a/src/runtime/native/styles/box-shadow.ts
+++ b/src/runtime/native/styles/box-shadow.ts
@@ -1,4 +1,4 @@
-import { applyValue } from "../../utils";
+import { applyShorthand, isStyleDescriptorArray } from "../../utils";
import type { StyleFunctionResolver } from "./resolve";
import { shorthandHandler } from "./shorthand";
@@ -11,12 +11,12 @@ const spreadDistance = ["spreadDistance", "number"] as const;
const handler = shorthandHandler(
[
+ [offsetX, offsetY, blurRadius, spreadDistance, color],
[color, offsetX, offsetY],
[color, offsetX, offsetY, blurRadius],
[color, offsetX, offsetY, blurRadius, spreadDistance],
[offsetX, offsetY, color],
[offsetX, offsetY, blurRadius, color],
- [offsetX, offsetY, blurRadius, spreadDistance, color],
],
[],
);
@@ -27,28 +27,29 @@ export const boxShadow: StyleFunctionResolver = (
get,
options,
) => {
- return func[2]?.flatMap((maybeShadow): unknown[] => {
- const resolvedShadow = resolveValue(maybeShadow) as unknown;
-
- if (!Array.isArray(resolvedShadow)) {
- return [];
- }
+ const args = resolveValue(func[2]);
- return resolvedShadow.flat().flatMap((shadow): unknown => {
- const result: unknown = handler(
- resolveValue,
- [{}, "@boxShadowHandler", shadow],
- get,
- options,
- );
-
- if (result === undefined) {
+ if (!isStyleDescriptorArray(args)) {
+ return args;
+ } else {
+ return args.flatMap((shadows) => {
+ if (shadows === undefined) {
return [];
+ } else if (isStyleDescriptorArray(shadows)) {
+ if (shadows.length === 0) {
+ return [];
+ } else {
+ return shadows
+ .map((shadow) => {
+ return applyShorthand(
+ handler(resolveValue, shadow, get, options),
+ );
+ })
+ .filter((v) => v !== undefined);
+ }
+ } else {
+ return applyShorthand(handler(resolveValue, shadows, get, options));
}
-
- const target = {};
- applyValue(target, "", result);
- return target;
});
- });
+ }
};
diff --git a/src/runtime/native/styles/calc.ts b/src/runtime/native/styles/calc.ts
index ee4df57..530ac9a 100644
--- a/src/runtime/native/styles/calc.ts
+++ b/src/runtime/native/styles/calc.ts
@@ -1,4 +1,5 @@
/* eslint-disable */
+import { isStyleDescriptorArray } from "../../utils";
import type { StyleFunctionResolver } from "./resolve";
const calcPrecedence: Record = {
@@ -13,71 +14,69 @@ export const calc: StyleFunctionResolver = (resolveValue, func) => {
const values: number[] = [];
const ops: string[] = [];
- const args = func[2];
- if (!args) return;
+ const args = resolveValue(func[2]);
+
+ if (!isStyleDescriptorArray(args)) return;
for (let token of args) {
- switch (typeof token) {
- case "undefined":
- // Fail on an undefined value
- return;
- case "number":
- if (!mode) mode = "number";
- if (mode !== "number") return;
- values.push(token);
- continue;
- case "object": {
- // All values should resolve to a numerical value
- const value = resolveValue(token);
- switch (typeof value) {
- case "number": {
- if (!mode) mode = "number";
- if (mode !== "number") return;
- values.push(value);
- continue;
- }
- case "string": {
- if (!value.endsWith("%")) {
- return;
- }
- if (!mode) mode = "percentage";
- if (mode !== "percentage") return;
- values.push(Number.parseFloat(value.slice(0, -1)));
- continue;
- }
- default:
- return;
+ if (typeof token === "number") {
+ if (!mode) mode = "number";
+ if (mode !== "number") return;
+ values.push(token);
+ continue;
+ } else if (typeof token === "string") {
+ if (token === "(") {
+ ops.push(token);
+ } else if (token === ")") {
+ // Resolve all values within the brackets
+ while (ops.length && ops[ops.length - 1] !== "(") {
+ applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
}
- }
- case "string": {
- if (token === "(") {
- ops.push(token);
- } else if (token === ")") {
- // Resolve all values within the brackets
- while (ops.length && ops[ops.length - 1] !== "(") {
- applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
- }
- ops.pop();
- } else if (token.endsWith("%")) {
- if (!mode) mode = "percentage";
- if (mode !== "percentage") return;
- values.push(Number.parseFloat(token.slice(0, -1)));
- } else {
- // This means we have an operator
- while (
- ops.length &&
- ops[ops.length - 1] &&
- // @ts-ignore
- calcPrecedence[ops[ops.length - 1]] >= calcPrecedence[token]
- ) {
- applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
- }
- ops.push(token);
+ ops.pop();
+ } else if (token.endsWith("%")) {
+ if (!mode) mode = "percentage";
+ if (mode !== "percentage") return;
+ values.push(Number.parseFloat(token.slice(0, -1)));
+ } else {
+ // This means we have an operator
+ while (
+ ops.length &&
+ ops[ops.length - 1] &&
+ // @ts-ignore
+ calcPrecedence[ops[ops.length - 1]] >= calcPrecedence[token]
+ ) {
+ applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
}
+ ops.push(token);
}
+ } else {
+ // We got something unexpected
+ return;
}
}
-
+ // case "object": {
+ // // All values should resolve to a numerical value
+ // const value = resolveValue(token);
+ // switch (typeof value) {
+ // case "number": {
+ // if (!mode) mode = "number";
+ // if (mode !== "number") return;
+ // values.push(value);
+ // continue;
+ // }
+ // case "string": {
+ // if (!value.endsWith("%")) {
+ // return;
+ // }
+ // if (!mode) mode = "percentage";
+ // if (mode !== "percentage") return;
+ // values.push(Number.parseFloat(value.slice(0, -1)));
+ // continue;
+ // }
+ // default:
+ // return;
+ // }
+ // }
while (ops.length) {
applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
}
diff --git a/src/runtime/native/styles/platform-functions.ts b/src/runtime/native/styles/platform-functions.ts
index 0dcdbbb..2195a9f 100644
--- a/src/runtime/native/styles/platform-functions.ts
+++ b/src/runtime/native/styles/platform-functions.ts
@@ -29,7 +29,7 @@ export const pixelSizeForLayoutSize: StyleFunctionResolver = (
resolveValue,
value,
) => {
- const size: unknown = resolveValue(value[2]?.[0]);
+ const size: unknown = resolveValue(value[2]);
if (typeof size === "number") {
return PixelRatio.getPixelSizeForLayoutSize(size);
}
@@ -41,7 +41,7 @@ export const roundToNearestPixel: StyleFunctionResolver = (
resolveValue,
value,
) => {
- const size: unknown = resolveValue(value[2]?.[0]);
+ const size: unknown = resolveValue(value[2]);
if (typeof size === "number") {
return PixelRatio.roundToNearestPixel(size);
}
diff --git a/src/runtime/native/styles/resolve.ts b/src/runtime/native/styles/resolve.ts
index 096187e..aac223f 100644
--- a/src/runtime/native/styles/resolve.ts
+++ b/src/runtime/native/styles/resolve.ts
@@ -28,22 +28,30 @@ import { varResolver } from "./variables";
export type SimpleResolveValue = (
value: StyleDescriptor,
castToArray?: boolean,
-) => any;
+) => unknown;
export type StyleFunctionResolver = (
resolveValue: SimpleResolveValue,
value: StyleFunction,
get: Getter,
options: ResolveValueOptions,
-) => any;
-
-const shorthands: Record<`@${string}`, StyleFunctionResolver> = {
- "@animation": animation,
- "@textShadow": textShadow,
- "@transform": transform,
- "@boxShadow": boxShadow,
- "@border": border,
-};
+) => unknown;
+
+export type StyleResolver = (
+ resolveValue: SimpleResolveValue,
+ value: StyleDescriptor,
+ get: Getter,
+ options: ResolveValueOptions,
+) => unknown;
+
+const shorthands: Record<`@${string}`, StyleFunctionResolver | StyleResolver> =
+ {
+ "@animation": animation,
+ "@textShadow": textShadow,
+ "@transform": transform,
+ "@boxShadow": boxShadow,
+ "@border": border,
+ };
const functions: Record = {
calc,
@@ -100,10 +108,9 @@ export function resolveValue(
}
if (isDescriptorArray(value)) {
- value = value.flatMap((d) => {
- const value = resolveValue(d, get, options);
- return value === undefined ? [] : value;
- }) as StyleDescriptor[];
+ value = value
+ .map((d) => resolveValue(d, get, options))
+ .filter((d) => d !== undefined);
if (castToArray && !Array.isArray(value)) {
return [value];
@@ -127,23 +134,25 @@ export function resolveValue(
throw new Error(`Unknown function: ${name}`);
}
- value = fn(simpleResolve, value as StyleFunction, get, options);
+ value = fn(
+ simpleResolve,
+ value as StyleFunction,
+ get,
+ options,
+ ) as StyleDescriptor;
} else if (transformKeys.has(name)) {
// translate, rotate, scale, etc.
- return simpleResolve(value[2]?.[0], castToArray);
+ const args = value[2];
+ return simpleResolve(args, castToArray);
} else if (transformKeys.has(name.slice(1))) {
// @translate, @rotate, @scale, etc.
- return { [name.slice(1)]: simpleResolve(value[2], castToArray)[0] };
+ return { [name.slice(1)]: simpleResolve(value[2], castToArray) };
} else {
let args = simpleResolve(value[2], castToArray);
if (args === undefined) {
return;
} else if (Array.isArray(args)) {
- if (args.length === 1) {
- args = args[0];
- }
-
let joinedArgs = args
.map((arg: unknown) => {
if (Array.isArray(arg)) {
diff --git a/src/runtime/native/styles/shorthand.ts b/src/runtime/native/styles/shorthand.ts
index 8976976..68b6410 100644
--- a/src/runtime/native/styles/shorthand.ts
+++ b/src/runtime/native/styles/shorthand.ts
@@ -1,7 +1,8 @@
/* eslint-disable */
import type { StyleDescriptor } from "../../../compiler";
+import { isStyleDescriptorArray } from "../../utils";
import { defaultValues } from "./defaults";
-import type { StyleFunctionResolver } from "./resolve";
+import type { StyleResolver } from "./resolve";
type ShorthandType =
| "string"
@@ -25,25 +26,30 @@ export const ShortHandSymbol = Symbol();
export function shorthandHandler(
mappings: ShorthandRequiredValue[][],
defaults: ShorthandDefaultValue[],
-) {
- const resolveFn: StyleFunctionResolver = (
- resolveValue,
- func,
- _,
- { castToArray },
- ) => {
- const args = func[2] || [];
-
- const resolved = args.flatMap((value) => {
- return resolveValue(value, castToArray);
- });
+): StyleResolver {
+ return (resolve, value, __, { castToArray }) => {
+ let args = isStyleDescriptorArray(value)
+ ? resolve(value)
+ : Array.isArray(value)
+ ? resolve(value[2])
+ : value;
+
+ if (!Array.isArray(args)) {
+ return;
+ }
+
+ args = args.flat();
+
+ if (!Array.isArray(args)) {
+ return;
+ }
const match = mappings.find((mapping) => {
return (
- resolved.length === mapping.length &&
+ args.length === mapping.length &&
mapping.every((map, index) => {
const type = map[1];
- const value = resolved[index];
+ const value = args[index];
if (Array.isArray(type)) {
return type.includes(value) || type.includes(typeof value);
@@ -77,12 +83,12 @@ export function shorthandHandler(
seenDefaults.delete(map);
}
- let value = resolved[index];
+ let value = args[index];
if (castToArray && value && !Array.isArray(value)) {
value = [value];
}
- return [value, map[0]];
+ return [value, map[0] as StyleDescriptor];
}),
...Array.from(seenDefaults).map((map): StyleDescriptor => {
let value = defaultValues[map[2]] ?? map[2];
@@ -96,6 +102,4 @@ export function shorthandHandler(
{ [ShortHandSymbol]: true },
);
};
-
- return resolveFn;
}
diff --git a/src/runtime/native/styles/transform.ts b/src/runtime/native/styles/transform.ts
index 691c3e2..4fc51cb 100644
--- a/src/runtime/native/styles/transform.ts
+++ b/src/runtime/native/styles/transform.ts
@@ -1,4 +1,3 @@
-/* eslint-disable */
import type { StyleFunctionResolver } from "./resolve";
/**
@@ -9,7 +8,14 @@ export const transform: StyleFunctionResolver = (
resolveValue,
transformDescriptor,
) => {
- return (transformDescriptor[2] as any[])
- .map((args) => resolveValue(args))
- .filter(Boolean);
+ const transforms = resolveValue(transformDescriptor[2]);
+
+ if (Array.isArray(transforms)) {
+ return transforms.filter((transform) => transform !== undefined) as unknown;
+ } else if (transforms) {
+ // If it's a single transform, wrap it in an array
+ return [transforms];
+ } else {
+ return;
+ }
};
diff --git a/src/runtime/native/styles/units.ts b/src/runtime/native/styles/units.ts
index 6d7de49..7e806b5 100644
--- a/src/runtime/native/styles/units.ts
+++ b/src/runtime/native/styles/units.ts
@@ -3,18 +3,23 @@ import { rem as remObs, vh as vhObs, vw as vwObs } from "../reactivity";
import type { StyleFunctionResolver } from "./resolve";
export const em: StyleFunctionResolver = (resolve, func) => {
- let value = func[2]?.[0];
+ let value = func[2];
if (!value) {
return;
}
const emValue = resolve([{}, "var", ["__rn-css-em"]]);
+
+ if (typeof emValue !== "number") {
+ return undefined;
+ }
+
return round(Number(value) * emValue);
};
export const vw: StyleFunctionResolver = (_, func, get) => {
- const value = func[2]?.[0];
+ const value = func[2];
if (typeof value !== "number") {
return;
@@ -24,7 +29,7 @@ export const vw: StyleFunctionResolver = (_, func, get) => {
};
export const vh: StyleFunctionResolver = (_, func, get) => {
- const value = func[2]?.[0];
+ const value = func[2];
if (typeof value !== "number") {
return;
@@ -34,7 +39,7 @@ export const vh: StyleFunctionResolver = (_, func, get) => {
};
export const rem: StyleFunctionResolver = (_, func, get) => {
- const value = func[2]?.[0];
+ const value = func[2];
if (typeof value !== "number") {
return;
diff --git a/src/runtime/native/styles/variables.ts b/src/runtime/native/styles/variables.ts
index 31f41f3..346cfe4 100644
--- a/src/runtime/native/styles/variables.ts
+++ b/src/runtime/native/styles/variables.ts
@@ -1,5 +1,5 @@
-/* eslint-disable */
-import type { StyleFunction } from "../../../compiler";
+import type { StyleDescriptor, StyleFunction } from "../../../compiler";
+import { isStyleDescriptorArray } from "../../utils";
import {
rootVariables,
universalVariables,
@@ -23,11 +23,23 @@ export function varResolver(
const args = fn[2];
- if (!args) return;
+ let name: string | undefined;
+ let fallback: StyleDescriptor | undefined;
- const [nameDescriptor, fallback] = args;
+ if (typeof args === "string") {
+ name = args;
+ } else {
+ const result = resolve(args);
- const name = resolve(nameDescriptor);
+ if (isStyleDescriptorArray(result)) {
+ name = result[0] as string;
+ fallback = result[1];
+ }
+ }
+
+ if (typeof name !== "string") {
+ return;
+ }
// If this recurses back to the same variable, we need to stop
if (variableHistory.has(name)) {
@@ -41,7 +53,7 @@ export function varResolver(
variableHistory.add(name);
- let value = resolve(inlineVariables?.[name]);
+ let value = resolve(inlineVariables?.[name] as StyleDescriptor);
if (value !== undefined) {
options.inlineVariables ??= { [VAR_SYMBOL]: "inline" };
options.inlineVariables[name] = value;
diff --git a/src/runtime/utils/objects.ts b/src/runtime/utils/objects.ts
index 5d32069..69e7bf7 100644
--- a/src/runtime/utils/objects.ts
+++ b/src/runtime/utils/objects.ts
@@ -28,6 +28,16 @@ export function getDeepPath(source: any, paths: string | string[] | false) {
}
}
+export function applyShorthand(value: any) {
+ if (value === undefined) {
+ return;
+ }
+
+ const target = {};
+ applyValue(target, "", value);
+ return target;
+}
+
export function applyValue(
target: Record,
prop: string,
diff --git a/src/runtime/utils/style-value.ts b/src/runtime/utils/style-value.ts
index d4409e6..2daca9e 100644
--- a/src/runtime/utils/style-value.ts
+++ b/src/runtime/utils/style-value.ts
@@ -1,7 +1,7 @@
import type { StyleDescriptor, StyleFunction } from "../../compiler";
export function isStyleDescriptorArray(
- value: StyleDescriptor | StyleDescriptor[],
+ value: unknown,
): value is StyleDescriptor[] {
if (Array.isArray(value)) {
// If its an array and the first item is an object, the only allowed value is an array