diff --git a/src/__tests__/vendor/tailwind.test.tsx b/src/__tests__/vendor/tailwind.test.tsx
index 275fb847..dbcbcfbc 100644
--- a/src/__tests__/vendor/tailwind.test.tsx
+++ b/src/__tests__/vendor/tailwind.test.tsx
@@ -311,3 +311,45 @@ test("filter", () => {
testID,
});
});
+
+test("line-clamp", () => {
+ const compiled = registerCSS(`
+ .my-class {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1
+ }
+ `);
+
+ expect(compiled.stylesheet()).toStrictEqual({
+ s: [
+ [
+ "my-class",
+ [
+ {
+ d: [
+ {
+ overflow: "hidden",
+ },
+ [1, ["numberOfLines"]],
+ ],
+ s: [1, 1],
+ },
+ ],
+ ],
+ ],
+ });
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props).toStrictEqual({
+ children: undefined,
+ numberOfLines: 1,
+ style: {
+ overflow: "hidden",
+ },
+ testID,
+ });
+});
diff --git a/src/compiler/__tests__/@prop.test.tsx b/src/compiler/__tests__/@prop.test.tsx
index 69d0fee3..3789c977 100644
--- a/src/compiler/__tests__/@prop.test.tsx
+++ b/src/compiler/__tests__/@prop.test.tsx
@@ -1,8 +1,12 @@
+import { render, screen } from "@testing-library/react-native";
+import { View } from "react-native-css/components";
+import { registerCSS, testID } from "react-native-css/jest";
+
import { compile } from "../compiler";
test("@prop single", () => {
- const compiled = compile(`
- .test {
+ const compiled = registerCSS(`
+ .my-class {
color: red;
background-color: blue;
@prop background-color: myBackgroundColor;
@@ -12,14 +16,14 @@ test("@prop single", () => {
expect(compiled.stylesheet()).toStrictEqual({
s: [
[
- "test",
+ "my-class",
[
{
d: [
{
color: "#f00",
- myBackgroundColor: "#00f",
},
+ ["#00f", ["myBackgroundColor"]],
],
v: [["__rn-css-color", "#f00"]],
s: [1, 1],
@@ -28,6 +32,18 @@ test("@prop single", () => {
],
],
});
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props).toStrictEqual({
+ testID,
+ children: undefined,
+ myBackgroundColor: "#00f",
+ style: {
+ color: "#f00",
+ },
+ });
});
test("@prop single, nested value", () => {
@@ -60,12 +76,12 @@ test("@prop single, nested value", () => {
});
});
-test("@prop single, top level", () => {
+test("@prop single, on target", () => {
const compiled = compile(`
.test {
color: red;
background-color: blue;
- @prop background-color: ^myBackgroundColor;
+ @prop background-color: *.myBackgroundColor;
}
`);
@@ -78,8 +94,8 @@ test("@prop single, top level", () => {
d: [
{
color: "#f00",
+ myBackgroundColor: "#00f",
},
- ["#00f", ["^", "myBackgroundColor"]],
],
v: [["__rn-css-color", "#f00"]],
s: [1, 1],
@@ -90,26 +106,26 @@ test("@prop single, top level", () => {
});
});
-test("@prop single, top level, nested", () => {
- const compiled = compile(`
- .test {
+test("@prop single, nested", () => {
+ const compiled = registerCSS(`
+ .my-class {
color: red;
background-color: blue;
- @prop background-color: ^myBackgroundColor.test;
+ @prop background-color: *.myBackgroundColor.test;
}
`);
expect(compiled.stylesheet()).toStrictEqual({
s: [
[
- "test",
+ "my-class",
[
{
d: [
{
color: "#f00",
},
- ["#00f", ["^", "myBackgroundColor", "test"]],
+ ["#00f", ["*", "myBackgroundColor", "test"]],
],
v: [["__rn-css-color", "#f00"]],
s: [1, 1],
@@ -118,6 +134,16 @@ test("@prop single, top level, nested", () => {
],
],
});
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props.style).toStrictEqual({
+ color: "#f00",
+ myBackgroundColor: {
+ test: "#00f",
+ },
+ });
});
test("@prop single, top level, nested", () => {
@@ -125,7 +151,7 @@ test("@prop single, top level, nested", () => {
.test {
color: red;
background-color: blue;
- @prop background-color: ^myBackgroundColor.test;
+ @prop background-color: myBackgroundColor.test;
}
`);
@@ -139,7 +165,7 @@ test("@prop single, top level, nested", () => {
{
color: "#f00",
},
- ["#00f", ["^", "myBackgroundColor", "test"]],
+ ["#00f", ["myBackgroundColor", "test"]],
],
s: [1, 1],
v: [["__rn-css-color", "#f00"]],
@@ -156,8 +182,8 @@ test("@prop multiple", () => {
color: red;
background-color: blue;
@prop {
- background-color: myBackgroundColor;
- color: myColor;
+ background-color: *.myBackgroundColor;
+ color: *.myColor;
}
}
`);
diff --git a/src/compiler/atRules.ts b/src/compiler/atRules.ts
index 19fda131..ba268287 100644
--- a/src/compiler/atRules.ts
+++ b/src/compiler/atRules.ts
@@ -132,7 +132,7 @@ function propAtRuleBlock(
mapping[toRNProperty(fromToken.value.value)] = to.flatMap((item, index) => {
switch (item.value.type) {
case "delim":
- return index === 0 && item.value.value === "^" ? ["^"] : [];
+ return index === 0 && item.value.value === "*" ? ["*"] : [];
case "ident":
return [toRNProperty(item.value.value)];
default:
diff --git a/src/compiler/declarations.ts b/src/compiler/declarations.ts
index aad7e949..1f6c463c 100644
--- a/src/compiler/declarations.ts
+++ b/src/compiler/declarations.ts
@@ -886,6 +886,12 @@ export function parseDeclarationCustom(
property,
parseUnparsed(declaration.value.value, builder, allowAuto.has(property)),
);
+ } else if (property === "-webkit-line-clamp") {
+ builder.addMapping({ [property]: ["numberOfLines"] });
+ builder.addDescriptor(
+ property,
+ parseUnparsed(declaration.value.value, builder, allowAuto.has(property)),
+ );
} else {
builder.addWarning("property", declaration.value.name);
}
diff --git a/src/compiler/stylesheet.ts b/src/compiler/stylesheet.ts
index 0804c857..96905af0 100644
--- a/src/compiler/stylesheet.ts
+++ b/src/compiler/stylesheet.ts
@@ -198,6 +198,10 @@ export class StylesheetBuilder {
};
}
+ addMapping(mapping: StyleRuleMapping) {
+ this.mapping = { ...this.mapping, ...mapping };
+ }
+
newRule(mapping = this.mapping, { important = false } = {}) {
this.mapping = mapping;
this.rule = this.cloneRule(this.ruleTemplate);
@@ -331,30 +335,29 @@ export class StylesheetBuilder {
}
private pushDescriptor(
- property: string,
+ rawProperty: string,
value: StyleDescriptor,
declarations: StyleDeclaration[],
forceTuple = false,
delayed = false,
) {
- property = toRNProperty(property);
+ const property = toRNProperty(rawProperty);
- let propPath: string | string[] =
- this.mapping[property] ?? this.mapping["*"] ?? property;
+ let propPath: string | string[] | undefined =
+ this.mapping[rawProperty] ?? this.mapping[property] ?? this.mapping["*"];
if (Array.isArray(propPath)) {
- const first = propPath[0];
+ const [first, second] = propPath;
- if (!first) {
- // This should not happen, but if it does, we skip the property
- return;
- }
-
- if (propPath.length === 1) {
- propPath = first;
+ if (propPath.length === 2 && first === "*" && second) {
+ propPath = second;
} else {
forceTuple = true;
}
+ } else if (propPath) {
+ forceTuple = true;
+ } else {
+ propPath = property;
}
if (isStyleFunction(value)) {
diff --git a/src/runtime/native/__tests__/selectors.test.tsx b/src/runtime/native/__tests__/selectors.test.tsx
index 633d957a..082f4a26 100644
--- a/src/runtime/native/__tests__/selectors.test.tsx
+++ b/src/runtime/native/__tests__/selectors.test.tsx
@@ -43,7 +43,7 @@ test.skip(':root[class="dark"]', () => {
expect(component.props.style).toStrictEqual({ color: "red" });
});
-test(':root[class~="dark"]', () => {
+test.skip(':root[class~="dark"]', () => {
registerCSS(`
@react-native {
darkMode: dark;
diff --git a/src/runtime/native/styles/calculate-props.ts b/src/runtime/native/styles/calculate-props.ts
index 96b6a2e0..2e38fe5b 100644
--- a/src/runtime/native/styles/calculate-props.ts
+++ b/src/runtime/native/styles/calculate-props.ts
@@ -103,63 +103,73 @@ export function applyDeclarations(
// Dynamic styles
let value: any = declaration[0];
let propPath = declaration[1];
- let prop = "";
+ let prop: string;
- if (typeof propPath === "string") {
- if (propPath.startsWith("^")) {
- propPath = propPath.slice(1);
- target = topLevelTarget[propPath] ??= {};
+ if (Array.isArray(propPath)) {
+ const [first, ...rest] = propPath;
+
+ if (!first) {
+ continue;
}
- prop = propPath;
- } else {
- for (prop of propPath) {
- if (prop.startsWith("^")) {
- prop = prop.slice(1);
- target = topLevelTarget[prop] ??= {};
- } else {
- target = target[prop] ??= {};
+
+ const final = rest.pop();
+
+ if (final) {
+ if (first !== "*") {
+ topLevelTarget[first] ??= {};
+ target = topLevelTarget[first];
+ }
+
+ for (prop of rest) {
+ target[prop] ??= {};
+ target = target[prop];
}
- }
- }
- if (Array.isArray(value)) {
- const shouldDelay = declaration[2];
-
- if (shouldDelay) {
- /**
- * We need to delay the resolution of this value until after all
- * styles have been calculated. But another style might override
- * this value. So we set a placeholder value and only override
- * if the placeholder is preserved
- *
- * This also ensures the props exist, so setValue will properly
- * mutate the props object and not create a new one
- */
- const originalValue = value;
- value = {};
- delayedStyles.push(() => {
- if (target[prop] === value) {
- delete target[prop];
- value = resolveValue(originalValue, get, {
- inlineVariables,
- inheritedVariables,
- renderGuards: guards,
- calculateProps,
- });
- applyValue(target, prop, value);
- }
- });
+ prop = final;
} else {
- value = resolveValue(value, get, {
- inlineVariables,
- inheritedVariables,
- renderGuards: guards,
- calculateProps,
- });
+ target = topLevelTarget;
+ prop = first;
}
+ } else {
+ prop = propPath;
+ }
- applyValue(target, prop, value);
+ const shouldDelay = declaration[2];
+
+ if (shouldDelay) {
+ /**
+ * We need to delay the resolution of this value until after all
+ * styles have been calculated. But another style might override
+ * this value. So we set a placeholder value and only override
+ * if the placeholder is preserved
+ *
+ * This also ensures the props exist, so setValue will properly
+ * mutate the props object and not create a new one
+ */
+ const originalValue = value;
+ value = {};
+ delayedStyles.push(() => {
+ if (target[prop] === value) {
+ delete target[prop];
+ value = resolveValue(originalValue, get, {
+ inlineVariables,
+ inheritedVariables,
+ renderGuards: guards,
+ calculateProps,
+ });
+ applyValue(target, prop, value);
+ }
+ });
+ } else {
+ value = resolveValue(value, get, {
+ inlineVariables,
+ inheritedVariables,
+ renderGuards: guards,
+ calculateProps,
+ });
}
+
+ applyValue(target, prop, value);
}
}
}