mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Add parsers for the background css shorthand property, make ViewBase unit testable in node environment
Add background parser and linear-gradient parser
Use sticky regexes
Simplify some types, introduce generic Parsed<T> instead of & TokenRange
Apply each parser to return a { start, end, value } object
Move the css selector parser to the css/parser and unify types
Add the first steps toward building homegrown css parser
Add somewhat standards compliant tokenizer, add baseline, rework and shady css parsers
Enable all tests again, skip flaky perf test
Improve css parser tokenizer by converting some char token types to simple string
Implement 'parse a stylesheet'
Add gonzales css-parser
Add parseLib and css-tree perf
Add a thin parser layer that will convert CSS3 tokens to values, for now output is compatible with rework
Make root tsc green
Return the requires of tns-core-modules to use relative paths for webpack to work
Implement support for '@import 'url-string';
Fix function parser, function-token is no-longer neglected
Make the style-scope be able to load from "css" and "css-ast" modules
Add a loadAppCss event so theme can be added to snapshot separately from loaded
1042 lines
43 KiB
TypeScript
1042 lines
43 KiB
TypeScript
// Types
|
|
import {
|
|
Transformation,
|
|
TransformationValue,
|
|
TransformFunctionsInfo,
|
|
} from "../animation/animation";
|
|
|
|
import { dip, px, percent } from "../core/view";
|
|
|
|
import { Color } from "../../color";
|
|
import { Font, parseFont, FontStyle, FontWeight } from "../../ui/styling/font";
|
|
import { layout } from "../../utils/utils";
|
|
import { Background } from "../../ui/styling/background";
|
|
import { isIOS } from "../../platform";
|
|
|
|
import { Style } from "./style";
|
|
|
|
import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty, makeValidator, makeParser } from "../core/properties";
|
|
|
|
import { hasDuplicates } from "../../utils/utils";
|
|
import { radiansToDegrees } from "../../utils/number-utils";
|
|
|
|
import {
|
|
decompose2DTransformMatrix,
|
|
getTransformMatrix,
|
|
matrixArrayToCssMatrix,
|
|
multiplyAffine2d,
|
|
} from "../../matrix";
|
|
|
|
export type LengthDipUnit = { readonly unit: "dip", readonly value: dip };
|
|
export type LengthPxUnit = { readonly unit: "px", readonly value: px };
|
|
export type LengthPercentUnit = { readonly unit: "%", readonly value: percent };
|
|
|
|
export type Length = "auto" | dip | LengthDipUnit | LengthPxUnit;
|
|
export type PercentLength = "auto" | dip | LengthDipUnit | LengthPxUnit | LengthPercentUnit;
|
|
|
|
function equalsCommon(a: Length, b: Length): boolean;
|
|
function equalsCommon(a: PercentLength, b: PercentLength): boolean;
|
|
function equalsCommon(a: PercentLength, b: PercentLength): boolean {
|
|
if (a == "auto") { // tslint:disable-line
|
|
return b == "auto"; // tslint:disable-line
|
|
}
|
|
if (typeof a === "number") {
|
|
if (b == "auto") { // tslint:disable-line
|
|
return false;
|
|
}
|
|
if (typeof b === "number") {
|
|
return a == b; // tslint:disable-line
|
|
}
|
|
return b.unit == "dip" && a == b.value; // tslint:disable-line
|
|
}
|
|
if (b == "auto") { // tslint:disable-line
|
|
return false;
|
|
}
|
|
if (typeof b === "number") {
|
|
return a.unit == "dip" && a.value == b; // tslint:disable-line
|
|
}
|
|
return a.value == b.value && a.unit == b.unit; // tslint:disable-line
|
|
}
|
|
|
|
function convertToStringCommon(length: Length | PercentLength): string {
|
|
if (length == "auto") { // tslint:disable-line
|
|
return "auto";
|
|
}
|
|
|
|
if (typeof length === "number") {
|
|
return length.toString();
|
|
}
|
|
|
|
let val = length.value;
|
|
if (length.unit === "%") {
|
|
val *= 100;
|
|
}
|
|
|
|
return val + length.unit;
|
|
}
|
|
|
|
function toDevicePixelsCommon(length: PercentLength, auto: number = Number.NaN, parentAvailableWidth: number = Number.NaN): number {
|
|
if (length == "auto") { // tslint:disable-line
|
|
return auto;
|
|
}
|
|
if (typeof length === "number") {
|
|
return layout.round(layout.toDevicePixels(length));
|
|
}
|
|
switch (length.unit) {
|
|
case "px":
|
|
return layout.round(length.value);
|
|
case "%":
|
|
return layout.round(parentAvailableWidth * length.value);
|
|
case "dip":
|
|
default:
|
|
return layout.round(layout.toDevicePixels(length.value));
|
|
}
|
|
}
|
|
|
|
export namespace PercentLength {
|
|
export function parse(fromValue: string | Length): PercentLength {
|
|
if (fromValue == "auto") { // tslint:disable-line
|
|
return "auto";
|
|
}
|
|
if (typeof fromValue === "string") {
|
|
let stringValue = fromValue.trim();
|
|
let percentIndex = stringValue.indexOf("%");
|
|
if (percentIndex !== -1) {
|
|
let value: percent;
|
|
// if only % or % is not last we treat it as invalid value.
|
|
if (percentIndex !== (stringValue.length - 1) || percentIndex === 0) {
|
|
value = Number.NaN;
|
|
} else {
|
|
value = parseFloat(stringValue.substring(0, stringValue.length - 1).trim()) / 100;
|
|
}
|
|
|
|
if (isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Invalid value: ${fromValue}`);
|
|
}
|
|
return { unit: "%", value }
|
|
} else if (stringValue.indexOf("px") !== -1) {
|
|
stringValue = stringValue.replace("px", "").trim();
|
|
let value: px = parseFloat(stringValue);
|
|
if (isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Invalid value: ${fromValue}`);
|
|
}
|
|
return { unit: "px", value };
|
|
} else {
|
|
let value: dip = parseFloat(stringValue);
|
|
if (isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Invalid value: ${fromValue}`);
|
|
}
|
|
return value;
|
|
}
|
|
} else {
|
|
return fromValue;
|
|
}
|
|
}
|
|
|
|
export const equals: { (a: PercentLength, b: PercentLength): boolean } = equalsCommon;
|
|
export const toDevicePixels: { (length: PercentLength, auto: number, parentAvailableWidth: number): number } = toDevicePixelsCommon;
|
|
export const convertToString: { (length: PercentLength): string } = convertToStringCommon;
|
|
}
|
|
|
|
export namespace Length {
|
|
export function parse(fromValue: string | Length): Length {
|
|
if (fromValue == "auto") { // tslint:disable-line
|
|
return "auto";
|
|
}
|
|
if (typeof fromValue === "string") {
|
|
let stringValue = fromValue.trim();
|
|
if (stringValue.indexOf("px") !== -1) {
|
|
stringValue = stringValue.replace("px", "").trim();
|
|
let value: px = parseFloat(stringValue);
|
|
if (isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Invalid value: ${stringValue}`);
|
|
}
|
|
return { unit: "px", value };
|
|
} else {
|
|
let value: dip = parseFloat(stringValue);
|
|
if (isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Invalid value: ${stringValue}`);
|
|
}
|
|
return value;
|
|
}
|
|
} else {
|
|
return fromValue;
|
|
}
|
|
}
|
|
export const equals: { (a: Length, b: Length): boolean } = equalsCommon;
|
|
export const toDevicePixels: { (length: Length, auto?: number): number } = toDevicePixelsCommon;
|
|
export const convertToString: { (length: Length): string } = convertToStringCommon;
|
|
}
|
|
|
|
export const zeroLength: Length = { value: 0, unit: "px" };
|
|
|
|
export const minWidthProperty = new CssProperty<Style, Length>({
|
|
name: "minWidth", cssName: "min-width", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectiveMinWidth = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
minWidthProperty.register(Style);
|
|
|
|
export const minHeightProperty = new CssProperty<Style, Length>({
|
|
name: "minHeight", cssName: "min-height", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectiveMinHeight = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
minHeightProperty.register(Style);
|
|
|
|
export const widthProperty = new CssProperty<Style, PercentLength>({ name: "width", cssName: "width", defaultValue: "auto", affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
widthProperty.register(Style);
|
|
|
|
export const heightProperty = new CssProperty<Style, PercentLength>({ name: "height", cssName: "height", defaultValue: "auto", affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
heightProperty.register(Style);
|
|
|
|
const marginProperty = new ShorthandProperty<Style, string | PercentLength>({
|
|
name: "margin", cssName: "margin",
|
|
getter: function (this: Style) {
|
|
if (PercentLength.equals(this.marginTop, this.marginRight) &&
|
|
PercentLength.equals(this.marginTop, this.marginBottom) &&
|
|
PercentLength.equals(this.marginTop, this.marginLeft)) {
|
|
return this.marginTop;
|
|
}
|
|
return `${PercentLength.convertToString(this.marginTop)} ${PercentLength.convertToString(this.marginRight)} ${PercentLength.convertToString(this.marginBottom)} ${PercentLength.convertToString(this.marginLeft)}`;
|
|
},
|
|
converter: convertToMargins
|
|
});
|
|
marginProperty.register(Style);
|
|
|
|
export const marginLeftProperty = new CssProperty<Style, PercentLength>({ name: "marginLeft", cssName: "margin-left", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
marginLeftProperty.register(Style);
|
|
|
|
export const marginRightProperty = new CssProperty<Style, PercentLength>({ name: "marginRight", cssName: "margin-right", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
marginRightProperty.register(Style);
|
|
|
|
export const marginTopProperty = new CssProperty<Style, PercentLength>({ name: "marginTop", cssName: "margin-top", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
marginTopProperty.register(Style);
|
|
|
|
export const marginBottomProperty = new CssProperty<Style, PercentLength>({ name: "marginBottom", cssName: "margin-bottom", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
|
|
marginBottomProperty.register(Style);
|
|
|
|
const paddingProperty = new ShorthandProperty<Style, string | Length>({
|
|
name: "padding", cssName: "padding",
|
|
getter: function (this: Style) {
|
|
if (Length.equals(this.paddingTop, this.paddingRight) &&
|
|
Length.equals(this.paddingTop, this.paddingBottom) &&
|
|
Length.equals(this.paddingTop, this.paddingLeft)) {
|
|
return this.paddingTop;
|
|
}
|
|
return `${Length.convertToString(this.paddingTop)} ${Length.convertToString(this.paddingRight)} ${Length.convertToString(this.paddingBottom)} ${Length.convertToString(this.paddingLeft)}`;
|
|
},
|
|
converter: convertToPaddings
|
|
});
|
|
paddingProperty.register(Style);
|
|
|
|
export const paddingLeftProperty = new CssProperty<Style, Length>({
|
|
name: "paddingLeft", cssName: "padding-left", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectivePaddingLeft = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
paddingLeftProperty.register(Style);
|
|
|
|
export const paddingRightProperty = new CssProperty<Style, Length>({
|
|
name: "paddingRight", cssName: "padding-right", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectivePaddingRight = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
paddingRightProperty.register(Style);
|
|
|
|
export const paddingTopProperty = new CssProperty<Style, Length>({
|
|
name: "paddingTop", cssName: "padding-top", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectivePaddingTop = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
paddingTopProperty.register(Style);
|
|
|
|
export const paddingBottomProperty = new CssProperty<Style, Length>({
|
|
name: "paddingBottom", cssName: "padding-bottom", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
target.view.effectivePaddingBottom = Length.toDevicePixels(newValue, 0);
|
|
}, valueConverter: Length.parse
|
|
});
|
|
paddingBottomProperty.register(Style);
|
|
|
|
export type HorizontalAlignment = "left" | "center" | "right" | "stretch";
|
|
export namespace HorizontalAlignment {
|
|
export const LEFT: "left" = "left";
|
|
export const CENTER: "center" = "center";
|
|
export const RIGHT: "right" = "right";
|
|
export const STRETCH: "stretch" = "stretch";
|
|
export const isValid = makeValidator<HorizontalAlignment>(LEFT, CENTER, RIGHT, STRETCH);
|
|
export const parse = makeParser<HorizontalAlignment>(isValid);
|
|
}
|
|
|
|
export const horizontalAlignmentProperty = new CssProperty<Style, HorizontalAlignment>({ name: "horizontalAlignment", cssName: "horizontal-align", defaultValue: HorizontalAlignment.STRETCH, affectsLayout: isIOS, valueConverter: HorizontalAlignment.parse });
|
|
horizontalAlignmentProperty.register(Style);
|
|
|
|
export type VerticalAlignment = "top" | "middle" | "bottom" | "stretch";
|
|
export namespace VerticalAlignment {
|
|
export const TOP: "top" = "top";
|
|
export const MIDDLE: "middle" = "middle";
|
|
export const BOTTOM: "bottom" = "bottom";
|
|
export const STRETCH: "stretch" = "stretch";
|
|
export const isValid = makeValidator<VerticalAlignment>(TOP, MIDDLE, BOTTOM, STRETCH);
|
|
export const parse = (value: string) => value.toLowerCase() === "center" ? MIDDLE : parseStrict(value);
|
|
const parseStrict = makeParser<VerticalAlignment>(isValid);
|
|
}
|
|
|
|
export const verticalAlignmentProperty = new CssProperty<Style, VerticalAlignment>({ name: "verticalAlignment", cssName: "vertical-align", defaultValue: VerticalAlignment.STRETCH, affectsLayout: isIOS, valueConverter: VerticalAlignment.parse });
|
|
verticalAlignmentProperty.register(Style);
|
|
|
|
interface Thickness {
|
|
top: string;
|
|
right: string;
|
|
bottom: string;
|
|
left: string;
|
|
}
|
|
|
|
function parseThickness(value: string): Thickness {
|
|
if (typeof value === "string") {
|
|
let arr = value.split(/[ ,]+/);
|
|
|
|
let top: string;
|
|
let right: string;
|
|
let bottom: string;
|
|
let left: string;
|
|
|
|
if (arr.length === 1) {
|
|
top = arr[0];
|
|
right = arr[0];
|
|
bottom = arr[0];
|
|
left = arr[0];
|
|
}
|
|
else if (arr.length === 2) {
|
|
top = arr[0];
|
|
bottom = arr[0];
|
|
right = arr[1];
|
|
left = arr[1];
|
|
}
|
|
else if (arr.length === 3) {
|
|
top = arr[0];
|
|
right = arr[1];
|
|
left = arr[1];
|
|
bottom = arr[2];
|
|
}
|
|
else if (arr.length === 4) {
|
|
top = arr[0];
|
|
right = arr[1];
|
|
bottom = arr[2];
|
|
left = arr[3];
|
|
}
|
|
else {
|
|
throw new Error("Expected 1, 2, 3 or 4 parameters. Actual: " + value);
|
|
}
|
|
|
|
return {
|
|
top: top,
|
|
right: right,
|
|
bottom: bottom,
|
|
left: left
|
|
};
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function convertToMargins(this: void, value: string | PercentLength): [CssProperty<any, any>, any][] {
|
|
if (typeof value === "string" && value !== "auto") {
|
|
let thickness = parseThickness(value);
|
|
return [
|
|
[marginTopProperty, PercentLength.parse(thickness.top)],
|
|
[marginRightProperty, PercentLength.parse(thickness.right)],
|
|
[marginBottomProperty, PercentLength.parse(thickness.bottom)],
|
|
[marginLeftProperty, PercentLength.parse(thickness.left)]
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
[marginTopProperty, value],
|
|
[marginRightProperty, value],
|
|
[marginBottomProperty, value],
|
|
[marginLeftProperty, value]
|
|
];
|
|
}
|
|
}
|
|
|
|
function convertToPaddings(this: void, value: string | Length): [CssProperty<any, any>, any][] {
|
|
if (typeof value === "string" && value !== "auto") {
|
|
let thickness = parseThickness(value);
|
|
return [
|
|
[paddingTopProperty, Length.parse(thickness.top)],
|
|
[paddingRightProperty, Length.parse(thickness.right)],
|
|
[paddingBottomProperty, Length.parse(thickness.bottom)],
|
|
[paddingLeftProperty, Length.parse(thickness.left)]
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
[paddingTopProperty, value],
|
|
[paddingRightProperty, value],
|
|
[paddingBottomProperty, value],
|
|
[paddingLeftProperty, value]
|
|
];
|
|
}
|
|
}
|
|
|
|
export const rotateProperty = new CssAnimationProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: parseFloat });
|
|
rotateProperty.register(Style);
|
|
|
|
export const scaleXProperty = new CssAnimationProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: parseFloat });
|
|
scaleXProperty.register(Style);
|
|
|
|
export const scaleYProperty = new CssAnimationProperty<Style, number>({ name: "scaleY", cssName: "scaleY", defaultValue: 1, valueConverter: parseFloat });
|
|
scaleYProperty.register(Style);
|
|
|
|
function parseDIPs(value: string): dip {
|
|
if (value.indexOf("px") !== -1) {
|
|
return layout.toDeviceIndependentPixels(parseFloat(value.replace("px", "").trim()));
|
|
} else {
|
|
return parseFloat(value.replace("dip", "").trim());
|
|
}
|
|
}
|
|
|
|
export const translateXProperty = new CssAnimationProperty<Style, dip>({ name: "translateX", cssName: "translateX", defaultValue: 0, valueConverter: parseDIPs });
|
|
translateXProperty.register(Style);
|
|
|
|
export const translateYProperty = new CssAnimationProperty<Style, dip>({ name: "translateY", cssName: "translateY", defaultValue: 0, valueConverter: parseDIPs });
|
|
translateYProperty.register(Style);
|
|
|
|
const transformProperty = new ShorthandProperty<Style, string>({
|
|
name: "transform", cssName: "transform",
|
|
getter: function (this: Style) {
|
|
let scaleX = this.scaleX;
|
|
let scaleY = this.scaleY;
|
|
let translateX = this.translateX;
|
|
let translateY = this.translateY;
|
|
let rotate = this.rotate;
|
|
let result = "";
|
|
if (translateX !== 0 || translateY !== 0) {
|
|
result += `translate(${translateX}, ${translateY}) `;
|
|
}
|
|
if (scaleX !== 1 || scaleY !== 1) {
|
|
result += `scale(${scaleX}, ${scaleY}) `;
|
|
}
|
|
if (rotate !== 0) {
|
|
result += `rotate (${rotate})`;
|
|
}
|
|
|
|
return result.trim();
|
|
},
|
|
converter: convertToTransform
|
|
});
|
|
transformProperty.register(Style);
|
|
|
|
const IDENTITY_TRANSFORMATION = {
|
|
translate: { x: 0, y: 0 },
|
|
rotate: 0,
|
|
scale: { x: 1, y: 1 },
|
|
};
|
|
|
|
const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
|
|
const TRANSFORMATIONS = Object.freeze([
|
|
"rotate",
|
|
"translate",
|
|
"translate3d",
|
|
"translateX",
|
|
"translateY",
|
|
"scale",
|
|
"scale3d",
|
|
"scaleX",
|
|
"scaleY",
|
|
]);
|
|
|
|
const STYLE_TRANSFORMATION_MAP = Object.freeze({
|
|
"scale": value => ({ property: "scale", value }),
|
|
"scale3d": value => ({ property: "scale", value }),
|
|
"scaleX": ({ x }) => ({ property: "scale", value: { x, y: IDENTITY_TRANSFORMATION.scale.y } }),
|
|
"scaleY": ({ y }) => ({ property: "scale", value: { y, x: IDENTITY_TRANSFORMATION.scale.x } }),
|
|
|
|
"translate": value => ({ property: "translate", value }),
|
|
"translate3d": value => ({ property: "translate", value }),
|
|
"translateX": ({ x }) => ({ property: "translate", value: { x, y: IDENTITY_TRANSFORMATION.translate.y } }),
|
|
"translateY": ({ y }) => ({ property: "translate", value: { y, x: IDENTITY_TRANSFORMATION.translate.x } }),
|
|
|
|
"rotate": value => ({ property: "rotate", value }),
|
|
});
|
|
|
|
function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
|
if (value === unsetValue) {
|
|
value = "none";
|
|
}
|
|
|
|
const { translate, rotate, scale } = transformConverter(value);
|
|
return [
|
|
[translateXProperty, translate.x],
|
|
[translateYProperty, translate.y],
|
|
|
|
[scaleXProperty, scale.x],
|
|
[scaleYProperty, scale.y],
|
|
|
|
[rotateProperty, rotate],
|
|
];
|
|
}
|
|
|
|
export function transformConverter(text: string): TransformFunctionsInfo {
|
|
const transformations = parseTransformString(text);
|
|
|
|
if (text === "none" || text === "" || !transformations.length) {
|
|
return IDENTITY_TRANSFORMATION;
|
|
}
|
|
|
|
const usedTransforms = transformations.map(t => t.property);
|
|
if (!hasDuplicates(usedTransforms)) {
|
|
const fullTransformations = Object.assign({}, IDENTITY_TRANSFORMATION);
|
|
transformations.forEach(transform => {
|
|
fullTransformations[transform.property] = transform.value;
|
|
});
|
|
|
|
return fullTransformations;
|
|
}
|
|
|
|
const affineMatrix = transformations
|
|
.map(getTransformMatrix)
|
|
.reduce(multiplyAffine2d)
|
|
const cssMatrix = matrixArrayToCssMatrix(affineMatrix)
|
|
|
|
return decompose2DTransformMatrix(cssMatrix);
|
|
}
|
|
|
|
// using general regex and manually checking the matched
|
|
// properties is faster than using more specific regex
|
|
// https://jsperf.com/cssparse
|
|
function parseTransformString(text: string): Transformation[] {
|
|
let matches: Transformation[] = [];
|
|
let match;
|
|
|
|
while ((match = TRANSFORM_SPLITTER.exec(text)) !== null) {
|
|
const property = match[1];
|
|
const value = convertTransformValue(property, match[2]);
|
|
|
|
if (TRANSFORMATIONS.indexOf(property) !== -1) {
|
|
matches.push(normalizeTransformation({ property, value }));
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
function normalizeTransformation({ property, value }: Transformation): Transformation {
|
|
return <any>STYLE_TRANSFORMATION_MAP[property](value);
|
|
}
|
|
|
|
function convertTransformValue(property: string, stringValue: string)
|
|
: TransformationValue {
|
|
|
|
const [x, y = x] = stringValue.split(",").map(parseFloat);
|
|
|
|
if (property === "rotate") {
|
|
return stringValue.slice(-3) === "rad" ? radiansToDegrees(x) : x;
|
|
}
|
|
|
|
return { x, y };
|
|
}
|
|
|
|
// Background properties.
|
|
export const backgroundInternalProperty = new CssProperty<Style, Background>({
|
|
name: "backgroundInternal",
|
|
cssName: "_backgroundInternal",
|
|
defaultValue: Background.default
|
|
});
|
|
backgroundInternalProperty.register(Style);
|
|
|
|
// const pattern: RegExp = /url\(('|")(.*?)\1\)/;
|
|
export const backgroundImageProperty = new CssProperty<Style, string>({
|
|
name: "backgroundImage", cssName: "background-image", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withImage(newValue);
|
|
target.backgroundInternal = background;
|
|
}
|
|
});
|
|
backgroundImageProperty.register(Style);
|
|
|
|
export const backgroundColorProperty = new CssAnimationProperty<Style, Color>({
|
|
name: "backgroundColor", cssName: "background-color", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withColor(newValue);
|
|
target.backgroundInternal = background;
|
|
}, equalityComparer: Color.equals, valueConverter: (value) => new Color(value)
|
|
});
|
|
backgroundColorProperty.register(Style);
|
|
|
|
export type BackgroundRepeat = "repeat" | "repeat-x" | "repeat-y" | "no-repeat";
|
|
export namespace BackgroundRepeat {
|
|
export const REPEAT: "repeat" = "repeat";
|
|
export const REPEAT_X: "repeat-x" = "repeat-x";
|
|
export const REPEAT_Y: "repeat-y" = "repeat-y";
|
|
export const NO_REPEAT: "no-repeat" = "no-repeat";
|
|
export const isValid = makeValidator<BackgroundRepeat>(REPEAT, REPEAT_X, REPEAT_Y, NO_REPEAT);
|
|
export const parse = makeParser<BackgroundRepeat>(isValid);
|
|
}
|
|
|
|
export const backgroundRepeatProperty = new CssProperty<Style, BackgroundRepeat>({
|
|
name: "backgroundRepeat", cssName: "background-repeat", valueConverter: BackgroundRepeat.parse,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withRepeat(newValue);
|
|
target.backgroundInternal = background;
|
|
}
|
|
});
|
|
backgroundRepeatProperty.register(Style);
|
|
|
|
export const backgroundSizeProperty = new CssProperty<Style, string>({
|
|
name: "backgroundSize", cssName: "background-size", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withSize(newValue);
|
|
target.backgroundInternal = background;
|
|
}
|
|
});
|
|
backgroundSizeProperty.register(Style);
|
|
|
|
export const backgroundPositionProperty = new CssProperty<Style, string>({
|
|
name: "backgroundPosition", cssName: "background-position", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withPosition(newValue);
|
|
target.backgroundInternal = background;
|
|
}
|
|
});
|
|
backgroundPositionProperty.register(Style);
|
|
|
|
function parseBorderColor(value: string): { top: Color, right: Color, bottom: Color, left: Color } {
|
|
let result: { top: Color, right: Color, bottom: Color, left: Color } = { top: undefined, right: undefined, bottom: undefined, left: undefined };
|
|
if (value.indexOf("rgb") === 0) {
|
|
result.top = result.right = result.bottom = result.left = new Color(value);
|
|
return result;
|
|
}
|
|
|
|
let arr = value.split(/[ ,]+/);
|
|
if (arr.length === 1) {
|
|
let arr0 = new Color(arr[0]);
|
|
result.top = arr0;
|
|
result.right = arr0;
|
|
result.bottom = arr0;
|
|
result.left = arr0;
|
|
}
|
|
else if (arr.length === 2) {
|
|
let arr0 = new Color(arr[0]);
|
|
let arr1 = new Color(arr[1]);
|
|
result.top = arr0;
|
|
result.right = arr1;
|
|
result.bottom = arr0;
|
|
result.left = arr1;
|
|
}
|
|
else if (arr.length === 3) {
|
|
let arr0 = new Color(arr[0]);
|
|
let arr1 = new Color(arr[1]);
|
|
let arr2 = new Color(arr[2]);
|
|
result.top = arr0;
|
|
result.right = arr1;
|
|
result.bottom = arr2;
|
|
result.left = arr1;
|
|
}
|
|
else if (arr.length === 4) {
|
|
let arr0 = new Color(arr[0]);
|
|
let arr1 = new Color(arr[1]);
|
|
let arr2 = new Color(arr[2]);
|
|
let arr3 = new Color(arr[3]);
|
|
result.top = arr0;
|
|
result.right = arr1;
|
|
result.bottom = arr2;
|
|
result.left = arr3;
|
|
}
|
|
else {
|
|
throw new Error(`Expected 1, 2, 3 or 4 parameters. Actual: ${value}`);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Border Color properties.
|
|
const borderColorProperty = new ShorthandProperty<Style, string | Color>({
|
|
name: "borderColor", cssName: "border-color",
|
|
getter: function (this: Style) {
|
|
if (Color.equals(this.borderTopColor, this.borderRightColor) &&
|
|
Color.equals(this.borderTopColor, this.borderBottomColor) &&
|
|
Color.equals(this.borderTopColor, this.borderLeftColor)) {
|
|
return this.borderTopColor;
|
|
}
|
|
else {
|
|
return `${this.borderTopColor} ${this.borderRightColor} ${this.borderBottomColor} ${this.borderLeftColor}`;
|
|
}
|
|
},
|
|
converter: function (value) {
|
|
if (typeof value === "string") {
|
|
let fourColors = parseBorderColor(value);
|
|
return [
|
|
[borderTopColorProperty, fourColors.top],
|
|
[borderRightColorProperty, fourColors.right],
|
|
[borderBottomColorProperty, fourColors.bottom],
|
|
[borderLeftColorProperty, fourColors.left]
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
[borderTopColorProperty, value],
|
|
[borderRightColorProperty, value],
|
|
[borderBottomColorProperty, value],
|
|
[borderLeftColorProperty, value]
|
|
];
|
|
}
|
|
}
|
|
});
|
|
borderColorProperty.register(Style);
|
|
|
|
export const borderTopColorProperty = new CssProperty<Style, Color>({
|
|
name: "borderTopColor", cssName: "border-top-color", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withBorderTopColor(newValue);
|
|
target.backgroundInternal = background;
|
|
}, equalityComparer: Color.equals, valueConverter: (value) => new Color(value)
|
|
});
|
|
borderTopColorProperty.register(Style);
|
|
|
|
export const borderRightColorProperty = new CssProperty<Style, Color>({
|
|
name: "borderRightColor", cssName: "border-right-color", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withBorderRightColor(newValue);
|
|
target.backgroundInternal = background;
|
|
}, equalityComparer: Color.equals, valueConverter: (value) => new Color(value)
|
|
});
|
|
borderRightColorProperty.register(Style);
|
|
|
|
export const borderBottomColorProperty = new CssProperty<Style, Color>({
|
|
name: "borderBottomColor", cssName: "border-bottom-color", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withBorderBottomColor(newValue);
|
|
target.backgroundInternal = background;
|
|
}, equalityComparer: Color.equals, valueConverter: (value) => new Color(value)
|
|
});
|
|
borderBottomColorProperty.register(Style);
|
|
|
|
export const borderLeftColorProperty = new CssProperty<Style, Color>({
|
|
name: "borderLeftColor", cssName: "border-left-color", valueChanged: (target, oldValue, newValue) => {
|
|
const background = target.backgroundInternal.withBorderLeftColor(newValue);
|
|
target.backgroundInternal = background;
|
|
}, equalityComparer: Color.equals, valueConverter: (value) => new Color(value)
|
|
});
|
|
borderLeftColorProperty.register(Style);
|
|
|
|
// Border Width properties.
|
|
const borderWidthProperty = new ShorthandProperty<Style, string | Length>({
|
|
name: "borderWidth", cssName: "border-width",
|
|
getter: function (this: Style) {
|
|
if (Length.equals(this.borderTopWidth, this.borderRightWidth) &&
|
|
Length.equals(this.borderTopWidth, this.borderBottomWidth) &&
|
|
Length.equals(this.borderTopWidth, this.borderLeftWidth)) {
|
|
return this.borderTopWidth;
|
|
}
|
|
else {
|
|
return `${Length.convertToString(this.borderTopWidth)} ${Length.convertToString(this.borderRightWidth)} ${Length.convertToString(this.borderBottomWidth)} ${Length.convertToString(this.borderLeftWidth)}`;
|
|
}
|
|
},
|
|
converter: function (value) {
|
|
if (typeof value === "string" && value !== "auto") {
|
|
let borderWidths = parseThickness(value);
|
|
return [
|
|
[borderTopWidthProperty, borderWidths.top],
|
|
[borderRightWidthProperty, borderWidths.right],
|
|
[borderBottomWidthProperty, borderWidths.bottom],
|
|
[borderLeftWidthProperty, borderWidths.left]
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
[borderTopWidthProperty, value],
|
|
[borderRightWidthProperty, value],
|
|
[borderBottomWidthProperty, value],
|
|
[borderLeftWidthProperty, value]
|
|
];
|
|
}
|
|
}
|
|
});
|
|
borderWidthProperty.register(Style);
|
|
|
|
export const borderTopWidthProperty = new CssProperty<Style, Length>({
|
|
name: "borderTopWidth", cssName: "border-top-width", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-top-width should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
|
|
target.view.effectiveBorderTopWidth = value;
|
|
const background = target.backgroundInternal.withBorderTopWidth(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderTopWidthProperty.register(Style);
|
|
|
|
export const borderRightWidthProperty = new CssProperty<Style, Length>({
|
|
name: "borderRightWidth", cssName: "border-right-width", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-right-width should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
|
|
target.view.effectiveBorderRightWidth = value;
|
|
const background = target.backgroundInternal.withBorderRightWidth(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderRightWidthProperty.register(Style);
|
|
|
|
export const borderBottomWidthProperty = new CssProperty<Style, Length>({
|
|
name: "borderBottomWidth", cssName: "border-bottom-width", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-bottom-width should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
|
|
target.view.effectiveBorderBottomWidth = value;
|
|
const background = target.backgroundInternal.withBorderBottomWidth(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderBottomWidthProperty.register(Style);
|
|
|
|
export const borderLeftWidthProperty = new CssProperty<Style, Length>({
|
|
name: "borderLeftWidth", cssName: "border-left-width", defaultValue: zeroLength, affectsLayout: isIOS, equalityComparer: Length.equals,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-left-width should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
|
|
target.view.effectiveBorderLeftWidth = value;
|
|
const background = target.backgroundInternal.withBorderLeftWidth(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderLeftWidthProperty.register(Style);
|
|
|
|
// Border Radius properties.
|
|
const borderRadiusProperty = new ShorthandProperty<Style, string | Length>({
|
|
name: "borderRadius", cssName: "border-radius",
|
|
getter: function (this: Style) {
|
|
if (Length.equals(this.borderTopLeftRadius, this.borderTopRightRadius) &&
|
|
Length.equals(this.borderTopLeftRadius, this.borderBottomRightRadius) &&
|
|
Length.equals(this.borderTopLeftRadius, this.borderBottomLeftRadius)) {
|
|
return this.borderTopLeftRadius;
|
|
}
|
|
return `${Length.convertToString(this.borderTopLeftRadius)} ${Length.convertToString(this.borderTopRightRadius)} ${Length.convertToString(this.borderBottomRightRadius)} ${Length.convertToString(this.borderBottomLeftRadius)}`;
|
|
},
|
|
converter: function (value) {
|
|
if (typeof value === "string") {
|
|
let borderRadius = parseThickness(value);
|
|
return [
|
|
[borderTopLeftRadiusProperty, borderRadius.top],
|
|
[borderTopRightRadiusProperty, borderRadius.right],
|
|
[borderBottomRightRadiusProperty, borderRadius.bottom],
|
|
[borderBottomLeftRadiusProperty, borderRadius.left]
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
[borderTopLeftRadiusProperty, value],
|
|
[borderTopRightRadiusProperty, value],
|
|
[borderBottomRightRadiusProperty, value],
|
|
[borderBottomLeftRadiusProperty, value]
|
|
];
|
|
}
|
|
}
|
|
});
|
|
borderRadiusProperty.register(Style);
|
|
|
|
export const borderTopLeftRadiusProperty = new CssProperty<Style, Length>({
|
|
name: "borderTopLeftRadius", cssName: "border-top-left-radius", defaultValue: 0, affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-top-left-radius should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
const background = target.backgroundInternal.withBorderTopLeftRadius(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderTopLeftRadiusProperty.register(Style);
|
|
|
|
export const borderTopRightRadiusProperty = new CssProperty<Style, Length>({
|
|
name: "borderTopRightRadius", cssName: "border-top-right-radius", defaultValue: 0, affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-top-right-radius should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
const background = target.backgroundInternal.withBorderTopRightRadius(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderTopRightRadiusProperty.register(Style);
|
|
|
|
export const borderBottomRightRadiusProperty = new CssProperty<Style, Length>({
|
|
name: "borderBottomRightRadius", cssName: "border-bottom-right-radius", defaultValue: 0, affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-bottom-right-radius should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
const background = target.backgroundInternal.withBorderBottomRightRadius(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderBottomRightRadiusProperty.register(Style);
|
|
|
|
export const borderBottomLeftRadiusProperty = new CssProperty<Style, Length>({
|
|
name: "borderBottomLeftRadius", cssName: "border-bottom-left-radius", defaultValue: 0, affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let value = Length.toDevicePixels(newValue, 0);
|
|
if (!isNonNegativeFiniteNumber(value)) {
|
|
throw new Error(`border-bottom-left-radius should be Non-Negative Finite number. Value: ${value}`);
|
|
}
|
|
const background = target.backgroundInternal.withBorderBottomLeftRadius(value);
|
|
target.backgroundInternal = background;
|
|
}, valueConverter: Length.parse
|
|
});
|
|
borderBottomLeftRadiusProperty.register(Style);
|
|
|
|
function isNonNegativeFiniteNumber(value: number): boolean {
|
|
return isFinite(value) && !isNaN(value) && value >= 0;
|
|
}
|
|
|
|
let supportedPaths = ["rect", "circle", "ellipse", "polygon", "inset"];
|
|
function isClipPathValid(value: string): boolean {
|
|
if (!value) {
|
|
return true;
|
|
}
|
|
let functionName = value.substring(0, value.indexOf("(")).trim();
|
|
return supportedPaths.indexOf(functionName) !== -1;
|
|
}
|
|
|
|
export const clipPathProperty = new CssProperty<Style, string>({
|
|
name: "clipPath", cssName: "clip-path", valueChanged: (target, oldValue, newValue) => {
|
|
if (!isClipPathValid(newValue)) {
|
|
throw new Error("clip-path is not valid.");
|
|
}
|
|
|
|
const background = target.backgroundInternal.withClipPath(newValue);
|
|
target.backgroundInternal = background;
|
|
}
|
|
});
|
|
clipPathProperty.register(Style);
|
|
|
|
function isFloatValueConverter(value: string): number {
|
|
let newValue = parseFloat(value);
|
|
if (isNaN(newValue)) {
|
|
throw new Error(`Invalid value: ${newValue}`);
|
|
}
|
|
|
|
return newValue;
|
|
}
|
|
|
|
export const zIndexProperty = new CssProperty<Style, number>({ name: "zIndex", cssName: "z-index", valueConverter: isFloatValueConverter });
|
|
zIndexProperty.register(Style);
|
|
|
|
function opacityConverter(value: any): number {
|
|
let newValue = parseFloat(value);
|
|
if (!isNaN(newValue) && 0 <= newValue && newValue <= 1) {
|
|
return newValue;
|
|
}
|
|
|
|
throw new Error(`Opacity should be between [0, 1]. Value: ${newValue}`);
|
|
}
|
|
|
|
export const opacityProperty = new CssAnimationProperty<Style, number>({ name: "opacity", cssName: "opacity", defaultValue: 1, valueConverter: opacityConverter });
|
|
opacityProperty.register(Style);
|
|
|
|
export const colorProperty = new InheritedCssProperty<Style, Color>({ name: "color", cssName: "color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) });
|
|
colorProperty.register(Style);
|
|
|
|
export const fontInternalProperty = new CssProperty<Style, Font>({ name: "fontInternal", cssName: "_fontInternal", defaultValue: Font.default });
|
|
fontInternalProperty.register(Style);
|
|
|
|
export const fontFamilyProperty = new InheritedCssProperty<Style, string>({
|
|
name: "fontFamily", cssName: "font-family", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let currentFont = target.fontInternal;
|
|
if (currentFont.fontFamily !== newValue) {
|
|
const newFont = currentFont.withFontFamily(newValue);
|
|
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
|
|
}
|
|
}
|
|
});
|
|
fontFamilyProperty.register(Style);
|
|
|
|
export const fontSizeProperty = new InheritedCssProperty<Style, number>({
|
|
name: "fontSize", cssName: "font-size", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
|
|
let currentFont = target.fontInternal;
|
|
if (currentFont.fontSize !== newValue) {
|
|
const newFont = currentFont.withFontSize(newValue);
|
|
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
|
|
}
|
|
},
|
|
valueConverter: (v) => parseFloat(v)
|
|
});
|
|
fontSizeProperty.register(Style);
|
|
|
|
export const fontStyleProperty = new InheritedCssProperty<Style, FontStyle>({
|
|
name: "fontStyle", cssName: "font-style", affectsLayout: isIOS, defaultValue: FontStyle.NORMAL, valueConverter: FontStyle.parse, valueChanged: (target, oldValue, newValue) => {
|
|
let currentFont = target.fontInternal;
|
|
if (currentFont.fontStyle !== newValue) {
|
|
const newFont = currentFont.withFontStyle(newValue);
|
|
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
|
|
}
|
|
}
|
|
});
|
|
fontStyleProperty.register(Style);
|
|
|
|
export const fontWeightProperty = new InheritedCssProperty<Style, FontWeight>({
|
|
name: "fontWeight", cssName: "font-weight", affectsLayout: isIOS, defaultValue: FontWeight.NORMAL, valueConverter: FontWeight.parse, valueChanged: (target, oldValue, newValue) => {
|
|
let currentFont = target.fontInternal;
|
|
if (currentFont.fontWeight !== newValue) {
|
|
const newFont = currentFont.withFontWeight(newValue);
|
|
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
|
|
}
|
|
}
|
|
});
|
|
fontWeightProperty.register(Style);
|
|
|
|
const fontProperty = new ShorthandProperty<Style, string>({
|
|
name: "font", cssName: "font",
|
|
getter: function (this: Style) {
|
|
return `${this.fontStyle} ${this.fontWeight} ${this.fontSize} ${this.fontFamily}`;
|
|
},
|
|
converter: function (value) {
|
|
if (value === unsetValue) {
|
|
return [
|
|
[fontStyleProperty, unsetValue],
|
|
[fontWeightProperty, unsetValue],
|
|
[fontSizeProperty, unsetValue],
|
|
[fontFamilyProperty, unsetValue]
|
|
];
|
|
} else {
|
|
const font = parseFont(value);
|
|
const fontSize = parseFloat(font.fontSize);
|
|
|
|
return [
|
|
[fontStyleProperty, font.fontStyle],
|
|
[fontWeightProperty, font.fontWeight],
|
|
[fontSizeProperty, fontSize],
|
|
[fontFamilyProperty, font.fontFamily]
|
|
];
|
|
}
|
|
}
|
|
});
|
|
fontProperty.register(Style);
|
|
|
|
export type Visibility = "visible" | "hidden" | "collapse";
|
|
export namespace Visibility {
|
|
export const VISIBLE: "visible" = "visible";
|
|
export const HIDDEN: "hidden" = "hidden";
|
|
export const COLLAPSE: "collapse" = "collapse";
|
|
export const isValid = makeValidator<Visibility>(VISIBLE, HIDDEN, COLLAPSE);
|
|
export const parse = (value: string) => value.toLowerCase() === "collapsed" ? COLLAPSE : parseStrict(value);
|
|
const parseStrict = makeParser<Visibility>(isValid);
|
|
}
|
|
|
|
export const visibilityProperty = new CssProperty<Style, Visibility>({
|
|
name: "visibility", cssName: "visibility", defaultValue: Visibility.VISIBLE, affectsLayout: isIOS, valueConverter: Visibility.parse, valueChanged: (target, oldValue, newValue) => {
|
|
target.view.isCollapsed = (newValue === Visibility.COLLAPSE);
|
|
}
|
|
});
|
|
visibilityProperty.register(Style);
|