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
@@ -34,6 +34,7 @@
|
||||
"nativescript-typedoc-theme": "git://github.com/NativeScript/nativescript-typedoc-theme.git#master",
|
||||
"parse-css": "git+https://github.com/tabatkins/parse-css.git",
|
||||
"parserlib": "^1.1.1",
|
||||
"reduce-css-calc": "^2.1.6",
|
||||
"shady-css-parser": "^0.1.0",
|
||||
"shelljs": "^0.7.0",
|
||||
"source-map-support": "^0.4.17",
|
||||
@@ -71,8 +72,8 @@
|
||||
"pretest": "npm run setup && npm run tsc",
|
||||
"test-android": "tns run android --path tests --justlaunch --no-watch",
|
||||
"test-ios": "tns run ios --path tests --justlaunch --no-watch",
|
||||
"test-watch-android": "npm run pretest && concurrently --kill-others \"npm run tsc-tiw\" \"tns livesync android --path tests --watch\"",
|
||||
"test-watch-ios": "npm run pretest && concurrently --kill-others \"npm run tsc-tiw\" \"tns livesync ios --path tests --watch\"",
|
||||
"test-watch-android": "npm run pretest && concurrently --kill-others \"npm run tsc-w\" \"tns run android --path tests --watch\"",
|
||||
"test-watch-ios": "npm run pretest && concurrently --kill-others \"npm run tsc-w\" \"tns run ios --path tests --watch\"",
|
||||
"typedoc": "typedoc --tsconfig tsconfig.typedoc.json --out bin/dist/apiref --includeDeclarations --name NativeScript --theme ./node_modules/nativescript-typedoc-theme --excludeExternals --externalPattern \"**/+(tns-core-modules|module).d.ts\"",
|
||||
"dev-typedoc": "npm run typedoc && cd bin/dist/apiref && npx http-server",
|
||||
"test-tsc-es2016": "npm run tsc -- -p tsconfig.public.es2016.json",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"typings": "tns-core-modules.d.ts",
|
||||
"dependencies": {
|
||||
"reduce-css-calc": "^2.1.6",
|
||||
"tns-core-modules-widgets": "next",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
|
||||
@@ -144,6 +144,10 @@ export function makeParser<T>(isValid: (value: any) => boolean): (value: any) =>
|
||||
export function getSetProperties(view: ViewBase): [string, any][];
|
||||
export function getComputedCssValues(view: ViewBase): [string, any][];
|
||||
|
||||
export function isCssVariable(property: string): boolean;
|
||||
export function isCssCalcExpression(value: string): boolean;
|
||||
export function isCssVariableExpression(value: string): boolean;
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @private get all properties defined on ViewBase
|
||||
@@ -154,4 +158,8 @@ export function _getProperties(): Property<any, any>[];
|
||||
* @private get all properties defined on Style
|
||||
*/
|
||||
export function _getStyleProperties(): CssProperty<any, any>[];
|
||||
|
||||
export function _evaluateCssVariableExpression(view: ViewBase, cssName: string, value: string): string;
|
||||
|
||||
export function _evaluateCssCalcExpression(value: string): string;
|
||||
//@endprivate
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import reduceCSSCalc from "reduce-css-calc";
|
||||
|
||||
// Definitions.
|
||||
import * as definitions from "../view-base";
|
||||
import { ViewBase } from "../view-base";
|
||||
@@ -56,6 +58,76 @@ export function _getStyleProperties(): CssProperty<any, any>[] {
|
||||
return getPropertiesFromMap(cssSymbolPropertyMap) as CssProperty<any, any>[];
|
||||
}
|
||||
|
||||
const cssVariableExpressionRegexp = /\bvar\(\s*(--[^,\s]+?)(?:\s*,\s*(.+))?\s*\)/;
|
||||
const cssVariableAllExpressionsRegexp = /\bvar\(\s*(--[^,\s]+?)(?:\s*,\s*(.+))?\s*\)/g;
|
||||
|
||||
export function isCssVariable(property: string) {
|
||||
return /^--[^,\s]+?$/.test(property);
|
||||
}
|
||||
|
||||
export function isCssCalcExpression(value: string) {
|
||||
return /\bcalc\(/.test(value);
|
||||
}
|
||||
|
||||
export function isCssVariableExpression(value: string) {
|
||||
return cssVariableExpressionRegexp.test(value);
|
||||
}
|
||||
|
||||
export function _evaluateCssVariableExpression(view: ViewBase, cssName: string, value: string): string {
|
||||
if (typeof value !== "string") {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!isCssVariableExpression(value)) {
|
||||
// Value is not using css-variable(s)
|
||||
return value;
|
||||
}
|
||||
|
||||
let output = value.trim();
|
||||
|
||||
// Evaluate every (and nested) css-variables in the value.
|
||||
let lastValue: string;
|
||||
while (lastValue !== output) {
|
||||
lastValue = output;
|
||||
|
||||
output = output.replace(cssVariableAllExpressionsRegexp, (matchStr, cssVariableName: string, fallbackStr: string) => {
|
||||
const cssVariableValue = view.style.getCssVariable(cssVariableName);
|
||||
if (cssVariableValue !== null) {
|
||||
return cssVariableValue;
|
||||
}
|
||||
|
||||
if (fallbackStr) {
|
||||
// css-variable not found, using fallback-string.
|
||||
const fallbackOutput = _evaluateCssVariableExpression(view, cssName, fallbackStr);
|
||||
if (fallbackOutput) {
|
||||
// If the fallback have multiple values, return the first of them.
|
||||
return fallbackOutput.split(",")[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't find a value for the css-variable or the fallback, return "unset"
|
||||
traceWrite(`Failed to get value for css-variable "${cssVariableName}" used in "${cssName}"=[${value}] to ${view}`, traceCategories.Style, traceMessageType.error);
|
||||
|
||||
return "unset";
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function _evaluateCssCalcExpression(value: string) {
|
||||
if (typeof value !== "string") {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (isCssCalcExpression(value)) {
|
||||
// WORKAROUND: reduce-css-calc can't handle the dip-unit.
|
||||
return reduceCSSCalc(value.replace(/([0-9]+(\.[0-9]+)?)dip\b/g, "$1"));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function getPropertiesFromMap(map): Property<any, any>[] | CssProperty<any, any>[] {
|
||||
const props = [];
|
||||
Object.getOwnPropertySymbols(map).forEach(symbol => props.push(map[symbol]));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Keyframes } from "../animation/keyframe-animation";
|
||||
import { ViewBase } from "../core/view-base";
|
||||
import { View } from "../core/view";
|
||||
import { unsetValue } from "../core/properties";
|
||||
import { unsetValue, _evaluateCssVariableExpression, _evaluateCssCalcExpression, isCssVariable, isCssVariableExpression, isCssCalcExpression } from "../core/properties";
|
||||
import {
|
||||
SyntaxTree,
|
||||
Keyframes as KeyframesDefinition,
|
||||
@@ -42,6 +42,7 @@ function ensureKeyframeAnimationModule() {
|
||||
import * as capm from "./css-animation-parser";
|
||||
import { sanitizeModuleName } from "../builder/module-name-sanitizer";
|
||||
import { resolveModuleName } from "../../module-name-resolver";
|
||||
|
||||
let cssAnimationParserModule: typeof capm;
|
||||
function ensureCssAnimationParserModule() {
|
||||
if (!cssAnimationParserModule) {
|
||||
@@ -59,6 +60,28 @@ try {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate css-variable and css-calc expressions
|
||||
*/
|
||||
function evaluateCssExpressions(view: ViewBase, property: string, value: string) {
|
||||
const newValue = _evaluateCssVariableExpression(view, property, value);
|
||||
if (newValue === "unset") {
|
||||
return unsetValue;
|
||||
}
|
||||
|
||||
value = newValue;
|
||||
|
||||
try {
|
||||
value = _evaluateCssCalcExpression(value);
|
||||
} catch (e) {
|
||||
traceWrite(`Failed to evaluate css-calc for property [${property}] for expression [${value}] to ${view}. ${e.stack}`, traceCategories.Error, traceMessageType.error);
|
||||
|
||||
return unsetValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function mergeCssSelectors(): void {
|
||||
applicationCssSelectors = applicationSelectors.slice();
|
||||
applicationCssSelectors.push.apply(applicationCssSelectors, applicationAdditionalSelectors);
|
||||
@@ -522,22 +545,60 @@ export class CssState {
|
||||
matchingSelectors.forEach(selector =>
|
||||
selector.ruleset.declarations.forEach(declaration =>
|
||||
newPropertyValues[declaration.property] = declaration.value));
|
||||
Object.freeze(newPropertyValues);
|
||||
|
||||
const oldProperties = this._appliedPropertyValues;
|
||||
for (const key in oldProperties) {
|
||||
if (!(key in newPropertyValues)) {
|
||||
if (key in view.style) {
|
||||
view.style[`css:${key}`] = unsetValue;
|
||||
|
||||
let isCssExpressionInUse = false;
|
||||
|
||||
// Update values for the scope's css-variables
|
||||
view.style.resetScopedCssVariables();
|
||||
|
||||
for (const property in newPropertyValues) {
|
||||
const value = newPropertyValues[property];
|
||||
if (isCssVariable(property)) {
|
||||
view.style.setScopedCssVariable(property, value);
|
||||
|
||||
delete newPropertyValues[property];
|
||||
continue;
|
||||
}
|
||||
|
||||
isCssExpressionInUse = isCssExpressionInUse || isCssVariableExpression(value) || isCssCalcExpression(value);
|
||||
}
|
||||
|
||||
if (isCssExpressionInUse) {
|
||||
// Evalute css-expressions to get the latest values.
|
||||
for (const property in newPropertyValues) {
|
||||
const value = evaluateCssExpressions(view, property, newPropertyValues[property]);
|
||||
if (value === unsetValue) {
|
||||
delete newPropertyValues[property];
|
||||
continue;
|
||||
}
|
||||
|
||||
newPropertyValues[property] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Property values are fully updated, freeze the object to be used for next update.
|
||||
Object.freeze(newPropertyValues);
|
||||
|
||||
// Unset removed values
|
||||
for (const property in oldProperties) {
|
||||
if (!(property in newPropertyValues)) {
|
||||
if (property in view.style) {
|
||||
view.style[`css:${property}`] = unsetValue;
|
||||
} else {
|
||||
// TRICKY: How do we unset local value?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set new values to the style
|
||||
for (const property in newPropertyValues) {
|
||||
if (oldProperties && property in oldProperties && oldProperties[property] === newPropertyValues[property]) {
|
||||
// Skip unchanged values
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = newPropertyValues[property];
|
||||
try {
|
||||
if (property in view.style) {
|
||||
@@ -547,7 +608,7 @@ export class CssState {
|
||||
view[camelCasedProperty] = value;
|
||||
}
|
||||
} catch (e) {
|
||||
traceWrite(`Failed to apply property [${property}] with value [${value}] to ${view}. ${e}`, traceCategories.Error, traceMessageType.error);
|
||||
traceWrite(`Failed to apply property [${property}] with value [${value}] to ${view}. ${e.stack}`, traceCategories.Error, traceMessageType.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,21 +880,41 @@ function resolveFilePathFromImport(importSource: string, fileName: string): stri
|
||||
export const applyInlineStyle = profile(function applyInlineStyle(view: ViewBase, styleStr: string) {
|
||||
let localStyle = `local { ${styleStr} }`;
|
||||
let inlineRuleSet = CSSSource.fromSource(localStyle, new Map()).selectors;
|
||||
const style = view.style;
|
||||
|
||||
// Reset unscoped css-variables
|
||||
view.style.resetUnscopedCssVariables();
|
||||
|
||||
// Set all the css-variables first, so we can be sure they are up-to-date
|
||||
inlineRuleSet[0].declarations.forEach(d => {
|
||||
// Use the actual property name so that a local value is set.
|
||||
let property = d.property;
|
||||
if (isCssVariable(property)) {
|
||||
view.style.setUnscopedCssVariable(property, d.value);
|
||||
}
|
||||
});
|
||||
|
||||
inlineRuleSet[0].declarations.forEach(d => {
|
||||
// Use the actual property name so that a local value is set.
|
||||
let name = d.property;
|
||||
let property = d.property;
|
||||
try {
|
||||
if (name in style) {
|
||||
style[name] = d.value;
|
||||
if (isCssVariable(property)) {
|
||||
// Skip css-variables, they have been handled
|
||||
return;
|
||||
}
|
||||
|
||||
const value = evaluateCssExpressions(view, property, d.value);
|
||||
if (property in view.style) {
|
||||
view.style[property] = value;
|
||||
} else {
|
||||
view[name] = d.value;
|
||||
view[property] = value;
|
||||
}
|
||||
} catch (e) {
|
||||
traceWrite(`Failed to apply property [${d.property}] with value [${d.value}] to ${view}. ${e}`, traceCategories.Error, traceMessageType.error);
|
||||
}
|
||||
});
|
||||
|
||||
// This is needed in case of changes to css-variable or css-calc expressions.
|
||||
view._onCssStateChange();
|
||||
});
|
||||
|
||||
function isCurrentDirectory(uriPart: string): boolean {
|
||||
|
||||
26
tns-core-modules/ui/styling/style/style.d.ts
vendored
26
tns-core-modules/ui/styling/style/style.d.ts
vendored
@@ -168,6 +168,32 @@ export class Style extends Observable {
|
||||
* The shorthand properties are defined as non-enumerable so it should be safe to for-in the keys that are set in the bag.
|
||||
*/
|
||||
public readonly PropertyBag: PropertyBagClass;
|
||||
|
||||
/**
|
||||
* Set a scoped css-value. These are css-variables set from CssState
|
||||
*/
|
||||
public setScopedCssVariable(varName: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Set a unscoped css-value. These are css-variables set on view.style
|
||||
*/
|
||||
public setUnscopedCssVariable(varName: string, value: string): void;
|
||||
|
||||
/**
|
||||
* Get value of the css-variable.
|
||||
* If the value is not set on this style-object, try the parent view.
|
||||
*/
|
||||
public getCssVariable(varName: string): string | null;
|
||||
|
||||
/**
|
||||
* Remove all scoped css-variables
|
||||
*/
|
||||
public resetScopedCssVariables(): void;
|
||||
|
||||
/**
|
||||
* Remove all unscoped css-variables
|
||||
*/
|
||||
public resetUnscopedCssVariables(): void;
|
||||
}
|
||||
|
||||
interface PropertyBagClass {
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from "../../text-base";
|
||||
|
||||
export class Style extends Observable implements StyleDefinition {
|
||||
private unscopedCssVariables = new Map<string, string>();
|
||||
private scopedCssVariables = new Map<string, string>();
|
||||
|
||||
constructor(ownerView: ViewBase | WeakRef<ViewBase>) {
|
||||
super();
|
||||
|
||||
@@ -29,6 +32,43 @@ export class Style extends Observable implements StyleDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
public setScopedCssVariable(varName: string, value: string): void {
|
||||
this.scopedCssVariables.set(varName, value);
|
||||
}
|
||||
|
||||
public setUnscopedCssVariable(varName: string, value: string): void {
|
||||
this.unscopedCssVariables.set(varName, value);
|
||||
}
|
||||
|
||||
public getCssVariable(varName: string): string | null {
|
||||
const view = this.view;
|
||||
if (!view) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.unscopedCssVariables.has(varName)) {
|
||||
return this.unscopedCssVariables.get(varName);
|
||||
}
|
||||
|
||||
if (this.scopedCssVariables.has(varName)) {
|
||||
return this.scopedCssVariables.get(varName);
|
||||
}
|
||||
|
||||
if (!view.parent || !view.parent.style) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return view.parent.style.getCssVariable(varName);
|
||||
}
|
||||
|
||||
public resetScopedCssVariables() {
|
||||
this.scopedCssVariables.clear();
|
||||
}
|
||||
|
||||
public resetUnscopedCssVariables() {
|
||||
this.unscopedCssVariables.clear();
|
||||
}
|
||||
|
||||
toString() {
|
||||
const view = this.viewRef.get();
|
||||
if (!view) {
|
||||
|
||||
Reference in New Issue
Block a user