mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00
feat: improved css-shadow parser
This commit is contained in:

committed by
Nathan Walker

parent
673387cf99
commit
d2f50e50bb
@ -1,6 +1,15 @@
|
|||||||
import { EventData, TextBase } from '@nativescript/core';
|
import { EventData, TextBase } from '@nativescript/core';
|
||||||
|
|
||||||
const possibleValues = ['2 10 4 rgb(255, 100, 100)', '2 10 2 rgba(10, 10, 10, 0.5)', '1 1 1 #55a', '2 2 2 #aaa', '0 0 1 yellow', '-1 -1 1 #aaa', ''];
|
// prettier-ignore
|
||||||
|
const possibleValues = [
|
||||||
|
'2 10 4 rgb(255, 100, 100)',
|
||||||
|
'2 10 2 rgba(10, 10, 10, 0.5)',
|
||||||
|
'1 1 1 #55a',
|
||||||
|
'2 2 2 #333',
|
||||||
|
'0 0 1 yellow',
|
||||||
|
'-1 -1 1 #333',
|
||||||
|
''
|
||||||
|
];
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
export function butonTap(args: EventData) {
|
export function butonTap(args: EventData) {
|
||||||
|
@ -1,9 +1,68 @@
|
|||||||
import { Color } from '../../color';
|
import { Color } from '../../color';
|
||||||
|
import { Length, zeroLength } from './style-properties';
|
||||||
|
|
||||||
export class CSSShadow {
|
export interface CSSShadow {
|
||||||
public offsetX: number;
|
inset: boolean;
|
||||||
public offsetY: number;
|
offsetX: Length;
|
||||||
public blurRadius: number;
|
offsetY: Length;
|
||||||
public spreadRadius: number;
|
blurRadius?: Length;
|
||||||
public color: Color;
|
spreadRadius?: Length;
|
||||||
|
color: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches whitespace except if the whitespace is contained in parenthesis - ex. rgb(a), hsl color.
|
||||||
|
*/
|
||||||
|
const PARTS_RE = /\s(?![^(]*\))/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a Length value with or without a unit
|
||||||
|
*/
|
||||||
|
const LENGTH_RE = /^-?[0-9]+[a-zA-Z%]*?$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the value is a Length or 0
|
||||||
|
*/
|
||||||
|
const isLength = (v) => v === '0' || LENGTH_RE.test(v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a string into a CSSShadow
|
||||||
|
* Supports any valid css box/text shadow combination.
|
||||||
|
*
|
||||||
|
* inspired by https://github.com/jxnblk/css-box-shadow/blob/master/index.js (MIT License)
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
export function parseCSSShadow(value: string): CSSShadow {
|
||||||
|
const parts = value.split(PARTS_RE);
|
||||||
|
const inset = parts.includes('inset');
|
||||||
|
const first = parts[0];
|
||||||
|
const last = parts[parts.length - 1];
|
||||||
|
|
||||||
|
let colorRaw = 'black';
|
||||||
|
if (!isLength(first) && first !== 'inset') {
|
||||||
|
colorRaw = first;
|
||||||
|
} else if (!isLength(last)) {
|
||||||
|
colorRaw = last;
|
||||||
|
}
|
||||||
|
const nums = parts
|
||||||
|
.filter((n) => n !== 'inset')
|
||||||
|
.filter((n) => n !== colorRaw)
|
||||||
|
.map((val) => {
|
||||||
|
try {
|
||||||
|
return Length.parse(val);
|
||||||
|
} catch (err) {
|
||||||
|
return zeroLength;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const [offsetX, offsetY, blurRadius, spreadRadius] = nums;
|
||||||
|
|
||||||
|
return {
|
||||||
|
inset,
|
||||||
|
offsetX: offsetX,
|
||||||
|
offsetY: offsetY,
|
||||||
|
blurRadius: blurRadius,
|
||||||
|
spreadRadius: spreadRadius,
|
||||||
|
color: new Color(colorRaw),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import { Trace } from '../../trace';
|
|||||||
|
|
||||||
import * as parser from '../../css/parser';
|
import * as parser from '../../css/parser';
|
||||||
import { LinearGradient } from './linear-gradient';
|
import { LinearGradient } from './linear-gradient';
|
||||||
import { CSSShadow } from './css-shadow';
|
import { CSSShadow, parseCSSShadow } from './css-shadow';
|
||||||
|
|
||||||
export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip };
|
export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip };
|
||||||
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
export type LengthPxUnit = { readonly unit: 'px'; readonly value: px };
|
||||||
@ -452,53 +452,6 @@ export const verticalAlignmentProperty = new CssProperty<Style, VerticalAlignmen
|
|||||||
});
|
});
|
||||||
verticalAlignmentProperty.register(Style);
|
verticalAlignmentProperty.register(Style);
|
||||||
|
|
||||||
export function parseShadowProperites(value: string): CSSShadow {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
let arr;
|
|
||||||
let colorRaw = 'black';
|
|
||||||
if (value.indexOf('rgb') > -1) {
|
|
||||||
arr = value.split(' ');
|
|
||||||
colorRaw = arr.pop();
|
|
||||||
} else {
|
|
||||||
arr = value.split(/[ ,]+/);
|
|
||||||
colorRaw = arr.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let offsetX = 0;
|
|
||||||
let offsetY = 0;
|
|
||||||
let blurRadius = 0; // not currently in use
|
|
||||||
let spreadRadius = 0; // maybe rename this to just radius
|
|
||||||
let color: Color = new Color(colorRaw);
|
|
||||||
|
|
||||||
if (arr.length === 1) {
|
|
||||||
Trace.write('Expected 3, 4 or 5 parameters. Actual: ' + value, Trace.categories.Error, Trace.messageType.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arr.length > 1) {
|
|
||||||
offsetX = parseFloat(arr[0]);
|
|
||||||
offsetY = parseFloat(arr[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arr.length > 2) {
|
|
||||||
blurRadius = parseFloat(arr[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arr.length > 3) {
|
|
||||||
spreadRadius = parseFloat(arr[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
offsetX: offsetX,
|
|
||||||
offsetY: offsetY,
|
|
||||||
blurRadius: blurRadius,
|
|
||||||
spreadRadius: spreadRadius,
|
|
||||||
color: color,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Thickness {
|
interface Thickness {
|
||||||
top: string;
|
top: string;
|
||||||
right: string;
|
right: string;
|
||||||
@ -1330,7 +1283,7 @@ const boxShadowProperty = new CssProperty<Style, CSSShadow>({
|
|||||||
target.boxShadow = newValue;
|
target.boxShadow = newValue;
|
||||||
},
|
},
|
||||||
valueConverter: (value) => {
|
valueConverter: (value) => {
|
||||||
return parseShadowProperites(value);
|
return parseCSSShadow(value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
boxShadowProperty.register(Style);
|
boxShadowProperty.register(Style);
|
||||||
|
@ -10,7 +10,7 @@ import { Observable } from '../../../data/observable';
|
|||||||
|
|
||||||
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from '../../layouts/flexbox-layout';
|
import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from '../../layouts/flexbox-layout';
|
||||||
import { Trace } from '../../../trace';
|
import { Trace } from '../../../trace';
|
||||||
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace, TextShadow } from '../../text-base';
|
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from '../../text-base';
|
||||||
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types';
|
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types';
|
||||||
import { CSSShadow } from '../css-shadow';
|
import { CSSShadow } from '../css-shadow';
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ export class Style extends Observable implements StyleDefinition {
|
|||||||
public textAlignment: TextAlignment;
|
public textAlignment: TextAlignment;
|
||||||
public textDecoration: TextDecoration;
|
public textDecoration: TextDecoration;
|
||||||
public textTransform: TextTransform;
|
public textTransform: TextTransform;
|
||||||
public textShadow: TextShadow;
|
public textShadow: CSSShadow;
|
||||||
public whiteSpace: WhiteSpace;
|
public whiteSpace: WhiteSpace;
|
||||||
|
|
||||||
public minWidth: Length;
|
public minWidth: Length;
|
||||||
|
9
packages/core/ui/text-base/index.d.ts
vendored
9
packages/core/ui/text-base/index.d.ts
vendored
@ -3,6 +3,7 @@ import { FormattedString } from './formatted-string';
|
|||||||
import { Style } from '../styling/style';
|
import { Style } from '../styling/style';
|
||||||
import { Length } from '../styling/style-properties';
|
import { Length } from '../styling/style-properties';
|
||||||
import { Property, CssProperty, InheritedCssProperty } from '../core/properties';
|
import { Property, CssProperty, InheritedCssProperty } from '../core/properties';
|
||||||
|
import { CSSShadow } from '../styling/css-shadow';
|
||||||
|
|
||||||
export class TextBase extends View implements AddChildFromBuilder {
|
export class TextBase extends View implements AddChildFromBuilder {
|
||||||
/**
|
/**
|
||||||
@ -54,7 +55,7 @@ export class TextBase extends View implements AddChildFromBuilder {
|
|||||||
/**
|
/**
|
||||||
* Gets or sets text shadow style property.
|
* Gets or sets text shadow style property.
|
||||||
*/
|
*/
|
||||||
textShadow: TextShadow;
|
textShadow: CSSShadow;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or sets white space style property.
|
* Gets or sets white space style property.
|
||||||
@ -125,12 +126,6 @@ export type WhiteSpace = 'initial' | 'normal' | 'nowrap';
|
|||||||
export type TextAlignment = 'initial' | 'left' | 'center' | 'right';
|
export type TextAlignment = 'initial' | 'left' | 'center' | 'right';
|
||||||
export type TextTransform = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase';
|
export type TextTransform = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase';
|
||||||
export type TextDecoration = 'none' | 'underline' | 'line-through' | 'underline line-through';
|
export type TextDecoration = 'none' | 'underline' | 'line-through' | 'underline line-through';
|
||||||
export type TextShadow = {
|
|
||||||
offsetX: Length;
|
|
||||||
offsetY: Length;
|
|
||||||
blurRadius: Length;
|
|
||||||
color: Color;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const textAlignmentProperty: InheritedCssProperty<Style, TextAlignment>;
|
export const textAlignmentProperty: InheritedCssProperty<Style, TextAlignment>;
|
||||||
export const textDecorationProperty: CssProperty<Style, TextDecoration>;
|
export const textDecorationProperty: CssProperty<Style, TextDecoration>;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// Types
|
// Types
|
||||||
import { TextDecoration, TextAlignment, TextTransform, TextShadow, getClosestPropertyValue } from './text-base-common';
|
import { TextDecoration, TextAlignment, TextTransform, getClosestPropertyValue } from './text-base-common';
|
||||||
|
import { CSSShadow } from '../styling/css-shadow';
|
||||||
|
|
||||||
// Requires
|
// Requires
|
||||||
import { Font } from '../styling/font';
|
import { Font } from '../styling/font';
|
||||||
@ -8,7 +9,7 @@ import { Color } from '../../color';
|
|||||||
import { FormattedString } from './formatted-string';
|
import { FormattedString } from './formatted-string';
|
||||||
import { Span } from './span';
|
import { Span } from './span';
|
||||||
import { colorProperty, fontInternalProperty, Length, VerticalAlignment } from '../styling/style-properties';
|
import { colorProperty, fontInternalProperty, Length, VerticalAlignment } from '../styling/style-properties';
|
||||||
import { isString, isDefined, isNullOrUndefined } from '../../utils/types';
|
import { isString, isNullOrUndefined } from '../../utils/types';
|
||||||
import { iOSNativeHelper } from '../../utils';
|
import { iOSNativeHelper } from '../../utils';
|
||||||
import { Trace } from '../../trace';
|
import { Trace } from '../../trace';
|
||||||
|
|
||||||
@ -152,8 +153,7 @@ export class TextBase extends TextBaseCommon {
|
|||||||
if (!(value instanceof Font) || !this.formattedText) {
|
if (!(value instanceof Font) || !this.formattedText) {
|
||||||
let nativeView = this.nativeTextViewProtected;
|
let nativeView = this.nativeTextViewProtected;
|
||||||
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
||||||
const font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
|
nativeView.font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
|
||||||
nativeView.font = font;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ export class TextBase extends TextBaseCommon {
|
|||||||
this._setNativeText();
|
this._setNativeText();
|
||||||
}
|
}
|
||||||
|
|
||||||
[textShadowProperty.setNative](value: TextShadow) {
|
[textShadowProperty.setNative](value: CSSShadow) {
|
||||||
this._setShadow(value);
|
this._setShadow(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ export class TextBase extends TextBaseCommon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setShadow(value: TextShadow): void {
|
_setShadow(value: CSSShadow): void {
|
||||||
const layer = getShadowLayer(this);
|
const layer = getShadowLayer(this);
|
||||||
if (!layer) {
|
if (!layer) {
|
||||||
Trace.write('text-shadow not applied, no layer.', Trace.categories.Style, Trace.messageType.info);
|
Trace.write('text-shadow not applied, no layer.', Trace.categories.Style, Trace.messageType.info);
|
||||||
@ -377,7 +377,7 @@ export class TextBase extends TextBaseCommon {
|
|||||||
// layer.shadowOffset = CGSizeMake(Length.toDevicePixels(value.offsetX), Length.toDevicePixels(value.offsetY));
|
// layer.shadowOffset = CGSizeMake(Length.toDevicePixels(value.offsetX), Length.toDevicePixels(value.offsetY));
|
||||||
layer.masksToBounds = false;
|
layer.masksToBounds = false;
|
||||||
|
|
||||||
// NOTE: generally should not need shouldRaterize
|
// NOTE: generally should not need shouldRasterize
|
||||||
// however for various detailed animation work which involves text-shadow applicable layers, we may want to give users the control of enabling this with text-shadow
|
// however for various detailed animation work which involves text-shadow applicable layers, we may want to give users the control of enabling this with text-shadow
|
||||||
// if (!(this.nativeTextViewProtected instanceof UITextView)) {
|
// if (!(this.nativeTextViewProtected instanceof UITextView)) {
|
||||||
// layer.shouldRasterize = true;
|
// layer.shouldRasterize = true;
|
||||||
@ -504,6 +504,7 @@ export function getTransformedText(text: string, textTransform: TextTransform):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: clean up nesting & logs
|
||||||
export function getShadowLayer(view: TextBase): CALayer {
|
export function getShadowLayer(view: TextBase): CALayer {
|
||||||
let layer: CALayer;
|
let layer: CALayer;
|
||||||
const name = 'shadow-layer';
|
const name = 'shadow-layer';
|
||||||
|
@ -9,13 +9,13 @@ import { Span } from './span';
|
|||||||
import { View } from '../core/view';
|
import { View } from '../core/view';
|
||||||
import { Property, CssProperty, InheritedCssProperty, makeValidator, makeParser } from '../core/properties';
|
import { Property, CssProperty, InheritedCssProperty, makeValidator, makeParser } from '../core/properties';
|
||||||
import { Style } from '../styling/style';
|
import { Style } from '../styling/style';
|
||||||
import { Length, parseShadowProperites } from '../styling/style-properties';
|
import { Length } from '../styling/style-properties';
|
||||||
import { Observable } from '../../data/observable';
|
import { Observable } from '../../data/observable';
|
||||||
|
|
||||||
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from './text-base-interfaces';
|
import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from './text-base-interfaces';
|
||||||
import { TextBase as TextBaseDefinition } from '.';
|
import { TextBase as TextBaseDefinition } from '.';
|
||||||
import { Color } from '../../color';
|
import { Color } from '../../color';
|
||||||
import { CSSShadow } from '../styling/css-shadow';
|
import { CSSShadow, parseCSSShadow } from '../styling/css-shadow';
|
||||||
|
|
||||||
export * from './text-base-interfaces';
|
export * from './text-base-interfaces';
|
||||||
|
|
||||||
@ -115,10 +115,10 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition
|
|||||||
this.style.textTransform = value;
|
this.style.textTransform = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get textShadow(): TextShadow {
|
get textShadow(): CSSShadow {
|
||||||
return this.style.textShadow;
|
return this.style.textShadow;
|
||||||
}
|
}
|
||||||
set textShadow(value: TextShadow) {
|
set textShadow(value: CSSShadow) {
|
||||||
this.style.textShadow = value;
|
this.style.textShadow = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +273,7 @@ export const textShadowProperty = new CssProperty<Style, string | CSSShadow>({
|
|||||||
cssName: 'text-shadow',
|
cssName: 'text-shadow',
|
||||||
affectsLayout: global.isIOS,
|
affectsLayout: global.isIOS,
|
||||||
valueConverter: (value) => {
|
valueConverter: (value) => {
|
||||||
return parseShadowProperites(value);
|
return parseCSSShadow(value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
textShadowProperty.register(Style);
|
textShadowProperty.register(Style);
|
||||||
|
Reference in New Issue
Block a user