mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
feat(core): style properties module improvements and organization (#10685)
This commit is contained in:

committed by
GitHub

parent
56af0b2f7e
commit
3a7206fc3b
@ -29,8 +29,9 @@ export namespace CoreTypes {
|
||||
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
||||
export type LengthPercentUnit = { readonly unit: '%'; readonly value: percent };
|
||||
|
||||
export type LengthType = 'auto' | dip | LengthDipUnit | LengthPxUnit;
|
||||
export type PercentLengthType = 'auto' | dip | LengthDipUnit | LengthPxUnit | LengthPercentUnit;
|
||||
export type FixedLengthType = dip | LengthDipUnit | LengthPxUnit;
|
||||
export type LengthType = 'auto' | FixedLengthType;
|
||||
export type PercentLengthType = 'auto' | FixedLengthType | LengthPercentUnit;
|
||||
|
||||
export const zeroLength: LengthType = {
|
||||
value: 0,
|
||||
|
@ -8,13 +8,19 @@ export type Transformation = {
|
||||
value: TransformationValue;
|
||||
};
|
||||
|
||||
export type TransformationType = 'rotate' | 'translate' | 'translateX' | 'translateY' | 'scale' | 'scaleX' | 'scaleY';
|
||||
export type TransformationType = 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY';
|
||||
|
||||
export type TransformationValue = Pair | number;
|
||||
export type TransformationValue = Point3D | Pair | number;
|
||||
|
||||
export interface Point3D {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export type TransformFunctionsInfo = {
|
||||
translate: Pair;
|
||||
rotate: number;
|
||||
rotate: Point3D;
|
||||
scale: Pair;
|
||||
};
|
||||
|
||||
@ -55,7 +61,7 @@ export interface AnimationDefinition {
|
||||
scale?: Pair;
|
||||
height?: CoreTypes.PercentLengthType | string;
|
||||
width?: CoreTypes.PercentLengthType | string;
|
||||
rotate?: number;
|
||||
rotate?: Point3D;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
iterations?: number;
|
||||
|
2
packages/core/ui/animation/index.d.ts
vendored
2
packages/core/ui/animation/index.d.ts
vendored
@ -83,7 +83,7 @@ export type Transformation = {
|
||||
/**
|
||||
* Defines possible css transformations
|
||||
*/
|
||||
export type TransformationType = 'rotate' | 'rotateX' | 'rotateY' | 'translate' | 'translateX' | 'translateY' | 'scale' | 'scaleX' | 'scaleY';
|
||||
export type TransformationType = 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY';
|
||||
|
||||
/**
|
||||
* Defines possible css transformation values
|
||||
|
@ -3,6 +3,7 @@ import { LinearGradient } from './linear-gradient';
|
||||
// Types.
|
||||
import { Color } from '../../color';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
|
||||
/**
|
||||
* Flags used to hint the background handler if it has to clear a specific property
|
||||
@ -39,7 +40,7 @@ export class Background {
|
||||
public borderTopRightRadius = 0;
|
||||
public borderBottomLeftRadius = 0;
|
||||
public borderBottomRightRadius = 0;
|
||||
public clipPath: string;
|
||||
public clipPath: string | ClipPathFunction;
|
||||
public boxShadow: BoxShadow;
|
||||
public clearFlags: number = BackgroundClearFlags.NONE;
|
||||
|
||||
@ -192,7 +193,7 @@ export class Background {
|
||||
return clone;
|
||||
}
|
||||
|
||||
public withClipPath(value: string): Background {
|
||||
public withClipPath(value: string | ClipPathFunction): Background {
|
||||
const clone = this.clone();
|
||||
clone.clipPath = value;
|
||||
|
||||
@ -224,16 +225,23 @@ export class Background {
|
||||
return false;
|
||||
}
|
||||
|
||||
let imagesEqual = false;
|
||||
let isImageEqual = false;
|
||||
if (value1 instanceof LinearGradient && value2 instanceof LinearGradient) {
|
||||
imagesEqual = LinearGradient.equals(value1, value2);
|
||||
isImageEqual = LinearGradient.equals(value1, value2);
|
||||
} else {
|
||||
imagesEqual = value1.image === value2.image;
|
||||
isImageEqual = value1.image === value2.image;
|
||||
}
|
||||
|
||||
let isClipPathEqual = false;
|
||||
if (value1.clipPath instanceof ClipPathFunction && value2.clipPath instanceof ClipPathFunction) {
|
||||
isClipPathEqual = ClipPathFunction.equals(value1.clipPath, value2.clipPath);
|
||||
} else {
|
||||
isClipPathEqual = value1.clipPath === value2.clipPath;
|
||||
}
|
||||
|
||||
return (
|
||||
Color.equals(value1.color, value2.color) &&
|
||||
imagesEqual &&
|
||||
isImageEqual &&
|
||||
value1.position === value2.position &&
|
||||
value1.repeat === value2.repeat &&
|
||||
value1.size === value2.size &&
|
||||
@ -249,7 +257,7 @@ export class Background {
|
||||
value1.borderTopRightRadius === value2.borderTopRightRadius &&
|
||||
value1.borderBottomRightRadius === value2.borderBottomRightRadius &&
|
||||
value1.borderBottomLeftRadius === value2.borderBottomLeftRadius &&
|
||||
value1.clipPath === value2.clipPath
|
||||
isClipPathEqual
|
||||
// && value1.clearFlags === value2.clearFlags
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { View } from '../core/view';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
import { isDataURI, isFileOrResourcePath, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils';
|
||||
import { parse } from '../../css-value';
|
||||
import { path, knownFolders } from '../../file-system';
|
||||
@ -92,7 +93,7 @@ export function refreshBorderDrawable(view: View, borderDrawable: org.nativescri
|
||||
background.borderBottomRightRadius,
|
||||
background.borderBottomLeftRadius,
|
||||
|
||||
background.clipPath,
|
||||
background.clipPath instanceof ClipPathFunction ? background.clipPath.toString() : background.clipPath,
|
||||
|
||||
background.color ? background.color.android : 0,
|
||||
imageUri,
|
||||
@ -103,7 +104,7 @@ export function refreshBorderDrawable(view: View, borderDrawable: org.nativescri
|
||||
background.position,
|
||||
backgroundPositionParsedCSSValues,
|
||||
background.size,
|
||||
backgroundSizeParsedCSSValues
|
||||
backgroundSizeParsedCSSValues,
|
||||
);
|
||||
//console.log(`>>> ${borderDrawable.toDebugString()}`);
|
||||
}
|
||||
|
17
packages/core/ui/styling/background.d.ts
vendored
17
packages/core/ui/styling/background.d.ts
vendored
@ -1,7 +1,8 @@
|
||||
import { Color } from '../../color';
|
||||
import { View } from '../core/view';
|
||||
import { BackgroundRepeat } from '../../css/parser';
|
||||
import { LinearGradient } from '../styling/linear-gradient';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { Background as BackgroundDefinition } from './background-common';
|
||||
|
||||
@ -32,7 +33,7 @@ export enum CacheMode {
|
||||
// public borderTopRightRadius: number;
|
||||
// public borderBottomRightRadius: number;
|
||||
// public borderBottomLeftRadius: number;
|
||||
// public clipPath: string;
|
||||
// public clipPath: string | ClipPathFunction;
|
||||
// public boxShadow: string | BoxShadow;
|
||||
// public clearFlags: number;
|
||||
|
||||
@ -80,12 +81,12 @@ export namespace ios {
|
||||
export function drawBackgroundVisualEffects(view: View): void;
|
||||
export function clearBackgroundVisualEffects(view: View): void;
|
||||
export function createUIImageFromURI(view: View, imageURI: string, flip: boolean, callback: (image: any) => void): void;
|
||||
export function generateClipPath(view: View, bounds: CGRect): any;
|
||||
export function generateShadowLayerPaths(view: View, bounds: CGRect): { maskPath: any; shadowPath: any };
|
||||
export function getUniformBorderRadius(view: View, bounds: CGRect): number;
|
||||
export function generateNonUniformBorderInnerClipRoundedPath(view: View, bounds: CGRect): any;
|
||||
export function generateNonUniformBorderOuterClipRoundedPath(view: View, bounds: CGRect): any;
|
||||
export function generateNonUniformMultiColorBorderRoundedPaths(view: View, bounds: CGRect): Array<any>;
|
||||
export function generateClipPath(view: View, bounds: any /* CGRect */): any;
|
||||
export function generateShadowLayerPaths(view: View, bounds: any /* CGRect */): { maskPath: any; shadowPath: any };
|
||||
export function getUniformBorderRadius(view: View, bounds: any /* CGRect */): number;
|
||||
export function generateNonUniformBorderInnerClipRoundedPath(view: View, bounds: any /* CGRect */): any;
|
||||
export function generateNonUniformBorderOuterClipRoundedPath(view: View, bounds: any /* CGRect */): any;
|
||||
export function generateNonUniformMultiColorBorderRoundedPaths(view: View, bounds: any /* CGRect */): Array<any>;
|
||||
}
|
||||
|
||||
export namespace ad {
|
||||
|
@ -11,6 +11,7 @@ import { parse as cssParse } from '../../css-value/reworkcss-value.js';
|
||||
import { BoxShadow } from './box-shadow';
|
||||
import { Length } from './style-properties';
|
||||
import { BackgroundClearFlags } from './background-common';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
|
||||
export * from './background-common';
|
||||
|
||||
@ -303,30 +304,28 @@ export namespace ios {
|
||||
let path: UIBezierPath;
|
||||
const clipPath = background.clipPath;
|
||||
|
||||
const functionName: string = clipPath.substring(0, clipPath.indexOf('('));
|
||||
const value: string = clipPath.replace(`${functionName}(`, '').replace(')', '');
|
||||
|
||||
switch (functionName) {
|
||||
if (clipPath instanceof ClipPathFunction) {
|
||||
switch (clipPath.shape) {
|
||||
case 'rect':
|
||||
path = rectPath(value, position);
|
||||
path = rectPath(clipPath.rule, position);
|
||||
break;
|
||||
|
||||
case 'inset':
|
||||
path = insetPath(value, position);
|
||||
path = insetPath(clipPath.rule, position);
|
||||
break;
|
||||
|
||||
case 'circle':
|
||||
path = circlePath(value, position);
|
||||
path = circlePath(clipPath.rule, position);
|
||||
break;
|
||||
|
||||
case 'ellipse':
|
||||
path = ellipsePath(value, position);
|
||||
path = ellipsePath(clipPath.rule, position);
|
||||
break;
|
||||
|
||||
case 'polygon':
|
||||
path = polygonPath(value, position);
|
||||
path = polygonPath(clipPath.rule, position);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
path = null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
39
packages/core/ui/styling/clip-path-function.ts
Normal file
39
packages/core/ui/styling/clip-path-function.ts
Normal file
@ -0,0 +1,39 @@
|
||||
type ClipPathShape = 'rect' | 'circle' | 'ellipse' | 'polygon' | 'inset';
|
||||
|
||||
interface IClipPathFunction {
|
||||
shape: ClipPathShape;
|
||||
rule: string;
|
||||
}
|
||||
|
||||
export class ClipPathFunction implements IClipPathFunction {
|
||||
private readonly _shape: ClipPathShape;
|
||||
private readonly _rule: string;
|
||||
|
||||
constructor(shape: ClipPathShape, rule: string) {
|
||||
this._shape = shape;
|
||||
this._rule = rule;
|
||||
}
|
||||
|
||||
get shape(): ClipPathShape {
|
||||
return this._shape;
|
||||
}
|
||||
|
||||
get rule(): string {
|
||||
return this._rule;
|
||||
}
|
||||
|
||||
public static equals(value1: ClipPathFunction, value2: ClipPathFunction): boolean {
|
||||
return value1.shape === value2.shape && value1.rule === value2.rule;
|
||||
}
|
||||
|
||||
toJSON(): IClipPathFunction {
|
||||
return {
|
||||
shape: this._shape,
|
||||
rule: this._rule,
|
||||
};
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this._shape}(${this._rule})`;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import { CssAnimationProperty } from '../core/properties';
|
||||
import { KeyframeAnimationInfo, KeyframeDeclaration, KeyframeInfo, UnparsedKeyframe } from '../animation/keyframe-animation';
|
||||
import { timeConverter, animationTimingFunctionConverter } from '../styling/converters';
|
||||
|
||||
import { transformConverter } from '../styling/style-properties';
|
||||
import { transformConverter } from '../styling/css-transform';
|
||||
import { cleanupImportantFlags } from './css-utils';
|
||||
|
||||
const ANIMATION_PROPERTY_HANDLERS = Object.freeze({
|
||||
|
133
packages/core/ui/styling/css-transform.ts
Normal file
133
packages/core/ui/styling/css-transform.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { Pair, Transformation, TransformationType, TransformationValue, TransformFunctionsInfo } from '../animation';
|
||||
import { radiansToDegrees } from '../../utils/number-utils';
|
||||
import { decompose2DTransformMatrix, getTransformMatrix, matrixArrayToCssMatrix, multiplyAffine2d } from '../../matrix';
|
||||
import { hasDuplicates } from '../../utils';
|
||||
|
||||
type TransformationStyleMap = {
|
||||
[key: string]: (value: TransformationValue) => Transformation;
|
||||
};
|
||||
|
||||
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<TransformationType[]>(['rotate', 'rotateX', 'rotateY', 'rotate3d', 'translate', 'translate3d', 'translateX', 'translateY', 'scale', 'scale3d', 'scaleX', 'scaleY']);
|
||||
|
||||
const STYLE_TRANSFORMATION_MAP: TransformationStyleMap = Object.freeze<TransformationStyleMap>({
|
||||
scale: (value: number) => ({ property: 'scale', value }),
|
||||
scale3d: (value: number) => ({ property: 'scale', value }),
|
||||
scaleX: ({ x }: Pair) => ({
|
||||
property: 'scale',
|
||||
value: { x, y: IDENTITY_TRANSFORMATION.scale.y },
|
||||
}),
|
||||
scaleY: ({ y }: Pair) => ({
|
||||
property: 'scale',
|
||||
value: { y, x: IDENTITY_TRANSFORMATION.scale.x },
|
||||
}),
|
||||
translate: (value) => ({ property: 'translate', value }),
|
||||
translate3d: (value) => ({ property: 'translate', value }),
|
||||
translateX: ({ x }: Pair) => ({
|
||||
property: 'translate',
|
||||
value: { x, y: IDENTITY_TRANSFORMATION.translate.y },
|
||||
}),
|
||||
translateY: ({ y }: Pair) => ({
|
||||
property: 'translate',
|
||||
value: { y, x: IDENTITY_TRANSFORMATION.translate.x },
|
||||
}),
|
||||
|
||||
rotate3d: (value) => ({ property: 'rotate', value }),
|
||||
rotateX: (x: number) => ({
|
||||
property: 'rotate',
|
||||
value: {
|
||||
x,
|
||||
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||
z: IDENTITY_TRANSFORMATION.rotate.z,
|
||||
},
|
||||
}),
|
||||
rotateY: (y: number) => ({
|
||||
property: 'rotate',
|
||||
value: {
|
||||
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||
y,
|
||||
z: IDENTITY_TRANSFORMATION.rotate.z,
|
||||
},
|
||||
}),
|
||||
rotate: (z: number) => ({
|
||||
property: 'rotate',
|
||||
value: {
|
||||
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||
z,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
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 = { ...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);
|
||||
}
|
||||
|
||||
function isTransformType(propertyName: string): propertyName is TransformationType {
|
||||
return (TRANSFORMATIONS as string[]).indexOf(propertyName) !== -1;
|
||||
}
|
||||
|
||||
// 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[] {
|
||||
const matches: Transformation[] = [];
|
||||
let match: RegExpExecArray;
|
||||
|
||||
while ((match = TRANSFORM_SPLITTER.exec(text)) !== null) {
|
||||
const property = match[1];
|
||||
|
||||
if (isTransformType(property)) {
|
||||
const value = convertTransformValue(property, match[2]);
|
||||
matches.push(STYLE_TRANSFORMATION_MAP[property](value));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function convertTransformValue(property: TransformationType, rawValue: string): TransformationValue {
|
||||
const values = rawValue.split(',').map(parseFloat);
|
||||
const x = values[0];
|
||||
|
||||
let y = values[1];
|
||||
let z = values[2];
|
||||
|
||||
if (property === 'translate') {
|
||||
y ??= IDENTITY_TRANSFORMATION.translate.y;
|
||||
} else {
|
||||
y ??= x;
|
||||
z ??= y;
|
||||
}
|
||||
|
||||
if (property === 'rotate' || property === 'rotateX' || property === 'rotateY') {
|
||||
return rawValue.slice(-3) === 'rad' ? radiansToDegrees(x) : x;
|
||||
}
|
||||
|
||||
return { x, y, z };
|
||||
}
|
24
packages/core/ui/styling/style-properties.d.ts
vendored
24
packages/core/ui/styling/style-properties.d.ts
vendored
@ -1,17 +1,29 @@
|
||||
import { TransformFunctionsInfo } from '../animation';
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { Color } from '../../color';
|
||||
import { CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
|
||||
import { Style } from './style';
|
||||
import { Font, FontStyleType, FontWeightType, FontVariationSettingsType } from './font';
|
||||
import { Background } from './background';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
import { LinearGradient } from './linear-gradient';
|
||||
|
||||
export namespace FixedLength {
|
||||
export function parse(text: string): CoreTypes.FixedLengthType;
|
||||
export function equals(a: CoreTypes.FixedLengthType, b: CoreTypes.FixedLengthType): boolean;
|
||||
/**
|
||||
* Converts FixedLengthType unit to device pixels.
|
||||
* @param length The FixedLengthType to convert.
|
||||
*/
|
||||
export function toDevicePixels(length: CoreTypes.FixedLengthType): number;
|
||||
export function convertToString(length: CoreTypes.FixedLengthType): string;
|
||||
}
|
||||
|
||||
export namespace Length {
|
||||
export function parse(text: string): CoreTypes.LengthType;
|
||||
export function equals(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
||||
/**
|
||||
* Converts Length unit to device pixels.
|
||||
* @param length The Length to convert.
|
||||
* Converts LengthType unit to device pixels.
|
||||
* @param length The LengthType to convert.
|
||||
* @param auto Value to use for conversion of "auto". By default is Math.NaN.
|
||||
*/
|
||||
export function toDevicePixels(length: CoreTypes.LengthType, auto?: number): number;
|
||||
@ -39,14 +51,12 @@ export const scaleYProperty: CssAnimationProperty<Style, number>;
|
||||
export const translateXProperty: CssAnimationProperty<Style, CoreTypes.dip>;
|
||||
export const translateYProperty: CssAnimationProperty<Style, CoreTypes.dip>;
|
||||
|
||||
export function transformConverter(text: string): TransformFunctionsInfo;
|
||||
|
||||
export const clipPathProperty: CssProperty<Style, string>;
|
||||
export const clipPathProperty: CssProperty<Style, string | ClipPathFunction>;
|
||||
export const colorProperty: InheritedCssProperty<Style, Color>;
|
||||
|
||||
export const backgroundProperty: ShorthandProperty<Style, string>;
|
||||
export const backgroundColorProperty: CssAnimationProperty<Style, Color>;
|
||||
export const backgroundImageProperty: CssProperty<Style, string>;
|
||||
export const backgroundImageProperty: CssProperty<Style, string | LinearGradient>;
|
||||
export const backgroundRepeatProperty: CssProperty<Style, CoreTypes.BackgroundRepeatType>;
|
||||
export const backgroundSizeProperty: CssProperty<Style, string>;
|
||||
export const backgroundPositionProperty: CssProperty<Style, string>;
|
||||
|
@ -1,59 +1,59 @@
|
||||
// Types
|
||||
import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties';
|
||||
import { Style } from './style';
|
||||
import { Transformation, TransformationValue, TransformFunctionsInfo } from '../animation';
|
||||
|
||||
import { Color } from '../../color';
|
||||
import { Font, parseFont, FontStyle, FontStyleType, FontWeight, FontWeightType, FontVariationSettings, FontVariationSettingsType } from './font';
|
||||
import { Background } from './background';
|
||||
import { layout, hasDuplicates } from '../../utils';
|
||||
import { layout } 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, ShadowCSSValues } from './css-shadow';
|
||||
import { transformConverter } from './css-transform';
|
||||
import { ClipPathFunction } from './clip-path-function';
|
||||
|
||||
interface ShorthandPositioning {
|
||||
top: string;
|
||||
right: string;
|
||||
bottom: string;
|
||||
left: string;
|
||||
}
|
||||
|
||||
function equalsCommon(a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean;
|
||||
function equalsCommon(a: CoreTypes.PercentLengthType, b: CoreTypes.PercentLengthType): boolean;
|
||||
function equalsCommon(a: CoreTypes.PercentLengthType, b: CoreTypes.PercentLengthType): boolean {
|
||||
if (a == 'auto') {
|
||||
// tslint:disable-line
|
||||
return b == 'auto'; // tslint:disable-line
|
||||
return b == 'auto';
|
||||
}
|
||||
if (typeof a === 'number') {
|
||||
|
||||
if (b == 'auto') {
|
||||
// tslint:disable-line
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof a === 'number') {
|
||||
if (typeof b === 'number') {
|
||||
return a == b; // tslint:disable-line
|
||||
return a == b;
|
||||
}
|
||||
if (!b) {
|
||||
return false;
|
||||
}
|
||||
return b.unit == 'dip' && a == b.value; // tslint:disable-line
|
||||
}
|
||||
if (b == 'auto') {
|
||||
// tslint:disable-line
|
||||
return false;
|
||||
return b.unit == 'dip' && a == b.value;
|
||||
}
|
||||
|
||||
if (typeof b === 'number') {
|
||||
return a ? a.unit == 'dip' && a.value == b : false; // tslint:disable-line
|
||||
return a ? a.unit == 'dip' && a.value == b : false;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return a.value == b.value && a.unit == b.unit; // tslint:disable-line
|
||||
return a.value == b.value && a.unit == b.unit;
|
||||
}
|
||||
|
||||
function convertToStringCommon(length: CoreTypes.LengthType | CoreTypes.PercentLengthType): string {
|
||||
if (length == 'auto') {
|
||||
// tslint:disable-line
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
@ -71,7 +71,6 @@ function convertToStringCommon(length: CoreTypes.LengthType | CoreTypes.PercentL
|
||||
|
||||
function toDevicePixelsCommon(length: CoreTypes.PercentLengthType, auto: number = Number.NaN, parentAvailableWidth: number = Number.NaN): number {
|
||||
if (length == 'auto') {
|
||||
// tslint:disable-line
|
||||
return auto;
|
||||
}
|
||||
if (typeof length === 'number') {
|
||||
@ -94,9 +93,9 @@ function toDevicePixelsCommon(length: CoreTypes.PercentLengthType, auto: number
|
||||
export namespace PercentLength {
|
||||
export function parse(fromValue: string | CoreTypes.LengthType): CoreTypes.PercentLengthType {
|
||||
if (fromValue == 'auto') {
|
||||
// tslint:disable-line
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
if (typeof fromValue === 'string') {
|
||||
let stringValue = fromValue.trim();
|
||||
const percentIndex = stringValue.indexOf('%');
|
||||
@ -147,12 +146,8 @@ export namespace PercentLength {
|
||||
} = convertToStringCommon;
|
||||
}
|
||||
|
||||
export namespace Length {
|
||||
export function parse(fromValue: string | CoreTypes.LengthType): CoreTypes.LengthType {
|
||||
if (fromValue == 'auto') {
|
||||
// tslint:disable-line
|
||||
return 'auto';
|
||||
}
|
||||
export namespace FixedLength {
|
||||
export function parse(fromValue: string | CoreTypes.FixedLengthType): CoreTypes.FixedLengthType {
|
||||
if (typeof fromValue === 'string') {
|
||||
let stringValue = fromValue.trim();
|
||||
if (stringValue.indexOf('px') !== -1) {
|
||||
@ -175,6 +170,23 @@ export namespace Length {
|
||||
return fromValue;
|
||||
}
|
||||
}
|
||||
export const equals: { (a: CoreTypes.FixedLengthType, b: CoreTypes.FixedLengthType): boolean } = equalsCommon;
|
||||
export const toDevicePixels: {
|
||||
(length: CoreTypes.FixedLengthType): number;
|
||||
} = toDevicePixelsCommon;
|
||||
export const convertToString: {
|
||||
(length: CoreTypes.FixedLengthType): string;
|
||||
} = convertToStringCommon;
|
||||
}
|
||||
|
||||
export namespace Length {
|
||||
export function parse(fromValue: string | CoreTypes.LengthType): CoreTypes.LengthType {
|
||||
if (fromValue == 'auto') {
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
return FixedLength.parse(fromValue);
|
||||
}
|
||||
export const equals: { (a: CoreTypes.LengthType, b: CoreTypes.LengthType): boolean } = equalsCommon;
|
||||
export const toDevicePixels: {
|
||||
(length: CoreTypes.LengthType, auto?: number): number;
|
||||
@ -184,6 +196,185 @@ export namespace Length {
|
||||
} = convertToStringCommon;
|
||||
}
|
||||
|
||||
function isNonNegativeFiniteNumber(value: number): boolean {
|
||||
return isFinite(value) && !isNaN(value) && value >= 0;
|
||||
}
|
||||
|
||||
function parseClipPath(value: string): string | ClipPathFunction {
|
||||
const functionStartIndex = value.indexOf('(');
|
||||
|
||||
if (functionStartIndex > -1) {
|
||||
const functionName = value.substring(0, functionStartIndex).trim();
|
||||
|
||||
switch (functionName) {
|
||||
case 'rect':
|
||||
case 'circle':
|
||||
case 'ellipse':
|
||||
case 'polygon':
|
||||
case 'inset': {
|
||||
const rule: string = value.replace(`${functionName}(`, '').replace(')', '');
|
||||
return new ClipPathFunction(functionName, rule);
|
||||
}
|
||||
default:
|
||||
throw new Error(`Clip-path function ${functionName} is not valid.`);
|
||||
}
|
||||
} else {
|
||||
if (value === 'none') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only shape functions and none are supported for now
|
||||
throw new Error(`Clip-path value ${value} is not valid.`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseShorthandPositioning(value: string): ShorthandPositioning {
|
||||
const 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,
|
||||
};
|
||||
}
|
||||
|
||||
function parseBorderColorPositioning(value: string): ShorthandPositioning {
|
||||
if (value.indexOf('rgb') === 0 || value.indexOf('hsl') === 0) {
|
||||
return {
|
||||
top: value,
|
||||
right: value,
|
||||
bottom: value,
|
||||
left: value,
|
||||
};
|
||||
}
|
||||
|
||||
return parseShorthandPositioning(value);
|
||||
}
|
||||
|
||||
function convertToBackgrounds(value: string): [CssProperty<any, any> | CssAnimationProperty<any, any>, any][] {
|
||||
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: string | LinearGradient;
|
||||
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 convertToMargins(value: string | CoreTypes.PercentLengthType): [CssProperty<Style, CoreTypes.PercentLengthType>, CoreTypes.PercentLengthType][] {
|
||||
if (typeof value === 'string' && value !== 'auto') {
|
||||
const thickness = parseShorthandPositioning(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: string | CoreTypes.LengthType): [CssProperty<Style, CoreTypes.LengthType>, CoreTypes.LengthType][] {
|
||||
if (typeof value === 'string' && value !== 'auto') {
|
||||
const thickness = parseShorthandPositioning(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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function convertToTransform(value: string): [CssAnimationProperty<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.z],
|
||||
[rotateXProperty, rotate.x],
|
||||
[rotateYProperty, rotate.y],
|
||||
];
|
||||
}
|
||||
|
||||
export const minWidthProperty = new CssProperty<Style, CoreTypes.LengthType>({
|
||||
name: 'minWidth',
|
||||
cssName: 'min-width',
|
||||
@ -416,97 +607,6 @@ export const verticalAlignmentProperty = new CssProperty<Style, CoreTypes.Vertic
|
||||
});
|
||||
verticalAlignmentProperty.register(Style);
|
||||
|
||||
interface Thickness {
|
||||
top: string;
|
||||
right: string;
|
||||
bottom: string;
|
||||
left: string;
|
||||
}
|
||||
|
||||
function parseThickness(value: string): Thickness {
|
||||
if (typeof value === 'string') {
|
||||
const 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 | CoreTypes.PercentLengthType): [CssProperty<any, any>, any][] {
|
||||
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(this: void, value: string | CoreTypes.LengthType): [CssProperty<any, any>, any][] {
|
||||
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<Style, number>({
|
||||
name: 'rotate',
|
||||
cssName: 'rotate',
|
||||
@ -555,27 +655,21 @@ export const scaleYProperty = new CssAnimationProperty<Style, number>({
|
||||
});
|
||||
scaleYProperty.register(Style);
|
||||
|
||||
function parseDIPs(value: string): CoreTypes.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, CoreTypes.dip>({
|
||||
export const translateXProperty = new CssAnimationProperty<Style, CoreTypes.FixedLengthType>({
|
||||
name: 'translateX',
|
||||
cssName: 'translateX',
|
||||
defaultValue: 0,
|
||||
valueConverter: parseDIPs,
|
||||
equalityComparer: FixedLength.equals,
|
||||
valueConverter: FixedLength.parse,
|
||||
});
|
||||
translateXProperty.register(Style);
|
||||
|
||||
export const translateYProperty = new CssAnimationProperty<Style, CoreTypes.dip>({
|
||||
export const translateYProperty = new CssAnimationProperty<Style, CoreTypes.FixedLengthType>({
|
||||
name: 'translateY',
|
||||
cssName: 'translateY',
|
||||
defaultValue: 0,
|
||||
valueConverter: parseDIPs,
|
||||
equalityComparer: FixedLength.equals,
|
||||
valueConverter: FixedLength.parse,
|
||||
});
|
||||
translateYProperty.register(Style);
|
||||
|
||||
@ -608,148 +702,6 @@ const transformProperty = new ShorthandProperty<Style, string>({
|
||||
});
|
||||
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: string): [CssProperty<any, any>, any][] {
|
||||
if (value === unsetValue) {
|
||||
value = 'none';
|
||||
}
|
||||
|
||||
const { translate, rotate, scale } = transformConverter(value);
|
||||
|
||||
return [
|
||||
[<any>translateXProperty, translate.x],
|
||||
[<any>translateYProperty, translate.y],
|
||||
|
||||
[<any>scaleXProperty, scale.x],
|
||||
[<any>scaleYProperty, scale.y],
|
||||
|
||||
[<any>rotateProperty, rotate.z],
|
||||
[<any>rotateXProperty, rotate.x],
|
||||
[<any>rotateYProperty, rotate.y],
|
||||
];
|
||||
}
|
||||
|
||||
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 = { ...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[] {
|
||||
const 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 {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [x, y, z] = stringValue.split(',').map(parseFloat);
|
||||
if (property === 'translate') {
|
||||
y ??= IDENTITY_TRANSFORMATION.translate.y;
|
||||
} else {
|
||||
y ??= x;
|
||||
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<Style, string | Color>({
|
||||
name: 'background',
|
||||
@ -768,7 +720,6 @@ export const backgroundInternalProperty = new CssProperty<Style, Background>({
|
||||
});
|
||||
backgroundInternalProperty.register(Style);
|
||||
|
||||
// const pattern: RegExp = /url\(('|")(.*?)\1\)/;
|
||||
export const backgroundImageProperty = new CssProperty<Style, string | LinearGradient>({
|
||||
name: 'backgroundImage',
|
||||
cssName: 'background-image',
|
||||
@ -834,91 +785,6 @@ export const backgroundPositionProperty = new CssProperty<Style, string>({
|
||||
});
|
||||
backgroundPositionProperty.register(Style);
|
||||
|
||||
function convertToBackgrounds(this: void, value: string): [CssProperty<any, any>, any][] {
|
||||
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: string | LinearGradient;
|
||||
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 [
|
||||
[<any>backgroundColorProperty, backgroundColor],
|
||||
[backgroundImageProperty, backgroundImage],
|
||||
[backgroundRepeatProperty, backgroundRepeat],
|
||||
[backgroundPositionProperty, backgroundPosition],
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
[<any>backgroundColorProperty, unsetValue],
|
||||
[backgroundImageProperty, unsetValue],
|
||||
[backgroundRepeatProperty, unsetValue],
|
||||
[backgroundPositionProperty, unsetValue],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function parseBorderColor(value: string): { top: Color; right: Color; bottom: Color; left: Color } {
|
||||
const result: { top: Color; right: Color; bottom: Color; left: Color } = {
|
||||
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<Style, string | Color>({
|
||||
name: 'borderColor',
|
||||
@ -932,13 +798,13 @@ const borderColorProperty = new ShorthandProperty<Style, string | Color>({
|
||||
},
|
||||
converter: function (value) {
|
||||
if (typeof value === 'string') {
|
||||
const fourColors = parseBorderColor(value);
|
||||
const colors = parseBorderColorPositioning(value);
|
||||
|
||||
return [
|
||||
[borderTopColorProperty, fourColors.top],
|
||||
[borderRightColorProperty, fourColors.right],
|
||||
[borderBottomColorProperty, fourColors.bottom],
|
||||
[borderLeftColorProperty, fourColors.left],
|
||||
[borderTopColorProperty, new Color(colors.top)],
|
||||
[borderRightColorProperty, new Color(colors.right)],
|
||||
[borderBottomColorProperty, new Color(colors.bottom)],
|
||||
[borderLeftColorProperty, new Color(colors.left)],
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
@ -1009,13 +875,13 @@ const borderWidthProperty = new ShorthandProperty<Style, string | CoreTypes.Leng
|
||||
},
|
||||
converter: function (value) {
|
||||
if (typeof value === 'string' && value !== 'auto') {
|
||||
const borderWidths = parseThickness(value);
|
||||
const borderWidths = parseShorthandPositioning(value);
|
||||
|
||||
return [
|
||||
[borderTopWidthProperty, borderWidths.top],
|
||||
[borderRightWidthProperty, borderWidths.right],
|
||||
[borderBottomWidthProperty, borderWidths.bottom],
|
||||
[borderLeftWidthProperty, borderWidths.left],
|
||||
[borderTopWidthProperty, Length.parse(borderWidths.top)],
|
||||
[borderRightWidthProperty, Length.parse(borderWidths.right)],
|
||||
[borderBottomWidthProperty, Length.parse(borderWidths.bottom)],
|
||||
[borderLeftWidthProperty, Length.parse(borderWidths.left)],
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
@ -1138,13 +1004,13 @@ const borderRadiusProperty = new ShorthandProperty<Style, string | CoreTypes.Len
|
||||
},
|
||||
converter: function (value) {
|
||||
if (typeof value === 'string') {
|
||||
const borderRadius = parseThickness(value);
|
||||
const borderRadius = parseShorthandPositioning(value);
|
||||
|
||||
return [
|
||||
[borderTopLeftRadiusProperty, borderRadius.top],
|
||||
[borderTopRightRadiusProperty, borderRadius.right],
|
||||
[borderBottomRightRadiusProperty, borderRadius.bottom],
|
||||
[borderBottomLeftRadiusProperty, borderRadius.left],
|
||||
[borderTopLeftRadiusProperty, Length.parse(borderRadius.top)],
|
||||
[borderTopRightRadiusProperty, Length.parse(borderRadius.right)],
|
||||
[borderBottomRightRadiusProperty, Length.parse(borderRadius.bottom)],
|
||||
[borderBottomLeftRadiusProperty, Length.parse(borderRadius.left)],
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
@ -1249,63 +1115,54 @@ const boxShadowProperty = new CssProperty<Style, ShadowCSSValues>({
|
||||
});
|
||||
boxShadowProperty.register(Style);
|
||||
|
||||
function isNonNegativeFiniteNumber(value: number): boolean {
|
||||
return isFinite(value) && !isNaN(value) && value >= 0;
|
||||
}
|
||||
|
||||
const supportedPaths = ['rect', 'circle', 'ellipse', 'polygon', 'inset'];
|
||||
function isClipPathValid(value: string): boolean {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
const functionName = value.substring(0, value.indexOf('(')).trim();
|
||||
|
||||
return supportedPaths.indexOf(functionName) !== -1;
|
||||
}
|
||||
|
||||
export const clipPathProperty = new CssProperty<Style, string>({
|
||||
export const clipPathProperty = new CssProperty<Style, string | ClipPathFunction>({
|
||||
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);
|
||||
},
|
||||
equalityComparer: (value1, value2) => {
|
||||
if (value1 instanceof ClipPathFunction && value2 instanceof ClipPathFunction) {
|
||||
return ClipPathFunction.equals(value1, value2);
|
||||
}
|
||||
return value1 === value2;
|
||||
},
|
||||
valueConverter(value: string | ClipPathFunction) {
|
||||
if (typeof value === 'string') {
|
||||
return parseClipPath(value);
|
||||
}
|
||||
|
||||
target.backgroundInternal = target.backgroundInternal.withClipPath(newValue);
|
||||
return value;
|
||||
},
|
||||
});
|
||||
clipPathProperty.register(Style);
|
||||
|
||||
function isFloatValueConverter(value: string): number {
|
||||
export const zIndexProperty = new CssProperty<Style, number>({
|
||||
name: 'zIndex',
|
||||
cssName: 'z-index',
|
||||
valueConverter: (value: string): number => {
|
||||
const 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 {
|
||||
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<Style, number>({
|
||||
name: 'opacity',
|
||||
cssName: 'opacity',
|
||||
defaultValue: 1,
|
||||
valueConverter: opacityConverter,
|
||||
valueConverter: (value: string): number => {
|
||||
const newValue = parseFloat(value);
|
||||
if (!isNonNegativeFiniteNumber(newValue) || newValue > 1) {
|
||||
throw new Error(`Opacity should be between [0, 1]. Value: ${newValue}`);
|
||||
}
|
||||
|
||||
return newValue;
|
||||
},
|
||||
});
|
||||
opacityProperty.register(Style);
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { CoreTypes } from '../../../core-types';
|
||||
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types';
|
||||
import { ShadowCSSValues } from '../css-shadow';
|
||||
import { StrokeCSSValues } from '../css-stroke';
|
||||
import { ClipPathFunction } from '../clip-path-function';
|
||||
|
||||
export interface CommonLayoutParams {
|
||||
width: number;
|
||||
@ -122,7 +123,7 @@ export class Style extends Observable implements StyleDefinition {
|
||||
public translateX: CoreTypes.dip;
|
||||
public translateY: CoreTypes.dip;
|
||||
|
||||
public clipPath: string;
|
||||
public clipPath: string | ClipPathFunction;
|
||||
public color: Color;
|
||||
public tintColor: Color;
|
||||
public placeholderColor: Color;
|
||||
|
Reference in New Issue
Block a user