diff --git a/.config/jest.config.cjs b/.config/jest.config.cjs
index 66b8c1a..f4e425f 100644
--- a/.config/jest.config.cjs
+++ b/.config/jest.config.cjs
@@ -6,4 +6,5 @@ const jestExpo = require("jest-expo/jest-preset");
module.exports = {
...jestExpo,
testPathIgnorePatterns: ["dist/"],
+ setupFilesAfterEnv: ["./.config/jest.setup.js"],
};
diff --git a/.config/jest.setup.js b/.config/jest.setup.js
index 17ef774..53b5b12 100644
--- a/.config/jest.setup.js
+++ b/.config/jest.setup.js
@@ -1,3 +1,3 @@
-// import { setUpTests } from "react-native-reanimated";
+import { setUpTests } from "react-native-reanimated";
-// setUpTests();
+setUpTests();
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 742a593..e8de0c3 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -6,7 +6,7 @@ export default function App() {
return (
<>
-
+
Test Component
diff --git a/src/compiler/compiler.types.ts b/src/compiler/compiler.types.ts
index d29eb77..78e89e5 100644
--- a/src/compiler/compiler.types.ts
+++ b/src/compiler/compiler.types.ts
@@ -1,11 +1,6 @@
/* eslint-disable */
import type { Debugger } from "debug";
-import type {
- AnimationDirection,
- AnimationFillMode,
- AnimationPlayState,
- MediaFeatureNameFor_MediaFeatureId,
-} from "lightningcss";
+import type { MediaFeatureNameFor_MediaFeatureId } from "lightningcss";
import { VAR_SYMBOL } from "../runtime/native/reactivity";
@@ -147,134 +142,12 @@ export type InlineVariable = {
[key: string]: unknown | undefined;
};
-/****************************** Animations V1 ******************************/
-
-/**
- * An animation with a fallback style value
- */
-export type AnimationWithDefault_V1 =
- | [AnimationRule_V1]
- | [AnimationRule_V1, StyleFunction];
-
-/**
- * A CSS Animation rule
- */
-export interface AnimationRule_V1 {
- /**
- * The animation delay.
- */
- de?: number[];
- /**
- * The direction of the animation.
- */
- di?: AnimationDirection[];
- /**
- * The animation duration.
- */
- du?: number[];
- /**
- * The animation fill mode.
- */
- f?: AnimationFillMode[];
- /**
- * The number of times the animation will run.
- */
- i?: number[];
- /**
- * The animation name.
- */
- n?: string[];
- /**
- * The current play state of the animation.
- */
- p?: AnimationPlayState[];
- /**
- * The animation timeline.
- */
- t?: never[];
- /**
- * The easing function for the animation.
- */
- e?: EasingFunction[];
-}
-
-export type AnimationKeyframes_V1 =
- | [AnimationInterpolation_V1[]]
- | [AnimationInterpolation_V1[], AnimationEasing[]];
-
-export type AnimationEasing = number | [number, EasingFunction];
-
-export type AnimationInterpolation_V1 =
- | [string, number[], StyleDescriptor[]]
- | [string, number[], StyleDescriptor[], number]
- | [string, number[], StyleDescriptor[], number, AnimationInterpolationType];
-
-export type AnimationInterpolationType = "color" | "%" | undefined;
-
-export type EasingFunction =
- | "linear"
- | "ease"
- | "ease-in"
- | "ease-out"
- | "ease-in-out"
- | {
- type: "cubic-bezier";
- /**
- * The x-position of the first point in the curve.
- */
- x1: number;
- /**
- * The x-position of the second point in the curve.
- */
- x2: number;
- /**
- * The y-position of the first point in the curve.
- */
- y1: number;
- /**
- * The y-position of the second point in the curve.
- */
- y2: number;
- }
- | {
- type: "steps";
- /**
- * The number of intervals in the function.
- */
- c: number;
- /**
- * The step position.
- */
- p?: "start" | "end" | "jump-none" | "jump-both";
- };
-
/****************************** Animations V2 ******************************/
export type Animation_V2 = [string, AnimationKeyframes_V2[]];
export type AnimationRecord = Record;
export type AnimationKeyframes_V2 = [string | number, StyleDeclaration[]];
-/****************************** Transitions *****************************/
-
-export type TransitionRule = {
- /**
- * Delay before the transition starts in milliseconds.
- */
- de?: number[];
- /**
- * Duration of the transition in milliseconds.
- */
- du?: number[];
- /**
- * Property to transition.
- */
- p?: string[];
- /**
- * Easing function for the transition.
- */
- e?: EasingFunction[];
-};
-
/****************************** Conditions ******************************/
export type MediaCondition =
@@ -368,7 +241,7 @@ export type LoggerOptions = {
export interface CompilerCollection extends CompilerOptions {
features: FeatureFlagRecord;
rules: Map;
- keyframes: Map;
+ keyframes: Map;
darkMode?: string | null;
rootVariables: VariableRecord;
universalVariables: VariableRecord;
diff --git a/src/compiler/keyframes.ts b/src/compiler/keyframes.ts
index 1c719e2..bf9e2a6 100644
--- a/src/compiler/keyframes.ts
+++ b/src/compiler/keyframes.ts
@@ -4,7 +4,7 @@ import type {
KeyframesRule,
} from "lightningcss";
-import type { EasingFunction, StyleDescriptor } from "./compiler.types";
+import type { StyleDescriptor } from "./compiler.types";
import { parseDeclaration } from "./declarations";
import type { StylesheetBuilder } from "./stylesheet";
@@ -18,8 +18,8 @@ export function parseIterationCount(
export function parseEasingFunction(
value: CSSEasingFunction[],
-): StyleDescriptor[] {
- return value.map((value): EasingFunction => {
+): StyleDescriptor {
+ const easingFn = value.map((value) => {
switch (value.type) {
case "linear":
case "ease":
@@ -28,15 +28,20 @@ export function parseEasingFunction(
case "ease-in-out":
return value.type;
case "cubic-bezier":
- return value;
+ return [
+ {},
+ "cubic-bezier",
+ [value.x1, value.y1, value.x2, value.y2],
+ ] as const;
case "steps":
- return {
- type: "steps",
- c: value.count,
- p: value.position?.type,
- };
+ return [{}, "steps", [value.count, value.position?.type]] as const;
}
}) as StyleDescriptor[];
+
+ if (easingFn.length === 1) {
+ return easingFn[0];
+ }
+ return easingFn;
}
export function extractKeyFrames(
diff --git a/src/compiler/stylesheet.ts b/src/compiler/stylesheet.ts
index 64280b2..d180c42 100644
--- a/src/compiler/stylesheet.ts
+++ b/src/compiler/stylesheet.ts
@@ -266,6 +266,8 @@ export class StylesheetBuilder {
}
} else if (forceTuple || Array.isArray(propPath)) {
declarations.push([value, propPath]);
+ } else if (Array.isArray(value) && value.some(isStyleFunction)) {
+ declarations.push([value, propPath]);
} else {
if (!this.staticDeclarations) {
this.staticDeclarations = {};
diff --git a/src/runtime/native/styles/animation.ts b/src/runtime/native/styles/animation.ts
index 255a1e5..8da3f3d 100644
--- a/src/runtime/native/styles/animation.ts
+++ b/src/runtime/native/styles/animation.ts
@@ -1,6 +1,7 @@
/* eslint-disable */
import type { ComponentType } from "react";
+import { applyShorthand } from "../../utils";
import { StyleCollection } from "../injection";
import { weakFamily } from "../reactivity";
import type { StyleFunctionResolver } from "./resolve";
@@ -57,6 +58,7 @@ export const animationShorthand = shorthandHandler(
playState,
timingFunction,
],
+ "tuples",
);
export const animatedComponentFamily = weakFamily(
@@ -92,6 +94,8 @@ export const animation: StyleFunctionResolver = (
return;
}
+ animationShortHandTuples.pop();
+
const nameTuple = animationShortHandTuples.find(
(tuple) => tuple[1] === "animationName",
);
@@ -133,7 +137,7 @@ export const animation: StyleFunctionResolver = (
nameTuple[0] = animation;
- return animationShortHandTuples;
+ return applyShorthand(animationShortHandTuples);
};
const advancedTimingFunctions: Record<
diff --git a/src/runtime/native/styles/box-shadow.ts b/src/runtime/native/styles/box-shadow.ts
index 29c5c88..e6d0786 100644
--- a/src/runtime/native/styles/box-shadow.ts
+++ b/src/runtime/native/styles/box-shadow.ts
@@ -1,4 +1,4 @@
-import { applyShorthand, isStyleDescriptorArray } from "../../utils";
+import { isStyleDescriptorArray } from "../../utils";
import type { StyleFunctionResolver } from "./resolve";
import { shorthandHandler } from "./shorthand";
@@ -19,6 +19,7 @@ const handler = shorthandHandler(
[offsetX, offsetY, blurRadius, color],
],
[],
+ "object",
);
export const boxShadow: StyleFunctionResolver = (
@@ -38,17 +39,15 @@ export const boxShadow: StyleFunctionResolver = (
} else if (isStyleDescriptorArray(shadows)) {
if (shadows.length === 0) {
return [];
- } else {
+ } else if (Array.isArray(shadows[0])) {
return shadows
- .map((shadow) => {
- return applyShorthand(
- handler(resolveValue, shadow, get, options),
- );
- })
+ .map((shadow) => handler(resolveValue, shadow, get, options))
.filter((v) => v !== undefined);
+ } else {
+ return handler(resolveValue, shadows, get, options);
}
} else {
- return applyShorthand(handler(resolveValue, shadows, get, options));
+ return handler(resolveValue, shadows, get, options);
}
});
}
diff --git a/src/runtime/native/styles/calc.ts b/src/runtime/native/styles/calc.ts
index 530ac9a..911c760 100644
--- a/src/runtime/native/styles/calc.ts
+++ b/src/runtime/native/styles/calc.ts
@@ -30,7 +30,7 @@ export const calc: StyleFunctionResolver = (resolveValue, func) => {
} else if (token === ")") {
// Resolve all values within the brackets
while (ops.length && ops[ops.length - 1] !== "(") {
- applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
+ applyCalcOperator(ops.pop()!, values.pop(), values.pop(), values);
}
ops.pop();
} else if (token.endsWith("%")) {
@@ -45,7 +45,7 @@ export const calc: StyleFunctionResolver = (resolveValue, func) => {
// @ts-ignore
calcPrecedence[ops[ops.length - 1]] >= calcPrecedence[token]
) {
- applyCalcOperator(ops.pop()!, values.pop()!, values.pop()!, values);
+ applyCalcOperator(ops.pop()!, values.pop(), values.pop(), values);
}
ops.push(token);
}
@@ -54,31 +54,9 @@ export const calc: StyleFunctionResolver = (resolveValue, func) => {
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);
+ applyCalcOperator(ops.pop()!, values.pop(), values.pop(), values);
}
if (!mode) return;
@@ -100,8 +78,8 @@ export const calc: StyleFunctionResolver = (resolveValue, func) => {
function applyCalcOperator(
operator: string,
- b: number, // These are reversed because we pop them off the stack
- a: number,
+ b = 0, // These are reversed because we pop them off the stack
+ a = 0,
values: number[],
) {
switch (operator) {
diff --git a/src/runtime/native/styles/constants.ts b/src/runtime/native/styles/constants.ts
index 6bc8a4e..8de5af9 100644
--- a/src/runtime/native/styles/constants.ts
+++ b/src/runtime/native/styles/constants.ts
@@ -1 +1,2 @@
export const emVariableName = "__rn-css-em";
+export const ShortHandSymbol = Symbol();
diff --git a/src/runtime/native/styles/shorthand.ts b/src/runtime/native/styles/shorthand.ts
index 68b6410..ca074a5 100644
--- a/src/runtime/native/styles/shorthand.ts
+++ b/src/runtime/native/styles/shorthand.ts
@@ -1,6 +1,7 @@
/* eslint-disable */
-import type { StyleDescriptor } from "../../../compiler";
-import { isStyleDescriptorArray } from "../../utils";
+import { setDeepPath } from "../../utils/objects";
+import { isStyleDescriptorArray } from "../../utils/style-value";
+import { ShortHandSymbol } from "./constants";
import { defaultValues } from "./defaults";
import type { StyleResolver } from "./resolve";
@@ -21,11 +22,10 @@ type ShorthandDefaultValue = readonly [
any,
];
-export const ShortHandSymbol = Symbol();
-
export function shorthandHandler(
mappings: ShorthandRequiredValue[][],
defaults: ShorthandDefaultValue[],
+ returnType: "shorthandObject" | "tuples" | "object" = "shorthandObject",
): StyleResolver {
return (resolve, value, __, { castToArray }) => {
let args = isStyleDescriptorArray(value)
@@ -76,30 +76,46 @@ export function shorthandHandler(
const seenDefaults = new Set(defaults);
- return Object.assign(
- [
- ...match.map((map, index): StyleDescriptor => {
- if (map.length === 3) {
- seenDefaults.delete(map);
- }
-
- let value = args[index];
- if (castToArray && value && !Array.isArray(value)) {
- value = [value];
- }
-
- return [value, map[0] as StyleDescriptor];
- }),
- ...Array.from(seenDefaults).map((map): StyleDescriptor => {
+ const tuples = [
+ ...match.map((map, index): [unknown, ShorthandRequiredValue[0]] => {
+ if (map.length === 3) {
+ seenDefaults.delete(map);
+ }
+
+ let value = args[index];
+ if (castToArray && value && !Array.isArray(value)) {
+ value = [value];
+ }
+
+ return [value, map[0]];
+ }),
+ ...Array.from(seenDefaults).map(
+ (map): [unknown, ShorthandRequiredValue[0]] => {
let value = defaultValues[map[2]] ?? map[2];
if (castToArray && value && !Array.isArray(value)) {
value = [value];
}
return [value, map[0]];
- }),
- ],
- { [ShortHandSymbol]: true },
- );
+ },
+ ),
+ ];
+
+ if (returnType === "shorthandObject" || returnType === "object") {
+ const target: Record =
+ returnType === "shorthandObject" ? { [ShortHandSymbol]: true } : {};
+
+ for (const [value, prop] of tuples) {
+ if (typeof prop === "string") {
+ target[prop] = value;
+ } else {
+ setDeepPath(target, prop, value);
+ }
+ }
+
+ return target;
+ } else {
+ return tuples;
+ }
};
}
diff --git a/src/runtime/runtime.types.ts b/src/runtime/runtime.types.ts
index 118e6ba..08214b4 100644
--- a/src/runtime/runtime.types.ts
+++ b/src/runtime/runtime.types.ts
@@ -7,12 +7,6 @@ import type {
ViewStyle,
} from "react-native";
-import type { makeMutable, SharedValue } from "react-native-reanimated";
-
-import type {
- AnimationInterpolation_V1,
- AnimationKeyframes_V1,
-} from "../compiler";
import type { ComponentPropsDotNotation, ReactComponent } from "./utils";
import type { DotNotation, ResolveDotPath } from "./utils/dot-notation.types";
@@ -122,25 +116,6 @@ export type InlineStyle =
| (Record | undefined | null)[]
| (() => unknown);
-/****************************** Animations ******************************/
-
-export type Mutable = ReturnType>;
-export type AnimationMutable = Mutable;
-
-export interface KeyFramesWithStyles {
- animation: AnimationKeyframes_V1;
- baseStyles: Record;
-}
-
-export type SharedValueInterpolation = [
- SharedValue,
- AnimationInterpolation_V1[],
-];
-
-/****************************** Transitions *****************************/
-
-export type Transition = [string | string[], Mutable];
-
/********************************* Misc *********************************/
export type Props = Record | undefined | null;
diff --git a/src/runtime/utils/objects.ts b/src/runtime/utils/objects.ts
index 69e7bf7..7109e5c 100644
--- a/src/runtime/utils/objects.ts
+++ b/src/runtime/utils/objects.ts
@@ -1,6 +1,6 @@
/* eslint-disable */
+import { ShortHandSymbol } from "../native/styles/constants";
import { transformKeys } from "../native/styles/defaults";
-import { ShortHandSymbol } from "../native/styles/shorthand";
export function getDeepPath(source: any, paths: string | string[] | false) {
if (!source) {
@@ -33,8 +33,14 @@ export function applyShorthand(value: any) {
return;
}
- const target = {};
- applyValue(target, "", value);
+ const target: Record = { [ShortHandSymbol]: true };
+
+ const values = value as [unknown, string][];
+
+ for (const [value, prop] of values) {
+ target[prop] = value;
+ }
+
return target;
}
@@ -61,9 +67,8 @@ export function applyValue(
}
return;
} else if (typeof value === "object" && value && ShortHandSymbol in value) {
- for (const entry of value) {
- setDeepPath(target, entry[1], entry[0]);
- }
+ delete value[ShortHandSymbol];
+ Object.assign(target, value);
return;
}
@@ -72,7 +77,7 @@ export function applyValue(
export function setDeepPath(
target: Record,
- paths: string | string[],
+ paths: string | string[] | readonly string[],
value: any,
) {
if (typeof paths === "string") {