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); } } }