mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 12:57:42 +08:00
Merge pull request #1773 from NativeScript/css-property
Plain component properties now can be applied from CSS
This commit is contained in:
@ -30,6 +30,28 @@ export function test_css_dataURI_is_applied_to_backgroundImageSource() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function test_css_is_applied_to_normal_properties() {
|
||||||
|
var stack = new stackModule.StackLayout();
|
||||||
|
|
||||||
|
helper.buildUIAndRunTest(stack, function (views: Array<viewModule.View>) {
|
||||||
|
var page = <pageModule.Page>views[1];
|
||||||
|
var expected = "horizontal";
|
||||||
|
page.css = `StackLayout { orientation: ${expected}; }`;
|
||||||
|
TKUnit.assertEqual(stack.orientation, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function test_css_is_applied_to_special_properties() {
|
||||||
|
var stack = new stackModule.StackLayout();
|
||||||
|
|
||||||
|
helper.buildUIAndRunTest(stack, function (views: Array<viewModule.View>) {
|
||||||
|
var page = <pageModule.Page>views[1];
|
||||||
|
var expected = "test";
|
||||||
|
page.css = `StackLayout { class: ${expected}; }`;
|
||||||
|
TKUnit.assertEqual(stack.className, expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Test for inheritance in different containers
|
// Test for inheritance in different containers
|
||||||
export function test_css_is_applied_inside_StackLayout() {
|
export function test_css_is_applied_inside_StackLayout() {
|
||||||
var testButton = new buttonModule.Button();
|
var testButton = new buttonModule.Button();
|
||||||
|
@ -6,6 +6,7 @@ import {File, Folder, path, knownFolders} from "file-system";
|
|||||||
import {getBindingOptions, bindingConstants} from "./binding-builder";
|
import {getBindingOptions, bindingConstants} from "./binding-builder";
|
||||||
import * as debugModule from "utils/debug";
|
import * as debugModule from "utils/debug";
|
||||||
import * as platformModule from "platform";
|
import * as platformModule from "platform";
|
||||||
|
import {convertString} from "utils/utils";
|
||||||
|
|
||||||
//the imports below are needed for special property registration
|
//the imports below are needed for special property registration
|
||||||
import "ui/layouts/dock-layout";
|
import "ui/layouts/dock-layout";
|
||||||
@ -182,20 +183,8 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports
|
|||||||
if (!attrHandled && (<any>instance)._applyXmlAttribute) {
|
if (!attrHandled && (<any>instance)._applyXmlAttribute) {
|
||||||
attrHandled = (<any>instance)._applyXmlAttribute(propertyName, propertyValue);
|
attrHandled = (<any>instance)._applyXmlAttribute(propertyName, propertyValue);
|
||||||
}
|
}
|
||||||
if (!attrHandled) {
|
if (!attrHandled) {
|
||||||
if (propertyValue.trim() === "") {
|
instance[propertyName] = convertString(propertyValue);
|
||||||
instance[propertyName] = propertyValue;
|
|
||||||
} else {
|
|
||||||
// Try to convert value to number.
|
|
||||||
var valueAsNumber = +propertyValue;
|
|
||||||
if (!isNaN(valueAsNumber)) {
|
|
||||||
instance[propertyName] = valueAsNumber;
|
|
||||||
} else if (propertyValue && (propertyValue.toLowerCase() === "true" || propertyValue.toLowerCase() === "false")) {
|
|
||||||
instance[propertyName] = propertyValue.toLowerCase() === "true" ? true : false;
|
|
||||||
} else {
|
|
||||||
instance[propertyName] = propertyValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import * as trace from "trace";
|
|||||||
import * as styleProperty from "ui/styling/style-property";
|
import * as styleProperty from "ui/styling/style-property";
|
||||||
import * as types from "utils/types";
|
import * as types from "utils/types";
|
||||||
import * as utils from "utils/utils";
|
import * as utils from "utils/utils";
|
||||||
|
import {getSpecialPropertySetter} from "ui/builder/special-properties";
|
||||||
|
|
||||||
var ID_SPECIFICITY = 1000000;
|
var ID_SPECIFICITY = 1000000;
|
||||||
var ATTR_SPECIFITY = 10000;
|
var ATTR_SPECIFITY = 10000;
|
||||||
@ -38,7 +39,7 @@ export class CssSelector {
|
|||||||
get expression(): string {
|
get expression(): string {
|
||||||
return this._expression;
|
return this._expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
get attrExpression(): string {
|
get attrExpression(): string {
|
||||||
return this._attrExpression;
|
return this._attrExpression;
|
||||||
}
|
}
|
||||||
@ -57,11 +58,25 @@ export class CssSelector {
|
|||||||
|
|
||||||
public apply(view: view.View) {
|
public apply(view: view.View) {
|
||||||
this.eachSetter((property, value) => {
|
this.eachSetter((property, value) => {
|
||||||
try {
|
if(types.isString(property)) {
|
||||||
view.style._setValue(property, value, observable.ValueSource.Css);
|
let attrHandled = false;
|
||||||
}
|
let specialSetter = getSpecialPropertySetter(property);
|
||||||
catch (ex) {
|
|
||||||
trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error);
|
if (!attrHandled && specialSetter) {
|
||||||
|
specialSetter(view, value);
|
||||||
|
attrHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attrHandled && property in view) {
|
||||||
|
view[property] = utils.convertString(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
view.style._setValue(property, value, observable.ValueSource.Css);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -74,7 +89,7 @@ export class CssSelector {
|
|||||||
|
|
||||||
let property = styleProperty.getPropertyByCssName(name);
|
let property = styleProperty.getPropertyByCssName(name);
|
||||||
|
|
||||||
if (property) {
|
if (property) {
|
||||||
// The property.valueConverter is now used to convert the value later on in DependencyObservable._setValueInternal.
|
// The property.valueConverter is now used to convert the value later on in DependencyObservable._setValueInternal.
|
||||||
callback(property, resolvedValue);
|
callback(property, resolvedValue);
|
||||||
}
|
}
|
||||||
@ -85,6 +100,8 @@ export class CssSelector {
|
|||||||
let pair = pairs[j];
|
let pair = pairs[j];
|
||||||
callback(pair.property, pair.value);
|
callback(pair.property, pair.value);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
callback(declaration.property, declaration.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,10 +125,10 @@ function matchesType(expression: string, view: view.View): boolean {
|
|||||||
let exprArr = expression.split(".");
|
let exprArr = expression.split(".");
|
||||||
let exprTypeName = exprArr[0];
|
let exprTypeName = exprArr[0];
|
||||||
let exprClassName = exprArr[1];
|
let exprClassName = exprArr[1];
|
||||||
|
|
||||||
let typeCheck = exprTypeName.toLowerCase() === view.typeName.toLowerCase() ||
|
let typeCheck = exprTypeName.toLowerCase() === view.typeName.toLowerCase() ||
|
||||||
exprTypeName.toLowerCase() === view.typeName.split(/(?=[A-Z])/).join("-").toLowerCase();
|
exprTypeName.toLowerCase() === view.typeName.split(/(?=[A-Z])/).join("-").toLowerCase();
|
||||||
|
|
||||||
if (typeCheck) {
|
if (typeCheck) {
|
||||||
if (exprClassName) {
|
if (exprClassName) {
|
||||||
return view._cssClasses.some((cssClass, i, arr) => { return cssClass === exprClassName });
|
return view._cssClasses.some((cssClass, i, arr) => { return cssClass === exprClassName });
|
||||||
@ -155,14 +172,14 @@ class CssClassSelector extends CssSelector {
|
|||||||
class CssCompositeSelector extends CssSelector {
|
class CssCompositeSelector extends CssSelector {
|
||||||
get specificity(): number {
|
get specificity(): number {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
for(let i = 0; i < this.parentCssSelectors.length; i++) {
|
for (let i = 0; i < this.parentCssSelectors.length; i++) {
|
||||||
result += this.parentCssSelectors[i].selector.specificity;
|
result += this.parentCssSelectors[i].selector.specificity;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private parentCssSelectors: [{ selector: CssSelector, onlyDirectParent: boolean}];
|
private parentCssSelectors: [{ selector: CssSelector, onlyDirectParent: boolean }];
|
||||||
|
|
||||||
private splitExpression(expression) {
|
private splitExpression(expression) {
|
||||||
let result = [];
|
let result = [];
|
||||||
let tempArr = [];
|
let tempArr = [];
|
||||||
@ -191,29 +208,29 @@ class CssCompositeSelector extends CssSelector {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(expr: string, declarations: cssParser.Declaration[]) {
|
constructor(expr: string, declarations: cssParser.Declaration[]) {
|
||||||
super(expr, declarations);
|
super(expr, declarations);
|
||||||
let expressions = this.splitExpression(expr);
|
let expressions = this.splitExpression(expr);
|
||||||
let onlyParent = false;
|
let onlyParent = false;
|
||||||
this.parentCssSelectors = <any>[];
|
this.parentCssSelectors = <any>[];
|
||||||
for(let i = expressions.length - 1; i >= 0; i--) {
|
for (let i = expressions.length - 1; i >= 0; i--) {
|
||||||
if (expressions[i].trim() === GTHAN) {
|
if (expressions[i].trim() === GTHAN) {
|
||||||
onlyParent = true;
|
onlyParent = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.parentCssSelectors.push({selector: createSelector(expressions[i].trim(), null), onlyDirectParent: onlyParent});
|
this.parentCssSelectors.push({ selector: createSelector(expressions[i].trim(), null), onlyDirectParent: onlyParent });
|
||||||
onlyParent = false;
|
onlyParent = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public matches(view: view.View): boolean {
|
public matches(view: view.View): boolean {
|
||||||
let result = this.parentCssSelectors[0].selector.matches(view);
|
let result = this.parentCssSelectors[0].selector.matches(view);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
let tempView = view.parent;
|
let tempView = view.parent;
|
||||||
for(let i = 1; i < this.parentCssSelectors.length; i++) {
|
for (let i = 1; i < this.parentCssSelectors.length; i++) {
|
||||||
let parentCounter = 0;
|
let parentCounter = 0;
|
||||||
while (tempView && parentCounter === 0) {
|
while (tempView && parentCounter === 0) {
|
||||||
result = this.parentCssSelectors[i].selector.matches(tempView);
|
result = this.parentCssSelectors[i].selector.matches(tempView);
|
||||||
@ -238,7 +255,7 @@ class CssAttrSelector extends CssSelector {
|
|||||||
get specificity(): number {
|
get specificity(): number {
|
||||||
return ATTR_SPECIFITY;
|
return ATTR_SPECIFITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public matches(view: view.View): boolean {
|
public matches(view: view.View): boolean {
|
||||||
return matchesAttr(this.attrExpression, view);
|
return matchesAttr(this.attrExpression, view);
|
||||||
}
|
}
|
||||||
@ -256,7 +273,7 @@ function matchesAttr(attrExpression: string, view: view.View): boolean {
|
|||||||
attrValue = nameValueRegexRes[2].trim().replace(/^(["'])*(.*)\1$/, '$2');
|
attrValue = nameValueRegexRes[2].trim().replace(/^(["'])*(.*)\1$/, '$2');
|
||||||
}
|
}
|
||||||
// extract entire sign (=, ~=, |=, ^=, $=, *=)
|
// extract entire sign (=, ~=, |=, ^=, $=, *=)
|
||||||
let escapedAttrValue = utils.escapeRegexSymbols(attrValue);
|
let escapedAttrValue = utils.escapeRegexSymbols(attrValue);
|
||||||
let attrCheckRegex;
|
let attrCheckRegex;
|
||||||
switch (attrExpression.charAt(equalSignIndex - 1)) {
|
switch (attrExpression.charAt(equalSignIndex - 1)) {
|
||||||
case "~":
|
case "~":
|
||||||
@ -277,10 +294,10 @@ function matchesAttr(attrExpression: string, view: view.View): boolean {
|
|||||||
|
|
||||||
// only = (EQUAL)
|
// only = (EQUAL)
|
||||||
default:
|
default:
|
||||||
attrCheckRegex = new RegExp("^"+escapedAttrValue+"$");
|
attrCheckRegex = new RegExp("^" + escapedAttrValue + "$");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return !types.isNullOrUndefined(view[attrName]) && attrCheckRegex.test(view[attrName]+"");
|
return !types.isNullOrUndefined(view[attrName]) && attrCheckRegex.test(view[attrName] + "");
|
||||||
} else {
|
} else {
|
||||||
return !types.isNullOrUndefined(view[attrExpression]);
|
return !types.isNullOrUndefined(view[attrExpression]);
|
||||||
}
|
}
|
||||||
@ -297,7 +314,7 @@ export class CssVisualStateSelector extends CssSelector {
|
|||||||
|
|
||||||
get specificity(): number {
|
get specificity(): number {
|
||||||
return (this._isById ? ID_SPECIFICITY : 0) +
|
return (this._isById ? ID_SPECIFICITY : 0) +
|
||||||
(this._isByAttr ? ATTR_SPECIFITY : 0) +
|
(this._isByAttr ? ATTR_SPECIFITY : 0) +
|
||||||
(this._isByClass ? CLASS_SPECIFICITY : 0) +
|
(this._isByClass ? CLASS_SPECIFICITY : 0) +
|
||||||
(this._isByType ? TYPE_SPECIFICITY : 0);
|
(this._isByType ? TYPE_SPECIFICITY : 0);
|
||||||
}
|
}
|
||||||
@ -347,7 +364,7 @@ export class CssVisualStateSelector extends CssSelector {
|
|||||||
if (this._isByType) {
|
if (this._isByType) {
|
||||||
matches = matchesType(this._match, view);
|
matches = matchesType(this._match, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isByAttr) {
|
if (this._isByAttr) {
|
||||||
matches = matchesAttr(this._key, view);
|
matches = matchesAttr(this._key, view);
|
||||||
}
|
}
|
||||||
@ -371,12 +388,12 @@ export function createSelector(expression: string, declarations: cssParser.Decla
|
|||||||
if (spaceIndex >= 0) {
|
if (spaceIndex >= 0) {
|
||||||
return new CssCompositeSelector(goodExpr, declarations);
|
return new CssCompositeSelector(goodExpr, declarations);
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftSquareBracketIndex = goodExpr.indexOf(LSBRACKET);
|
let leftSquareBracketIndex = goodExpr.indexOf(LSBRACKET);
|
||||||
if (leftSquareBracketIndex === 0) {
|
if (leftSquareBracketIndex === 0) {
|
||||||
return new CssAttrSelector(goodExpr, declarations);
|
return new CssAttrSelector(goodExpr, declarations);
|
||||||
}
|
}
|
||||||
|
|
||||||
var colonIndex = goodExpr.indexOf(COLON);
|
var colonIndex = goodExpr.indexOf(COLON);
|
||||||
if (colonIndex >= 0) {
|
if (colonIndex >= 0) {
|
||||||
return new CssVisualStateSelector(goodExpr, declarations);
|
return new CssVisualStateSelector(goodExpr, declarations);
|
||||||
|
@ -34,6 +34,26 @@ export function escapeRegexSymbols(source: string): string {
|
|||||||
return source.replace(escapeRegex, "\\$&");
|
return source.replace(escapeRegex, "\\$&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertString(value: string): any {
|
||||||
|
var result;
|
||||||
|
|
||||||
|
if (value.trim() === "") {
|
||||||
|
result = value;
|
||||||
|
} else {
|
||||||
|
// Try to convert value to number.
|
||||||
|
var valueAsNumber = +value;
|
||||||
|
if (!isNaN(valueAsNumber)) {
|
||||||
|
result = valueAsNumber;
|
||||||
|
} else if (value && (value.toLowerCase() === "true" || value.toLowerCase() === "false")) {
|
||||||
|
result = value.toLowerCase() === "true" ? true : false;
|
||||||
|
} else {
|
||||||
|
result = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export module layout {
|
export module layout {
|
||||||
|
|
||||||
var MODE_SHIFT = 30;
|
var MODE_SHIFT = 30;
|
||||||
|
6
utils/utils.d.ts
vendored
6
utils/utils.d.ts
vendored
@ -235,4 +235,10 @@
|
|||||||
* @param source The original value.
|
* @param source The original value.
|
||||||
*/
|
*/
|
||||||
export function escapeRegexSymbols(source: string): string
|
export function escapeRegexSymbols(source: string): string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string value to number or boolean.
|
||||||
|
* @param value The original value.
|
||||||
|
*/
|
||||||
|
export function convertString(value: string): any
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user