mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: implement css-variables and css-calc (#7553)
* feat: implement basic support for css-variables * fix(test): test-watch-android and test-watch-ios was broken * fix: processing css-variables belong in CssProperty-classes Not in the StyleScope. * fix(css-variables): set style attribute override value from css-classes * feat: add css calc-support using 'reduce-css-calc' * fix(tslint): missing semicolon and incorrect quotemark * feat: move css-variable handling to Style-class * chor: add comments explaining css-variable implmentation * chor: set css-variables before other style properties * chor(css-variables): cleaning up * chor: code style fixes * test(CSS-CALC): Add tests for nested css-calc statements * fix(CSS-CALC): dip-unit not supported by reduce-css-calc * fix(tslint): use double quotemarks * test(css-calc): test _cssCalcConverter directly * chor(css-variables): rename and clean up _cssVariableConverter to _evaluateCssVariable * chor: rename varname to varName for consistency * chor: support css-calc and variables for normal properties * chor: use string.replace to evaluate css-variables * fix: Missing blank line before return * chor: rename css-calc functions * fix: undefined css-variables treated as 'unset' * fix(tslint): use double quotemarks * feat(css-variable): handle fallback values * chor(css-variables): handle unsetValue * chor: process css-calc and css-variables in style-scope * chore: clean-up css-calc/variable expressions * fix(css-calc): handle invalid expressions * chore(CSSState): update comments * chore(Style): rename css-variable functions * chore(css-variables): describe fallback logic * chore: move reset scoped css-variables to Style-class * chore(CssState): simplify check for css expressions * chore: add reduce-css-calc to /package.json
This commit is contained in:
committed by
Svetoslav
parent
0adcb64cc3
commit
673c8087e0
@@ -13,6 +13,7 @@ import { resolveFileNameFromUrl, removeTaggedAdditionalCSS, addTaggedAdditionalC
|
||||
import { unsetValue } from "tns-core-modules/ui/core/view";
|
||||
import * as color from "tns-core-modules/color";
|
||||
import * as fs from "tns-core-modules/file-system";
|
||||
import { _evaluateCssCalcExpression } from "tns-core-modules/ui/core/properties/properties";
|
||||
|
||||
export function test_css_dataURI_is_applied_to_backgroundImageSource() {
|
||||
const stack = new stackModule.StackLayout();
|
||||
@@ -1426,6 +1427,413 @@ export function test_CascadingClassNamesAppliesAfterPageLoad() {
|
||||
});
|
||||
}
|
||||
|
||||
export function test_evaluateCssCalcExpression() {
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(1px + 1px)"), "2px", "Simple calc (1)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(50px - (20px - 30px))"), "60px", "Simple calc (2)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(100px - (100px - 100%))"), "100%", "Simple calc (3)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(100px + (100px - 100%))"), "calc(200px - 100%)", "Simple calc (4)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(100% - 10px + 20px)"), "calc(100% + 10px)", "Simple calc (5)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(100% + 10px - 20px)"), "calc(100% - 10px)", "Simple calc (6)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(10.px + .0px)"), "10px", "Simple calc (8)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("a calc(1px + 1px)"), "a 2px", "Ignore value surrounding calc function (1)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(1px + 1px) a"), "2px a", "Ignore value surrounding calc function (2)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("a calc(1px + 1px) b"), "a 2px b", "Ignore value surrounding calc function (3)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("a calc(1px + 1px) b calc(1em + 2em) c"), "a 2px b 3em c", "Ignore value surrounding calc function (4)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression(`calc(\n1px \n* 2 \n* 1.5)`), "3px", "Handle new lines");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(1/100)"), "0.01", "Handle precision correctly (1)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(5/1000000)"), "0.00001", "Handle precision correctly (2)");
|
||||
TKUnit.assertEqual(_evaluateCssCalcExpression("calc(5/100000)"), "0.00005", "Handle precision correctly (3)");
|
||||
}
|
||||
|
||||
export function test_css_calc() {
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout.slim {
|
||||
width: calc(100 * .1);
|
||||
}
|
||||
|
||||
StackLayout.wide {
|
||||
width: calc(100 * 1.25);
|
||||
}
|
||||
|
||||
StackLayout.invalid-css-calc {
|
||||
width: calc(asd3 * 1.25);
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack.addChild(label);
|
||||
|
||||
stack.className = "slim";
|
||||
TKUnit.assertEqual(stack.width as any, 10, "Stack - width === 10");
|
||||
|
||||
stack.className = "wide";
|
||||
TKUnit.assertEqual(stack.width as any, 125, "Stack - width === 125");
|
||||
|
||||
(stack as any).style = `width: calc(100% / 2)`;
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
|
||||
|
||||
// This should log an error for the invalid css-calc expression, but not cause a crash
|
||||
stack.className = "invalid-css-calc";
|
||||
}
|
||||
|
||||
export function test_css_calc_units() {
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout.no_unit {
|
||||
width: calc(100 * .1);
|
||||
}
|
||||
|
||||
StackLayout.dip_unit {
|
||||
width: calc(100dip * .1);
|
||||
}
|
||||
|
||||
StackLayout.pct_unit {
|
||||
width: calc(100% * .1);
|
||||
}
|
||||
|
||||
StackLayout.px_unit {
|
||||
width: calc(100px * .1);
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack.addChild(label);
|
||||
|
||||
stack.className = "no_unit";
|
||||
TKUnit.assertEqual(stack.width as any, 10, "Stack - width === 10");
|
||||
|
||||
stack.className = "dip_unit";
|
||||
TKUnit.assertEqual(stack.width as any, 10, "Stack - width === 10dip");
|
||||
|
||||
stack.className = "pct_unit";
|
||||
TKUnit.assertDeepEqual(stack.width as any, { unit: "%", value: 0.1 }, "Stack - width === 10%");
|
||||
|
||||
stack.className = "px_unit";
|
||||
TKUnit.assertDeepEqual(stack.width as any, { unit: "px", value: 10 }, "Stack - width === 10px");
|
||||
}
|
||||
|
||||
export function test_nested_css_calc() {
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout.slim {
|
||||
width: calc(calc(10 * 10) * .1);
|
||||
}
|
||||
|
||||
StackLayout.wide {
|
||||
width: calc(calc(10 * 10) * 1.25);
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack.addChild(label);
|
||||
|
||||
stack.className = "slim";
|
||||
TKUnit.assertEqual(stack.width as any, 10, "Stack - width === 10");
|
||||
|
||||
stack.className = "wide";
|
||||
TKUnit.assertEqual(stack.width as any, 125, "Stack - width === 125");
|
||||
|
||||
(stack as any).style = `width: calc(100% * calc(1 / 2)`;
|
||||
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
|
||||
}
|
||||
|
||||
export function test_css_variables() {
|
||||
const blackColor = "#000000";
|
||||
const redColor = "#FF0000";
|
||||
const greenColor = "#00FF00";
|
||||
const blueColor = "#0000FF";
|
||||
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const cssVarName = `--my-background-color-${Date.now()}`;
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout[use-css-vars] {
|
||||
background-color: var(${cssVarName});
|
||||
}
|
||||
|
||||
StackLayout.make-red {
|
||||
${cssVarName}: red;
|
||||
}
|
||||
|
||||
StackLayout.make-blue {
|
||||
${cssVarName}: blue;
|
||||
}
|
||||
|
||||
Label.lab1 {
|
||||
background-color: var(${cssVarName});
|
||||
color: black;
|
||||
}`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack.addChild(label);
|
||||
|
||||
// This should log an error about not finding the css-variable but not cause a crash
|
||||
stack["use-css-vars"] = true;
|
||||
label.className = "lab1";
|
||||
|
||||
stack.className = "make-red";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, redColor, "Stack - background-color is red");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, redColor, "Label - background-color is red");
|
||||
|
||||
stack.className = "make-blue";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, blueColor, "Stack - background-color is blue");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, blueColor, "Label - background-color is blue");
|
||||
|
||||
stack.className = "make-red";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, redColor, "Stack - background-color is red");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, redColor, "Label - background-color is red");
|
||||
|
||||
// view.style takes priority over css-classes.
|
||||
(stack as any).style = `${cssVarName}: ${greenColor}`;
|
||||
stack.className = "";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, greenColor, "Stack - background-color is green");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, greenColor, "Label - background-color is green");
|
||||
|
||||
stack.className = "make-red";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, greenColor, "Stack - background-color is green");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, greenColor, "Label - background-color is green");
|
||||
|
||||
(stack as any).style = "";
|
||||
TKUnit.assertEqual(label.color.hex, blackColor, "text color is black");
|
||||
TKUnit.assertEqual((<color.Color>stack.backgroundColor).hex, redColor, "Stack - background-color is red");
|
||||
TKUnit.assertEqual((<color.Color>label.backgroundColor).hex, redColor, "Label - background-color is red");
|
||||
}
|
||||
|
||||
export function test_css_calc_and_variables() {
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const cssVarName = `--my-width-factor-${Date.now()}`;
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout[use-css-vars] {
|
||||
${cssVarName}: 1;
|
||||
width: calc(100% * var(${cssVarName}));
|
||||
}
|
||||
|
||||
StackLayout.slim {
|
||||
${cssVarName}: 0.1;
|
||||
}
|
||||
|
||||
StackLayout.wide {
|
||||
${cssVarName}: 1.25;
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack["use-css-vars"] = true;
|
||||
stack.addChild(label);
|
||||
|
||||
stack.className = "";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1 }, "Stack - width === 100%");
|
||||
|
||||
stack.className = "slim";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.1 }, "Stack - width === 10%");
|
||||
|
||||
stack.className = "wide";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1.25 }, "Stack - width === 125%");
|
||||
|
||||
// Test setting the CSS variable via the style-attribute, this should override any value set via css-class
|
||||
(stack as any).style = `${cssVarName}: 0.5`;
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
|
||||
}
|
||||
|
||||
export function test_css_variable_fallback() {
|
||||
const redColor = "#FF0000";
|
||||
const blueColor = "#0000FF";
|
||||
const limeColor = new color.Color("lime").hex;
|
||||
const yellowColor = new color.Color("yellow").hex;
|
||||
|
||||
const classToValue = [
|
||||
{
|
||||
className: "defined-css-variable",
|
||||
expectedColor: blueColor,
|
||||
}, {
|
||||
className: "defined-css-variable-with-fallback",
|
||||
expectedColor: blueColor,
|
||||
}, {
|
||||
className: "undefined-css-variable-without-fallback",
|
||||
expectedColor: undefined,
|
||||
}, {
|
||||
className: "undefined-css-variable-with-fallback",
|
||||
expectedColor: redColor,
|
||||
}, {
|
||||
className: "undefined-css-variable-with-defined-fallback",
|
||||
expectedColor: limeColor,
|
||||
}, {
|
||||
className: "undefined-css-variable-with-multiple-fallbacks",
|
||||
expectedColor: limeColor,
|
||||
}, {
|
||||
className: "undefined-css-variable-with-missing-fallback-value",
|
||||
expectedColor: undefined,
|
||||
}, {
|
||||
className: "undefined-css-variable-with-nested-fallback",
|
||||
expectedColor: yellowColor,
|
||||
},
|
||||
];
|
||||
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
.defined-css-variable {
|
||||
--my-var: blue;
|
||||
color: var(--my-var); /* resolved as color: blue; */
|
||||
}
|
||||
|
||||
.defined-css-variable-with-fallback {
|
||||
--my-var: blue;
|
||||
color: var(--my-var, red); /* resolved as color: blue; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-without-fallback {
|
||||
color: var(--undefined-var); /* resolved as color: unset; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-with-fallback {
|
||||
color: var(--undefined-var, red); /* resolved as color: red; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-with-defined-fallback {
|
||||
--my-fallback-var: lime;
|
||||
color: var(--undefined-var, var(--my-fallback-var)); /* resolved as color: lime; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-with-multiple-fallbacks {
|
||||
--my-fallback-var: lime;
|
||||
color: var(--undefined-var, var(--my-fallback-var), yellow); /* resolved as color: lime; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-with-missing-fallback-value {
|
||||
color: var(--undefined-var, var(--undefined-fallback-var)); /* resolved as color: unset; */
|
||||
}
|
||||
|
||||
.undefined-css-variable-with-nested-fallback {
|
||||
color: var(--undefined-var, var(--undefined-fallback-var, yellow)); /* resolved as color: yellow; */
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack.addChild(label);
|
||||
|
||||
for (const { className, expectedColor } of classToValue) {
|
||||
label.className = className;
|
||||
TKUnit.assertEqual(label.color && label.color.hex, expectedColor, className);
|
||||
}
|
||||
}
|
||||
|
||||
export function test_nested_css_calc_and_variables() {
|
||||
const page = helper.getClearCurrentPage();
|
||||
|
||||
const cssVarName = `--my-width-factor-base-${Date.now()}`;
|
||||
const cssVarName2 = `--my-width-factor-${Date.now()}`;
|
||||
|
||||
const stack = new stackModule.StackLayout();
|
||||
stack.css = `
|
||||
StackLayout[use-css-vars] {
|
||||
${cssVarName}: 0.5;
|
||||
${cssVarName2}: var(${cssVarName});
|
||||
width: calc(100% * calc(var(${cssVarName2}) * 2));
|
||||
}
|
||||
|
||||
StackLayout.slim {
|
||||
${cssVarName}: 0.05;
|
||||
}
|
||||
|
||||
StackLayout.wide {
|
||||
${cssVarName}: 0.625
|
||||
}
|
||||
|
||||
StackLayout.nested {
|
||||
${cssVarName2}: calc(var(${cssVarName}) * 2);
|
||||
}
|
||||
`;
|
||||
|
||||
const label = new labelModule.Label();
|
||||
page.content = stack;
|
||||
stack["use-css-vars"] = true;
|
||||
stack.addChild(label);
|
||||
|
||||
stack.className = "";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1 }, "Stack - width === 100%");
|
||||
|
||||
stack.className = "nested";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 2 }, "Stack - width === 200%");
|
||||
|
||||
stack.className = "slim";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.1 }, "Stack - width === 10%");
|
||||
|
||||
stack.className = "slim nested";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.2 }, "Stack - width === 20%");
|
||||
|
||||
stack.className = "wide";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1.25 }, "Stack - width === 125%");
|
||||
|
||||
stack.className = "wide nested";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 2.5 }, "Stack - width === 250%");
|
||||
|
||||
// Test setting the CSS variable via the style-attribute, this should override any value set via css-class
|
||||
stack.className = "wide";
|
||||
(stack as any).style = `${cssVarName}: 0.25`;
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 0.5 }, "Stack - width === 50%");
|
||||
|
||||
stack.className = "nested";
|
||||
TKUnit.assertDeepEqual(stack.width, { unit: "%", value: 1 }, "Stack - width === 100%");
|
||||
}
|
||||
|
||||
export function test_css_variable_is_applied_to_normal_properties() {
|
||||
const stack = new stackModule.StackLayout();
|
||||
|
||||
const cssVarName = `--my-custom-variable-${Date.now()}`;
|
||||
|
||||
helper.buildUIAndRunTest(stack, function (views: Array<viewModule.View>) {
|
||||
const page = <pageModule.Page>views[1];
|
||||
const expected = "horizontal";
|
||||
page.css = `StackLayout {
|
||||
${cssVarName}: ${expected};
|
||||
orientation: var(${cssVarName});
|
||||
}`;
|
||||
TKUnit.assertEqual(stack.orientation, expected);
|
||||
});
|
||||
}
|
||||
|
||||
export function test_css_variable_is_applied_to_special_properties() {
|
||||
const stack = new stackModule.StackLayout();
|
||||
|
||||
const cssVarName = `--my-custom-variable-${Date.now()}`;
|
||||
|
||||
helper.buildUIAndRunTest(stack, function (views: Array<viewModule.View>) {
|
||||
const page = <pageModule.Page>views[1];
|
||||
const expected = "test";
|
||||
page.css = `StackLayout {
|
||||
${cssVarName}: ${expected};
|
||||
class: var(${cssVarName});
|
||||
}`;
|
||||
TKUnit.assertEqual(stack.className, expected);
|
||||
});
|
||||
}
|
||||
|
||||
export function test_resolveFileNameFromUrl_local_file_tilda() {
|
||||
const localFileExistsMock = (fileName: string) => true;
|
||||
const url = "~/theme/core.css";
|
||||
|
||||
Reference in New Issue
Block a user