diff --git a/apps/ui/src/css/text-shadow-page.ts b/apps/ui/src/css/text-shadow-page.ts index b3cab3f05..57769de3d 100644 --- a/apps/ui/src/css/text-shadow-page.ts +++ b/apps/ui/src/css/text-shadow-page.ts @@ -1,6 +1,15 @@ 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; export function butonTap(args: EventData) { diff --git a/packages/core/ui/styling/css-shadow.ts b/packages/core/ui/styling/css-shadow.ts index 2410474ed..c8820f3b0 100644 --- a/packages/core/ui/styling/css-shadow.ts +++ b/packages/core/ui/styling/css-shadow.ts @@ -1,9 +1,68 @@ import { Color } from '../../color'; +import { Length, zeroLength } from './style-properties'; -export class CSSShadow { - public offsetX: number; - public offsetY: number; - public blurRadius: number; - public spreadRadius: number; - public color: Color; +export interface CSSShadow { + inset: boolean; + offsetX: Length; + offsetY: Length; + blurRadius?: Length; + 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), + }; } diff --git a/packages/core/ui/styling/style-properties.ts b/packages/core/ui/styling/style-properties.ts index d40d68914..49b5e751a 100644 --- a/packages/core/ui/styling/style-properties.ts +++ b/packages/core/ui/styling/style-properties.ts @@ -17,7 +17,7 @@ import { Trace } from '../../trace'; import * as parser from '../../css/parser'; 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 LengthPxUnit = { readonly unit: 'px'; readonly value: px }; @@ -452,53 +452,6 @@ export const verticalAlignmentProperty = new CssProperty -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 { top: string; right: string; @@ -1330,7 +1283,7 @@ const boxShadowProperty = new CssProperty({ target.boxShadow = newValue; }, valueConverter: (value) => { - return parseShadowProperites(value); + return parseCSSShadow(value); }, }); boxShadowProperty.register(Style); diff --git a/packages/core/ui/styling/style/index.ts b/packages/core/ui/styling/style/index.ts index 7d9d715e0..1174c6e1f 100644 --- a/packages/core/ui/styling/style/index.ts +++ b/packages/core/ui/styling/style/index.ts @@ -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 { 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 { CSSShadow } from '../css-shadow'; @@ -159,7 +159,7 @@ export class Style extends Observable implements StyleDefinition { public textAlignment: TextAlignment; public textDecoration: TextDecoration; public textTransform: TextTransform; - public textShadow: TextShadow; + public textShadow: CSSShadow; public whiteSpace: WhiteSpace; public minWidth: Length; diff --git a/packages/core/ui/text-base/index.d.ts b/packages/core/ui/text-base/index.d.ts index a60f8dc36..e3106227e 100644 --- a/packages/core/ui/text-base/index.d.ts +++ b/packages/core/ui/text-base/index.d.ts @@ -3,6 +3,7 @@ import { FormattedString } from './formatted-string'; import { Style } from '../styling/style'; import { Length } from '../styling/style-properties'; import { Property, CssProperty, InheritedCssProperty } from '../core/properties'; +import { CSSShadow } from '../styling/css-shadow'; 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. */ - textShadow: TextShadow; + textShadow: CSSShadow; /** * 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 TextTransform = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase'; 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; export const textDecorationProperty: CssProperty; diff --git a/packages/core/ui/text-base/index.ios.ts b/packages/core/ui/text-base/index.ios.ts index 4e4543dbd..7fcf4e961 100644 --- a/packages/core/ui/text-base/index.ios.ts +++ b/packages/core/ui/text-base/index.ios.ts @@ -1,5 +1,6 @@ // 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 import { Font } from '../styling/font'; @@ -8,7 +9,7 @@ import { Color } from '../../color'; import { FormattedString } from './formatted-string'; import { Span } from './span'; 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 { Trace } from '../../trace'; @@ -152,8 +153,7 @@ export class TextBase extends TextBaseCommon { if (!(value instanceof Font) || !this.formattedText) { let nativeView = this.nativeTextViewProtected; nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView; - const font = value instanceof Font ? value.getUIFont(nativeView.font) : value; - nativeView.font = font; + nativeView.font = value instanceof Font ? value.getUIFont(nativeView.font) : value; } } @@ -189,7 +189,7 @@ export class TextBase extends TextBaseCommon { this._setNativeText(); } - [textShadowProperty.setNative](value: TextShadow) { + [textShadowProperty.setNative](value: CSSShadow) { this._setShadow(value); } @@ -348,7 +348,7 @@ export class TextBase extends TextBaseCommon { } } - _setShadow(value: TextShadow): void { + _setShadow(value: CSSShadow): void { const layer = getShadowLayer(this); if (!layer) { 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.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 // if (!(this.nativeTextViewProtected instanceof UITextView)) { // 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 { let layer: CALayer; const name = 'shadow-layer'; diff --git a/packages/core/ui/text-base/text-base-common.ts b/packages/core/ui/text-base/text-base-common.ts index 9755cc1b7..d3be1d299 100644 --- a/packages/core/ui/text-base/text-base-common.ts +++ b/packages/core/ui/text-base/text-base-common.ts @@ -9,13 +9,13 @@ import { Span } from './span'; import { View } from '../core/view'; import { Property, CssProperty, InheritedCssProperty, makeValidator, makeParser } from '../core/properties'; import { Style } from '../styling/style'; -import { Length, parseShadowProperites } from '../styling/style-properties'; +import { Length } from '../styling/style-properties'; import { Observable } from '../../data/observable'; import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from './text-base-interfaces'; import { TextBase as TextBaseDefinition } from '.'; import { Color } from '../../color'; -import { CSSShadow } from '../styling/css-shadow'; +import { CSSShadow, parseCSSShadow } from '../styling/css-shadow'; export * from './text-base-interfaces'; @@ -115,10 +115,10 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition this.style.textTransform = value; } - get textShadow(): TextShadow { + get textShadow(): CSSShadow { return this.style.textShadow; } - set textShadow(value: TextShadow) { + set textShadow(value: CSSShadow) { this.style.textShadow = value; } @@ -273,7 +273,7 @@ export const textShadowProperty = new CssProperty({ cssName: 'text-shadow', affectsLayout: global.isIOS, valueConverter: (value) => { - return parseShadowProperites(value); + return parseCSSShadow(value); }, }); textShadowProperty.register(Style);