Repository: NativeScript/NativeScript
File: tns-core-modules/ui/styling/style-properties.ts (TypeScript)

Code before:

function isFloatValueConverter(value: string): number {
    let 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 {
    let 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 });
opacityProperty.register(Style);

export const elevationProperty = new CssAnimationProperty<Style, number>({ name: "elevation", cssName: "elevation", defaultValue: 0, valueConverter: parseInt });
elevationProperty.register(Style);

export const androidPressedZProperty = new CssAnimationProperty<Style, number>({ name: "androidPressedZ", cssName: "android-pressed-z", defaultValue: 0, valueConverter: parseInt });
androidPressedZProperty.register(Style);

export const colorProperty = new InheritedCssProperty<Style, Color>({ name: "color", cssName: "color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) });
colorProperty.register(Style);

export const fontInternalProperty = new CssProperty<Style, Font>({ name: "fontInternal", cssName: "_fontInternal", defaultValue: Font.default });
fontInternalProperty.register(Style);

export const fontFamilyProperty = new InheritedCssProperty<Style, string>({
    name: "fontFamily", cssName: "font-family", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
        let currentFont = target.fontInternal;
        if (currentFont.fontFamily !== newValue) {
            const newFont = currentFont.withFontFamily(newValue);
            target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
        }
    }
});
fontFamilyProperty.register(Style);

export const fontSizeProperty = new InheritedCssProperty<Style, number>({
    name: "fontSize", cssName: "font-size", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
        let currentFont = target.fontInternal;

Review comment (suggestion):
No need to declare the new properties as CssAnimationProperty (or CssProperty for that matter). With NativeScript all simple properties (i.e. declared as Property<...>) can be set through CSS by converting their naming to kebab case (looks-like-this).

Our guideline would be to declare CssProperty only for properties that are actually part of the CSS specification (and yes, there is an `elevation` property in css spec but it does something completely different).

Also, as obviously we haven't communicated this feature very well (the ability to set simple properties via CSS) we are currently discussing an enhancement that should make this more obvious (and also clearly mark simple vs css properties when used in CSS) -- by using something like CSS vendor prefixes (-ns- in our case).

Currently you can do this in css:
``` css
.btn {
    color: red;
    elevation: 8;
    some-other-simple-prop: 0;
}
```
We are considering also enabling this syntax:
``` css
.btn {
    color: red;
    -ns-elevation: 8;
    -ns-some-other-simple-prop: 0;
}
```

Code after:

function isFloatValueConverter(value: string): number {
    let 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 {
    let 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 });
opacityProperty.register(Style);

export const colorProperty = new InheritedCssProperty<Style, Color>({ name: "color", cssName: "color", equalityComparer: Color.equals, valueConverter: (v) => new Color(v) });
colorProperty.register(Style);

export const fontInternalProperty = new CssProperty<Style, Font>({ name: "fontInternal", cssName: "_fontInternal", defaultValue: Font.default });
fontInternalProperty.register(Style);

export const fontFamilyProperty = new InheritedCssProperty<Style, string>({
    name: "fontFamily", cssName: "font-family", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
        let currentFont = target.fontInternal;
        if (currentFont.fontFamily !== newValue) {
            const newFont = currentFont.withFontFamily(newValue);
            target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
        }
    }
});
fontFamilyProperty.register(Style);

export const fontSizeProperty = new InheritedCssProperty<Style, number>({
    name: "fontSize", cssName: "font-size", affectsLayout: isIOS, valueChanged: (target, oldValue, newValue) => {
        let currentFont = target.fontInternal;
        if (currentFont.fontSize !== newValue) {
            const newFont = currentFont.withFontSize(newValue);
            target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont;
        }
    },
    valueConverter: (v) => parseFloat(v)