From 1c80dabd3cd02bb47bd03e50ebe4f07880ddc1ea Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 23 Feb 2025 21:40:42 +0200 Subject: [PATCH 1/7] feat(core): Added basic support for CSS wide keywords --- packages/core/core-types/index.ts | 24 +++---- packages/core/ui/core/properties/index.ts | 69 +++++++++++-------- packages/core/ui/core/view/index.android.ts | 13 ++-- .../core/ui/layouts/flexbox-layout/index.d.ts | 3 +- packages/core/ui/styling/css-utils.ts | 53 +++++++------- packages/core/ui/styling/font-common.ts | 2 +- packages/core/ui/styling/font-interfaces.ts | 6 +- packages/core/ui/styling/style-properties.ts | 12 ++-- packages/core/ui/text-base/index.android.ts | 12 ++-- .../core/ui/text-base/text-base-common.ts | 8 +-- 10 files changed, 110 insertions(+), 92 deletions(-) diff --git a/packages/core/core-types/index.ts b/packages/core/core-types/index.ts index b244998e19..195a6121f4 100644 --- a/packages/core/core-types/index.ts +++ b/packages/core/core-types/index.ts @@ -7,6 +7,8 @@ import { makeValidator, makeParser } from '../ui/core/properties'; import { CubicBezierAnimationCurve } from '../ui/animation/animation-interfaces'; export namespace CoreTypes { + export type CSSWideKeywords = 'initial' | 'inherit' | 'unset' | 'revert'; + /** * Denotes a length number that is in device independent pixel units. */ @@ -29,7 +31,7 @@ export namespace CoreTypes { export type LengthPxUnit = { readonly unit: 'px'; readonly value: px }; export type LengthPercentUnit = { readonly unit: '%'; readonly value: percent }; - export type FixedLengthType = dip | LengthDipUnit | LengthPxUnit; + export type FixedLengthType = dip | LengthDipUnit | LengthPxUnit | CSSWideKeywords; export type LengthType = 'auto' | FixedLengthType; export type PercentLengthType = 'auto' | FixedLengthType | LengthPercentUnit; @@ -66,7 +68,7 @@ export namespace CoreTypes { export const send = 'send'; } - export type TextAlignmentType = 'initial' | 'left' | 'center' | 'right' | 'justify'; + export type TextAlignmentType = 'left' | 'center' | 'right' | 'justify' | CSSWideKeywords; export namespace TextAlignment { export const left = 'left'; export const center = 'center'; @@ -74,14 +76,14 @@ export namespace CoreTypes { export const justify = 'justify'; } - export type TextDecorationType = 'none' | 'underline' | 'line-through' | 'underline line-through'; + export type TextDecorationType = 'none' | 'underline' | 'line-through' | 'underline line-through' | CSSWideKeywords; export namespace TextDecoration { export const none = 'none'; export const underline = 'underline'; export const lineThrough = 'line-through'; } - export type TextTransformType = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase'; + export type TextTransformType = 'none' | 'capitalize' | 'uppercase' | 'lowercase' | CSSWideKeywords; export namespace TextTransform { export const none = 'none'; export const capitalize = 'capitalize'; @@ -89,18 +91,16 @@ export namespace CoreTypes { export const lowercase = 'lowercase'; } - export type WhiteSpaceType = 'initial' | 'normal' | 'nowrap'; + export type WhiteSpaceType = 'normal' | 'nowrap' | CSSWideKeywords; export namespace WhiteSpace { export const normal = 'normal'; export const nowrap = 'nowrap'; } - export type TextOverflowType = 'clip' | 'ellipsis' | 'initial' | 'unset'; + export type TextOverflowType = 'clip' | 'ellipsis' | CSSWideKeywords; export namespace TextOverflow { export const clip = 'clip'; export const ellipsis = 'ellipsis'; - export const initial = 'initial'; - export const unset = 'unset'; } export type MaxLinesType = number; @@ -118,7 +118,7 @@ export namespace CoreTypes { export const unknown = 'unknown'; } - export type HorizontalAlignmentType = 'left' | 'center' | 'right' | 'stretch'; + export type HorizontalAlignmentType = 'left' | 'center' | 'right' | 'stretch' | CSSWideKeywords; export namespace HorizontalAlignment { export const left = 'left'; export const center = 'center'; @@ -128,7 +128,7 @@ export namespace CoreTypes { export const parse = makeParser(isValid); } - export type VerticalAlignmentType = 'top' | 'middle' | 'bottom' | 'stretch'; + export type VerticalAlignmentType = 'top' | 'middle' | 'bottom' | 'stretch' | CSSWideKeywords; export namespace VerticalAlignment { export const top = 'top'; export const middle = 'middle'; @@ -159,7 +159,7 @@ export namespace CoreTypes { export const fill: ImageStretchType = 'fill'; } - export type VisibilityType = 'visible' | 'hidden' | 'collapse' | 'collapsed'; + export type VisibilityType = 'visible' | 'hidden' | 'collapse' | 'collapsed' | CSSWideKeywords; export namespace Visibility { export const visible: VisibilityType = 'visible'; export const collapse: VisibilityType = 'collapse'; @@ -254,7 +254,7 @@ export namespace CoreTypes { export const black: string = '900'; } - export type BackgroundRepeatType = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat'; + export type BackgroundRepeatType = 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat' | CSSWideKeywords; export namespace BackgroundRepeat { export const repeat: BackgroundRepeatType = 'repeat'; export const repeatX: BackgroundRepeatType = 'repeat-x'; diff --git a/packages/core/ui/core/properties/index.ts b/packages/core/ui/core/properties/index.ts index 6a85d040f9..07fe63c665 100644 --- a/packages/core/ui/core/properties/index.ts +++ b/packages/core/ui/core/properties/index.ts @@ -7,6 +7,7 @@ import { Trace } from '../../../trace'; import { Style } from '../../styling/style'; import { profile } from '../../../profiling'; +import { CoreTypes } from '../../enums'; /** * Value specifying that Property should be set to its initial value. @@ -53,6 +54,14 @@ const cssSymbolPropertyMap = {}; const inheritableProperties = new Array>(); const inheritableCssProperties = new Array>(); +const enum ValueSource { + Default = 0, + Inherited = 1, + Css = 2, + Local = 3, + Keyframe = 4, +} + function print(map) { const symbols = Object.getOwnPropertySymbols(map); for (const symbol of symbols) { @@ -63,19 +72,19 @@ function print(map) { } } +function isCssUnsetValue(value: any): boolean { + return value === 'unset' || value === 'revert'; +} + +function isResetValue(value: any): boolean { + return value === unsetValue || value === '' || value === 'initial' || value === 'inherit' || isCssUnsetValue(value); +} + export function _printUnregisteredProperties(): void { print(symbolPropertyMap); print(cssSymbolPropertyMap); } -const enum ValueSource { - Default = 0, - Inherited = 1, - Css = 2, - Local = 3, - Keyframe = 4, -} - export function _getProperties(): Property[] { return getPropertiesFromMap(symbolPropertyMap) as Property[]; } @@ -96,6 +105,10 @@ export function isCssVariableExpression(value: string) { return value.includes('var(--'); } +export function isCssWideKeyword(value: any): value is CoreTypes.CSSWideKeywords { + return value === 'initial' || value === 'inherit' || isCssUnsetValue(value); +} + export function _evaluateCssVariableExpression(view: ViewBase, cssName: string, value: string): string { if (typeof value !== 'string') { return value; @@ -236,7 +249,7 @@ export class Property implements TypedPropertyDescriptor< const property = this; this.set = function (this: T, boxedValue: U): void { - const reset = boxedValue === unsetValue; + const reset = isResetValue(boxedValue); let value: U; let wrapped: boolean; if (reset) { @@ -419,7 +432,7 @@ export class CoercibleProperty extends Property imp }; this.set = function (this: T, boxedValue: U): void { - const reset = boxedValue === unsetValue; + const reset = isResetValue(boxedValue); let value: U; let wrapped: boolean; if (reset) { @@ -528,11 +541,11 @@ export class InheritedProperty extends Property imp let unboxedValue: U; let newValueSource: number; - if (value === unsetValue) { - // If unsetValue - we want to reset the property. + if (isResetValue(value)) { const parent: ViewBase = that.parent; - // If we have parent and it has non-default value we use as our inherited value. - if (parent && parent[sourceKey] !== ValueSource.Default) { + + // If value is not initial or unset and view has a parent that has non-default value, use it as the reset value. + if (value !== 'initial' && parent && parent[sourceKey] !== ValueSource.Default) { unboxedValue = parent[name]; newValueSource = ValueSource.Inherited; } else { @@ -659,8 +672,9 @@ export class CssProperty implements CssProperty { return; } - const reset = newValue === unsetValue || newValue === ''; + const reset = isResetValue(newValue); let value: U; + if (reset) { value = defaultValue; delete this[sourceKey]; @@ -744,8 +758,9 @@ export class CssProperty implements CssProperty { return; } - const reset = newValue === unsetValue || newValue === ''; + const reset = isResetValue(newValue); let value: U; + if (reset) { value = defaultValue; delete this[sourceKey]; @@ -942,7 +957,7 @@ export class CssAnimationProperty implements CssAnimationPro const oldValue = this[computedValue]; const oldSource = this[computedSource]; const wasSet = oldSource !== ValueSource.Default; - const reset = boxedValue === unsetValue || boxedValue === ''; + const reset = isResetValue(boxedValue); if (reset) { this[symbol] = unsetValue; @@ -1082,9 +1097,9 @@ export class InheritedCssProperty extends CssProperty const getDefault = this.getDefault; const setNative = this.setNative; const defaultValueKey = this.defaultValueKey; - const eventName = propertyName + 'Change'; - let defaultValue: U = options.defaultValue; + const defaultValue: U = options.defaultValue; + let affectsLayout: boolean = options.affectsLayout; let equalityComparer = options.equalityComparer; let valueChanged = options.valueChanged; @@ -1112,12 +1127,12 @@ export class InheritedCssProperty extends CssProperty const view = this.viewRef.get(); if (!view) { Trace.write(`${boxedValue} not set to view's property because ".viewRef" is cleared`, Trace.categories.Style, Trace.messageType.warn); - return; } - const reset = boxedValue === unsetValue || boxedValue === ''; + const reset = isResetValue(boxedValue); const currentValueSource: number = this[sourceKey] || ValueSource.Default; + if (reset) { // If we want to reset cssValue and we have localValue - return; if (valueSource === ValueSource.Css && currentValueSource === ValueSource.Local) { @@ -1132,13 +1147,13 @@ export class InheritedCssProperty extends CssProperty const oldValue: U = key in this ? this[key] : defaultValue; let value: U; let unsetNativeValue = false; + if (reset) { - // If unsetValue - we want to reset this property. - const parent = view.parent; - const style = parent ? parent.style : null; - // If we have parent and it has non-default value we use as our inherited value. - if (style && style[sourceKey] > ValueSource.Default) { - value = style[propertyName]; + const parentStyle = view.parent ? view.parent.style : null; + + // If value is not initial or unset and view has a parent that has non-default value, use it as the reset value. + if (boxedValue !== 'initial' && parentStyle && parentStyle[sourceKey] > ValueSource.Default) { + value = parentStyle[propertyName]; this[sourceKey] = ValueSource.Inherited; this[key] = value; } else { diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 3e3178e762..3fc09cf5e4 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -1,13 +1,13 @@ // Definitions. -import type { Point, CustomLayoutView as CustomLayoutViewDefinition, Position } from '.'; +import { type Point, type CustomLayoutView as CustomLayoutViewDefinition, type Position } from '.'; import type { GestureTypes, GestureEventData } from '../../gestures'; -// Types. import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; import { ShowModalOptions, hiddenProperty } from '../view-base'; +import { isCssWideKeyword } from '../properties'; import { EventData } from '../../../data/observable'; import { perspectiveProperty, visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty, minWidthProperty, minHeightProperty, widthProperty, heightProperty, marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty, rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty } from '../../styling/style-properties'; @@ -16,14 +16,13 @@ import { CoreTypes } from '../../../core-types'; import { Background, BackgroundClearFlags, refreshBorderDrawable } from '../../styling/background'; import { profile } from '../../../profiling'; import { topmost } from '../../frame/frame-stack'; -import { Device, Screen } from '../../../platform'; +import { Screen } from '../../../platform'; import { AndroidActivityBackPressedEventData, Application } from '../../../application'; -import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; -import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent, isAccessibilityServiceEnabled, sendAccessibilityEvent, updateAccessibilityProperties, updateContentDescription, AccessibilityState } from '../../../accessibility'; +import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; +import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent, updateAccessibilityProperties, updateContentDescription, AccessibilityState } from '../../../accessibility'; import * as Utils from '../../../utils'; import { SDK_VERSION } from '../../../utils/constants'; import { BoxShadow } from '../../styling/box-shadow'; -import { _setAndroidFragmentTransitions, _getAnimatedEntries, _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, addNativeTransitionListener } from '../../frame/fragment.transitions'; import { NativeScriptAndroidView } from '../../utils'; export * from './view-common'; @@ -1366,7 +1365,7 @@ function createNativePercentLengthProperty(options: NativePercentLengthPropertyO setPercent = options.setPercent || percentNotSupported; options = null; } - if (length == 'auto' || length == null) { + if (length == 'auto' || length == null || isCssWideKeyword(length)) { // tslint:disable-line setPixels(this.nativeViewProtected, auto); } else if (typeof length === 'number') { diff --git a/packages/core/ui/layouts/flexbox-layout/index.d.ts b/packages/core/ui/layouts/flexbox-layout/index.d.ts index 012fa4b4fe..d73626d92b 100644 --- a/packages/core/ui/layouts/flexbox-layout/index.d.ts +++ b/packages/core/ui/layouts/flexbox-layout/index.d.ts @@ -2,6 +2,7 @@ import { LayoutBase } from '../layout-base'; import { Style } from '../../styling/style'; import { CssProperty } from '../../core/properties'; import { View } from '../../core/view'; +import { CoreTypes } from '../../enums'; export type FlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse'; export type FlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse'; @@ -9,7 +10,7 @@ export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-betwe export type AlignItems = 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch'; export type AlignContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'stretch'; export type FlexFlow = `${FlexDirection} ${FlexWrap}`; -export type Flex = number | 'initial' | 'auto' | 'none' | `${FlexGrow} ${FlexShrink}`; +export type Flex = number | 'auto' | 'none' | `${FlexGrow} ${FlexShrink}` | CoreTypes.CSSWideKeywords; /** * A flex order integer. diff --git a/packages/core/ui/styling/css-utils.ts b/packages/core/ui/styling/css-utils.ts index b5bd49f4a9..409f5509f6 100644 --- a/packages/core/ui/styling/css-utils.ts +++ b/packages/core/ui/styling/css-utils.ts @@ -40,33 +40,34 @@ export function parseCSSShorthand(value: string): { const parts = value.trim().split(PARTS_RE); const first = parts[0]; - if (['', 'none', 'unset'].includes(first)) { + if (['', 'none'].includes(first)) { return null; - } else { - const invalidColors = ['inset', 'unset']; - const inset = parts.includes('inset'); - const last = parts[parts.length - 1]; - let color = 'black'; - if (first && !isLength(first) && !invalidColors.includes(first)) { - color = first; - } else if (last && !isLength(last) && !invalidColors.includes(last)) { - color = last; - } + } - const values = parts - .filter((n) => !invalidColors.includes(n)) - .filter((n) => n !== color) - .map((val) => { - try { - return Length.parse(val); - } catch (err) { - return CoreTypes.zeroLength; - } - }); - return { - inset, - color, - values, - }; + const invalidColors = ['inset', 'unset']; + const inset = parts.includes('inset'); + const last = parts[parts.length - 1]; + let color = 'black'; + if (first && !isLength(first) && !invalidColors.includes(first)) { + color = first; + } else if (last && !isLength(last) && !invalidColors.includes(last)) { + color = last; } + + const values = parts + .filter((n) => !invalidColors.includes(n)) + .filter((n) => n !== color) + .map((val) => { + try { + return Length.parse(val); + } catch (err) { + return CoreTypes.zeroLength; + } + }); + + return { + inset, + color, + values, + }; } diff --git a/packages/core/ui/styling/font-common.ts b/packages/core/ui/styling/font-common.ts index 63e6743aa0..3472f6c59b 100644 --- a/packages/core/ui/styling/font-common.ts +++ b/packages/core/ui/styling/font-common.ts @@ -83,7 +83,7 @@ export namespace FontVariationSettings { return null; } - const excluded = ['normal', 'inherit', 'initial', 'revert', 'revert-layer', 'unset']; + const excluded = ['normal', 'revert-layer']; const variationSettingsValue: string = fontVariationSettings.trim(); if (excluded.indexOf(variationSettingsValue.toLowerCase()) !== -1) { diff --git a/packages/core/ui/styling/font-interfaces.ts b/packages/core/ui/styling/font-interfaces.ts index 6fd7180c19..50d5afb0bc 100644 --- a/packages/core/ui/styling/font-interfaces.ts +++ b/packages/core/ui/styling/font-interfaces.ts @@ -1,5 +1,7 @@ -export type FontStyleType = 'normal' | 'italic'; -export type FontWeightType = '100' | '200' | '300' | 'normal' | '400' | '500' | '600' | 'bold' | '700' | '800' | '900' | number; +import { CoreTypes } from '../enums'; + +export type FontStyleType = 'normal' | 'italic' | CoreTypes.CSSWideKeywords; +export type FontWeightType = '100' | '200' | '300' | 'normal' | '400' | '500' | '600' | 'bold' | '700' | '800' | '900' | number | CoreTypes.CSSWideKeywords; export interface ParsedFont { fontStyle?: FontStyleType; diff --git a/packages/core/ui/styling/style-properties.ts b/packages/core/ui/styling/style-properties.ts index 6d097189bc..e82b0e696f 100644 --- a/packages/core/ui/styling/style-properties.ts +++ b/packages/core/ui/styling/style-properties.ts @@ -1,4 +1,4 @@ -import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty } from '../core/properties'; +import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty, isCssWideKeyword } from '../core/properties'; import { Style } from './style'; import { Color } from '../../color'; @@ -25,11 +25,11 @@ interface ShorthandPositioning { 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') { - return b == 'auto'; + if (a == 'auto' || isCssWideKeyword(a)) { + return b == 'auto' || isCssWideKeyword(b); } - if (b == 'auto') { + if (b == 'auto' || isCssWideKeyword(b)) { return false; } @@ -53,7 +53,7 @@ function equalsCommon(a: CoreTypes.PercentLengthType, b: CoreTypes.PercentLength } function convertToStringCommon(length: CoreTypes.LengthType | CoreTypes.PercentLengthType): string { - if (length == 'auto') { + if (length == 'auto' || isCssWideKeyword(length)) { return 'auto'; } @@ -70,7 +70,7 @@ function convertToStringCommon(length: CoreTypes.LengthType | CoreTypes.PercentL } function toDevicePixelsCommon(length: CoreTypes.PercentLengthType, auto: number = Number.NaN, parentAvailableWidth: number = Number.NaN): number { - if (length == 'auto') { + if (length == 'auto' || isCssWideKeyword(length)) { return auto; } if (typeof length === 'number') { diff --git a/packages/core/ui/text-base/index.android.ts b/packages/core/ui/text-base/index.android.ts index fa785b7046..c3b5aacca4 100644 --- a/packages/core/ui/text-base/index.android.ts +++ b/packages/core/ui/text-base/index.android.ts @@ -16,7 +16,7 @@ import { layout } from '../../utils'; import { SDK_VERSION } from '../../utils/constants'; import { isString, isNullOrUndefined } from '../../utils/types'; import { accessibilityIdentifierProperty } from '../../accessibility/accessibility-properties'; -import { testIDProperty } from '../../ui/core/view'; +import { isCssWideKeyword, testIDProperty } from '../../ui/core/view'; export * from './text-base-common'; @@ -289,7 +289,6 @@ export class TextBase extends TextBaseCommon { [textTransformProperty.setNative](value: CoreTypes.TextTransformType) { if (value === 'initial') { this.nativeTextViewProtected.setTransformationMethod(this._defaultTransformationMethod); - return; } @@ -411,9 +410,6 @@ export class TextBase extends TextBaseCommon { [textDecorationProperty.setNative](value: number | CoreTypes.TextDecorationType) { switch (value) { - case 'none': - this.nativeTextViewProtected.setPaintFlags(0); - break; case 'underline': this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG); break; @@ -424,7 +420,11 @@ export class TextBase extends TextBaseCommon { this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG); break; default: - this.nativeTextViewProtected.setPaintFlags(value); + if (value === 'none' || isCssWideKeyword(value)) { + this.nativeTextViewProtected.setPaintFlags(0); + } else { + this.nativeTextViewProtected.setPaintFlags(value); + } break; } } diff --git a/packages/core/ui/text-base/text-base-common.ts b/packages/core/ui/text-base/text-base-common.ts index 437328b830..4f42c1d254 100644 --- a/packages/core/ui/text-base/text-base-common.ts +++ b/packages/core/ui/text-base/text-base-common.ts @@ -280,7 +280,7 @@ export function getClosestPropertyValue(property: CssProperty, span: } } -const textAlignmentConverter = makeParser(makeValidator('initial', 'left', 'center', 'right', 'justify')); +const textAlignmentConverter = makeParser(makeValidator('left', 'center', 'right', 'justify')); export const textAlignmentProperty = new InheritedCssProperty({ name: 'textAlignment', cssName: 'text-align', @@ -289,7 +289,7 @@ export const textAlignmentProperty = new InheritedCssProperty(makeValidator('initial', 'none', 'capitalize', 'uppercase', 'lowercase')); +const textTransformConverter = makeParser(makeValidator('none', 'capitalize', 'uppercase', 'lowercase')); export const textTransformProperty = new InheritedCssProperty({ name: 'textTransform', cssName: 'text-transform', @@ -318,7 +318,7 @@ export const textStrokeProperty = new InheritedCssProperty(makeValidator('initial', 'normal', 'nowrap')); +const whiteSpaceConverter = makeParser(makeValidator('normal', 'nowrap')); export const whiteSpaceProperty = new InheritedCssProperty({ name: 'whiteSpace', cssName: 'white-space', @@ -328,7 +328,7 @@ export const whiteSpaceProperty = new InheritedCssProperty(makeValidator('clip', 'ellipsis', 'initial', 'unset')); +const textOverflowConverter = makeParser(makeValidator('clip', 'ellipsis')); export const textOverflowProperty = new CssProperty({ name: 'textOverflow', cssName: 'text-overflow', From 59e1f54bb48e26a3b573d57629f55b01cc3a8239 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 23 Feb 2025 22:06:13 +0200 Subject: [PATCH 2/7] chore: Small import type change --- packages/core/ui/core/view/index.android.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 3fc09cf5e4..8a384fcbaf 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -1,5 +1,5 @@ // Definitions. -import { type Point, type CustomLayoutView as CustomLayoutViewDefinition, type Position } from '.'; +import type { Point, CustomLayoutView as CustomLayoutViewDefinition, Position } from '.'; import type { GestureTypes, GestureEventData } from '../../gestures'; import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common'; From d2b271b3733d04fe8ea57b579686f977c93c32f6 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 26 Feb 2025 02:49:29 +0200 Subject: [PATCH 3/7] fix: Use empty string for reset only if property is css --- packages/core/ui/core/properties/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/ui/core/properties/index.ts b/packages/core/ui/core/properties/index.ts index 07fe63c665..e45fb72522 100644 --- a/packages/core/ui/core/properties/index.ts +++ b/packages/core/ui/core/properties/index.ts @@ -77,7 +77,7 @@ function isCssUnsetValue(value: any): boolean { } function isResetValue(value: any): boolean { - return value === unsetValue || value === '' || value === 'initial' || value === 'inherit' || isCssUnsetValue(value); + return value === unsetValue || value === 'initial' || value === 'inherit' || isCssUnsetValue(value); } export function _printUnregisteredProperties(): void { @@ -672,7 +672,7 @@ export class CssProperty implements CssProperty { return; } - const reset = isResetValue(newValue); + const reset = isResetValue(newValue) || newValue === ''; let value: U; if (reset) { @@ -758,7 +758,7 @@ export class CssProperty implements CssProperty { return; } - const reset = isResetValue(newValue); + const reset = isResetValue(newValue) || newValue === ''; let value: U; if (reset) { @@ -957,16 +957,16 @@ export class CssAnimationProperty implements CssAnimationPro const oldValue = this[computedValue]; const oldSource = this[computedSource]; const wasSet = oldSource !== ValueSource.Default; - const reset = isResetValue(boxedValue); + const reset = isResetValue(boxedValue) || boxedValue === ''; if (reset) { - this[symbol] = unsetValue; + this[symbol] = boxedValue; if (this[computedSource] === propertySource) { // Fallback to lower value source. - if (this[styleValue] !== unsetValue) { + if (!isResetValue(this[styleValue])) { this[computedSource] = ValueSource.Local; this[computedValue] = this[styleValue]; - } else if (this[cssValue] !== unsetValue) { + } else if (!isResetValue(this[cssValue])) { this[computedSource] = ValueSource.Css; this[computedValue] = this[cssValue]; } else { @@ -1130,7 +1130,7 @@ export class InheritedCssProperty extends CssProperty return; } - const reset = isResetValue(boxedValue); + const reset = isResetValue(boxedValue) || boxedValue === ''; const currentValueSource: number = this[sourceKey] || ValueSource.Default; if (reset) { From a353867fd7245c9812991fe7bc25f679994eaa0c Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 26 Feb 2025 03:23:53 +0200 Subject: [PATCH 4/7] test: Added automated tests --- apps/automated/src/test-runner.ts | 3 + .../src/ui/styling/css-keywords-tests.ts | 132 ++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 apps/automated/src/ui/styling/css-keywords-tests.ts diff --git a/apps/automated/src/test-runner.ts b/apps/automated/src/test-runner.ts index 6990aca1a1..33a4c6858e 100644 --- a/apps/automated/src/test-runner.ts +++ b/apps/automated/src/test-runner.ts @@ -176,6 +176,9 @@ allTests['STYLE'] = styleTests; import * as visualStateTests from './ui/styling/visual-state-tests'; allTests['VISUAL-STATE'] = visualStateTests; +import * as cssKeywordsTests from './ui/styling/css-keywords-tests'; +allTests['CSS-KEYWORDS'] = cssKeywordsTests; + import * as valueSourceTests from './ui/styling/value-source-tests'; allTests['VALUE-SOURCE'] = valueSourceTests; diff --git a/apps/automated/src/ui/styling/css-keywords-tests.ts b/apps/automated/src/ui/styling/css-keywords-tests.ts new file mode 100644 index 0000000000..6d39f64dbd --- /dev/null +++ b/apps/automated/src/ui/styling/css-keywords-tests.ts @@ -0,0 +1,132 @@ +import * as helper from '../../ui-helper'; +import * as TKUnit from '../../tk-unit'; +import { Color, Button, StackLayout, unsetValue } from '@nativescript/core'; + +export var test_value_after_initial = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { background-color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.backgroundColor = new Color('#0000FF'); + TKUnit.assertEqual(btn.backgroundColor.hex, '#0000FF', 'backgroundColor property'); + btn.backgroundColor = 'initial'; + TKUnit.assertEqual(btn.backgroundColor, undefined, 'backgroundColor property'); +}; + +export var test_value_Inherited_after_initial = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.color = new Color('#0000FF'); + TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); + btn.color = 'initial'; + TKUnit.assertEqual(btn.color, undefined, 'color property'); +}; + +export var test_value_after_unset = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { background-color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.backgroundColor = new Color('#0000FF'); + TKUnit.assertEqual(btn.backgroundColor.hex, '#0000FF', 'backgroundColor property'); + btn.backgroundColor = 'unset'; + TKUnit.assertEqual(btn.backgroundColor, undefined, 'backgroundColor property'); +}; + +export var test_value_Inherited_after_unset = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.color = new Color('#0000FF'); + TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); + btn.color = 'unset'; + TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); +}; + +export var test_value_after_revert = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { background-color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.backgroundColor = new Color('#0000FF'); + TKUnit.assertEqual(btn.backgroundColor.hex, '#0000FF', 'backgroundColor property'); + btn.backgroundColor = 'revert'; + TKUnit.assertEqual(btn.backgroundColor, undefined, 'backgroundColor property'); +}; + +export var test_value_Inherited_after_revert = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.color = new Color('#0000FF'); + TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); + btn.color = 'revert'; + TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); +}; + +// TODO: Add missing inherit support for non-inherited properties +export var test_value_after_inherit = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { background-color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.backgroundColor = new Color('#0000FF'); + TKUnit.assertEqual(btn.backgroundColor.hex, '#0000FF', 'backgroundColor property'); + btn.backgroundColor = 'inherit'; + TKUnit.assertEqual(btn.backgroundColor, undefined, 'backgroundColor property'); +}; + +export var test_value_Inherited_after_inherit = function () { + let page = helper.getCurrentPage(); + let btn = new Button(); + let testStack = new StackLayout(); + + page.css = 'StackLayout { color: #FF0000; }'; + + page.content = testStack; + testStack.addChild(btn); + + btn.color = new Color('#0000FF'); + TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); + btn.color = 'inherit'; + TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); +}; From 33af944d380214c720fda28b1b82e160feb58f0d Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 26 Feb 2025 03:43:28 +0200 Subject: [PATCH 5/7] fix: Re-added box-shadow unset check --- packages/core/ui/styling/css-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/styling/css-utils.ts b/packages/core/ui/styling/css-utils.ts index 409f5509f6..dfd701f927 100644 --- a/packages/core/ui/styling/css-utils.ts +++ b/packages/core/ui/styling/css-utils.ts @@ -40,7 +40,7 @@ export function parseCSSShorthand(value: string): { const parts = value.trim().split(PARTS_RE); const first = parts[0]; - if (['', 'none'].includes(first)) { + if (['', 'none', 'unset'].includes(first)) { return null; } From 16416967864ab55a9c26ef963fb5ee5e0c97415c Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 26 Feb 2025 03:48:27 +0200 Subject: [PATCH 6/7] chore: Type casts --- apps/automated/src/ui/styling/css-keywords-tests.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/automated/src/ui/styling/css-keywords-tests.ts b/apps/automated/src/ui/styling/css-keywords-tests.ts index 6d39f64dbd..032ae9ae5c 100644 --- a/apps/automated/src/ui/styling/css-keywords-tests.ts +++ b/apps/automated/src/ui/styling/css-keywords-tests.ts @@ -30,7 +30,7 @@ export var test_value_Inherited_after_initial = function () { btn.color = new Color('#0000FF'); TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); - btn.color = 'initial'; + (btn as any).color = 'initial'; TKUnit.assertEqual(btn.color, undefined, 'color property'); }; @@ -62,7 +62,7 @@ export var test_value_Inherited_after_unset = function () { btn.color = new Color('#0000FF'); TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); - btn.color = 'unset'; + (btn as any).color = 'unset'; TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); }; @@ -94,7 +94,7 @@ export var test_value_Inherited_after_revert = function () { btn.color = new Color('#0000FF'); TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); - btn.color = 'revert'; + (btn as any).color = 'revert'; TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); }; @@ -127,6 +127,6 @@ export var test_value_Inherited_after_inherit = function () { btn.color = new Color('#0000FF'); TKUnit.assertEqual(btn.color.hex, '#0000FF', 'color property'); - btn.color = 'inherit'; + (btn as any).color = 'inherit'; TKUnit.assertEqual(btn.color.hex, '#FF0000', 'color property'); }; From 3b993044c9dee2d8a6b6c104d3ce080d8a7f8bfd Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Wed, 26 Feb 2025 03:59:16 +0200 Subject: [PATCH 7/7] chore: Removed unused import --- apps/automated/src/ui/styling/css-keywords-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/automated/src/ui/styling/css-keywords-tests.ts b/apps/automated/src/ui/styling/css-keywords-tests.ts index 032ae9ae5c..e463ced6aa 100644 --- a/apps/automated/src/ui/styling/css-keywords-tests.ts +++ b/apps/automated/src/ui/styling/css-keywords-tests.ts @@ -1,6 +1,6 @@ import * as helper from '../../ui-helper'; import * as TKUnit from '../../tk-unit'; -import { Color, Button, StackLayout, unsetValue } from '@nativescript/core'; +import { Color, Button, StackLayout } from '@nativescript/core'; export var test_value_after_initial = function () { let page = helper.getCurrentPage();