@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
1,351 lines • 49.8 kB
JavaScript
// Types
import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
import { Style } from './style';
import { Color } from '../../color';
import { Font, parseFont, FontStyle, FontWeight, FontVariationSettings } from './font';
import { Background } from './background';
import { layout, hasDuplicates } from '../../utils';
import { radiansToDegrees } from '../../utils/number-utils';
import { decompose2DTransformMatrix, getTransformMatrix, matrixArrayToCssMatrix, multiplyAffine2d } from '../../matrix';
import { Trace } from '../../trace';
import { CoreTypes } from '../../core-types';
import { parseBackground } from '../../css/parser';
import { LinearGradient } from './linear-gradient';
import { parseCSSShadow } from './css-shadow';
function equalsCommon(a, b) {
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
}
if (!b) {
return false;
}
return b.unit == 'dip' && a == b.value; // tslint:disable-line
}
if (b == 'auto') {
// tslint:disable-line
return false;
}
if (typeof b === 'number') {
return a ? a.unit == 'dip' && a.value == b : false; // tslint:disable-line
}
if (!a || !b) {
return false;
}
return a.value == b.value && a.unit == b.unit; // tslint:disable-line
}
function convertToStringCommon(length) {
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, auto = Number.NaN, parentAvailableWidth = Number.NaN) {
if (length == 'auto') {
// tslint:disable-line
return auto;
}
if (typeof length === 'number') {
return layout.round(layout.toDevicePixels(length));
}
if (!length) {
return auto;
}
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 var PercentLength;
(function (PercentLength) {
function parse(fromValue) {
if (fromValue == 'auto') {
// tslint:disable-line
return 'auto';
}
if (typeof fromValue === 'string') {
let stringValue = fromValue.trim();
const percentIndex = stringValue.indexOf('%');
if (percentIndex !== -1) {
let value;
// if only % or % is not last we treat it as invalid value.
if (percentIndex !== stringValue.length - 1 || percentIndex === 0) {
value = Number.NaN;
}
else {
// Normalize result to values between -1 and 1
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();
const value = parseFloat(stringValue);
if (isNaN(value) || !isFinite(value)) {
throw new Error(`Invalid value: ${fromValue}`);
}
return { unit: 'px', value };
}
else {
const value = parseFloat(stringValue);
if (isNaN(value) || !isFinite(value)) {
throw new Error(`Invalid value: ${fromValue}`);
}
return value;
}
}
else {
return fromValue;
}
}
PercentLength.parse = parse;
PercentLength.equals = equalsCommon;
PercentLength.toDevicePixels = toDevicePixelsCommon;
PercentLength.convertToString = convertToStringCommon;
})(PercentLength || (PercentLength = {}));
export var Length;
(function (Length) {
function parse(fromValue) {
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();
const value = parseFloat(stringValue);
if (isNaN(value) || !isFinite(value)) {
throw new Error(`Invalid value: ${stringValue}`);
}
return { unit: 'px', value };
}
else {
const value = parseFloat(stringValue);
if (isNaN(value) || !isFinite(value)) {
throw new Error(`Invalid value: ${stringValue}`);
}
return value;
}
}
else {
return fromValue;
}
}
Length.parse = parse;
Length.equals = equalsCommon;
Length.toDevicePixels = toDevicePixelsCommon;
Length.convertToString = convertToStringCommon;
})(Length || (Length = {}));
export const minWidthProperty = new CssProperty({
name: 'minWidth',
cssName: 'min-width',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectiveMinWidth = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
minWidthProperty.register(Style);
export const minHeightProperty = new CssProperty({
name: 'minHeight',
cssName: 'min-height',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectiveMinHeight = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
minHeightProperty.register(Style);
export const widthProperty = new CssAnimationProperty({
name: 'width',
cssName: 'width',
defaultValue: 'auto',
equalityComparer: Length.equals,
// TODO: CSSAnimationProperty was needed for keyframe (copying other impls), but `affectsLayout` does not exist
// on the animation property, so fake it here. x_x
valueChanged: (target, oldValue, newValue) => {
if (global.isIOS) {
const view = target.viewRef.get();
if (view) {
view.requestLayout();
}
}
},
valueConverter: PercentLength.parse,
});
widthProperty.register(Style);
export const heightProperty = new CssAnimationProperty({
name: 'height',
cssName: 'height',
defaultValue: 'auto',
equalityComparer: Length.equals,
// TODO: CSSAnimationProperty was needed for keyframe (copying other impls), but `affectsLayout` does not exist
// on the animation property, so fake it here. -_-
valueChanged: (target, oldValue, newValue) => {
if (global.isIOS) {
const view = target.viewRef.get();
if (view) {
view.requestLayout();
}
}
},
valueConverter: PercentLength.parse,
});
heightProperty.register(Style);
const marginProperty = new ShorthandProperty({
name: 'margin',
cssName: 'margin',
getter: function () {
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({
name: 'marginLeft',
cssName: 'margin-left',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueConverter: PercentLength.parse,
});
marginLeftProperty.register(Style);
export const marginRightProperty = new CssProperty({
name: 'marginRight',
cssName: 'margin-right',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueConverter: PercentLength.parse,
});
marginRightProperty.register(Style);
export const marginTopProperty = new CssProperty({
name: 'marginTop',
cssName: 'margin-top',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueConverter: PercentLength.parse,
});
marginTopProperty.register(Style);
export const marginBottomProperty = new CssProperty({
name: 'marginBottom',
cssName: 'margin-bottom',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueConverter: PercentLength.parse,
});
marginBottomProperty.register(Style);
const paddingProperty = new ShorthandProperty({
name: 'padding',
cssName: 'padding',
getter: function () {
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({
name: 'paddingLeft',
cssName: 'padding-left',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectivePaddingLeft = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
paddingLeftProperty.register(Style);
export const paddingRightProperty = new CssProperty({
name: 'paddingRight',
cssName: 'padding-right',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectivePaddingRight = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
paddingRightProperty.register(Style);
export const paddingTopProperty = new CssProperty({
name: 'paddingTop',
cssName: 'padding-top',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectivePaddingTop = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
paddingTopProperty.register(Style);
export const paddingBottomProperty = new CssProperty({
name: 'paddingBottom',
cssName: 'padding-bottom',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.effectivePaddingBottom = Length.toDevicePixels(newValue, 0);
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
valueConverter: Length.parse,
});
paddingBottomProperty.register(Style);
export const horizontalAlignmentProperty = new CssProperty({
name: 'horizontalAlignment',
cssName: 'horizontal-align',
defaultValue: CoreTypes.HorizontalAlignment.stretch,
affectsLayout: global.isIOS,
valueConverter: CoreTypes.HorizontalAlignment.parse,
});
horizontalAlignmentProperty.register(Style);
export const verticalAlignmentProperty = new CssProperty({
name: 'verticalAlignment',
cssName: 'vertical-align',
defaultValue: CoreTypes.VerticalAlignmentText.stretch,
affectsLayout: global.isIOS,
valueConverter: CoreTypes.VerticalAlignmentText.parse,
});
verticalAlignmentProperty.register(Style);
function parseThickness(value) {
if (typeof value === 'string') {
const arr = value.split(/[ ,]+/);
let top;
let right;
let bottom;
let left;
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(value) {
if (typeof value === 'string' && value !== 'auto') {
const 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(value) {
if (typeof value === 'string' && value !== 'auto') {
const 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({
name: 'rotate',
cssName: 'rotate',
defaultValue: 0,
valueConverter: parseFloat,
});
rotateProperty.register(Style);
export const rotateXProperty = new CssAnimationProperty({
name: 'rotateX',
cssName: 'rotatex',
defaultValue: 0,
valueConverter: parseFloat,
});
rotateXProperty.register(Style);
export const rotateYProperty = new CssAnimationProperty({
name: 'rotateY',
cssName: 'rotatey',
defaultValue: 0,
valueConverter: parseFloat,
});
rotateYProperty.register(Style);
export const perspectiveProperty = new CssAnimationProperty({
name: 'perspective',
cssName: 'perspective',
defaultValue: 1000,
valueConverter: parseFloat,
});
perspectiveProperty.register(Style);
export const scaleXProperty = new CssAnimationProperty({
name: 'scaleX',
cssName: 'scaleX',
defaultValue: 1,
valueConverter: parseFloat,
});
scaleXProperty.register(Style);
export const scaleYProperty = new CssAnimationProperty({
name: 'scaleY',
cssName: 'scaleY',
defaultValue: 1,
valueConverter: parseFloat,
});
scaleYProperty.register(Style);
function parseDIPs(value) {
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({
name: 'translateX',
cssName: 'translateX',
defaultValue: 0,
valueConverter: parseDIPs,
});
translateXProperty.register(Style);
export const translateYProperty = new CssAnimationProperty({
name: 'translateY',
cssName: 'translateY',
defaultValue: 0,
valueConverter: parseDIPs,
});
translateYProperty.register(Style);
const transformProperty = new ShorthandProperty({
name: 'transform',
cssName: 'transform',
getter: function () {
const scaleX = this.scaleX;
const scaleY = this.scaleY;
const translateX = this.translateX;
const translateY = this.translateY;
const rotate = this.rotate;
const rotateX = this.rotateX;
const rotateY = this.rotateY;
let result = '';
if (translateX !== 0 || translateY !== 0) {
result += `translate(${translateX}, ${translateY}) `;
}
if (scaleX !== 1 || scaleY !== 1) {
result += `scale(${scaleX}, ${scaleY}) `;
}
if (rotateX !== 0 || rotateY !== 0 || rotate !== 0) {
result += `rotate(${rotateX}, ${rotateY}, ${rotate}) `;
result += `rotate (${rotate})`;
}
return result.trim();
},
converter: convertToTransform,
});
transformProperty.register(Style);
const IDENTITY_TRANSFORMATION = {
translate: { x: 0, y: 0 },
rotate: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1 },
};
const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
const TRANSFORMATIONS = Object.freeze(['rotate', 'rotateX', 'rotateY', 'rotate3d', '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 },
}),
rotate3d: (value) => ({ property: 'rotate', value }),
rotateX: (x) => ({
property: 'rotate',
value: {
x,
y: IDENTITY_TRANSFORMATION.rotate.y,
z: IDENTITY_TRANSFORMATION.rotate.z,
},
}),
rotateY: (y) => ({
property: 'rotate',
value: {
x: IDENTITY_TRANSFORMATION.rotate.x,
y,
z: IDENTITY_TRANSFORMATION.rotate.z,
},
}),
rotate: (z) => ({
property: 'rotate',
value: {
x: IDENTITY_TRANSFORMATION.rotate.x,
y: IDENTITY_TRANSFORMATION.rotate.y,
z,
},
}),
});
function convertToTransform(value) {
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.z],
[rotateXProperty, rotate.x],
[rotateYProperty, rotate.y],
];
}
export function transformConverter(text) {
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 = { ...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) {
const matches = [];
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 }) {
return STYLE_TRANSFORMATION_MAP[property](value);
}
function convertTransformValue(property, stringValue) {
// eslint-disable-next-line prefer-const
let [x, y, z] = stringValue.split(',').map(parseFloat);
if (property === 'translate') {
y ?? (y = IDENTITY_TRANSFORMATION.translate.y);
}
else {
y ?? (y = x);
z ?? (z = y);
}
if (property === 'rotate' || property === 'rotateX' || property === 'rotateY') {
return stringValue.slice(-3) === 'rad' ? radiansToDegrees(x) : x;
}
return { x, y, z };
}
// Background properties.
const backgroundProperty = new ShorthandProperty({
name: 'background',
cssName: 'background',
getter: function () {
return `${this.backgroundColor} ${this.backgroundImage} ${this.backgroundRepeat} ${this.backgroundPosition}`;
},
converter: convertToBackgrounds,
});
backgroundProperty.register(Style);
export const backgroundInternalProperty = new CssProperty({
name: 'backgroundInternal',
cssName: '_backgroundInternal',
defaultValue: Background.default,
});
backgroundInternalProperty.register(Style);
// const pattern: RegExp = /url\(('|")(.*?)\1\)/;
export const backgroundImageProperty = new CssProperty({
name: 'backgroundImage',
cssName: 'background-image',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withImage(newValue);
},
equalityComparer: (value1, value2) => {
if (value1 instanceof LinearGradient && value2 instanceof LinearGradient) {
return LinearGradient.equals(value1, value2);
}
else {
return value1 === value2;
}
},
valueConverter: (value) => {
if (typeof value === 'string') {
const parsed = parseBackground(value);
if (parsed) {
value = typeof parsed.value.image === 'object' ? LinearGradient.parse(parsed.value.image) : value;
}
}
return value;
},
});
backgroundImageProperty.register(Style);
export const backgroundColorProperty = new CssAnimationProperty({
name: 'backgroundColor',
cssName: 'background-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
});
backgroundColorProperty.register(Style);
export const backgroundRepeatProperty = new CssProperty({
name: 'backgroundRepeat',
cssName: 'background-repeat',
valueConverter: CoreTypes.BackgroundRepeat.parse,
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withRepeat(newValue);
},
});
backgroundRepeatProperty.register(Style);
export const backgroundSizeProperty = new CssProperty({
name: 'backgroundSize',
cssName: 'background-size',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withSize(newValue);
},
});
backgroundSizeProperty.register(Style);
export const backgroundPositionProperty = new CssProperty({
name: 'backgroundPosition',
cssName: 'background-position',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withPosition(newValue);
},
});
backgroundPositionProperty.register(Style);
function convertToBackgrounds(value) {
if (typeof value === 'string') {
const backgrounds = parseBackground(value).value;
let backgroundColor = unsetValue;
if (backgrounds.color) {
backgroundColor = backgrounds.color instanceof Color ? backgrounds.color : new Color(backgrounds.color);
}
let backgroundImage;
if (typeof backgrounds.image === 'object' && backgrounds.image) {
backgroundImage = LinearGradient.parse(backgrounds.image);
}
else {
backgroundImage = backgrounds.image || unsetValue;
}
const backgroundRepeat = backgrounds.repeat || unsetValue;
const backgroundPosition = backgrounds.position ? backgrounds.position.text : unsetValue;
return [
[backgroundColorProperty, backgroundColor],
[backgroundImageProperty, backgroundImage],
[backgroundRepeatProperty, backgroundRepeat],
[backgroundPositionProperty, backgroundPosition],
];
}
else {
return [
[backgroundColorProperty, unsetValue],
[backgroundImageProperty, unsetValue],
[backgroundRepeatProperty, unsetValue],
[backgroundPositionProperty, unsetValue],
];
}
}
function parseBorderColor(value) {
const result = {
top: undefined,
right: undefined,
bottom: undefined,
left: undefined,
};
if (value.indexOf('rgb') === 0 || value.indexOf('hsl') === 0) {
result.top = result.right = result.bottom = result.left = new Color(value);
return result;
}
const arr = value.split(/[ ,]+/);
if (arr.length === 1) {
const arr0 = new Color(arr[0]);
result.top = arr0;
result.right = arr0;
result.bottom = arr0;
result.left = arr0;
}
else if (arr.length === 2) {
const arr0 = new Color(arr[0]);
const arr1 = new Color(arr[1]);
result.top = arr0;
result.right = arr1;
result.bottom = arr0;
result.left = arr1;
}
else if (arr.length === 3) {
const arr0 = new Color(arr[0]);
const arr1 = new Color(arr[1]);
const arr2 = new Color(arr[2]);
result.top = arr0;
result.right = arr1;
result.bottom = arr2;
result.left = arr1;
}
else if (arr.length === 4) {
const arr0 = new Color(arr[0]);
const arr1 = new Color(arr[1]);
const arr2 = new Color(arr[2]);
const 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({
name: 'borderColor',
cssName: 'border-color',
getter: function () {
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') {
const 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({
name: 'borderTopColor',
cssName: 'border-top-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withBorderTopColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
});
borderTopColorProperty.register(Style);
export const borderRightColorProperty = new CssProperty({
name: 'borderRightColor',
cssName: 'border-right-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withBorderRightColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
});
borderRightColorProperty.register(Style);
export const borderBottomColorProperty = new CssProperty({
name: 'borderBottomColor',
cssName: 'border-bottom-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withBorderBottomColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
});
borderBottomColorProperty.register(Style);
export const borderLeftColorProperty = new CssProperty({
name: 'borderLeftColor',
cssName: 'border-left-color',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withBorderLeftColor(newValue);
},
equalityComparer: Color.equals,
valueConverter: (value) => new Color(value),
});
borderLeftColorProperty.register(Style);
// Border Width properties.
const borderWidthProperty = new ShorthandProperty({
name: 'borderWidth',
cssName: 'border-width',
getter: function () {
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') {
const 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({
name: 'borderTopWidth',
cssName: 'border-top-width',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-top-width should be Non-Negative Finite number. Value: ${value}`);
}
const view = target.viewRef.get();
if (view) {
view.effectiveBorderTopWidth = value;
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
target.backgroundInternal = target.backgroundInternal.withBorderTopWidth(value);
},
valueConverter: Length.parse,
});
borderTopWidthProperty.register(Style);
export const borderRightWidthProperty = new CssProperty({
name: 'borderRightWidth',
cssName: 'border-right-width',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-right-width should be Non-Negative Finite number. Value: ${value}`);
}
const view = target.viewRef.get();
if (view) {
view.effectiveBorderRightWidth = value;
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
target.backgroundInternal = target.backgroundInternal.withBorderRightWidth(value);
},
valueConverter: Length.parse,
});
borderRightWidthProperty.register(Style);
export const borderBottomWidthProperty = new CssProperty({
name: 'borderBottomWidth',
cssName: 'border-bottom-width',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-bottom-width should be Non-Negative Finite number. Value: ${value}`);
}
const view = target.viewRef.get();
if (view) {
view.effectiveBorderBottomWidth = value;
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
target.backgroundInternal = target.backgroundInternal.withBorderBottomWidth(value);
},
valueConverter: Length.parse,
});
borderBottomWidthProperty.register(Style);
export const borderLeftWidthProperty = new CssProperty({
name: 'borderLeftWidth',
cssName: 'border-left-width',
defaultValue: CoreTypes.zeroLength,
affectsLayout: global.isIOS,
equalityComparer: Length.equals,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-left-width should be Non-Negative Finite number. Value: ${value}`);
}
const view = target.viewRef.get();
if (view) {
view.effectiveBorderLeftWidth = value;
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
target.backgroundInternal = target.backgroundInternal.withBorderLeftWidth(value);
},
valueConverter: Length.parse,
});
borderLeftWidthProperty.register(Style);
// Border Radius properties.
const borderRadiusProperty = new ShorthandProperty({
name: 'borderRadius',
cssName: 'border-radius',
getter: function () {
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') {
const 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({
name: 'borderTopLeftRadius',
cssName: 'border-top-left-radius',
defaultValue: 0,
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-top-left-radius should be Non-Negative Finite number. Value: ${value}`);
}
target.backgroundInternal = target.backgroundInternal.withBorderTopLeftRadius(value);
},
valueConverter: Length.parse,
equalityComparer: Length.equals,
});
borderTopLeftRadiusProperty.register(Style);
export const borderTopRightRadiusProperty = new CssProperty({
name: 'borderTopRightRadius',
cssName: 'border-top-right-radius',
defaultValue: 0,
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-top-right-radius should be Non-Negative Finite number. Value: ${value}`);
}
target.backgroundInternal = target.backgroundInternal.withBorderTopRightRadius(value);
},
valueConverter: Length.parse,
equalityComparer: Length.equals,
});
borderTopRightRadiusProperty.register(Style);
export const borderBottomRightRadiusProperty = new CssProperty({
name: 'borderBottomRightRadius',
cssName: 'border-bottom-right-radius',
defaultValue: 0,
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-bottom-right-radius should be Non-Negative Finite number. Value: ${value}`);
}
target.backgroundInternal = target.backgroundInternal.withBorderBottomRightRadius(value);
},
valueConverter: Length.parse,
equalityComparer: Length.equals,
});
borderBottomRightRadiusProperty.register(Style);
export const borderBottomLeftRadiusProperty = new CssProperty({
name: 'borderBottomLeftRadius',
cssName: 'border-bottom-left-radius',
defaultValue: 0,
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const value = Length.toDevicePixels(newValue, 0);
if (!isNonNegativeFiniteNumber(value)) {
throw new Error(`border-bottom-left-radius should be Non-Negative Finite number. Value: ${value}`);
}
target.backgroundInternal = target.backgroundInternal.withBorderBottomLeftRadius(value);
},
valueConverter: Length.parse,
equalityComparer: Length.equals,
});
borderBottomLeftRadiusProperty.register(Style);
const boxShadowProperty = new CssProperty({
name: 'boxShadow',
cssName: 'box-shadow',
valueChanged: (target, oldValue, newValue) => {
target.backgroundInternal = target.backgroundInternal.withBoxShadow(newValue
? {
inset: newValue.inset,
offsetX: Length.toDevicePixels(newValue.offsetX, 0),
offsetY: Length.toDevicePixels(newValue.offsetY, 0),
blurRadius: Length.toDevicePixels(newValue.blurRadius, 0),
spreadRadius: Length.toDevicePixels(newValue.spreadRadius, 0),
color: newValue.color,
}
: null);
},
valueConverter: (value) => {
return parseCSSShadow(value);
},
});
boxShadowProperty.register(Style);
function isNonNegativeFiniteNumber(value) {
return isFinite(value) && !isNaN(value) && value >= 0;
}
const supportedPaths = ['rect', 'circle', 'ellipse', 'polygon', 'inset'];
function isClipPathValid(value) {
if (!value) {
return true;
}
const functionName = value.substring(0, value.indexOf('(')).trim();
return supportedPaths.indexOf(functionName) !== -1;
}
export const clipPathProperty = new CssProperty({
name: 'clipPath',
cssName: 'clip-path',
valueChanged: (target, oldValue, newValue) => {
if (!isClipPathValid(newValue)) {
throw new Error('clip-path is not valid.');
}
target.backgroundInternal = target.backgroundInternal.withClipPath(newValue);
},
});
clipPathProperty.register(Style);
function isFloatValueConverter(value) {
const newValue = parseFloat(value);
if (isNaN(newValue)) {
throw new Error(`Invalid value: ${newValue}`);
}
return newValue;
}
export const zIndexProperty = new CssProperty({
name: 'zIndex',
cssName: 'z-index',
valueConverter: isFloatValueConverter,
});
zIndexProperty.register(Style);
function opacityConverter(value) {
const 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({
name: 'opacity',
cssName: 'opacity',
defaultValue: 1,
valueConverter: opacityConverter,
});
opacityProperty.register(Style);
export const colorProperty = new InheritedCssProperty({
name: 'color',
cssName: 'color',
equalityComparer: Color.equals,
valueConverter: (v) => new Color(v),
});
colorProperty.register(Style);
export const fontInternalProperty = new CssProperty({
name: 'fontInternal',
cssName: '_fontInternal',
});
fontInternalProperty.register(Style);
export const fontFamilyProperty = new InheritedCssProperty({
name: 'fontFamily',
cssName: 'font-family',
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const currentFont = target.fontInternal || Font.default;
if (currentFont.fontFamily !== newValue) {
const newFont = currentFont.withFontFamily(newValue);
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
}
},
});
fontFamilyProperty.register(Style);
export const fontScaleInternalProperty = new InheritedCssProperty({
name: 'fontScaleInternal',
cssName: '_fontScaleInternal',
defaultValue: 1.0,
valueConverter: (v) => parseFloat(v),
});
fontScaleInternalProperty.register(Style);
export const fontSizeProperty = new InheritedCssProperty({
name: 'fontSize',
cssName: 'font-size',
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
if (target.viewRef['handleFontSize'] === true) {
return;
}
const currentFont = target.fontInternal || Font.default;
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({
name: 'fontStyle',
cssName: 'font-style',
affectsLayout: global.isIOS,
defaultValue: FontStyle.NORMAL,
valueConverter: FontStyle.parse,
valueChanged: (target, oldValue, newValue) => {
const currentFont = target.fontInternal || Font.default;
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({
name: 'fontWeight',
cssName: 'font-weight',
affectsLayout: global.isIOS,
defaultValue: FontWeight.NORMAL,
valueConverter: FontWeight.parse,
valueChanged: (target, oldValue, newValue) => {
const currentFont = target.fontInternal || Font.default;
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({
name: 'font',
cssName: 'font',
getter: function () {
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 const fontVariationSettingsProperty = new InheritedCssProperty({
name: 'fontVariationSettings',
cssName: 'font-variation-settings',
affectsLayout: global.isIOS,
valueChanged: (target, oldValue, newValue) => {
const currentFont = target.fontInternal || Font.default;
if (currentFont.fontVariationSettings !== newValue) {
const newFont = currentFont.withFontVariationSettings(newValue);
target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
}
},
valueConverter: (value) => {
return FontVariationSettings.parse(value);
},
});
fontVariationSettingsProperty.register(Style);
export const visibilityProperty = new CssProperty({
name: 'visibility',
cssName: 'visibility',
defaultValue: CoreTypes.Visibility.visible,
affectsLayout: global.isIOS,
valueConverter: CoreTypes.Visibility.parse,
valueChanged: (target, oldValue, newValue) => {
const view = target.viewRef.get();
if (view) {
view.isCollapsed = newValue === CoreTypes.Visibility.collapse;
}
else {
Trace.write(`${newValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn);
}
},
});
visibilityProperty.register(Style);
export const androidElevationProperty = new CssProperty({
name: 'androidElevation',
cssName: 'android-elevation',
valueConverter: parseFloat,
});
androidElevationProperty.register(Style);
export const androidDynamicElevationOffsetProperty = new CssProperty({
name: 'androidDynamicElevationOffset',
cssName: 'android-dynamic-elevation-offset',
valueConverter: parseFloat,
});
androidDynamicElevationOffsetProperty.register(Style);
//# sourceMappingURL=style-properties.js.map